场景
在进行Socket通讯时, 因为网络字节序是 Big-Endian模式(标准), 而大部分Windows系统都是 Little Endian模式, 所以在传输数值类型的数据时, 需要把 Little Endian的内存数据转换为 Big-Endian再发送.
如果客户端和服务端都是你自己实现的代码, 可以不需要转换, 因为你自己知道字节序.
因为 Big-Endian的系统很少见,其实大部分情况下需要进行转换都是和网络相关的.
说明
不同计算机基础结构有时存储使用不同的字节顺序的数据, 我们所使用的 Intel,x86架构都是 Little-Endian模式存储数据.
Big-Endian: 就是低地址存储数据高位. Little-Endian: 低地址存储数据低位.
Windows系统为我们提供了两种API来处理大小端字节序处理,还是比较方便的. 如果是处理double类型的数据, 需要自己转换. 第一种是WinSock2提供的转换函数, 比如
ntohl
. 另一种是标准库扩展, 比如_byteswap_ulong
. 第一种明确的是在网络字节序Big-Endian和宿主系统之间转换, 第二种是进行字节位反转, 没有明确输入输出是 Little-Endian还是 Big-Endian, 使用时要注意.当然, 如果理解了大小端其实字节反转的原理之后也可以自己写出来,其实真没那个必要.
Big-Endian The most significant byte is on the left end of a word.
Little-Endian The most significant byte is on the right end of a word
ntohs Convert a 16-bit quantity from network byte order to host byte order (big-Endian to little-Endian).
ntohl Convert a 32-bit quantity from network byte order to host byte order (big-Endian to little-Endian).
Htons Convert a 16-bit quantity from host byte order to network byte order (little-Endian to big-Endian).
Htonl Convert a 32-bit quantity from host byte order to network byte order (little-Endian to big-Endian).
例子
下边的代码演示了这两种API的使用方法:
// test-big-endian.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <iostream>
#include <assert.h>
#include <WinSock2.h>
#include <stdint.h>
struct Message
{
long m_lMagicNumber;
short m_nCommand;
short m_nParam1;
long m_lParam2;
};
// 判断CPU的存储是大端序还是小端序.
bool IsLittleEndian()
{
union w
{
int a;
char b;
}w1;
w1.a = 1;
return (w1.b == 1);
}
int32_t ToBigEndian(int32_t value)
{
if(IsLittleEndian())
return _byteswap_ulong(value);
else
return value;
}
uint32_t ToLittleEndian(uint32_t value)
{
if(IsLittleEndian())
return value;
else
return _byteswap_ulong(value);
}
double ToLittleEndian(double value)
{
if(IsLittleEndian()){
return value;
}else{
auto temp = _byteswap_uint64(value);
return *(double*)&temp;
}
}
uint8_t ToLittleEndian(uint8_t value)
{
if(IsLittleEndian())
return value;
else
return _byteswap_ushort(value);
}
int _tmain(int argc, _TCHAR* argv[])
{
if(!IsLittleEndian())
return -1;
std::cout << "Little Endian" << std::endl;
long value = -1;
auto value_sock_bigendian1 = htonl(value);
auto value_byteswap_bigendial1 = ToBigEndian(value);
assert(value_sock_bigendian1 == value_byteswap_bigendial1);
std::cout << "1->Little Endian: " << value_sock_bigendian1 << std::endl;
auto value_nl_1 = ntohl(value_sock_bigendian1);
auto value_swap_1 = _byteswap_ulong((uint32_t)value_byteswap_bigendial1);
assert(value_nl_1 == value_swap_1);
std::cout << "to host byte : " << value_nl_1 << std::endl;
return 0;
}
输出:
Little Endian
1->Little Endian: 4294967295
to host byte : 4294967295
参考
Windows Sockets Byte Ordering
Windows 套接字 字节排序
大端模式和小端模式
byteswap_uint64
how-do-i-convert-between-big-endian-and-little-endian-values-in-c
判断大小端序Little Endian Order