Linux高级编程系列之IPC通信
-
IPC:Inter-Process Communication,进程间通信 -
进程间通信的一些方式
方式 Descriptions Comments 管道-匿名管道 pipe()
只能用于有亲缘关系的进程(父子)
单向字节流、不能双向简单、快
不能跨无关进程
不能双向通信管道-命名管道 mkfifo()
通过文件路径通信
单向(双向需要两个FIFO)可跨无关进程
扩展性差信号Signal kill sigaction
异步通知不用于数据传输,只用于事件 共享内存Shared Memory System V:shmget / shmat
POSIX:shm_open / mmap最快IPC,通常需要配合信号量/mutex 信号量Sempaphore System V:semget
POSIX:sem_open进程同步,不传数据,控制访问共享资源 消息队列Message Queue System V:msgget
POSIX:mq_open有消息边界
适合命令/事件队列套接字Socket Unix Domain Socket(本地IPC):AF_UNIX
网络 Socket:AF_INET/AF_INET6最强、最通用
代码复杂 -
扩展:
mmapeventfdsignalfdtimerfd
用c语言实现命名管道ipc
-
首先要创建一个命名管道,可以直接使用
mkfifo my_pipe命令,也可以使用c代码实现void create_pipe() { if (mkfifo(fifo_path, 0666) == -1) { perror("fail to mkfifo"); exit(1); } printf("create fifo file %s successfully!\n", fifo_path); } -
创建一个读取命名管道文件的进程:
open -> readint fd = open(fifo_path, O_RDONLY); if (fd == -1) { perror("fail to open the fifo file"); return 1; } char buffer[128]; ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1); if (bytesRead > 0) { buffer[bytesRead] = '\0'; printf("Received: %s\n", buffer); } close(fd);read是一个普通的系统调用函数,读取普通文件也是使用该函数,唯一不同的是在该处读取管道文件时如果没有数据将阻塞住。
-
再创建一个写入命名管道文件的进程:
open -> writeint fd = open(fifo_path, O_WRONLY); if (-1 == fd) { perror("fail to open the fifo file"); return 1; } const char* msg = "Hello from the writer!"; write(fd, msg, strlen(msg)); close(fd); printf("write to fifo successfully!\n");write在这里与上面的read一样,如果没有其他进程读取管道的话,这里将阻塞住。
socket方式实现ipc
-
socket方式需要一个
server和client -
这里将使用
dotnet的NamedPipeServerStream来实现socket server- 别看
NamedPipeServerStream中包含了NamedPipe,但是在unix/linux平台下,它的底层是用socket实现的
using System.IO.Pipes; using System.Text; NamedPipeServerStream server = new NamedPipeServerStream("/tmp/test_socket", PipeDirection.InOut); await server.WaitForConnectionAsync(); byte[] buffer = new byte[1024]; int readCnt = await server.ReadAsync(buffer, 0, 1024); string message = Encoding.UTF8.GetString(buffer, 0, readCnt); Console.WriteLine(message); Console.WriteLine("end");dotnet run
- 别看
-
用c语言实现
client#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #define SOCKET_PATH "/tmp/test_socket" int main() { int client_fd; struct sockaddr_un addr; const char *message = "Hello from client!"; char buffer[100]; // 创建 UNIX 域套接字 if ((client_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置服务器的地址 memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); // 连接到服务器 if (connect(client_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) == -1) { perror("connect"); close(client_fd); exit(EXIT_FAILURE); } // 发送数据 write(client_fd, message, strlen(message)); // 接收响应 int n = read(client_fd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; printf("Received: %s\n", buffer); } // 关闭连接 close(client_fd); return 0; } -
sockaddr_in和sockaddr_un的区别?struct sockaddr_un { as_family_t sun_family; // 地址族,通常是 AF_UNIX 占两个字节 char sun_path[108]; // Unix 域套接字路径,最大长度位108字节 }sockaddr_un用于本地机器通信,不通过 **ip **和 端口号 来通信,而是通过 socket 文件来通信,地址族为AF_UNIXsockaddr_in通过ip和端口号来通信,地址族为AF_INET
-
socket(AF_UNIX, SOCK_STREAM, 0):int socket(int domain, int type, int protocol);- 用于创建一个套接字,并返回一个套接字描述符。套接字是用于进程间通信的基本工具,提供了数据传输的接口
domain: 套接字的地址族,决定了通信的方式(如AF_INET表示 IPv4 网络,AF_UNIX表示 Unix 域套接字)。type: 套接字的类型,决定了通信的行为(如SOCK_STREAM表示面向连接的流式套接字,SOCK_DGRAM表示无连接的报文套接字)。protocol: 套接字使用的协议,通常可以设为0,由系统自动选择协议。