• 6.2 socket 流协议与粘包


      TCP IP协议是流协议,对上层协议来讲是没有边界的,主机A发送两个消息M1和M2,如下图所示:

    主机A发送了M1和M2,主机B在接收时有4种情况:

    1、先收了M1,又收了M2

    2、M1、M2一起收到了

    3、M1和M2的一部分一起收到的,又收到了M2的一部分

    4、先收到了M1的一部分,然后M1的下一部分和M2一起收到

    说明:

      tcp字节流无边界

      udp消息是基于数据报的,是有边界的,可以不处理

      对等方一次读操作,不能保证完全把消息读完

      对方接收数据包的个数是不确定的

    应用程序发数据时,先把数据写到socket的缓冲区里面,缓冲区的大小也是有规定的,当缓冲区写到一定程度,这时候TCP IP协议开始往对等方发数据。IP层有MSS最大数据报限制,如果数据包大于了MSS,则IP层会对数据分片,到对等方再进行组合。在链路层有MTU最大传输单元限制。

    产生粘包的原因:

      1、套接字本身有缓冲区(发送缓冲区、接受缓冲区)

      2、tcp传送端的mss大小限制

      3、链路层的MTU限制,如果数据包大于MTU,则要在IP层进行分片,导致消息分割

      4、tcp的流量控制和拥塞控制,也可能导致粘包

      5、tcp延迟发送机制

     我们前几篇博客中的read函数是有bug的,但是我们的实验都是在局域网(在一个机器上)进行的,包传输较快,所以没有凸显出来。也就是在局域网上传输较快,先发送的包也先接收到了,没有出现粘包的现象。但是在公网传输时,延迟较大,如果我们不对流式数据包不进行处理,这时可能就会出现我们上面说的粘包现象了。真正的商用软件一定会进行粘包处理。

      包之间没有边界,我们可以人为的造边界。

      目前有以下处理方法:

      1、在包之间加 ,ftp就是这样处理的。

      2、在包之间加自定义报文。例如,在报文头之前加4个字节,指示后面的报文大小。

      3、定长包

      4、更复杂的应用层协议

     我们使用在包头加上四字节自定义报文的方式解决粘包问题,直接给出如下的程序:

    服务器端:

      1 #include <sys/types.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <errno.h>
      7 #include <arpa/inet.h>
      8 #include <sys/socket.h>
      9 #include <netinet/in.h>
     10 #include <sys/socket.h>
     11 #include <netinet/ip.h> /* superset of previous */
     12 
     13 ssize_t readn(int fd, void *buf, size_t count)
     14 {
     15     size_t nleft = count;
     16     ssize_t nread;
     17     
     18     char *bufp = (char*)buf;
     19     
     20     while(nleft > 0)
     21     {
     22         if( (nread = read(fd, bufp, nleft)) < 0 )
     23         {
     24             if(errno == EINTR)
     25             {
     26                 continue;
     27             }
     28             
     29             return -1;
     30         }
     31         else if(nread == 0)
     32         {
     33             return count - nleft;
     34         }
     35         
     36         bufp += nread;
     37         nleft -= nread;
     38     }
     39     
     40     return count;
     41 }
     42 
     43 ssize_t writen(int fd, const void *buf, size_t count)
     44 {
     45     size_t nleft = count;
     46     ssize_t nwritten;
     47     
     48     char *bufp = (char*)buf;
     49     
     50     while(nleft > 0)
     51     {
     52         if( (nwritten = write(fd, bufp, nleft)) < 0)
     53         {
     54             if(errno == EINTR)
     55             {
     56                 continue;
     57             }
     58             
     59             return -1;
     60         }
     61         else if(nwritten == 0)
     62         {
     63             continue;
     64         }
     65         
     66         bufp += nwritten;
     67         nleft -= nwritten;
     68     }
     69     
     70     return count;
     71 }
     72 
     73 struct packet 
     74 {
     75     int len;
     76     char buf[1024];
     77 };
     78 
     79 int main()
     80 {
     81     int sockfd = 0;
     82     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     83     
     84     if(sockfd == -1)
     85     {
     86         perror("socket error");
     87         exit(0);
     88     }
     89     
     90     struct sockaddr_in addr;
     91     addr.sin_family = AF_INET;
     92     addr.sin_port = htons(8001);
     93     inet_aton("192.168.31.128", &addr.sin_addr);
     94     //addr.sin_addr.s_addr = inet_addr("192.168.6.249");
     95     //addr.sin_addr.s_addr = INADDR_ANY;
     96     
     97     int optval = 1;
     98     if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
     99     {
    100         perror("setsockopt error");
    101         exit(0);    
    102     }
    103     
    104     if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    105     {
    106         perror("bind error");
    107         exit(0);
    108     }
    109     
    110     if(listen(sockfd, SOMAXCONN) < 0)
    111     {
    112         perror("listen error");
    113         exit(0);
    114     }
    115     
    116     struct sockaddr_in peeraddr;
    117     socklen_t peerlen;
    118     
    119     pid_t pid;
    120     int conn = 0;
    121     
    122     while(1)
    123     {
    124         conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
    125         if(conn == -1)
    126         {
    127             perror("accept error");
    128             exit(0);
    129         }
    130     
    131         char *p = NULL;
    132         int peerport = 0;
    133         p = inet_ntoa(peeraddr.sin_addr);
    134         peerport = ntohs(peeraddr.sin_port);
    135     
    136         printf("peeraddr = %s
     peerport = %d
    ", p, peerport);
    137         
    138         pid = fork();
    139         if(pid == -1)
    140         {
    141             perror("fork error");
    142             exit(0);
    143         }
    144         
    145         if(pid == 0)
    146         {
    147             struct packet recvbuf;
    148             int n;
    149             int ret = 0;
    150             
    151             close(sockfd);
    152             
    153             while(1)
    154             {
    155                 memset(&recvbuf, 0, sizeof(struct packet));
    156                 ret = readn(conn, &recvbuf.len, 4);
    157         
    158                 if(ret == -1)
    159                 {
    160                     printf("client closed 
    ");
    161                     exit(0);
    162                 }
    163                 else if(ret < 4)
    164                 {
    165                     perror("read error");
    166                     break;
    167                 }
    168                 
    169                 n = ntohl(recvbuf.len);
    170                 ret = readn(conn, recvbuf.buf, n);
    171                 
    172                 if(ret == -1)
    173                 {
    174                     perror("readn error");
    175                     exit(0);
    176                 }
    177                 else if(ret < n)
    178                 {
    179                     printf("client closed
    ");
    180                     break;
    181                 }
    182                 
    183                 fputs(recvbuf.buf, stdout);
    184         
    185                 writen(conn, &recvbuf, 4+n);
    186             }
    187         }
    188         
    189         close(conn);
    190         
    191     }
    192     
    193     close(conn);
    194     close(sockfd);
    195     
    196     return 0;
    197 }

    客户端:

      1 #include <sys/types.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <errno.h>
      7 #include <arpa/inet.h>
      8 #include <sys/socket.h>
      9 #include <netinet/in.h>
     10 #include <sys/socket.h>
     11 #include <netinet/ip.h> /* superset of previous */
     12 
     13 ssize_t readn(int fd, void *buf, size_t count)
     14 {
     15     size_t nleft = count;
     16     ssize_t nread;
     17     
     18     char *bufp = (char*)buf;
     19     
     20     while(nleft > 0)
     21     {
     22         if( (nread = read(fd, bufp, nleft)) < 0 )
     23         {
     24             if(errno == EINTR)
     25             {
     26                 continue;
     27             }
     28             
     29             return -1;
     30         }
     31         else if(nread == 0)
     32         {
     33             return count - nleft;
     34         }
     35         
     36         bufp += nread;
     37         nleft -= nread;
     38     }
     39     
     40     return count;
     41 }
     42 
     43 ssize_t writen(int fd, const void *buf, size_t count)
     44 {
     45     size_t nleft = count;
     46     ssize_t nwritten;
     47     
     48     char *bufp = (char*)buf;
     49     
     50     while(nleft > 0)
     51     {
     52         if( (nwritten = write(fd, bufp, nleft)) < 0)
     53         {
     54             if(errno == EINTR)
     55             {
     56                 continue;
     57             }
     58             
     59             return -1;
     60         }
     61         else if(nwritten == 0)
     62         {
     63             continue;
     64         }
     65         
     66         bufp += nwritten;
     67         nleft -= nwritten;
     68     }
     69     
     70     return count;
     71 }
     72 
     73 struct packet 
     74 {
     75     int len;
     76     char buf[1024];
     77 };
     78 
     79 int main()
     80 {
     81     int sockfd = 0;
     82     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     83     
     84     struct sockaddr_in addr;
     85     addr.sin_family = AF_INET;
     86     addr.sin_port = htons(8001);
     87     inet_aton("192.168.31.128", &addr.sin_addr);
     88     //addr.sin_addr.s_addr = inet_addr("192.168.31.128");
     89     
     90     if( connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1 )
     91     {
     92         perror("connect error");
     93         exit(0);
     94     }
     95     
     96     struct packet sendbuf;
     97     struct packet recvbuf;
     98     memset(&recvbuf, 0, sizeof(struct packet));
     99     memset(&sendbuf, 0, sizeof(struct packet));
    100     int ret = 0;
    101     int n = 0;
    102     while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
    103     {
    104         n = strlen(sendbuf.buf);
    105         sendbuf.len = htonl(n);
    106         
    107         writen(sockfd, &sendbuf, 4+n);
    108         
    109         ret = readn(sockfd, &recvbuf.len, 4);
    110         
    111         if(ret == -1)
    112         {
    113             perror("readn error");
    114             exit(0);
    115         }
    116         else if(ret < 4)
    117         {
    118             printf("server close
    ");
    119         }
    120         
    121         n = ntohl(recvbuf.len);
    122         
    123         ret = readn(sockfd, recvbuf.buf, n);
    124         
    125         if(ret == -1)
    126         {
    127             perror("readn error");
    128             exit(0);
    129         }
    130         else if(ret < n)
    131         {
    132             printf("client close
    ");
    133             break;
    134         }
    135         
    136         fputs(recvbuf.buf, stdout);
    137         memset(&recvbuf, 0, sizeof(struct packet));
    138         memset(&sendbuf, 0, sizeof(struct packet));
    139 
    140     }
    141     
    142     close(sockfd);
    143     
    144     return 0;
    145 }

    最重要的就是readn和writen函数,readn先读取4字节,然后根据这四字节的内容确定继续读取后面数据的大小。writen是写4+n字节,n是真正有用的数据,4字节是包头,如果写的过程中不出错,则writen一定会将4+n字节写完。如果writen返回0,那么可能是真的没有写进去,也可能是对端已经关闭,这时候我们重写一次,如果是对端关闭,则这次写writen就会返回小于0的数了。返回小于零的数可能是由于对端关闭,也可能是被中断唤醒,所以我们要判断一下errno,如果是被中断的,则再次尝试写入,如果是对端关闭,则writen就直接出错返回了(返回-1)。

    下面我们使用第二种解决方案,在数据包的后面加上' ',这样的话接收端就要解析数据查找' ',我们之前用的都是read读数据,如果解析时还用read的话,就要一个字节一个字节的读取并解析,需要多次调用read,效率很低,为了提高效率,这时候可以选择recv函数。原型如下:

    ssize_t  recv(int s, void *buf, size_t len, int flags)

    与read相比,recv函数只能用于套接字文件描述符。而且多了一个flags参数。flags常用的参数有以下两个:

    MSG_OOB:带外数据,紧急指针

    MSG_PEEK:数据包的“偷窥”,提前预读,当设置成“偷窥”模式时,可以判断数据包的长度和内容。相当于提前读缓冲区,但是并没有将数据清走。read函数读的时候也会清缓冲区。

    用fgets读键盘,客户端从键盘输入数据时默认会带一个' ',这是fgets自动加的。

    示例程序如下:

    服务器端:

      1 #include <sys/types.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <errno.h>
      7 #include <arpa/inet.h>
      8 #include <sys/socket.h>
      9 #include <netinet/in.h>
     10 #include <sys/socket.h>
     11 #include <netinet/ip.h> /* superset of previous */
     12 
     13 ssize_t readn(int fd, void *buf, size_t count)
     14 {
     15     size_t nleft = count;
     16     ssize_t nread;
     17     
     18     char *bufp = (char*)buf;
     19     
     20     while(nleft > 0)
     21     {
     22         if( (nread = read(fd, bufp, nleft)) < 0 )
     23         {
     24             if(errno == EINTR)
     25             {
     26                 continue;
     27             }
     28             
     29             return -1;
     30         }
     31         else if(nread == 0)
     32         {
     33             return count - nleft;
     34         }
     35         
     36         bufp += nread;
     37         nleft -= nread;
     38     }
     39     
     40     return count;
     41 }
     42 
     43 ssize_t writen(int fd, const void *buf, size_t count)
     44 {
     45     size_t nleft = count;
     46     ssize_t nwritten;
     47     
     48     char *bufp = (char*)buf;
     49     
     50     while(nleft > 0)
     51     {
     52         if( (nwritten = write(fd, bufp, nleft)) < 0)
     53         {
     54             if(errno == EINTR)
     55             {
     56                 continue;
     57             }
     58             
     59             return -1;
     60         }
     61         else if(nwritten == 0)
     62         {
     63             continue;
     64         }
     65         
     66         bufp += nwritten;
     67         nleft -= nwritten;
     68     }
     69     
     70     return count;
     71 }
     72 
     73 ssize_t recv_peek(int sockfd, void *buf, size_t len)
     74 {
     75     while(1)
     76     {
     77         int ret = recv(sockfd, buf, len, MSG_PEEK);
     78         if(ret == -1 && errno == EINTR)
     79             continue;
     80         return ret;
     81     }
     82 }
     83 
     84 ssize_t readline(int sockfd, void *buf, size_t maxline)
     85 {
     86     int ret;
     87     int nread;
     88     char *bufp = (char*)buf;
     89     int nleft = maxline;
     90     
     91     while(1)
     92     {
     93         ret = recv_peek(sockfd, bufp, nleft);
     94         if(ret < 0)
     95         {
     96             return ret;
     97         }
     98         else if(ret == 0)
     99         {
    100             return ret;
    101         }
    102         
    103         nread = ret;
    104         int i;
    105         for(i = 0; i < nread; i++)
    106         {
    107             if(bufp[i] == '
    ')
    108             {
    109                 ret = readn(sockfd, bufp, i+1);
    110                 if(ret != i+1)
    111                 {
    112                     perror("readn error");
    113                     exit(0);
    114                 }
    115                 
    116                 return ret;
    117             }
    118         }
    119         
    120         if(nread > nleft)
    121         {
    122             perror("FAILURE");
    123             exit(0);
    124         }
    125         
    126         nleft -= nread;
    127         ret = readn(sockfd, bufp, nread);
    128         if(ret != nread)
    129         {
    130             perror("readn error");
    131             exit(0);
    132         }
    133         bufp += nread;
    134     }
    135     
    136     return -1;
    137 }
    138 
    139 
    140 int main()
    141 {
    142     int sockfd = 0;
    143     sockfd = socket(AF_INET, SOCK_STREAM, 0);
    144     
    145     if(sockfd == -1)
    146     {
    147         perror("socket error");
    148         exit(0);
    149     }
    150     
    151     struct sockaddr_in addr;
    152     addr.sin_family = AF_INET;
    153     addr.sin_port = htons(8001);
    154     inet_aton("192.168.31.128", &addr.sin_addr);
    155     //addr.sin_addr.s_addr = inet_addr("192.168.6.249");
    156     //addr.sin_addr.s_addr = INADDR_ANY;
    157     
    158     int optval = 1;
    159     if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
    160     {
    161         perror("setsockopt error");
    162         exit(0);    
    163     }
    164     
    165     if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    166     {
    167         perror("bind error");
    168         exit(0);
    169     }
    170     
    171     if(listen(sockfd, SOMAXCONN) < 0)
    172     {
    173         perror("listen error");
    174         exit(0);
    175     }
    176     
    177     struct sockaddr_in peeraddr;
    178     socklen_t peerlen;
    179     
    180     pid_t pid;
    181     int conn = 0;
    182     
    183     while(1)
    184     {
    185         conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
    186         if(conn == -1)
    187         {
    188             perror("accept error");
    189             exit(0);
    190         }
    191     
    192         char *p = NULL;
    193         int peerport = 0;
    194         p = inet_ntoa(peeraddr.sin_addr);
    195         peerport = ntohs(peeraddr.sin_port);
    196     
    197         printf("peeraddr = %s
     peerport = %d
    ", p, peerport);
    198         
    199         pid = fork();
    200         if(pid == -1)
    201         {
    202             perror("fork error");
    203             exit(0);
    204         }
    205         
    206         if(pid == 0)
    207         {
    208             char recvbuf[1024];
    209             int ret = 0;
    210             
    211             close(sockfd);
    212             
    213             while(1)
    214             {
    215                 memset(&recvbuf, 0, sizeof(recvbuf));
    216                 ret = readline(conn, recvbuf, 1024);
    217         
    218                 if(ret == 0)
    219                 {
    220                     printf("client closed 
    ");
    221                     break;
    222                 }
    223                 else if(ret == -1)
    224                 {
    225                     perror("readline error");
    226                     break;
    227                 }
    228                 
    229                 fputs(recvbuf, stdout);
    230         
    231                 writen(conn, recvbuf, strlen(recvbuf));
    232             }
    233         }
    234         
    235         close(conn);
    236         
    237     }
    238     
    239     close(conn);
    240     close(sockfd);
    241     
    242     return 0;
    243 }

    客户端:

      1 #include <sys/types.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <errno.h>
      7 #include <arpa/inet.h>
      8 #include <sys/socket.h>
      9 #include <netinet/in.h>
     10 #include <sys/socket.h>
     11 #include <netinet/ip.h> /* superset of previous */
     12 
     13 ssize_t readn(int fd, void *buf, size_t count)
     14 {
     15     size_t nleft = count;
     16     ssize_t nread;
     17     
     18     char *bufp = (char*)buf;
     19     
     20     while(nleft > 0)
     21     {
     22         if( (nread = read(fd, bufp, nleft)) < 0 )
     23         {
     24             if(errno == EINTR)
     25             {
     26                 continue;
     27             }
     28             
     29             return -1;
     30         }
     31         else if(nread == 0)
     32         {
     33             return count - nleft;
     34         }
     35         
     36         bufp += nread;
     37         nleft -= nread;
     38     }
     39     
     40     return count;
     41 }
     42 
     43 ssize_t writen(int fd, const void *buf, size_t count)
     44 {
     45     size_t nleft = count;
     46     ssize_t nwritten;
     47     
     48     char *bufp = (char*)buf;
     49     
     50     while(nleft > 0)
     51     {
     52         if( (nwritten = write(fd, bufp, nleft)) < 0)
     53         {
     54             if(errno == EINTR)
     55             {
     56                 continue;
     57             }
     58             
     59             return -1;
     60         }
     61         else if(nwritten == 0)
     62         {
     63             continue;
     64         }
     65         
     66         bufp += nwritten;
     67         nleft -= nwritten;
     68     }
     69     
     70     return count;
     71 }
     72 
     73 ssize_t recv_peek(int sockfd, void *buf, size_t len)
     74 {
     75     while(1)
     76     {
     77         int ret = recv(sockfd, buf, len, MSG_PEEK);
     78         if(ret == -1 && errno == EINTR)
     79             continue;
     80         return ret;
     81     }
     82 }
     83 
     84 ssize_t readline(int sockfd, void *buf, size_t maxline)
     85 {
     86     int ret;
     87     int nread;
     88     char *bufp = (char*)buf;
     89     int nleft = maxline;
     90     
     91     while(1)
     92     {
     93         ret = recv_peek(sockfd, bufp, nleft);
     94         if(ret < 0)
     95         {
     96             return ret;
     97         }
     98         else if(ret == 0)
     99         {
    100             return ret;
    101         }
    102         
    103         nread = ret;
    104         int i;
    105         for(i = 0; i < nread; i++)
    106         {
    107             if(bufp[i] == '
    ')
    108             {
    109                 ret = readn(sockfd, bufp, i+1);
    110                 if(ret != i+1)
    111                 {
    112                     perror("readn error");
    113                     exit(0);
    114                 }
    115                 
    116                 return ret;
    117             }
    118         }
    119         
    120         if(nread > nleft)
    121         {
    122             perror("FAILURE");
    123             exit(0);
    124         }
    125         
    126         nleft -= nread;
    127         ret = readn(sockfd, bufp, nread);
    128         if(ret != nread)
    129         {
    130             perror("readn error");
    131             exit(0);
    132         }
    133         bufp += nread;
    134     }
    135     
    136     return -1;
    137 }
    138 
    139 int main()
    140 {
    141     int sockfd = 0;
    142     sockfd = socket(AF_INET, SOCK_STREAM, 0);
    143     
    144     struct sockaddr_in addr;
    145     addr.sin_family = AF_INET;
    146     addr.sin_port = htons(8001);
    147     inet_aton("192.168.31.128", &addr.sin_addr);
    148     //addr.sin_addr.s_addr = inet_addr("192.168.31.128");
    149     
    150     if( connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1 )
    151     {
    152         perror("connect error");
    153         exit(0);
    154     }
    155     
    156     char sendbuf[1024] = {0};
    157     char recvbuf[1024] = {0};
    158     int ret = 0;
    159     int n = 0;
    160     while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    161     {
    162         writen(sockfd, sendbuf, strlen(sendbuf));
    163         
    164         ret = readline(sockfd, recvbuf, sizeof(recvbuf));
    165         
    166         if(ret == -1)
    167         {
    168             perror("readline error");
    169             exit(0);
    170         }
    171         else if(ret == 0)
    172         {
    173             printf("server close
    ");
    174             break;
    175         }
    176         
    177         fputs(recvbuf, stdout);
    178         memset(recvbuf, 0, sizeof(recvbuf));
    179         memset(sendbuf, 0, sizeof(sendbuf));
    180 
    181     }
    182     
    183     close(sockfd);
    184     
    185     return 0;
    186 }

    主要的函数就是readline函数,该函数从套接字缓冲区读取maxline长度的数据,readline调用了recv_peek,recv_peek返回实际读取的数据长度,然后在readline函数中判断这些数据中是否有' ',如果有' '的话,就用readn函数真正的将数据读出来,然后直接返回。如果没有' ',则会跳到120行判断一下recv_peek读取的长度,然后用readn将这些长度的数据读出来,然后移动缓冲区指针,接着再次调用recv_peek去缓冲区读数据,直到读到' '为止。或者读满整个maxline长度就返回。

    执行结果如下:

      

  • 相关阅读:
    C++ 如何重复利用一个内存地址块
    C与C++在const用法上的区别
    C++ 与设计模式学习(其一)
    C/C++ 关于生成静态库(lib)/动态库(dll)文件如何使用(基于windows基础篇)
    c/c++----网站及其后门(CGI应用程序)
    C/C++深度copy和浅copy
    C/C++ 一段代码区分数组指针|指针数组|函数指针|函数指针数组
    C/C++ 如何劫持别人家的命令||函数||程序(只能对于window而言)
    C++继承与派生(原理归纳)
    Linux下如何查看自己的服务器有没有无线网卡
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9393476.html
Copyright © 2020-2023  润新知