Linux下IPC实现——Unix domain socket

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()函数的参数如下:

  1. family:指定为 AF_UNIX,使用 AF_UNIX 会在系统上创建一个 socket 文件,不同进程通过读写这个文件来实现通信。
  2. type:可以选择 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM意味着会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无连接的通信
  3. 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;	
}


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unix Domain socket的优势主要体现在以下几个方面: 1. 性能优势:与TCP套接字相比,Unix Domain socket在同一台主机上的进程间通信更快速和高效。因为Unix Domain socket不依赖于网络协议栈,数据不需要通过网络传输,而是直接在内核中进行进程间通信,减少了网络协议栈的开销和数据拷贝的次数,从而提高了性能。\[2\] 2. API相似性:Unix Domain socket的API与TCP套接字非常相似,因此对于已经使用TCP套接字编写的代码,只需将地址从本地更改为Unix Domain socket的地址,就可以继续工作。这意味着可以很容易地编写同时支持Unix Domain socket和TCP套接字的代码,实现在单个主机上的进程间通信和远程IPC。\[1\] 3. 稳定性和可靠性:Unix Domain socket在同一主机上的进程间通信,不受网络的影响,因此更加稳定可靠。同时,Unix Domain socket使用文件作为通信的方式,具有较好的稳定性和可靠性。\[3\] 综上所述,Unix Domain socket具有性能优势、API相似性以及稳定性和可靠性等优点,适用于同一主机上的进程间通信。 #### 引用[.reference_title] - *1* *2* *3* [Unix Domain Socket简介](https://blog.csdn.net/kworkers/article/details/130118981)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值