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 调用、数据库访问 等场景。支持多个客户端连接,服务器能正确接收 & 发送数据。
服务器端
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 域套接字:
- 编译服务器端和客户端
使用 gcc
进行编译:
bash
gcc unix_socket_server.c -o unix_socket_server
gcc unix_socket_client.c -o unix_socket_client
如果没有编译错误,将会生成 unix_socket_server
和 unix_socket_client
可执行文件。
- 运行服务器
bash
./unix_socket_server
预期输出
bash
监听 UNIX 域套接字: /tmp/mysocket
此时服务器将等待客户端连接。
- 运行客户端
在另一个终端窗口运行:
bash
./unix_socket_client
预期输出
bash
📩 服务器回复: ✅ 服务器收到消息
服务器端的终端应显示:
bash
📩 收到消息: 你好,服务器!
- 测试多个客户端
在 多个终端 运行:
bash
./unix_socket_client
服务器端应能处理多个客户端连接,每次连接后显示:
bash
📩 收到消息: 你好,服务器!
- 测试过程中可能出现的异常情况
🚨 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);
在服务器端被正确调用。
- 关闭服务器
按 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() 失败。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!