系列文章目录:C++ asio网络编程-CSDN博客
1、字节序的问题
先引用网上的统一写法:计算机内部存储数据的方式有两种:大端序(Big-Endian)和小端序(Little-Endian)。在大端序中,高位字节存储在低地址处,而低位字节存储在高地址处;在小端序中,高位字节存储在高地址处,而低位字节存储在低地址处。
开始讲人话:我们都知道,int占四个字节的内存,假设现在有一个变量:
int num = 0x12345678;
那么它将被拆为四个字节存储:12、34、56、78,假设存储他们的内存地址为0x00、0x01、0x02、0x03,那么大端模式的存储方式为:
小端模式的存储方式为:
这样再带入天书中的 “大端序中,高位字节存储在低地址处,而低位字节存储在高地址处;在小端序中,高位字节存储在高地址处,而低位字节存储在低地址处。” 应该就很清楚了。
2、判断本机字节序
其实原理很简单,根据上面的解释,我们可以设置一个变量
int num = 1;
那么它在内存中的存储应该被分为:00 00 00 01 这四份,然后我们将他的指针转化为char*类型,再去解引用看看是不是1
if (*(char*)&num == 1) {
// 当前系统为小端序
return false;
} else {
// 当前系统为大端序
return true;
}
}
首先通过&num得到他的地址,(char*)将他转化为char*类型,再去解引用得到它的值。如果是大端,就是00 00 00 01依次存储,转化为char一个字节前面的舍去,留下01,所以就返回true,如果是小段,就是01 00 00 00依次存储,转化为char就只保留最后一个00,返回false。通过这段代码就很容易判断本机字节序。
3、服务器使用网络字节序
网络字节序其实就是大端模式,因为大多数网络协议规定了网络字节序必须为大端序。在boost::asio中也提供了api供我们使用:
-
将boost::asio::detail::socket_ops::host_to_network_short
uint16_t
类型的值从主机字节序转换为网络字节序(大端字节序)。 -
将boost::asio::detail::socket_ops::host_to_network_long
uint32_t
类型的值从主机字节序转换为网络字节序(大端字节序)。 -
将boost::asio::detail::socket_ops::network_to_host_short
uint16_t
类型的值从网络字节序(大端字节序)转换为主机字节序。 -
将boost::asio::detail::socket_ops::network_to_host_long
uint32_t
类型的值从网络字节序(大端字节序)转换为主机字节序。
需要注意的是,在使用这些函数时,应该确保输入参数和返回结果都是无符号整数类型,否则可能会出现错误。 同样的道理,我们只需要在服务器发送数据时,将数据长度转化为网络字节序,在接收数据时,将长度转为本机字节序。 在服务器的HandleRead函数里,添加对data_len的转换,将网络字节转为本地字节序:
// 获取数据长度
short data_len = 0;
memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
// 网络字节序转化为本地字节序
data_len = boost::asio::detail::socket_ops::network_to_host_short(data_len);
std::cout << "data len: " << data_len << std::endl;
// 头部长度非法
if (data_len > MAX_LENGTH) {
std::cout << "invalid data length" << std::endl;
_server->clearSession(_uuid);
return;
}
MsgNode(char* msg, int max_len) : _total_len(max_len + HEAD_LENGTH), _cur_len(0) {
_data = new char[_total_len + 1]();
// 本地字节序转化为网络字节序
int max_len_host = boost::asio::detail::socket_ops::host_to_network_short(max_len);
// 接收到的数据长度
memcpy(_data, &max_len_host, HEAD_LENGTH);
// 接收到的数据
memcpy(_data + HEAD_LENGTH, msg, max_len);
// 数据结束标志,其实是为了方便控制台打印用的
_data[_total_len] = '\0';
}
客户端的处理方式相同。
4、消息队列控制
发送时我们会将发送的消息放入队列里以保证发送的时序性,每个session都有一个发送队列,因为有的时候发送的频率过高会导致队列增大,所以要对队列的大小做限制,当队列大于指定数量的长度时,就丢弃要发送的数据包,以保证消息的快速收发。
#define MAX_SENDQUE 1000
void Session::send(char* msg, int max_length)
{
bool pending = false;
std::lock_guard<std::mutex> lock(_send_lock);
if (_send_que.size() > MAX_SENDQUE) {
std::cout << "session: " << _uuid << " send que fulled, size is " << MAX_SENDQUE << std::endl;
return;
}
if (_send_que.size() > 0) {
pending = true;
}
_send_que.push(std::make_shared<MsgNode>(msg, max_length));
if (pending) {
return;
}
auto& msgnode = _send_que.front();
boost::asio::async_write(_socket, boost::asio::buffer(msgnode->_data, msgnode->_total_len),
std::bind(&Session::handle_write, this, std::placeholders::_1, shared_from_this()));
}
5、总结
本文介绍了网络字节序以及控制它的必要性,限制了发送队列长度保证发送数据的高效率。