IPC通信之Socket

这是 IPC 的终极形态,也是也是目前互联网的基石。

我们将使用 Unix Domain Socket (UDS),因为它在本地通信中比 TCP/IP 更快、更高效(不需要经过网络协议栈),但编程接口和 TCP 几乎一模一样。

实验设计

  • Server (one.c):
    1. 创建一个 Socket 文件 /tmp/my_socket.sock
    2. 像前台一样坐在那里 accept (等待连接)。
    3. 一旦有人连上来,就读取数据并打印。
  • Client (two.c):
    1. 创建一个 Socket。
    2. connect/tmp/my_socket.sock
    3. 发送消息,然后断开。

代码

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;
}

实验步骤

  1. 接受修改
  2. 编译
    gcc one.c -o process_one
    gcc two.c -o process_two
    
  3. 运行服务端 (one)
    ./process_one
    
    它会显示“正在监听...”,然后阻塞等待。
  4. 运行客户端 (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 的状态?

虽然看不了内容,但你可以看“连接状态”(比如谁连着谁)。
使用 netstatss 命令:

# 查看所有 Unix Domain Socket 的监听情况
netstat -u -a 
# 或者
ss -x -a | grep my_socket

你会看到类似这样的信息,表明这个插座正在被 process_one 监听。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容