zoukankan      html  css  js  c++  java
  • 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发送和接收部分时,可以说必须要使用上述封装,否则等到造成数据不完整或者不一致后再去找问题,可能就麻烦了。这个是必不可少滴。

  • 相关阅读:
    LeetCode 231. 2的幂
    LeetCode 50. Pow(x, n)
    LeetCode 80. 删除有序数组中的重复项 II
    LeetCode 26. 删除有序数组中的重复项
    LeetCode 88. 合并两个有序数组
    LeetCode 781. 森林中的兔子
    在linux下使用 Fitilink 3D Webcam (18e3:5031)
    ros tf2使用示例
    使用QtCreator作为ROS调试器
    linux基于file的logger
  • 原文地址:https://www.cnblogs.com/wxyy/p/3308857.html
Copyright © 2011-2022 走看看