3.4. 套接字和网络连接

套接字用于通信,尤其是在网络上。套接字最初由 Unix 系统的 BSD 分支开发,但它们通常可以移植到其他类 Unix 系统:Linux 和 System V 变体也支持套接字,并且 Open Group 的 Single Unix Specification [Open Group 1997] 要求支持套接字。System V 系统传统上使用不同的(不兼容的)网络通信接口,但值得注意的是,像 Solaris 这样的系统也包括对套接字的支持。`socket(2)` 创建一个通信端点并返回一个描述符,方式类似于文件的 `open(2)`。`socket` 的参数指定协议族和类型,例如 Internet 域(TCP/IP 版本 4)、Novell 的 IPX 或“Unix 域”。然后服务器通常调用 `bind(2)`、`listen(2)` 和 `accept(2)` 或 `select(2)`。客户端通常调用 `bind(2)`(虽然可以省略)和 `connect(2)`。有关更多信息,请参阅这些例程各自的手册页。从它们的手册页中理解如何使用套接字可能很困难;您可能需要查阅其他论文,例如 Hall "Beej" [1999],以了解如何一起使用这些调用。

“Unix 域套接字”实际上并不代表网络协议;它们只能连接到同一台机器上的套接字。(在编写本文时,针对标准 Linux 内核)。当用作流时,它们与命名管道非常相似,但具有显着的优势。特别是,Unix 域套接字是面向连接的;到套接字的每个新连接都会产生一个新的通信通道,这与命名管道的情况非常不同。由于此特性,Unix 域套接字通常用于代替命名管道来实现许多重要服务的 IPC。就像你可以拥有未命名管道一样,你可以使用 `socketpair(2)` 拥有未命名的 Unix 域套接字;未命名的 Unix 域套接字以类似于未命名管道的方式用于 IPC。

Unix 域套接字有几个有趣的安全性含义。首先,虽然 Unix 域套接字可以出现在文件系统中,并且可以对其应用 `stat(2)`,但你不能使用 `open(2)` 打开它们(你必须使用 `socket(2)` 及其友元接口)。其次,Unix 域套接字可以用于在进程之间传递文件描述符(而不仅仅是文件的内容)。这种奇怪的功能,在任何其他 IPC 机制中都不可用,已被用于破解各种方案(描述符基本上可以用作计算机科学意义上“能力”的有限版本)。文件描述符使用 `sendmsg(2)` 发送,其中 msg(消息)的字段 msg_control 指向控制消息头数组(字段 msg_controllen 必须指定数组中包含的字节数)。每个控制消息都是一个结构体 `cmsghdr`,后跟数据,为此目的,您需要将 `cmsg_type` 设置为 `SCM_RIGHTS`。文件描述符通过 `recvmsg(2)` 检索,然后以类似的方式跟踪。坦率地说,此功能非常巴洛克式,但值得了解。

Linux 2.2 及更高版本在 Unix 域套接字中支持一项附加功能:您可以获取对等方的“凭据”(pid、uid 和 gid)。这是一个示例代码
 /* fd= file descriptor of Unix domain socket connected
    to the client you wish to identify */

 struct ucred cr;
 int cl=sizeof(cr);

 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
   printf("Peer's pid=%d, uid=%d, gid=%d\n",
           cr.pid, cr.uid, cr.gid);

标准 Unix 约定是绑定到小于 1024 的 TCP 和 UDP 本地端口号需要 root 权限,而任何进程都可以绑定到 1024 或更大的未绑定端口号。Linux 遵循此约定,更具体地说,Linux 要求进程具有 CAP_NET_BIND_SERVICE 能力才能绑定到小于 1024 的端口号;此能力通常仅由 EUID 为 0 的进程持有。喜欢冒险的人可以通过检查 Linux 源代码来在 Linux 中验证这一点;在 Linux 2.2.12 中,它是文件/usr/src/linux/net/ipv4/af_inet.c,函数 `inet_bind()`。