本地套接字—domain
socket API
原本是为网络通讯设计的,但后来在
socket
的框架上发展出一种
IPC
机制,就是
UNIX Domain Socket
。虽然网络
socket
也可用于同一台主机的进程间通讯(通过
loopback
地址
127.0.0.1
),但是
UNIX Domain Socket
用于
IPC
更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,
IPC
机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。
UNIX Domain Socket
也提供面向流和面向数据包两种API接口,类似于
TCP
和
UDP
,但是面向消息的
UNIX Domain Socket
也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket
是全双工的,
API
接口语义丰富,相比其它
IPC
机制有明显的优越性,目前已成为使用最广泛的
IPC
机制,比如
X Window
服务器和
GUI
程序之间就是通过
UNIXDomain Socket
通讯的。
使用
UNIX Domain Socket
的过程和网络
socket
十分相似,也要先调用
socket()
创建一个
socket
文件描述符,
address family
指定为
AF_UNIX
,
type
可以选择
SOCK_DGRAM
或
SOCK_STREAM
,
protocol
参数仍然指定为
0
即可。
UNIX Domain Socket
与网络
socket
编程最明显的不同在于地址格式不同,用结构体
sockaddr_un
表示,网络编程的
socket
地址是
IP
地址加端口号,而
UNIX Domain Socket
的地址是一个
socket
类型的**文件在文件系统中的路径,**这个
socket
文件由
bind()
调用创建,如果调用
bind()
时该文件已存在,则
bind()
错误返回。
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in {__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型__be16 sin_port; /* Port number */ 端口号struct in_addr sin_addr; /* Internet address */ IP地址};struct sockaddr_un {__kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)};
以下程序将
UNIX Domain socke
t绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);#define offsetof(type, member) ((int)&((type *)0)->MEMBER)
server
#include <stdlib.h>#include <stdio.h>#include <stddef.h>#include <sys/socket.h>#include <sys/un.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#define QLEN 10/** Create a server endpoint of a connection.* Returns 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);/* in case it already exists */unlink(name);/* 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);}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;}if (S_ISSOCK(statbuf.st_mode) == 0) {rval = -3; /* not a socket */goto errout;}if (uidptr != NULL)*uidptr = statbuf.st_uid; /* return uid of caller *//* we\'re done with pathname now */unlink(un.sun_path);return(clifd);errout:err = errno;close(clifd);errno = err;return(rval);}int main(void){int lfd, cfd, n, i;uid_t cuid;char buf[1024];lfd = serv_listen(\"foo.socket\");if (lfd < 0) {switch (lfd) {case -3:perror(\"listen\"); break;case -2:perror(\"bind\"); break;case -1:perror(\"socket\"); break;}exit(-1);}cfd = serv_accept(lfd, &cuid);if (cfd < 0) {switch (cfd) {case -3:perror(\"not a socket\"); break;case -2:perror(\"a bad filename\"); break;case -1:perror(\"accept\"); break;}exit(-1);}while (1) {r_again:n = read(cfd, buf, 1024);if (n == -1) {if (errno == EINTR)goto r_again;}else if (n == 0) {printf(\"the other side has been closed.\\n\");break;}for (i = 0; i < n; i++)buf[i] = toupper(buf[i]);write(cfd, buf, n);}close(cfd);close(lfd);return 0;}
client
#include <stdio.h>#include <stdlib.h>#include <stddef.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <sys/socket.h>#include <sys/un.h>#include <errno.h>#define CLI_PATH \"/var/tmp/\" /* +5 for pid = 14 chars *//** 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_STREAM, 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);/* in case it already exists */unlink(un.sun_path);if (bind(fd, (struct sockaddr *)&un, len) < 0) {rval = -2;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);}int main(void){int fd, n;char buf[1024];fd = cli_conn(\"foo.socket\");if (fd < 0) {switch (fd) {case -4:perror(\"connect\"); break;case -3:perror(\"listen\"); break;case -2:perror(\"bind\"); break;case -1:perror(\"socket\"); break;}exit(-1);}while (fgets(buf, sizeof(buf), stdin) != NULL) {write(fd, buf, strlen(buf));n = read(fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, n);}close(fd);return 0;}