在这篇短文之前,有一篇:
c++服务器 拆包粘包 过程(1)
这是我第一篇发的关于TCP socket 服务器拆粘包的代码,这个代码有些繁琐。下面我再传我修改后的第二版的这个例子,这个例子也是经过了几万次的消息收发的运行以及调试后的代码,尽管不是最好,也希望能帮助一些朋友。等我修改完毕第三次的时候,会再次更新。
void TCPReceiveManager::AsynReceive(__tcp_phd_weak_ptr phd,
Common::__cr_boost_ec error, std::size_t bytes_transferred)
{
if (_func_add == NULL){
/* 上层没有设置消息接收接口,那么这个消息无法传递,所以释放 */
Release(phd);
return;
}
__tcp_phd *tcpphd = static_cast<__tcp_phd*>(phd.lock().get());
Common::__tcp_skt* skt = tcpphd->_socket;
// 设置总共收到的消息的长度,必须要用+=,而不是=
tcpphd->_offset += bytes_transferred;
if (Receive(tcpphd->_buffer, bytes_transferred, phd)){
// 投递接收操作
PostReceive(skt);
return;
}
// 这里不需要再次释放,因为不bytes_transferred是不是0,收到的这条消息
// 都会被加入到消息队列,通知上层,这里释放的话,那么上层在释放的时候会造
// 成多次释放以至于崩溃的结果
// Release(phd);
}
/*
* t:表明还要投递接收包
* f:表明不用再次投递接收包,socket已经断开连接
*/
bool TCPReceiveManager::Receive(char* buffer,
std::size_t bytes_transferred, Common::__tcp_phd_weak_ptr phd)
{
if (bytes_transferred == 0){
_func_add(phd);
// NETLOG(__type_ec::ec_general, "user offline.", LOG_TAG);
return false;
}
typedef MessageInterface::TCPMessageHeader* header;
#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
int loopCnt = 100;
#endif
do
{
#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
--loopCnt;
if (!loopCnt){
break;
}
#endif
header tmpPacket = (header)buffer;
__tcp_phd *tcpphd = static_cast<__tcp_phd*>(phd.lock().get());
if (tmpPacket->_packet_size > tcpphd->_buffer_size){
NETLOG(__type_ec::ec_error,
"the header size is wrong when recv a packet.close the socket.", LOG_TAG);
/* 通知上层,这个客户端要被关闭! */
tcpphd->_offset = 0;
_func_add(phd);
return false;
}
if (tcpphd->_offset < HEADSIZE){
/* 消息不全,跳转到[2]继续读取消息 */
goto FALG_2;
}
/*[1]*/if (tmpPacket->_packet_size == tcpphd->_offset){
/* 消息包完整 */
_func_add(phd);
return true;
}
/*[2]*/else if (tcpphd->_offset < tmpPacket->_packet_size){
/*
* 收到的消息不全,投递消息句柄继续接收
*/
FALG_2:
PostReceive(phd);
/* 这里不需要投递接收消息 */
return false;
}
/*[3]*/else if (tcpphd->_offset > tmpPacket->_packet_size && tmpPacket->_packet_size != 0){
__tcp_phd_weak_ptr tmpPtr = Get();
__tcp_phd *tmptcpphd = static_cast<__tcp_phd*>(tmpPtr.lock().get());
if (!tmptcpphd){
/* 如果分配内存失败,关闭客户端连接 */
NETLOG(__type_ec::ec_error, "get a chunk failed.", LOG_TAG);
tcpphd->_offset = 0;
_func_add(phd);
return false;
}
/* 创建一个新的数据包,这个数据包肯定是完整的数据包 */
tmptcpphd->_socket = tcpphd->_socket;
tmptcpphd->_offset = tmpPacket->_packet_size;
/* 创建一个新的完整的数据包 */
memcpy(tmptcpphd->_buffer, buffer, bytes_transferred);
/* 剩余的数据部分,把buffer指针指向剩余的数据的部分的开头 */
/* 以tmptcpphd->_buffer作为交换区域,把buffer中尾部的数据移动到开头 */
memcpy(buffer, tmptcpphd->_buffer + tmpPacket->_packet_size, bytes_transferred - tmpPacket->_packet_size);
bytes_transferred -= tmptcpphd->_offset;
tcpphd->_offset -= tmptcpphd->_offset;
/* 消息处理 */
_func_add(tmpPtr);
/* 继续循环解析消息 */
continue;
}
/*[4]*/else{
/*
* 出现这种情况呢,一般来说,正常的情况下是收到的消息/或者可能是拆包后剩下的消息长度达
* 不到包头的长度,导致消息解析失败.跳转到[2]再次去读取消息
*/
goto FALG_2;
}
} while (true);
#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
if (!loopCnt){
NETLOG(__type_ec::ec_error,
"the loop performs 100 times that it unpacks and splices tcp message packet."
"Generally, it is impossible because no packets so much big there are."
"it maybe occur a error to cause infinite loop.", LOG_TAG);
assert(false);
}
#endif
NETLOG(__type_ec::ec_error,
"it should return before this line if anything correctly.", LOG_TAG);
assert(false);
return false;
}
上面的代码依然是2个函数,不同于第一个版本,这个版本的(数据句柄)用的是智能指针,并且数据封装的包头也是简化了,第一版的太烦。
针对这一期的代码:
使用的智能指针,因为一开始使用smt-ptr,所以呢,用的不太熟而且这个smt-ptr是后来加上去的,所以自我感觉这个使用的都是乱七八糟的。
而且在c++11中,对于复制和移动都有优化,我这里还没有使用到。
-----
下面对于包的数据结构说明下:
本次的包的数据结构是:
/*
* TCP包头定义
* 这个包头只含一个int成员,用来标志整个包的大小,负责TCP包的拆包/粘包过程。
* 后面定义的所有的TCP消息包,都必须继承此对象
* 4bytes
*/
struct TCPMessageHeader
{
std::size_t _packet_size : 32/*bit*/;
};
#define HEADSIZE /*sizeof(TCPMessageHeader)*/4
就像上面的代码,包头的前4个字节定义了包的总大小,后面的数据为内容。包头是可以强转为int*的。这样可以直观的判断。
------
ok!就这样。