scoket 的方式进程间通信就是我们常用的网络通信和本地 Unix domain socket 通信,支持丰富的连接方式、通信协议等,这里对网络通信不深入描述,只介绍 Unix domain socket 概念和用法。

概念

Socket,又称套接字,是Linux跨进程通信(IPC,Inter Process Communication,详情参考:Linux进程间通信方式总结)方式的一种。相比于其他IPC方式,Socket更牛的地方在于,它不仅仅可以做到同一台主机内跨进程通信,它还可以做到不同主机间的跨进程通信。根据通信域的不同可以划分成2种:Unix domain socket 和 Internet domain socket。

  • Internet domain socket用于实现不同主机上的进程间通信,大部分情况下我们所说的socket都是指internet domain socket。”IP+端口+协议” 的组合就可以唯一标识网络中一台主机上的一个进程
  • Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。

相关函数

1
2
3
4
5
#include <sys/socket.h>
#include <sys/un.h>

unix_socket = socket(AF_UNIX, type, 0);
error = socketpair(AF_UNIX, type, 0, int *sv);

使用 UNIX domain socket 的过程和网络 socket 十分相似,也要先调用 socket() 创建一个 socket 文件描述符。

family 指定为 AF_UNIX,使用 AF_UNIX 会在系统上创建一个 socket 文件,不同进程通过读写这个文件来实现通信。

type 可以选择 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM 意味着会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无连接的通信。

protocol 参数指定为 0 即可。

1
2
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
;

UNIX domain socket 与网络 socket 编程最明显的不同在于地址格式不同,用结构体 sockaddr_un 表示,网络编程的 socket 地址是 IP 地址加端口号,而 UNIX domain socket 的地址是一个 socket 类型的文件在文件系统中的路径,这个 socket 文件由 bind() 调用创建,如果调用 bind() 时该文件已存在,则 bind() 错误返回。因此,一般在调用 bind() 前会检查 socket 文件是否存在,如果存在就删除掉。

1
int listen(int sockfd, int backlog);

在 bind 之后要 listen,表示通过 bind 的地址(也就是 socket 文件)提供服务。

1
2
int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen)
;

接下来必须用 accept() 函数初始化连接。accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。

示例代码

server 端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_NAME "/tmp/DemoSocket" // 定义 socket 文件路径

int main(void)
{

struct sockaddr_un name;

// 创建 socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

// 删除 socket 文件,以防止程序异常退出导致文件未删除
unlink(SOCKET_NAME);

// 设置 socket 地址
memset(&name, 0, sizeof(struct sockaddr_un));
name.sun_family = AF_UNIX;
strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);

// 绑定 socket
int ret = bind(sockfd, (const struct sockaddr *) &name, sizeof(struct sockaddr_un));
if (ret == -1) {
perror("bind");
exit(EXIT_FAILURE);
}

printf("server waiting for connection...\n");

// 监听 socket
ret = listen(sockfd, 20);
if (ret == -1) {
perror("listen");
exit(EXIT_FAILURE);
}

// 接收客户端连接
int connection = accept(sockfd, NULL, NULL);
if (connection == -1) {
perror("accept");
exit(EXIT_FAILURE);
}

// 接收客户端消息并打印
char buffer[100];
read(connection, buffer, 100);
printf("Received: %s\n", buffer);

// 关闭 socket
close(sockfd);

return 0;
}

SOCKET_NAME 是 Unix Domain Socket 的文件路径。它可以是任意有效的文件路径,但是需要注意以下几点:

  • 路径应该是可访问的,以便客户端和服务端都能找到它。
  • 如果路径所在的目录不存在,会导致创建失败。
  • 如果路径已经被其他进程使用,可能会导致冲突。
  • 在程序结束时,通常需要删除这个文件,以免占用系统资源。
  • 通常,Unix Domain Socket 文件路径会放在 /tmp 或 /var/run 目录下,以便于管理

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_NAME "/tmp/DemoSocket" // 定义 socket 文件路径

int main(void)
{

struct sockaddr_un addr;
int sockfd;

// 创建 socket
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

// 设置 socket 地址
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);

// 连接到服务端
int ret = connect(sockfd, (const struct sockaddr *) &addr, sizeof(struct sockaddr_un));
if (ret == -1) {
perror("connect");
exit(EXIT_FAILURE);
}

// 发送消息到服务端
write(sockfd, "Hello from client", strlen("Hello from client") + 1);

// 关闭 socket
close(sockfd);

return 0;
}

运行结果:

image.png

参考文档:

☞ 参与评论