1. 程式人生 > >C語言網路程式設計(三)建立套接字通訊TCP

C語言網路程式設計(三)建立套接字通訊TCP

為了實現伺服器與客戶機的通訊,伺服器和客戶機都必須建立套接字。伺服器與客戶機的工作原理可以用下面的過程來描述。
(1)伺服器先用socket函式來建立一個套接字,用這個套接字完成通訊的監聽。
(2)用bind函式來繫結一個埠號和IP地址。因為本地計算機可能有多個網絡卡和IP,每一個IP有多個埠。需要指定一個IP和埠進行監聽。
(3)伺服器呼叫listen函式,使伺服器的這個埠和IP處於監聽狀態,等待客戶機的連線。
(4)客戶機用socket函式建立一個套接字,設定遠端IP和埠。
(5)客戶機呼叫connect函式連線遠端計算機指定的埠。
(6)伺服器用accept函式來接受遠端計算機的連線,建立起與客戶機之間的通訊。
(7)建立連線以後,客戶機用write函式向socket中寫入資料。也可以用read函式讀取伺服器傳送來的資料。
(8)伺服器用read函式讀取客戶機發送來的資料,也可以用write函式來發送資料。
(9)完成通訊以後,用close函式關閉socket連線。
客戶機與伺服器建立面向連線的套接字進行通訊,請求與響應過程可用下圖來表示。

 

繫結埠

繫結埠指的是將套接字與指定的埠相連。用socket函式建立起一個套接字以後,需要用bind函式在這個套接字上面繫結一個埠。
提示:只有套接字建立後才能夠執行埠繫結操作。 
繫結埠函式bind:函式bind可以將一個埠繫結到一個已經建立的socket上,這個函式的使用方法如下所示。
int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
引數列表中,sockfd是已經建立的socket編號。sockaddr是一個指向sockaddr結構體型別的指標。sockaddr的定義方法如下所示。
struct sockaddr
{
  unsigned short int sa_family;
  char sa_data[14];
};
這個結構體的成員含義如下所示。
sa_family:為呼叫socket()時的domain引數,即AF_xxxx值。
sa_data:最多使用14個字元長度,含有IP地址與埠的資訊。
如果建立socket時使用的是AF_INET引數,則sockaddr結構體的定義方法如下所示。
struct sockaddr_in
{
  unsigned short int sin_family;
  uint16_t sin_port;
  struct in_addr sin_addr;
  unsigned char sin_zero[8];
};

結構體的成員addr也是一個結構體,定義方式如下所示。
struct in_addr
{
  uint32_t s_addr;
};
在這些結構體中,成員變數的作用與含義如下所示。
sin_family:即為sa_family,為呼叫socket()時的domain引數。
sin_port:使用的埠號。
sin_addr.s_addr:IP 地址。
sin_zero:未使用的欄位,填充為0。
引數addrlen是my_addr的長度,可以用sizeof函式來取得。函式可以把指定的IP與埠繫結到已經建立的socket上面。
如果繫結成功,則返回0。失敗則返回-1。函式可能發生下面的錯誤,可以用error捕獲發生的錯誤。
EBADF:引數sockfd不是一個合法的socket。
EACCESS:許可權不足。
ENOTSOCK:引數sockfd是檔案描述詞,而不是socket。

監聽

所謂監聽,指的是socket的埠處於等待狀態,如果有客戶端有連線請求,這個埠會接受這個連線。連線指的是客戶端向服務端傳送一個通訊申請,服務端會響應這個請求。本節將講述socket的監聽與連線操作。
等待監聽函式listen:伺服器必需等待客戶端的連線請,listen函式用於實現監聽等待功能。這個函式的使用方法如下所示。
int listen(int s,int backlog);
在引數列表中,s是已經建立的socket。backlog是能同時處理的最大連線請求。如果超過這個數目,客戶端將會接收到ECONNREFUSED拒絕連線的錯誤。
注意:listen並未真正的接受連線,只是設定socket的狀態為listen模式。真正接受客戶端連線的是accept函式。通常情況下,listen函式會在socket、bind函式之後呼叫,然後才會呼叫accept函式。
listen函式只適用SOCK_STREAM或SOCK_SEQPACKET的socket型別。如果socket為AF_INET則引數backlog最大值可設至128,即最多可以同時接受128個客戶端的請求。
如果呼叫成功,則函式的返回值為0,失敗返回-1。函式可能發生如下所示的錯誤,可以用errno來捕獲發生的錯誤。
EBADF:引數sockfd不是一個合法的socket。
EACCESS:許可權不足。
EOPNOTSUPP:指定的socket不支援listen模式。

接受連線

接受連線函式accept:伺服器處於監聽狀態時,如果獲得客戶機的請求,會將這個請求放在等待佇列中。當系統空閒時,將處理客戶機的連線請求。接受連線請求的函式是accept,這個函式的使用方法如下所示。
int accept(int s,struct sockaddr * addr,int * addrlen);
在引數列表中,s是處於監聽狀態的socket。addr是一個sockaddr結構體型別的指標,系統會把遠端主機的這些資訊儲存到這個結構體指標上。addrlen是sockaddr的記憶體長度,可以用sizeof函式來取得。
注意:當accept函式接受一個連線時,會返回一個新的socket編號。以後的資料傳輸與讀取就是通過這個新的socket編號來處理。原來引數中的socket可以繼續使用。接受連線以後,遠端主機的地址和埠資訊將會儲存到addr所指的結構體內。如果處理失敗,返回值為-1。
函式可能產生下面的錯誤,可以用error來捕獲發生的錯誤。
EBADF:引數s不是一個合法的socket程式碼。
EFAULT:引數addr指標指向無法存取的記憶體空間。
ENOTSOCK 引數s為一檔案描述詞,而不是一個socket。
EOPNOTSUPP:指定的socket不是SOCK_STREAM。
EPERM:防火牆拒絕這一個連線。
ENOBUFS:系統的緩衝記憶體不足。
ENOMEM:核心記憶體不足。

接受資訊

建立套接字並完成網路連線以後,可以把資訊傳送到遠端主機上,這個過程就是資訊的傳送。遠端主機發送來的資訊,本地主機需要進行接收處理。本節將講述這種面向連線的套接字資訊傳送與接收操作。
資料接收函式recv:函式recv可以接收遠端主機發送來的資料,並把這些資料儲存到一個數組中。該函式的使用方法如下所示。
int recv(int s,void *buf,int len,unsigned int flags);
在引數列表中,s是已經建立的socket。buf是一個指標,指向一個數組。接收到的資料會儲存到這個陣列上。len是陣列的長度,可以用sizeof函式來取得。flags一般設定為0,其他可能的賦值與含義如下所示。
MSG_OOB:接收以out-of-band送出的資料。
MSG_PEEK:返回來的資料並不會在系統內刪除,如果再呼叫recv時會返回相同的資料內容。
MSG_WAITALL:強迫接收到len大小的資料後才能返回,除非有錯誤或訊號產生。
MSG_NOSIGNAL:此操作被SIGPIPE訊號中斷。
recv函式如果接收到資料,會把這些資料儲存在buf指標指向的記憶體中,然後返回接收到字元的個數。如果發生錯誤則會返回-1。函式可能發生下面這些錯誤,可以用errno來捕獲錯誤。
EBADF:引數s不是一個合法的socket。
EFAULT:引數中的指標指向了無法讀取的記憶體空間。
ENOTSOCK:引數s是檔案描述詞,而不是一個socket。
EINTR:被訊號所中斷。
EAGAIN:此動作會阻斷程序,但引數s的socket不可阻斷。
ENOBUFS:系統的緩衝記憶體不足。
ENOMEM:核心記憶體不足
EINVAL:引數不正確。

傳送資訊

資訊傳送函式send:用connect函式連線到遠端計算機以後,可以用send函式將資訊傳送到對方的計算機。這個函式的使用方法如下所示。
int send(int s,const void * msg,int len,unsigned int flags);
在引數列表中,s是已經建立的socket。msg是需要傳送資料的指標。len是需要傳送資料的長度。這個長度可以用sizeof函式來取得。引數flags一般設定為0,可能的賦值與含義如下所示。
MSG_OOB:傳送的資料以out-of-band的方式送出。
MSG_DONTROUTE:取消路由表查詢。
MSG_DONTWAIT:設定為不可阻斷傳輸。
MSG_NOSIGNAL:此傳輸不可被SIGPIPE 訊號中斷。。
如果資料成功,函式會返回已經傳送的字元個數。否則會返回-1。函式可能發生下面這些錯誤,可以用errno來捕獲函式的錯誤。
EBADF:引數s不是一個正確的socket。
EFAULT:引數中的指標指向了不可讀取的記憶體空間。
ENOTSOCK:引數s是一個檔案,而不是一個socket。
EINTR:被訊號所中斷。
EAGAIN:此操作會中斷程序,但socket不允許中斷。
ENOBUFS:系統的緩衝記憶體不足。
ENOMEM:核心記憶體不足。
EINVAL:傳給系統呼叫的引數不正確。

DEMO:

static void
tcp_server_example()
{
	int sockfd,rs,accept_sockfd,addr_len;
	char buff[SERVER_MAX_BUFF];
	char *msg="hello world!\n";
	addr_len=sizeof(struct sockaddr);
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0)
	{
		return ;
	}
	struct sockaddr_in addr_in;
	addr_in.sin_family=AF_INET;
	addr_in.sin_port=htons(SERVER_PORT);//網路使用的整數與程式使用的不同,需要轉換
	addr_in.sin_addr.s_addr=htonl(INADDR_ANY);
	bzero(&(addr_in.sin_zero),8);
	rs=bind(sockfd,(struct sockaddr*)&addr_in,sizeof(addr_in));
	if(rs<0)
	{
		printf("bind error!\n");
		return;
	}
	rs=listen(sockfd,SERVER_MAX_CONECTION);
	if(rs<0)
	{
		printf("listen error!\n");
		return;
	}
	accept_sockfd=accept(sockfd,(struct sockaddr*)&addr_in,&addr_len);
	while(1)
	{
		recv(accept_sockfd,buff,SERVER_MAX_BUFF,0);
		printf("recv data:%s\n",buff);
		send(accept_sockfd,msg,strlen(msg),0);
	}
	return ;
}

客戶端連線 

請求連線函式connet:所謂請求連線,指的是客戶機需要向伺服器傳送資訊時,需要傳送一個連線請求。connect函式可以完成這項功能,這個函式的使用方法如下所示。
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
在引數列表中,sockfd是已經建立的socket。serv_addr是一個結構體指標,指向一個sockaddr結構體。這個結構體儲存著遠端伺服器的IP與埠資訊。addrlen是sockaddr結構體的記憶體長度,可以用sizeof函式來獲取。sockaddr結構體的定義見前面小節中的bind函式所述。
函式會將本地的socket連線到serv_addr所指定的伺服器IP與埠。如果連線成功,返回值為0,連線失敗則返回-1。函式可能發生下面的錯誤,可以用error來捕獲發生的錯誤。
EBADF:引數sockfd 不是一個合法的socket。
EFAULT:引數serv_addr指標指向了一個無法讀取的記憶體空間。
ENOTSOCK:引數sockfd是檔案描述詞,而不是一個正常的socket。
EISCONN:引數sockfd的socket已經處於連線狀態。
ECONNREFUSED:連線要求被伺服器拒絕。
ETIMEDOUT:需要的連線操作超過限定時間仍未得到響應。
ENETUNREACH:無法傳送資料包至指定的主機。
EAFNOSUPPORT:sockaddr結構的sa_family不正確。
EALREADY:socket不能阻斷,但是以前的連線操作還未完成。

DEMO:

static void
tcp_client_example()
{
	int sockfd,val,len,rs=-1,fromlen;
	char buff[5000];
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0)
	{
		return;
	}
	//傳送TCP請求
	struct sockaddr_in addr_in;
	addr_in.sin_family=AF_INET;
	addr_in.sin_port=htons(80);//網路使用的整數與程式使用的不同,需要轉換
	addr_in.sin_addr.s_addr=inet_addr("1.1.1.1");
	bzero(&(addr_in.sin_zero),8);
	char *msg="GET /play.php HTTP/1.1\r\n\
Host: xxx.xxx.com\r\n\
Connection: keep-alive\r\n\
Cache-Control: max-age=0\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n\
Upgrade-Insecure-Requests: 1\r\n\
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36\r\n\
Accept-Language: zh-CN,zh;q=0.8\r\n\
Cookie: \r\n\r\n";
	rs=connect(sockfd,(struct sockaddr *)&addr_in,sizeof(struct sockaddr));
	if(rs<0)
	{
		printf("connect error!\n");
		return ;
	}
	rs=send(sockfd,msg,strlen(msg),0);
	printf("send %d bytes\n",rs);
	rs=recv(sockfd,buff,sizeof(buff),0);
	printf("recv %d bytes\n",rs);
	printf("recv data:\n%s",buff);
	return ;
}


其他技巧參考部落格:

http://blog.csdn.net/maopig/article/details/17193021