Unix domain socket 简介

Unix domain socket 又叫 IPC(inter-process communication 进程间通讯) socket,用于实现同一主机上的进程间通讯。socket 本来是为网络通信设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通信(经过 loopback 地址 127.0.0.1),可是 UNIX domain socket 用于 IPC 更有效率:不须要通过网络协议栈,不须要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另外一个进程。这是由于,IPC 机制本质上是可靠的通信,而网络协议是为不可靠的通信设计的。
UNIX domain socket 是全双工的,API 接口语义丰富,相比其它 IPC 机制有明显的优越性,目前已成为使用最普遍的 IPC 机制,好比 X Window 服务器和 GUI 程序之间就是经过 UNIX domain socket 通信的。
Unix domain socket 是 POSIX 标准中的一个组件,因此不要被名字迷惑,linux 系统也是支持它的。html

下面经过一个简单的 demo 来理解相关概念。程序分为服务器端和客户端两部分,它们之间经过 unix domain socket 进行通讯。linux

服务器端程序

下面是一个很是简单的服务器端程序,它从客户端读字符,而后将每一个字符转换为大写并回送给客户端:编程

#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <ctype.h>   
 
#define MAXLINE 80  
 
char *socket_path = "server.socket"; int main(void) { struct sockaddr_un serun, cliun; socklen_t cliun_len; int listenfd, connfd, size; char buf[MAXLINE]; int i, n; if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } memset(&serun, 0, sizeof(serun)); serun.sun_family = AF_UNIX; strcpy(serun.sun_path, socket_path); size = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path); unlink(socket_path); if (bind(listenfd, (struct sockaddr *)&serun, size) < 0) { perror("bind error"); exit(1); } printf("UNIX domain socket bound\n"); if (listen(listenfd, 20) < 0) { perror("listen error"); exit(1); } printf("Accepting connections ...\n"); while(1) { cliun_len = sizeof(cliun); if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){ perror("accept error"); continue; } while(1) { n = read(connfd, buf, sizeof(buf)); if (n < 0) { perror("read error"); break; } else if(n == 0) { printf("EOF\n"); break; } printf("received: %s", buf); for(i = 0; i < n; i++) { buf[i] = toupper(buf[i]); } write(connfd, buf, n); } close(connfd); } close(listenfd); return 0; } 

简单介绍一下这段代码:服务器

int socket(int family, int type, int protocol);

使用 UNIX domain socket 的过程和网络 socket 十分类似,也要先调用 socket() 建立一个 socket 文件描述符.
family 指定为 AF_UNIX,使用 AF_UNIX 会在系统上建立一个 socket 文件,不一样进程经过读写这个文件来实现通讯。
type 能够选择 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM 意味着会提供按顺序的、可靠、双向、面向链接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无链接的通讯。
protocol 参数指定为 0 便可。
UNIX domain socket 与网络 socket 编程最明显的不一样在于地址格式不一样,用结构体 sockaddr_un 表示,网络编程的 socket 地址是 IP 地址加端口号,而 UNIX domain socket 的地址是一个 socket 类型的文件在文件系统中的路径,这个 socket 文件由 bind() 调用建立,若是调用 bind() 时该文件已存在,则 bind() 错误返回。所以,通常在调用 bind() 前会检查 socket 文件是否存在,若是存在就删除掉。
网络 socket 编程相似,在 bind 以后要 listen,表示经过 bind 的地址(也就是 socket 文件)提供服务。
接下来必须用 accept() 函数初始化链接。accept() 为每一个链接创立新的套接字并从监听队列中移除这个链接。网络

客户端程序

下面是客户端程序,它接受用户的输入,并把字符串发送给服务器,而后接收服务器返回的字符串并打印:框架

#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> #include <unistd.h>  
 
#define MAXLINE 80  
 
char *client_path = "client.socket"; char *server_path = "server.socket"; int main() { struct sockaddr_un cliun, serun; int len; char buf[100]; int sockfd, n; if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ perror("client socket error"); exit(1); } // 通常显式调用bind函数,以便服务器区分不一样客户端 
    memset(&cliun, 0, sizeof(cliun)); cliun.sun_family = AF_UNIX; strcpy(cliun.sun_path, client_path); len = offsetof(struct sockaddr_un, sun_path) + strlen(cliun.sun_path); unlink(cliun.sun_path); if (bind(sockfd, (struct sockaddr *)&cliun, len) < 0) { perror("bind error"); exit(1); } memset(&serun, 0, sizeof(serun)); serun.sun_family = AF_UNIX; strcpy(serun.sun_path, server_path); len = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path); if (connect(sockfd, (struct sockaddr *)&serun, len) < 0){ perror("connect error"); exit(1); } while(fgets(buf, MAXLINE, stdin) != NULL) { write(sockfd, buf, strlen(buf)); n = read(sockfd, buf, MAXLINE); if ( n < 0 ) { printf("the other side has been closed.\n"); }else { write(STDOUT_FILENO, buf, n); } } close(sockfd); return 0; } 

与网络 socket 编程不一样的是,UNIX domain socket 客户端通常要显式调用 bind 函数,而不依赖系统自动分配的地址。客户端 bind 一个本身指定的 socket 文件名的好处是,该文件名能够包含客户端的 pid 等信息以便服务器区分不一样的客户端。dom

运行上面的程序

分别把服务器端程序和客户端程序保存为 server.c 和 client.c 文件,并编译:socket

$ gcc server.c -o server $ gcc client.c -o client

先启动服务器端程序,而后启动客户端程序输入字符串并回车:ide

还不错,客户端获得了服务器端返回的大写字符串。接下来看看当前目录下的文件:函数

哈哈,多了两个 socket 文件。

总结

Unix domain socket 主要用于同一主机上的进程间通讯。与主机间的进程通讯不一样,它不是经过 "IP地址 + TCP或UDP端口号" 的方式进程通讯,而是使用 socket 类型的文件来完成通讯,所以在稳定性、可靠性以及效率方面的表现都很不错。

参考:
UNIX Domain Socket IPC
[linux] unix domain socket 例子

相关文章
相关标签/搜索