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. 如何使用phpredis连接Redis的方法

    本文跟大家介绍使用同一VPC内弹性云服务器ECS上的phpredis连接Redis的方法. 更多的客户端的使用方法,请参考https://redis.io/clients 前提条件 已成功申请Redi ...

  2. AirSim的搭建和使用

    由于自己使用设备拍摄的数据质量太差,所以决定使用AirSim这个框架来生成数据.之所以使用这个框架,是因为之前同事用其生成了一些有效数据. 当然,我是不可能把我搭建的步骤一一写出来的,一来是因为太麻烦 ...

  3. How to pass an Amazon account review

    Have you ever sold products on Amazon? How about sold so much within the first week that amazon deci ...

  4. ES6的新特性(21)——Proxy

    Proxy 概述 Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程. Proxy 可以理解成,在目标对 ...

  5. 卡特兰数(Catalan)及其应用

    卡特兰数 大佬博客https://blog.csdn.net/doc_sgl/article/details/8880468 卡特兰数是组合数学中一个常出现在各种计数问题中出现的数列. 卡特兰数前几项 ...

  6. 吴恩达机器学习笔记——正规方程(Normal Equation)

    问题描述:m examples : (x(1),y(1)), (x(2),y(2)),..., (x(m),y(m)) and n features; 计算方法:θ = (XTX)-1XTy; 计算过 ...

  7. 互评Alpha版本——二次元梦之队——“I Do”

    基于NABCD评论作品,及改进建议 1.根据(不限于)NABCD评论作品的选题 (1)N(Need,需求) 随着智能科技的发展和普及,编程教育的重要性已经逐渐凸显出来.美国前总统奥巴马曾说“编程应当与 ...

  8. 1019psp

    1.本周psp: 2.本周进度条: 3.累计进度图(折线图): 4.psp饼状图:

  9. mysql You can't specify target table 'xxx' for update in FROM clause

    含义:您不能在子句中为更新指定目标表'xxx'. 错误描述:删除语句中直接含select,如下: DELETE FROM meriadianannotation WHERE SeriesID IN ( ...

  10. MyEclipse快捷方式

    选择你要注释的那一行或多行代码,按Ctrl+/即可,取消注释也是选中之后按Ctrl+/即可. 如果你想使用的快捷键的注释是的话,那么你的快捷键是ctrl+shift+/我以前都是手动注释的,直接打// ...