套接字接口不直接使用协议类型来标识通信时的协议,而是采用一种更灵活的方式—协议簇 +套接字类型,这样便于网络应用程序在多协议簇的同类套接字程序之间相互移植。
协议簇
在套接字通信中,常用的协议簇有如下几种。
- PF_INET:IPv4 协议簇。
- PF_INET6:IPv6 协议簇。
- PF_IPX:IPX/SPX 协议簇。
- PF_NETBIOS:NetBIOS 协议簇。
AF_INET 是地址族(Address Family)的宏定义,PF_INET 是协议簇(Protocol Family)的宏定义。 在 Linux 内核和绝大多数现代系统中,它们本质上是同一个值,可以互换使用。
TCP/IP 协议定义了端点地址来标识通信端点,它包括一个 IP 地址加一个协议端口号。由于套接字适用于多种协议簇,它既没有指明如何定义端点地址,也没有定义一种特定的协议地址格式, 而是改为允许每个协议簇自由定义。为了允许这种自由选择地址表示方式,套接字为每种类型的地址定义了一个地址族。一个协议簇可以使用一种或多种地址族来定义地址表示方式。常见的地址族 有以下几种。
- AF_INET: IPv4 地址族。
- AF_INET6: IPv6 地址族。
- AF_IPX: IPX/SPX 地址族。
- AF_ NETBIOS: NetBIOS 地址族。
套接字类型
在套接字通信中,常用套接字类型包括 3 类:
-
流式套接字(SOCK_STREAM)。流式套接字用于提供面向连接、可靠的数据传输服务,该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。数据传输可以是双向的字节流。
-
数据报套接字(SOCK_DGRAM)。数据报套接字用于提供无连接的数据传输服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或重复,且无法保证顺序地接收到数据。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
-
原始套接字(SOCK_RAW)。当使用前两种套接字无法完成数据收发任务时,原始套接字提供了更加灵活的数据访问接口,使用它可以在网络层上对 Socket 进行编程,发送和接收网络层上的原始数据包。
TCP/IP模型在API中的体现
Linux 的 socket API完美映射了网络协议栈的分层模型:
- 网络层:负责主机到主机的通信(IP地址寻址、路由)。
- 传输层:负责进程到进程的通信(端口、连接可靠性、数据流)。
socket API将这两个职责分离开:
- AF_INET: 处理网络层的约定。它告诉系统:“我将在IPv4的地址空间中使用地址(如192.168.1.1:8080)。”
- SOCK_STREAM: 处理传输层的“服务类型”。它告诉系统:“我想要一个有序、可靠、双向、基于连接的字节流服务。”
- protocol参数: 通常为0,由系统根据前两个参数自动选择。如果你想显式指定,可以用IPPROTO_TCP。内核会查找一个能同时满足AF_INET和SOCK_STREAM的协议,自然就找到了TCP。
// 语义化写法:我要一个“流式”套接字,在 IPv4 域里
socket(AF_INET, SOCK_STREAM, 0);
// 移植到 IPv6 时,只需修改协议簇,类型不变
socket(AF_INET6, SOCK_STREAM, 0);
这种解耦的好处是:如果未来有一种新的网络层协议(比如AF_XXX),但只要它也支持流式服务,那么现有的、基于SOCK_STREAM的应用程序代码(处理连接、收发数据)理论上可以几乎不加修改地运行在这个新协议上,只需要在创建socket时改变地址族即可。这就是抽象的力量。