一、本地Socket到底是什么?
咱们先聊聊本地Socket。说白了,它就是同一台机器上两个进程之间通信的"管道"。你想想看,网络Socket是用来跨机器通信的,而本地Socket呢?就是给本机上的进程们互相递个话。
我记得刚入行那会儿,有个同事非要用TCP回环地址(127.0.0.1)做进程间通信。我说你干嘛不用本地Socket?他还不信。后来压测的时候,本地Socket比TCP回环快了将近一倍,他才服气。
1.1 地址结构:sockaddr_un
本地Socket的地址结构长这样,我直接上代码:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; // 地址族,填 AF_UNIX 或 AF_LOCAL
char sun_path[]; // 路径名,比如 "/tmp/my_socket"
};
这里有个坑,我踩过。sun_path的长度是有限的,不同系统不一样。Linux上是108个字符,BSD上是104个。你要是路径写太长,直接截断,连个警告都没有。
避坑指南:我曾经在一个项目里,把socket文件路径写成了嵌套目录,结果bind()一直失败。后来才发现,父目录必须存在,而且进程要有写权限。嗯,这个坑我记了好几年。
二、两种工作模式
2.1 本地流式Socket(SOCK_STREAM)
流式Socket,就是面向连接的。像打电话一样,先拨号,再通话,最后挂断。它保证数据有序、不丢失、不重复。
我个人的习惯是,如果两个进程需要频繁交换大量数据,就用流式。比如数据库客户端和服务器之间,用流式就特别合适。
// 服务端示例
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysocket");
bind(sfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sfd, 5);
int cfd = accept(sfd, NULL, NULL);
// 然后就可以用 read/write 通信了
2.2 本地数据报Socket(SOCK_DGRAM)
数据报Socket,像发快递。不需要建立连接,直接扔数据包。但它不保证顺序,也不保证一定到达。
什么时候用数据报?我举个例子。监控系统里,各个模块上报心跳包,丢一两个无所谓,但要求实时性高。这时候数据报就比流式合适。
// 发送端
int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/dgram_socket");
sendto(sfd, "hello", 5, 0,
(struct sockaddr*)&addr, sizeof(addr));
三、本地Socket vs 网络Socket
我整理了个表格,方便你对比:
| 对比项 | 本地Socket | 网络Socket |
|---|---|---|
| 地址族 | AF_UNIX / AF_LOCAL | AF_INET / AF_INET6 |
| 地址形式 | 文件路径 | IP:端口 |
| 跨机器 | 不行 | 可以 |
| 性能 | 高(不走网络协议栈) | 相对低 |
| 安全性 | 依赖文件权限 | 依赖防火墙等 |
为什么会这样?因为本地Socket的数据传输,压根不经过网络协议栈。数据直接从用户态拷贝到另一个用户态,中间没有TCP/IP那堆开销。你想想看,这能不快吗?
四、应用场景
我这些年做过的项目里,本地Socket最常见的几个场景:
- 数据库通信:比如MySQL、PostgreSQL的本地连接,用的就是本地Socket
- 容器间通信:Docker里同一个宿主机上的容器,用本地Socket比走网络快得多
- 多媒体框架:比如PulseAudio,用本地Socket传递音频数据
- 系统服务:systemd的journald日志服务,内部通信全用本地Socket
我的建议:只要两个进程在同一台机器上,优先考虑本地Socket。别图省事直接用TCP回环,性能差距真的很大。
五、性能优势到底有多大?
我做过一个简单的压测。同样传输1MB数据,本地Socket比TCP回环快了大概30%-50%。原因有三:
- 没有协议栈开销:TCP的三次握手、四次挥手全免了
- 数据拷贝少:内核态到用户态的拷贝次数更少
- 没有网络延迟:哪怕回环接口,也有微秒级的延迟
嗯,这里要注意。性能优势在高频小数据包场景下特别明显。如果你一次传几MB的大块数据,差距反而没那么大,因为瓶颈在内存拷贝上。
六、综合案例:本地日志收集器
我写个完整的例子,展示本地Socket怎么用。这个案例是一个简单的日志收集系统:
/* log_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/log_collector"
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[BUFFER_SIZE];
// 1. 创建socket
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
// 2. 绑定地址
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 如果socket文件已存在,先删除
unlink(SOCKET_PATH);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
// 3. 监听
if (listen(server_fd, 5) == -1) {
perror("listen");
exit(1);
}
printf("日志服务器启动,等待连接...\n");
// 4. 接受连接
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
exit(1);
}
// 5. 读取日志
while (1) {
memset(buffer, 0, BUFFER_SIZE);
int n = read(client_fd, buffer, BUFFER_SIZE - 1);
if (n <= 0) break;
printf("[收到日志] %s", buffer);
}
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
return 0;
}
/* log_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/log_collector"
int main() {
int sock_fd;
struct sockaddr_un addr;
char message[] = "这是一条测试日志\n";
// 1. 创建socket
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket");
exit(1);
}
// 2. 连接服务器
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("connect");
exit(1);
}
// 3. 发送日志
write(sock_fd, message, strlen(message));
printf("日志已发送\n");
close(sock_fd);
return 0;
}
运行方式:先启动服务端 ./log_server,再启动客户端 ./log_client。服务端会打印出客户端发送的日志。
七、知识体系总览
我把本章的核心知识点画了张图,方便你理解整体结构:
好了,本地Socket的核心内容就这些。记住一句话:同机通信,首选本地Socket。下一章咱们聊聊有名管道,那也是个好东西。
公众号:蓝海资料掘金营,微信deep3321