linux Unix Socket 介绍

发布于:2025-06-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

非常好!Unix Socket 是在 Linux / Unix 系统中进行**本地进程间通信(IPC)**的一种非常高效和灵活的方式,被广泛用于:

  • 系统内部通信(如 systemd 与各服务)
  • 数据库通信(如 PostgreSQL)
  • 复杂 IPC 系统(比如 Nginx 和 FastCGI)

🧠 一、什么是 Unix Socket?

Unix Socket 是一种基于文件系统路径(如 /tmp/mysock)来标识通信端点的本地 socket 通信机制,工作原理与 TCP/UDP 非常相似,但它:

特性 Unix Socket TCP/IP Socket
通信对象 同一台机器的进程 可跨主机
地址类型 文件路径 (/tmp/sock) IP地址 + 端口
性能 更高(不走网络协议栈) 相对较低
常用于 IPC 本地通信 网络服务

它实际上使用 socket API,但地址族是 AF_UNIX(也叫 AF_LOCAL),不是 AF_INET


🧱 二、基本结构和 API 调用流程

和网络 socket 很相似:

服务端流程:

  1. 创建 socket (socket)
  2. 绑定路径地址 (bind)
  3. 开始监听 (listen)
  4. 接受连接 (accept)
  5. 通信(read / write
  6. 关闭 socket (close)

客户端流程:

  1. 创建 socket (socket)
  2. 连接服务端路径 (connect)
  3. 通信
  4. 关闭

🔧 三、最小 Unix Socket 通信示例

🔌 服务端:

// unix_server.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

int main() {
    int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); return 1; }

    sockaddr_un addr{};
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/mysocket");

    unlink("/tmp/mysocket"); // 清理旧的 socket 文件

    if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 1;
    }

    listen(server_fd, 5);
    std::cout << "[Server] Waiting for connections...\n";

    int client_fd = accept(server_fd, nullptr, nullptr);
    char buf[128] = {0};
    read(client_fd, buf, sizeof(buf));
    std::cout << "[Server] Received: " << buf << std::endl;

    const char *reply = "Hello from server";
    write(client_fd, reply, strlen(reply));

    close(client_fd);
    close(server_fd);
    unlink("/tmp/mysocket"); // 删除 socket 文件
}

🔌 客户端:

// unix_client.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <iostream>

int main() {
    int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd < 0) { perror("socket"); return 1; }

    sockaddr_un addr{};
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/mysocket");

    if (connect(sock_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        return 1;
    }

    const char *msg = "Hello server!";
    write(sock_fd, msg, strlen(msg));

    char buf[128] = {0};
    read(sock_fd, buf, sizeof(buf));
    std::cout << "[Client] Received: " << buf << std::endl;

    close(sock_fd);
}

✅ 编译方式:

g++ unix_server.cpp -o server
g++ unix_client.cpp -o client

运行:

./server      # 一个终端
./client      # 另一个终端

📦 四、Unix Socket 类型

类型 用法 说明
SOCK_STREAM 面向连接(类似 TCP) 常用,支持 read / write
SOCK_DGRAM 无连接(类似 UDP) sendto / recvfrom

示例里用的是 SOCK_STREAM,最常见。


🧠 五、Unix Socket 的优势

  • 速度快:不经过网络协议栈(相比 TCP 快 2~3 倍)
  • 文件系统集成:用文件路径表示 socket,便于权限管理
  • 支持多种通信模型:阻塞/非阻塞、epoll 事件模型等
  • 无需监听端口:安全性高,防外网攻击

🛡️ 六、权限控制与安全性

因为是普通文件路径,所以:

  • 可以使用 chmod 控制权限
  • 例如只允许某些用户连接 /tmp/mysocket
  • 系统服务也常这么做(如 /run/docker.sock

🧩 七、进阶玩法

你可以结合:

  • epoll 实现高并发 Unix socket 服务器
  • sendmsg / recvmsg 传递文件描述符(高级功能)
  • 使用 SO_PASSCRED 传递 UID/GID 验证身份
  • socketpair() 创建双向通道(比如守护进程与客户端通信)

✅ 总结一句话:

Unix Socket 是最适合本机进程间高效、可靠、灵活通信的机制,几乎所有高级 IPC 系统都在用它。


如果你想继续深入:

✅ 想不想我基于这个再帮你写一个 epoll + Unix socket 的并发服务 demo?
✅ 或者讲讲 socketpair() / sendmsg() 传 FD 的用法?
你说,我就写。
好!我们来详细讲讲两个非常实用的高级 IPC 技巧:


🧠 一、socketpair()sendmsg() 传文件描述符(FD)

这两者分别解决了两个问题:

功能 技术 用途
双向通信 socketpair() 类似管道,但支持双向
传递 FD sendmsg() / recvmsg() 把一个进程中的 fd 发送给另一个进程使用

🧱 二、socketpair() —— 创建一对连接的 socket

就像一根“管道”,但是双向的、基于 socket 的,比 pipe() 更灵活。

🔧 使用方式

int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
// sv[0] 和 sv[1] 是两端的 socket fd,可以双向通信

通常用于:

  • 父子进程通信
  • 多线程通信(适用于 epoll
  • 模拟 TCP 连接通道但无需网络

🔄 三、进程间传递 FD 的思路

✅ 为什么要传 FD?

比如你用主进程监听连接,但你要把 连接 fd 交给另一个子进程去处理,就得“传过去”。

✅ 怎样传?

必须用 sendmsg() / recvmsg(),用带外带外数据结构 cmsghdr 传递 SCM_RIGHTS


📦 四、传 FD 的完整 demo 示例

我们写一个程序:

  • 父进程建立 socketpair
  • 父进程打开一个文件,将文件的 fd 发送给子进程
  • 子进程收到 fd,读取内容

🔹父子进程传递文件描述符示例:

// send_fd.cpp
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <cstring>

void send_fd(int sock, int fd_to_send) {
    struct msghdr msg = {};
    struct iovec iov;
    char dummy = '*';
    iov.iov_base = &dummy;
    iov.iov_len = 1;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // 用于携带控制信息(FD)
    char cmsgbuf[CMSG_SPACE(sizeof(int))];
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;  // 表示发送文件描述符
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));

    *((int *) CMSG_DATA(cmsg)) = fd_to_send;

    if (sendmsg(sock, &msg, 0) < 0)
        perror("sendmsg");
}

int recv_fd(int sock) {
    struct msghdr msg = {};
    struct iovec iov;
    char dummy;
    iov.iov_base = &dummy;
    iov.iov_len = 1;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    char cmsgbuf[CMSG_SPACE(sizeof(int))];
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    if (recvmsg(sock, &msg, 0) < 0)
        perror("recvmsg");

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    int fd = -1;
    if (cmsg && cmsg->cmsg_level == SOL_SOCKET &&
        cmsg->cmsg_type == SCM_RIGHTS) {
        fd = *((int *) CMSG_DATA(cmsg));
    }
    return fd;
}

int main() {
    int sv[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("socketpair");
        return 1;
    }

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:接收文件描述符
        close(sv[0]);
        int fd = recv_fd(sv[1]);
        std::cout << "[Child] Got fd: " << fd << std::endl;

        char buf[100] = {0};
        lseek(fd, 0, SEEK_SET); // 确保读到开头
        read(fd, buf, sizeof(buf));
        std::cout << "[Child] File content: " << buf << std::endl;

        close(fd);
        close(sv[1]);
    } else {
        // 父进程:发送文件描述符
        close(sv[1]);
        int fd = open("test.txt", O_RDONLY);
        if (fd < 0) { perror("open"); return 1; }

        send_fd(sv[0], fd);
        close(fd);
        close(sv[0]);
    }

    return 0;
}

✅ 运行步骤

  1. 准备一个 test.txt 文件:
echo "Hello FD World!" > test.txt
  1. 编译运行:
g++ send_fd.cpp -o send_fd
./send_fd

你将看到:

[Child] Got fd: 4
[Child] File content: Hello FD World!

🔍 五、总结

技术 用途 特点
socketpair() 本地双向通信通道 快速、无需监听、适合父子进程
sendmsg() / recvmsg() 跨进程传文件描述符 内核传递,不依赖全局变量

✅ 衍生场景

  • 多进程网络服务器:主进程 accept() 后把连接 fd 分发给子进程
  • 守护进程管理:通过 FD 实现权限隔离和统一资源管理
  • 父进程传 pipe、socket、eventfd 等给子进程

如果你想我可以写:

  • ✅ 多进程 echo server,使用 socketpair 分发任务
  • ✅ 多进程 epoll + FD 传递高并发处理模型

你想继续往哪边深入?我可以马上写代码+讲解。




非常好,我们来系统讲解一下在 Linux 下如何通过 Unix Domain Socket(UNIX 套接字) 实现进程间通信(IPC)


🧭 目标说明

你有两个进程:

  • Server 进程:接收数据
  • Client 进程:发送数据

它们在 同一台机器的同一操作系统内核空间下运行,通过一个“socket 文件”进行通信。这个 socket 文件存在于文件系统中,例如 /tmp/mysock


🧱 方式选择:Unix Socket 是 IPC 中的高效方式

通信方式 适合场景 优缺点
Pipe 父子进程通信 简单但不能跨非亲缘进程
FIFO 任意进程通信 文件路径通信,半双工
Unix Socket 任意进程、双工通信、支持 select/epoll 🚀推荐
消息队列 / 共享内存 / 信号量 高性能 / 同步控制 复杂、调试麻烦
TCP/IP socket 可跨主机,性能差一点 多用于网络

✅ Unix Socket 通信原理

  • 使用 AF_UNIX 地址族
  • 可以像 TCP socket 一样 bind()listen()accept()connect()send()recv()
  • 通信是 面向流(SOCK_STREAM)数据报(SOCK_DGRAM)
  • 不经过网络栈,效率高

📦 实例演示(基于 SOCK_STREAM)

🚦 1. 创建 socket 服务器进程(接收端)

// server.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

#define SOCK_PATH "/tmp/mysock"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un addr;
    char buf[128];

    // 创建 socket
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        return 1;
    }

    // 绑定路径
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCK_PATH);
    unlink(SOCK_PATH); // 如果之前有就删掉

    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 2;
    }

    listen(server_fd, 5);

    std::cout << "[Server] Waiting for connection...\n";
    client_fd = accept(server_fd, NULL, NULL);
    std::cout << "[Server] Client connected!\n";

    int n = read(client_fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        std::cout << "[Server] Received: " << buf << std::endl;
    }

    close(client_fd);
    close(server_fd);
    unlink(SOCK_PATH);
    return 0;
}

🚦 2. 创建 socket 客户端进程(发送端)

// client.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

#define SOCK_PATH "/tmp/mysock"

int main() {
    int client_fd;
    struct sockaddr_un addr;

    client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (client_fd < 0) {
        perror("socket");
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCK_PATH);

    if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        return 2;
    }

    const char *msg = "Hello from client!";
    write(client_fd, msg, strlen(msg));
    std::cout << "[Client] Sent message.\n";

    close(client_fd);
    return 0;
}

🛠 编译与运行

g++ server.cpp -o server
g++ client.cpp -o client

运行:

./server    # 先启动服务端
./client    # 启动客户端发送消息

输出:

[Server] Waiting for connection...
[Server] Client connected!
[Server] Received: Hello from client!

✅ 优势总结

  • ✅ 可以用于任意两个进程通信(不需要是父子进程)
  • ✅ 支持双向通信(read+write
  • ✅ 支持 select()epoll() 等异步模型
  • ✅ 性能远高于 TCP socket(因为不经过网络协议栈)

📦 衍生用法

你还可以用 Unix Socket 实现:

  • 🔄 RPC 通信(例如 gRPC + unix socket)
  • 📺 服务进程间的信号通知(配合 select)
  • 🔐 传送 fd(file descriptor)文件描述符
  • 📊 与 systemd 的 socket activation 通信
  • 🚀 用于嵌入式设备中代替 DBus 进行本地模块间通信

📌 是否需要我为你扩展下面内容?

  • 🔁 使用 epoll() 实现多个客户端接入?
  • 🔐 传递文件描述符(fd-passing)?
  • 💡 把 client/server 封装成库?
  • 🌟 更复杂的二进制结构体协议发送/接收?

你可以告诉我你的应用场景,我可以给你定制化。是否继续?


网站公告

今日签到

点亮在社区的每一天
去签到