进程间通信

进程间通信

进程间通信(Inter-Process Communication,IPC)是指在不同进程之间进行数据交换和信息传递的机制。常见的通信方式有:管道、消息队列、共享内存、信号量、socket。

管道(Pipe)

无名管道:

特点:只能在具有亲缘关系的进程之间使用(如父子进程)。它是半双工的,即数据只能在一个方向上流动,若要实现双向通信,需要建立两个管道。
示例用法:

     #include <iostream>
     #include <unistd.h>

     int main() {
         int pipefd[2];
         pipe(pipefd);

         if (fork() == 0) {
             // 子进程
             close(pipefd[1]);
             char buffer[100];
             read(pipefd[0], buffer, sizeof(buffer));
             std::cout << "Child received: " << buffer << std::endl;
             close(pipefd[0]);
         } else {
             // 父进程
             close(pipefd[0]);
             write(pipefd[1], "Hello from parent!", 19);
             close(pipefd[1]);
         }

         return 0;
     }

命名管道(FIFO):

特点:可以在不相关的进程之间进行通信。命名管道有一个文件名,可以通过这个文件名进行访问。它也是半双工的,但可以通过建立两个命名管道实现双向通信。
示例用法:

     #include <iostream>
     #include <fcntl.h>
     #include <sys/stat.h>
     #include <unistd.h>

     int main() {
         const char* fifoName = "myfifo";
         mkfifo(fifoName, 0666);

         if (fork() == 0) {
             // 子进程
             int fd = open(fifoName, O_RDONLY);
             char buffer[100];
             read(fd, buffer, sizeof(buffer));
             std::cout << "Child received: " << buffer << std::endl;
             close(fd);
         } else {
             // 父进程
             int fd = open(fifoName, O_WRONLY);
             write(fd, "Hello from parent!", 19);
             close(fd);
         }

         unlink(fifoName);
         return 0;
     }

消息队列(Message Queue)

特点:消息队列是一种独立于发送和接收进程的存储机制。进程可以向消息队列发送消息,也可以从消息队列接收消息。消息队列可以实现异步通信,发送进程不需要等待接收进程的响应。
示例用法:

   #include <iostream>
   #include <sys/ipc.h>
   #include <sys/msg.h>

   struct message {
       long mtype;
       char mtext[100];
   };

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

       if (fork() == 0) {
           // 子进程
           struct message msg;
           msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);
           std::cout << "Child received: " << msg.mtext << std::endl;
           msgctl(msgid, IPC_RMID, nullptr);
       } else {
           // 父进程
           struct message msg;
           msg.mtype = 1;
           strcpy(msg.mtext, "Hello from parent!");
           msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
       }

       return 0;
   }

共享内存(Shared Memory)

特点:共享内存是最快的 IPC 方式之一,因为它允许不同的进程直接访问同一块物理内存区域。这避免了数据在进程之间的复制,提高了通信效率。但是,由于存在多个进程同时访问同一块内存的情况,需要考虑同步机制。而且共享内存在不同操作系统中的实现可能不同,所以使用共享内存的程序在不同平台的可移植性比较差。
示例用法:

   #include <iostream>
   #include <sys/ipc.h>
   #include <sys/shm.h>

   int main() {
       key_t key = ftok("shmfile", 65);
       int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
       char* shmaddr = (char*)shmat(shmid, nullptr, 0);

       if (fork() == 0) {
           // 子进程
           std::cout << "Child read from shared memory: " << shmaddr << std::endl;
           shmdt(shmaddr);
       } else {
           // 父进程
           strcpy(shmaddr, "Hello from parent!");
           wait(nullptr);
           shmdt(shmaddr);
           shmctl(shmid, IPC_RMID, nullptr);
       }

       return 0;
   }

信号量(Semaphore)

特点:信号量主要用于进程间的同步和互斥。它可以控制对共享资源的访问,确保多个进程不会同时访问同一资源而导致数据不一致。
示例用法:

   #include <iostream>
   #include <sys/ipc.h>
   #include <sys/sem.h>

   union semun {
       int val;
       struct semid_ds* buf;
       unsigned short* array;
   };

   void semaphoreWait(int semid) {
       struct sembuf sops = {0, -1, 0};
       semop(semid, &sops, 1);
   }

   void semaphoreSignal(int semid) {
       struct sembuf sops = {0, 1, 0};
       semop(semid, &sops, 1);
   }

   int main() {
       key_t key = ftok("semfile", 65);
       int semid = semget(key, 1, 0666 | IPC_CREAT);
       semun initVal = {1};
       semctl(semid, 0, SETVAL, initVal);

       if (fork() == 0) {
           // 子进程
           semaphoreWait(semid);
           std::cout << "Child accessed shared resource." << std::endl;
           semaphoreSignal(semid);
       } else {
           // 父进程
           semaphoreWait(semid);
           std::cout << "Parent accessed shared resource." << std::endl;
           semaphoreSignal(semid);
       }

       semctl(semid, 0, IPC_RMID);
       return 0;
   }

套接字(Socket)

特点:套接字不仅可以用于同一台机器上不同进程之间的通信,还可以用于不同机器上的进程之间的通信。它支持多种通信协议,如 TCP 和 UDP。
示例用法(基于 TCP 的进程间通信):
服务器端:

     #include <iostream>
     #include <cstring>
     #include <unistd.h>
     #include <arpa/inet.h>

     int main() {
         int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
         if (serverSocket == -1) {
             std::cerr << "Error creating server socket." << std::endl;
             return -1;
         }

         sockaddr_in serverAddress;
         serverAddress.sin_family = AF_INET;
         serverAddress.sin_addr.s_addr = INADDR_ANY;
         serverAddress.sin_port = htons(8888);

         if (bind(serverSocket, reinterpret_cast<struct sockaddr*>(&serverAddress), sizeof(serverAddress)) == -1) {
             std::cerr << "Error binding server socket." << std::endl;
             close(serverSocket);
             return -1;
         }

         if (listen(serverSocket, 5) == -1) {
             std::cerr << "Error listening for connections." << std::endl;
             close(serverSocket);
             return -1;
         }

         int clientSocket = accept(serverSocket, nullptr, nullptr);
         if (clientSocket == -1) {
             std::cerr << "Error accepting client connection." << std::endl;
             close(serverSocket);
             return -1;
         }

         char buffer[1024];
         ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer));
         if (bytesRead > 0) {
             std::cout << "Received from client: " << buffer << std::endl;
         }

         close(clientSocket);
         close(serverSocket);

         return 0;
     }

客户端:

     #include <iostream>
     #include <cstring>
     #include <unistd.h>
     #include <arpa/inet.h>

     int main() {
         int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
         if (clientSocket == -1) {
             std::cerr << "Error creating client socket." << std::endl;
             return -1;
         }

         sockaddr_in serverAddress;
         serverAddress.sin_family = AF_INET;
         serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
         serverAddress.sin_port = htons(8888);

         if (connect(clientSocket, reinterpret_cast<struct sockaddr*>(&serverAddress), sizeof(serverAddress)) == -1) {
             std::cerr << "Error connecting to server." << std::endl;
             close(clientSocket);
             return -1;
         }

         const char* message = "Hello from client!";
         send(clientSocket, message, strlen(message), 0);

         close(clientSocket);

         return 0;
     }

性能对比

管道(Pipe)

性能特点:

  • 无名管道通常比较快速,适用于父子进程之间的简单通信。由于数据在内存中传递,速度相对较快。
  • 命名管道在性能上稍逊于无名管道,因为它涉及到文件系统的操作。但是,命名管道可以在不相关的进程之间使用,提供了更大的灵活性。

适用场景:

  • 无名管道适用于简单的父子进程通信场景,例如在命令行中使用管道将一个命令的输出作为另一个命令的输入。
  • 命名管道适用于需要在不相关进程之间进行通信的场景,例如不同的应用程序之间的通信。

消息队列(Message Queue)

性能特点:

  • 消息队列提供了一种异步通信方式,发送进程不需要等待接收进程的响应。这可以提高系统的并发性。
  • 消息队列的性能取决于消息的大小和数量。较小的消息和较少的消息数量通常会导致更好的性能。
  • 消息队列的操作相对较慢,因为它涉及到内核的干预和系统调用。

适用场景:

  • 消息队列适用于需要异步通信的场景,例如在分布式系统中,不同的节点之间需要进行通信,但不需要立即响应。
  • 消息队列也适用于需要发送大量小消息的场景,例如在日志系统中,不同的进程可以将日志消息发送到消息队列中,然后由一个专门的进程进行处理。

共享内存(Shared Memory)

性能特点:

  • 共享内存是最快的进程间通信方式之一,因为它允许不同的进程直接访问同一块物理内存区域,避免了数据在进程之间的复制。
  • 共享内存的性能取决于内存的访问模式和并发访问的程度。如果多个进程同时访问共享内存,可能会出现竞争条件,需要使用同步机制来确保数据的一致性。
  • 共享内存的操作相对简单,不需要进行系统调用,因此可以提高系统的性能。

适用场景:

  • 共享内存适用于需要高效数据共享的场景,例如在高性能计算中,多个进程需要共享大量的数据。
  • 共享内存也适用于需要快速通信的场景,例如在实时系统中,不同的进程需要快速交换数据。

信号量(Semaphore)

性能特点:

  • 信号量主要用于进程间的同步和互斥,而不是用于数据传输。因此,它的性能主要取决于同步操作的频率和复杂性。
  • 信号量的操作相对较慢,因为它涉及到系统调用和内核的干预。
  • 信号量的性能还受到信号量的初始值和使用方式的影响。如果信号量的初始值设置不当,可能会导致死锁或性能下降。

适用场景:

  • 信号量适用于需要同步和互斥的场景,例如在多线程编程中,不同的线程需要访问共享资源,需要使用信号量来确保数据的一致性。
  • 信号量也适用于需要控制对共享资源的访问的场景,例如在数据库系统中,多个进程需要访问数据库,需要使用信号量来控制并发访问的程度。

套接字(Socket)

性能特点:

  • 套接字可以用于不同机器上的进程之间的通信,因此它的性能受到网络延迟和带宽的限制。
  • 套接字的操作相对较慢,因为它涉及到网络通信和系统调用。
  • 套接字的性能还受到通信协议的选择和使用方式的影响。例如,TCP 协议提供了可靠的通信,但相对较慢;UDP 协议提供了不可靠的通信,但相对较快。

适用场景:

  • 套接字适用于需要在不同机器上的进程之间进行通信的场景,例如在分布式系统中,不同的节点之间需要进行通信。
  • 套接字也适用于需要与外部系统进行通信的场景,例如在网络应用程序中,需要与服务器进行通信。

总体而言,共享内存通常是最快的进程间通信方式,但它需要谨慎使用同步机制来确保数据的一致性。管道和消息队列适用于简单的通信场景,而套接字适用于需要在不同机器上进行通信的场景。信号量主要用于同步和互斥,而不是用于数据传输。在选择进程间通信方式时,需要根据具体的应用场景和性能要求进行权衡。

posted on 2024-08-25 13:14  zc32  阅读(10)  评论(0编辑  收藏  举报

导航