UNIX域套接字用于在同一台机器上运行的进程之间的通讯。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;它们并不执行协议处理,不须要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。html
UNIX域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。为了建立一对非命名的、相互链接的UNIX域套接字,用户可使用它们面向网络的域套接字接口,也可以使用socketpair函数。node
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sockfd[2]); 返回值:若成功则返回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 fd[0] and fd[1]. */ int s_pipe(int fd[2]) { return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)); }
某些基于BSD的系统使用UNIX域套接字实现管道。但当调用pipe时,第一描述符的写端和第二描述符的读端都被关闭。为了获得全双工管道,咱们必须直接调用socketpair。dom
一、命名UNIX域套接字socket
虽然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>中。ui
struct sockaddr_un { sa_family sun_family; /* AF_UNIX */ char sun_path[108]; /* pathname */ };
sockaddr_un结构的sun_path成员包含一路径名。当咱们将以地址绑定至UNIX域套接字时,系统用该路径名建立一类型为S_IFSOCK的文件。
该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通讯。
若是当咱们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,因此咱们必须确保在应用程序终止前,对该文件执行解除连接操做。
实例
程序清单17-7 将一个地址绑定一UNIX域套接字
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> int main(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 bound\n"); 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开始,此表达式求得成员起始地址的整型值。
二、惟一链接
服务器进程可使用标准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. */ int serv_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 we're 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 /* client's name can't be older than this (sec) */ /* * Wait for a client connection to arrive, and accept it. * We also obtain the client's 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 client's uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */ if(stat(un.sun_path, &statbuf) < 0) { rval = -2; goto errout; } #ifdef S_ISSOCK /* not defined fro SVR4 */ if(S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout; } #endif if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || (statbuf.st_mode & S_IRWXU) != S_IRWXU) { rval = -4; /* is not rwx------ */ goto errout; } staletime = time(NULL) - STALE; if(statbuf.st_atime < staletime || statbuf.st_ctime < staletime || statbuf.st_mtime < staletime) { rval = -5; /* i-node is too old */ goto errout; } if(uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ unlink(un.sun_path); /* we're done with pathname now */ return(clifd); errout: err = errno; close(clifd); errno = err; return(rval); }
服务器进程在调用serv_accept中阻塞以等待一客户进程调用cli_conn。从accept返回时,返回值是链接到客户进程的崭新的描述符。另外,accept函数也经由其第二个参数(指向sockaddr_un结构的指针)返回客户进程赋予其套接字的路径名(包含客户进程ID的名字)。接着,程序在此路径名结尾处填补null字符,而后调用stat函数。这使咱们验证该路径名确实是一个套接字,其权限容许用户-读、用户-写及用户-执行。咱们也验证与套接字相关联的3个时间不比当前时间早30秒。(time函数参考http://www.cnblogs.com/nufangrensheng/p/3507715.html)。
如若经过了全部这些检验,则可认为客户进程的身份(其有效用户ID)是该套接字的全部者。
客户进程调用cli_conn函数对联向服务器进程的链接进行初始化。
程序清单17-10 用于UNIX域套接字的cli_conn函数
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 fro pid = 14 chars */ #define CLI_PERM S_IRWXU /* rwx for user only */ /* * Create a client endpoint and connect to a server. * Returns fd if all ok, <0 on error. */ int cli_conn(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if((fd = socket(AF_UNIX, SOCK_STREM, 0)) < 0) return(-1); /* fill socket address structure with our address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); unlink(un.sun_path); /* in case it already exits */ if(bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if(chmod(un.sun_path, CLI_PERM) < 0) { rval = -3; goto errout; } /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if(connect(fd, (struct sockaddr *)&un, len) < 0) { rval = -4; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); }
咱们调用socket函数建立UNIX域套接字的客户端进程,而后用客户端进程专有的名字填入sockaddr_un结构。
咱们不让系统为咱们选择一个默认的地址,缘由是这样处理后,服务器进程不能区分各个客户进程。因而,咱们绑定咱们本身的地址,在开发使用套接字的客户端程序时一般并不采用这一步骤。
咱们绑定的路径名的最后5个字符来自客户进程ID。咱们调用unlink,以防该路径名已经存在,而后,调用bind将名字赋予客户进程套接字。这在文件系统中建立了一个套接字文件,所用的名字与被绑定的路径名同样。接着,调用chmod关闭除用户-读、用户-写以及用户-执行之外的其余权限。在serv_accept中,服务器进程检验这些权限以及套接字用户ID以验证客户进程的身份。
而后,咱们必须填充另外一个sockaddr_un结构,此次用的是服务器进程众所周知的路径名。最后,调用connect函数初始化与服务器进程的链接。
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅做我的学习记录所用。关于本书可参考:http://www.apuebook.com/。