zoukankan      html  css  js  c++  java
  • TCP网络通讯如何解决分包粘包问题(有模拟代码)

    TCP作为常用的网络传输协议,数据流解析是网络应用开发人员永远绕不开的一个问题。
    TCP数据传输是以无边界的数据流传输形式,所谓无边界是指数据发送端发送的字节数,在数据接收端接受时并不一定等于发送的字节数,可能会出现粘包情况。

    一、TCP粘包情况:

    1. 发送端发送了数量比较的数据,接收端读取数据时候数据分批到达,造成一次发送多次读取;通常网络路由的缓存大小有关系,一个数据段大小超过缓存大小,那么就要拆包发送。
    2. 发送端发送了几次数据,接收端一次性读取了所有数据,造成多次发送一次读取;通常是网络流量优化,把多个小的数据段集满达到一定的数据量,从而减少网络链路中的传输次数。

    TCP粘包的解决方案有很多种方法,最简单的一种就是发送的数据协议定义发送的数据包的结构:
    1. 数据头:数据包的大小,固定长度。
    2. 数据内容:数据内容,长度为数据头定义的长度大小。
    实际操作如下:
    a)发送端:先发送数据包的大小,再发送数据内容。
    b)接收端:先解析本次数据包的大小N,在读取N个字节,这N个字节就是一个完整的数据内容。
    具体流程如下:

    实现源码
    [cpp] view plain copy
     
    1. /**  
    2.  * read size of len from sock into buf.  
    3.  */    
    4. bool readPack(int sock, char* buf, size_t len) {    
    5.     if (NULL == buf || len < 1) {    
    6.         return false;    
    7.     }    
    8.     memset(buf, 0, len); // only reset buffer len.    
    9.     ssize_t read_len = 0, readsum = 0;    
    10.     do {    
    11.         read_len = read(sock, buf + readsum, len - readsum);    
    12.         if (-1 == read_len) { // ignore error case    
    13.             return false;    
    14.         }    
    15.         printf("receive data: %s ", buf + readsum);    
    16.         readsum += read_len;    
    17.     } while (readsum < len && 0 != read_len);    
    18.     return true;    
    19. }   

    二、测试用例介绍

    本篇提供的demo主要流程如下:
    1. 客户端负责模拟发送数据,服务端负责接受数据,处理粘包问题
    a)emulate_subpackage
    模拟情况1,一个长数据经过多次才到达目的地,
    在客户端字符串“This is a test case for client send subpackage data. data is not send complete at once.”每次只发送6个字节长度。服务端要把字符串集满才能处理数据(打印字符串)
    b)emulate_adheringpackage
    模拟情况2,多个数据在一次性到达目的地
    在客户端将字符串“Hello I'm lucky. Nice too me you”切成三个数据段(都包含数据头和数据内容),然后一次性发送,服务端读取数据时对三个数据段逐个处理。

    三、源码实现

    server.cpp
    [cpp] view plain copy
     
    1. #include <cstdio>  
    2. #include <cstdlib>  
    3. #include <cstring>  
    4. #include <errno.h>  
    5. #include <sys/socket.h>  
    6. #include <sys/types.h>  
    7. #include <arpa/inet.h>  
    8. #include <unistd.h>  
    9.   
    10. void newclient(int sock);  
    11. bool readPack(int sock, char* buf, size_t len);  
    12. void safe_close(int &sock);  
    13.   
    14. int main(int argc, char *argv[]) {  
    15.     int sockfd = -1, newsockfd = -1;  
    16.     socklen_t c = 0;  
    17.     struct sockaddr_in serv_addr, cli_addr;  
    18.   
    19.     // Create socket  
    20.     sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    21.     if (-1 == sockfd) {  
    22.         printf("new socket failed. errno: %d, error: %s ", errno, strerror(errno));  
    23.         exit(-1);  
    24.     }  
    25.   
    26.     // Prepare the sockaddr_in structure  
    27.     serv_addr.sin_family = AF_INET;  
    28.     serv_addr.sin_addr.s_addr = INADDR_ANY;  
    29.     serv_addr.sin_port = htons(7890);  
    30.   
    31.     // bind  
    32.     if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {  
    33.         printf("bind failed. errno: %d, error: %s ", errno, strerror(errno));  
    34.         exit(-1);  
    35.     }  
    36.   
    37.     // listen  
    38.     listen(sockfd, 5);  
    39.   
    40.     printf("listening... ");  
    41.     // accept new connection.  
    42.     c = sizeof(struct sockaddr_in);  
    43.     int i = 0;  
    44.     while (i++ < 3) {  
    45.         printf("waiting for new socket accept. ");  
    46.         newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, (socklen_t*)&c);  
    47.         if (newsockfd < 0) {  
    48.             printf("accept connect failed. errno: %d, error: %s ", errno, strerror(errno));  
    49.             safe_close(sockfd);  
    50.             exit(-1);  
    51.         }  
    52.         pid_t pid = fork();  
    53.         if (0 == pid) {  
    54.             newclient(newsockfd);  
    55.             safe_close(sockfd);  
    56.             break;  
    57.         } else if (pid > 0) {  
    58.             safe_close(newsockfd);  
    59.         }  
    60.     }  
    61.     safe_close(sockfd);  
    62.     return 0;  
    63. }  
    64.   
    65. void newclient(int sock) {  
    66.     printf("newclient sock fd: %d ", sock);  
    67.     int datasize = 0;  
    68.     const int HEAD_SIZE = 9;  
    69.     char buf[512] = {0};  
    70.     while (true) {  
    71.         memset(buf, 0, sizeof(buf));  
    72.         if (! readPack(sock, buf, HEAD_SIZE)) {  
    73.             printf("read head buffer failed. ");  
    74.             safe_close(sock);  
    75.             return;  
    76.         }  
    77.   
    78.         datasize = atoi(buf);  
    79.         printf("data size: %s, value:%d ", buf, datasize);  
    80.         memset(buf, 0, sizeof(buf));  
    81.         if (! readPack(sock, buf, datasize)) {  
    82.             printf("read data buffer failed ");  
    83.             safe_close(sock);  
    84.             return;  
    85.         }  
    86.         printf("data size: %d, text: %s ", datasize, buf);  
    87.         if (0 == strcmp(buf, "exit")) {  
    88.             break;  
    89.         }  
    90.     }  
    91.     memset(buf, 0, sizeof(buf));  
    92.     snprintf(buf, sizeof(buf), "from server read complete.");  
    93.     write(sock, buf, strlen(buf) + 1);  
    94.     printf("newclient sockfd: %d, finish. ", sock);  
    95.     safe_close(sock);  
    96. }  
    97.   
    98. void safe_close(int &sock) {  
    99.     if (sock > 0) {  
    100.         close(sock);  
    101.         sock = -1;  
    102.     }  
    103. }  
    104.   
    105. /** 
    106.  * read size of len from sock into buf. 
    107.  */  
    108. bool readPack(int sock, char* buf, size_t len) {  
    109.     if (NULL == buf || len < 1) {  
    110.         return false;  
    111.     }  
    112.     memset(buf, 0, len); // only reset buffer len.  
    113.     ssize_t read_len = 0, readsum = 0;  
    114.     do {  
    115.         read_len = read(sock, buf + readsum, len - readsum);  
    116.         if (-1 == read_len) { // ignore error case  
    117.             return false;  
    118.         }  
    119.         printf("receive data: %s ", buf + readsum);  
    120.         readsum += read_len;  
    121.     } while (readsum < len && 0 != read_len);  
    122.     return true;  
    123. }  
    client.cpp
    [cpp] view plain copy
     
    1. #include <cstdio>  
    2. #include <cstdlib>  
    3. #include <cstring>  
    4. #include <time.h>  
    5. #include <errno.h>  
    6. #include <sys/socket.h>  
    7. #include <arpa/inet.h>  
    8. #include <unistd.h>  
    9.   
    10. void safe_close(int &sock);  
    11. void emulate_subpackage(int sock);  
    12. void emulate_adheringpackage(int sock);  
    13.   
    14. int main(int argc, char *argv[]) {  
    15.     char buf[128] = {0};  
    16.     int sockfd = -1;  
    17.     struct sockaddr_in serv_addr;  
    18.   
    19.     // Create sock  
    20.     sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    21.     if (-1 == sockfd) {  
    22.         printf("new socket failed. errno: %d, error: %s ", errno, strerror(errno));  
    23.         exit(-1);  
    24.     }  
    25.   
    26.     serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    27.     serv_addr.sin_family = AF_INET;  
    28.     serv_addr.sin_port = htons(7890);  
    29.   
    30.     // Connect to remote server  
    31.     if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {  
    32.         printf("connection failed. errno: %d, error: %s ", errno, strerror(errno));  
    33.         exit(-1);  
    34.     }  
    35.     emulate_subpackage(sockfd);  
    36.     emulate_adheringpackage(sockfd);  
    37.   
    38.     const int HEAD_SIZE = 9;  
    39.     const char temp[] = "exit";  
    40.     memset(buf, 0, sizeof(buf));  
    41.     snprintf(buf, sizeof(buf), "%0.*zu", HEAD_SIZE - 1, sizeof(temp));  
    42.     write(sockfd, buf, HEAD_SIZE);  
    43.     write(sockfd, temp, sizeof(temp));  
    44.   
    45.     printf("send complete. ");  
    46.     memset(buf, 0, sizeof(buf));  
    47.     read(sockfd, buf, sizeof(buf));  
    48.     printf("receive data: %s ", buf);  
    49.     printf("client finish. ");  
    50.   
    51.     safe_close(sockfd);  
    52.     return 0;  
    53. }  
    54.   
    55. void safe_close(int &sock) {  
    56.     if (sock > 0) {  
    57.         close(sock);  
    58.         sock = -1;  
    59.     }  
    60. }  
    61.   
    62. /** 
    63.  * emulate socket data write multi part. 
    64.  */  
    65. void emulate_subpackage(int sock) {  
    66.     printf("emulate_subpackage... ");  
    67.     char text[] = "This is a test case for client send subpackage data. data is not send complete at once.";  
    68.     const size_t TEXTSIZE = sizeof(text);  
    69.     ssize_t len = 0;  
    70.     size_t sendsize = 0, sendsum = 0;  
    71.   
    72.     const int HEAD_SIZE = 9;  
    73.     char buf[64] = {0};  
    74.     snprintf(buf, HEAD_SIZE, "%08zu", TEXTSIZE);  
    75.     write(sock, buf, HEAD_SIZE);  
    76.     printf("send data size: %s ", buf);  
    77.   
    78.     do {  
    79.         sendsize = 6;  
    80.         if (sendsum + sendsize > TEXTSIZE) {  
    81.             sendsize = TEXTSIZE - sendsum;  
    82.         }  
    83.         len = write(sock, text + sendsum, sendsize);  
    84.         if (-1 == len) {  
    85.             printf("send data failed. errno: %d, error: %s ", errno, strerror(errno));  
    86.             return;  
    87.         }  
    88.         memset(buf, 0, sizeof(buf));  
    89.         snprintf(buf, len + 1, text + sendsum);  
    90.         printf("send data: %s ", buf);  
    91.         sendsum += len;  
    92.         sleep(1);  
    93.     } while (sendsum < TEXTSIZE && 0 != len);  
    94. }  
    95.   
    96. /** 
    97.  * emualte socket data write adhering. 
    98.  */  
    99. void emulate_adheringpackage(int sock) {  
    100.     printf("emulate_adheringpackage... ");  
    101.     const int HEAD_SIZE = 9;  
    102.     char buf[1024] = {0};  
    103.     char text[128] = {0};  
    104.     char *pstart = buf;  
    105.   
    106.     // append text  
    107.     memset(text, 0, sizeof(text));  
    108.     snprintf(text, sizeof(text), "Hello ");  
    109.     snprintf(pstart, HEAD_SIZE, "%08zu", strlen(text) + 1);  
    110.     pstart += HEAD_SIZE;  
    111.     snprintf(pstart, strlen(text) + 1, "%s", text);  
    112.     pstart += strlen(text) + 1;  
    113.   
    114.     // append text  
    115.     memset(text, 0, sizeof(text));  
    116.     snprintf(text, sizeof(text), "I'm lucky.");  
    117.     snprintf(pstart, HEAD_SIZE, "%08zu", strlen(text) + 1);  
    118.     pstart += HEAD_SIZE;  
    119.     snprintf(pstart, strlen(text) + 1, "%s", text);  
    120.     pstart += strlen(text) + 1;  
    121.   
    122.     // append text  
    123.     memset(text, 0, sizeof(text));  
    124.     snprintf(text, sizeof(text), "Nice too me you");  
    125.     snprintf(pstart, HEAD_SIZE, "%08zu", strlen(text) + 1);  
    126.     pstart += HEAD_SIZE;  
    127.     snprintf(pstart, strlen(text) + 1, "%s", text);  
    128.     pstart += strlen(text) + 1;  
    129.     write(sock, buf, pstart - buf);  
    130. }  
    Makefile
    [plain] view plain copy
     
    1. CC=g++  
    2. CFLAGS=-I  
    3.   
    4. all: server.o client.o  
    5.   
    6. server.o: server.cpp  
    7.     $(CC) -o server.o server.cpp  
    8.   
    9. client.o: client.cpp  
    10.     $(CC) -o client.o client.cpp  
    11.   
    12. clean:  
    13.     rm *.o  

    四、测试结果

    编译及运行
    $ make
    g++ -o server.o server.cpp
    g++ -o client.o client.cpp
    客户端模拟发送数据
    $ ./client.o
    emulate_subpackage...
    send data size: 00000088
    send data: This i
    send data: s a te
    send data: st cas
    send data: e for
    send data: client
    send data: send
    send data: subpac
    send data: kage d
    send data: ata. d
    send data: ata is
    send data: not s
    send data: end co
    send data: mplete
    send data: at on
    send data: ce.
    emulate_adheringpackage...
    send complete.
    receive data: from server read complete.
    client finish.

    服务端模拟接受数据
    $ ./server.o
    listening...
    waiting for new socket accept.
    waiting for new socket accept.
    newclient sock fd: 4
    receive data: 00000088
    data size: 00000088, value:88
    receive data: This i
    receive data: s a te
    receive data: st cas
    receive data: e for
    receive data: client
    receive data: send
    receive data: subpac
    receive data: kage d
    receive data: ata. d
    receive data: ata is
    receive data: not s
    receive data: end co
    receive data: mplete
    receive data: at on
    receive data: ce.
    data size: 88, text: This is a test case for client send subpackage data. data is not send complete at once.
    receive data: 00000007
    data size: 00000007, value:7
    receive data: Hello
    data size: 7, text: Hello
    receive data: 00000011
    data size: 00000011, value:11
    receive data: I'm lucky.
    data size: 11, text: I'm lucky.
    receive data: 00000016
    data size: 00000016, value:16
    receive data: Nice too me you
    data size: 16, text: Nice too me you
    receive data: 00000005
    data size: 00000005, value:5
    receive data: exit
    data size: 5, text: exit
    newclient sockfd: 4, finish.
     
     
    http://blog.csdn.net/sweettool/article/details/77018506
  • 相关阅读:
    LightOJ 1132 Summing up Powers(矩阵快速幂)
    hdu 3804 Query on a tree (树链剖分+线段树)
    LightOJ 1052 String Growth && uva 12045 Fun with Strings (矩阵快速幂)
    uva 12304 2D Geometry 110 in 1! (Geometry)
    LA 3263 That Nice Euler Circuit (2D Geometry)
    2013 SCAUCPC Summary
    poj 3321 Apple Tree (Binary Index Tree)
    uva 11796 Dog Distance (几何+模拟)
    uva 11178 Morley's Theorem (2D Geometry)
    动手动脑
  • 原文地址:https://www.cnblogs.com/findumars/p/7486665.html
Copyright © 2011-2022 走看看