IPC实现主要有两种方式
共享内存
消息传递
概念介绍
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 系统也是支持它的。
使用说明
使用 UNIX domain socket 的过程和网络 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 编程最明显的不同,还有一点在于地址格式不同。unix domain socket用结构体sockaddr_un表示,而网络编程使用sockaddr_in结构体表示。网络编程的socket地址是IP 地址加端口号,而 UNIX domain socket的地址是一个 socket 类型的文件在文件系统中的路径,这个 socket 文件由 bind() 调用创建,如果调用 bind()时该文件已存在,则 bind() 错误返回。因此,一般在调用 bind() 前会检查 socket 文件是否存在,如果存在就删除掉。
网络 socket 编程类似,在 bind 之后要 listen,表示通过 bind 的地址(也就是 socket 文件)提供服务。
接下来必须用 accept() 函数初始化连接。accept() 为每个连接创立新的套接字并从监听队列中移除这个连接
详细可参考如下博文:
unix domain socket介绍
sockaddr_un和sockaddr_in
安全加固
unix domain socket进程凭据
进程凭据是指unix domain socket(AF_UNIX)发送方的pid,uid,gid信息。
如果在服务端做安全加固,那么socket server作为接收方,socket client作为发送方。
只能是AF_UNIX,不能是AF_INET的原因很简单,AF_INET可能都不在同一台机器上,pid,uid,gid没有意义。
https://blog.csdn.net/ayu_ag/article/details/52413874
我们在使用unix domain socket的时候,可以通过在server添加白名单,对比client的uid和gid是否在白名单中,从而判断是否信任client并做相应处理
示例代码
服务端
#define _GNU_SOURCE // 如果要获取进程凭据,则需要打开该宏定义才可以,这个宏定义一定要放在编译最开始
#include <sys/socket.h> // 定义了cred结构体,用于存放进程凭据(pid uid和gid)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h> // 定义了sockaddr_un结构体
char *server_socket_path = "server.socket"; // 在server运行目录生成socket文件用于接收client发来的消息
int main()
{
struct sockaddr_un server_addr;
int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listenfd < 0) {
printf("create socket failed\n");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
strcpy(server_addr.sun_path, server_socket_path); // 初始化socket文件路径
unlink(server_socket_path); // 一定要删除掉旧文件重新创建,否则会报错
server_addr.sun_family=AF_UNIX; // 初始化socket family为AF_UNIX,使用本地IPC模式
if (bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
close(listenfd);
return -1;
}
if (listen(listenfd, 128) < 0) { // 最大支持连接128个client
close(listenfd);
return -1;
}
int connfd;
struct sockaddr_un client_addr;
int msglen;
while (1) { // 循环监听,等待client连接
printf("[*] waiting for client connecting\n");
if ((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &msglen)) < 0) {
continue;
}
struct ucred cred; // 保存client的pid uid和gid
socklen_t len = sizeof(struct ucred);
getsockopt(connfd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
printf("[*] Credentials from SO_PEERCRED: pid=%d, uid=%d, gid=%d\n", cred.pid , cred.uid, cred.gid);
printf("[*] close connect\n");
close(connfd);
}
close(listenfd);
return 0;
}
客户端
#define _GNU_SOURCE // 如果要获取进程凭据,则需要打开该宏定义才可以,这个宏定义一定要放在编译最开始
#include <sys/socket.h> // 定义了cred结构体,用于存放进程凭据(pid uid和gid)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h> // 定义了sockaddr_un结构体
char *server_socket_path="server.socket"; // 这里跟server保持一致
int main()
{
struct sockaddr_un server_addr;
int clientfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (clientfd < 0) {
printf("create socket faield!\n");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family=AF_UNIX;
strcpy(server_addr.sun_path, server_socket_path);
if (connect(clientfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("connect to server failed!\n");
close(clientfd);
return -1;
}
close(clientfd);
return 0;
}