【Linux】进程间通信(IPC)

发布于:2024-05-16 ⋅ 阅读:(108) ⋅ 点赞:(0)

        在Linux操作系统中,进程间通信(Inter-Process Communication, IPC)是多任务编程中的核心概念之一。它允许不同进程之间共享数据、传递消息和同步执行,是构建复杂应用程序的基础。本文将深入浅出地介绍Linux下的几种主要IPC机制,并通过实例加以说明,力求让读者对这一主题有全面而深刻的理解。

一、为什么需要进程间通信?

        在多进程环境下,每个进程都有自己的内存空间,彼此隔离。为了实现信息共享或协同工作,就必须借助于特定的通信机制。IPC不仅能够帮助进程之间传递数据,还能协调它们的执行顺序,确保数据的一致性和完整性。

二、Linux下的主要IPC方式

        Linux系统提供了多种进程间通信手段,包括管道、命名管道、信号、消息队列、共享内存、信号量和套接字。下面,我们将逐一探讨这些机制的特点、使用场景及示例代码。

1. 管道(Pipe)

        管道是最简单的IPC方式,用于具有亲缘关系的进程(通常是一个进程创建的子进程)之间的单向数据传输。它分为匿名管道和命名管道两种。

实例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    int fd[2];
    pid_t pid;

    pipe(fd); // 创建管道

    if ((pid = fork()) == 0) { // 子进程写入管道
        close(fd[0]); // 关闭读端
        write(fd[1], "Hello, parent!", 14);
        close(fd[1]);
    } else { // 父进程读取管道
        close(fd[1]); // 关闭写端
        char buf[100];
        read(fd[0], buf, 100);
        printf("Received: %s", buf);
        close(fd[0]);
    }

    return 0;
}
2. 命名管道(Named Pipe, FIFO)

命名管道与管道类似,但它是存在于文件系统中的一种特殊文件,允许无亲缘关系的进程间通信。

实例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>

int main() {
    const char *fifo = "mypipe";
    mkfifo(fifo, 0666); // 创建命名管道

    if (fork() == 0) { // 写进程
        FILE *fp = fopen(fifo, "w");
        fprintf(fp, "Greetings from child!\n");
        fclose(fp);
    } else { // 读进程
        FILE *fp = fopen(fifo, "r");
        char buf[100];
        fgets(buf, 100, fp);
        printf("Parent received: %s", buf);
        fclose(fp);
        unlink(fifo); // 用完后删除命名管道
    }

    return 0;
}
3. 信号(Signal)

信号是一种进程间的异步通信方式,用于通知接收进程某个事件已经发生。常见的信号如SIGINT(Ctrl+C中断)。

实例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

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

int main() {
    signal(SIGINT, signal_handler); // 设置SIGINT信号处理器
    while(1) pause(); // 让进程暂停,等待信号
    return 0;
}
4. 消息队列(Message Queue)

消息队列允许进程发送和接收带有类型标识的消息。消息存储在内核中,直到被接收。

实例

#include <stdio.h>
#include <mqueue.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    mqd_t mq;
    struct mq_attr attr = {0, 10, sizeof(long), 0};

    mq = mq_open("/my_queue", O_CREAT | O_RDWR, 0644, &attr); // 创建消息队列

    if (fork() == 0) { // 发送进程
        for(int i=0; i<5; ++i) {
            long msg = i;
            mq_send(mq, (char *)&msg, sizeof(msg), 0);
            sleep(1);
        }
    } else { // 接收进程
        char buf[sizeof(long)];
        for(int i=0; i<5; ++i) {
            mq_receive(mq, buf, sizeof(buf), NULL);
            printf("Received message: %ld\n", *(long*)buf);
        }
    }

    mq_close(mq);
    mq_unlink("/my_queue"); // 使用完毕后删除消息队列

    return 0;
}
5. 共享内存(Shared Memory)

共享内存允许进程直接访问同一块内存区域,效率高但需要同步机制来避免冲突。

实例

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

int main() {
    key_t key = ftok("shared_memory_example", 65);
    int shmid = shmget(key, 100, 0666 | IPC_CREAT);
    char *data = shmat(shmid, NULL, 0);

    if (fork() == 0) { // 写进程
        strcpy(data, "Hello from child");
    } else { // 读进程
        sleep(1); // 确保写操作完成
        printf("Parent reads: %s\n", data);
    }

    shmdt(data); // 分离共享内存
    shmctl(shmid, IPC_RMID, NULL); // 删除共享内存段

    return 0;
}
6. 信号量(Semaphore)

信号量用于解决多个进程对共享资源的访问控制问题,是一种同步机制。

实例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem;

void *thread_func(void *arg) {
    sem_wait(&sem); // 等待信号量
    printf("Thread says Hello!\n");
    sem_post(&sem); // 释放信号量
    return NULL;
}

int main() {
    sem_init(&sem, 0, 1); // 初始化信号量

    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    sem_wait(&sem); // 主线程等待
    printf("Main thread says Hi!\n");
    sem_post(&sem); // 释放信号量

    pthread_join(tid, NULL);
    sem_destroy(&sem); // 销毁信号量

    return 0;
}
7. 套接字(Socket)

        虽然主要用于网络通信,但套接字同样适用于同一台机器上的进程间通信,支持TCP(面向连接)和UDP(无连接)通信。

实例(简单TCP服务器-客户端通信):

服务器端:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    char buffer[1024] = {0};
    socklen_t addrlen = sizeof(address);

    // 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置socket选项,允许端口复用
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    // 绑定地址到socket
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    valread = read(new_socket, buffer, 1024);
    printf("Client: %s\n", buffer );

    return 0;
}

客户端:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char *message = "Hello from client";
    char buffer[1024] = {0};

    // 创建socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    // 转换IPv4和主机字节序
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    send(sock , message , strlen(message) , 0 );
    printf("Hello message sent\n");

    valread = read(sock , buffer, 1024);
    printf("%s\n",buffer );

    return 0;
}
三、总结

        Linux提供的多样化IPC机制,每种都有其独特的应用场景和优势。开发者应根据实际需求选择最合适的通信方式:简单数据交换可选管道或FIFO;需要消息传递和优先级控制时,消息队列更合适;共享内存适合大量数据快速交换,但需额外同步机制;信号量和互斥锁用于同步访问;套接字则广泛应用于网络编程和跨机器进程通信。理解并灵活运用这些机制,是进行高效多进程编程的关键。