十一、day11
下午学习如何使用asio库用另一种比较简便的方式处理粘包问题,之前有提过一种粘包处理方式:通过async_read_some函数监听读事件,并在读事件的回调函数HandleRead中对数据进行处理
1 2
| _socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, SharedSelf()));
|
async_read_some 这个函数的特点是只要对端发数据,服务器接收到数据,即使没有收全对端发送的数据也会触发HandleRead函数,所以我们会在HandleRead回调函数里判断接收的字节数,接收的数据可能不满足头部长度,可能大于头部长度但小于消息体的长度,可能大于消息体的长度,还可能大于多个消息体的长度,所以要切包等,这些逻辑写起来很复杂,所以我们可以通过读取指定字节数,直到读完这些字节才触发回调函数,那么可以采用async_read函数,这个函数指定读取指定字节数,只有完全读完才会触发回调函数。
详细代码步骤可参考
爱吃土豆:网络编程(7)——粘包问题1 赞同 · 0 评论文章
今天学习如何使用async_read函数,监听读事件获取指定字节数才触发回调函数,用这种办法处理粘包问题很简单。
参考:
恋恋风辰官方博客
visual studio配置C++ boost库_哔哩哔哩_bilibili
服务器
1)start()
原本的Start函数中,需要将数据读取至缓存区data中(最大1024*2),然后调用回调函数进行切包处理。
本节中,数据不继续往缓存区中存储,而是使用async_read函数读取指定字节后调用回调头函数或者回调消息体函数,数据直接往构造的头结点**_recv_head_node或者消息体节点_recv_msg_node**存储。
1 2 3 4 5 6 7 8
| void CSession::Start(){ _recv_head_node->Clear(); boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH), std::bind(&CSession::HandleReadHead, this, std::placeholders::_1, std::placeholders::_2, SharedSelf())); }
|
注释部分为第一个处理粘包方式的部分代码,需要把数据往缓存区中存储,然后判断是否满足头结点_recv_head_node大小(2字节),然后将缓冲区中的数据填充头结点,根据头结点建立指定大小的消息体结点_recv_msg_node,缓存区数据往_recv_msg_node中存储。
本节中,首先将头结点初始化,然后调用异步读async_read往头结点_recv_head_node读取指定长度的数据(2字节),读取结束后调用回调头函数(2字节代表头结点已经读取完毕,根据头结点内容构建消息体结点)。
2)回调头函数
回调头函数中,头结点数据需要通过boost库自带的字节序处理函数将网络序转换为本地序、protobuf进行序列转换或者jsoncpp进行序列转换,这里为了方便展示第二张粘包处理方式,没有写出来。
当头部消息序列转换完成之后,根据头结点内容构建消息体_recv_msg_node ,然后调用异步读函数async_read往_recv_msg_node 读取指定头结点内容的数据,读取完毕后调用回调消息体函数HandleReadMsg。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| void CSession::HandleReadHead(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self) { if (!error) { if (bytes_transferred < HEAD_LENGTH) { cout << "read head length error!"; Close(); _server->ClearSession(_uuid); return; }
short data_len = 0; memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH); cout << "data len is " << data_len << endl;
if (data_len > MAX_LENGTH) { cout << "invalid data length is " << data_len << endl; _server->ClearSession(_uuid); return; }
_recv_msg_node = make_shared<MsgNode>(data_len); boost::asio::async_read(_socket, boost::asio::buffer(_recv_msg_node->_data, _recv_msg_node->_total_len), std::bind(&CSession::HandleReadMsg, this, std::placeholders::_1, std::placeholders::_2, SharedSelf())); } else { cout << "handle read head failed, error is " << error.what() << endl; Close(); _server->ClearSession(_uuid); return; }
}
|
3)回调消息体函数
这里为了展示粘包方式的有效性,间接性睡眠2s造成数据堆积,验证切包的有效性。
消息体中填充完指定长度数据之后,在末尾添加’\0’形成c风格字符串,并将其回传。
回传后再次执行异步读函数,往头结点读取指定长度(2字节)的数据,并绑定回调函数HandleReadHead。如此循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void CSession::HandleReadMsg(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self) { if (!error) { PrintRecvData(_data,bytes_transferred); std::chrono::milliseconds dura(2000); std::this_thread::sleep_for(dura); _recv_msg_node->_data[_recv_msg_node->_total_len] = '\0'; cout << "receive data is " << _recv_msg_node->_data << endl; Send(_recv_msg_node->_data, _recv_msg_node->_total_len); _recv_head_node->Clear(); boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH), std::bind(&CSession::HandleReadHead, this, std::placeholders::_1, std::placeholders::_2, SharedSelf())); } else { cout << "handle read msg failed, error is " << error.what() << endl; Close(); _server->ClearSession(_uuid); return; } }
|
客户端
客户端代码和下面文章中的代码相同,同样,也不进行字节序处理。
爱吃土豆:网络编程(7)——粘包问题1 赞同 · 0 评论文章