1. 程式人生 > >Linux系統編程之進程間通信

Linux系統編程之進程間通信

col type ces types rcp 用途 pid_t stdio.h 存在


今天我們接著談Linux系統編程中的進程間的通信,上一節我們討論了進程的基本操作。這一節我們來討論一下進程間的通信。
常見的進程間的通信方式有:無名管道、命名管道、信號、共享內存、消息隊列、信號量、套接字。


接下來我們先來談:
一、無名管道:
1、管道是UNIX系統的IPC的最古老方式,並且多數unix系統都提供此種通信方式。、
2、管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
3、雖然管道是半雙工的,但是我們可以通過創建兩個無名管道實現父子進程間的相互通信。我們來看下圖常用的編程模型:

技術分享圖片


技術分享圖片

4、函數原型:

       #include <unistd.h>

       int pipe(int pipefd[2]);

5、上面就是無名管道的一些基本語法,當使用無名管道通信的時候,在父進程創建子進程之前,我們創建的數據都是共享的,所以,通過在fork之前創建管道就可以實現父子進程之間的通信,這裏給出一個測試用例,幫助理解如何使用管道:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    pid_t pid = -1;
    char str[BUFSIZE] = {0};
    
    //先實現單雙工,父寫子讀
    int fd[1];
    int res = -1;

    res = pipe(fd);

    if(-1 == res)
    {
        perror("pipe");
        exit(1);
    }

    pid = fork();

    if(pid > 0)
    {
        //父進程,父寫子讀,就需要在父進程關閉讀端,0讀1寫
        printf("I am Parent\n");
        memset(str,0,BUFSIZE);
        int status = 0;

        //關閉讀端
        close(fd[0]);
        strcpy(str,"Hello World Hello Linux\n");
        //向管道寫數據
       res =  write(fd[1],str,strlen(str));

       if(-1 == res)
       {
           perror("write");
           exit(1);
       }

       res = wait(status);

       if(-1 == res)
       {
           perror("wait ");
           exit(1);
       }
    }
    else if(0 == pid)
    {
        //子進程,父寫子讀,在子進程裏關閉寫端
        printf("I am child\n");

        close(fd[1]);
        memset(str,0,BUFSIZE);

        res = read(fd[0],str,BUFSIZE);
        if(-1 == res )
        {
            perror("resd ");
            exit(1);
        }
        res = write(STDOUT_FILENO,str,strlen(str));
        if(-1 == res)
        {
            perror("write");
            exit(1);
        }
    }

    return 0;
}

上邊給出了半雙工的無名管道的通信方式,所謂全雙工的測試代碼,就是在創建進程之前創建兩個管道,在父子進程中分別關掉一個讀寫端,這樣就可以實現父子進程間通信。雖然這樣實現了父子進程間的通信,我們也能看到一個問題,有名管道只能用到有親緣關系的父子進程間通信。這樣是很不方便的,下來我們來看有名管道:


二、 FIFO(有名管道)

a、基礎概念:

FIFO有時被命名為命名管道。管道只能由相關進程使用,這些相關進程的共同祖先進程創建了管道。通過FIFO不相關的進程也能交換數據。
FIFO是一種文件類型。stat結構成員st_mode的編碼指明文件是否是FIFO類型,可以用S_ISFIFO宏對其進行測試。

b、函數原型:
創建FIFO類似於創建文件。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
一旦已經用了mkfifo創建了一個FIFO,就可用open打開它。其實,一般的文件I/O函數(close、read、write、unlink等)都可以用。
c、FIFO的兩種用途:
1、FIFO由shell命令使用以便將數據從一條管線傳送到另一條,為此無需創建中間臨時文件。
2、FIFO用於客戶進程-服務器進程應用程序中,一再客戶進程和服務器進程之間傳遞數據
d、 實例:
1、FIFO復制輸出流:
FIFO可以復制串行管道命令之間的輸出流,於是也就不需要寫數據到中間磁盤文件中。管道只能用於進程間的線性連接,然而FIFO可以用來非線性連接。
2、客戶-服務器進程
FIFO的另一個應用就是在客戶進程和服務器進程之間傳輸數據。如果有一個服務器進程,它與多個客戶進程都可以將其請求寫到一個該服務器進程創建的眾所周知的FIFO中,這中類型的客戶進程-服務器進程通信中使用FIFO的問題是:服務器如何將回答送回各個客戶進程。
不能使用單個FIFO,因為該服務器進程發出對各個客戶進程的請求響應,而請求者卻不能知道什麽時候去讀才能恰如其分的讀到對它的相應。一種解決方法是每個客戶進程都在其請求中包含它的進程ID。然後服務器進程為每個進程創建一個FIFO,所使用的路徑名是以客戶進程的進程ID為基礎的。

3、下來我們來看具體代碼:

我們用mkfifo實現進程間簡單通信,這裏存在一個進程間同步問題,通俗來講就是當一個進程寫入數據後,另一個進程沒有讀取之前都被自己讀取了,進程間的同步我們到後邊總結,這裏先簡單實現兩個進程間的通信。

/**********************************************************
*    > File Name: mkfifo-ser-1.c
*    > Author:xiao_k
*    > Mail:[email protected] 
*    > Created Time: Wed 21 Feb 2018 09:55:21 PM CST
**********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    char buf[BUFSIZE];
    int res = -1,fd = -1;

   res =  mkfifo("./testfifo",0777);

   if(-1 == res)
   {
       perror("mkfifo ");
       exit(1);
   }

   fd = open("./testfifo",O_RDWR|O_EXCL);
   if(-1 == fd)
   {
       perror("open");
   }

   while(1)
   {
       printf("ser-> ");
       memset(buf,0,BUFSIZE);
       scanf("%s",buf);
       if(0 == strcmp(buf,"quit"))
           break;
       write(fd,buf,strlen(buf));
       sleep(2);
       read(fd,buf,BUFSIZE);
            printf("cli-> %s\n",buf);
   }

   if(open("./testfifo",O_EXCL) > 0 )
   {
        if(-1 == (res = unlink("./testfifo")))
        {
            perror("unlink");
            exit(1);
        }
    }
   else
   {
       perror("open ");
       exit(1);
   }

    return 0;
}

/**********************************************************
 *    > File Name: mkfifo-ser-1.c
 *    > Author:xiao_k
 *    > Mail:[email protected] 
 *    > Created Time: Wed 21 Feb 2018 09:55:21 PM CST
 **********************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define BUFSIZE 1024

int main()
{
    char buf[BUFSIZE];
    int fd = -1,res= -1;


    fd = open("./testfifo",O_RDWR|O_EXCL);

    if(-1 == fd)
    {
        perror("open");
    }

    while(1)
    {
        memset(buf,0,BUFSIZE);
        read(fd,buf,BUFSIZE);
        printf("ser-> %s\n",buf);
        memset(buf,0,BUFSIZE);
        printf("cli->");
        scanf("%s",buf);
        if(0 == strcmp(buf,"quit"))
            break;
        write(fd,buf,strlen(buf));
        sleep(1);

    }

    if(open("./testfifo",O_EXCL) > 0 )
    {
        if(-1 == (res = unlink("./testfifo")))
        {
            perror("unlink");
            exit(1);
        }
    }
    else
    {
        perror("open ");
        exit(1);
    }
    return 0;
}

上邊就是Linux通信的最基本的兩種方式都只是做了最基本的舉例,後幾種我將分成專題分別總結,總結完了所有專題後,將綜合運用。

Linux系統編程之進程間通信