socket网络编程快速上手(二)——细节问题(2)

2.TCP数据包接收问题

  对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收打印,长度都为1000。但是,事实上并不是这样,发送打印基本不会有什么问题(只是一般情况,如果发生调度或者其他情况,有可能导致差别,因此也要注意封装),接收打印却不是固定的,下面是测试代码:

测试客户端程序:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <string.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 #include <netdb.h>
 9 
10 #define  PORT        1234
11 #define  MAXDATASIZE 1000
12 
13 int main(int argc, char *argv[])
14 {
15     int  sockfd, num;
16     char  buf[MAXDATASIZE + 1] = {0};
17     struct sockaddr_in server;
18     int   iCount = 0;
19     
20     if (argc != 2) 
21     {
22         printf("Usage:%s <IP Address>
", argv[0]);
23         exit(1);
24     }
25     
26     if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
27     {
28         printf("socket()error
");
29         exit(1);
30     }
31     bzero(&server, sizeof(server));
32     server.sin_family = AF_INET;
33     server.sin_port = htons(PORT);
34     server.sin_addr.s_addr = inet_addr(argv[1]);
35     if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
36     {
37         printf("connect()error
");
38         exit(1);
39     }
40 
41     while (1)
42     {
43         memset(buf, 0, sizeof(buf));
44         if ((num = recv(sockfd, buf, MAXDATASIZE,0)) == -1)
45         {
46             printf("recv() error
");
47             exit(1);
48         }
49         buf[num - 1]='';
50         printf("%dth Recv Length: %d
", iCount++, num);
51     }
52     
53     close(sockfd);
54     
55     return 0;
56 }
TCP客户端

测试服务器程序:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 #include <arpa/inet.h>
 9 #include <signal.h>
10 
11 #define  PORT         1234
12 #define  BACKLOG      5
13 #define  MAXDATASIZE  1000
14 
15 int main()
16 {
17     int  listenfd, connectfd;
18     struct  sockaddr_in server;
19     struct  sockaddr_in client;
20     socklen_t  addrlen;
21     char    szbuf[MAXDATASIZE] = {0};
22     int     iCount = 0;
23     int     iLength = 0;
24     
25     if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
26     {
27         perror("Creating  socket failed.");
28         exit(1);
29     }
30     
31     int opt = SO_REUSEADDR;
32     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
33     
34     bzero(&server, sizeof(server));
35     server.sin_family = AF_INET;
36     server.sin_port = htons(PORT);
37     server.sin_addr.s_addr = htonl(INADDR_ANY);
38     if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 
39     {
40         perror("Bind()error.");
41         exit(1);
42     }   
43     if (listen(listenfd, BACKLOG) == -1)
44     {
45         perror("listen()error
");
46         exit(1);
47     }
48     
49     addrlen = sizeof(client);
50     if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 
51     {
52         perror("accept()error
");
53         exit(1);
54     }
55     printf("You got a connection from cient's ip is %s, prot is %d
", inet_ntoa(client.sin_addr), htons(client.sin_port));
56 
57     memset(szbuf, 'a', sizeof(szbuf));
58     while (iCount < 1000)
59     {
60         iLength = send(connectfd, szbuf, sizeof(szbuf), 0);
61         printf("%dth Server Send Length %d
", iCount++, iLength);
62     }
63     
64     printf("send over!
");
65     sleep(10);
66     
67     close(connectfd);
68     close(listenfd);
69     
70     return 0;
71 }
TCP服务器程序

客户端接收打印片段如下:

  1 936th Recv Length: 1000
  2 937th Recv Length: 1000
  3 938th Recv Length: 1000
  4 939th Recv Length: 1000
  5 940th Recv Length: 1000
  6 941th Recv Length: 1000
  7 942th Recv Length: 384
  8 943th Recv Length: 616
  9 944th Recv Length: 1000
 10 945th Recv Length: 1000
 11 946th Recv Length: 1000
 12 947th Recv Length: 1000
 13 948th Recv Length: 1000
 14 949th Recv Length: 1000
 15 950th Recv Length: 1000
 16 951th Recv Length: 1000
 17 952th Recv Length: 1000
 18 953th Recv Length: 1000
 19 954th Recv Length: 1000
 20 955th Recv Length: 1000
 21 956th Recv Length: 1000
 22 957th Recv Length: 1000
 23 958th Recv Length: 1000
 24 959th Recv Length: 1000
 25 960th Recv Length: 1000
 26 961th Recv Length: 1000
 27 962th Recv Length: 384
 28 963th Recv Length: 616
 29 964th Recv Length: 1000
 30 965th Recv Length: 1000
 31 966th Recv Length: 1000
 32 967th Recv Length: 1000
 33 968th Recv Length: 1000
 34 969th Recv Length: 1000
 35 970th Recv Length: 1000
 36 971th Recv Length: 1000
 37 972th Recv Length: 1000
 38 973th Recv Length: 1000
 39 974th Recv Length: 1000
 40 975th Recv Length: 1000
 41 976th Recv Length: 1000
 42 977th Recv Length: 1000
 43 978th Recv Length: 1000
 44 979th Recv Length: 1000
 45 980th Recv Length: 1000
 46 981th Recv Length: 1000
 47 982th Recv Length: 384
 48 983th Recv Length: 616
 49 984th Recv Length: 1000
 50 985th Recv Length: 1000
 51 986th Recv Length: 1000
 52 987th Recv Length: 1000
 53 988th Recv Length: 1000
 54 989th Recv Length: 1000
 55 990th Recv Length: 1000
 56 991th Recv Length: 1000
 57 992th Recv Length: 1000
 58 993th Recv Length: 1000
 59 994th Recv Length: 1000
 60 995th Recv Length: 1000
 61 996th Recv Length: 1000
 62 997th Recv Length: 1000
 63 998th Recv Length: 1000
 64 999th Recv Length: 1000
 65 1000th Recv Length: 1000
 66 1001th Recv Length: 1000
 67 1002th Recv Length: 384
 68 1003th Recv Length: 616
 69 1004th Recv Length: 1000
 70 1005th Recv Length: 1000
 71 1006th Recv Length: 1000
 72 1007th Recv Length: 1000
 73 1008th Recv Length: 1000
 74 1009th Recv Length: 1000
 75 1010th Recv Length: 1000
 76 1011th Recv Length: 1000
 77 1012th Recv Length: 1000
 78 1013th Recv Length: 1000
 79 1014th Recv Length: 1000
 80 1015th Recv Length: 1000
 81 1016th Recv Length: 1000
 82 1017th Recv Length: 1000
 83 1018th Recv Length: 1000
 84 1019th Recv Length: 1000
 85 1020th Recv Length: 1000
 86 1021th Recv Length: 1000
 87 1022th Recv Length: 384
 88 1023th Recv Length: 616
 89 1024th Recv Length: 1000
 90 1025th Recv Length: 1000
 91 1026th Recv Length: 1000
 92 1027th Recv Length: 1000
 93 1028th Recv Length: 1000
 94 1029th Recv Length: 1000
 95 1030th Recv Length: 1000
 96 1031th Recv Length: 1000
 97 1032th Recv Length: 1000
 98 1033th Recv Length: 1000
 99 1034th Recv Length: 1000
100 1035th Recv Length: 1000
101 1036th Recv Length: 1000
102 1037th Recv Length: 1000
103 1038th Recv Length: 1000
104 1039th Recv Length: 1000
105 1040th Recv Length: 1000
106 1041th Recv Length: 1000
107 1042th Recv Length: 384
108 1043th Recv Length: 616
109 1044th Recv Length: 1000
110 1045th Recv Length: 1000
111 1046th Recv Length: 1000
112 1047th Recv Length: 1000
113 1048th Recv Length: 1000
114 1049th Recv Length: 1000
115 1050th Recv Length: 1000
客户端接收打印片段

服务器发送打印片段整理时发现丢失了,大家可以自己试试,没有问题。

 不难发现,服务器发送正常,客户端在接收时却和我们想的很不一样,但发送和接收的总数据量是一致的,就是说数据没有丢失。如果编程者认为TCP情况下发送和接收的数据长度都一致的,那就极有可能在代码中体现出这一思想,最终出现问题。

  其实,这就是所谓的“粘包”现象,Stevens很明确地已经指出了这一点,他说,“UDP是长度固定的、无连接的不可靠报文传输;TCP是有序、可靠、双向的面向连接字节流”。他没说TCP是长度固定的,有没有?当然我更倾向于这样的理解,UDP是面向报文的,报文在传输时是不能被分割的(只是从应用层来看);TCP是面向字节流的,接收多少数据完全取决于发送和接收的速度了,有多少数据recv就返回多少,数据长度并不和send保持一致,也没这个必要。

  那么这个问题怎么解决呢?其实,我们只要将recv封装一层就可以了,那就是我们熟悉的readn函数(该函数不是系统调用),代码如下:

 1 int readn(int connfd, void *vptr, int n)
 2 {
 3     int    nleft;
 4     int    nread;
 5     char *ptr;
 6     struct timeval     select_timeout;
 7     fd_set rset;
 8 
 9     ptr = vptr;
10     nleft = n;
11 
12     while (nleft > 0)
13     {
14         FD_ZERO(&rset);
15         FD_SET(connfd, &rset);
16         select_timeout.tv_sec = 5;
17         select_timeout.tv_usec = 0;
18         if (select(connfd+1, &rset, NULL, NULL, &select_timeout) <= 0)
19         {
20             return -1;
21         }
22         if ((nread = recv(connfd, ptr, nleft, 0)) < 0)
23         {
24             if(errno == EINTR)
25             {
26                 nread = 0;
27             }
28             else
29             {
30                 return -1;
31             }
32         }
33         else if (nread == 0)
34         {
35             break;
36         }
37         nleft -= nread;
38         ptr   += nread;
39     }
40     return(n - nleft);
41 }
readn

相应的也有writen函数

 1 int writen(int connfd, void *vptr, size_t n)
 2 {
 3     int nleft, nwritten;
 4      char    *ptr;
 5 
 6     ptr = vptr;
 7     nleft = n;
 8 
 9     while(nleft>0)
10     {
11         if((nwritten = send(connfd, ptr, nleft, 0)) == ERROR)
12         {
13             if(errnoGet() == EINTR)
14             {
15                 //PRT_ERR(("EINTR
"));
16                 nwritten = 0;
17             }
18             else 
19             {
20                 //PRT_ERR(("Send() error, 0x%x
", errnoGet()));
21                 return ERROR;
22             }
23         }
24         nleft -= nwritten;
25         ptr   += nwritten;
26     }
27 
28     return(n);
29 }
writen

函数中为什么对EINTR进行处理后面再说,也是必不可少的。

  在处理TCP发送和接收部分时,可以说必须要使用上述封装,否则等到造成数据不完整或者不一致后再去找问题,可能就麻烦了。这个是必不可少滴。

原文地址:https://www.cnblogs.com/wxyy/p/3308857.html