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

server/2025/2/24 8:44:33/

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 调用、数据库访问 等场景。支持多个客户端连接,服务器能正确接收 & 发送数据。

服务器端
#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;
}
客户端
#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),如日志采集、进程间通知。

服务器端
#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 256int 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;
}
客户端
#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 256int 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 进行编译:

gcc unix_socket_server.c -o unix_socket_server
gcc unix_socket_client.c -o unix_socket_client

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

  1. 运行服务器
./unix_socket_server

预期输出

监听 UNIX 域套接字: /tmp/mysocket

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

  1. 运行客户端

在另一个终端窗口运行:

./unix_socket_client

预期输出

📩 服务器回复: ✅ 服务器收到消息

服务器端的终端应显示:

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

多个终端 运行:

./unix_socket_client

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

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

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

./unix_socket_client

预期错误:

❌ connect 失败: No such file or directory

解决方案:先运行 ./unix_socket_server

🚨 5.2 服务器端 bind() 失败

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

bind 失败: Address already in use

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

rm -f /tmp/mysocket

然后重新运行服务器:

./unix_socket_server

🚨 5.3 Too many open files

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

❌ accept 失败: Too many open files

✅ 解决方案:

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

按 Ctrl + C 终止服务器。
如果服务器异常退出,建议手动删除 /tmp/mysocket 文件:

rm -f /tmp/mysocket

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

UNIX 域套接字错误处理指南

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

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

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

✅ 解决方案:
bind() 之前 删除旧的套接字文件

unlink("/tmp/mysocket");  // 删除已有的套接字文件

如下示例:

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)。

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

ls -l /tmp/mysocket  # 确保服务器创建了该文件

确保路径正确

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() 失败,导致文件描述符泄漏。

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

ulimit -n

临时增加文件描述符限制

ulimit -n 65535

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

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 信号

signal(SIGPIPE, SIG_IGN);  // 忽略 SIGPIPE,防止进程崩溃

检测 send() 返回值

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

检测 recv() 返回值

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() 了正确的地址

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 值

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 域套接字完整错误处理示例
#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() 失败

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

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


http://www.ppmy.cn/server/170300.html

相关文章

RabbitMQ 消息队列

1. 消息队列是什么&#xff1f; 当用户注册成功后&#xff0c;就发送邮件。当邮件发送成功了&#xff0c;接口才会提示注册成功信息。但由于发送邮件&#xff0c;依赖于其他厂商的服务&#xff0c;有可能他们的接口会非常耗时。那么用户就一直要等着邮件发送成功了&#xff0c;…

Linux系统编程基础详解

Linux 系统详解 大纲 引言 Linux 的定义Linux 的历史与发展本文结构概述 Linux 的基本概念 Linux 的架构 内核与用户空间系统调用 Linux 的文件系统 文件与目录结构权限管理 Linux 的进程管理 进程与线程进程调度 Linux 的基本命令与操作 常用命令概述 文件与目录操作命令文…

20250223下载并制作RTX2080Ti显卡的显存的测试工具mats

20250223下载并制作RTX2080Ti显卡的显存的测试工具mats 2025/2/23 23:23 缘起&#xff1a;我使用X99的主板&#xff0c;使用二手的RTX2080Ti显卡【显存22GB版本&#xff0c;准备学习AI的】 但是半年后发现看大码率的视频容易花屏&#xff0c;最初以为是WIN10经常更换显卡/来回更…

单链表:数据结构中的灵活“链条”

目录 &#x1f680;前言&#x1f914;单链表是什么&#xff1f;&#x1f4af;单链表的结构特点&#x1f4af;单链表的用途 ✍️单链表的实现与接口解释&#x1f4af;打印链表&#x1f4af;尾插操作&#x1f4af;头插操作&#x1f4af;头删操作&#x1f4af;尾删操作&#x1f4a…

sh脚本把服务器B,服务器C目录的文件下载到服务器A目录,添加开机自启动并且一小时执行一次脚本

脚本逻辑 第一次会下载,第二次比较如果有就不下载 文件已存在&#xff1a; 如果目标目录中已经存在同名文件&#xff0c;rsync 会比较源文件和目标文件的大小和修改时间。 如果源文件和目标文件的大小和修改时间完全相同&#xff0c;rsync 会跳过该文件&#xff0c;不会重新下载…

【NLP 31、预训练模型的发展过程】

人的行为&#xff0c;究竟是人所带来的思维方式不同还是与机器一样&#xff0c;刻在脑海里的公式呢&#xff1f; 只是因为不同的人公式不同&#xff0c;所以人的行为才不同&#xff0c;可这又真的是人引以为傲的意识吗&#xff1f; 人脑只是相当于一个大型、驳杂的处理器&#…

anaconda不显示jupyter了?

以前下载的anaconda显示jupyter&#xff0c;但是最近学习吴恩达的机器学习视频&#xff0c;需要用到jupyter&#xff0c;以前的jupyter运行不了&#xff0c;就重新下载了一个anaconda&#xff0c;发现新版的anaconda首页不显示jupyter了&#xff0c;在查找资料之后&#xff0c;…

【设计模式】【创建型模式】原型模式(Prototype)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…