字节序处理和消息队列的控制
文章目录字节序的问题区分本机字节序使用网络字节序消息队列控制字节序的问题在计算机网络中由于不同的计算机使用的 CPU 架构和字节顺序可能不同因此在传输数据时需要对数据的字节序进行统一以保证数据能够正常传输和解析。这就是网络字节序的作用。具体来说计算机内部存储数据的方式有两种大端序Big-Endian和小端序Little-Endian。在大端序中高位字节存储在低地址处而低位字节存储在高地址处。在小端序中高位字节存储在高地址处而低位字节存储在低地址处。:::color1⚠️**在网络通信过程中通常使用的是大端序**。这是因为早期的网络硬件大多采用了 Motorola 处理器而 Motorola 处理器使用的是大端序。此外大多数网络协议规定了网络字节序必须为大端序。因此在进行网络编程时需要将主机字节序转换为网络字节序也就是将数据从本地字节序转换为大端序。可以使用诸如 htonl、htons、ntohl 和 ntohs 等函数来实现字节序转换操作。综上所述网络字节序的主要作用是统一不同计算机间的数据表示方式以保证数据在网络中的正确传输和解析。:::区分本机字节序#includeiostreamusingnamespacestd;// 判断当前系统的字节序是大端序还是小端序boolis_big_endian(){// 定义一个整型变量 num 并赋值为 1。在内存中它的十六进制表示为 0x00000001intnum1;// (char*)num: 强行将 int 型变量的地址转换为 char* 类型的指针// 这样指针就会指向该整型变量在内存中占据的【最低字节地址】首地址。// *: 解引用该 char* 指针从而只取出这一个字节8位的值。if(*(char*)num1){// 如果最低字节地址处存储的值是 1说明低位数据存储在了低地址处// 当前系统为小端序returnfalse;}else{// 如果最低字节地址处存储的值是 0说明高位数据存储在了低地址处// 当前系统为大端序returntrue;}}intmain(){// 定义一个 4 字节的整型变量其十六进制值为 0x12345678// 其中 12 是高位字节78 是低位字节intnum0x12345678;// 获取 num 的首地址并转换为字节指针 char*以便后续能逐字节访问内存char*p(char*)num;// 以十六进制hex形式打印出原始数据cout原始数据hexnumendl;// 根据自定义函数的返回值进入不同的打印逻辑if(is_big_endian()){cout当前系统为大端序endl;cout字节序为;// 大端序高位字节存放在低地址起始地址低位字节存放在高地址。// 因此直接从内存低地址向高地址正向遍历i 从 0 到 3即可按逻辑顺序输出 12 34 56 78for(inti0;isizeof(num);i){// (int)*(p i): 移动字节指针解引用取出该字节并强转为 int 供 cout 以十六进制打印couthex(int)*(pi) ;}coutendl;}else{cout当前系统为小端序endl;cout字节序为;// 小端序低位字节存放在低地址起始地址高位字节存放在高地址。// 在小端内存中实际存储顺序为78 56 34 12。// 为了在控制台能按人类阅读习惯正向显示出 12 34 56 78// 这里采用了逆序遍历i 从 3 减小到 0即先从高地址存放12的地方开始向前打印。for(intisizeof(num)-1;i0;i--){// (int)*(p i): 倒序移动字节指针解引用取出该字节并强转为 int 打印couthex(int)*(pi) ;}coutendl;}return0;}:::info在上述代码中使用了一个 is_big_endian() 函数来判断当前系统的字节序是否为大端序。该函数通过创建一个整型变量 num并将其最低位设置为 1然后通过指针强制转换成字符指针判断第一个字节是否为 1 来判断当前系统的字节序。在 main 函数中定义了一个整型变量 num并将其初始化为 0x12345678。接着使用 char* 类型的指针 p 来指向 num 的地址。然后通过判断当前系统的字节序来输出 num 的字节序。如果当前系统为大端序则按照原始顺序输出各个字节如果当前系统为小端序则需要逆序输出各个字节。:::原始数据12345678 当前系统为大端序 字节序为12345678原始数据12345678 当前系统为小端序 字节序为78563412使用网络字节序为保证字节序一致性网络传输使用网络字节序也就是大端模式。在 boost::asio 库中可以使用boost::asio::detail::socket_ops::host_to_network_long()和boost::asio::detail::socket_ops::host_to_network_short()函数将主机字节序转换为网络字节序。具体方法如下#includeboost/asio.hpp#includeiostreamintmain(){// 定义一个本地主机字节序的 32 位4字节无符号整数// 十六进制表示为 0x12345678uint32_thost_long_value0x12345678;// 定义一个本地主机字节序的 16 位2字节无符号整数// 十六进制表示为 0x5678常用于 TCP/UDP 端口号或 2 字节的包头长度uint16_thost_short_value0x5678;// 【4字节转换主机序 - 网络序】// 调用 Boost.Asio 底层函数将 32 位长整数转换为网络字节序即大端序 Big-Endian。// 如果当前系统本身就是大端序此函数什么都不做如果是小端序如 Intel x86/x64它会把字节顺序完全颠倒变成 0x78563412。uint32_tnetwork_long_valueboost::asio::detail::socket_ops::host_to_network_long(host_long_value);// 【2字节转换主机序 - 网络序】// 将 16 位短整数转换为网络字节序。// 在小端序系统上转换后字节会发生对调变成 0x7856。uint16_tnetwork_short_valueboost::asio::detail::socket_ops::host_to_network_short(host_short_value);// 以十六进制std::hex格式打印出转换前后的对比结果std::coutHost long value: 0xstd::hexhost_long_valuestd::endl;std::coutNetwork long value: 0xstd::hexnetwork_long_valuestd::endl;std::coutHost short value: 0xstd::hexhost_short_valuestd::endl;std::coutNetwork short value: 0xstd::hexnetwork_short_valuestd::endl;return0;}在上述代码中分别将 32 位和 16 位的主机字节序数值转换为网络字节序并输出转换结果。需要注意的是在使用这些函数时应该确保输入参数和返回结果都是无符号整数类型否则可能会出现错误。同样的道理**我们只需要在服务器发送数据时将数据长度转化为网络字节序在接收数据时将长度转为本机字节序**。shortdata_len0;memcpy(data_len,_recv_head_node-_data,HEAD_LENGTH);//网络字节序转化为本地字节序data_lenboost::asio::detail::socket_ops::network_to_host_short(data_len);coutdata_len is data_lenendl;MsgNode(char*msg,shortmax_len):_total_len(max_lenHEAD_LENGTH),_cur_len(0){_datanewchar[_total_len1]();//转为网络字节序intmax_len_hostboost::asio::detail::socket_ops::host_to_network_short(max_len);memcpy(_data,max_len_host,HEAD_LENGTH);memcpy(_dataHEAD_LENGTH,msg,max_len);_data[_total_len]\0;}客户端也遵循同样的处理。消息队列控制发送时我们会将发送的消息放入队列里以保证发送的时序性每个session都有一个发送队列因为有的时候发送的频率过高会导致队列增大所以要对队列的大小做限制当队列大于指定数量的长度时就丢弃要发送的数据包以保证消息的快速收发。voidCSession::Send(char*msg,intmax_length){// 【加锁保护】使用互斥锁的 RAII 机制进行自动加锁// 保护发送队列 _send_que防止多线程同时调用 Send 导致队列数据损坏因为 STL 队列不是线程安全的std::lock_guardstd::mutexlock(_send_lock);// 获取当前发送队列中已经积压的数据包数量intsend_que_size_send_que.size();// 【流量控制/防爆内存】如果队列中的包数量已经超过了设定的最大限制MAX_SENDQUEif(send_que_sizeMAX_SENDQUE){// 打印错误日志警告该会话的发送队列已满coutsession: _uuid send que fulled, size is MAX_SENDQUEendl;// 直接拒绝本次发送请求丢弃该数据包并返回防止内存被无限制撑爆return;}// 【入队缓存】将本次需要发送的数据包装成智能指针包裹的内存节点MsgNode并推入发送队列末尾_send_que.push(make_sharedMsgNode(msg,max_length));// 【状态判定】// 如果 send_que_size 0说明在本次数据入队之前队列里就已经有旧的数据包在排队了。// 这意味着底层必然已经有一个正在执行的 async_write 异步写操作在内核中挂起。if(send_que_size0){// 当前线程只需要把数据放入队列即可后续的 HandleWrite 回调会自动消费新入队的包因此直接返回return;}// 【主动点火触发】// 如果 send_que_size 等于 0说明此前队列为空底层没有处于挂起状态的异步写操作。// 此时必须由当前调用 Send 的线程主动“点火”触发第一次异步写操作。automsgnode_send_que.front();// 【异步全量写】调用 async_write 将队列头部的节点数据异步发送出去// 绑定 SharedSelf() 增加会话的智能指针引用计数确保在异步发送中途 CSession 节点不会被销毁boost::asio::async_write(_socket,boost::asio::buffer(msgnode-_data,msgnode-_total_len),std::bind(CSession::HandleWrite,this,std::placeholders::_1,SharedSelf()));}