Android C++ 系列:Linux Socket 编程(一)预备知识
1. 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节。例如前面文章介绍的的 UDP 段格式,地址 0-1 是 16 位的源端口号,如果这个端口号是 1000(0x3e8),则地址 0 是 0x03, 地址 1 是 0xe8,也就是先发 0x03,再发 0xe8,这 16 位在发送主机的缓冲区中也应该是低地址 存 0x03,高地址存 0xe8。但是,如果发送主机是小端字节序的,这 16 位被解释成 0xe803,而 不是 1000。因此,发送主机把 1000 填到发送缓冲区之前需要做字节序的转换。同样地,接收 主机如果是小端字节序的,接到 16 位的源端口号也要做字节序的转换。如果主机是大端字节 序的,发送和接收都不需要做转换。同理,32 位的 IP 地址也要考虑网络字节序和主机字节序 的问题。
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运 行,可以调用以下库函数做网络字节序和主机字节序的转换。
h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
2. IP 地址转换函数
早期:
最新:
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr, 因此函数接口是 void *addrptr。
3. sockaddr 数据结构
strcut sockaddr 很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 sockaddr 结 构体,为了向前兼容,现在 sockaddr 退化成了(void *)的作用,传递一个地址给函数,至 于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转 化为所需的地址类型
Pv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包 括 16 位端口号和 32 位 IP 地址,IPv6 地址用 sockaddr_in6 结构体表示,包括 16 位端口号、128 位 IP 地址和一些控制字段。UNIX Domain Socket 的地址格式定义在 sys/un.h 中,用 sock- addr_un 结构体表示。各种 socket 地址结构体的开头都是相同的,前 16 位表示整个结构 体的长度(并不是所有 UNIX 的实现都有长度字段,如 Linux 就没有),后 16 位表示地址类 型。IPv4、IPv6 和 Unix Domain Socket 的地址类型分别定义为常数 AF_INET、AF_INET6、AF_UNIX。 这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构 体,就可以根据地址类型字段确定结构体中的内容。因此,socket API 可以接受各种类型的 sockaddr 结构体指针做参数,例如 bind、accept、connect 等函数,这些函数的参数应该设 计成 void *类型以便接受各种类型的指针,但是 sock API 的实现早于 ANSI C 标准化,那时还 没有 void *类型,因此这些函数的参数都用 struct sockaddr *类型表示,在传递参数之前 要强制类型转换一下,例如:
4. 总结
本文介绍了网络字节序概念以及字节序转换 C 函数、IP 地址转换函数、sockaddr 数据结构等。
版权声明: 本文为 InfoQ 作者【轻口味】的原创文章。
原文链接:【http://xie.infoq.cn/article/9dbffd216732d3a7855d0f16c】。文章转载请联系作者。
评论