UNIX 域套接字
UNIX 域套接字(UDS, Unix Domain Socket)是一种 本地进程间通信(IPC) 方式,适用于同一台机器上的进程之间的通信。相比 TCP/IP 套接字,UDS 效率更高、延迟更低,因为它省去了网络协议的开销。
🔹 UDS 特点
- 仅限本机通信(不能用于跨网络通信)。
- 速度快(不像 TCP/IP 需要数据封装和解析)。
- 支持:
- 流式套接字(SOCK_STREAM)(类似 TCP)。
- 数据报套接字(SOCK_DGRAM)(类似 UDP)。
- 本地地址由文件路径标识(如
/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 域套接字:
- 编译服务器端和客户端
使用 gcc
进行编译:
gcc unix_socket_server.c -o unix_socket_server
gcc unix_socket_client.c -o unix_socket_client
如果没有编译错误,将会生成 unix_socket_server
和 unix_socket_client
可执行文件。
- 运行服务器
./unix_socket_server
预期输出
监听 UNIX 域套接字: /tmp/mysocket
此时服务器将等待客户端连接。
- 运行客户端
在另一个终端窗口运行:
./unix_socket_client
预期输出
📩 服务器回复: ✅ 服务器收到消息
服务器端的终端应显示:
📩 收到消息: 你好,服务器!
- 测试多个客户端
在 多个终端 运行:
./unix_socket_client
服务器端应能处理多个客户端连接,每次连接后显示:
📩 收到消息: 你好,服务器!
- 测试过程中可能出现的异常情况
🚨 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);
在服务器端被正确调用。
- 关闭服务器
按 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() 失败。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!