Unix 域套接字(本地套接字)

发布于:2025-03-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

Unix 域套接字(Unix Domain Sockets),也称为本地套接字(Local Sockets),是一种用于同一主机上进程间通信(IPC)的机制。Unix 域套接字提供了一种高效的进程间通信方式,它利用文件系统作为传输媒介,而不是网络栈,因此可以避免网络层的开销。下面详细介绍 Unix 域套接字的概念、用途、API 以及示例代码。

概述

Unix 域套接字是一种只在 Unix 和类 Unix 操作系统(包括 Linux)中可用的套接字类型。它允许在同一主机上的进程之间通过文件系统进行通信,而不必通过网络层。Unix 域套接字可以分为两种类型:

  1. 流式套接字 (SOCK_STREAM):提供面向连接的服务,类似于 TCP。
  2. 数据报套接字 (SOCK_DGRAM):提供无连接的服务,类似于 UDP。

特点

  • 高效性:由于通信发生在同一主机上,不需要经过网络层,因此效率更高。
  • 安全性:通信数据不离开本机,减少了外部攻击的风险。
  • 简单性:API 与传统的网络套接字相似,但无需处理 IP 地址和端口。

API

Unix 域套接字主要使用的函数包括:

  • socket():

    • int socket(int domain, int type, int protocol): 创建一个套接字。
    • 参数domain指定域,对于 Unix 域套接字为AF_UNIXtype指定套接字类型,如SOCK_STREAMSOCK_DGRAMprotocol通常设为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是监听套接字描述符,addraddrlen用于返回客户端的地址信息。
  • 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 对于开发可靠的多进程应用程序非常重要。