进程通信方式介绍

引言

进程间通信(Inter-Process Communication,IPC)是指在不同进程之间传递数据或信号的机制。由于进程有独立的地址空间,操作系统必须提供专门的机制来实现进程间通信。

1. 本机通信

1.1 管道

管道是最古老的IPC形式,用于父子进程或兄弟进程间的通信。

c 复制代码
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    int fd[2];  // fd[0]: 读端, fd[1]: 写端
    pid_t pid;
    char buffer[100];
    
    // 1. 创建管道
    if (pipe(fd) == -1) {
        perror("pipe failed");
        return 1;
    }
    
    pid = fork();  // 创建子进程
    
    if (pid < 0) {
        perror("fork failed");
        return 1;
    }
    
    if (pid > 0) {  // 父进程
        close(fd[0]);  // 关闭读端
        
        char *message = "Hello from parent!";
        write(fd[1], message, strlen(message) + 1);
        close(fd[1]);
        
        wait(NULL);  // 等待子进程
    } else {  // 子进程
        close(fd[1]);  // 关闭写端
        
        read(fd[0], buffer, sizeof(buffer));
        printf("Child received: %s\n", buffer);
        close(fd[0]);
    }
    
    return 0;
}

管道的特点:

特性 说明
单向通信 数据只能单向流动
亲缘关系 只能用于父子/兄弟进程
字节流 无消息边界
缓存有限 通常 4KB-64KB
生命周期 随进程结束而销毁

1.2 命名管道(FIFO)

命名管道突破了管道只能用于亲缘进程的限制。

c 复制代码
// writer.c
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {
    const char *fifo_path = "/tmp/myfifo";
    
    // 创建命名管道
    mkfifo(fifo_path, 0666);
    
    int fd = open(fifo_path, O_WRONLY);
    write(fd, "Hello FIFO!", 12);
    close(fd);
    
    return 0;
}

// reader.c
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *fifo_path = "/tmp/myfifo";
    char buffer[100];
    
    int fd = open(fifo_path, O_RDONLY);
    read(fd, buffer, sizeof(buffer));
    printf("Received: %s\n", buffer);
    close(fd);
    
    unlink(fifo_path);  // 删除FIFO文件
    return 0;
}

1.2 消息队列(Message Queue)

消息队列是内核维护的消息链表,支持多对多通信。

(note: 这里和后端常用中间件如kafaka等消息队列不一样

c 复制代码
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msg_buffer {
    long msg_type;
    char msg_text[100];
};

int main() {
    key_t key = ftok("progfile", 65);  // 生成key
    int msgid = msgget(key, 0666 | IPC_CREAT);
    
    struct msg_buffer message;
    
    // 发送消息
    message.msg_type = 1;
    strcpy(message.msg_text, "Hello Message Queue!");
    msgsnd(msgid, &message, sizeof(message), 0);
    
    // 接收消息
    msgrcv(msgid, &message, sizeof(message), 1, 0);
    printf("Received: %s\n", message.msg_text);
    
    // 删除消息队列
    msgctl(msgid, IPC_RMID, NULL);
    
    return 0;
}

消息队列特点:

  • 独立于进存在
  • 支持不同类型的消息
  • 异步通信
  • 内核持久化

1.4 共享内存(Shared Memory)

最快的IPC方式,多个进程映射到同一物理内存。

c 复制代码
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>

int main() {
    // 1. 创建共享内存段
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    
    // 2. 附加到进程地址空间
    char *str = (char*)shmat(shmid, NULL, 0);
    
    // 3. 写入数据
    printf("Write Data: ");
    fgets(str, 1024, stdin);
    
    printf("Data written in memory: %s\n", str);
    
    // 4. 分离共享内存
    shmdt(str);
    
    // 5. 另一个进程可以读取
    // 6. 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    
    return 0;
}

1.5 信号量

信号量(Semaphore)用于进程同步,控制对共享资源的访问。

c 复制代码
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    key_t key = ftok("semfile", 65);
    int semid = semget(key, 1, 0666 | IPC_CREAT);
    
    // 初始化信号量为1
    union semun arg;
    arg.val = 1;
    semctl(semid, 0, SETVAL, arg);
    
    pid_t pid = fork();
    
    if (pid == 0) {  // 子进程
        struct sembuf wait_op = {0, -1, SEM_UNDO};  // P操作
        
        printf("Child waiting for semaphore...\n");
        semop(semid, &wait_op, 1);
        printf("Child entered critical section\n");
        
        sleep(2);  // 模拟临界区操作
        
        struct sembuf signal_op = {0, 1, SEM_UNDO};  // V操作
        semop(semid, &signal_op, 1);
        printf("Child left critical section\n");
    } else {  // 父进程
        sleep(1);  // 让子进程先运行
        
        struct sembuf wait_op = {0, -1, SEM_UNDO};
        
        printf("Parent waiting for semaphore...\n");
        semop(semid, &wait_op, 1);
        printf("Parent entered critical section\n");
        
        sleep(1);
        
        struct sembuf signal_op = {0, 1, SEM_UNDO};
        semop(semid, &signal_op, 1);
        printf("Parent left critical section\n");
    }
    
    // 清理
    if (pid > 0) {
        wait(NULL);
        semctl(semid, 0, IPC_RMID);
    }
    
    return 0;
}

1.6 信号(Signal)

软件中断,用于通知进程事件发生。

c 复制代码
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int sig) {
    printf("Received signal %d\n", sig);
}

int main() {
    // 注册信号处理函数
    signal(SIGINT, signal_handler);   // Ctrl+C
    signal(SIGTERM, signal_handler);  // 终止信号
    signal(SIGUSR1, signal_handler);  // 用户自定义信号
    
    printf("Process PID: %d\n", getpid());
    printf("Send signals with: kill -SIGUSR1 %d\n", getpid());
    
    // 发送信号
    raise(SIGUSR1);  // 给自己发送信号
    
    // 等待信号
    while(1) {
        printf("Waiting...\n");
        sleep(1);
    }
    
    return 0;
}
信号 值 描述 默认动作
SIGHUP 1 终端挂起 终止
SIGINT 2 中断(Ctrl+C) 终止
SIGQUIT 3 退出(Ctrl+) 终止+core
SIGKILL 9 强制终止 终止
SIGTERM 15 软件终止 终止
SIGUSR1 10 用户自定义1 终止
SIGUSR2 12 用户自定义2 终止

1.7 套接字(Socket)

最通用的IPC方式,支持网络通信和本地通信。

UNIX域套接字(本地通信)

c 复制代码
// server.c
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[100];
    
    // 创建UNIX域套接字
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    // 绑定地址
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "/tmp/mysocket");
    unlink(server_addr.sun_path);  // 删除已存在的socket文件
    
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    // 监听
    listen(server_fd, 5);
    
    // 接受连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    
    // 接收数据
    read(client_fd, buffer, sizeof(buffer));
    printf("Server received: %s\n", buffer);
    
    // 发送响应
    write(client_fd, "Hello from server!", 19);
    
    close(client_fd);
    close(server_fd);
    unlink("/tmp/mysocket");
    
    return 0;
}

// client.c
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int sock_fd;
    struct sockaddr_un server_addr;
    char buffer[100];
    
    sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "/tmp/mysocket");
    
    connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    write(sock_fd, "Hello from client!", 19);
    read(sock_fd, buffer, sizeof(buffer));
    printf("Client received: %s\n", buffer);
    
    close(sock_fd);
    return 0;
}

网络套接字(跨机器通信)

c 复制代码
// TCP服务器示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);
    
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(server_fd, 10);
    
    printf("Server listening on port 8080...\n");
    
    while(1) {
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        
        int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
        
        char buffer[1024];
        read(client_fd, buffer, sizeof(buffer));
        printf("Received: %s\n", buffer);
        
        char *response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!";
        write(client_fd, response, strlen(response));
        
        close(client_fd);
    }
    
    close(server_fd);
    return 0;
}

1.8 内存映射文件(Memory-Mapped File)

c 复制代码
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *filename = "shared.txt";
    const int size = 4096;
    
    // 打开文件
    int fd = open(filename, O_RDWR | O_CREAT, 0666);
    ftruncate(fd, size);  // 设置文件大小
    
    // 内存映射
    char *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
    // 写入数据
    sprintf(ptr, "Hello from process 1!");
    
    // 同步到磁盘
    msync(ptr, size, MS_SYNC);
    
    // 另一个进程可以读取相同文件
    printf("Data: %s\n", ptr);
    
    // 清理
    munmap(ptr, size);
    close(fd);
    
    return 0;
}

小结

IPC方式 通信方向 进程关系 速度 使用复杂度 数据量 适用场景
管道 单向 亲缘进程 中 低 小 父子进程通信
命名管道 单向 任意进程 中 低 小 持久化进程通信
消息队列 双向 任意进程 中 中 中 结构化消息传递
共享内存 双向 任意进程 最快 高 大 高性能数据共享
信号量 同步 任意进程 快 中 小 进程同步控制
信号 单向 任意进程 快 低 极小 事件通知
套接字 双向 任意进程(可跨网络) 慢 高 大 网络通信/分布式
内存映射 双向 任意进程 快 中 大 文件共享
场景 推荐IPC方式 理由
父子进程简单通信 匿名管道 简单、高效
无亲缘关系进程通信 命名管道/UNIX套接字 支持任意进程
高性能数据共享 共享内存+信号量 最快速度
结构化消息传递 消息队列 支持消息类型
进程同步 信号量 专门用于同步
事件通知 信号 轻量级通知
网络通信 套接字 支持跨机器
文件数据共享 内存映射文件 高效文件访问

2. 现代编程语言中的IPC实现

python 复制代码
# 1. 管道
import os, sys

def pipe_example():
    r, w = os.pipe()
    pid = os.fork()
    
    if pid > 0:  # 父进程
        os.close(r)
        os.write(w, b"Hello from parent")
        os.close(w)
    else:  # 子进程
        os.close(w)
        data = os.read(r, 100)
        print(f"Child received: {data.decode()}")
        os.close(r)

# 2. 命名管道(FIFO)
import os
from multiprocessing import Process

def fifo_writer():
    if not os.path.exists("/tmp/myfifo"):
        os.mkfifo("/tmp/myfifo")
    with open("/tmp/myfifo", "w") as f:
        f.write("Hello FIFO")

def fifo_reader():
    with open("/tmp/myfifo", "r") as f:
        print(f.read())

# 3. 共享内存(multiprocessing模块)
from multiprocessing import Process, Value, Array

def worker(n, arr):
    n.value += 1
    for i in range(len(arr)):
        arr[i] *= 2

if __name__ == '__main__':
    num = Value('i', 0)  # 共享整数
    arr = Array('d', [1.0, 2.0, 3.0])  # 共享数组
    
    p = Process(target=worker, args=(num, arr))
    p.start()
    p.join()
    
    print(num.value)  # 1
    print(arr[:])     # [2.0, 4.0, 6.0]
java 复制代码
// 1. 管道
import java.io.*;

public class PipeExample {
    public static void main(String[] args) throws IOException {
        PipedOutputStream output = new PipedOutputStream();
        PipedInputStream input = new PipedInputStream(output);
        
        new Thread(() -> {
            try {
                output.write("Hello Pipe".getBytes());
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        
        new Thread(() -> {
            try {
                int data;
                while ((data = input.read()) != -1) {
                    System.out.print((char) data);
                }
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

// 2. 内存映射文件
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MappedFileExample {
    public static void main(String[] args) throws Exception {
        RandomAccessFile file = new RandomAccessFile("shared.dat", "rw");
        FileChannel channel = file.getChannel();
        
        // 映射100MB文件到内存
        MappedByteBuffer buffer = channel.map(
            FileChannel.MapMode.READ_WRITE, 0, 100 * 1024 * 1024);
        
        // 写入数据
        buffer.put("Hello Mapped File".getBytes());
        
        // 另一个进程可以读取相同文件
        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        System.out.println(new String(data));
        
        channel.close();
        file.close();
    }
}
go 复制代码
package main

import (
    "fmt"
    "net"
    "os"
    "time"
)

// 1. 管道(Go的管道是channel)
func goPipe() {
    ch := make(chan string)
    
    go func() {
        ch <- "Hello from goroutine"
    }()
    
    msg := <-ch
    fmt.Println(msg)
}

// 2. UNIX域套接字
func unixSocket() {
    socketPath := "/tmp/go.sock"
    
    // 服务器
    go func() {
        os.Remove(socketPath)
        
        l, err := net.Listen("unix", socketPath)
        if err != nil {
            panic(err)
        }
        defer l.Close()
        
        conn, err := l.Accept()
        if err != nil {
            panic(err)
        }
        defer conn.Close()
        
        buf := make([]byte, 512)
        n, err := conn.Read(buf)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Server received: %s\n", buf[:n])
    }()
    
    time.Sleep(time.Second)
    
    // 客户端
    conn, err := net.Dial("unix", socketPath)
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    
    _, err = conn.Write([]byte("Hello UNIX socket"))
    if err != nil {
        panic(err)
    }
}

// 3. 共享内存(通过mmap)
import "golang.org/x/exp/mmap"

func sharedMemory() {
    // 打开内存映射文件
    reader, err := mmap.Open("shared.dat")
    if err != nil {
        panic(err)
    }
    defer reader.Close()
    
    // 读取数据
    data := make([]byte, 100)
    reader.ReadAt(data, 0)
    fmt.Println(string(data))
}
相关推荐
阿钱真强道8 天前
20 嵌入式操作系统 | ubus:把自己的程序状态暴露出去
c语言·进程通信·ubus·ubusd·blobmsg
贾伟康15 天前
【HarmonyOS 7新能力|048】Taihe IPC工程封装:把接入逻辑放进可维护的分层结构
harmonyos·arkts·ipc·分布式通信·taihe
Lucis__22 天前
基于责任链模式的消息队列—异步处理流水线的最佳实践
linux·c++·消息队列·责任链模式·ipc
2601_962296281 个月前
多进程编程入门:快速提升Python程序性能
进程通信·多进程编程·python性能提升·线程命名·pool类
会周易的程序员1 个月前
aiDgeController软PLC控制通讯协议文档
c++·物联网·网关·iot·ipc·进程间通讯
神威难绷泪1 个月前
TCP并发服务器:多进程 多线程 IO多路复用(select poll epoll)
linux·ipc·tcp并发服务器
祖力551 个月前
进程间通信(IPC机制):信号
linux·运维·服务器·进程·进程间通信·多任务·ipc
susplus1 个月前
【linux应用软件编程】进程间的通信方式1【管道&IPC】
linux·进程通信·管道·ipc
祖力551 个月前
进程间通信(IPC机制):无名通道与有名通道
linux·运维·服务器·进程·通信·进程间通信·ipc
微小冷1 个月前
pyqt+matplotlib多进程图像刷新
pyqt·matplotlib·进程通信·多进程·queue·process