Unix 域套接字(本地套接字)
Unix 域套接字(Unix Domain Sockets),也称为本地套接字(Local Sockets),是一种用于同一主机上进程间通信(IPC)的机制。Unix 域套接字提供了一种高效的进程间通信方式,它利用文件系统作为传输媒介,而不是网络栈,因此可以避免网络层的开销。下面详细介绍 Unix 域套接字的概念、用途、API 以及示例代码。
概述
Unix 域套接字是一种只在 Unix 和类 Unix 操作系统(包括 Linux)中可用的套接字类型。它允许在同一主机上的进程之间通过文件系统进行通信,而不必通过网络层。Unix 域套接字可以分为两种类型:
- 流式套接字 (SOCK_STREAM):提供面向连接的服务,类似于 TCP。
- 数据报套接字 (SOCK_DGRAM):提供无连接的服务,类似于 UDP。
特点
- 高效性:由于通信发生在同一主机上,不需要经过网络层,因此效率更高。
- 安全性:通信数据不离开本机,减少了外部攻击的风险。
- 简单性:API 与传统的网络套接字相似,但无需处理 IP 地址和端口。
API
Unix 域套接字主要使用的函数包括:
-
socket():
int socket(int domain, int type, int protocol)
: 创建一个套接字。- 参数
domain
指定域,对于 Unix 域套接字为AF_UNIX
,type
指定套接字类型,如SOCK_STREAM
或SOCK_DGRAM
,protocol
通常设为0。
-
bind():
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
: 绑定套接字到一个地址。- 参数
sockfd
是套接字描述符,addr
是地址结构的指针,addrlen
是地址结构的大小。
-
listen():
int listen(int sockfd, int backlog)
: 将套接字标记为监听状态。- 参数
sockfd
是套接字描述符,backlog
是连接队列的最大长度。
-
accept():
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
: 接受传入的连接。- 参数
sockfd
是监听套接字描述符,addr
和addrlen
用于返回客户端的地址信息。
-
connect():
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
: 连接到一个服务器。- 参数
sockfd
是套接字描述符,addr
是服务器地址结构的指针,addrlen
是地址结构的大小。
-
send() 和 recv():
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
: 发送数据。ssize_t recv(int sockfd, void *buf, size_t len, int flags)
: 接收数据。- 参数
sockfd
是套接字描述符,buf
是缓冲区指针,len
是数据长度,flags
用于指定发送或接收的选项。
-
close():
int close(int sockfd)
: 关闭套接字。- 参数
sockfd
是套接字描述符。
示例代码
下面是一个简单的 Unix 域套接字示例,演示了如何在服务器和客户端之间进行通信。
服务器端 (server.c)
1#include <sys/socket.h>
2#include <sys/un.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7
8#define SOCK_PATH "/tmp/unix_socket_example.sock"
9
10int main() {
11 int server_sock, client_sock;
12 struct sockaddr_un addr;
13 char buf[1024];
14
15 // 创建 Unix 域套接字
16 server_sock = socket(AF_UNIX, SOCK_STREAM, 0);
17 if (server_sock == -1) {
18 perror("socket");
19 exit(EXIT_FAILURE);
20 }
21
22 // 清空地址结构
23 memset(&addr, 0, sizeof(addr));
24 addr.sun_family = AF_UNIX;
25 strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
26
27 // 绑定套接字
28 if (bind(server_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
29 perror("bind");
30 exit(EXIT_FAILURE);
31 }
32
33 // 监听连接
34 if (listen(server_sock, 5) == -1) {
35 perror("listen");
36 exit(EXIT_FAILURE);
37 }
38
39 // 接受连接
40 socklen_t len = sizeof(addr);
41 client_sock = accept(server_sock, (struct sockaddr *)&addr, &len);
42 if (client_sock == -1) {
43 perror("accept");
44 exit(EXIT_FAILURE);
45 }
46
47 // 接收数据
48 ssize_t bytes_received = recv(client_sock, buf, sizeof(buf), 0);
49 if (bytes_received == -1) {
50 perror("recv");
51 exit(EXIT_FAILURE);
52 }
53 buf[bytes_received] = '\0';
54
55 // 输出收到的数据
56 printf("Received: %s\n", buf);
57
58 // 关闭连接
59 close(client_sock);
60 close(server_sock);
61
62 // 删除套接字文件
63 unlink(SOCK_PATH);
64
65 return 0;
66}
客户端 (client.c)
1#include <sys/socket.h>
2#include <sys/un.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7
8#define SOCK_PATH "/tmp/unix_socket_example.sock"
9
10int main() {
11 int sock;
12 struct sockaddr_un addr;
13 char buf[] = "Hello, Unix Domain Socket!";
14
15 // 创建 Unix 域套接字
16 sock = socket(AF_UNIX, SOCK_STREAM, 0);
17 if (sock == -1) {
18 perror("socket");
19 exit(EXIT_FAILURE);
20 }
21
22 // 清空地址结构
23 memset(&addr, 0, sizeof(addr));
24 addr.sun_family = AF_UNIX;
25 strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
26
27 // 连接到服务器
28 if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
29 perror("connect");
30 exit(EXIT_FAILURE);
31 }
32
33 // 发送数据
34 if (send(sock, buf, strlen(buf), 0) == -1) {
35 perror("send");
36 exit(EXIT_FAILURE);
37 }
38
39 // 关闭连接
40 close(sock);
41
42 return 0;
43}
编译和运行
为了编译上述代码,你可以使用以下命令:
1gcc -o server server.c
2gcc -o client client.c
然后运行这两个程序:
1./server &
2./client
注意事项
- 在使用 Unix 域套接字之前,确保检查所有 API 调用的返回值,以确保操作成功。
- 在关闭套接字之后,记得删除套接字文件,以避免占用不必要的系统资源。
- 如果套接字文件不再需要,应使用
unlink()
删除它,以避免占用不必要的系统资源。 - 在实际应用中,可能需要处理更复杂的错误情况,比如处理连接失败的情况。
Unix 域套接字提供了一种简单而强大的机制来进行进程间的通信,非常适合那些需要快速访问共享数据的应用场景。理解和熟练掌握这些 API 对于开发可靠的多进程应用程序非常重要。