进程通信方式介绍

引言

进程间通信(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))
}
相关推荐
code monkey.6 天前
【Linux之旅】Linux 进程间通信(IPC)全解析:从管道到共享内存,吃透进程协作核心
linux·c++·ipc
柏木乃一15 天前
进程间通信IPC(1)IPC概述,匿名管道,进程池
linux·服务器·c++·进程间通信·ipc·匿名管道·进程池
好多渔鱼好多23 天前
【IPC】 RTSP Server 如何实现推流
服务器·ipc·rtsp server·rtsp 推流实现
我在人间贩卖青春1 个月前
进程通信之信号量
进程通信·信号量
我在人间贩卖青春1 个月前
进程通信之管道
进程通信·管道·命名管道·无名管道
HalvmånEver1 个月前
Linux:命名管道实现IPC(进程间通信七)
linux·运维·服务器·ipc·命名管道·管道pipe
好多渔鱼好多1 个月前
【IPC】V4L2 数据结构详解
ipc·v4l2·video for linux·ipc摄像头
努力的小帅2 个月前
Linux_进程间通信(Linux入门到精通)
linux·c++·centos·共享内存·进程通信·命名管道·管道的学习
CQ_YM2 个月前
Linux管道通信
linux·c语言·管道·ipc·管道通信