Socket编程--TCP粘包问题
- TCP是个流协议,它存在粘包问题
- 产生粘包的原因是:
- TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。
- 由于链路层最大发送单元MTU,在IP层会进行数据的分片。
- 应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。
- 粘包问题的解决
- ①:发送定长包
- 这里需要封装两个函数:
ssize_t readn(int fd, void *buf, size_t count)
ssize_t writen(int fd, void *buf, size_t count)
- 这两个函数的参数列表和返回值与
read、write一致。它们的作用的读取/写入count个字节后再返回。其实现如下:ssize_t readn(int fd, void *buf, size_t count)
{
int left = count ; //剩下的字节
char * ptr = (char*)buf ;
while(left>)
{
int readBytes = read(fd,ptr,left);
if(readBytes< )//read函数小于0有两种情况:1中断 2出错
{
if(errno == EINTR)//读被中断
{
continue;
}
return -;
}
if(readBytes == )//读到了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 >)
{
int writeBytes = write(fd,ptr,left);
if(writeBytes<)
{
if(errno == EINTR)
continue;
return -;
}
else if(writeBytes == )
continue;
left -= writeBytes;
ptr += writeBytes;
}
return count;
}有了这两个函数之后,我们就可以使用定长包来发送数据了,我抽取其关键代码来讲诉:
char readbuf[];
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[] ; //数据部分
} - 读写过程如下所示,这里抽取关键代码进行说明:
//发送数据过程
struct packet writebuf;
memset(&writebuf,,sizeof(writebuf));
while(fgets(writebuf.data,sizeof(writebuf.data),stdin)!=NULL)
{
int n = strlen(writebuf.data); //计算要发送的数据的字节数
writebuf.msgLen =htonl(n); //将该字节数保存在msgLen字段,注意字节序的转换
writen(conn,&writebuf,+n); //发送数据,数据长度为4个字节的msgLen 加上data长度
memset(&writebuf,,sizeof(writebuf));
} - 下面是读取数据的过程,先读取msgLen字段,该字段指示了有效数据data的长度。依据该字段再读出data。
memset(&readbuf,,sizeof(readbuf));
int ret = readn(conn,&readbuf.msgLen,); //先读取四个字节,确定后续数据的长度
if(ret == -)
{
err_exit("readn");
}
else if(ret == )
{
printf("peer close\n");
break;
}
int dataBytes = ntohl(readbuf.msgLen); //字节序的转换
int readBytes = readn(conn,readbuf.data,dataBytes); //读取出后续的数据
if(readBytes == )
{
printf("peer close\n");
break;
}
if(readBytes<)
{
err_exit("read");
}
- 在这个方案中,我们需要定义一个‘struct packet’包结构,结构中指明数据部分的长度,用四个字节来表示。发送端的对等方接收报文时,先读取前四个字节,获取数据的长度,由长度来进行数据的读取。定义一个结构体
- ③:粘包解决方案三:按行读取
- ftp协议采用/r/n来识别一个消息的边界,我们在这里实现一个按行读取的功能,该功能能够按/n来识别消息的边界。这里介绍一个函数:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
与read函数相比,recv函数的区别在于两点:
- recv函数只能够用于套接口IO。
- recv函数含有flags参数,可以指定一些选项。
recv函数的flags参数常用的选项是:
- MSG_OOB 接收带外数据,即通过紧急指针发送的数据
- MSG_PEEK 从缓冲区中读取数据,但并不从缓冲区中清除所读数据
为了实现按行读取,我们需要使用recv函数的MSG_PEEK选项。PEEK的意思是"偷看",我们可以理解为窥视,看看socket的缓冲区内是否有某种内容,而清除缓冲区。
/*
* 封装了recv函数
返回值说明:-1 读取出错
*/
ssize_t read_peek(int sockfd,void *buf ,size_t len)
{
while()
{
//从缓冲区中读取,但不清除缓冲区
int ret = recv(sockfd,buf,len,MSG_PEEK);
if(ret == - && errno == EINTR)//文件读取中断
continue;
return ret;
}
} 下面是按行读取的代码: /*
*读取一行内容
* 返回值说明:
== 0 :对端关闭
== -1 : 读取错误
其他:一行的字节数,包含\n
*
**/
ssize_t readLine(int sockfd ,void * buf ,size_t maxline)
{
int ret ;
int nRead = ;
int left = maxline ;
char * pbuf = (char *) buf;
int count = ;
while(true)
{
//从socket缓冲区中读取指定长度的内容,但并不删除
ret = read_peek(sockfd,pbuf,left);
// ret = recv(sockfd , pbuf , left , MSG_PEEK);
if(ret<= )
return ret;
nRead = ret ;
for(int i = ;i< nRead ; ++i)
{
if(pbuf[i]=='\n') //探测到有\n
{
ret = readn (sockfd , pbuf, i+);
if(ret != i+)
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 -;
}
- ftp协议采用/r/n来识别一个消息的边界,我们在这里实现一个按行读取的功能,该功能能够按/n来识别消息的边界。这里介绍一个函数:
Socket编程--TCP粘包问题的更多相关文章
- socket编程 TCP 粘包和半包 的问题及解决办法
一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...
- python/socket编程之粘包
python/socket编程之粘包 粘包 只有TCP有粘包现象,UDP永远不会粘包. 首先需要掌握一个socket收发消息的原理 发送端可以是1k,1k的发送数据而接受端的应用程序可以2k,2k的提 ...
- Python全栈-网络编程-TCP粘包
一.什么是TCP粘包 C/S架构下,接收方不知道每个消息的发送间隙.也不知道每次应该提取多少个字节的数据,与此同时,TCP是面向连接的,面向流的,收发两端都要有,因此发送端为了将多个发往接收端的数据包 ...
- socket编程解决粘包和丢包问题
##socket 丢包粘包解决方式 采用固定头部长度(一般为4个字节),包头保存的是包体的长度 header+body 包头+包体 下面的例子不是按照上图中规定的格式编写的,但是思路都是一样的,先读出 ...
- Socket编程 Tcp和粘包
大多数程序员都要接触网络编程,Web开发天天和http打交道.稍微底层一点的程序员,就是TCP/UDP . 对程序员来说,Tcp/udp的核心是Socket编程. 我的浅薄的观点---------理解 ...
- Socket编程(4)TCP粘包问题及解决方案
① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...
- Socket编程实践(5) --TCP粘包问题与解决
TCP粘包问题 由于TCP协议是基于字节流且无边界的传输协议, 因此很有可能产生粘包问题, 问题描述如下 对于Host A 发送的M1与M2两个各10K的数据块, Host B 接收数据的方式不确定, ...
- 查漏补缺:socket编程:TCP粘包问题和常用解决方案(上)
1.TCP粘包问题的产生(发送端) 由于TCP协议是基于字节流并且无边界的传输协议,因此很容易产生粘包问题.TCP的粘包可能发生在发送端,也可能发生在接收端.发送端的粘包是TCP协议本身引起的,TCP ...
- 【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案
引子 现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客 ...
随机推荐
- 【整理】C++中的unique函数
之前总结了一下我觉得有用的erase,lower_bound,upper_bound. 现在总结一下unique,unique的作用是“去掉”容器中相邻元素的重复元素(不一定要求数组有序),它会把重复 ...
- 关于overflow:hidden
(本文只针对hidden这个值的用处进行阐述) 关于overflow:hidden;很多人都知道他是溢出隐藏的一个属性,但是并不是很多人知道它的一些神奇的地方!首先先讲一下众所周知的溢出隐藏吧! 溢出 ...
- Codeforces 808D. Array Division
题目大意 给定你一个长为\(n\)的序列,问能否在最多一次取出某一元素然后插入到某一点后可以将整个序列分成两段使得其两段的元素之和相同. \(n \leq 10^5\) 题解 发现插入操作实际上是让某 ...
- J2EE分布式服务基础之RPC
一.RPC介绍 什么是RPC 远程过程调用(RPC)是一个协议,程序可以使用这个协议请求网络中另一台计算机上某程序的服务而不需知道网络细节. RPC模型 C/S模式 基于传输层协议 (例如 TCP/I ...
- Linux终端录屏与播放 script 命令
本文由Suzzz原创,发布于 http://www.cnblogs.com/Suzzz/p/4107700.html ,转载请保留此声明. 有时候可能想要记录在终端的所有操作包括输出等,将来作为视频播 ...
- hdu 2899 Strange fuction——模拟退火
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2899 还可三分.不过只写了模拟退火. #include<iostream> #include& ...
- [转载]Ubuntu下ssh服务的安装与登陆(ssh远程登陆)
转载地址:http://blog.csdn.net/zht666/article/details/9340633 Ubuntu默认并没有安装ssh服务,如果通过ssh远程连接到Ubuntu,需要自己手 ...
- Java类与继承
Java:类与继承 对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础.抽象.封装.继承.多态这四大特性都离不开类,只有存在类,才能体现面向对象编程的特点,今天我们就来了解一些类与继承的相 ...
- MyBatis 批量插入数据对插入记录数的限制
<基于 MyBatis 框架的批量数据插入的性能问题的探讨>(作者:魏静敏 刘欢杰 来源:<计算机光盘软件与应用> 2013 年第 19 期)中提到批量插入的记录数不能超过10 ...
- Redis 复制技术和高可用sentinel(哨兵模式)
redis的复制技术和高可用(哨兵模式) 1 复制 为什么要复制 实现数据的多副本存储,从而可以实现服务的高可用 提供更好的读性能复制技术的关键点及难点 如何指定被复制对象 增量还是全量以及如何实现增 ...