【网络编程】UNIX 域套接字(Unix Domain Sockets, UDS)

UNIX 域套接字

UNIX 域套接字 (UDS, Unix Domain Socket)是一种 本地进程间通信(IPC) 方式,适用于同一台机器上的进程之间的通信。相比 TCP/IP 套接字,UDS 效率更高、延迟更低,因为它省去了网络协议的开销。

🔹 UDS 特点

  1. 仅限本机通信(不能用于跨网络通信)。
  2. 速度快(不像 TCP/IP 需要数据封装和解析)。
  3. 支持
    • 流式套接字(SOCK_STREAM)(类似 TCP)。
    • 数据报套接字(SOCK_DGRAM)(类似 UDP)。
  4. 本地地址由文件路径标识 (如 /tmp/mysocket)。

🔹1. UNIX 域流式套接字(SOCK_STREAM)

适用于 前后台进程通信、RPC 调用、数据库访问 等场景。支持多个客户端连接,服务器能正确接收 & 发送数据。

服务器端
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/mysocket"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[256];

    // 1️⃣ 创建流式套接字
    server_fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("❌ socket 创建失败");
        exit(EXIT_FAILURE);
    }

    // 2️⃣ 绑定本地地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = PF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
    
    unlink(SOCKET_PATH); // 删除旧的套接字文件,防止 bind 失败
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("❌ 绑定失败");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3️⃣ 监听连接请求
    listen(server_fd, 5);
    printf("监听 UNIX 域套接字: %s\n", SOCKET_PATH);

    // 4️⃣ 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd < 0) {
        perror("❌ accept 失败");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5️⃣ 读取 & 发送数据
    read(client_fd, buffer, sizeof(buffer));
    printf("📩 收到消息: %s\n", buffer);
    write(client_fd, "✅ 服务器收到消息", 20);

    // 6️⃣ 关闭套接字
    close(client_fd);
    close(server_fd);
    unlink(SOCKET_PATH);
    return 0;
}
客户端
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/mysocket"

int main() {
    int client_fd;
    struct sockaddr_un server_addr;
    char buffer[256] = "你好,服务器!";

    // 1️⃣ 创建流式套接字
    client_fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (client_fd < 0) {
        perror("❌ socket 创建失败");
        exit(EXIT_FAILURE);
    }

    // 2️⃣ 连接服务器
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = PF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("❌ 连接失败");
        close(client_fd);
        exit(EXIT_FAILURE);
    }

    // 3️⃣ 发送 & 接收数据
    write(client_fd, buffer, strlen(buffer));
    read(client_fd, buffer, sizeof(buffer));
    printf("📩 服务器回复: %s\n", buffer);

    // 4️⃣ 关闭套接字
    close(client_fd);
    return 0;
}

🔹 2. UNIX 域数据报套接字(SOCK_DGRAM)

适用于 非连接通信(类似 UDP) ,如日志采集、进程间通知。

服务器端
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/mysocket_dgram"
#define BUFFER_SIZE 256

int main() {
    int server_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    // 1️⃣ 创建数据报套接字
    server_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (server_fd < 0) {
        perror("❌ socket 创建失败");
        exit(EXIT_FAILURE);
    }

    // 2️⃣ 绑定本地地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = PF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    unlink(SOCKET_PATH);
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("❌ 绑定失败");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("监听数据报套接字: %s\n", SOCKET_PATH);

    // 3️⃣ 接收数据
    recvfrom(server_fd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_len);
    printf("📩 收到消息: %s\n", buffer);

    // 4️⃣ 发送回复
    sendto(server_fd, "✅ 服务器收到消息", 20, 0, (struct sockaddr*)&client_addr, client_len);

    // 5️⃣ 关闭套接字
    close(server_fd);
    unlink(SOCKET_PATH);
    return 0;
}
客户端
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SERVER_SOCKET_PATH "/tmp/mysocket_dgram"
#define CLIENT_SOCKET_PATH "/tmp/mysocket_dgram_client"
#define BUFFER_SIZE 256

int main() {
    int client_fd;
    struct sockaddr_un server_addr, client_addr;
    char buffer[BUFFER_SIZE] = "你好,数据报服务器!";

    // 1️⃣ 创建数据报套接字
    client_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (client_fd < 0) {
        perror("❌ socket 创建失败");
        exit(EXIT_FAILURE);
    }

    // 2️⃣ 绑定客户端地址(可选)
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sun_family = PF_UNIX;
    strncpy(client_addr.sun_path, CLIENT_SOCKET_PATH, sizeof(client_addr.sun_path) - 1);
    unlink(CLIENT_SOCKET_PATH);
    bind(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));

    // 3️⃣ 发送数据
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = PF_UNIX;
    strncpy(server_addr.sun_path, SERVER_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    sendto(client_fd, buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

    // 4️⃣ 接收服务器回复
    recvfrom(client_fd, buffer, BUFFER_SIZE, 0, NULL, NULL);
    printf("📩 服务器回复: %s\n", buffer);

    // 5️⃣ 关闭套接字
    close(client_fd);
    unlink(CLIENT_SOCKET_PATH);
    return 0;
}
编译 & 运行 UNIX 域套接字:
  1. 编译服务器端和客户端

使用 gcc 进行编译:

bash 复制代码
gcc unix_socket_server.c -o unix_socket_server
gcc unix_socket_client.c -o unix_socket_client

如果没有编译错误,将会生成 unix_socket_serverunix_socket_client 可执行文件。

  1. 运行服务器
bash 复制代码
./unix_socket_server

预期输出

bash 复制代码
监听 UNIX 域套接字: /tmp/mysocket

此时服务器将等待客户端连接。

  1. 运行客户端

在另一个终端窗口运行:

bash 复制代码
./unix_socket_client

预期输出

bash 复制代码
📩 服务器回复: ✅ 服务器收到消息

服务器端的终端应显示:

bash 复制代码
📩 收到消息: 你好,服务器!
  1. 测试多个客户端

多个终端 运行:

bash 复制代码
./unix_socket_client

服务器端应能处理多个客户端连接,每次连接后显示:

bash 复制代码
📩 收到消息: 你好,服务器!
  1. 测试过程中可能出现的异常情况

🚨 5.1 服务器未启动时运行客户端

bash 复制代码
./unix_socket_client

预期错误:

bash 复制代码
❌ connect 失败: No such file or directory

解决方案:先运行 ./unix_socket_server

🚨 5.2 服务器端 bind() 失败

如果服务器 未正常退出 ,则 bind() 可能失败:

bash 复制代码
❌ bind 失败: Address already in use

解决方案:删除旧的套接字文件:

bash 复制代码
rm -f /tmp/mysocket

然后重新运行服务器:

bash 复制代码
./unix_socket_server

🚨 5.3 Too many open files

如果服务器长时间运行,可能会遇到:

bash 复制代码
❌ accept 失败: Too many open files

✅ 解决方案:

  • 设置更高的文件描述符限制:
bash 复制代码
ulimit -n 65535
  • 确保 close(client_fd); 在服务器端被正确调用。
  1. 关闭服务器

按 Ctrl + C 终止服务器。

如果服务器异常退出,建议手动删除 /tmp/mysocket 文件:

bash 复制代码
rm -f /tmp/mysocket

关于如何处理UNIX域套接字的错误

UNIX 域套接字错误处理指南

📌 1. 常见错误及解决方案

在使用 UNIX 域套接字(Unix Domain Sockets, UDS) 进行 本地 IPC 通信 时,可能会遇到各种错误。以下是常见错误及其解决方法。

🚨 1.1 bind() 失败 - Address already in use

  • 发生原因
    • 旧的套接字文件仍然存在 ,导致 bind() 失败。
    • 上次程序异常退出,未正确 unlink() 套接字文件。

✅ 解决方案:

bind() 之前 删除旧的套接字文件

c 复制代码
unlink("/tmp/mysocket");  // 删除已有的套接字文件

如下示例:

c 复制代码
struct sockaddr_un server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = PF_UNIX;
strcpy(server_addr.sun_path, "/tmp/mysocket");

unlink("/tmp/mysocket");  // 删除旧文件,避免 "Address already in use"

if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
    perror("❌ bind 失败");
    exit(EXIT_FAILURE);
}

🚨 1.2 connect() 失败 - No such file or directory

  • 发生原因
    • 服务器套接字文件不存在bind() 失败或者未运行)。
    • 路径拼写错误 (如 /tmp/mysocket 被写成 /tmp/mysocke)。

✅ 解决方案:
检查服务器是否成功创建了套接字文件

bash 复制代码
ls -l /tmp/mysocket  # 确保服务器创建了该文件

确保路径正确

c 复制代码
struct sockaddr_un client_addr;
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sun_family = PF_UNIX;
strcpy(client_addr.sun_path, "/tmp/mysocket");  // 确保路径正确

if (connect(sockfd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
    perror("❌ connect 失败");
    exit(EXIT_FAILURE);
}

🚨 1.3 accept() 失败 - Too many open files

  • 发生原因
    • 文件描述符使用过多 ,达到系统 ulimit 限制(默认 1024)。
    • 调用 close() 失败,导致文件描述符泄漏。

✅ 解决方案:
查询当前进程的文件描述符限制

bash 复制代码
ulimit -n

临时增加文件描述符限制

bash 复制代码
ulimit -n 65535

在服务器端正确关闭 accept() 返回的 client_fd

c 复制代码
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
    perror("❌ accept 失败");
} else {
    // 处理客户端请求...
    close(client_fd);  // 关闭连接,防止文件描述符泄漏
}

🚨 1.4 recv() / send() 失败 - Broken pipe

  • 发生原因
    • 客户端突然断开连接 ,但服务器仍然尝试 send() 数据。
    • SIGPIPE 信号未被处理,导致进程崩溃。

✅ 解决方案:
忽略 SIGPIPE 信号

c 复制代码
signal(SIGPIPE, SIG_IGN);  // 忽略 SIGPIPE,防止进程崩溃

检测 send() 返回值

c 复制代码
ssize_t bytes_sent = send(sockfd, data, strlen(data), 0);
if (bytes_sent < 0) {
    perror("❌ send 失败");
}

检测 recv() 返回值

c 复制代码
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received == 0) {
    printf("客户端已断开连接\n");
} else if (bytes_received < 0) {
    perror("❌ recv 失败");
}

🚨 1.5 recvfrom() 失败 - Invalid argument

  • 发生原因
    • 在数据报(SOCK_DGRAM)模式下,未正确绑定地址。
    • 调用 recvfrom() 时,传递了错误的 socklen_t 值。

✅ 解决方案:
确保服务器端 bind() 了正确的地址

c 复制代码
struct sockaddr_un server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = PF_UNIX;
strcpy(server_addr.sun_path, "/tmp/mysocket_dgram");

unlink("/tmp/mysocket_dgram");  // 防止地址被占用
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
    perror("❌ bind 失败");
    exit(EXIT_FAILURE);
}

确保 recvfrom() 传递正确的 socklen_t 值

c 复制代码
struct sockaddr_un client_addr;
socklen_t client_len = sizeof(client_addr);
recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &client_len);
UNIX 域套接字完整错误处理示例
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/mysocket"

void handle_sigpipe(int signo) {
    printf("SIGPIPE 信号,客户端可能已断开\n");
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[256];

    // 1️⃣ 忽略 SIGPIPE,防止 send() 失败时进程崩溃
    signal(SIGPIPE, handle_sigpipe);

    // 2️⃣ 创建套接字
    server_fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("❌ socket 创建失败");
        exit(EXIT_FAILURE);
    }

    // 3️⃣ 绑定地址
    unlink(SOCKET_PATH);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = PF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("❌ bind 失败");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 4️⃣ 监听连接
    listen(server_fd, 5);
    printf("监听 UNIX 域套接字: %s\n", SOCKET_PATH);

    while (1) {
        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("❌ accept 失败");
            continue;
        }

        // 5️⃣ 读取数据
        ssize_t bytes_received = read(client_fd, buffer, sizeof(buffer));
        if (bytes_received > 0) {
            printf("📩 收到消息: %s\n", buffer);
            write(client_fd, "✅ 服务器收到消息", 20);
        } else if (bytes_received == 0) {
            printf("客户端已断开连接\n");
        } else {
            perror("❌ recv 失败");
        }
        close(client_fd);
    }

    close(server_fd);
    unlink(SOCKET_PATH);
    return 0;
}

正确处理 UNIX 域套接字错误,可以提高通信稳定性,防止进程崩溃;建议使用 unlink() 处理 bind() 失败忽略 SIGPIPE 处理 send() 失败

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

相关推荐
YancyYue3 分钟前
HTTP实验(ENSP模拟器实现)
网络·网络协议·http
xianwu54321 分钟前
反向代理模块kfj
开发语言·网络·数据库·c++·mysql
EPSDA25 分钟前
网络基础知识
linux·运维·服务器·开发语言·c++
小冷爱学习!1 小时前
华为动态路由-OSPF-综合案例
服务器·网络·华为
guoguoqiang.2 小时前
我与Linux的爱恋:了解信号量+共享内存+消息队列的应用
linux·运维·服务器
沐千熏2 小时前
安装Liunx(CentOS-6-x86_64)系统
linux·centos
小许不内卷3 小时前
TCPDF 任意文件读取漏洞:隐藏在 PDF 生成背后的危险
网络·安全
fusugongzi3 小时前
clickhouse集群搭建
linux·服务器·clickhouse
yuanbenshidiaos3 小时前
【僵尸进程】
linux·服务器·网络
Oracle_6663 小时前
《Linux 指令集:开启极客世界的钥匙_01》
linux·运维·前端