网络字节序与主机字节序的转换

前言

端口号和IP地址都是以网络字节序存储的,不是主机字节序。网络字节序都是大端模式,而我们常用的机器都是小端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。其实这个转换实质是:字节“搬家”。

先分析一下我们平时使用ntohlntohshtonlhtons函数是怎么实现的,然后在本文最后写一个判断机器是大/小端的函数。

函数追踪

函数定义的地方:
/usr/include/netinet/in.h

# 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


可以看到ntohlhtonl使用的是同一个函数__bswap_32ntohshtons使用的是同一个函数__bswap_16,在这里只对__bswap_32函数的实现进行追踪:
/usr/include/i386-linux-gnu/bits/byteswap.h

/* Swap bytes in 32 bit value.  */
#define __bswap_constant_32(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#ifdef __GNUC__
# if __GNUC__ >= 2
/* To swap the bytes in a word the i486 processors and up provide the
   `bswap' opcode.  On i386 we have to use three instructions.  */
#  if !defined __i486__ && !defined __pentium__ && !defined __pentiumpro__ \
      && !defined __pentium4__ && !defined __k8__ && !defined __athlon__ \
      && !defined __k6__ && !defined __nocona__ && !defined __core2__ \
      && !defined __geode__ && !defined __amdfam10__
#   define __bswap_32(x)                              \
     (__extension__                               \
      ({ register unsigned int __v, __x = (x);                    \
     if (__builtin_constant_p (__x))                      \
       __v = __bswap_constant_32 (__x);                   \
     else                                     \
       __asm__ ("rorw $8, %w0;"                       \
            "rorl $16, %0;"                       \
            "rorw $8, %w0"                        \
            : "=r" (__v)                          \
            : "0" (__x)                           \
            : "cc");                              \
     __v; }))
#  else
#   define __bswap_32(x) \
     (__extension__                               \
      ({ register unsigned int __v, __x = (x);                    \
     if (__builtin_constant_p (__x))                      \
       __v = __bswap_constant_32 (__x);                   \
     else                                     \
       __asm__ ("bswap %0" : "=r" (__v) : "0" (__x));             \
     __v; }))
#  endif
# else
#  define __bswap_32(x) \
     (__extension__                               \
      ({ register unsigned int __x = (x); __bswap_constant_32 (__x); }))
# endif
#else
static __inline unsigned int
__bswap_32 (unsigned int __bsx)
{                                        
  return __bswap_constant_32 (__bsx);
}
#endif

这里有对不同的处理器有不同的处理情况,还有汇编指令,这里先不对这部分做深究,所以最终的实现为:

/* Swap bytes in 32 bit value.  */
#define __bswap_constant_32(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))


虽然没有对应的64bit的转换函数接口,但在文件bits/byteswap.h中还是有实现:

#if defined __GNUC__ && __GNUC__ >= 2
/* Swap bytes in 64 bit value.  */
# define __bswap_constant_64(x) \
     (__extension__ ((((x) & 0xff00000000000000ull) >> 56)            \
             | (((x) & 0x00ff000000000000ull) >> 40)              \
             | (((x) & 0x0000ff0000000000ull) >> 24)              \
             | (((x) & 0x000000ff00000000ull) >> 8)           \
             | (((x) & 0x00000000ff000000ull) << 8)           \
             | (((x) & 0x0000000000ff0000ull) << 24)              \
             | (((x) & 0x000000000000ff00ull) << 40)              \
             | (((x) & 0x00000000000000ffull) << 56)))

# define __bswap_64(x) \
     (__extension__                               \
      ({ union { __extension__ unsigned long long int __ll;           \
         unsigned long int __l[2]; } __w, __r;                \
     if (__builtin_constant_p (x))                        \
       __r.__ll = __bswap_constant_64 (x);                    \
     else                                     \
       {                                      \
         __w.__ll = (x);                              \
         __r.__l[0] = __bswap_32 (__w.__l[1]);                \
         __r.__l[1] = __bswap_32 (__w.__l[0]);                \
       }                                      \
     __r.__ll; }))
#endif


对于__BYTE_ORDER:

/* i386 is little-endian.  */                                                                                                       

#ifndef _ENDIAN_H
# error "Never use <bits/endian.h> directly; include <endian.h> instead."
#endif

#define __BYTE_ORDER __LITTLE_ENDIAN

这里大小端模式竟然是直接定义好的,还以为会用使用数据存储来判断一下呢。。。那我们自己动手来写一下大小端的判断函数:

#include <stdio.h>

#define BIG_ENDIAN 1  
#define LITTLE_ENDIAN 0  

#define BOOL unsigned int

/*
 * 定义一个2个字节长度的数据,并赋值为1,则其16进制表示为0x0001 
 * 如果系统以“大端”存放数据,那么低字节存放的必定是0x00,高字节存放的必定是0x01 
 * 如果系统以“小端”存放数据,那么低字节存放的必定是0x01,高字节存放的必定是0x00 
 */  
static BOOL is_bigendian()
{  
    const short n = 1;  
    if (*(char *)&n)  
    {   
        return LITTLE_ENDIAN;  
    }   
    return BIG_ENDIAN;  
} 

int main()
{
    if (BIG_ENDIAN == is_bigendian())
    {   
        printf("Big Endian\n");
    }   
    else
    {   
        printf("Little Endian\n");
    }   

    return 0;
} 


结语

知其然,也要知其所以然。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值