IPC 进程间通信 —— 见见猪

inter process communication

文章目录

  • 管道通信
    • 匿名管道
    • 命名管道
      • 写入端
      • 读取端
  • 消息队列
      • 发送端
      • 接收端:
  • 共享内存
      • 写入端
      • 读取端
  • 信号量
        • PV操作
      • 父子进程
  • 信号
      • 父子进程
  • 套接字
      • 服务器
      • 客户端

管道通信

匿名管道

【单进程内创建父子进程通信】

pipe(int[2])

#include 
#include 
#include 

int main() {
    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe创建失败");
        return 1;
    }

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork失败");
        return 1;
    }

    if (pid > 0) {  // 父进程(写端)
        close(fd[0]);  // 关闭读端
        const char* msg = "来自父进程的消息";
        write(fd[1], msg, strlen(msg)+1);
        close(fd[1]);
    } else {         // 子进程(读端)
        close(fd[1]);  // 关闭写端
        char buffer[1024];
        read(fd[0], buffer, sizeof(buffer));
        std::cout << "子进程收到: " << buffer << std::endl;
        close(fd[0]);
    }
    return 0;
}

命名管道

写入端

#include 
#include 
#include 
#include 
#include 

int main() {
    const char* fifo_path = "/tmp/my_fifo";
    
    // 创建FIFO(如果不存在)
    if (mkfifo(fifo_path, 0666) == -1) {
        if(errno != EEXIST) {  // 忽略已存在的错误
            perror("mkfifo失败");
            return 1;
        }
    }

    // 打开FIFO进行写入
    int fd = open(fifo_path, O_WRONLY);
    if (fd == -1) {
        perror("打开FIFO失败");
        return 1;
    }

    const char* msg = "通过命名管道发送的消息";
    write(fd, msg, strlen(msg)+1);
    close(fd);

    std::cout << "消息已写入FIFO" << std::endl;
    return 0;
}

读取端

#include 
#include 
#include 
#include 

int main() {
    const char* fifo_path = "/tmp/my_fifo";
    
    // 打开FIFO进行读取(会阻塞直到有写入端打开)
    int fd = open(fifo_path, O_RDONLY);
    if (fd == -1) {
        perror("打开FIFO失败");
        return 1;
    }

    char buffer[1024];
    read(fd, buffer, sizeof(buffer));
    std::cout << "收到FIFO消息: " << buffer << std::endl;

    close(fd);
    return 0;
}

消息队列

接收端会自动清理消息队列

发送端

msgget()
msgsnd()

#include 
#include 
#include 
#include 

struct Message {
    long type;
    char text[1024];
};

int main() {
    key_t key = ftok("msgqfile", 65);
    int msgid = msgget(key, 0666 | IPC_CREAT);
    
    Message msg;
    msg.type = 1;
    strcpy(msg.text, "通过消息队列发送的消息");

    if (msgsnd(msgid, &msg, sizeof(msg.text), 0) == -1) {
        perror("消息发送失败");
        return 1;
    }
    std::cout << "消息已发送: " << msg.text << std::endl;
    return 0;
}

接收端:

msgget()
msgrcv()
msgctl()

#include 
#include 
#include 

struct Message {
    long type;
    char text[1024];
};

int main() {
    key_t key = ftok("msgqfile", 65);
    int msgid = msgget(key, 0666 | IPC_CREAT);

    Message msg;
    if (msgrcv(msgid, &msg, sizeof(msg.text), 1, 0) == -1) {
        perror("消息接收失败");
        return 1;
    }
    std::cout << "收到消息: " << msg.text << std::endl;

    msgctl(msgid, IPC_RMID, nullptr);  // 清理消息队列
    return 0;
}

共享内存

对于需要频繁地在多个进程间共享大量数据的场景,共享内存是最佳选择。

通过 key 来标识同一个对象,使得不同进程能够在同一系统中通过这个 key 获取同一对象的文件描述符或者其他标识符。

写入端

shmget()
shmat()
shmdt()

#include 
#include 
#include 
#include 

int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    
    char* str = (char*)shmat(shmid, nullptr, 0);
    strcpy(str, "共享内存中的消息");
    std::cout << "写入共享内存: " << str << std::endl;
    
    shmdt(str);
    return 0;
}

读取端

shmget()
shmat()
shmdt()
shmctl()

读取端会主动删除共享内存段
注意实际使用时需要同步机制

#include 
#include 
#include 

int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    
    char* str = (char*)shmat(shmid, nullptr, 0);
    std::cout << "读取共享内存: " << str << std::endl;
    
    shmdt(str);
    shmctl(shmid, IPC_RMID, nullptr);  // 删除共享内存
    return 0;
}

信号量

当需要控制多个进程对共享资源的访问顺序,或者实现进程之间的同步时,信号量是必不可少的工具。

PV操作

P操作(荷兰语单词 “proberen”(尝试)的首字母):通常称为 “wait” 或 “down” 操作。
wait:等待信号量的值变为大于零,然后将其减一。
down:将信号量的值减一,若信号量值为零,则进入等待队列。

V 操作(V 是荷兰语单词 “verhogen”(增加)的首字母):通常称为 “signal” 或 “up” 操作,表示将信号量的值加一。
signal*:表示释放资源,信号量的值加一,并唤醒因 P 操作而被阻塞的进程。
up:将信号量值加一,表示资源的释放。

struct sembuf
semget()
semctl()
semop()

父子进程

(即便不是父子进程,多个进程也可以通过相同的 SEM_KEY 来访问同一个信号量集)

// semaphore_example.cpp
#include 
#include 
#include 
#include 
#include 

#define SEM_KEY 1234  // 信号量的键值

// 定义信号量操作结构体
struct sembuf sem_op;

int main() {
    // 获取信号量的标识符
    int sem_id = semget(SEM_KEY, 1, IPC_CREAT | 0666);
    if (sem_id == -1) {
        perror("semget failed");
        return 1;
    }

    // 初始化信号量
    semctl(sem_id, 0, SETVAL, 1);  // 初始化信号量的值为1

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:等待信号量(P操作)
        sem_op.sem_num = 0;
        sem_op.sem_op = -1;  // P操作,减少信号量
        sem_op.sem_flg = 0;
        semop(sem_id, &sem_op, 1);

        std::cout << "Child process: Critical section accessed\n";

        // 临界区访问完毕,释放信号量(V操作)
        sem_op.sem_op = 1;  // V操作,增加信号量
        semop(sem_id, &sem_op, 1);
    } else {
        // 父进程:等待信号量(P操作)
        sem_op.sem_num = 0;
        sem_op.sem_op = -1;  // P操作
        sem_op.sem_flg = 0;
        semop(sem_id, &sem_op, 1);

        std::cout << "Parent process: Critical section accessed\n";

        // 临界区访问完毕,释放信号量(V操作)
        sem_op.sem_op = 1;  // V操作
        semop(sem_id, &sem_op, 1);
    }

    // 删除信号量
    semctl(sem_id, 0, IPC_RMID);

    return 0;
}

信号

signal()
kill()

父子进程

// signal_example.cpp
#include 
#include 
#include 

void signal_handler(int sig) {
    if (sig == SIGUSR1) {
        std::cout << "Received SIGUSR1 signal in process: " << getpid() << std::endl;
    }
}

int main() {
    signal(SIGUSR1, signal_handler);  // 注册信号处理函数

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:发送信号
        sleep(2);
        std::cout << "Child process: Sending SIGUSR1 to parent process\n";
        kill(getppid(), SIGUSR1);  // 向父进程发送信号
    } else {
        // 父进程:等待信号
        std::cout << "Parent process: Waiting for signal\n";
        pause();  // 等待信号
    }

    return 0;
}

套接字

不同主机之间进程只能通过套接字通信

struct sockaddr_un 【sun_family】【sun_path】
socket(AF_UNIX, SOCK_STREAM, 0)

(SOCK_STREAM:面向连接的套接字类型,表示 TCP 协议。)
这里是:
Unix 域套接字是专门设计用来在同一台机器上运行的进程之间进行通信的。
不需要通过网络栈来进行数据传输,所有的数据传输都发生在内核空间内。这样可以避免网络协议栈的开销,减少了数据传输的延迟。

使用TCP套接字:
struct sockaddr_in(IPv4) 或 struct sockaddr_in6(IPv6)
AF_INET:IPv4 网络协议(如果你要使用 TCP 连接并且是 IPv4 地址)。
AF_INET6:IPv6 网络协议(如果你要使用 TCP 连接并且是 IPv6 地址)。

unlink()
bind()
listen()
accept()
send()


connect()
send()
recv()

服务器

// server.cpp
#include 
#include 
#include 
#include 
#include 

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    // 创建 Unix 域套接字
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        return 1;
    }

    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCKET_PATH);
    unlink(SOCKET_PATH);  // 删除已有的 socket 文件

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("listen");
        return 1;
    }

    std::cout << "Server waiting for connection...\n";

    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("accept");
        return 1;
    }

    std::cout << "Client connected. Waiting for message...\n";

    char buffer[256];
    ssize_t len = read(client_fd, buffer, sizeof(buffer) - 1);
    if (len > 0) {
        buffer[len] = '\0';
        std::cout << "Server received: " << buffer << std::endl;

        // 服务器向客户端发送回应消息
        const char* response = "Message received by server!";
        send(client_fd, response, strlen(response), 0);
    }

    close(client_fd);
    close(server_fd);
    return 0;
}


客户端

// client.cpp
#include 
#include 
#include 
#include 

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int sock;
    struct sockaddr_un addr;

    // 创建 Unix 域套接字
    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        return 1;
    }

    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCKET_PATH);

    // 连接到服务器
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        return 1;
    }

    const char* message = "Hello from client!";
    if (send(sock, message, strlen(message), 0) == -1) {
        perror("send");
        return 1;
    }

    std::cout << "Client sent message: " << message << std::endl;

    close(sock);
    return 0;
}

你可能感兴趣的:(Linux学习,算法,linux,服务器)