自学内容网 自学内容网

网络编程(11)——另一种简便的粘包处理方式

十一、day11

下午学习如何使用asio库用另一种比较简便的方式处理粘包问题,之前有提过一种粘包处理方式:通过async_read_some函数监听读事件,并在读事件的回调函数HandleRead中对数据进行处理

_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函数,监听读事件获取指定字节数才触发回调函数,用这种办法处理粘包问题很简单。

服务器

1)start()

原本的Start函数中,需要将数据读取至缓存区data中(最大1024*2),然后调用回调函数进行切包处理。

本节中,数据不继续往缓存区中存储,而是使用async_read函数读取指定字节后调用回调头函数或者回调消息体函数,数据直接往构造的头结点_recv_head_node或者消息体节点_recv_msg_node存储。

void CSession::Start(){
//::memset(_data, 0, MAX_LENGTH);
//_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, 
//std::placeholders::_1, std::placeholders::_2, SharedSelf()));
_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。

void CSession::HandleReadHead(const boost::system::error_code& error, size_t  bytes_transferred, std::shared_ptr<CSession> shared_self) {
if (!error) {
// async_read只有当读取指定字节后才会调用异步读,所以在没有出错的情况下,bytes_transferred一定等于2字节,但出错了可能就小于2字节
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。如此循环。

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 评论文章


原文地址:https://blog.csdn.net/m0_63086198/article/details/142577803

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!