Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

IPC进程间通信

  • 全称 Inter Process Communication

  • 出现原因

    • 不同进程之间的内存空间是隔离的,就连fork创建的子进程也无法和父进程共享内存空间
    • 但相关的多个进程有交换信息的需求,所以产生了进程间通信
  • 共有6种,下面一一解释

    • 管道
    • FIFO
    • 信号量
    • 共享存储
    • 消息队列
    • socket

管道

  • 通常是指无名管道

  • 特点

    • 只能用于父子或兄弟进程之间
    • 看成一种特殊文件,可以读写,但又只存在内存中
    • 向管道传递数据时,先读的进程会把数据取走
      • 因此,只用1个管道实现双向通信很难
        • 因为进程A写完之后,要等进程B读走再写入,A才能继续执行读取,否则将会读到自己写入的数据
      • 若想轻松实现双向通信,可以创建2个管道
  • 原型

    #include <unistd.h>
    int pipe(int fd[2]);
    
  • 当一个管道建立时,会创建两个文件描述符

    • fd[0]为读而打开
    • fd[1]为写而打开
    • 要关闭管道,只需要关闭这两个文件描述符即可
    • 单个进程创建管道没啥用,所以要fork出子进程来使用
  • 例子

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char* argv[]){
        int fd[2];
        pid_t pid;
        char buff[20];
        
        if (pipe(fd) < 0) {
            printf("Create Pipe Error!\n");
        }
    
        pid = fork();
        if (pid < 0) {
            printf("Fork Error!\n");
        } else if (pid > 0) {
            close(fd[0]); //关掉父进程的读端
            write(fd[1], "hello\n", 12);
        } else {
            close(fd[1]); //关掉子进程的写端
            read(fd[0], buff, 20);
            printf("%s", buff);
        }
        
        return 0;
    }
    

FIFO

  • 也称为命名管道

  • 特点

    • 是一种文件类型,有路径名与之关联
    • 可在无关的进程之间交换数据
  • 原型

    #include <sys/stat.h>
    int mkfifo(const char* pathname, mode_t mode);
    
    • pathname —— 真实的文件路径
    • mode_t —— 文件的读写权限
    • 仅仅是创建了一个FIFO文件
      • 在创建之后,要使用open函数打开,选项可用O_RDONLY读O_WRONLY写
        • 注意不能O_RDWR又读又写,不然会读回自己的输出
      • open函数可以选择追加 | O_NONBLOCK 非堵塞选项
        • 如果没追加,那只有在其他进程以写的方式打开此FIFO,当前进程才会返回
        • 如果追加了,那当前进程无论如何都将返回
  • 例子

    • 服务端:用文件名创建并打开管道,监听有无内容
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/stat.h>
      
      int main(int argc, char* argv[]){
          if (mkfifo("fifo1", 0777) < 0) {
              printf("Create FIFO Failed");
          }
          
          int fd;
          int len;
          char buf[1024];
          
          fd = open("fifo1", O_RDONLY);
          
          while (read(len = read(fd, buf, 1024)) > 0) {
              printf("read msg: %s", buf);
          }
          
          close(fd);
          return 0;
      }
      
    • 客户端:往文件里面写东西
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/stat.h>
      
      int main(int argc, char* argv[]){
          int fd;
          char buf[1024];
          
          fd = open("fifo1", O_WRONLY);
          
          n = sprintf(buf, "pid is", getpid());
          write(fd, buf, n + 1)
          
          close(fd)
          return 0;
      }
      

信号量

  • 跟前面的IPC结构不同,它是一个计数器,用于实现进程的互斥与同步

  • 特点

    • 基于操作系统的PV操作,是原子级别
      • P是获取资源,信号量-1,如果减了后变负数,则挂起等待
      • V是释放资源,信号量+1,如果发现有进程在等待,则唤醒它们
    • 可加减任意数值,不限于1
  • 分类

    • 二值信号量:只能取0或者1
    • 通用信号量:任意正整数
  • 原型

    #include <sys/sem.h>
    
    //创建信号量组,num_sems为信号量的个数,返回信号量组ID
    int semget(key_t key, int num_sems, int sem_flags);
    
    //操作信号量组
    int semop(int semid, struct sembuf semoparray[], size_t numops);  
    
    //控制信号量组
    int semctl(int semid, int sem_num, int cmd, ...);
    
    • sembuf结构
      struct sembuf 
      {
          short sem_num; // 信号量组中对应的序号,0~sem_nums-1
          short sem_op;  // 信号量值在一次操作中的改变量
          short sem_flg; // IPC_NOWAIT, SEM_UNDO
      }
      
      • 解释一下结构中的sem_op
        • 若sem_op > 0,表示进程释放相应资源数,加到信号量上面
        • 若sem_op < 0,请求 sem_op 绝对值的资源
          • 这个操作与sem_flg有关
            • sem_flg 指定IPC_NOWAIT,则出错返回
            • sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,直到如下情况发生
              • 当相应资源数能被满足,就将semncnt值减掉1,让sem_op减掉sem_op 绝对值
              • 或者信号量被删除,则出错返回
        • 若sem_op = 0,进程阻塞直到信号量相应值为0

共享存储

  • 指两个或多个进程共享一个给定的存储区

  • 特点

    • 是最快的IPC,因为直接对内存操作
    • 因为多个进程可能同时操作,所以要进行同步,这里要结合信号量去使用
  • 原型

    #include <sys/shm.h>
    
    //创建共享内存,要指定大小,返回shm_id
    int shmget(key_t key, size_t size, int flag);
    
    //连接共享内存,成功则返回指向共享内存的指针
    void *shmat(int shm_id, const void *addr, int flag);
    
    //断开连接
    int shmdt(void *addr); 
    
    //控制共享内存
    int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
    
    • 解释一下控制函数中的cmd参数
      • 最常用的是IPC_RMID,从系统中删除改共享内存

消息队列

  • 是消息的链接表,存放在内核中,具有独一无二的队列ID

  • 特点

    • 每个消息是一个数据块,允许有不同的类型,但是会有最大的长度限制
    • 独立于发送和接收进程,进程终止时,队列内容不会被删除
    • 可以随机查询,不一定要先进先出
  • 原型

    #include <sys/msg.h>
    
    //创建消息队列,返回队列ID
    int msgget(key_t key, int flag);
    
    //添加消息
    int msgsnd(int msqid, const void *ptr, size_t size, int flag);
    
    //读取消息,可指定特定的type类型(数字)
    int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
    
    //控制消息队列
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    
  • 在下列两种情况下会创建,否则直接返回现有的(这样才能共用而实现通信)

    • 1、没有生成过key对应的队列,且flag含IPC_CREAT
    • 2、key为IPC_PRIVATE

socket

  • 又称套接字,是对TCP/IP、UDP等协议进行封装后形成的API,属于传输层

    • 主要用于跨主机的进程间通信
  • 类型有两种

    • streamSocket,流套接字
      • 基于 TCP协议
      • 采用 流的方式 提供可靠的字节流服务
    • datagramSocket,数据报套接字
      • 基于 UDP协议
      • 采用 数据报文 提供数据打包发送服务
  • 原型

    #include <sys/socket.h>
    
    
  • 创建套接字,返回描述符sockfd

    int socket(int domain, int type, int protocol);
    
    • domain参数——套接字域,常见有两种
      • AF_INET IPv4
      • AF_INET6 IPv6
    • type参数——套接字类型
      • SOCK_STREAM 字节流
      • SOCK_DGRAM 不可靠的报文传递
      • SOCK_SEQPACKET 可靠的报文传递
    • protocol参数——协议
      • 通常是0,表示根据上面两个参数选择唯一协议
  • 绑定IP地址和端口(多用于服务端)

    int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
    
  • 开启监听(多用于服务端)

    int listen(int sockfd, int backlog);
    
  • 建立连接(多用于客户端)

    int connect(int sockfd , const struct sockaddr *addr, socklen_t len);
    
  • 数据传输

    • 发送
      ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
      
    • 接收
      ssize_t recv(int sockdfd, void *buf, size_t nbytes, int flags);
      
  • 读取选项

    int getsockopt(int sockdf, int level, int optname, void* optval, socklen_t* optlen);
    
  • 设置选项

    int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);