大端与小端概念、多字节之间与单字节多部分的大小端转换详解
- 前言
- 高字节、低字节
- 高地址、低地址
- 大端、小端
- 网络字节序和主机字节序
- 大小端转换用在什么地方
- 特殊情况:关注每个字节具体数值,单字节多部分的大小端转换
- websocket协议
- ip协议
- 多字节之间 常用大小端转换api
前言
本文主要介绍三个内容:
- 大端与小端概念
- 多字节之间的大小端转换
- 单字节多部分的大小端
定义TCP/IP协议结构体的时候,常常搞不清楚一字节分成多个部分时,大小端是如何转换的,今天特意写下来,以免过几天又搞不清楚了。
字节是计算机的基本单位,8bit=1byte,八位一字节,如果存储一个数,大于一个字节,由于计算机内存排布的不同,就要区分字节顺序:大端-Big Endian(High-byte first) 和 小端-Litter Endian(Low-byte first)
本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。
高字节、低字节
一个int类型的整数:123456789
- 最左边的叫高字节,即 0x07
- 最右边的叫低字节,即 0x15
进制 | 值 |
二进制 | 00000111 01011011 11001101 00010101 |
十六进制 | 07 5B CD 15 |
高地址、低地址
在内存中,多字节对象都是被存储为连续的字节序列。例如在C语言中,一个类型为int的变量n,如果其存储的首个字节的地址为0x1000,那么剩余3个字节的地址将存储在0x1001~0x1003。总之,不管具体字节顺序是以什么方式排列,内存地址的分配一般是从小到大的增长。我们常把0x1000称为低地址端,把0x1003称为高地址端。
大端、小端
- 大端:高字节存放在低地址,低字节存放在高地址(大端从左往右,很符合人的思维)
- 小端:低字节存放在低地址,高字节存放在高地址(低放低,大端的逆序)
(123456789)10 = (07 5B CD 15)16 ,这个int整形4字节该如何存储呢
内存地址(低——>高) | 字节存储顺序 | ||
大端(Big Endian) | 0x1000 0x1001 0x1002 0x1003 | 0x07 0x5B 0xCD 0x15 | 高字节存放在低地址 低字节存放在高地址 |
小端(Litter Endian) | 0x1000 0x1001 0x1002 0x1003 | 0x15 0xCD 0x5B 0x07 | 低字节存放在低地址 高字节存放在高地址 |
网络字节序和主机字节序
网络字节序(Network Order):TCP/IP各层协议将字节序定义为大端(Big Endian),因此TCP/IP协议中使用的字节序通常称之为网络字节序。
主机字节序(Host Order):整数在内存中保存的顺序,它遵循小端(Little Endian)规则(不一定,要看主机的CPU架构,不过大多数都是小端)。所以当两台主机之间要通过TCP/IP协议进行通信的时候就需要调用相应的函数进行主机序列(Little Endian)和网络序(Big Endian)的转换。
大小端转换用在什么地方
从上面我们已经知道了,TCP/IP各层协议 一般都是大端 , 而我们常用的计算机,一般都是小端。
所以如果我们要发送协议的时候,是要发送大端的;当我们接收到大端的协议,想要解析出来使用的时候,就要转成小端。
再往小的看,我们转换,转换的是整数(short ,int ,uint等等),因为只有整数才会有多个字节,对于单字节的char来说,是不需要转换的(特殊情况见下)。
如果是做跨平台开发时,双方需要协商好字节序,然后根据程序运行的环境,确定是否需要字节序转换。例如约定的通讯字节序为大端,默认的windows采用的小端,那收到数据后就需要做转换操作。
特殊情况:关注每个字节具体数值,单字节多部分的大小端转换
关注某个字节的具体 bit 的时候,是需要考虑大小端的。我们上面所说的,都是多字节的情况,对于int来说,4字节,我们只关注字节与字节之间的顺序;而对于一个字节,并且我们把这8bit,分成多个部分的情况的时候,我们需要考虑 bit 与 bit 之间的顺序,是需要考虑大小端的。
那么如何将大端的一字节的,转换成小端呢? 根据协议规定的字节内 每部分 的顺序,做 逆序 即可。
websocket协议
举个例子,在websocket协议介绍与基于reactor模型的websocket服务器实现,里面,我们就根据websocket的协议,定义了结构体。
在协议中,将一字节,分成了5个部分,我们常用的ntohs,ntohl都是针对多字节的,那么收到了这份协议,如何将这一字节的大端转换成小端呢?我们通过结构体来实现。当这一字节存入结构体内,我们通过下面的方法,变成小端。
typedef struct _ws_ophdr {
unsigned char opcode: 4,
rsv3: 1,
rsv2: 1,
rsv1: 1,
fin: 1;
unsigned char payload_len: 7,
mask: 1;
} ws_ophdr;
ip协议
在ip协议里面,我们观察到第一个字节中,version4个bit,hdrlen4个bit。但是这个大端,如果要转换成小端,就逆序即可。
struct iphdr {
unsigned char hdrlen: 4,
version: 4;
unsigned char tos;
unsigned short totlen;
unsigned short id;
unsigned short flag_offset;
unsigned char ttl;
unsigned char type;
unsigned short check;
unsigned int sip;
unsigned int dip;
};
多字节之间 常用大小端转换api
- 函数名的h表示主机host, n表示网络network, s表示short, l表示long;下面这几个函数, 如果本来不需要转换,函数内部就不会做转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
IP地址转换函数:
p->表示点分十进制的字符串形式
to->到
n->表示network网络
- 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
int inet_pton(int af, const char *src, void *dst);
- 网络IP转换为字符串形式的点分十进制的IP
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);