面经及项目开发之网络编程核心概念:大端与小端


面经及项目开发之网络编程核心概念:大端与小端

0.导语

最近做的项目都涉及了协议,网络编程,针对协议与网络通信数据传输,大家使用抓包工具抓出来的数据例如:0x5634... 这些就是所谓的网络字节序,俗称大端!而针对不同的机器,有着不同的模式,有些是大端,有些是小端,如果在网络传输中发送的是原数据0x3456,而不是0x5634,那么会发生灾难性的错误,因此需要在发送前调用htons或者htonl函数将其转换为大端模式,也就是网络字节序,相信在深入理解一些开源的项目中,底层用C/C++ 写的程序中,大家会看到这些函数。

另外,在面试过程中,这个点也非常的重要,通常会考察这些概念与碰到的问题之类的,那么下面一起来从零学起。

简化一下需求:

(1)WORD类型传输约定:先传递高八位,再传递低八位。

(2)DWORD传递约定:先传递高24位,然后传递高16位,再传递高八位,最后传递低八位

针对这样的类型如何传输呢?

下面看完本篇文章就知道怎么传输了!

1.What?

所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

简单来说:大端——高尾端,小端——低尾端。

实际例子如下:

16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x6411开始存放)为:

内存地址小端模式存放内容大端模式存放内容
0x64100x340x12
0x64110x120x34

32bit宽的数0x12345678在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x6411开始存放)为:

内存地址小端模式存放内容大端模式存放内容
0x64100x780x12
0x64110x560x34
0x64120x340x56
0x64130x120x78

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 endian\n");
  } else {
       // 低位地址存储高位
       printf("Big endian\n");
  }
   //方法二
   int num = 0x12345678;
   char *q = (char *)#
   if (*q == 0x78) {
       printf("Little endian\n");
  } else {
       printf("Big endian\n");
  }
}

运行结果:

=========两种方式验证机器大端还是小端==========
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位小端--->大端:%x\n", 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位小端--->大端:%x\n", EndianSwap32(b, buf1));
printf("%x %x %x %x\n", buf1[0], buf1[1], buf1[2], buf1[3]);

输出结果:

==================================================
32位小端--->大端:78563412
78 56 34 12

4.调用函数

在C/C++网络开发中可以通过引入

#include <netinet/in.h>

调用htonlhtonsntohlntohs来完成小端与大端转换。

那么下面来使用一下,使用之前先阐述一下这几个函数:

  • htonl()

32位无符号整型的主机字节顺序到网络字节顺序的转换(小端->大端)  

  • htons()

16位无符号短整型的主机字节顺序到网络字节顺序的转换  (小端->大端)

  • ntohl()

32位无符号整型的网络字节顺序到主机字节顺序的转换  (大端->小端)

  • ntohs()

16位无符号短整型的网络字节顺序到主机字节顺序的转换  (大端->小端)

注,主机字节顺序,X86一般多为小端(little-endian),网络字节顺序,即大端(big-endian);

调用:

cout<<"==========htonl htons ntohl ntohs函数调用=========="<<endl;
printf("16位小端--->大端:%x\n",htons(a));
printf("32位小端--->大端:%x\n",htonl(b));

输出结果:

==========htonl htons ntohl ntohs函数调用==========
16位小端--->大端:3412

32位小端--->大端:78563412


640?wx_fmt=png


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值