Socket
Socket是什么
Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组编程接口(API),可以看作是主机间通信的端点。Socket 编程是网络应用开发的基础,几乎所有网络协议和应用程序都是基于 Socket 接口实现的。
Socket原理
Socket的系统调用
int socket(int domain, int type, int protocal)
-
domain:即协议域,又称为协议族(family)。 协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。常用的地址族有:
- AF_INET
- AF_INET6
- AF_LOCAL(AF_UNIX,本地通信用)
- AF_ROUTE
-
type:信息传送方式。常用的信息传送方式有:
- SOCK_STREAM
- SOCK_DGRAM
- SOCK_RAW
- SOCK_PACKET
- SOCK_SEQPACKET
-
protocol:对应协议。通常设置为0,让其自动匹配。
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP STCP传输协议
- IPPROTO_TIPCTIPC传输协议
-
实现 TCP 字节流通信: socket 类型是 AF-INET 和 SOCK_STREAM;
-
实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
-
实现本地进程间通信: 「本地字节流 socket」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报socket 」类型是AF_LOCAL和SOCK_DGRAM。另外, AF_UNIX和AF_LOCAL是等价的,所以AF_UNIX 也属于本地 socket;
Unix domain socket
UNIX Domain Socket 详解:高效的进程间通信机制
一、UNIX Domain Socket 概述
UNIX Domain Socket(简称 UDS),又称 IPC socket,是一种基于 socket 框架发展而来的高效进程间通信(IPC)机制,专为同一主机上的进程通信设计。作为 POSIX 标准组件,它不仅限于 UNIX 系统,Linux 等现代操作系统也都提供了完善支持。
与网络 Socket 的关键区别
虽然传统网络 socket 通过 loopback 地址(127.0.0.1)也能实现本地进程通信,但 UDS 具有显著优势:
-
协议栈差异:
- 网络 socket:需要经过完整的 TCP/IP 协议栈处理
- UDS:完全绕过网络协议栈,直接在内核中完成数据传递
-
数据传输效率:
- 网络 socket:需要打包/拆包、计算校验和、维护序列号和应答机制
- UDS:直接将应用层数据从一个进程缓冲区拷贝到另一个进程缓冲区
-
性能表现:
- 实测表明 UDS 的传输效率比 loopback 网络 socket 快近一倍
- 减少了至少两次数据拷贝(用户态-内核态-用户态)
-
可靠性:
- UDS 基于本地文件系统实现,不受网络环境影响
- 内核保证数据传输的原子性和顺序性
二、UNIX Domain Socket 核心特性
1. 全双工通信
UDS 支持全双工工作模式,通信双方可以同时进行读写操作,这与管道(Pipe)等半双工 IPC 机制形成鲜明对比。
2. 丰富的 API 语义
继承自 BSD socket 的完整 API 体系,提供比传统 IPC 机制更灵活的控制能力:
- 支持面向连接(SOCK_STREAM)和无连接(SOCK_DGRAM)两种模式
- 提供 select/poll/epoll 等多路复用机制
- 支持带外数据等高级特性
3. 文件系统集成
UDS 通过特殊的 socket 文件进行标识:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* 路径名 */
};
这种设计带来了额外优势:
- 天然的权限控制(通过文件权限位)
- 直观的进程间可见性(通过文件系统)
- 持久化能力(虽然不常用)
4. 广泛的应用支持
已成为事实上的标准 IPC 机制:
- X Window 系统(GUI 通信)
- Docker/容器技术(控制面通信)
- DBMS(如 MySQL 的本地连接)
- 系统监控工具(如 Prometheus 的 node_exporter)
三、UNIX Domain Socket 编程详解
基本通信流程
服务器端流程:
- 创建 socket:
socket(AF_UNIX, SOCK_STREAM, 0)
- 绑定地址:
bind()
指定 socket 文件路径 - 监听连接:
listen()
- 接受连接:
accept()
- 数据交换:
read()/write()
或send()/recv()
- 关闭连接:
close()
客户端流程:
- 创建 socket
- 连接服务器:
connect()
- 数据交换
- 关闭连接
关键系统调用
#include <sys/socket.h>
#include <sys/un.h>
// 创建socket
int socket(int domain, int type, int protocol);
// domain: AF_UNIX
// type: SOCK_STREAM(可靠连接)或SOCK_DGRAM(数据报)
// 地址绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 连接建立
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 数据收发
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
完整示例代码
服务器端 server.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/demo_socket"
int main() {
int server_fd, client_fd;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
char buf[256];
// 1. 创建socket
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_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)) == -1) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening...\n");
// 4. 接受连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5. 数据交换
ssize_t num_read = read(client_fd, buf, sizeof(buf));
printf("Received: %.*s\n", (int)num_read, buf);
const char *msg = "Hello from server!";
write(client_fd, msg, strlen(msg));
// 6. 清理
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
return 0;
}
客户端 client.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/demo_socket"
int main() {
int sockfd;
struct sockaddr_un addr;
char buf[256];
// 1. 创建socket
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 连接服务器
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)-1);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}
// 3. 数据交换
const char *msg = "Hello from client!";
write(sockfd, msg, strlen(msg));
ssize_t num_read = read(sockfd, buf, sizeof(buf));
printf("Received: %.*s\n", (int)num_read, buf);
// 4. 清理
close(sockfd);
return 0;
}
四、高级特性与最佳实践
1. 性能优化技巧
- 使用 sendmsg/recvmsg:支持分散/聚集IO,减少数据拷贝
- 设置非阻塞模式:配合IO多路复用提高并发能力
- 适当调整缓冲区大小:平衡内存使用和系统调用开销
2. 安全注意事项
- 设置严格的文件权限(通常设为0700)
- 验证对端进程身份(通过 getsockopt SO_PEERCRED)
- 避免使用公共目录存放socket文件
3. 抽象socket命名空间
Linux 3.8+支持抽象socket命名空间:
addr.sun_path[0] = '\0'; // 第一个字节为null
strncpy(addr.sun_path+1, "abstract_name", sizeof(addr.sun_path)-2);
特点:
- 不创建实际文件
- 生命周期与内核相同
- 自动清理
4. 与其它IPC机制对比
特性 | UDS | 管道 | 共享内存 | 消息队列 |
---|---|---|---|---|
通信方向 | 全双工 | 半双工 | 双向 | 双向 |
数据边界 | 保留 | 字节流 | 无 | 保留 |
访问控制 | 文件权限 | 文件权限 | 无 | 系统权限 |
性能 | 高 | 中 | 最高 | 中低 |
复杂度 | 中 | 低 | 高 | 中 |
五、实际应用场景
- 微服务架构:同一主机上的服务间通信
- 安全沙箱:受限环境下的进程隔离与通信
- 高性能代理:Nginx等服务器与后端进程通信
- 设备控制:用户空间与内核模块的交互
- 桌面环境:GUI应用与后台服务通信
结语
UNIX Domain Socket 凭借其高效性、可靠性和丰富的功能特性,已成为现代操作系统中进程间通信的首选方案。无论是系统级开发还是应用层编程,深入理解 UDS 的工作原理和最佳实践,都能帮助开发者构建更高效、更可靠的本地通信系统。随着容器化技术的普及,UDS 在隔离环境通信中的作用将愈发重要。
参考文章
https://blog.youkuaiyun.com/weixin_39258979/article/details/80835555
https://xiaolincoding.com/os/4_process/process_commu.html#socket
https://www.cnblogs.com/sparkdev/p/8359028.html
https://blog.youkuaiyun.com/weixin_39258979/article/details/80931464