Linux高级编程系列之IPC通信

5 阅读3分钟

Linux高级编程系列之IPC通信

  • IPC: Inter-Process Communication,进程间通信

  • 进程间通信的一些方式

    方式DescriptionsComments
    管道-匿名管道pipe()
    只能用于有亲缘关系的进程(父子)
    单向字节流、不能双向
    简单、快
    不能跨无关进程
    不能双向通信
    管道-命名管道mkfifo()
    通过文件路径通信
    单向(双向需要两个FIFO)
    可跨无关进程
    扩展性差
    信号Signalkill sigaction
    异步通知
    不用于数据传输,只用于事件
    共享内存Shared MemorySystem V: shmget / shmat
    POSIX: shm_open / mmap
    最快IPC,通常需要配合信号量/mutex
    信号量SempaphoreSystem V: semget
    POSIX: sem_open
    进程同步,不传数据,控制访问共享资源
    消息队列Message QueueSystem V: msgget
    POSIX: mq_open
    有消息边界
    适合命令/事件队列
    套接字SocketUnix Domain Socket(本地IPC)AF_UNIX
    网络 Socket:
    AF_INET/AF_INET6
    最强、最通用
    代码复杂
  • 扩展:

    • mmap
    • eventfd
    • signalfd
    • timerfd

用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 -> read

    int 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 -> write

    int 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方式需要一个 serverclient

  • 这里将使用dotnetNamedPipeServerStream 来实现 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_insockaddr_un 的区别?

    struct sockaddr_un 
    {
        as_family_t sun_family; // 地址族,通常是 AF_UNIX 占两个字节
        char sun_path[108]; // Unix 域套接字路径,最大长度位108字节
    }
    
    • sockaddr_un 用于本地机器通信,不通过 **ip **和 端口号 来通信,而是通过 socket 文件来通信,地址族为 AF_UNIX
    • sockaddr_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,由系统自动选择协议。