当前位置: 首页 > news >正文

UNIX 域套接字实现本地进程间通信

🚀 使用 UNIX 域套接字 (AF_UNIX) 实现高效进程通信

在 Linux 和其他类 UNIX 系统中,进程间通信 (IPC) 的方法有很多种,例如管道、消息队列、共享内存等。然而,当你的应用程序需要在 同一台机器上的不同进程间进行高效、低延迟的数据交换时,UNIX 域套接字 (AF_UNIX) 往往是最佳选择。
它的工作方式类似于网络套接字,但所有数据传输都发生在内核内部,避免了网络协议栈的开销,因此速度更快、效率更高。


💡 为什么选择 UNIX 域套接字?

在深入代码之前,我们先来聊聊 AF_UNIX 的优势:

  1. 高性能:数据在内核中直接传递,无需经过网络层,减少了数据复制和上下文切换,显著提升了通信速度。
  2. 低延迟:由于没有网络开销,通信延迟极低,非常适合对实时性要求高的应用。
  3. 安全性:UNIX 域套接字在文件系统中表现为一个特殊文件。这意味着你可以使用标准的文件权限来控制哪些用户或进程可以访问它,提供了比某些其他 IPC 机制更细粒度的安全控制。
  4. API 熟悉度:如果你熟悉网络套接字(AF_INET),那么AF_UNIX 的 API会让你感到非常亲切。socket(), bind(), listen(), accept(), connect(), read(), write() 等函数都通用,降低了学习成本

🛠️ 如何工作?(核心概念)

UNIX 域套接字的运作方式很直观:

  1. 文件路径作为地址:与网络套接字使用 IP 地址和端口号不同,AF_UNIX 套接字使用文件系统路径作为其唯一的标识符。服务器会绑定到一个特定的文件路径(例如 /tmp/my_socket),客户端则通过这个路径来连接服务器。

  2. 服务器端:
    创建一个 AF_UNIX 类型的套接字。
    将套接字绑定到文件系统中的一个路径。这个操作会在指定路径下创建一个特殊的套接字文件。
    开始监听传入的连接请求。
    接受客户端的连接,每次接受都会返回一个新的套接字文件描述符,用于与该客户端单独通信。
    使用 read()write() 进行数据传输。

  3. 客户端:
    创建一个 AF_UNIX 类型的套接字。
    连接到服务器绑定的文件路径。
    使用read() write() 进行数据传输。


🧑‍💻 代码实战:构建一个简单的 Echo 服务器

下面将通过 C 语言代码,一步步实现一个 UNIX 域套接字服务器和对应的客户端。服务器将接收客户端发送的消息,并原样返回(Echo)。

  1. 服务器端 (server.c)
    服务器的职责是创建、绑定、监听并接受连接,然后处理数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h> // 包含 sockaddr_un 结构体
#include <unistd.h>#define SOCKET_PATH "/tmp/my_unix_socket" // 服务器将绑定的套接字文件路径
#define BUFFER_SIZE 256int main() {int server_fd, client_fd;struct sockaddr_un server_addr, client_addr;socklen_t client_len;char buffer[BUFFER_SIZE];int bytes_read;// 1. 创建UNIX域套接字 (AF_UNIX, 流式套接字 SOCK_STREAM)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;// 将套接字路径复制到 sun_path 字段strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);// 3. 重要的清理步骤:删除旧的套接字文件(如果存在)// 如果服务器上次异常退出,可能留下这个文件,导致绑定失败unlink(SOCKET_PATH); // 4. 将套接字绑定到指定的文件路径if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind");close(server_fd);exit(EXIT_FAILURE);}// 5. 开始监听连接请求,队列长度为 5if (listen(server_fd, 5) == -1) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}printf("🚀 服务器已启动,监听在 %s...\n", SOCKET_PATH);while (1) {client_len = sizeof(client_addr);// 6. 接受新的客户端连接,这是一个阻塞调用client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd == -1) {perror("accept");continue; // 继续等待下一个连接}printf("🤝 接受到一个新连接。\n");// 7. 与客户端进行数据通信(Echo 功能)while ((bytes_read = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾printf("➡️ 接收到客户端消息: %s", buffer);// 将收到的消息原样回传给客户端if (write(client_fd, buffer, bytes_read) == -1) {perror("write");break; // 写入失败则退出循环}printf("⬅️ 已回复客户端。\n");}if (bytes_read == -1) {perror("read");} else if (bytes_read == 0) {printf("🔴 客户端已关闭连接。\n");}// 8. 关闭与当前客户端的连接close(client_fd);}// 9. 关闭服务器监听套接字,并清理套接字文件(通常在程序退出时执行)close(server_fd);unlink(SOCKET_PATH); return 0;
}
  1. 客户端 (client.c)
    客户端负责创建套接字并连接到服务器,然后发送和接收数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h> // 包含 sockaddr_un 结构体
#include <unistd.h>#define SOCKET_PATH "/tmp/my_unix_socket" // 服务器的套接字文件路径
#define BUFFER_SIZE 256int main() {int client_fd;struct sockaddr_un server_addr;char buffer[BUFFER_SIZE];int bytes_read;// 1. 创建UNIX域套接字client_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (client_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);// 3. 连接到服务器if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect");close(client_fd);exit(EXIT_FAILURE);}printf("✅ 成功连接到服务器。\n");// 4. 与服务器进行数据通信while (1) {printf("请输入消息(输入 'exit' 退出): ");if (fgets(buffer, sizeof(buffer), stdin) == NULL) {break; // 读取失败或EOF}// 检查用户是否输入 'exit'if (strcmp(buffer, "exit\n") == 0) {break;}// 将消息发送给服务器if (write(client_fd, buffer, strlen(buffer)) == -1) {perror("write");break;}// 从服务器接收回复bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);if (bytes_read == -1) {perror("read");break;} else if (bytes_read == 0) {printf("🚫 服务器已关闭连接。\n");break;}buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾printf("⬅️ 收到服务器回复: %s", buffer);}// 5. 关闭客户端套接字close(client_fd);printf("👋 客户端已退出。\n");return 0;
}

🖥️ 编译与运行

在你的 Linux 终端中,按照以下步骤编译和运行:

编译服务器和客户端:

gcc server.c -o server
gcc client.c -o client

启动服务器:
打开一个终端窗口,运行服务器程序。

./server

你应该会看到输出 🚀 服务器已启动,监听在 /tmp/my_unix_socket…

启动客户端:
打开另一个终端窗口,运行客户端程序。

./client

客户端会显示 ✅ 成功连接到服务器。

现在,你可以在客户端终端输入消息,服务器会接收到并将其原样返回给客户端!


🧹 清理注意事项

UNIX 域套接字文件 (/tmp/my_unix_socket 在我们的例子中) 会在服务器绑定时创建。如果服务器非正常退出 (例如,被 Ctrl+C 中断),这个文件可能不会被自动删除。下次你尝试启动服务器时,可能会遇到 “Address already in use” (地址已被占用) 的错误。

这就是为什么在服务器代码中,我们特意加入了 unlink(SOCKET_PATH); 这一行。它确保在绑定新套接字之前,删除任何可能残留的旧套接字文件,从而避免启动失败。

http://www.dtcms.com/a/274214.html

相关文章:

  • 【React Native】样式、网络请求和Loading
  • Hadoop 用户入门指南:驾驭大数据的力量
  • 【React Native】原生组件
  • Dify 1.5.0,1.5.1,1.6.0 新特性
  • 小旺AI截图×英特尔强强联合:AIPC生态开启智能生产力新纪元
  • C++设计秘籍:为什么所有参数都需类型转换时,非成员函数才是王道?
  • 基于强化学习的智能推荐系统优化实践
  • 继续Java的jpackage模块打包Linux可执行程序(包含第三方非模块化包)
  • 4G Cat.1 时代,如何选对 DTU?
  • IoC 是如何为 Spring 的其他核心功能(如 AOP、事务管理)提供基础支持的
  • openpilot:为您的汽车插上智能驾驶的翅膀
  • CV目标检测中的LetterBox操作
  • Swift 解 LeetCode 324:一步步实现摆动排序 II,掌握数组重排的节奏感
  • 使用自然语言体验对话式MySQL数据库运维
  • Claude Code:完爆 Cursor 的编程体验
  • UI前端大数据处理新趋势:基于边缘计算的数据处理与响应
  • 炎热工厂救援:算法打造安全壁垒
  • 对S32K144做的BMS安装快速开发Simulink库及BMS例程介绍
  • 【SpringBoot】 整合MyBatis+Postgresql
  • ROS1学习第三弹
  • uniApp实战五:自定义组件实现便捷选择
  • MyBatis缓存穿透深度解析:从原理到实战解决方案
  • Selenium 自动化实战技巧【selenium】
  • frame 与新窗口切换操作【selenium 】
  • 【JMeter】调试方法
  • Conda 安装与配置详解及常见问题解决
  • 从代码学习深度强化学习 - PPO PyTorch版
  • 番外-linux系统运行.net framework 4.0的项目
  • 《Java EE与中间件》实验三 基于Spring Boot框架的购物车
  • 【flutter】flutter网易云信令 + im + 声网rtm从0实现通话视频文字聊天的踩坑