TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,

因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,

然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,

由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,

在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

TCP粘包我总结了几种情况

tcp发送端发送三个包过来,tcp接收缓存区收到了这三个包,而用户的读写缓存区比这三个包的总大小还大,

此时数据是接受完全的,用户缓存区读到三个包需要分开,这是比较好处理的。

第二种情况是因为用户的接收缓存区比tcp接受缓存区大,或者比tcp目前接收到的总数据大,那么用户缓存区读到

的数据就是tcp接收缓存区的数据,这是第一种情况的特例,这种情况需要判断那些包接受完全,那些包没接受完全。

第三种情况是用户的接受缓存区比tcp接受缓存区要小,导致用户缓存区读到的数据是tcp接收缓存区

的一部分,这其中有完整的包,也有残缺的包。

第四种情况是第三种情况的一个特例,用户缓存区的数据是不完全的,只是tcp缓存区的一部分。

对应特别大的那种包。

我提倡的解决办法就是首先实现一套从tcp缓存区中读取数据的数据结构和算法,因为tcp是面向

字节流的,将tcp缓存区中的数据读到用户缓存区里,这里我简单叫做outstreambuffer和instreambuffer,

这两个结构一个用于向tcp写,一个用于从tcp读。把tcp缓存区的数据尽可能多的读出来,不要判断是否是

完整的包,保证tcp缓存区没数据,这样会减少tcp粘包几率。

第二部就是将读到的数据,也就是instreambuffer中的数据进行分割,我叫做切包,切出一个个完整的包,

剩余不完整的留着下次继续接收。

第三步服务器应用层接口从instreambuffer中读取切割好的完整的包进行逻辑处理。

所以为了处理粘包和切包,需要我们自己设计包头,我设计的包头是八字节的结构体,

包含四字节的包id和四字节的包长度,这个长度既可以表示包头+消息体的长度,

也可以表示后面消息体的长度。我设计的是表示后面消息体的长度。

而上面所说的instreambuffer和outstreambuffer用户可以自己设计实现,也可以

利用成熟的网络库,我用的是libevent中的bufferevent,bufferevent实现了类似

的instreambuffer和outstreambuffer。

我设计的服务器部分代码如下,感兴趣可以去git下载:

https://github.com/secondtonone1/smartserver

简单列举下接收端处理读数据的过程。

void NetWorkSystem::tcpread_cb(struct bufferevent *bev, void *ctx)
{
getSingleton().dealReadEvent(bev, ctx);
}

networksystem是单例模式,处理读事件。因为静态函数tcpread_cb是libevent

设计格式的回调处理函数,在静态函数中调用非静态函数,我采用了单例调用。

void NetWorkSystem::dealReadEvent(struct bufferevent *bev, void *ctx)
{ //
evutil_socket_t bufferfd = bufferevent_getfd(bev);
std::map<evutil_socket_t, TcpHandler *>::iterator tcpHandlerIter = m_mapTcpHandlers.find(bufferfd);
if(tcpHandlerIter != m_mapTcpHandlers.end())
{
tcpHandlerIter->second->dealReadEvent();
}
}

tcphandler是我设计的切包类,这里通过bufferfd找到对应的instream和outstream,从而处理里面的数据完成切包。

//处理读事件
void TcpHandler::dealReadEvent()
{
evbuffer * inputBuf = bufferevent_get_input(m_pBufferevent);
size_t inputLen = evbuffer_get_length(inputBuf); while(inputLen > )
{
//tcphandler第一次接收消息或者该node接收完消息,需要开辟新的node接受消息
if(!m_pLastNode || m_pLastNode->m_nMsgLen <= m_pLastNode->m_nOffSet)
{
//判断消息长度是否满足包头大小,不满足跳出
if(inputLen < PACKETHEADLEN)
{
break;
} char data[PACKETHEADLEN] = {};
bufferevent_read(m_pBufferevent, data, PACKETHEADLEN);
struct PacketHead packetHead; memcpy(&packetHead, data, PACKETHEADLEN); cout << "packetId is : " <<packetHead.packetID << endl; cout << "packetLen is : " << packetHead.packetLen << endl; insertNode(packetHead.packetID, packetHead.packetLen); inputLen -= PACKETHEADLEN;
} //考虑可能去包头后剩余的为0
if(inputLen <= )
{
break;
}
//读取去除包头后剩余消息
tcpRead(inputLen);
} }

这个函数判断是否读完一个消息,读完就开辟新的节点存储新来的消息,否则就将新来的消息放入没读完的节点里。

void TcpHandler::tcpRead(UInt32 &inputLen)
{
//node节点中的数据还有多少没读完
UInt32 remainLen = m_pLastNode->m_nMsgLen - m_pLastNode->m_nOffSet; UInt32 readLen = bufferevent_read(m_pBufferevent, m_pLastNode->m_pMsg + m_pLastNode->m_nOffSet, remainLen);
//统计bufferevent 的inputbuffer中剩余的长度
inputLen -= readLen;
//更改偏移标记
m_pLastNode->m_nOffSet += readLen;
//判断读完
if(m_pLastNode->m_nOffSet >= m_pLastNode->m_nMsgLen)
{
m_pLastNode->m_pMsg[m_pLastNode->m_nMsgLen + ] = '\0';
cout << "receive msg is : " << m_pLastNode->m_pMsg << endl;
//cout <<"read times is : " << ++readtimes<< endl;
} }

我的服务器还在完善中,目前已经能处理连续收到1万个包的切包和大并发的问题了,最近在设计应用层的序列化

和应用层消息回调。感兴趣可以下载看看,下载地址:https://github.com/secondtonone1/smartserver

我的微信公众号平台,谢谢关注:

TCP粘包处理的更多相关文章

  1. Socket编程(4)TCP粘包问题及解决方案

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

  2. Netty(三)TCP粘包拆包处理

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...

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

    TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...

  4. Netty的TCP粘包/拆包(源码二)

    假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况: 1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包. 2.服 ...

  5. netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...

  6. TCP粘包/拆包问题

    无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...

  7. tcp粘包问题(封包)

    tcp粘包分析     http://blog.csdn.net/zhangxinrun/article/details/6721495 解决TCP网络传输“粘包”问题(经典)       http: ...

  8. 1. Netty解决Tcp粘包拆包

    一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...

  9. TCP 粘包/拆包问题

    简介    TCP 是一个’流’协议,所谓流,就是没有界限的一串数据. 大家可以想想河里的流水,是连成一片的.期间并没有分界线, TCP 底层并不了解上层业务数据的具体含义 ,它会根据 TCP 缓冲区 ...

  10. TCP粘包/拆包问题的解决

    TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...

随机推荐

  1. Scrum立会报告+燃尽图(十月二十九日总第二十次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2288 项目地址:https://git.coding.net/zhang ...

  2. Scrum立会报告+燃尽图(十月十四日总第五次):前期宣传工作进行中

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2195 Scrum立会master:段晓睿 一.小组介绍 组长:付佳 组员 ...

  3. 关于解决MySort

    关于解决MySort 那天老师教给我们关于sort的用法以及String类中的split方法.在一定程度上告诉我们sort用法的原理和一些特别的用法后,老师叫我们用JAVA尝试去设计一个"M ...

  4. C++总结作业

    在博客作业中学到的内容 在博客作业中,首先我学到了Markdown编辑器的排版格式,github的上传,如果没有博客作业,可能根本不会接触到这些内容.然后,就C++来讲,我在博客作业中学会了命令行参数 ...

  5. NSURLErrorDomain错误代码

    kCFURLErrorUnknown = -998, kCFURLErrorCancelled = -999, kCFURLErrorBadURL = -1000, kCFURLErrorTimedO ...

  6. virtualbox 5.0.6 在debian jessie amd64启动报错

    通过dmesg发现vboxdrv启动报错: [ 18.844888] systemd[1]: [/lib/systemd/system/vboxdrv.service:5] Failed to add ...

  7. Scrum Meeting Beta - 4

    Scrum Meeting Beta - 4 NewTeam 2017/12/2 地点:新主楼F座二楼 任务反馈 团队成员 完成任务 计划任务 安万贺 完成了部分页面标题栏颜色的修改和字体的调整Iss ...

  8. 补发9.26“天天向上”团队Scrum站立会议

    组长:王森 组员:张金生 张政 栾骄阳 时间:2016.09.26 地点:612寝 组员 已完成 未完成 王森 可行性分析 找出设计亮点 张金生 寻找UI素材 设计用户操作 张政 搭建环境 基础逻辑框 ...

  9. Win2019 + Oracle18c SQLPLUS 命令行出现乱码的解决

    1. Win2019 中文版 安装了 Oracle数据库, dbca 建库时选择的 的字符集是 ZHS16GBK 然后发现使用sqlplus 时有乱码的现象如图示: 2. csdn 上面有一个博客有解 ...

  10. phpMyadmin导入导出数据中出现的错误处理

    1 2