- 英文小册原文地址:beej.us/guide/bgnet…
- 作者:Beej
- 中文翻译地址:www.chanmufeng.com/posts/netwo…
现将目录贴下:
- 什么是socket
- 两种Socket
- 漫谈网络
- IP地址、struct以及地址转换
- IP地址,IPv4和IPv6
- 子网
- 字节序
- struct结构
- 再谈IP地址
- 从IPv4迁移到IPv6
- IP地址,IPv4和IPv6
- 字节序
- struct结构
- 再谈IP地址
- System call 或 Bust
- getaddrinfo()—准备开始!
- socket()—拿到文件描述符!
- bind()—我在监听哪个端口?
- connect()—嘿,你好啊!
- listen()—会有人联系我吗?
- accept()—感谢呼叫3490端口
- send() and recv()—跟我唠唠吧,宝儿!
- sendto() and recvfrom()—用DGRAM风格跟我说话
- close() and shutdown()—滚犊子!
- getpeername()—你哪位?
- gethostname()—我是谁?
- Client-Server基础
- 一个简单的流服务器
- 一个简单的流客户端
- Datagram Sockets
- 技术进阶
- Blocking—何谓阻塞?
- poll()—同步的I/O多路复用
- select()—老古董的同步I/O多路复用
- 数据只传了一部分怎么办?
- Serialization-如何封装数据?
- 数据封装
- 广播数据包-大声说「Hello,World」
什么是字节序
字节是内存读写的最小单位,一个字节能够表示的数据范围是0~255,也就是说如果你要保存一个在此范围区间的数字,一个字节足矣。
但是像占4个字节的int
,8个字节的double
,该怎么存储呢?多个字节该用怎样的顺序来进行排列?举个例子,0xb3f4
,数值的高位b3
是存储在了内存的高地址处,还是内存的低地址处呢?
这种字节的排列顺序就叫做字节序,字节序有两种:
- 大端字节序
数值的低字节放在内存的低地址处,数值的高字节放在内存的高地址。
- 小端字节序
数值的低字节放在内存的高地址处,数值的高字节放在内存的低地址。
主机字节序
当我们谈论字节序,大部分时候对应的都是CPU访问内存时的概念,比如对下图而言:
内存低位存储的字节是0xb3
,高位存储的是0xf4
,至于CPU读取这个2字节数据的时候,是将其解释为0xb3f4
(小端)还是0xf4b3
(大端)就取决于CPU采用的是哪一种字节序了。
常用的CPU字节序如下:
- 大端字节序:IBM、PowerPC
- 小端字节序:x86
这种CPU字节序也被称为主机字节序(Host Byte Order) 。翻译一下就是,IBM的主机序是大端,x86的主机序是小端。
问题来了。
世界上的主机这么多,每台主机的CPU类型还不一样,A主机按照A的主机字节序发信息给B主机,B主机如果直接按照B的主机字节序来解析信息,那么极有可能会产生错误。
要想解决这个问题,还必须引出一些其他情况下的字节序。
为了避免引起混淆,我在这里强调一下,字节序就分为2种,大端和小端。而所谓的主机字节序以及下面我将提到的两种,都只是字节序作用在不同的场景中取得特定名称罢了。
文件存储字节序
顾名思义,就是存储文件信息的时候用的是大端还是小端。
bmp格式的图片属于小端字节序,jpeg格式的图片就是大端字节序。
这里提及文件存储字节序主要是为了让大家理解一个事实:采用什么字节序完全是开发者设计产品时的一种技术选型罢了。但是这种选型一定要成为一种标准,让其他开发者解析的时候明白应该用哪种字节序,否则jpeg的图片也就没办法在所有的电脑上正常显示了,你说对吗?
现在好了,所有jpeg软件的开发者都知道应该用大端字节序来保存jpeg图片,但是对于从其他主机传过来的jpeg数据流,开发者又怎么知道这个数据流用的是大端还是小端呢?
这就是网络字节序(Network Byte Order) 的问题了。
网络字节序
我想你应该能想明白了,网络字节序不是大端就是小端,不可能存在「可能是大端也可能是小端」这种情况,否则网络传递的消息全都乱套了!
TCP/IP既然是一种标准,那标准自然就有自己的字节序规定。
TCP/IP网络采用的是大端,是规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。
也就是说,如果你想让你的消息能在其他设备正常解析,就必须按照大端字节序进行处理。比如,网络传输的是0x12345678
这个整形变量,首先被发送的应该是0x12
,接着是0x34
,然后是0x56
,最后是0x78
。
字节序在socket中的例子
当你在网络封包或者在填充数据的时候,你需要确定你的端口号(port number)和ip地址都是符合网络字节序的。但是你并不知道你的主机字节序是大端还是小端,你自然也就搞不清你到底需不需要进行转换。
其实不必如此费神,不用在意主机序,直接调用函数强行将主机序转换为网络字节序即可。下面举一个C语言编写服务器代码绑定端口的例子。
代码中被红色标记的部分就是分别将端口号33000
由主机序转换为网络字节序,将ip地址由主机序转换为网络字节序的函数。
乍眼一看,函数名非常古怪,不好记忆,下面稍微解释一下,各位就豁然开朗了。
Socket函数库
提供了这种字节序转换的函数,函数的作用对象有两种,分别是short
(2字节)和long
(4字节),这就是函数最后的「s
」和「l
」的含义。
还有,主机字节序的英文是Host Byte Order
,简写为「h
」,网络字节序的英文是Network Byte Order
,简写为「n
」,「to
」表示转换,所以,如果你想将short
数据从主机字节序转换为网络字节序,那就应该是h-to-n-s
,表示Host to Network Short
。是不是很简单?
所以理论上,你可以用「h
」、「to
」、「n
」、「s/l
」这几个字母灵活组合,组成你想要的api,但是也别瞎搞,stolh()["Short to Long Host"]这种组合压根都不存在。
基本上你需要的也就是以下四种罢了:
htons() // host to network short
htonl() // host to network long
ntohs() // network to host short
ntohl() // network to host long
基本上,我们在进行socket编程时,在发送数据之前都需要把数据转换为网络字节序,并在收到数据之后再转换成主机字节序。
更多关于浮点数的处理,会在之后专门的章节提及。
如无特殊说明,本小册的数值预设都视为主机字节序。