Linux学习: TCP粘包问题

TCP协议下:

当发送数据过长过短, 或缓冲区大小问题, 导致出现了所谓的 TCP“粘包”问题, 这是我们的俗称, TCP是流模式,并不是包;

现象解释:

TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。    
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。

好了, 根据上述的理论 我们自己人为制造一起 ”粘包“

server

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <sys/types.h>
 7 #include <sys/socket.h>
 8 #include <netinet/in.h>
 9 #include <arpa/inet.h>
10 #define ERR_EXIT(m) 11     do { 12         perror(m);13         exit(EXIT_FAILURE);14     }while(0)
15
16 void do_service(int sockfd);
17
18 int main(int argc, const char *argv[])
19 {
20     int listenfd = socket(PF_INET, SOCK_STREAM, 0);
21     if(listenfd == -1)
22         ERR_EXIT("socket");
23
24     //地址复用
25     int on = 1;
26     if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
27         ERR_EXIT("setsockopt");
28
29     struct sockaddr_in addr;
30     memset(&addr, 0, sizeof addr);
31     addr.sin_family = AF_INET;
32     addr.sin_addr.s_addr = inet_addr("127.0.0.1");
33     addr.sin_port = htons(8976);
34     if(bind(listenfd, (struct sockaddr*)&addr, sizeof addr) == -1)
35         ERR_EXIT("bind");
36
37     if(listen(listenfd, SOMAXCONN) == -1)
38         ERR_EXIT("listen");
39
40     int peerfd = accept(listenfd, NULL, NULL);
41     do_service(peerfd);
42
43     close(peerfd);
44     close(listenfd);
45
46     return 0;
47 }
48
49
50
51 void do_service(int sockfd)
52 {
53     int cnt = 0;
54     char recvbuf[1024000] = {0};
55     while(1)
56     {
57         int nread = read(sockfd, recvbuf, sizeof recvbuf);
58         if(nread == -1)
59         {
60             if(errno == EINTR)
61                 continue;
62             ERR_EXIT("read");
63         }
64         else if(nread == 0)
65         {
66             printf("close ...\n");
67             exit(EXIT_SUCCESS);
68         }
69
70         printf("count = %d, receive size = %d\n", ++cnt, nread);
71         //write(sockfd, recvbuf, strlen(recvbuf));
72         memset(recvbuf, 0, sizeof recvbuf);
73     }
74 }

注意, server端的接收缓冲区应该足够大,否则无法接收 “黏在一块的数据包”

client端

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <sys/types.h>
 7 #include <sys/socket.h>
 8 #include <netinet/in.h>
 9 #include <arpa/inet.h>
10 #define ERR_EXIT(m) 11     do { 12         perror(m);13         exit(EXIT_FAILURE);14     }while(0)
15
16 void do_service(int sockfd);
17 void nano_sleep(double val);
18
19 int main(int argc, const char *argv[])
20 {
21     int peerfd = socket(PF_INET, SOCK_STREAM, 0);
22     if(peerfd == -1)
23         ERR_EXIT("socket");
24
25     struct sockaddr_in addr;
26     memset(&addr, 0, sizeof addr);
27     addr.sin_family = AF_INET;
28     addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //localhost
29     addr.sin_port = htons(8976);
30     socklen_t len = sizeof addr;
31     if(connect(peerfd, (struct sockaddr*)&addr, len) == -1)
32         ERR_EXIT("Connect");
33
34     do_service(peerfd);
35
36
37     return 0;
38 }
39
40
41
42 void do_service(int sockfd)
43 {
44     //const int kSize = 1024;
45     #define SIZE 1024
46     char sendbuf[SIZE + 1] = {0};
47     int i;
48     for(i = 0; i < SIZE; ++i)
49         sendbuf[i] = ‘a‘;
50
51     int cnt = 0; //次数
52     while(1)
53     {
54         int i;
55         for(i = 0; i < 10; ++i)
56         {
57             write(sockfd, sendbuf, SIZE);
58             printf("count = %d, write %d bytes\n", ++cnt, SIZE);
59         }
60         nano_sleep(4);
61
62         memset(sendbuf, 0, sizeof sendbuf);
63     }
64 }
65
66 void nano_sleep(double val)
67 {
68     struct timespec tv;
69     tv.tv_sec = val; //取整
70     tv.tv_nsec = (val - tv.tv_sec) * 1000 * 1000 * 1000;
71
72     int ret;
73     do
74     {
75         ret = nanosleep(&tv, &tv);
76     }while(ret == -1 && errno == EINTR);
77 }

客户端应该 短时间发送 大量的数据, 使server端 处理接收时 造成粘包;

可以看到我们连续发送了 10次 长度为1024 的全是a的 字符串;  看下server端打印如何

count = 1, receive size = 1024
count = 2, receive size = 1024
count = 3, receive size = 1024
count = 4, receive size = 1024
count = 5, receive size = 1024
count = 6, receive size = 5120
count = 7, receive size = 10240
count = 8, receive size = 10240
count = 9, receive size = 10240

可以看到, 当第6次读取时便出现了粘包; 数据出现了相连的问题;

而我们的客户端 是均匀的每次发送1024字节的数据

count = 1, write 1024 bytes
count = 2, write 1024 bytes
count = 3, write 1024 bytes
count = 4, write 1024 bytes
count = 5, write 1024 bytes
count = 6, write 1024 bytes
count = 7, write 1024 bytes
count = 8, write 1024 bytes
count = 9, write 1024 bytes
count = 10, write 1024 bytes
count = 11, write 1024 bytes
count = 12, write 1024 bytes
count = 13, write 1024 bytes
count = 14, write 1024 bytes
count = 15, write 1024 bytes
count = 16, write 1024 bytes
count = 17, write 1024 bytes
count = 18, write 1024 bytes
count = 19, write 1024 bytes
count = 20, write 1024 bytes
count = 21, write 1024 bytes
count = 22, write 1024 bytes
count = 23, write 1024 bytes
count = 24, write 1024 bytes
count = 25, write 1024 bytes
count = 26, write 1024 bytes
count = 27, write 1024 bytes
count = 28, write 1024 bytes
count = 29, write 1024 bytes
count = 30, write 1024 bytes
count = 31, write 1024 bytes
count = 32, write 1024 bytes
count = 33, write 1024 bytes
count = 34, write 1024 bytes
count = 35, write 1024 bytes
count = 36, write 1024 bytes
count = 37, write 1024 bytes
count = 38, write 1024 bytes
count = 39, write 1024 bytes
count = 40, write 1024 bytes

显然不是我们发送数据时造成的问题, 而是TCP本身的缺陷。

时间: 2024-10-12 02:06:05

Linux学习: TCP粘包问题的相关文章

Netty学习之TCP粘包/拆包

一.TCP粘包/拆包问题说明,如图 二.未考虑TCP粘包导致功能异常案例 按照设计初衷,服务端应该收到100条查询时间指令的请求查询,客户端应该打印100次服务端的系统时间 1.服务端类 package com.phei.netty.s2016042302; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitial

Netty(三)TCP粘包拆包处理

tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D2给服务端,由于服务端一次性读取到的字节数是不确定的,所以可能存在以下4种情况. 1.服务端分2次读取到了两个独立的包,分别是D1,D2,没有粘包和拆包: 2.服务端一次性接收了两个包,D1和D2粘在一起了,被成为TCP粘包; 3.服务端分2次读取到了两个数据包,第一次读取到了完整的D1和D2包的部

Netty中使用MessagePack时的TCP粘包问题与解决方案

[toc] Netty中使用MessagePack时的TCP粘包问题与解决方案 通过下面的实例代码来演示在Netty中使用MessagPack时会出现的TCP粘包问题,为了学习的连贯性,参考了<Netty权威指南>第7章中的代码,但是需要注意的是,书中并没有提供完整代码,提供的代码都是片段性的,所以我根据自己的理解把服务端的代码和客户端的代码写了出来,可以作为参考. 仍然需要注意的是,我使用的是Netty 4.x的版本. 另外我在程序代码中写了非常详细的注释,所以这里不再进行更多的说明. 在使

TCP粘包问题,到底如何解决?

无论走到哪里,都应该记住,过去都是假的,回忆是一条没有尽头的路,一切以往的春天都不复存在,就连那最坚韧而又狂乱的爱情归根结底也不过是一种转瞬即逝的现实. --马尔克斯 <p align="center">本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues.</p><h4 align="center"><a href="https://github.com/midou-tech/articles&

netty 解决TCP粘包与拆包问题(二)

TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识消息的总长度 一.采用指定分割符解决粘包与拆包问题 服务端 1 package com.ming.netty.nio.stickpack; 2 3 4 5 import java.net.InetSocketAddress; 6 7 import io.netty.bootstrap.ServerB

tcp粘包问题(封包)

tcp粘包分析     http://blog.csdn.net/zhangxinrun/article/details/6721495 解决TCP网络传输“粘包”问题(经典)       http://blog.csdn.net/zhangxinrun/article/details/6721508 粘包出现原因:在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows 网络编程)1 发送端需要等缓冲区满才发送出去,造成粘包2 接收方不及时接收缓冲区的包,造成多个包接收 解决办

SOCKET TCP 粘包及半包问题

大家在使用SOCKET通信编程的时候,一般会采用UDP和TCP两种方式:TCP因为它没有包的概念,它只有流的概念,并且因为发送或接收缓冲区大小的设置问题,会产生粘包及半包的现象. 场景: 服务端向连续发送三个"HelloWorld"(三次消息无间隔),那么客户端接收到的情况会有以下三种: 1)HelloWorld HelloWorld HelloWorld (客户端接收三次) 2)HelloWorldHelloWor ldHelloWorld (客户端接收两次) 3)HelloWorl

TCP粘包问题分析和解决(全)

TCP通信粘包问题分析和解决(全) 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小.数据量小的数据,合并成一个大的数据块,然后进行封包.这样,接收端,就难于分辨出来了,必须提供科学的拆包机制. 对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,

TCP粘包, UDP丢包, nagle算法

一.TCP粘包 1. 什么时候考虑粘包 如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议,UDP不会出现粘包现象).关闭连接主要要双方都发送close连接(参考tcp关闭协议).如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问