① TCP是个流协议,它存在粘包问题

TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。主机A向主机B发送两个数据包,主机B的接收情况可能是

产生粘包问题的原因有以下几个:

  • 第一 。应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。
  • 第二种情况是,TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。
  • 第三种情况由于链路层最大发送单元MTU,在IP层会进行数据的分片。

这些情况都会导致一个完整的应用层数据被分割成多次发送,导致接收对等方不是按完整数据包的方式来接收数据。

② 粘包的问题的解决思路
粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:

  • 发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
  • 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
  • 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
  • 使用更加复杂的应用层协议。

③ 粘包解决方案一:使用定长包
这里需要封装两个函数:

ssize_t readn(int fd, void *buf, size_t count)
ssize_t writen(int fd, void *buf, size_t count)

这两个函数的参数列表和返回值与readwrite一致。它们的作用的读取/写入count个字节后再返回。其实现如下:

ssize_t readn(int fd, void *buf, size_t count)
{int left = count ; //剩下的字节char * ptr = (char*)buf ;while(left>0){int readBytes = read(fd,ptr,left);if(readBytes< 0)//read函数小于0有两种情况:1中断 2出错{if(errno == EINTR)//读被中断{continue;}return -1;}if(readBytes == 0)//读到了EOF{//对方关闭呀printf("peer close\n");return count - left;}left -= readBytes;ptr += readBytes ;}return count ;
}/*
writen 函数
写入count字节的数据
*/
ssize_t writen(int fd, void *buf, size_t count)
{int left = count ;char * ptr = (char *)buf;while(left >0){int writeBytes = write(fd,ptr,left);if(writeBytes<0){if(errno == EINTR)continue;return -1;}else if(writeBytes == 0)continue;left -= writeBytes;ptr += writeBytes;}return count;
}

有了这两个函数之后,我们就可以使用定长包来发送数据了,我抽取其关键代码来讲诉:

char readbuf[512];
readn(conn,readbuf,sizeof(readbuf));  //每次读取512个字节

同理的,写入的时候也写入512个字节

char writebuf[512];fgets(writebuf,sizeof(writebuf),stdin);writen(conn,writebuf,sizeof(writebuf);

每个消息都以固定的512字节(或其他数字,看你的应用层的缓冲区大小)来发送,以此区分每一个信息,这便是以固定长度解决粘包问题的思路。定长包解决方案的缺点在于会导致增加网络的负担,无论每次发送的有效数据是多大,都得按照定长的数据长度进行发送。

④ 粘包解决方案二:使用结构体,显式说明数据部分的长度

在这个方案中,我们需要定义一个‘struct packet’包结构,结构中指明数据部分的长度,用四个字节来表示。发送端的对等方接收报文时,先读取前四个字节,获取数据的长度,由长度来进行数据的读取。定义一个结构体

struct packet
{unsigned int msgLen ;  //4个字节字段,说明数据部分的大小char data[512] ;  //数据部分
}

读写过程如下所示,这里抽取关键代码进行说明:

//发送数据过程struct packet writebuf;memset(&writebuf,0,sizeof(writebuf));while(fgets(writebuf.data,sizeof(writebuf.data),stdin)!=NULL){      int n = strlen(writebuf.data);   //计算要发送的数据的字节数writebuf.msgLen =htonl(n);    //将该字节数保存在msgLen字段,注意字节序的转换writen(conn,&writebuf,4+n);   //发送数据,数据长度为4个字节的msgLen 加上data长度memset(&writebuf,0,sizeof(writebuf)); }

下面是读取数据的过程,先读取msgLen字段,该字段指示了有效数据data的长度。依据该字段再读出data。

  memset(&readbuf,0,sizeof(readbuf));int ret = readn(conn,&readbuf.msgLen,4); //先读取四个字节,确定后续数据的长度if(ret == -1){err_exit("readn");}else if(ret == 0){printf("peer close\n");break;
}int dataBytes = ntohl(readbuf.msgLen); //字节序的转换int readBytes = readn(conn,readbuf.data,dataBytes); //读取出后续的数据if(readBytes == 0){printf("peer close\n");break;}if(readBytes<0){err_exit("read");
}

⑤ 粘包解决方案三:按行读取
ftp协议采用/r/n来识别一个消息的边界,我们在这里实现一个按行读取的功能,该功能能够按/n来识别消息的边界。这里介绍一个函数:

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

与read函数相比,recv函数的区别在于两点:

  1. recv函数只能够用于套接口IO。
  2. recv函数含有flags参数,可以指定一些选项。

recv函数的flags参数常用的选项是:

  1. MSG_OOB 接收带外数据,即通过紧急指针发送的数据
  2. MSG_PEEK 从缓冲区中读取数据,但并不从缓冲区中清除所读数据

为了实现按行读取,我们需要使用recv函数的MSG_PEEK选项。PEEK的意思是"偷看",我们可以理解为窥视,看看socket的缓冲区内是否有某种内容,而清除缓冲区。

/*
* 封装了recv函数返回值说明:-1 读取出错
*/
ssize_t read_peek(int sockfd,void *buf ,size_t len)
{while(1){//从缓冲区中读取,但不清除缓冲区int ret = recv(sockfd,buf,len,MSG_PEEK);if(ret == -1 && errno == EINTR)//文件读取中断continue;return ret;}
}

下面是按行读取的代码:

/*
*读取一行内容
* 返回值说明:== 0 :对端关闭== -1 : 读取错误其他:一行的字节数,包含\n
*
**/
ssize_t readLine(int sockfd ,void * buf ,size_t maxline)
{int ret ;int nRead = 0;int left = maxline ;char * pbuf  = (char *) buf;int count  = 0;while(true){//从socket缓冲区中读取指定长度的内容,但并不删除ret = read_peek(sockfd,pbuf,left);// ret = recv(sockfd , pbuf , left , MSG_PEEK);if(ret<= 0)return ret;nRead = ret ;for(int i = 0 ;i< nRead ; ++i){if(pbuf[i]=='\n') //探测到有\n{ret = readn (sockfd , pbuf, i+1);if(ret != i+1)exit(EXIT_FAILURE);return ret + returnCount;}}//如果嗅探到没有\n//那么先将这一段没有\n的读取出来ret  = readn(sockfd , pbuf , nRead);if(ret != nRead)exit(EXIT_FAILURE);pbuf += nRead ;left -= nRead ;count += nRead;}return -1;
}

⑥ 实例程序
下面的链接中包含了上面提到的几种方案的代码,各个函数封装在common.h头文件中,TCP粘包解决方案

文章链接:http://www.cnblogs.com/QG-whz/p/5537447.html

Socket编程(4)TCP粘包问题及解决方案相关推荐

  1. linux下socket编程处理TCP粘包

    一. 数据接收时会出现以下几种情况 一次接收到了客户端发送过来的一个完整的数据包 一次接收到了客户端发送过来的 N 个数据包,由于每个包的长度不定,无法将各个数据包拆开 一次接收到了一个或者 N 个数 ...

  2. TCP粘包|拆包和解决方案

    1 产生原因 TCP是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化算法(Nagle ...

  3. Socket编程 TCP粘包问题及解决方案

    ① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...

  4. Python网络与并发编程 02 TCP粘包

    TCP/Socket与subprocess 我们准备做一个可以在Client端远程执行Server端的shell命令并拿到其执行结果的程序,而涉及到网络通信就必然会使用到socket模块,此外还需要s ...

  5. TCP粘包问题的解决方案01——自定义包体

    粘包问题:应用层要发送数据,需要调用write函数将数据发送到套接口发送缓冲区.如果应用层数据大小大于SO_SNDBUF,那么,可能产生这样一种情况,应用层的数据一部分已经被发送了,还有一部分还在套接 ...

  6. TCP 粘包和拆包及解决方案

    TCP 粘包和拆包基本介绍 1.TCP 是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使 ...

  7. 20-Netty TCP 粘包和拆包及解决方案

    TCP粘包和拆包的基本介绍 TCP是面向连接的, 面向流的, 提供可靠性服务, 收发两端(客户端和服务器端) 都有一一成对的Socket,因此发送端为了将多个发给接收端的包, 更有效的发给对方, 使用 ...

  8. netty编解码器注意事项及粘包和拆包解决方案

    netty编解码器 当 Netty 发送或者接受一个消息的时候,就将会发生一次数据转换.入站消息会被解码:从字节转换为另一种格式(比如 java 对象):如果是出站消息,它会被编码成字节. Netty ...

  9. Day09: socket网络编程-OSI七层协议,tcp/udp套接字,tcp粘包问题,socketserver

    今日内容:socket网络编程     1.OSI七层协议     2.基于tcp协议的套接字通信     3.模拟ssh远程执行命令     4.tcp的粘包问题及解决方案     5.基于udp协 ...

最新文章

  1. Oracle 12C -- 基于sequence的列的默认值
  2. jsr223 java_JSR223 Java使用脚本引擎动态修改业务逻辑
  3. sixth week:third work
  4. java robot 对象_用Java Robot对象实现服务器屏幕远程监视
  5. 实例讲解Oracle数据库设置默认表空间问题
  6. 1006:A+B问题
  7. 信息安全系统设计基础_exp1
  8. Visual Studio 2017发布会:黄金时代的家族聚会
  9. 【Clickhouse】Clickhouse PRIMARY KEY, CONSTRAINT, identifier, column declaration, INDEX
  10. ptaa乘以b_PTA|团体程序设计天梯赛-练习题目题解锦集(C/C++)(持续更新中……)...
  11. html5 实现 图片上传预览
  12. java 有序不重复_Java中自定义有序不重复的集合——SetList
  13. JAVA中两个数组比较可以使用Arrays.equals()
  14. mysql截取字符串最后两位_MySQL截取字段中最后两位不想要的字符串 以及截取函数...
  15. FTP工具FileZilla Client出现中文乱码问题解决
  16. Qt文档阅读笔记-Ping Pong States Example解析
  17. MariaDB的官方手册译文
  18. 翻转课堂十大精彩案例
  19. Python 处理Excel内的数据(案例介绍*2)
  20. leetcode576. 出界的路径数

热门文章

  1. 谷歌出品EfficientNet:比现有卷积网络小84倍,比GPipe快6.1倍
  2. 谷歌、OpenAI 做了一个“魔性AI显微镜”,打算撬开人工智能黑箱
  3. 一个优秀的CIO,应该具备如何的知识体系和管理能力?
  4. 业界丨2018年人工智能和机器学习路在何方? 看看美国公司准备怎么做
  5. 人物丨深度学习大神Hinton推翻自己30年的学术成果另造新世界
  6. torch.bmm()函数的使用
  7. 大脑活动与认知: 热力学与信息论的联系
  8. 论文作者串通抱团、威胁审稿人,ACM Fellow炮轰「同行评审」作弊
  9. 科学研究发现:说谎,是儿童成长的里程碑
  10. 后MATLAB时代的七种开源替代,一种堪称完美!