基於Linux的TCP網絡編程
一.Linux下TCP編程框架
TCP網絡編程的流程包含服務器和客戶端兩種模式。服務器模式創建一個服務程序,等待客戶端用戶的連接,接收到用戶的連接請求後,根據用戶的請求進行處理;客戶端模式則根據目的服務器的地址和端口進行連接,向服務器發送請求並對服務器的響應進行數據處理。
1.服務器端程序包括
? 建立套接字( socket())
? 套接字與端口的綁定(bind())
? 設置服務器的偵聽連接(listen())
? 接收客戶端連接(accept())
? 接收和發送數據(send(),recv())
? 關閉套接字(close())
2.說明
1>套接字初始化過程中,根據用戶對套接字的需求來確定套接字的選項。按照用戶定義的網絡類型,協議類型和具體的協議標號等參數來定以socket()函數。系統根據用戶的需求生成一個套接字文件描述符供用戶使用。
2>套接字與端口的綁定過程中,將套接字與一個地址結構進行綁定。綁定之後,套接字所代表IP地址和端口地址及協議類型等參數按照綁定值進行操作。
3>由於一個服務器需要滿足多個客戶端的連接請求,而服務器在某個時間僅能處理有限個數的客戶端連接請求,所以服務器需要設置服務器端排隊隊列的長度。
4>在客戶端發送連接請求之後,服務器需要接收客戶端的連接,然後才能進行其他的處理。
5>在服務器接收客戶端請求之後,可以從套接字文件描述符中讀取數據或者向文件描述符發送數據。接收數據後服務器按照定義的規則對數據進行處理,並將結果發送給客戶端。
6>當服務器處理完數據,要結束與客戶端的通信過程的時候,需要關閉套接字連接
2.客戶端程序包括
? 建立套接字(socket())
? 連接服務器(connect())
? 讀寫網絡數據(send(),recv())
? 關閉套接字(close())
3.服務器端和客戶端程序的區別
客戶端程序和服務器端程序不同之處是客戶端在建立套接字之後可以不進行地址綁定,而是直接連接服務器端。
服務器端有listen()和accept()兩個函數,而客戶端不需要這兩個函數。
二.基於Linux的TCP套接字函數
1. socket
1> 函數原型:
int socket(intdomain,int type,int protocol)
2> 函數功能:
函數socket()用於創建一個套接字描述符。
3> 形參:
? domain:用於指定創建套接字所使用的協議族,在頭文件
<linux/socket.h>中定義。有時候程序中會使用PF_INET,在頭文件中AF_INET和PF_INET的數值是一致的。
常見的協議族如下:
AF_UNIX:創建只在本機內進行通信的套接字。
AF_INET:使用IPv4TCP/IP協議
AF_INET6:使用IPv6 TCP/IP協議
說明:
AF_UNIX只能用於單一的UNIX系統進程間通信,而AF_INET是針對Interne的,因而可以允許在遠程主機之間通信。一般把它賦爲AF_INET。
? type:指明套接子通信的類型,對應的參數如下
SOCK_STREAM:創建TCP流套接字
SOCK_DGRAM:創建UDP數據報套接字
SOCK_RAW:創建原始套接字
? protocol:指定某個協議的特定類型
參數protocol通常設置爲0,表示通過參數domain指定的協議族和參數type指定的套接字類型來確定使用的協議。當爲原始套接字時,系統無法唯一的確定協議,此時就需要使用使用該參數指定所使用的協議。
4> 返回值:執行成功後返回一個新創建的套接字;若有錯誤發生則返回一個-1,錯誤代碼存入errno中。
5> 舉例:調用socket函數創建一個UDP套接字
int sock_fd;
sock_fd =socket(AF_INET,SOCK_DGRAM,0);
if(sock_fd<0){
perror(“socket”);
exit(1);
}
2. bind
1> 函數原型:
int bind(intsockfd,struct sockaddr *my_addr,socklen_t addrlen)
2> 函數功能
函數bind()的作用是將一個套接字文件描述符與地址和端口綁定。
3> 形參:
? sockfd:sockfd是調用socket函數返回的文件描述符;
? addrlen是sockaddr結構的長度。
? my_addr:是一個指向sockaddr結構的指針,它保存着本地套接字的地址(即端口和IP地址)信息。不過由於系統兼容性的問題,一般不使用這個結構,而使用另外一個結構(structsockaddr_in)來代替
4> 套接字地址結構:
(1)structsockaddr:
結構struct sockaddr定義了一種通用的套接字地址,它在
sys/socket.h 中定義。
struct sockaddr{
unsigned short sa_family;/*地址類型,AF_XXX*/
char sa_data[14];/*14字節的協議地址*/
}
a. sin_family:表示地址類型,對於使用TCP/IP協議進行的網絡編程,該值只能是AF_INET.
b. sa_data:存儲具體的協議地址。
(2)sockaddr_in
每種協議族都有自己的協議地址格式,TCP/IP協議組的地址格式爲結構體structsockaddr_in,它在netinet/in.h頭文件中定義。
structsockaddr_in{
unsigned short sin_family;/*地址類型*/
unsigned short sin_port;/*端口號*/
struct in_addr sin_addr;/*IP地址*/
unsigned char sin_zero[8];/*填充字節,一般賦值爲0*/
}
a. sin_family:表示地址類型,對於使用TCP/IP協議進行的網絡編程,該值只能是AF_INET.
b. sin_port:是端口號
c. sin_addr:用來存儲32位的IP地址。
d. 數組sin_zero爲填充字段,一般賦值爲0.
e. structin_addr的定義如下:
structin_addr{
unsigned long s_addr;
}
結構體sockaddr的長度爲16字節,結構體sockaddr_in的長度爲16字節。可以將參數my_addr的sin_addr設置爲INADDR_ANY而不是某個確定的IP地址就可以綁定到任何網絡接口。對於只有一IP地址的計算機,INADDR_ANY對應的就是它的IP地址;對於多宿主主機(擁有多個網卡),INADDR_ANY表示本服務器程序將處理來自所有網絡接口上相應端口的連接請求
5> 返回值:
函數成功後返回0,當有錯誤發生時則返回-1,錯誤代碼存入errno中。
6>舉例:調用socket函數創建一個UDP套接字
structsockaddr_in addr_serv,addr_client;/*本地的地址信息*/
memset(&serv_addr,0,sizeof(structsockaddr_in));
addr_serv.sin_family=AF_INET;/*協議族*/
addr_serv.sin_port=htons(SERV_PORT);/*本地端口號*/
addr_serv.sin_addr.s_addr=htonl(INADDR_ANY); /*任意本地地址*/
/*套接字綁定*/
if(bind(sock_fd,(structsockaddr*)&addr_serv),sizeof(struct sockaddr_in)) <0)
{
perror(“bind”);
exit(1);
}
3. 監聽本地端口listen()
1>函數功能:函數listen()用來初始化服務器可連接隊列,服務器處理客戶端連接請求的時候是順序處理的,同一時間僅能處理一個客戶端連接。當多個客戶端的連接請求同時到來的時候,服務器並不是同時處理,而是將不能處理的客戶端連接請求放到等待隊列中,這個隊列的長度由listen()函數來定義。
2>函數原型:
#includ<sys/socket.h>
int listen(intsockfd,int backlog);
3>形參
? sockfd:sockfd是調用socket函數返回的文件描述符
? backlog:指定該連接隊列的最大長度。如果連接隊列已經達到最大,之後的連接請求被服務器拒絕。大多數系統的設置爲20,可以將其設置修改爲5或者10,根據系統可承受負載或者應用程序的需求來確定。
4>返回值:當listen()函數成功運行時,返回值爲0;當運行失敗時,它的返回值爲-1,錯誤代碼存入errno中。
5>.listen()函數的例子:
#defineSERV_PORT 3000
int main(intargc,char *argv[])
{
int sock_fd;
structsockaddr_in addr_serv,addr_client;/*本地的地址信息*/
sock_fd =socket(AF_INET,SOCK_DGRAM,0);
if(sock_fd<0){
perror(“socket”);
exit(1);
}
memset(&serv_addr,0,sizeof(structsockaddr_in));
addr_serv.sin_family=AF_INET;/*協議族*/
addr_serv.sin_port=htons(SERV_PORT);/*本地端口號*/
addr_serv.sin_addr.s_addr=htonl(INADDR_ANY); /*任意本地地址*/
/*套接字綁定*/
if(bind(sock_fd,(structsockaddr*)&addr_serv),sizeof(struct sockaddr_in)) <0)
{
perror(“bind”);
exit(1);
}
//設置服務器偵聽隊列的長度
if(listen(sock_fd,5)<0){
perror(“listen”);
exit(1);
}
4. accept(接收一個網絡請求)
1>函數功能:
當一個客戶端的連接請求到達服務器主機偵聽的端口時,此時客戶端的連接會在隊列中等待,知道使用服務器處理接收請求。
函數accept()成功執行後,會返回一個新的套接口文件描述符來表示客戶端的連接,客戶端連接的信息可以通過這個新描述符來獲得。因此當服務器成功處理客戶端的請求連接後,會有兩個文件描述符,老的文件描述符表示客戶端的連接,函數send()和recv()通過新的文件描述符進行數據收發。
2>函數原型:
#include<sys/types.h>
#include<sys/socket.h>
int accept(intsock_fd,struct sockaddr*addr,socklen_t *addrlen);
3>形參
? sock_fd:是由函數socket創建,經函數bind綁定到本地某一端口上,然後通過函數listen轉化而來的監聽套接字。
? addr:用來保存發起連接請求的主機的地址和端口。
? addrlen是addr 所指向的結構體的大小。
4>返回值:accept()函數的返回值是新連接的客戶端套接字文件描述符,與客戶端之間的通信是通過accept()返回的新套接字文件描述符來進行的,而不是通過建立套接字時的文件描述符。如��accept()函數發生錯誤,accept()會返回-1,通過errno可以得到錯誤值。
5>如果參數sock_fd所指定的套接字被設置爲阻塞方式(Linux下的默認方式),且連接請求隊列爲空,則accept()將被阻塞直到有連接請求到此爲止;如果參數s所指定的套接字被設置爲非阻塞方式,如果隊列爲空,accept將立即返回-1,errno被設置爲EAGAIN.
6>實例:
int client_fd;
int client_len;
structsockaddr_in client_addr;
client_len =sizeof(struct sockaddr_in);
client_fd =accept(sock_fd,(struct sockaddr *)&client_addr,&client_len);
if(conn_fd<0){
perror(“accept”);
exit(1);
}
5. connect(連接目標網絡服務器)
1>函數功能:
客戶端在建立套接字之後,不需要進行地址綁定,就可以直接連接服務器。連接服務器的函數爲connect(),此函數連接指定參數的服務器,例如IP地址,端口號。
如果是TCP編程,則connect()函數用於服務器發出連接請求,服務器的IP地址和端口號由參數serv_addr指定。
如果是UDP編程,則connect函數並不建立真正的連接,它只是告訴內核與該套接字進行通信的目的地址(由第二個參數指定),只有該目的地址發來的數據纔會被該socket接收。調用connect函數的好處是不必在每次發送和接收數據時都指定目的地址。
2>函數原型:
#include<sys/types.h>
#include<sys/socket.h>
int connect(intsock_fd,struct sockaddr *serv_addr,socklen_taddrlen);
3>形參:
? sock_fd:建立套接字時返回的套接字文件描述符,調用socket()返回的。
? serv_addr:是一個指向數據結構sockaddr的指針,其中包括客戶端需要連接的服務器的目的IP地址和端口號。
? addrlen:表示了第二了參數的大小,可以使用sizeof(struct sockaddr)
4>執行成功後返回0,有錯誤發生則返回-1,錯誤代碼存入errno中。
5>實例:
int sock_fd;
structsockaddr_in serv_addr;
if(-1 == (sock_fd == socket(AF_INET,SOCK_STREAM,0))){
printf(“Error: Unable to createsocket(%i)…\n”,errno);
perror(“sockets”);
exit(1);
}
memset(&serv_addr,0,sizeof(structsockaddr_in));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(DEST_PORT);
serv_addr.sin_addr.s_addr=inet(DEST_IP_ADDRESS);
if(-1==connect(sock_fd,(struct sockaddr *)&serv_add,sizeof(struct sockaddr))){
printf(“Error:unable to the establishconnection to socket(%i)…\n”,errno);
perror(“socks”);
close(sock_fd);
exit(1);
}
6. send(發送數據)
1>函數功能:函數send用來在TCP套接字上發送數據,send只能對處於連接狀態的套接字使用。
2>函數原型
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(intconn_fd,const void *msg,size_t len, int flags);
3>函數形參:
? conn_fd:爲已建立好連接的套接字描述符,即調用accept()函數後返回的套接字描述符。
? msg:存放發送數據的緩衝區。
? len:發送緩衝區的長度
? flags:爲控制選項,一般設置爲0,或取以下值:
2 MSG_OOB:在指定的套接字上發送帶外數據(out-of-band data),該類型的套接字必須支持帶外數據(如:SOCK_STREAM).
2 MSG_DONTROUTE:通過最直接的路徑發送數據,而忽略下層協議的路由設置。
4>返回值:
執行成功返回實際發送數據的字節數,出錯則返回-1,錯誤代碼存入errno中。
執行成功只是說明數據寫入套接字的緩衝區中,並不表示數據已經成功地通過網絡發送到目的地。
5>實例:
#define BUFFERSIZE 1500
char send_buf[BUFFERSIZE];
……
if(send(conn_fd,send_buf,len,0)<0){
perror(“send”);
exit(1);
}
7. recv(接收數據)
1>函數功能:recv()用來TCP套接字上接收數據。函數recv從指定的套接字描述符上接收數據並保存到指定buf中。
2>函數原型
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(intconn_fd,void *buf,size_t len,int flags);
3>函數形參:
? conn_fd:爲已建立好連接的套接字描述符,即調用accept()函數後返回的套接字描述符
? buf:接收緩衝區
? len:接收緩衝區的大小
? flags:爲控制選項,一般設置爲0或取以下數值
2 MSG_OOB:請求接收帶外數據
2 MSG_PEEK:只查看數據而不讀出
2 MSG_WAITALL:只在接收緩衝區滿時才返回。
4>函數返回值
函數執行成功返回接收到的數據字節數,出錯返回-1,錯誤代碼存入errno中。
5>實例:
#define BUFFERSIZE 1500
charrecv_buf[BUFFERSIZE];
……
if(recv(conn_fd,recv_buf,sizeof(recv_buf),0)<0){
perror(“recv”);
exit(1);
}
8. close
1>函數原型:
int close(int fd);
2>函數功能:
函數close用來關閉一個套接字描述符。
3>函數形參:
? 參數fd爲一個套接字描述符。
4>返回值:
執行成功返回0,出錯則返回-1.錯誤代碼存入errno中。
說明:close()函數的頭文件是#include<unistd.h>.
三.基於Linux的TCP套接字編程實例
1.實例程序分爲服務器端和客戶端,客戶端把Hello tigerjibo發送給服務器端;服務器端接收到字符串後,發送接收到的總字符串個數給客戶端;
2.服務器端程序:
1#include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<errno.h>
5
6
7 #include<sys/types.h>
8 #include<sys/socket.h>
9 #include<unistd.h>//close()
10 #include<netinet/in.h>//struct sockaddr_in
11 #include<arpa/inet.h>//inet_ntoa
12 #define QUEUE_LINE 12
13 #define SOURCE_PORT 8000
14
15 #define SOURCE_IP_ADDRESS "192.168.1.6"
16
17 void process_info(int s)
18 {
19 int recv_num;
20 int send_num;
21 char recv_buf[50];
22 char send_buf[50];
23 while(1){
24 printf("begin recv:\n");
25 recv_num = recv(s,recv_buf,sizeof(recv_buf),0);
26 if(recv_num <0){
27 perror("recv");
28 exit(1);
29 } else {
30 recv_buf[recv_num] = '\0';
31 printf("recv sucessful:%s\n",recv_buf);
32 }
33 sprintf(send_buf,"recv %d numbers bytes\n",recv_num);
34 printf("begin send\n");
35 send_num = send(s,send_buf,sizeof(send_buf),0);
36 if (send_num < 0){
37 perror("sned");
38 exit(1);
39 } else {
40 printf("send sucess\n");
41 }
42 }
43 }
44 int main()
45 {
46 int sock_fd,conn_fd;
47 int client_len;
48 pid_t pid;
49 struct sockaddr_inaddr_serv,addr_client;
50 sock_fd =socket(AF_INET,SOCK_STREAM,0);
51 if(sock_fd < 0){
52 perror("socket");
53 exit(1);
54 } else {
55 printf("sock sucessful\n");
56 }
57 //初始化服務器端地址
58 memset(&addr_serv,0,sizeof(addr_serv));
59 addr_serv.sin_family =AF_INET;
60 addr_serv.sin_port =htons(SOURCE_PORT);
61 addr_serv.sin_addr.s_addr=inet_addr(SOURCE_IP_ADDRESS);
62 client_len =sizeof(struct sockaddr_in);
63 if(bind(sock_fd,(struct sockaddr *)&addr_serv,sizeof(structsockaddr_in))<0){
64 perror("bind");
65 exit(1);
66 } else {
67 printf("bind sucess\n");
68 }
69 if(listen(sock_fd,QUEUE_LINE) < 0){
70 perror("listen");
71 exit(1);
72 } else {
73 printf("listen sucessful\n");
74 }
75 while(1){
76 printf("begin accept:\n");
77 conn_fd = accept(sock_fd,(struct sockaddr *)&addr_client,&client_len);
78 if(conn_fd < 0){
79 perror("accept");
80 exit(1);
81 }
82 printf("accept a newclient,ip:%s\n",inet_ntoa(addr_client.sin_addr));
83 pid = fork();
84 if(0 == pid){ //子進程
85 close(sock_fd);//在子進程中關閉服務器的偵聽
86 process_info(conn_fd);//處理信息
87 } else {
88 close(conn_fd);//在父進程中關閉客戶端的連接
89 }
90 }
91
92 }
3.客戶端程序:
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<errno.h>
5
6 #include<sys/types.h>
7 #include<sys/socket.h>
8 #include<unistd.h>//close()
9 #include<netinet/in.h>//struct sockaddr_in
10 #include<arpa/inet.h>//inet_ntoa
11
12 #define DEST_PORT 8000
13 #define DEST_IP_ADDRESS "192.168.1.6"
14
15 /*客戶端的處理過程*/
16 void process_info(int s)
17 {
18 int send_num;
19 int recv_num;
20 charsend_buf[]="tigerjibo";
21 char recv_buf[50];
22 while(1){
23 printf("begin send\n");
24 send_num = send(s,send_buf,sizeof(send_buf),0);
25 if (send_num < 0){
26 perror("send");
27 exit(1);
28 } else {
29 printf("send sucess:%s\n",send_buf);
30 }
31 printf("begin recv:\n");
32 recv_num = recv(s,recv_buf,sizeof(recv_buf),0);
33 if(recv_num < 0){
34 perror("recv");
35 exit(1);
36 } else {
37 recv_buf[recv_num]='\0';
38 printf("recv sucess:%s\n",recv_buf);
39 }
40 }
41 }
42 int main(int argc,char *argv[])
43 {
44 int sock_fd;
45 struct sockaddr_inaddr_serv;//服務器端地址
46
47 sock_fd =socket(AF_INET,SOCK_STREAM,0);
48 if(sock_fd < 0){
49 perror("sock");
50 exit(1);
51 } else {
52 printf("sock sucessful:\n");
53 }
54 memset(&addr_serv,0,sizeof(addr_serv));
55 addr_serv.sin_family =AF_INET;
56 addr_serv.sin_port= htons(DEST_PORT);
57 addr_serv.sin_addr.s_addr = inet_addr(DEST_IP_ADDRESS);
58 if( connect(sock_fd,(structsockaddr *)&addr_serv,sizeof(struct sockaddr)) < 0){
59 perror("connect");
60 printf("connect (%d)\n",errno);
61 exit(1);
62 } else {
63 printf("connect sucessful\n");
64 }
65 process_info(sock_fd);
66 close(sock_fd);
67 }