高级进程间通信之UNIX域套接字
高级进程间通信之UNIX域套接字UNIX域套接字用于在同一台机器上运行的进程之间的通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。UNIX域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的、相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接口,也可使用socketpair函数。#include <sys/socket.h>int socketpair(int domain, int type, int protocol, int sockfd2);返回值:若成功则返回0,出错则返回-1虽然该接口具有足够的一般性,socketpair可用于任意域,但操作系统通常仅对UNIX域提供支持。实例:使用UNIX域套接字的s_pipe函数程序清单17-6 s_pipe函数的套接字版本(创建一对相连接的UNIX域流套接字)#include "apue.h"#include <sys/socket.h>/* Return a full-duplex "stream" pipe (a UNIX domain socket)* with the two file descriptors returned in fd0 and fd1.*/ints_pipe(int fd2) return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd);某些基于BSD的系统使用UNIX域套接字实现管道。但当调用pipe时,第一描述符的写端和第二描述符的读端都被关闭。为了得到全双工管道,我们必须直接调用socketpair。1、命名UNIX域套接字虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。在http:/www.cnblogs.com/nufangrensheng/p/3565402.html,我们学习了如何将一个地址绑定一因特网域套接字。恰如因特网域套接字一样,我们也可以命名UNIX域套接字,并可将其用于告示服务。但是要注意的是,UNIX域套接字使用的地址格式不同于因特网域套接字。套接字地址格式可能随实现而变。UNIX域套接字的地址由sockaddr_un结构表示。在Linux 2.4.22和Solaris 9中,sockaddr_un结构按下列形式定义在头文件<sys/un.h>中。struct sockaddr_un sa_family sun_family; /* AF_UNIX */ char sun_path108; /* pathname */;sockaddr_un结构的sun_path成员包含一路径名。当我们将以地址绑定至UNIX域套接字时,系统用该路径名创建一类型为S_IFSOCK的文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。实例程序清单17-7 将一个地址绑定一UNIX域套接字#include "apue.h"#include <sys/socket.h>#include <sys/un.h>intmain(void) int fd, size; struct sockaddr_un un; un.sun_family = AF_UNIX; strcpy(un.sun_path, "foo.socket"); if(fd = socket(AF_UNIX, SOCK_STREAM, 0) < 0) err_sys("socket failed"); size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); if(bind(fd, (struct sockaddr *)&un, size) < 0) err_sys("bind failed"); printf("UNIX domain socket boundn"); exit(0);当运行此程序时,bind请求成功执行,但是如果第二次运行该程序,则出错返回,其原因是该文件已经存在。在删去该文件之前,程序清单17-7不会成功执行。确定绑定地址长度的方法是,先确定sun_path成员在sockaddr_un结构中的偏移量,然后将此与路径名长度(不包括终止null字符)相加。因为在sun_path之前的成员与实现相关,所以我们使用<stddef.h>头文件中的offsetof宏计算sun_path成员从结构开始处的偏移量。如果查看<stddef.h>,则可见到类似于下列形式的定义:#define offsetof(TYPE, MEMBER) (int)&(TYPE *)0)->MEMBER)假定该结构从地址0开始,此表达式求得成员起始地址的整型值。2、唯一连接服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一的UNIX域连接(unique UNIX domain connection)。客户进程使用connect与服务器进程联系;服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。这种风格的操作与我们在http:/www.cnblogs.com/nufangrensheng/p/3567376.html中的程序清单16-4和程序清单16-5中所示的对因特网域套接字的操作相同。程序清单17-8 UNIX域套接字的serv_listen函数#include "apue.h"#include <sys/socket.h>#include <sys/un.h>#include <errno.h>#define QLEN 10/* Create a server endpoint of a connection.* Return fd if all ok, <0 on error. */intserv_listen(const char *name) int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if(fd = socket(AF_UNIX, SOCK_STREAM, 0) < 0) return(-1); unlink(name); /* in case it already exists */ /* fill in socket address structure */ memset(&un, 0, sizeof(un); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); /* bind the name to the descriptor */ if(bind(fd, (struct sockaddr *)&un, len) < 0) rval = -2; goto errout; if(listen(fd, QLEN) < 0) /* tell kernel were a server */ rval = -3; goto errout; return(fd);errout: err = errno; close(fd); errno = err; return(rval);首先,我们调用socket创建一个UNIX域套接字。然后将欲赋予套接字的众所周知路径名填入sockaddr_un结构。该结构是调用bind的参数。注意,我们不需要设置某些平台提供的sun_len字段,操作系统用传送给bind函数的地址长度设置该字段。最后调用listen函数以通知内核进程该进程将作为服务器进程等待客户进程的连接请求。当收到一个客户进程的连接请求后,服务器进程调用serv_accept函数。程序清单17-9 UNIX域套接字的serv_accept函数#include "apue.h"#include <sys/socket.h>#include <sys/un.h>#include <time.h>#include <errno.h>#define STALE 30 /* clients name cant be older than this (sec) */* Wait for a client connection to arrive, and accept it.* We also obtain the clients usr ID from the pathname* that it must bind before calling us.* Returns new fd if all ok, <0 on error*/int serv_accept(int listenfd, uid_t *uidptr) int clifd, len, err, rval; time_t staletime; struct sockaddr_un un; struct stat statbuf; len = sizeof(un); if(clifd = accept(listenfd, (struct sockaddr *)&un, &len) < 0) return(-1); /* often errno=EINTR, if signal caught */ /* obtain the clients uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_pathlen = 0; /* null