APUE 高级进程间通信

《UNIX环境高级编程》第17章 高级进程间通信 笔记

UNIX 域套接字

UNIX域套接字用于在同一台计算机上运行的进程之间的通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据,它们并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不要产生顺序号,无需发送确认报文。

UNIX域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失报文也不会传递出错。UNIX域套接字就像是套接字和管道的混合。

可以使用它们面向网络的域套接字接口或者使用socketpair函数来创建一对无命名的、相互连接的UNIX域套接字。

1
2
3
4
5
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sockfd[2]);

// Returns: 0 if OK, −1 on error

虽然接口足够通用,允许socketpair用于其他域,但一般来说操作系统仅对UNIX域提供支持

对相互连接的UNIX域套接字可以起到全双工管道的作用:两端对读和写开放。我们将其称为fd管道( fd-pipe),以便与普通的半双工管道区分开来。

曾经提到XSI消息队列的使用存在一个问题,即不能将它们和po11或者se1ect起使用,这是因为它们不能关联到文件描述符。然而,套接字是和文件描述符相关联的,消息到达时,可以用套接字来通知。对每个消息队列使用一个线程。每个线程都会在mmsgrcv调用中阻塞。当消息到达时,线程会把它写入一个UNIX域套接字的一端。当poll指示套接字可以读取数据时,应用程序会使用这个套接字的另外一端来接收这个消息。

命名UNIX域套接字

虽然socketpair函数能创建一对相互连接的套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。恰如因特网域套接字一样,可以命名UNIX域套接字,并可将其用于告示服务。但是要注意,UNX域套接字使用的地址格式不同于因特网域套接字,套接字地址格式会随实现而变。

UNIX域套接字的地址由 sockaddr_un结构表示。在 Linux3.20和 Solaris10中, sockaddr_un结构在头文件<sys/un.h>中的定义

sockaddr_un结构的sun_path成员包含一个路径名。当我们将一个地址绑定到一个UNIX域套接字时,系统会用该路径名创建一个S_IFSOCK类型的文件该文件仅用于向客户进程告示套接字名字。该文件无法打开,也不能由应用程序用于通信。

如果我们试图绑定同一地址时,该文件已经存在,那么bind请求会失败。当关闭套接字时,并不自动删除该文件,所以必须确保在应用程序退出前,对该文件执行解除链接操作。

唯一连接

服务器进程可以使用标准bind、listen和 accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用 connect与服务器进程联系。在服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。

下图展示了客户进程和服务器进程存在连接之前二者的情形。服务器端把它的套接字绑定到sockaddr_un的地址并监听新的连接请求。

现在,开发3个函数,使用这些函数可以在运行于同一台计算机上的两个无关进程之间创建唯一连接。

1
2
3
4
5
6
7
8
9
10
#include "apue.h" 

int serv_listen(const char *name);
// Returns: file descriptor to listen on if OK, negative value on error

int serv_accept(int listenfd, uid_t *uidptr);
// Returns: new file descriptor if OK, negative value on error

int cli_conn(const char *name);
// Returns: file descriptor if OK, negative value on error

服务器进程可以调用serv_listen函数声明它要在一个众所周知的名字(文件系统中的某个路径名)上监听客户进程的连接请求。当客户进程想要连接至服务器进程时,它们将使用该名字。serv_listen函数的返回值是用于接收客户进程连接请求的服务器UNIX域套接字

服务器进程可以使用serv_accept函数等待客户进程连接请求的到达。当个请求到达时,系统自动创建一个新的UNIX域套接字,并将它与客户端套接字连接,最后将这个新套接字返回给服务器。此外,客户进程的有效用户ID存放在 uidptr指向的存储区中客户

进程调用cli_conn函数连接至服务器进程。客户进程指定的name参数必须与服务器进程调用serv_listen函数时所用的名字相同

这三个函数的具体实现可以查看书本

传送文件描述符

在两个进程之间传送打开文件描述符的技术是非常有用的。因此可以对客户进程服务器进程应用进行不同的设计。它使一个进程(通常是服务器进程)能够处理打开一个文件所要做的一切操作(包括将网络名翻译为网络地址、拨号调制解调器、协商文件锁等)以及向调用进程送回个描述符,该描述符可被用于以后的所有I/O函数。涉及打开文件或设备的所有细节对客户进程而言都是透明的。

一般情况下,两个进程,它们打开了同一文件。虽然它们共享同一个v节点,但每个进程都有它自己的文件表项。当一个进程向另一个进程传送一个打开文件描述符时,我们想让发送进程和接收进程共享同文件表项。

在技术上,我们是将指向一个打开文件表项的指针从一个进程发送到另外一个进程。该指针被分配存放在接收进程的第一个可用描述符项中。(注意,不要造成错觉,以为发送进程和接收进程中的描述符编号是相同的,它们通常是不同的。)两个进程共享同一个打开文件表,这与foxk之后的父进程和子进程共享打开文件表的情况完全相同。

当发送进程将描述符传送给接收进程后,通常会关闭该描述符。发送进程关闭该描述符并不会真的关闭该文件或设备,其原因是该描述符仍被视为由接收进程打开(即使接收进程尚未接收到该描述符)

下面定义用以发送和接收文件描述符的3个函数。

1
2
3
4
5
6
7
8
#include "apue.h" 

int send_fd(int fd, int fd_to_send);
int send_err(int fd, int status, const char *errmsg);
// Both return: 0 if OK, −1 on error

int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t));
// Returns: file descriptor if OK, negative value on error

具体的实现原书代码