为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

字节序

2018-02-19 15页 doc 37KB 17阅读

用户头像

is_729658

暂无简介

举报
字节序字节序 lwip是瑞士计算机科学院的一个开源的TCP/IP协议栈实现.lwIP是TCP/IP协议栈的一个实现。lwIP协议 栈主要关注的是怎么样减少内存的使用河代码的大小,这样就可以让lwIP适用于资源有限的小型平台例如 嵌入式系统。本文我们将主要关于lwip对IP头的数据结构定义。 我们首先看看IP头的RFC描述: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Tota...
字节序
字节序 lwip是瑞士计算机科学院的一个开源的TCP/IP协议栈实现.lwIP是TCP/IP协议栈的一个实现。lwIP协议 栈主要关注的是怎么样减少内存的使用河代码的大小,这样就可以让lwIP适用于资源有限的小型平台例如 嵌入式系统。本文我们将主要关于lwip对IP头的数据结构定义。 我们首先看看IP头的RFC描述: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ lwip对IP头的数据结构定义为: PACK_STRUCT_BEGIN struct ip_hdr { /* version / header length / type of service */ PACK_STRUCT_FIELD(u16_t _v_hl_tos); /* total length */ PACK_STRUCT_FIELD(u16_t _len); /* identification */ PACK_STRUCT_FIELD(u16_t _id); /* fragment offset field */ PACK_STRUCT_FIELD(u16_t _offset); #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ /* time to live / protocol*/ PACK_STRUCT_FIELD(u16_t _ttl_proto); /* checksum */ PACK_STRUCT_FIELD(u16_t _chksum); /* source and destination IP addresses */ PACK_STRUCT_FIELD(struct ip_addr src); PACK_STRUCT_FIELD(struct ip_addr dest); } PACK_STRUCT_STRUCT; PACK_STRUCT_END 看到上面的数据结构以后,大家一定觉得十分的迷惑和不解,那么我们就一一为大家解答这些疑问。我们先看看PACK_STRUCT_BEGIN和PACK_STRUCT_END定义: #ifndef PACK_STRUCT_BEGIN #define PACK_STRUCT_BEGIN #endif /* PACK_STRUCT_BEGIN */ #ifndef PACK_STRUCT_END #define PACK_STRUCT_END #endif /* PACK_STRUCT_END */ 由上面的定义可以看出,此处定义了PACK_STRUCT_BEGIN和PACK_STRUCT_END两个常量。可能大多数人看到这里都会觉得奇怪,怎么会这么使用,相信大多数人都明白#ifndef/#define/#endif的作用,主要用于防止头文件的重复引用。其实,上面两个常量的定义和使用和头文件的重复引用是一样的道理,只是这里不是防止头文件的重复引用,而是防止数据结构的重复定义。这里之所以定义其实是考虑到移植性的缘故,我们知道,不同的CPU位数可能不同,读写总线传输的字节数也可能不同,除了这些以外,还可能在其他方面存在区别;除了硬件差别外,OS也可能存在很多区别。为了便于移植,lwip采用了宏定义的方式来进行,对于不同的OS或是硬件,更改相关的宏使之适合于特定的系统即可。 上面的数据结构和常用的数据结构定义还有一些不同的地方,为此,我们先看一个常见的数据结构的定义: typedef struct _iphdr { unsigned char version:4; //版本 unsigned char ihl:4; //首部长度 unsigned char tos; //服务类型 unsigned short tot_len; //总长度 unsigned short id; //标志 unsigned short frag_off; //分片偏移 unsigned char ttl; //生存时间 unsigned char protocol; //协议 unsigned char check; //检验和 unsigned long saddr; //源IP地址 unsigned long daaddr; //目的IP地址 }iphdr; 上面的数据结构的定义和RFC的说明是完全一一对应的,而lwip的定义则和此定义相隔万里。lwip的 定义里面使用了一个宏定义PACK_STRUCT_FIELD,该宏的定义为: #ifndef PACK_STRUCT_FIELD #define PACK_STRUCT_FIELD(x) x #endif /* PACK_STRUCT_FIELD */ 这三个宏的定义和前面的宏的作用相同,都是便于移植。 在说明lwip中如何进行主机序和网络序的转换时,我们有必要先了解几个基本概念:MSB、LSB、Big Endian、Little Endian、网络序和主机序。 MSB是Most Significant Bit/Byte的首字母缩写,通常译为最重要的位或者最重要的字节;那么对于一个数字而言,什么是MSB呢,显然最高位是MSB,例如15430,1就是MSB,因为它在万位,它的变化是以10000为基数的。知道了MSB,LSB也就不难理解;LSB是Least Significant Bit/Byte的首字母缩写,通常译为最不重要的位或者最不重要的字节;对于15430而言,0显然是LSB,因为它在各位,它的变化对于整个数值的大小影响最小。 Big Endian和Little Endian是描述排列存储在计算机内存里的字节序列的术语。之所以出现两种排列次序,是由于CPU的两大对立阵营的对抗导致的,PowerPC(Moto&IBM) VS X86 = Big Vs. Little。在Big Endian机制中最重要字节(MSB)存放在最低端的地址上;而Little Endian机制中,最不重要字节(LSB)存放在最低端的地址上。 例如0x12345678在采用Big Endian的CPU(PowerPC为代表)中,其存放顺序为: 0x0000 12 0x0001 34 0x0002 56 0x0003 78 图1 0x12345678在Big Endian CPU中的存储方式 而在采用Little Endian的CPU(X86为代表)中,其存放顺序为: 0x0000 78 0x0001 56 0x0002 34 0x0003 12 图2 0x12345678在Big Endian CPU中的存储方式 关于Big Endian和Little Endian还有一点需要说明的是:软件只需要关注字节顺序就可以了,硬件除了要处理字节顺序外,还需要处理位序。如果你觉得Big Endian和Little Endian很难理解,可以这么理解,Big Endian就是最先读出最高(最大)的字节,而Little Endian最先读出最低(最小)的字节。 通常在TCP/IP协议栈所说的网络序(Network Order)就是遵循Big-Endian规则。在TCP/IP网络通信中,通信双方把消息按照如图1的方式进行编码,然后按从MSB(Bit0)到LSB的顺序在网络上传送;而通常我们说的主机序(Host Order)(X86架构CPU)就是遵循Little-Endian规则。所以当两台主机之间要通过TCP/IP协议进行通信的时候就需要调用相应的函数进行主机序(Little-Endian)和网络序(Big-Endian)的转换。 了解了这些基本概念后,我们进入正题。lwip由于考虑到移植性问题,因此它没有默认主机序为Little Endian,而是两种情况都进行了处理;而且处于灵活性考虑,还允许我们用自己定义的代码替换lwip提供的函数: #if LWIP_PLATFORM_BYTESWAP #define htons(x) LWIP_PLATFORM_HTONS(x) #define ntohs(x) LWIP_PLATFORM_HTONS(x) #define htonl(x) LWIP_PLATFORM_HTONL(x) #define ntohl(x) LWIP_PLATFORM_HTONL(x) #else u16_t htons(u16_t x); u16_t ntohs(u16_t x); u32_t htonl(u32_t x); u32_t ntohl(u32_t x); #endif 如果我们需要采用自己定义的函数,只需要定义LWIP_PLATFORM_BYTESWAP为1,并编写相应的函数即可: #define LWIP_PLATFORM_BYTESWAP 1 #define LWIP_PLATFORM_HTONS(x) #define LWIP_PLATFORM_HTONL(x) 考察了lwip实现的灵活性后,我们再来看看其移植性问题。为了便于移植,lwip引入了3个宏:BYTE_ORDER、LITTLE_ENDIAN和BIG_ENDIAN,后两个宏的定义为: #ifndef LITTLE_ENDIAN #define LITTLE_ENDIAN 1234 #endif #ifndef BIG_ENDIAN #define BIG_ENDIAN 4321 #endif 而BYTE_ORDER由我们自己根据CPU类型来定义,如果CPU采用Big Endian,就定义为4321,反之就定义为1234。 有了这三个宏以后,代码的编写就很简单了: #if BYTE_ORDER == BIG_ENDIAN #define htons(x) (x) #define ntohs(x) (x) #define htonl(x) (x) #define ntohl(x) (x) #else /* BYTE_ORDER != BIG_ENDIAN */ #if LWIP_PLATFORM_BYTESWAP #define htons(x) LWIP_PLATFORM_HTONS(x) #define ntohs(x) LWIP_PLATFORM_HTONS(x) #define htonl(x) LWIP_PLATFORM_HTONL(x) #define ntohl(x) LWIP_PLATFORM_HTONL(x) #else u16_t htons(u16_t x); u16_t ntohs(u16_t x); u32_t htonl(u32_t x); u32_t ntohl(u32_t x); #endif 当CPU类型为Big Endian时,主机序与网络序同序,不需要改动;而CPU类型为Little Endian时,主机序与网络序正好相反,此时lwip定义了相应的函数来处理,这些函数通过移位来实现,本文以u32_t htonl(u32_t x)来说明: u32_t htonl(u32_t n) { return ((n & 0xff) << 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24); } 上面的代码其实很简单,就是将字节进行逆序排列。 写到这里,相信大家都对lwip处理网络序和字节序的机制有了一定的了解了。 lwip中有三个IP地址转换函数,分别是点分十进制字符串->数值、数值结构体<->点分十进制,比较奇怪的是lwip并未提供数值->点分十进制字符串的转换函数,不过添加这样一个函数十分简单,本文最后会给出这样一个函数。 在了解这几个函数前,我们先看看刚刚提到的结构体,定义该结构体很大程度上与BSD兼容有关: /* For compatibility with BSD code */ struct in_addr { u32_t s_addr; }; 该结构体十分简单,就是包含一个32bit的无符号整型数值。我们下面再来看看前面提到的三个函数: u32_t inet_addr(const char *cp); int inet_aton(const char *cp, struct in_addr *addr); char *inet_ntoa(struct in_addr addr); 我们先来看看inet_aton函数,该函数将字符串转换为in_addr结构体。在查看该函数的实现方式前,我们有必要了解下面的几个宏: /* Here for now until needed in other places in lwIP */ #ifndef isprint #define in_range(c, lo, up) ((u8_t)c >= lo && (u8_t)c <= up) #define isprint(c) in_range(c, 0x20, 0x7f) #define isdigit(c) in_range(c, '0', '9') #define isxdigit(c) (isdigit(c) || in_range(c, 'a', 'f') || in_range(c, 'A', 'F')) #define islower(c) in_range(c, 'a', 'z') #define isspace(c) (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v') #endif 很显然,上面的几个宏主要用于判断字符c所属的范围,isprint用于判断c是否可打印;isdigit用于判断c是否是数字;isxdigit用于判断是否是十六进制数字;islower用于判断是否是小写字母;isspace判断是否是广义空格符。 为了不纠缠于代码的细节,我们来看看inet_addr函数的伪码实现: 取字符串的第一个字符; for(;;) { 取得的字符是否是数字,即isdigit(c)是否返回真,不是则出错退出; 针对该数字进行基数判断,即判断给定的数是十进制,还是八进制或十六进制; for(;;) { 判断后续字符是否是数字或A-F(十六进制时),如果不是则出错直接退出; 采用刚才获得的基数解析后面的数字,并保存; } 判断给定的字符是否是'.',如果不是,直接退出循环; 判断数组的下标值是否大于等于3,如果是出错退出; 将刚刚计算得到的数值保存到数组中; } 判断字符串余下的字符是否是空白符,不是则出错退出; 计算数组的下标值,由此判断字符串属于何种类型值(即32、8.24、8.8.16、8.8.8.8) 根据以上判断进行转换。 上面描述了字符串到数值的转换,看似简单,实则不然,lwip代码中有一个明显的缺陷,就是缺少对8进制数的判断,具体情形我就不详细描述了,下面给出其实现代码,感兴趣的朋友下去可以自己研究改进: int inet_aton(const char *cp, struct in_addr *addr) { u32_t val; int base, n, c; u32_t parts[4]; u32_t *pp = parts; c = *cp; for (;;) { /* * Collect number up to ``.''. * Values are specified as for C: * 0x=hex, 0=octal, 1-9=decimal. */ if (!isdigit(c)) return (0); val = 0; base = 10; if (c == '0') { c = *++cp; if (c == 'x' || c == 'X') { base = 16; c = *++cp; } else base = 8; } for (;;) { if (isdigit(c)) { val = (val * base) + (int)(c - '0'); c = *++cp; } else if (base == 16 && isxdigit(c)) { val = (val << 4) | (int)(c + 10 - (islower(c) ? 'a' : 'A')); c = *++cp; } else break; } if (c == '.') { /* * Internet format: * a.b.c.d * a.b.c (with c treated as 16 bits) * a.b (with b treated as 24 bits) */ if (pp >= parts + 3) return (0); *pp++ = val; c = *++cp; } else break; } /* * Check for trailing characters. */ if (c != '#CONTENT#' && (!isprint(c) || !isspace(c))) return (0); /* * Concoct the address according to * the number of parts specified. */ n = pp - parts + 1; switch (n) { case 0: return (0); /* initial nondigit */ case 1: /* a -- 32 bits */ break; case 2: /* a.b -- 8.24 bits */ if (val > 0xffffff) return (0); val |= parts[0] << 24; break; case 3: /* a.b.c -- 8.8.16 bits */ if (val > 0xffff) return (0); val |= (parts[0] << 24) | (parts[1] << 16); break; case 4: /* a.b.c.d -- 8.8.8.8 bits */ if (val > 0xff) return (0); val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); break; } if (addr) addr->s_addr = htonl(val); return (1); } 看完了inet_aton函数,我们再来看看inet_ntoa函数,该函数要简单得多: char * inet_ntoa(struct in_addr addr) { static char str[16]; u32_t s_addr = addr.s_addr; char inv[3]; char *rp; u8_t *ap; u8_t rem; u8_t n; u8_t i; rp = str; ap = (u8_t *)&s_addr; for(n = 0; n < 4; n++) { i = 0; do { rem = *ap % (u8_t)10; *ap /= (u8_t)10; inv[i++] = '0' + rem; } while(*ap); while(i--) *rp++ = inv[i]; *rp++ = '.'; ap++; } *--rp = 0; return str; } 代码行数也少了不少,其思想也很简单,就是求余和求商操作,因此这里不再详述。 最后我们以自定义的一个十分简单的函数结束本文: char * inet_addrstr(const u32_t addr) { struct in_addr val; val.s_addr = htonl(addr); return inet_ntoa(val); }
/
本文档为【字节序】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索