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

一、TCP粘包情况:

1. 发送端发送了数量比较的数据,接收端读取数据时候数据分批到达,造成一次发送多次读取;通常网络路由的缓存大小有关系,一个数据段大小超过缓存大小,那么就要拆包发送。
2. 发送端发送了几次数据,接收端一次性读取了所有数据,造成多次发送一次读取;通常是网络流量优化,把多个小的数据段集满达到一定的数据量,从而减少网络链路中的传输次数。
TCP粘包的解决方案有很多种方法,最简单的一种就是发送的数据协议定义发送的数据包的结构:
1. 数据头:数据包的大小,固定长度。
2. 数据内容:数据内容,长度为数据头定义的长度大小。
实际操作如下:
a)发送端:先发送数据包的大小,再发送数据内容。
b)接收端:先解析本次数据包的大小N,在读取N个字节,这N个字节就是一个完整的数据内容。
具体流程如下:
实现源码
  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\n", 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
  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. void newclient(int sock);
  10. bool readPack(int sock, char* buf, size_t len);
  11. void safe_close(int &sock);
  12. int main(int argc, char *argv[]) {
  13. int sockfd = -1, newsockfd = -1;
  14. socklen_t c = 0;
  15. struct sockaddr_in serv_addr, cli_addr;
  16. // Create socket
  17. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  18. if (-1 == sockfd) {
  19. printf("new socket failed. errno: %d, error: %s\n", errno, strerror(errno));
  20. exit(-1);
  21. }
  22. // Prepare the sockaddr_in structure
  23. serv_addr.sin_family = AF_INET;
  24. serv_addr.sin_addr.s_addr = INADDR_ANY;
  25. serv_addr.sin_port = htons(7890);
  26. // bind
  27. if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
  28. printf("bind failed. errno: %d, error: %s\n", errno, strerror(errno));
  29. exit(-1);
  30. }
  31. // listen
  32. listen(sockfd, 5);
  33. printf("listening...\n");
  34. // accept new connection.
  35. c = sizeof(struct sockaddr_in);
  36. int i = 0;
  37. while (i++ < 3) {
  38. printf("waiting for new socket accept.\n");
  39. newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, (socklen_t*)&c);
  40. if (newsockfd < 0) {
  41. printf("accept connect failed. errno: %d, error: %s\n", errno, strerror(errno));
  42. safe_close(sockfd);
  43. exit(-1);
  44. }
  45. pid_t pid = fork();
  46. if (0 == pid) {
  47. newclient(newsockfd);
  48. safe_close(sockfd);
  49. break;
  50. } else if (pid > 0) {
  51. safe_close(newsockfd);
  52. }
  53. }
  54. safe_close(sockfd);
  55. return 0;
  56. }
  57. void newclient(int sock) {
  58. printf("newclient sock fd: %d\n", sock);
  59. int datasize = 0;
  60. const int HEAD_SIZE = 9;
  61. char buf[512] = {0};
  62. while (true) {
  63. memset(buf, 0, sizeof(buf));
  64. if (! readPack(sock, buf, HEAD_SIZE)) {
  65. printf("read head buffer failed.\n");
  66. safe_close(sock);
  67. return;
  68. }
  69. datasize = atoi(buf);
  70. printf("data size: %s, value:%d\n", buf, datasize);
  71. memset(buf, 0, sizeof(buf));
  72. if (! readPack(sock, buf, datasize)) {
  73. printf("read data buffer failed\n");
  74. safe_close(sock);
  75. return;
  76. }
  77. printf("data size: %d, text: %s\n", datasize, buf);
  78. if (0 == strcmp(buf, "exit")) {
  79. break;
  80. }
  81. }
  82. memset(buf, 0, sizeof(buf));
  83. snprintf(buf, sizeof(buf), "from server read complete.");
  84. write(sock, buf, strlen(buf) + 1);
  85. printf("newclient sockfd: %d, finish.\n", sock);
  86. safe_close(sock);
  87. }
  88. void safe_close(int &sock) {
  89. if (sock > 0) {
  90. close(sock);
  91. sock = -1;
  92. }
  93. }
  94. /**
  95. * read size of len from sock into buf.
  96. */
  97. bool readPack(int sock, char* buf, size_t len) {
  98. if (NULL == buf || len < 1) {
  99. return false;
  100. }
  101. memset(buf, 0, len); // only reset buffer len.
  102. ssize_t read_len = 0, readsum = 0;
  103. do {
  104. read_len = read(sock, buf + readsum, len - readsum);
  105. if (-1 == read_len) { // ignore error case
  106. return false;
  107. }
  108. printf("receive data: %s\n", buf + readsum);
  109. readsum += read_len;
  110. } while (readsum < len && 0 != read_len);
  111. return true;
  112. }

client.cpp

  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. void safe_close(int &sock);
  10. void emulate_subpackage(int sock);
  11. void emulate_adheringpackage(int sock);
  12. int main(int argc, char *argv[]) {
  13. char buf[128] = {0};
  14. int sockfd = -1;
  15. struct sockaddr_in serv_addr;
  16. // Create sock
  17. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  18. if (-1 == sockfd) {
  19. printf("new socket failed. errno: %d, error: %s\n", errno, strerror(errno));
  20. exit(-1);
  21. }
  22. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  23. serv_addr.sin_family = AF_INET;
  24. serv_addr.sin_port = htons(7890);
  25. // Connect to remote server
  26. if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
  27. printf("connection failed. errno: %d, error: %s\n", errno, strerror(errno));
  28. exit(-1);
  29. }
  30. emulate_subpackage(sockfd);
  31. emulate_adheringpackage(sockfd);
  32. const int HEAD_SIZE = 9;
  33. const char temp[] = "exit";
  34. memset(buf, 0, sizeof(buf));
  35. snprintf(buf, sizeof(buf), "%0.*zu", HEAD_SIZE - 1, sizeof(temp));
  36. write(sockfd, buf, HEAD_SIZE);
  37. write(sockfd, temp, sizeof(temp));
  38. printf("send complete.\n");
  39. memset(buf, 0, sizeof(buf));
  40. read(sockfd, buf, sizeof(buf));
  41. printf("receive data: %s\n", buf);
  42. printf("client finish.\n");
  43. safe_close(sockfd);
  44. return 0;
  45. }
  46. void safe_close(int &sock) {
  47. if (sock > 0) {
  48. close(sock);
  49. sock = -1;
  50. }
  51. }
  52. /**
  53. * emulate socket data write multi part.
  54. */
  55. void emulate_subpackage(int sock) {
  56. printf("emulate_subpackage...\n");
  57. char text[] = "This is a test case for client send subpackage data. data is not send complete at once.";
  58. const size_t TEXTSIZE = sizeof(text);
  59. ssize_t len = 0;
  60. size_t sendsize = 0, sendsum = 0;
  61. const int HEAD_SIZE = 9;
  62. char buf[64] = {0};
  63. snprintf(buf, HEAD_SIZE, "%08zu", TEXTSIZE);
  64. write(sock, buf, HEAD_SIZE);
  65. printf("send data size: %s\n", buf);
  66. do {
  67. sendsize = 6;
  68. if (sendsum + sendsize > TEXTSIZE) {
  69. sendsize = TEXTSIZE - sendsum;
  70. }
  71. len = write(sock, text + sendsum, sendsize);
  72. if (-1 == len) {
  73. printf("send data failed. errno: %d, error: %s\n", errno, strerror(errno));
  74. return;
  75. }
  76. memset(buf, 0, sizeof(buf));
  77. snprintf(buf, len + 1, text + sendsum);
  78. printf("send data: %s\n", buf);
  79. sendsum += len;
  80. sleep(1);
  81. } while (sendsum < TEXTSIZE && 0 != len);
  82. }
  83. /**
  84. * emualte socket data write adhering.
  85. */
  86. void emulate_adheringpackage(int sock) {
  87. printf("emulate_adheringpackage...\n");
  88. const int HEAD_SIZE = 9;
  89. char buf[1024] = {0};
  90. char text[128] = {0};
  91. char *pstart = buf;
  92. // append text
  93. memset(text, 0, sizeof(text));
  94. snprintf(text, sizeof(text), "Hello ");
  95. snprintf(pstart, HEAD_SIZE, "%08zu", strlen(text) + 1);
  96. pstart += HEAD_SIZE;
  97. snprintf(pstart, strlen(text) + 1, "%s", text);
  98. pstart += strlen(text) + 1;
  99. // append text
  100. memset(text, 0, sizeof(text));
  101. snprintf(text, sizeof(text), "I'm lucky.");
  102. snprintf(pstart, HEAD_SIZE, "%08zu", strlen(text) + 1);
  103. pstart += HEAD_SIZE;
  104. snprintf(pstart, strlen(text) + 1, "%s", text);
  105. pstart += strlen(text) + 1;
  106. // append text
  107. memset(text, 0, sizeof(text));
  108. snprintf(text, sizeof(text), "Nice too me you");
  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. write(sock, buf, pstart - buf);
  114. }

Makefile

  1. CC=g++
  2. CFLAGS=-I
  3. all: server.o client.o
  4. server.o: server.cpp
  5. $(CC) -o server.o server.cpp
  6. client.o: client.cpp
  7. $(CC) -o client.o client.cpp
  8. clean:
  9. 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

TCP网络通讯如何解决分包粘包问题(有模拟代码)的更多相关文章

  1. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

  2. 解决Socket粘包问题——C#代码

    解决Socket粘包问题——C#代码 前天晚上,曾经的一个同事问我socket发送消息如果太频繁接收方就会有消息重叠,因为当时在外面,没有多加思考 第一反应还以为是多线程导致的数据不同步导致的,让他加 ...

  3. Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象

    一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字 server.bind() #把地址绑定到套接字,网络地址加端口 server.lis ...

  4. Python网络编程(2)-粘包现象及socketserver模块实现TCP并发

    1. 基于Tcp的远程调用命令实现 很多人应该都使用过Xshell工具,这是一个远程连接工具,通过上面的知识,就可以模拟出Xshell远程连接服务器并调用命令的功能. Tcp服务端代码如下: impo ...

  5. 使用Newlife网络库管道模式解决数据粘包(二)

    上一篇我们讲了 如何创建一个基本的Newlife网络服务端 这边我们来讲一下如何解决粘包的问题 在上一篇总我们注册了Newlife的管道处理器 ,我们来看看他是如何实现粘包处理的 svr.Add< ...

  6. 网络编程基础【day09】:解决socket粘包之大数据(七)

    本节内容 概述 linux下运行效果 sleep解决粘包 服务端插入交互解决粘包问题 一.概述 刚刚我们在window的操作系统上,很完美的解决了,大数据量的数据传输出现的问题,但是在Linux环境下 ...

  7. TCP连接,传输数据时的粘包问题讨论

    第一个需要讨论的大概就是粘包问题了.因为这个是TCP的个性问题,UDP通信时不存在这个问题的.首先看一下什么叫粘包: 客户端采取与服务器的长连接方式建立通信(Open-Write/Read-Write ...

  8. TCP Socket 套接字 和 粘包问题

    一.Scoket 套接字 Scoket是应用层(应用程序)与TCP/IP协议通信的中间软件抽象层,它是一组接口.也可以理解为总共就三层:应用层,scoket抽象层,复杂的TCP/IP协议 基于TCP协 ...

  9. python------Socket网络编程(二)粘包问题

    一.socket网络编程 粘包:服务端两次发送指令在一起,它会把两次发送内容合在一起发送,称为粘包,从而出现错误. 解决方法:(比较low的方法) 有些需要实时更新的,用sleep有延迟,不能这样解决 ...

随机推荐

  1. [转] Python 爬虫的工具列表 附Github代码下载链接

    转自http://www.36dsj.com/archives/36417 这个列表包含与网页抓取和数据处理的Python库 网络 通用 urllib -网络库(stdlib). requests - ...

  2. ios开发之多线程---GCD

    一:基本概念 1:进程:正在运行的程序为进程. 2:线程:每个进程要想执行任务必须得有线程,进程中任务的执行都是在线程中. 3:线程的串行:一条线程里任务的执行都是串行的,假如有一个进程开辟了一条线程 ...

  3. mui列表跳转到详情页优化方案

    原理 因为列表页到详情页是多对一的形式,即列表页的多条数据列表对应的是一个详情页,只是数据不同而:因此,可以在加载列表页时预加载详情页,即创建一个详情页的webview,但是不显示出来,点击列表的时候 ...

  4. [React Router v4] Style a Link that is Active with NavLink

    We often need to be able to apply style to navigation links based on the current route. In React Rou ...

  5. [Angular] USING ZONES IN ANGULAR FOR BETTER PERFORMANCE

    Link to the artical. Zone detects any async opreations. Once an async oprations happens in Angular, ...

  6. asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二)

    原文:asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二) 续上一篇文章:asp.net core2.0 部署centos7/linux系统 -- ...

  7. SpringMVC中支持多视图解析

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/suo082407128/article/details/70173301 在SpringMVC模式当 ...

  8. System and method for dynamically adjusting to CPU performance changes

    FIELD OF THE INVENTION The present invention is related to computing systems, and more particularly ...

  9. solrj 7.x Expected mime type application/octet-stream but got text/html.

    出现这种情况是因为baseurl填写错误,最开始的时候我写的是用tomcat启动后浏览器中访问solr的地址 结果就出现了如题的异常,当然提示的是404,还有可能提示405,Method not al ...

  10. CentOS下Apache的停止和卸载

    昨晚搞到一台全球性价比最高的服务器,折腾一晚上,好不容易把node服务开启了,结果访问不了我的网站!!! 访问我的网站,显示的是一个Apache欢迎页面.我想,是不是像之前那样,80端口没有开放,然后 ...