2016-01-25 23:49:17 u011644231 阅读数 2028

网络字节序和本地字节序的理解和实现

工作中经常用到这两个概念,看了APUE关于大端模式和小端模式的说明,和博文:htonl、ntohl、htons、ntohs函数实现 在这里概括归纳一下,权当备忘。以下所有假设都是在32位x86系统上。
大端模式:网络字节序采用的模式,TCP/IP协议栈支持的模式
小端模式:linux主机采用小端模式存储。

这里要注意不管是大端还是小端存储,MSB(数据最高字节)始终是在最左边,LSB(数据最低字节)始终是在最右边
比如以十六进制的0x12345678为例,首先要了解十六进制一位表示4个二进制位,所以这个代表一个32位(8*4),4字节的数据,0x12是MSB,0x78是LSB
对于大端模式MSB存储在低位字节地址n,LSB存储在高位字节地址n+3,这里的字节地址也是32位的,数据按照地址偏移按单字节存放。
32整数内部的字节序

所以要将一个字符指针char* cp强制转换为这个整形地址,由于字节序的不同会带来差异。在小端机器上cp[0]指向最低位地址,存放的是0x78,然而cp[3]指向最高位地址,存放的是0x12;大端反之。
可以用gdb调试查看,内存内部的变量分布,gdb内存查看命令:
x /nfu addr
说明
x 是 examine 的缩写
n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。
u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八字节
参考博文:gdb查看内存区命令

小端模式内存查看实验:

int main(int arc, char* arg[])
{
    unsingned int a = 0x12345678;
    while(1);
}

编译:
编译必须加-g选项才能生成可调试的文件
gcc littleEnding.c -g -o littleEnding

调试:
(gdb)gdb ./littleEnding
(gdb)b 4 //源文件第四行设置断点
(gdb)r //执行程序
(gdb)p &a //打印变量地址
$1 = (unsingned int *)0xbffff543
(gdb)x 0xbffff543 //查看内存单元内变量
0xbffff543: 0x12345678
(gdb) x /4xb 0xbffff543 //单字节查看4个内存单元变量的值
0xbffff543: 0x78 0x56 0x34 0x12
(gdb) x /1xb 0xbffff543
0xbffff543: 0x78
(gdb) x /2xb 0xbffff543
0xbffff543: 0x56
(gdb) x /3xb 0xbffff543
0xbffff543: 0x34
(gdb) x /4xb 0xbffff543
0xbffff543: 0x78 0x56 0x34 0x12
//可以看到内存最低地址单元存放的是数据的LSB 0x78
//显然linux x86下是小端的存放方式
typedef unsigned short int uint16;

typedef unsigned long int uint32;



// 短整型大小端互换

#define BigLittleSwap16(A)  ((((uint16)(A) & 0xff00) >> 8) | \

                            (((uint16)(A) & 0x00ff) << 8))

 // 长整型大小端互换



#define BigLittleSwap32(A)  ((((uint32)(A) & 0xff000000) >> 24) | \

                            (((uint32)(A) & 0x00ff0000) >> 8) | \

                            (((uint32)(A) & 0x0000ff00) << 8) | \

                            (((uint32)(A) & 0x000000ff) << 24))




 // 本机大端返回1,小端返回0

int checkCPUendian()

{

       union{

              unsigned long int i;

              unsigned char s[4];

       }c;



       c.i = 0x12345678;
//利用了联合的内存分配规则,共享内存仅分配一种数据结构
       return (0x12 == c.s[0]);

}



// 模拟htonl函数,本机字节序转网络字节序

unsigned long int t_htonl(unsigned long int h)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,转换成大端再返回

       return checkCPUendian() ? h : BigLittleSwap32(h);

}



// 模拟ntohl函数,网络字节序转本机字节序

unsigned long int t_ntohl(unsigned long int n)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,网络数据转换成小端再返回

       return checkCPUendian() ? n : BigLittleSwap32(n);

}



// 模拟htons函数,本机字节序转网络字节序

unsigned short int t_htons(unsigned short int h)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,转换成大端再返回

       return checkCPUendian() ? h : BigLittleSwap16(h);

}



// 模拟ntohs函数,网络字节序转本机字节序

unsigned short int t_ntohs(unsigned short int n)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,网络数据转换成小端再返回

       return checkCPUendian() ? n : BigLittleSwap16(n);

}
2014-06-12 12:47:40 u013012494 阅读数 687
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
    char *str;
    struct in_addr inaddr;
    int addr = 0x12345678;
        inaddr.s_addr=htonl(addr);
    printf("转化前本机字节序:0x%x%x%x%x\n",*((char*)&addr+3),*((char*)&addr+2),*((char*)&addr+1),*((char*)&addr+0));
    str = inet_ntoa(inaddr);
    printf("转化后网络字节序为:%s\n",str);
    return 0;
}
2015-11-01 23:32:08 fendoubasaonian 阅读数 1986

1、网络字节序:是TCP/IP中一种固定好的数据表示格式,它与具体的CPU,操作系统,传输方式无关,从而可以保证数据在不同主机之间传输时能够兼容。

2、主机字节序:即大端(Big Endian)与小端(Little Endian)模式;由CPU和硬件的设置方式相关。

3、为了进行转换 bsd socket提供了转换的函数 有下面四个

BSD Socket 是UNIX系统中通用的网络接口,它不仅支持各种不同的网络类型,而且也是一种内部进程之间的通信机制)


①htons 把unsigned short类型从主机序转换到网络序(host to network short)
②htonl 把unsigned long类型从主机序转换到网络序(host to network long)


③ntohs 把unsigned short类型从网络序转换到主机序(network to host short)
④ntohl 把unsigned long类型从网络序转换到主机序(network to host long)


注意1:在使用Little Endian的系统中, 这些函数会对字节序进行转换 ,将字节序转换为Big Endian
注意2:在使用Big Endian类型的系统中,这些函数会定义成空宏

2016-12-21 03:19:28 Golf_research 阅读数 1095

1:大小端字节序

参考博文:大小端模式

2:存储字节序和传输字节序

存储字节序:一个多字节的变量在内存中的存储方式,变量的小端数据存储在内存起始位置是小端字节序;变量的大端数据存储在内存起始位置是大端字节序;

传输字节序:协议传输多字节变量时的传输方式,先传输大端字节的方式成为大端字节序;先传输小端字节的方式成为小端字节;网络协议使用大端字节序传输数据,USB协议使用小端字节序传输协议;

两者关系:由于系统主机存在大小端模式,并且不能保证网络通信的两个主机之间的存储字节序一致,所以网络协议就规定自身在传输数据的时候使用的字节序为大端字节序,这样网络通信的双方主机就会达成一致,对接收到的数据(大端模式)依据自身的存储字节序来进行处理;存储字节序和传输字节序本质上是一样的概念;

3:字节序转换函数

#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue)   //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue)   //返回网络字节序的值

uint16_t ntohs(uint16_t net16bitvalue)    //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue)    //返回主机字节序的值
上述函数中,h代表host,n代表network,s代表short,l代表short;

当使用这些函数时,我们并不关心主机字节序和网络字节序的真实值,只要调用适当的函数在主机和网络字节序之间转换某个给定的值,因为在那些与网络字节序(大端字节序)相同的系统中,这些函数为空,如下代码:

#ifdef __OPTIMIZE__
/* We can optimize calls to the conversion functions.  Either nothing has
    to be done or we are using directly the byte-swapping functions which
    often can be inlined.  */
 # if __BYTE_ORDER == __BIG_ENDIAN
 /* The host byte order is the same as network byte order,
    so these functions are all just identity.  */
 # define ntohl(x)   (x)
 # define ntohs(x)   (x)
 # define htonl(x)   (x)
 # define htons(x)   (x)
 # else
 #  if __BYTE_ORDER == __LITTLE_ENDIAN
 #   define ntohl(x) __bswap_32 (x)
 #   define ntohs(x) __bswap_16 (x)
 #   define htonl(x) __bswap_32 (x)
 #   define htons(x) __bswap_16 (x)
 #  endif
 # endif 
 #endif  

如果主机系统的字节序为大端模式,这些函数定义为空,在进行socket编程的时候,为了提高代码的可移植性,应该总是使用这些函数进行主机字节序和网络字节序的处理;

函数使用举例:

#include <stdio.h>
#include <netinet/in.h>

int main(int argc,char **argv)
{
	int value1 = 0x12345678;          //定义一个4字节的数据
	short int value2 = 0x1234;        //定义一个2字节的数据
	printf("htonl(0x%08x) = 0x%08x\r\n",value1,htonl(value1));
	printf("htons(0x%04x) = 0x%04x\r\n",value2,htons(value2));
}
代码执行如下:


4:地址转换函数

使用协议无关性的两个地址处理函数,函数如下:

#include <arpa/inet.h>

//返回:若成功返回1,若输入不是有效的表达格式返回0,出错返回-1
int inet_pton(int family,const char *strptr, void *addrptr);

//返回:若成功则为指向结果的指针,若出错则为NULL
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);
函数使用举例:

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc,char **argv)
{
	unsigned char address[4];   //存放ip地址
	char str[INET_ADDRSTRLEN] = {0};
	char *string = NULL;
	
	string = "192.168.1.1";
	inet_pton(AF_INET,string,address);
	printf("%d %d %d %d\r\n",address[0],address[1],address[2],address[3]);

	address[0] = 127;address[1] = 127;address[2] = 33;address[3] = 33;
	inet_ntop(AF_INET,address,str,16);
	printf("%s\r\n",str);

	return 0;
}

代码执行如下:

由代码验证可知,inet_pton函数转换完成后存放在address数组中的字节序已经是网络字节序(大端字节序);







2019-04-08 21:37:01 qq_41453285 阅读数 104

一、主机字节序、网络字节序

  • 主机字节序:数值的存储采用小端法存储。数值低位存储在内存的低地址,数值高位存储在内存的高地址
  • 网络字节序:数值的存储采用大端法存储。数值高位存储在内存的低地址,数值低位存储在内存的高地址
  • 例如:有一个整型0x12345678(4字节)。左侧为主机字节序,右侧为网络字节序

二、字节序的转换

  • 主机字节序转换为网络字节序:
#include <arpa/inet.h>
#include<netinet/in.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

//返回值:都返回网络字节序的值
  • 网络字节序转换为主机字节序:
#include <arpa/inet.h>
#include<netinet/in.h>

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

//返回值:都返回主机字节序的值

h---host:本地主机

n---network:网络的意思

l---unsigned long

s---unsigned short

三、演示案例

  • 检验自己的主机是大端存储还是小端存储
  • 字符串CPU_VENDOR_OS是由GNU的autoconf程序在配置本书中的软件时确定的,它表示CPU类型、厂家和操作系统版本
int main(int argc, char **argv)
{
    union {
        short  s;
        char   c[sizeof(short)];
    } un;

    un.s = 0x0102;
    printf("%s: ", CPU_VENDOR_OS);
    if (sizeof(short) == 2) {
        if (un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    } else
        printf("sizeof(short) = %d\n", sizeof(short));

    exit(0);
}

总结:

c/c++字节序转换

阅读数 145

没有更多推荐了,返回首页