程式間的幾種通訊方式

蓬萊道人發表於2020-12-13

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

 

相關文章