面经及项目开发之网络编程核心概念:大端与小端
- 2019 年 10 月 6 日
- 笔记
面经及项目开发之网络编程核心概念:大端与小端
0.导语
最近做的项目都涉及了协议,网络编程,针对协议与网络通信数据传输,大家使用抓包工具抓出来的数据例如:0x5634… 这些就是所谓的网络字节序,俗称大端!而针对不同的机器,有着不同的模式,有些是大端,有些是小端,如果在网络传输中发送的是原数据0x3456,而不是0x5634,那么会发生灾难性的错误,因此需要在发送前调用htons或者htonl函数将其转换为大端模式,也就是网络字节序,相信在深入理解一些开源的项目中,底层用C/C++ 写的程序中,大家会看到这些函数。
另外,在面试过程中,这个点也非常的重要,通常会考察这些概念与碰到的问题之类的,那么下面一起来从零学起。
简化一下需求:
(1)WORD类型传输约定:先传递高八位,再传递低八位。
(2)DWORD传递约定:先传递高24位,然后传递高16位,再传递高八位,最后传递低八位
针对这样的类型如何传输呢?
下面看完本篇文章就知道怎么传输了!
1.What?
所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
简单来说:大端——高尾端,小端——低尾端。
实际例子如下:
16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x6411开始存放)为:
内存地址 |
小端模式存放内容 |
大端模式存放内容 |
---|---|---|
0x6410 |
0x34 |
0x12 |
0x6411 |
0x12 |
0x34 |
32bit宽的数0x12345678在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x6411开始存放)为:
内存地址 |
小端模式存放内容 |
大端模式存放内容 |
---|---|---|
0x6410 |
0x78 |
0x12 |
0x6411 |
0x56 |
0x34 |
0x6412 |
0x34 |
0x56 |
0x6413 |
0x12 |
0x78 |
2.How?
上面阐述了如何判断大端与小端,那如何来判断自己的CPU是大端还是小端。
下面给出了两种方法。
方法1:使用联合体,给一个变量赋值,使用另一个变量查看低地址存储的是高位还是低位。
方法2:直接查看char的低地址存储的是高位还是低位。
/** * 检查机器的字节序 * @return */ bool isBigEndian() { // 使用联合体 union node { int num; char ch; }; union node p; //方法一 p.num = 0x12345678; // 低位地址存储低位 if (p.ch == 0x78) { printf("Little endiann"); } else { // 低位地址存储高位 printf("Big endiann"); } //方法二 int num = 0x12345678; char *q = (char *)# if (*q == 0x78) { printf("Little endiann"); } else { printf("Big endiann"); } }
运行结果:
=========两种方式验证机器大端还是小端========== Little endian Little endian
3.实现
那如何自己实现小端转大端(网络字节序列)呢?
分为两种,一种是16位,一种是32位。
针对16位,实现如下:
/** * 低地址存放高位,高地址存放低位 * WORD类型传输约定:先传递高八位,再传递低八位。 * 其中WORD 被定义为uint16_t * 2字节大端转换函数 * @param value * @param buf * @return */ WORD EndianSwap16(const WORD &value, unsigned char* buf = NULL) { assert(sizeof(value) == 2); if(buf) { *buf++ = value&0x00ff; *buf = (value&0xff00)>>8; } return (value&0x00ff)<<8|(value&0xff00)>>8; }
其中buf存储的是大端的每一个字节。
调用上述函数:
cout<<"==========调用自己实现的函数实现小端转换为大端=========="<<endl; uint16_t a = 0x1234; unsigned char buf[2]; printf("16位小端--->大端:%xn", EndianSwap16(a, buf));
输出结果:
==========调用自己实现的函数实现小端转换为大端========== 16位小端--->大端:3412 34 12
针对32位:实现如下:
/** * 低地址存放高位,高地址存放低位 * DWORD传递约定:先传递高24位,然后传递高16位,再传递高八位,最后传递低八位 * 4字节大端转换函数 * 其中DWORD 被定义为uint32_t * @param value * @param buf * @return */ DWORD EndianSwap32(const DWORD &value, unsigned char* buf=NULL) { assert(sizeof(value) == 4); if(buf) { *buf++ = value&0x000000ff; *buf++ = (value&0x0000ff00)>>8; *buf++ = (value&0x00ff0000)>>16; *buf = value>>24; } return ((value >> 24) | ((value & 0x00ff0000) >> 8) | ((value & 0x0000ff00) << 8) | (value << 24)); }
其中buf存储的是大端的每一个字节。
调用上述函数:
uint32_t b = 0x12345678; unsigned char buf1[4]; printf("32位小端--->大端:%xn", EndianSwap32(b, buf1)); printf("%x %x %x %xn", buf1[0], buf1[1], buf1[2], buf1[3]);
输出结果:
================================================== 32位小端--->大端:78563412 78 56 34 12
4.调用函数
在C/C++网络开发中可以通过引入
#include <netinet/in.h>
调用htonl
、htons
、ntohl
、 ntohs
来完成小端与大端转换。
那么下面来使用一下,使用之前先阐述一下这几个函数:
htonl()
32位无符号整型的主机字节顺序到网络字节顺序的转换(小端->大端)
htons()
16位无符号短整型的主机字节顺序到网络字节顺序的转换 (小端->大端)
ntohl()
32位无符号整型的网络字节顺序到主机字节顺序的转换 (大端->小端)
ntohs()
16位无符号短整型的网络字节顺序到主机字节顺序的转换 (大端->小端)
注,主机字节顺序,X86一般多为小端(little-endian),网络字节顺序,即大端(big-endian);
调用:
cout<<"==========htonl htons ntohl ntohs函数调用=========="<<endl; printf("16位小端--->大端:%xn",htons(a)); printf("32位小端--->大端:%xn",htonl(b));
输出结果:
==========htonl htons ntohl ntohs函数调用========== 16位小端--->大端:3412 32位小端--->大端:78563412