这是 IPC 的终极形态,也是也是目前互联网的基石。
我们将使用 Unix Domain Socket (UDS),因为它在本地通信中比 TCP/IP 更快、更高效(不需要经过网络协议栈),但编程接口和 TCP 几乎一模一样。
实验设计
-
Server (
one.c):- 创建一个 Socket 文件
/tmp/my_socket.sock。 - 像前台一样坐在那里
accept(等待连接)。 - 一旦有人连上来,就读取数据并打印。
- 创建一个 Socket 文件
-
Client (
two.c):- 创建一个 Socket。
-
connect到/tmp/my_socket.sock。 - 发送消息,然后断开。
代码
one.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h> // Unix Domain Socket 专用头文件
#define SOCKET_PATH "/tmp/my_socket.sock"
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[1024];
// 1. 创建 Socket
// AF_UNIX: 本地通信域
// SOCK_STREAM: 流式传输 (类似 TCP)
if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
return 1;
}
// 2. 绑定地址 (Bind)
// 先删除旧的 socket 文件,否则 bind 会失败
unlink(SOCKET_PATH);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)-1);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
return 1;
}
// 3. 监听 (Listen)
// 5 表示等待队列长度
if (listen(server_fd, 5) == -1) {
perror("listen");
return 1;
}
printf("=== 服务端 ONE (PID: %d) ===\n", getpid());
printf("正在监听 Socket: %s\n", SOCKET_PATH);
while(1) {
printf("等待客户端连接...\n");
// 4. 接受连接 (Accept)
// 这里会阻塞,直到有客户端连上来
// client_fd 是专门用来跟这个客户端聊天的专用电话线
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
continue;
}
printf("[ONE] 客户端已连接!准备接收数据...\n");
// 5. 读取数据
while(1) {
int bytes_read = read(client_fd, buffer, sizeof(buffer));
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("[ONE] 收到: %s\n", buffer);
} else if (bytes_read == 0) {
printf("[ONE] 客户端断开了连接。\n");
break;
} else {
perror("read");
break;
}
}
close(client_fd);
printf("----------------------------\n");
}
close(server_fd);
unlink(SOCKET_PATH);
return 0;
}
two.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/my_socket.sock"
int main(int argc, char *argv[]) {
int client_fd;
struct sockaddr_un addr;
char buffer[1024];
if (argc < 2) {
printf("使用方法: %s <消息内容>\n", argv[0]);
return 1;
}
// 1. 创建 Socket
if ((client_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
return 1;
}
// 2. 连接服务端 (Connect)
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)-1);
printf("=== 客户端 TWO ===\n");
printf("正在连接 %s ...\n", SOCKET_PATH);
if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("connect (可能服务端没开?)");
return 1;
}
printf("连接成功!\n");
// 3. 发送消息
strcpy(buffer, argv[1]);
if (write(client_fd, buffer, strlen(buffer)) == -1) {
perror("write");
return 1;
}
printf("已发送: \"%s\"\n", buffer);
// 关闭连接
close(client_fd);
return 0;
}
实验步骤
- 接受修改。
-
编译:
gcc one.c -o process_one gcc two.c -o process_two -
运行服务端 (one):
它会显示“正在监听...”,然后阻塞等待。./process_one -
运行客户端 (two)(在另一个终端):
你会看到./process_two "你好 Socket!"process_one收到消息,然后打印“客户端断开了连接”,并继续等待下一个客户端(因为有while(1)循环)。
为什么说 Socket 是终极形态?
如果你现在想把 one.c 部署到阿里云服务器,把 two.c 放在你家里的电脑上:
-
对于 Socket 代码:只需要把
AF_UNIX改成AF_INET,把文件路径改成 IP 地址和端口号。核心逻辑(socket,bind,listen,accept,read/write)完全不需要改! - 对于共享内存/管道/消息队列:你需要重写整个程序,因为它们都不支持跨网络。
能看到 #define SOCKET_PATH "/tmp/my_socket.sock" 文件里的内容吗?
不能。
这个 .sock 文件和之前的命名管道(FIFO)类似,但更彻底:
- 它只是一个连接点(Endpoint)。
- 它没有任何内容。
- 它的大小永远是 0。
如果你尝试 cat /tmp/my_socket.sock
你会得到一个错误:
cat: /tmp/my_socket.sock: No such device or address
或者
cat: /tmp/my_socket.sock: Operation not supported on socket
因为 Socket 本质上不是一个“存储数据的容器”,而是一个“插座”。
你不能问“插座里存了多少电?”,你只能把插头插进去,然后电流(数据)流过它。
怎么看 Socket 的状态?
虽然看不了内容,但你可以看“连接状态”(比如谁连着谁)。
使用 netstat 或 ss 命令:
# 查看所有 Unix Domain Socket 的监听情况
netstat -u -a
# 或者
ss -x -a | grep my_socket
你会看到类似这样的信息,表明这个插座正在被 process_one 监听。