引言上一篇文章中我们讲了 UDP。很多同学都知道 TCP 和 UDP但是对本地套接字却不甚了解。实际上本地套接字是 IPC也就是本地进程间通信的一种实现方式。除了本地套接字以外其它技术诸如管道、共享消息队列等也是进程间通信的常用方法但因为本地套接字开发便捷接受度高所以普遍适用于在同一台主机上进程间通信的各种场景。那么今天我们就来学习下本地套接字方面的知识并且利用本地套接字完成可靠字节流和数据报两种协议。从例子开始现在最火的云计算技术是什么无疑是 Kubernetes 和 Docker。在 Kubernetes 和 Docker 的技术体系中有很多优秀的设计比如 Kubernetes 的 CRIContainer Runtime Interface其思想是将 Kubernetes 的主要逻辑和 Container Runtime 的实现解耦。我们可以通过 netstat 命令查看 Linux 系统内的本地套接字状况下面这张图列出了路径为 /var/run/dockershim.socket 的 stream 类型的本地套接字可以清楚地看到开启这个套接字的进程为 kubelet。kubelet 是 Kubernetes 的一个组件这个组件负责将控制器和调度器的命令转化为单机上的容器实例。为了实现和容器运行时的解耦kubelet 设计了基于本地套接字的客户端 - 服务器 GRPC 调用。眼尖的同学可能发现列表里还有 docker-containerd.sock 等其他本地套接字是的Docker 其实也是大量使用了本地套接字技术来构建的。如果我们在 /var/run 目录下将会看到 docker 使用的本地套接字描述符:本地套接字概述本地套接字一般也叫做 UNIX 域套接字最新的规范已经改叫本地套接字。在前面的 TCP/UDP 例子中我们经常使用 127.0.0.1 完成客户端进程和服务器端进程同时在本机上的通信那么这里的本地套接字又是什么呢本地套接字是一种特殊类型的套接字和 TCP/UDP 套接字不同。TCP/UDP 即使在本地地址通信也要走系统网络协议栈而本地套接字严格意义上说提供了一种单主机跨进程间调用的手段减少了协议栈实现的复杂度效率比 TCP/UDP 套接字都要高许多。类似的 IPC 机制还有 UNIX 管道、共享内存和 RPC 调用等。比如 X Window 实现如果发现是本地连接就会走本地套接字工作效率非常高。现在你可以回忆一下在前面介绍套接字地址时我们讲到了本地地址这个本地地址就是本地套接字专属的。本地字节流套接字我们先从字节流本地套接字开始。这是一个字节流类型的本地套接字服务器端例子。在这个例子中服务器程序打开本地套接字后接收客户端发送来的字节流并往客户端回送了新的字节流。#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixstreamserver local_path); } int listenfd, connfd; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; listenfd socket(AF_LOCAL, SOCK_STREAM, 0); if (listenfd 0) { error(1, errno, socket create failed); } char *local_path argv[1]; unlink(local_path); bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, local_path); if (bind(listenfd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, bind failed); } if (listen(listenfd, LISTENQ) 0) { error(1, errno, listen failed); } clilen sizeof(cliaddr); if ((connfd accept(listenfd, (struct sockaddr *) cliaddr, clilen)) 0) { if (errno EINTR) error(1, errno, accept failed); /* back to for() */ else error(1, errno, accept failed); } char buf[BUFFER_SIZE]; while (1) { bzero(buf, sizeof(buf)); if (read(connfd, buf, BUFFER_SIZE) 0) { printf(client quit); break; } printf(Receive: %s, buf); char send_line[MAXLINE]; sprintf(send_line, Hi, %s, buf); int nbytes sizeof(send_line); if (write(connfd, send_line, nbytes) ! nbytes) error(1, errno, write error); } close(listenfd); close(connfd); exit(0); }我对这个程序做一个详细的解释第 1215 行非常关键这里创建的套接字类型注意是 AF_LOCAL并且使用字节流格式。你现在可以回忆一下TCP 的类型是 AF_INET 和字节流类型UDP 的类型是 AF_INET 和数据报类型。在前面的文章中我们提到 AF_UNIX 也是可以的基本上可以认为和 AF_LOCAL 是等价的。第 1721 行创建了一个本地地址这里的本地地址和 IPv4、IPv6 地址可以对应数据类型为 sockaddr_un这个数据类型中的 sun_family 需要填写为 AF_LOCAL最为关键的是需要对 sun_path 设置一个本地文件路径。我们这里还做了一个 unlink 操作以便把存在的文件删除掉这样可以保持幂等性。第 2329 行分别执行 bind 和 listen 操作这样就监听在一个本地文件路径标识的套接字上这和普通的 TCP 服务端程序没什么区别。第 4156 行使用 read 和 write 函数从套接字中按照字节流的方式读取和发送数据。我在这里着重强调一下本地文件路径。关于本地文件路径需要明确一点它必须是“绝对路径”这样的话编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”为了保持同样的目的这个程序的启动路径就必须固定这样一来对程序的管理反而是一个很大的负担。另外还要明确一点这个本地文件必须是一个“文件”不能是一个“目录”。如果文件不存在后面 bind 操作时会自动创建这个文件。还有一点需要牢记在 Linux 下任何文件操作都有权限的概念应用程序启动时也有应用属主。如果当前启动程序的用户权限不能创建文件你猜猜会发生什么呢这里我先卖个关子一会演示的时候你就会看到结果。下面我们再看一下客户端程序。#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixstreamclient local_path); } int sockfd; struct sockaddr_un servaddr; sockfd socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd 0) { error(1, errno, create socket failed); } bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, argv[1]); if (connect(sockfd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, connect failed); } char send_line[MAXLINE]; bzero(send_line, MAXLINE); char recv_line[MAXLINE]; while (fgets(send_line, MAXLINE, stdin) ! NULL) { int nbytes sizeof(send_line); if (write(sockfd, send_line, nbytes) ! nbytes) error(1, errno, write error); if (read(sockfd, recv_line, MAXLINE) 0) error(1, errno, server terminated prematurely); fputs(recv_line, stdout); } exit(0); }下面我带大家理解一下这个客户端程序。1114 行创建了一个本地套接字和前面服务器端程序一样用的也是字节流类型 SOCK_STREAM。1618 行初始化目标服务器端的地址。我们知道在 TCP 编程中使用的是服务器的 IP 地址和端口作为目标在本地套接字中则使用文件路径作为目标标识sun_path 这个字段标识的是目标文件路径所以这里需要对 sun_path 进行初始化。20 行和 TCP 客户端一样发起对目标套接字的 connect 调用不过由于是本地套接字并不会有三次握手。2838 行从标准输入中读取字符串向服务器端发送之后将服务器端传输过来的字符打印到标准输出上。总体上我们可以看到本地字节流套接字和 TCP 服务器端、客户端编程最大的差异就是套接字类型的不同。本地字节流套接字识别服务器不再通过 IP 地址和端口而是通过本地文件。接下来我们就运行这个程序来加深对此的理解。只启动客户端第一个场景中我们只启动客户端程序$ ./unixstreamclient /tmp/unixstream.sock connect failed: No such file or directory (2)我们看到由于没有启动服务器端没有一个本地套接字在 /tmp/unixstream.sock 这个文件上监听客户端直接报错提示我们没有文件存在。服务器端监听在无权限的文件路径上还记得我们在前面卖的关子吗在 Linux 下执行任何应用程序都有应用属主的概念。在这里我们让服务器端程序的应用属主没有 /var/lib/ 目录的权限然后试着启动一下这个服务器程序 $ ./unixstreamserver /var/lib/unixstream.sock bind failed: Permission denied (13)这个结果告诉我们启动服务器端程序的用户必须对本地监听路径有权限。这个结果和你期望的一致吗试一下 root 用户启动该程序sudo ./unixstreamserver /var/lib/unixstream.sock (阻塞运行中)我们看到服务器端程序正常运行了。打开另外一个 shell我们看到 /var/lib 下创建了一个本地文件大小为 0而且文件的最后结尾有一个号。其实这就是 bind 的时候自动创建出来的文件。$ ls -al /var/lib/unixstream.sock rwxr-xr-x 1 root root 0 Jul 15 12:41 /var/lib/unixstream.sock如果我们使用 netstat 命令查看 UNIX 域套接字就会发现 unixstreamserver 这个进程监听在 /var/lib/unixstream.sock 这个文件路径上。服务器 - 客户端应答现在我们让服务器和客户端都正常启动并且客户端依次发送字符$./unixstreamserver /tmp/unixstream.sock Receive: g1 Receive: g2 Receive: g3 client quit$./unixstreamclient /tmp/unixstream.sock g1 Hi, g1 g2 Hi, g2 g3 Hi, g3 ^C我们可以看到服务器端陆续收到客户端发送的字节同时客户端也收到了服务器端的应答最后当我们使用 CtrlC让客户端程序退出时服务器端也正常退出。本地数据报套接字我们再来看下在本地套接字上使用数据报的服务器端例子#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixdataserver local_path); } int socket_fd; socket_fd socket(AF_LOCAL, SOCK_DGRAM, 0); if (socket_fd 0) { error(1, errno, socket create failed); } struct sockaddr_un servaddr; char *local_path argv[1]; unlink(local_path); bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, local_path); if (bind(socket_fd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, bind failed); } char buf[BUFFER_SIZE]; struct sockaddr_un client_addr; socklen_t client_len sizeof(client_addr); while (1) { bzero(buf, sizeof(buf)); if (recvfrom(socket_fd, buf, BUFFER_SIZE, 0, (struct sockadd *) client_addr, client_len) 0) { printf(client quit); break; } printf(Receive: %s \n, buf); char send_line[MAXLINE]; bzero(send_line, MAXLINE); sprintf(send_line, Hi, %s, buf); size_t nbytes strlen(send_line); printf(now sending: %s \n, send_line); if (sendto(socket_fd, send_line, nbytes, 0, (struct sockadd *) client_addr, client_len) ! nbytes) error(1, errno, sendto error); } close(socket_fd); exit(0); }本地数据报套接字和前面的字节流本地套接字有以下几点不同第 9 行创建的本地套接字这里创建的套接字类型注意是 AF_LOCAL协议类型为 SOCK_DGRAM。2123 行 bind 到本地地址之后没有再调用 listen 和 accept回忆一下这其实和 UDP 的性质一样。2845 行使用 recvfrom 和 sendto 来进行数据报的收发不再是 read 和 send这其实也和 UDP 网络程序一致。然后我们再看一下客户端的例子#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixdataclient local_path); } int sockfd; struct sockaddr_un client_addr, server_addr; sockfd socket(AF_LOCAL, SOCK_DGRAM, 0); if (sockfd 0) { error(1, errno, create socket failed); } bzero(client_addr, sizeof(client_addr)); /* bind an address for us */ client_addr.sun_family AF_LOCAL; strcpy(client_addr.sun_path, tmpnam(NULL)); if (bind(sockfd, (struct sockaddr *) client_addr, sizeof(client_addr)) 0) { error(1, errno, bind failed); } bzero(server_addr, sizeof(server_addr)); server_addr.sun_family AF_LOCAL; strcpy(server_addr.sun_path, argv[1]); char send_line[MAXLINE]; bzero(send_line, MAXLINE); char recv_line[MAXLINE]; while (fgets(send_line, MAXLINE, stdin) ! NULL) { int i strlen(send_line); if (send_line[i - 1] \n) { send_line[i - 1] 0; } size_t nbytes strlen(send_line); printf(now sending %s \n, send_line); if (sendto(sockfd, send_line, nbytes, 0, (struct sockaddr *) server_addr, sizeof(server_addr)) ! nbytes) error(1, errno, sendto error); int n recvfrom(sockfd, recv_line, MAXLINE, 0, NULL, NULL); recv_line[n] 0; fputs(recv_line, stdout); fputs(\n, stdout); } exit(0); }这个程序和 UDP 网络编程的例子基本是一致的我们可以把它当作是用本地文件替换了 IP 地址和端口的 UDP 程序不过这里还是有一个非常大的不同的。这个不同点就在 1622 行。你可以看到 1622 行将本地套接字 bind 到本地一个路径上然而 UDP 客户端程序是不需要这么做的。本地数据报套接字这么做的原因是它需要指定一个本地路径以便在服务器端回包时可以正确地找到地址而在 UDP 客户端程序里数据是可以通过 UDP 包的本地地址和端口来匹配的。下面这段代码就展示了服务器端和客户端通过数据报应答的场景./unixdataserver /tmp/unixdata.sock Receive: g1 now sending: Hi, g1 Receive: g2 now sending: Hi, g2 Receive: g3 now sending: Hi, g3$ ./unixdataclient /tmp/unixdata.sock g1 now sending g1 Hi, g1 g2 now sending g2 Hi, g2 g3 now sending g3 Hi, g3 ^C我们可以看到服务器端陆续收到客户端发送的数据报同时客户端也收到了服务器端的应答。总结我在开头已经说过本地套接字作为常用的进程间通信技术被用于各种适用于在同一台主机上进程间通信的场景。关于本地套接字我们需要牢记以下两点本地套接字的编程接口和 IPv4、IPv6 套接字编程接口是一致的可以支持字节流和数据报两种协议。本地套接字的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报套接字实现。
07| 深入理解本地套接字
发布时间:2026/7/5 8:41:07
引言上一篇文章中我们讲了 UDP。很多同学都知道 TCP 和 UDP但是对本地套接字却不甚了解。实际上本地套接字是 IPC也就是本地进程间通信的一种实现方式。除了本地套接字以外其它技术诸如管道、共享消息队列等也是进程间通信的常用方法但因为本地套接字开发便捷接受度高所以普遍适用于在同一台主机上进程间通信的各种场景。那么今天我们就来学习下本地套接字方面的知识并且利用本地套接字完成可靠字节流和数据报两种协议。从例子开始现在最火的云计算技术是什么无疑是 Kubernetes 和 Docker。在 Kubernetes 和 Docker 的技术体系中有很多优秀的设计比如 Kubernetes 的 CRIContainer Runtime Interface其思想是将 Kubernetes 的主要逻辑和 Container Runtime 的实现解耦。我们可以通过 netstat 命令查看 Linux 系统内的本地套接字状况下面这张图列出了路径为 /var/run/dockershim.socket 的 stream 类型的本地套接字可以清楚地看到开启这个套接字的进程为 kubelet。kubelet 是 Kubernetes 的一个组件这个组件负责将控制器和调度器的命令转化为单机上的容器实例。为了实现和容器运行时的解耦kubelet 设计了基于本地套接字的客户端 - 服务器 GRPC 调用。眼尖的同学可能发现列表里还有 docker-containerd.sock 等其他本地套接字是的Docker 其实也是大量使用了本地套接字技术来构建的。如果我们在 /var/run 目录下将会看到 docker 使用的本地套接字描述符:本地套接字概述本地套接字一般也叫做 UNIX 域套接字最新的规范已经改叫本地套接字。在前面的 TCP/UDP 例子中我们经常使用 127.0.0.1 完成客户端进程和服务器端进程同时在本机上的通信那么这里的本地套接字又是什么呢本地套接字是一种特殊类型的套接字和 TCP/UDP 套接字不同。TCP/UDP 即使在本地地址通信也要走系统网络协议栈而本地套接字严格意义上说提供了一种单主机跨进程间调用的手段减少了协议栈实现的复杂度效率比 TCP/UDP 套接字都要高许多。类似的 IPC 机制还有 UNIX 管道、共享内存和 RPC 调用等。比如 X Window 实现如果发现是本地连接就会走本地套接字工作效率非常高。现在你可以回忆一下在前面介绍套接字地址时我们讲到了本地地址这个本地地址就是本地套接字专属的。本地字节流套接字我们先从字节流本地套接字开始。这是一个字节流类型的本地套接字服务器端例子。在这个例子中服务器程序打开本地套接字后接收客户端发送来的字节流并往客户端回送了新的字节流。#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixstreamserver local_path); } int listenfd, connfd; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; listenfd socket(AF_LOCAL, SOCK_STREAM, 0); if (listenfd 0) { error(1, errno, socket create failed); } char *local_path argv[1]; unlink(local_path); bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, local_path); if (bind(listenfd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, bind failed); } if (listen(listenfd, LISTENQ) 0) { error(1, errno, listen failed); } clilen sizeof(cliaddr); if ((connfd accept(listenfd, (struct sockaddr *) cliaddr, clilen)) 0) { if (errno EINTR) error(1, errno, accept failed); /* back to for() */ else error(1, errno, accept failed); } char buf[BUFFER_SIZE]; while (1) { bzero(buf, sizeof(buf)); if (read(connfd, buf, BUFFER_SIZE) 0) { printf(client quit); break; } printf(Receive: %s, buf); char send_line[MAXLINE]; sprintf(send_line, Hi, %s, buf); int nbytes sizeof(send_line); if (write(connfd, send_line, nbytes) ! nbytes) error(1, errno, write error); } close(listenfd); close(connfd); exit(0); }我对这个程序做一个详细的解释第 1215 行非常关键这里创建的套接字类型注意是 AF_LOCAL并且使用字节流格式。你现在可以回忆一下TCP 的类型是 AF_INET 和字节流类型UDP 的类型是 AF_INET 和数据报类型。在前面的文章中我们提到 AF_UNIX 也是可以的基本上可以认为和 AF_LOCAL 是等价的。第 1721 行创建了一个本地地址这里的本地地址和 IPv4、IPv6 地址可以对应数据类型为 sockaddr_un这个数据类型中的 sun_family 需要填写为 AF_LOCAL最为关键的是需要对 sun_path 设置一个本地文件路径。我们这里还做了一个 unlink 操作以便把存在的文件删除掉这样可以保持幂等性。第 2329 行分别执行 bind 和 listen 操作这样就监听在一个本地文件路径标识的套接字上这和普通的 TCP 服务端程序没什么区别。第 4156 行使用 read 和 write 函数从套接字中按照字节流的方式读取和发送数据。我在这里着重强调一下本地文件路径。关于本地文件路径需要明确一点它必须是“绝对路径”这样的话编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”为了保持同样的目的这个程序的启动路径就必须固定这样一来对程序的管理反而是一个很大的负担。另外还要明确一点这个本地文件必须是一个“文件”不能是一个“目录”。如果文件不存在后面 bind 操作时会自动创建这个文件。还有一点需要牢记在 Linux 下任何文件操作都有权限的概念应用程序启动时也有应用属主。如果当前启动程序的用户权限不能创建文件你猜猜会发生什么呢这里我先卖个关子一会演示的时候你就会看到结果。下面我们再看一下客户端程序。#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixstreamclient local_path); } int sockfd; struct sockaddr_un servaddr; sockfd socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd 0) { error(1, errno, create socket failed); } bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, argv[1]); if (connect(sockfd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, connect failed); } char send_line[MAXLINE]; bzero(send_line, MAXLINE); char recv_line[MAXLINE]; while (fgets(send_line, MAXLINE, stdin) ! NULL) { int nbytes sizeof(send_line); if (write(sockfd, send_line, nbytes) ! nbytes) error(1, errno, write error); if (read(sockfd, recv_line, MAXLINE) 0) error(1, errno, server terminated prematurely); fputs(recv_line, stdout); } exit(0); }下面我带大家理解一下这个客户端程序。1114 行创建了一个本地套接字和前面服务器端程序一样用的也是字节流类型 SOCK_STREAM。1618 行初始化目标服务器端的地址。我们知道在 TCP 编程中使用的是服务器的 IP 地址和端口作为目标在本地套接字中则使用文件路径作为目标标识sun_path 这个字段标识的是目标文件路径所以这里需要对 sun_path 进行初始化。20 行和 TCP 客户端一样发起对目标套接字的 connect 调用不过由于是本地套接字并不会有三次握手。2838 行从标准输入中读取字符串向服务器端发送之后将服务器端传输过来的字符打印到标准输出上。总体上我们可以看到本地字节流套接字和 TCP 服务器端、客户端编程最大的差异就是套接字类型的不同。本地字节流套接字识别服务器不再通过 IP 地址和端口而是通过本地文件。接下来我们就运行这个程序来加深对此的理解。只启动客户端第一个场景中我们只启动客户端程序$ ./unixstreamclient /tmp/unixstream.sock connect failed: No such file or directory (2)我们看到由于没有启动服务器端没有一个本地套接字在 /tmp/unixstream.sock 这个文件上监听客户端直接报错提示我们没有文件存在。服务器端监听在无权限的文件路径上还记得我们在前面卖的关子吗在 Linux 下执行任何应用程序都有应用属主的概念。在这里我们让服务器端程序的应用属主没有 /var/lib/ 目录的权限然后试着启动一下这个服务器程序 $ ./unixstreamserver /var/lib/unixstream.sock bind failed: Permission denied (13)这个结果告诉我们启动服务器端程序的用户必须对本地监听路径有权限。这个结果和你期望的一致吗试一下 root 用户启动该程序sudo ./unixstreamserver /var/lib/unixstream.sock (阻塞运行中)我们看到服务器端程序正常运行了。打开另外一个 shell我们看到 /var/lib 下创建了一个本地文件大小为 0而且文件的最后结尾有一个号。其实这就是 bind 的时候自动创建出来的文件。$ ls -al /var/lib/unixstream.sock rwxr-xr-x 1 root root 0 Jul 15 12:41 /var/lib/unixstream.sock如果我们使用 netstat 命令查看 UNIX 域套接字就会发现 unixstreamserver 这个进程监听在 /var/lib/unixstream.sock 这个文件路径上。服务器 - 客户端应答现在我们让服务器和客户端都正常启动并且客户端依次发送字符$./unixstreamserver /tmp/unixstream.sock Receive: g1 Receive: g2 Receive: g3 client quit$./unixstreamclient /tmp/unixstream.sock g1 Hi, g1 g2 Hi, g2 g3 Hi, g3 ^C我们可以看到服务器端陆续收到客户端发送的字节同时客户端也收到了服务器端的应答最后当我们使用 CtrlC让客户端程序退出时服务器端也正常退出。本地数据报套接字我们再来看下在本地套接字上使用数据报的服务器端例子#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixdataserver local_path); } int socket_fd; socket_fd socket(AF_LOCAL, SOCK_DGRAM, 0); if (socket_fd 0) { error(1, errno, socket create failed); } struct sockaddr_un servaddr; char *local_path argv[1]; unlink(local_path); bzero(servaddr, sizeof(servaddr)); servaddr.sun_family AF_LOCAL; strcpy(servaddr.sun_path, local_path); if (bind(socket_fd, (struct sockaddr *) servaddr, sizeof(servaddr)) 0) { error(1, errno, bind failed); } char buf[BUFFER_SIZE]; struct sockaddr_un client_addr; socklen_t client_len sizeof(client_addr); while (1) { bzero(buf, sizeof(buf)); if (recvfrom(socket_fd, buf, BUFFER_SIZE, 0, (struct sockadd *) client_addr, client_len) 0) { printf(client quit); break; } printf(Receive: %s \n, buf); char send_line[MAXLINE]; bzero(send_line, MAXLINE); sprintf(send_line, Hi, %s, buf); size_t nbytes strlen(send_line); printf(now sending: %s \n, send_line); if (sendto(socket_fd, send_line, nbytes, 0, (struct sockadd *) client_addr, client_len) ! nbytes) error(1, errno, sendto error); } close(socket_fd); exit(0); }本地数据报套接字和前面的字节流本地套接字有以下几点不同第 9 行创建的本地套接字这里创建的套接字类型注意是 AF_LOCAL协议类型为 SOCK_DGRAM。2123 行 bind 到本地地址之后没有再调用 listen 和 accept回忆一下这其实和 UDP 的性质一样。2845 行使用 recvfrom 和 sendto 来进行数据报的收发不再是 read 和 send这其实也和 UDP 网络程序一致。然后我们再看一下客户端的例子#include lib/common.h int main(int argc, char **argv) { if (argc ! 2) { error(1, 0, usage: unixdataclient local_path); } int sockfd; struct sockaddr_un client_addr, server_addr; sockfd socket(AF_LOCAL, SOCK_DGRAM, 0); if (sockfd 0) { error(1, errno, create socket failed); } bzero(client_addr, sizeof(client_addr)); /* bind an address for us */ client_addr.sun_family AF_LOCAL; strcpy(client_addr.sun_path, tmpnam(NULL)); if (bind(sockfd, (struct sockaddr *) client_addr, sizeof(client_addr)) 0) { error(1, errno, bind failed); } bzero(server_addr, sizeof(server_addr)); server_addr.sun_family AF_LOCAL; strcpy(server_addr.sun_path, argv[1]); char send_line[MAXLINE]; bzero(send_line, MAXLINE); char recv_line[MAXLINE]; while (fgets(send_line, MAXLINE, stdin) ! NULL) { int i strlen(send_line); if (send_line[i - 1] \n) { send_line[i - 1] 0; } size_t nbytes strlen(send_line); printf(now sending %s \n, send_line); if (sendto(sockfd, send_line, nbytes, 0, (struct sockaddr *) server_addr, sizeof(server_addr)) ! nbytes) error(1, errno, sendto error); int n recvfrom(sockfd, recv_line, MAXLINE, 0, NULL, NULL); recv_line[n] 0; fputs(recv_line, stdout); fputs(\n, stdout); } exit(0); }这个程序和 UDP 网络编程的例子基本是一致的我们可以把它当作是用本地文件替换了 IP 地址和端口的 UDP 程序不过这里还是有一个非常大的不同的。这个不同点就在 1622 行。你可以看到 1622 行将本地套接字 bind 到本地一个路径上然而 UDP 客户端程序是不需要这么做的。本地数据报套接字这么做的原因是它需要指定一个本地路径以便在服务器端回包时可以正确地找到地址而在 UDP 客户端程序里数据是可以通过 UDP 包的本地地址和端口来匹配的。下面这段代码就展示了服务器端和客户端通过数据报应答的场景./unixdataserver /tmp/unixdata.sock Receive: g1 now sending: Hi, g1 Receive: g2 now sending: Hi, g2 Receive: g3 now sending: Hi, g3$ ./unixdataclient /tmp/unixdata.sock g1 now sending g1 Hi, g1 g2 now sending g2 Hi, g2 g3 now sending g3 Hi, g3 ^C我们可以看到服务器端陆续收到客户端发送的数据报同时客户端也收到了服务器端的应答。总结我在开头已经说过本地套接字作为常用的进程间通信技术被用于各种适用于在同一台主机上进程间通信的场景。关于本地套接字我们需要牢记以下两点本地套接字的编程接口和 IPv4、IPv6 套接字编程接口是一致的可以支持字节流和数据报两种协议。本地套接字的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报套接字实现。