网络游戏粘包处理

int TcpClient::onRecv()
{
int error = ::recv(m_fd, m_recvBuffer+m_recvOffset, m_recvBufferSize-m_recvOffset, 0);
//m_recvBuffer = new char[m_recvBufferSize];//2k 接收消息的字符数组,开辟了2K的空间


if (error > 0)
{
m_recvOffset += error;//此次接收消息在m_recvBuffer中的终点位置 m_recvOffset 如果大于2K那就err
if (m_recvOffset > m_recvBufferSize)
{
LOG_INFO("TcpClient to ip %s m_recvOffset %d > m_recvBufferSize %d\n", m_serverAddr.getIP().c_str(), m_recvOffset, m_recvBufferSize);
error = SOCKET_ERROR;
}
else
{
decodeRecvBuffer();//处理
}
}
return error;
}


void TcpClient::decodeRecvBuffer()
{
//decodeOffset 当前处理消息的偏移位置,前面的都处理完了,后面的待处理
int decodeOffset = 0;
//m_decodeState 记录处理状态,是消息头呢还是消息体,(为何要记录,因为粘包可能一次处理的是半个消息,下次处理的消息需要记录上次处理到哪了)
//处理消息头,m_recvOffset-decodeOffset (接收的偏移-已处理的偏移=剩下待处理长度)不够7,说明数据头都没接收完,头必须长度够7字节才能解析正确,不处理,等下次接收完再处理
//处理消息体,消息体无所谓大于0即可
while ((m_decodeState == DECODE_HEAD && m_recvOffset-decodeOffset >= NET_PACKET_HEAD_LEN) || (m_decodeState == DECODE_DATA && m_recvOffset-decodeOffset >0))
{
if (m_decodeState == DECODE_HEAD)
{
//走到这里的 肯定是完整的消息头,前面已经判断,因此解析正确,type+proc+size
uint8_t packType = *((uint8_t*)(m_recvBuffer+decodeOffset));
uint16_t proc = *((uint16_t*)(m_recvBuffer+decodeOffset+1));
uint32_t datalen = *((uint32_t*)(m_recvBuffer+decodeOffset+3));


if (static_cast<int>(datalen) > m_recvBufferSize)
{
LOG_INFO("TcpClient::decodeRecvBuffer very large packet packType = %d, proc = %d, datalen = %d, m_recvBufferSize = %d\n", static_cast<int>(packType), static_cast<int>(proc), datalen, m_recvBufferSize);
}


decodeOffset += NET_PACKET_HEAD_LEN;
//从池子里面取出长度为datalen的m_recievePacket(实际上就是一个char[2048]的区域)的钥匙(char*)(池子里面是大于2K单独new,小于就给你2K的)
//这个m_recievePacket就是我收到的数据包,用于还原TcpServer发送的数据包,
m_recievePacket = m_bufferPool.pop(datalen);
m_recievePacket->setType(static_cast<PackType>(packType));
m_recievePacket->setProc(proc);
m_recievePacket->setSize(datalen);

m_decodeState = DECODE_DATA;


//头部解析完,decodeOffset+=7,我已经处理了7个字节,下面该处理消息体了,m_decodeState置为DECODE_DATA
}
// 处理消息体 前提一定是m_recievePacket不为空,也就是上面头部已经处理完,m_recievePacket也有了
//第一次就这样,如果走这里,说明前面的消息体没有处理完,这次接着处理,
if (m_decodeState == DECODE_DATA && m_recievePacket != NULL)
{
//m_recievePacket因为上面已经把头放进去了,因此getSize得到的就是这个消息包实际该有的大小,getWOffset就是写偏移,
//没写,写偏移指向7(头后面位置),写了多少,指向多少
uint32_t needlen = m_recievePacket->getSize() - m_recievePacket->getWOffset();
//haslen表示我现在读到的buffer里面拥有的数据,
//可能这个数据包还没发完,只发了一部分,因此haslen <= needlen,或者socket发了还有后面其他消息的一部分,那么haslen >= needlen
//如果这次只读了一部分消息体,那么下次依然处理消息体
//needlen则是你只把你需要的数据处理完就得了,包大小-写偏移,如果为0说明包已经完整了,
//只有needlen全部写完才说明这个消息包完完全全的接受并处理完毕,即得到和发送方一个一模一样的消息包
uint32_t haslen = m_recvOffset - decodeOffset;
uint32_t writelen = (std::min)(needlen, haslen);
//把buffer里面decodeOffset往后writelen的memcpy到m_recievePacket,、
//第一次是从头写,后面写从decodeOffset往后写writelen,因为之前已经把m_recvBuffer的前decodeOffset写进去了
//至于m_recievePacket里面是从哪里写,m_recievePacket自己会记录写偏移的,不用操心
m_recievePacket->write(m_recvBuffer+decodeOffset, writelen);
decodeOffset += writelen;
//写完 decodeOffset+=writelen,下次就从新的decodeOffset处开始拷贝
if (m_recievePacket->getSize() == m_recievePacket->getWOffset())
{
//写偏移等于包大小,说明数据包已全部写入,
//m_recievePacket已经是一个完整的TcpServer当时发的消息包的拷贝了,交给游戏逻辑去处理吧
//游戏逻辑那里得到了这块内存区域的指针。作为参数传递过去
onNetPacket(m_recievePacket);
//因此这个控制权在这里没什么用了,置空,准备迎接新的,作为下个接收消息包的钥匙吧
m_recievePacket = NULL;
//decodeState状态改变,因为下面接收肯定是先解析消息头
m_decodeState = DECODE_HEAD;
}
//如果没有进入上面的if 说明消息体部分没有写完,那么while循环继续把buffer里面的内容往m_recievePacket里面放
}
}


if (decodeOffset < m_recvOffset)
{
//相等说明所有buffer里面的已经完全处理到m_recievePacket里面了,
//为什么会有尚未处理的呢?
//比如剩了2个字节,小于7,肯定是下面消息的半个头
//剩了些消息待处理,比如10字节buffer,只处理了8字节,剩3自己,那么把buffer剩下的2字节拷到起始位置,
//只有这2字节有用,前面的都处理了所以没用了,后面接收消息直接覆盖。
//memcpy(buffer,buffer+8,2); 把剩下的消息拷贝过来,memmove重合区域可以 memcpy不行
memmove(m_recvBuffer, m_recvBuffer+decodeOffset, m_recvOffset-decodeOffset);
}
//正常情况下m_recvOffset-decodeOffset 回到起始位置,因为buffer里面的内容已经处理完了
//但是比如 10-8=2,比如消息体处理完,然后还有一个消息头,但是长度只有2,明显只接了一部分,那么肯定是不处理,因此接收偏移为2
//下次从 2 这个偏移位置继续接收消息,连同前面buffer里面剩的2字节,凑够7字节在下回合处理
//消息体没接收完同理,
m_recvOffset = m_recvOffset - decodeOffset;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值