Socket基础、字节序与编码
Socket
Socket(套接字)是对TCP/IP网络协议进行的包装(协议的抽象应用),它本身最大的特点是提供了不同进程之间的数据通讯操作。所有的网络协议的组成是非常繁琐的,如果所有的开发者去研究具体的通讯协议会对开发带来很大的难度,所以在不同的编程语言内部就会考虑对一些网络的协议进行包装。
Socket主要是针对两种协议的包装:
-
传输控制协议TCP(Transmission Control Protocol):采用有状态的通讯机制进行传输,在通讯时会通过三次握手机制保证与一个指定节点的数据传输的可靠性,在通讯完毕后会通过四次挥手的机制关闭连接。由于在每次数据通讯前都需要消耗大量的时间进行连接控制,所以执行性能较低,且系统资源占用较大。
-
用户数据报协议UDP(User Datagram Protocol):采用无状态的通讯机制进行传输,没有了TCP中复杂的握手与挥手处理机制,这样就节约了大量的系统资源,同时数据传输性能较高。但是由于不保存单个节点的连接状态,所以发送的数据不一定可以被全部接收。UDP不需要连接就可以直接发送数据,并且多个接收端都可以接收同样的消息,所以使用UDP适合于广播操作。
需要区分连接建立与每次发送:三次握手用于建立TCP连接,不是在同一连接上每次发送都重新握手。可靠传输还依赖序号、确认与重传等机制,不能只归因于握手。性能取决于负载与环境,TCP并非总比UDP慢。普通UDP单播也不会自动广播;广播需要适当目标地址、网络支持与套接字选项。
字节序(Endianness)
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。计算机有两种储存数据的方式,分别是大端字节序(big endian)和小端字节序(little endian)。
例如存储两个十六进制数0x12345678和0x11223344,小端字节序把值的低位存在低地址,而大端字节序把值的高位存在低地址。
内存地址从左向右增加。
| 字节序 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 小端 | 78 | 56 | 34 | 12 | 44 | 33 | 22 | 11 |
| 大端 | 12 | 34 | 56 | 78 | 11 | 22 | 33 | 44 |
0x12345678 · 0x11223344
由此引发了计算机界的大端与小端之争,不同的CPU厂商并没有达成一致。在网络通信中,RFC1700规定使用大端字节序作为网络字节序。因此不使用大端的计算机在发送数据时必须将自己的主机字节序转换为网络字节序,接收数据时需要转换成自己的主机字节序,这样就与CPU和操作系统无关了。
htons()和htonl()可用于将主机字节序转换为网络字节序,ntohs()和ntohl()可用于将网络字节序转换为主机字节序。
这些约定针对需要按协议解释的多字节数值,不是把任意正文逐字节倒序。Python可用struct.pack("!I", value)把无符号32位整数编码为网络序,struct.unpack执行对应解码;!显式指定网络序,与当前主机内存顺序无关。
发送接收方能够正确解释的整数
解锁完整互动实验、进阶解析与精选参考答案。
知识测验
字符串与字节
Python的str是文本,套接字传输的是bytes。text.encode("utf-8")把文本编码为字节,data.decode("utf-8")把完整编码的字节还原成文本。字符数不一定等于字节数,例如“网络”有两个字符,却有六个UTF-8字节。
UTF-8字符可能占多个字节。TCP一次接收不保证恰好落在字符或消息边界;应先按协议累积完整数据,或使用增量解码器,不要把每次任意收到的字节都直接当成完整文本。
知识测验
编码整数并计算文本字节数
用Python的struct把0x12345678编码为无符号32位网络序整数,打印十六进制字节并解码还原。再打印“网络”的字符数与UTF-8字节数。预期三行为12345678、305419896、2 6。不要依赖CPU本机字节序。