本节主要开始的是对于异步服务器的基础的一些API的认识
消息节点
首先定义一个消息节点:
class MsgNode
{
public:
MsgNode(const char* msg, int total_len)
: _total_len(total_len), _cur_len(0)
{
_msg = new char[total_len];
memcpy(_msg, msg, total_len);
}
MsgNode(int total_len)
: _total_len(total_len), _cur_len(0)
{
_msg = new char[total_len];
}
~MsgNode()
{
delete[] _msg;
}
int _total_len;
int _cur_len;
char* _msg;
};
异步写操作
有风险的操作
// 有问题的发送数据逻辑
void Session::WriteCallBackErr(const boost::system::error_code& ec, size_t bytes_transferred, std::shared_ptr<MsgNode> msg_node)
{
// 已经发送的长度和要求发送的长度做对比
if (bytes_transferred + msg_node->_cur_len < msg_node->_total_len)
{
_send_node->_cur_len += bytes_transferred;
this->_socket->async_write_some(asio::buffer(_send_node->_msg + _send_node->_cur_len,
_send_node->_total_len - _send_node->_cur_len),
bind(&Session::WriteCallBackErr, this, placeholders::_1, placeholders::_2, _send_node));
}
}
void Session::WriteToSocketErr(const string& buf)
{
_send_node = make_shared<MsgNode>(buf.c_str(), buf.size());
this->_socket->async_write_some(asio::buffer(_send_node->_msg, _send_node->_total_len),
bind(&Session::WriteCallBackErr, this, placeholders::_1, placeholders::_2, _send_node));
}
那么来看看,这里可能造成错误的原因是什么呢?
因为async_write_some回调函数返回已发送的字节数可能并不是全部长度。比如TCP发送缓存区总大小为8字节,但是有3字节未发送(上一次未发送完),这样剩余空间为5字节
此时我们调用async_write_some发送hello world!实际发送的长度就是为5,也就是只发送了hello,剩余world!通过我们的回调继续发送。
而实际开发的场景用户是不清楚底层tcp的多路复用调用情况的,用户想发送数据的时候就调用WriteToSocketErr,或者循环调用
WriteToSocketErr,很可能在一次没发送完数据还未调用回调函数时再次调用WriteToSocketErr,因为boost::asio封装的时epoll和iocp等多路复用模型,当写事件就绪后就发数据,发送的数据按照async_write_some调用的顺序发送,所以回调函数内调用的async_write_some可能并没有被及时调用
比如我们如下代码
//用户发送数据
WriteToSocketErr("Hello World!");
//用户无感知下层调用情况又一次发送了数据
WriteToSocketErr("Hello World!");
那么可能造成的效果是:
那么很可能第一次只发送了Hello,后面的数据没发完,第二次发送了Hello World!之后又发送了World!
所以对端收到的数据很可能是”HelloHello World! World!”
那怎么解决这个问题呢,我们可以通过队列保证应用层的发送顺序。我们在Session中定义一个发送队列,然后重新定义正确的异步发送函数和回调处理
递归进行正确的消息发送
定义了bool变量_send_pending,该变量为true表示一个节点还未发送完。
_send_queue用来缓存要发送的消息节点,是一个队列。
我们实现异步发送功能
// 正确的发送数据逻辑
void Session::WriteCallBack(const boost::system::error_code& ec, size_t bytes_transferred)
{
if (ec.value())
{
cout << "error code is " << ec.value() << ". Message is " << ec.message() << endl;
return;
}
auto& send_data = _send_queue.front();
send_data->_cur_len += bytes_transferred;
// 如果小于就继续发送
if (send_data->_cur_len < send_data->_total_len)
{
this->_socket->async_write_some(asio::buffer(send_data->_msg + _send_node->_cur_len,
send_data->_total_len - send_data->_cur_len),
bind(&Session::WriteCallBack, this, placeholders::_1, placeholders::_2));
return;
}
// 如果发送完了
_send_queue.pop();
if (_send_queue.empty())
{
_send_pending = false;
}
else
{
auto& send_data = _send_queue.front();
this->_socket->async_write_some(asio::buffer(send_data->_msg + _send_node->_cur_len,
_send_node->_total_len - send_data->_cur_len),
bind(&Session::WriteCallBack, this, placeholders::_1, placeholders::_2));
}
}
void Session::WriteToSocket(const string& buf)
{
_send_queue.emplace(new MsgNode(buf.c_str(), buf.size()));
// 如果队列有数据,那么就直接靠队列发送即可
if (_send_pending)
{
return;
}
this->_socket->async_write_some(asio::buffer(_send_node->_msg, _send_node->_total_len),
bind(&Session::WriteCallBack, this, placeholders::_1, placeholders::_2));
// 表示数据没发送完
_send_pending = true;
}
async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度,这样我们每次都要在回调函数判断发送数据是否完成,asio提供了一个更简单的发送函数async_send,这个函数在发送的长度未达到我们要求的长度时就不会触发回调,所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送,所以async_send不能和async_write_some混合使用,我们基于async_send封装另外一个发送函数
基于一次性发送进行的函数
async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度,这样我们每次都要在回调函数判断发送数据是否完成,asio提供了一个更简单的发送函数async_send,这个函数在发送的长度未达到我们要求的长度时就不会触发回调,所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送,所以async_send不能和async_write_some混合使用,我们基于async_send封装另外一个发送函数
// 不进行递归调用两种写法
void Session::WriteAllToSocket(const string& buf)
{
_send_queue.emplace(new MsgNode(buf.c_str(), buf.size()));
if (_send_pending)
{
return;
}
// 保证数据可以发送成功,内部封装了async_write_some,多次调用
this->_socket->async_send(asio::buffer(buf),
bind(&Session::WriteAllCallBack, this, placeholders::_1, placeholders::_2));
_send_pending = true;
}
void Session::WriteAllCallBack(const boost::system::error_code& ec, size_t bytes_transferred)
{
if (ec.value())
{
cout << "error code is " << ec.value() << ". Message is " << ec.message() << endl;
return;
}
_send_queue.pop();
if (_send_queue.empty())
{
_send_pending = false;
}
else
{
auto& send_data = _send_queue.front();
// 这里的地址偏移其实不需要也可以
this->_socket->async_write_some(asio::buffer(send_data->_msg + _send_node->_cur_len,
_send_node->_total_len - send_data->_cur_len),
bind(&Session::WriteCallBack, this, placeholders::_1, placeholders::_2));
}
}
异步读操作
异步读操作和异步的写操作类似同样又async_read_some和async_receive函数,前者触发的回调函数获取的读数据的长度可能会小于要求读取的总长度,后者触发的回调函数读取的数据长度等于读取的总长度
_recv_node用来缓存接收的数据,_recv_pending为true表示节点正在接收数据,还未接受完
// 正确递归调用的写法(复杂)
void Session::ReadFromSocket()
{
if (_recv_pending)
{
return;
}
_recv_node = std::make_shared<MsgNode>(RECVSIZE);
// 异步回调进行读取,分批读取
_socket->async_read_some(asio::buffer(_recv_node->_msg, _recv_node->_total_len),
bind(&Session::ReadCallBack, this, placeholders::_1, placeholders::_2));
_recv_pending = true;
}
void Session::ReadCallBack(const boost::system::error_code& ec, size_t bytes_transferred)
{
_recv_node->_cur_len += bytes_transferred;
if (_recv_node->_cur_len < _recv_node->_total_len)
{
_socket->async_read_some(asio::buffer(_recv_node->_msg + _recv_node->_cur_len, _recv_node->_total_len - _recv_node->_cur_len),
bind(&Session::ReadCallBack, this, placeholders::_1, placeholders::_2));
return;
}
_recv_pending = false;
_recv_node = nullptr;
}
基于async_receive再封装一个接收数据的函数
// 正确不进行递归调用写法(简单)
void Session::ReadAllFromSocket()
{
if (_recv_pending)
{
return;
}
_recv_node = std::make_shared<MsgNode>(RECVSIZE);
// 内部是async_read_some的封装,进行多次调用一次读取结束
_socket->async_receive(asio::buffer(_recv_node->_msg, _recv_node->_total_len),
bind(&Session::ReadAllCallBack, this, placeholders::_1, placeholders::_2));
_recv_pending = true;
}
void Session::ReadAllCallBack(const boost::system::error_code& ec, size_t bytes_transferred)
{
_recv_node->_cur_len += bytes_transferred;
_recv_node = nullptr;
_recv_pending = false;
}