进程间的几种通信方式

1、匿名管道pipe

2、命名管道FIFO

3、XSI IPC

  3.1、消息队列    

  3.2、信号量

  3.3、共享存储

4、网络套接字socket


1、匿名管道pipe

    匿名管道是半双工的,并且只能在具有公共祖先的两个进程之间使用。通常一个管道由一个进程创建,在进程调用fork之后,这个管道就能在父进程和子进程之间使用了。管道创建时会创建两个文件描述符,其中fd[0]为读而打开,fd[1]为写而打开。

#include "apue.h"

int main()
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];
    
    if (pipe(fd) < 0) {
        err_sys("pipe error");
    }
    if ((pid = fork()) < 0) {
        err_sys("fork error");
    }
    else if (pid > 0) {
        close(fd[0]);
        write(fd[1], "hello world\n", 12);
    }
    else {
        close(fd[1]);
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

2、命名管道FIFO

    pipe只能在两个相关的进程之间使用,而命名管道FIFO则可以在两个不相关的进程之间使用,使用的范围更广。 使用FIFO时,首先需要使用mkfifo来创建它,创建之后,可以使用文件的相关函数open、read、write、close等函数像普通文件一样操作这个命名管道。

  • 一般情况下,如果没有指定O_NONBLOCK,只读open要阻塞到某个其他进程为写而打开这个FIFO为止。类似的,只写open要阻塞到某个进程为读而打开它为止。如果指定了O_NONBLOCK,则只读open立即返回。但是,如果没有进程为读而打开一个FIFO,那么open将返回-1,并将errno设置为ENXIO。
  • 若write一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE,若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。

  从FIFO读数据的进程:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"
 
int main(int argc,char** argv)
{
     char buf_r[100];
     int  fd;
     int  nread;
     
     /* 创建FIFO */
     if ((mkfifo(FIFO, O_CREAT|O_EXCL) < 0)&&(errno != EEXIST)) 
     {
        printf("cannot create fifoserver\n");
     }
         
     printf("Preparing for reading bytes...\n");     
     memset(buf_r, 0, sizeof(buf_r));
     fd = open(FIFO, O_RDONLY|O_NONBLOCK, 0);
     if (fd == -1)
     {
         perror("open");
         exit(1);    
     }
     while (1)
     {
         memset(buf_r, 0, sizeof(buf_r));         
         if ((nread = read(fd, buf_r, 100)) == -1) 
         {
             if(errno == EAGAIN)
             {
                printf("no data yet\n");
             }                 
         }
         printf("read %s from FIFO\n", buf_r);
         sleep(1);
     }    
     pause();
     unlink(FIFO);
}

  写消息到FIFO的进程:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_SERVER "/tmp/myfifo"
 
int main(int argc,char** argv)
{
    int fd;
    char w_buf[100];
    int nwrite;
    
    fd = open(FIFO_SERVER, O_WRONLY | O_NONBLOCK, 0);
    if (fd == -1)
    {
        if (errno == ENXIO)
        {
            printf("open error; no reading process\n");
        }            
    }
    
    if (argc == 1)
    {
        printf("Please send something\n");
    }
        
    strcpy(w_buf, argv[1]);
    if ((nwrite = write(fd, w_buf, 100)) == -1)
    {
        if (errno == EAGAIN)
        {
            printf("The FIFO has not been read yet.Please try later\n");
        }            
    }
    else
    {
        printf("write %s to the FIFO\n", w_buf);
    }         
}

3、XSI IPC

    信号量、消息队列、共享存储这种IPC被称作XSI IPC,这三种IPC中,内核中都有一个非负整数的标识符加以引用。标识符是IPC的内部名,为此,每个IPC对象都与一个键(key)相关联,将这个键作为该对象的外部名。可以调用ftok函数,传入路径名和项目ID创建一个键,然后可以在信号量,消息队列和共享存储之间使用。

#include <sys/ipc.h>

key_t ftok(const char *path, int id);

     XSI IPC的几个缺点:

  • XSI IPC没有引用计数,如果进程创建了一个消息队列,放入几则消息然后终止,那么该消息队列及其内容不会被删除。它会一直驻留在系统中直到另外一个进程从该消息队列读消息或者显示删除消息队列。而对于pipe和FIFO,最后一个引用的进程终止时,pipe和FIFO的数据已经被删除了。
  • 这些IPC结构在文件系统中没有名字,不能使用文件系统的函数来访问这些IPC,也不能对它们使用多路转接IO函数(select、poll等)

3.1、消息队列    

#include <sys/msg.h>

// 创建一个消息队列
int msgget(key_t, int flag);

// 将数据放到消息队列中
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);

// 从消息队列中取数据
int msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);

3.2、信号量

    信号量与pipe、FIFO、消息队列同,它时一个计数器,用于多个进程提供对共享数据的访问,即P操作V操作

#include <sys/sem.h>

// 创建信号量
int semget(key_t key, int nsems, int flag);

// 设置信号量的值
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);

// 对信号量进行P操作和V操作
int semop(int semid, struct sembuf semoparray[], size_t nops);

  信号量、记录锁、互斥量的比较

  • 信号量:创建信号量集并且初始化为1,分配资源时sem_op为-1调用semop,释放资源时sem_op为1调用semop。
  • 记录锁:先创建一个空文件,用该文件的第一个字节作为锁字节。分配资源时先对该字节获得一个写锁,释放资源时,对该字节解锁。
  • 互斥量:所有进程将相同的文件映射到他们的地址空间里,在文件的相同偏移处初始化互斥量。分配资源时,对互斥量加锁,为了释放锁,我们解锁互斥量。

3.3、共享存储

    共享存储允许两个或者多个进程共享一个给定的存储区,数据不用在多个进程间复制,因此这是最快的一种IPC。通常信号量用于同步共享存储的访问,但是也可以使用记录锁或者互斥量。

#include <sys/shm.h>

// 获取一个共享存储标识符
int shmget(key_t key, size_t, int flag);

// 对共享存储的操作,包括获取共享存储的属性、删除共享存储等
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// 共享存储连接到进程的地址空间中
void *shmat(int shmid, const void *addr, int flag);

// 从地址空间中解锁共享存储
int shmdt(const void *addr);

4、网络套接字socket

 

上一篇:从0到1之saltstack:基本概念和通信流程


下一篇:从需求去理解 Linux dbus与基于dbus协议的无agent软件管理