Linux IPC 管道:標準流管道,無名管道(PIPE),命名管道(FIFO)

標準流管道

像文件操作有標準 io 流一樣,管道也支持文件流模式。用來創建連接到另一進程的管道,是通過函數 popen 和 pclose。

函數原型:

#include <stdio.h>
FILE* popen(const char* command, const char* open_mode);
int pclose(FILE* fp);

函數popen()

FILE* popen(const char* command, const char* open_mode);

popen允許一個程序將另一個程序作爲新進程來啓動,並可以傳遞數據給它或者通過它接收數據。這裏“另一個進程”是可以執行一定操作的可執行文件,例如用戶執行“ls -l”或者./pipe。由於這類操作很常見,所以將一系列創建過程合併到一個函數popen()中完成,這個函數會完成以下步驟:

https://blog.csdn.net/zzyoucan/article/details/9226599

創建一個管道
fork()一個子進程
在父子進程中關閉不需要的文件描述符
執行exec()函數族調用
執行函數中指定的命令

參數command 字符串是要運行的程序名

參數open_mode 必須是“r”或“w”。

如果 open_mode 是“r”,被調用程序的輸出就可以被調用程序(popen)使用,調用程序利用popen 函數返回的 FILE*文件流指針,就可以通過常用的 stdio 庫函數(如 fread)來讀取被調用程序的輸出。

如果 open_mode 是“w”,調用程序(popen)就可以用 fwrite 向被調用程序發送數據,而被調用程序可以在自己的標準輸入上讀取這些數據。

該函數調用成功返回文件流指針,調用失敗返回-1。

函數pclose()

函數 pclose():用 popen 啓動的進程結束時,我們可以用 pclose 函數關閉與之關聯的文件流。

popen_ls.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("ls","r");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128];
    fread(buf,sizeof(char),sizeof(buf)-1,fp);
    printf("buf=%s\n",buf);
    pclose(fp);
    return 0;
}
buf=add
add.c
a.out
popen_ls.c
popen_r
popen_r.c
popen_w.c
print
print.c

popen_read_print.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("./print","r");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128];
    fgets(buf,sizeof(buf),fp);
    printf("buf=%s\n",buf);
    pclose(fp);
    return 0;
}
print.c
#include <func.h>

int main(int argc, char* argv[])
{
    printf("I am print\n");
    return 0;
}

popen_write_add.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("./add","w");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128]="3 4";
    fputs(buf,fp);
    pclose(fp);
    return 0;
}
add.c
#include <func.h>

int main(int argc, char* argv[])
{
    int i,j;
    scanf("%d %d",&i,&j);
    printf("sum=%d\n",i+j);
    return 0;
}

無名管道PIPE

管道通訊原理

兩個進程之間進行通信,因爲它們擁有各自獨有的進程地址空間,所以必須使兩個進程指向一塊公共內存,而這塊內存就是在內核中開闢出來的緩衝區。

管道的兩端,一端負責輸入,一端負責輸出,管道的兩端分別連接兩個進程。進程1負責將數據輸入到緩衝區,進程2將緩衝區的數據讀出,這樣就實現了兩個進程的通信。

無名管道的特點

1、只能在親緣關係進程間通信(父子或兄弟),無名管道只能由創建進程所訪問。通常情況下,父進程創建一個管道,並使用它來與其子進程進行通信(該子進程由 fork() 來創建)。子進程繼承了父進程的打開文件。由於管道是一種特殊類型的文件,因此子進程也繼承了父進程的管道。

2、半雙工(固定的讀端和固定的寫端),數據只能單向流動。無名管道是單向的,只允許單向通信。如果需要雙向通信,那麼就要採用兩個管道

3、無名管道是一種特殊的文件,不屬於某種文件系統,自己單獨構成,可以用 read、write 等系統調用,並且無名管道只能存在於內存中。

4、管道數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。

函數原型:

#include <unistd.h>
int pipe(int fds[2]);

管道在程序中用一對文件描述符表示, 其中一個文件描述符有可讀屬性, 一個有可寫屬性。fds[0]是讀屬性,fds[1]是寫屬性。

函數 pipe()

用於創建一個無名管道,如果成功,fds[0]存放可讀的文件描述符,fds[1]存放可寫文件描述符,並且函數返回 0,否則返回-1。

通過調用 pipe 獲取這對打開的文件描述符後,一個進程就可以從 fds[0]中讀數據,而另一個進程就可以往 fds[1]中寫數據。當然兩進程間必須有繼承關係,才能繼承這對打開的文件描述符。

管道不是真正的物理文件,不是持久的,即兩進程終止後,管道也自動消失了。
在這裏插入圖片描述
管道兩端的關閉是有先後順序的, 如果先關閉寫端則從另一端讀數據時, read 函數將返
回 0,表示管道已經關閉。

但是如果先關閉讀端,則從另一端寫數據時,將會使寫數據的進程接收到 SIGPIPE 信號,如果寫進程不對該信號進行處理,將導致寫進程終止,如果寫進程處理了該信號,則寫數據的 write 函數返回一個負值,表示管道已經關閉。

SIGPIPE信號

什麼時候會產生在這個信號?

寫管道時,如果管道的讀端被close了話,向管道“寫”數據的進程會被內核發送一個SIGPIPE信號,發這個信號的目的就是想通知你,管道所有的“讀”都被關閉了。

這就好比別人把水管的出口(讀)給堵住了,結果你還一直往裏面灌水(寫),別人跟定會警告你,因爲你這樣可能會對水管造成損害,道理其實是類似的。

由於這個信號的默認動作是終止,所以收到這個信號的進程會被終止,如果你不想被終止的話,你可以忽略、捕獲、或者屏蔽這個信號。

pipe_sigpipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}
int main(void)
{
       int ret = 0;
        int pipefd[2] = {0};

        ret = pipe(pipefd);

        if(ret == -1) print_err("pipe fail");
        ret = fork();
        if(ret > 0)
        {
                signal(SIGPIPE, SIG_IGN);
                close(pipefd[0]);
                while(1)
                {
                        write(pipefd[1], "hello", 5);
                        sleep(1);
                }
        }
        else if(ret == 0)
        {
                close(pipefd[1]);
                close(pipefd[0]);
                while(1)
                {
                        char buf[30] = {0};
                        bzero(buf, sizeof(buf));
                        read(pipefd[0], buf, sizeof(buf));
                        printf("child, recv data:%s\n", buf);
                }
        }
        return 0;
}

父進程嘗試在向管道寫入數據的時候,被內核發送了SIGPIPE信號,父進程收到SIGPIPE信號之後就會被終止。如果不想要終止我們就可以進行忽略或者捕獲操作。使用signal函數:
signal(SIGPIPE,SIG_IGN); 就可以把信號設置爲忽略。

只有當管道所有的讀端都被關閉時,纔會產生這個信號,只有還有一個讀端開着,就不會產生。

SIGPIPE信號引用自:
https://blog.csdn.net/qq_43648751/article/details/104680142

pipe_father_write_son_read.c

創建父子進程,創建無名管道,父寫子讀

#include <func.h>

int main(int argc, char* argv[])
{
    int fds[2];
    //int fds1[2];
    pipe(fds);  //管道的讀端就存在fds[0],寫端存在fds[1]
    if(!fork())
    {
        close(fds[1]);  //關閉寫端,因爲子進程要讀數據
        char buf[128]={0};
        read(fds[0],buf,sizeof(buf));
        printf("I am child gets = %s\n",buf);
        exit(0);
    }
    else
    {
        close(fds[0]);  //關閉讀端,因爲父進程要寫數據
        write(fds[1],"I hen niu",9);
        wait(NULL);
    }
    return 0;
}

命名管道FIFO

無名管道只能在親緣關係的進程間通信大大限制了管道的使用,有名管道突破了這個限制,通過指定路徑名的範式實現不相關進程間的通信。

因爲有文件名,所以進程可以直接調用open函數打開文件,從而得到文件描述符,不需要像無名管道一樣,必須在通過繼承的方式才能獲取到文件描述符。

所以任何兩個進程之間,如果想要通過“有名管道”來通信的話,不管它們是親緣的還是非親緣的,只要調用open函數打開同一個“有名管道”文件,然後對同一個“有名管道文件”進行讀寫操作,即可實現通信。

總之,不管是親緣進程還是非親緣進程,都可以使用有名管道來通信。

創建FIFO文件 mkfifo

創建 FIFO 文件與創建普通文件很類似,只是創建後的文件用於 FIFO。

創建FIFO文件函數原型:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

參數 pathname 爲要創建的 FIFO 文件的全路徑名;
參數 mode 爲文件訪問權限
如果創建成功,則返回 0,否則-1。

create_fifo.c

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])   //演示通過命令行傳遞參數
{
	if(argc != 2){
		puts("Usage: MkFifo.exe {filename}");
		return -1;
	}
	if(mkfifo(argv[1], 0666) == -1){
		perror("mkfifo fail");
		return -2;
	}
	return 0;
}

刪除FIFO文件 unlink

刪除FIFO文件的函數原型爲:

#include <unistd.h>
int unlink(const char *pathname);

delete_fifo.c

#include <func.h>

int main(int argc, char* argv[])
{
    ARGS_CHECK(argc,2);
    int ret;
    ret=unlink(argv[1]);
    ERROR_CHECK(ret,-1,"unlink");
    return 0;
}

創建和刪除FIFO文件

用命令mkfifo創建 不能重複創建,用命令unlink刪除
創建完畢之後,就可以訪問FIFO文件了:一個終端:cat < myfifo;
另一個終端:echo “hello” > myfifo;

打開、關閉 FIFO 文件 open close

對 FIFO 類型的文件的打開/關閉跟普通文件一樣, 都是使用 open 和 close 函數。

如果打開時使用 O_WRONLY 選項,則打開 FIFO 的寫入端,如果使用 O_RDONLY 選項,則打開
FIFO 的讀取端,寫入端和讀取端都可以被幾個進程同時打開。

如果以讀取方式打開 FIFO, 並且還沒有其它進程以寫入方式打開 FIFO, open 函數將被阻塞。同樣,如果以寫入方式打開 FIFO,並且還沒其它進程以讀取方式 FIFO,open 函數也將被阻塞。

與無名管道PIPE 相同,關閉 FIFO 時,如果先關讀端,將導致繼續往 FIFO 中寫數據的進程接收 SIGPIPE 的信號。

讀寫FIFO文件 read write

可以採用與普通文件相同的讀寫方式讀寫 FIFO
先用管道創建命令創建管道MyFifo.pip

fifo_write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
	int fdFifo = open("MyFifo.pip",O_WRONLY); //1. 打開(判斷是否成功打開略)
	write(fdFifo, "hello", 6); //2. 寫
	close(fdFifo); //3. 關閉
	return 0;
}

fifo_read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
	char szBuf[128];
	int fdFifo = open("MyFifo.pip",O_RDONLY); //1. 打開
	if(read(fdFifo,szBuf,sizeof(szBuf)) > 0) //2. 讀
		puts(szBuf);
	close(fdFifo); //3. 關閉
	return 0;
}
gcc –o write write.c
gcc –o read read.c
./write //發現阻塞,要等待執行./read
./read
在屏幕上輸出 hello

有名管道雙向通信

同樣的,使用一個“有名管道”是無法實現雙向通信的,因爲也涉及到搶數據的問題。所以雙向通信時需要兩個管道。

圖解:
在這裏插入圖片描述
關閉端口之後的圖解:
在這裏插入圖片描述
當我們在一個進程裏面既要進行讀,也要進行寫的時候,如果如果沒有數據,就會阻塞在read函數,那麼就會導致write函數不能執行,所以我們給出下面圖解,讓兩個進程的讀寫都不會互相干擾到。

讓程序1和程序2分別創建一個子進程,在第一個程序裏面父進程讀管道2,子進程寫管道1,在另一個程序裏面父進程寫管道2,子進程讀管道1。那麼程序就會併發運行互不干擾,並且在捕獲SININT信號在刪除文件的時候只需要一個進程進行刪除即可。我們把刪除文件的操作放在父進程,子進程在收到SIGINT信號之後只是正常的終止進程。

圖解:
在這裏插入圖片描述
代碼演示:我們對於函數進行封裝,以便於我們創建多個管道文件

文件 name_pipe.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}

int creat_open_fifo(char *fifoname, int open_mode)
{
        int ret = -1;
        int fd = -1;

        ret = mkfifo(fifoname, 0664);
        if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");

        fd = open(fifoname, open_mode);
        if(fd == -1) print_err("open fail");

        return fd;
}

void signal_fun(int signo)
{
        //unlink();
        remove(FIFONAME1);
        remove(FIFONAME2);
        exit(-1);
}

int main(void)
{
        char buf[100] = {0};
        int ret = -1;
        int fd1 = -1;
        int fd2 = -1;


        fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
        fd2 = creat_open_fifo(FIFONAME2, O_RDONLY);

        ret = fork();
        if(ret > 0)
        {
                signal(SIGINT, signal_fun);
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        scanf("%s", buf);
                        write(fd1, buf, sizeof(buf));
                }
        }
        else if(ret == 0)
        {
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        read(fd2, buf, sizeof(buf));
                        printf("%s\n", buf);
                }
        }

        return 0;
}

文件:name_pipe_read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}

int creat_open_fifo(char *fifoname, int open_mode)
{
        int ret = -1;
        int fd = -1;

        ret = mkfifo(fifoname, 0664);

        fd = open(fifoname, open_mode);
        if(fd == -1) print_err("open fail");

        return fd;
}

void signal_fun(int signo)
{
        //unlink();
        remove(FIFONAME1);
        remove(FIFONAME2);
        exit(-1);
}

int main(void)
{
        char buf[100] = {0};
        int ret = -1;
        int fd1 = -1;
        int fd2 = -1;


        fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
        fd2 = creat_open_fifo(FIFONAME2, O_WRONLY);


        ret = fork();
        if(ret > 0)
        {
                signal(SIGINT, signal_fun);
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        read(fd1, buf, sizeof(buf));
                        printf("recv:%s\n", buf);
                }
        }
        else if(ret == 0)
        {
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        scanf("%s", buf);
                        write(fd2, buf, sizeof(buf));

                }
        }

        return 0;
}

有名管道雙向通信內容引用自:
https://blog.csdn.net/qq_43648751/article/details/104724453

管道示例:基於管道的客戶端服務器程序

程序說明:
1、服務器端:
維護服務器管道,接受來自客戶端的請求並處理(本程序爲接受客戶端發來的字符串,將小寫字母轉換爲大寫字母。)然後通過每個客戶端維護的管道發給客戶端。
2. 客戶端
向服務端管道發送數據,然後從自己的客戶端管道中接受服務器返回的數據。

服務器端 server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
typedef struct tagmag
{
	int client_pid ;
	char my_data[512] ;
}MSG;
int main()
{
	int server_fifo_fd , client_fifo_fd;
	char client_fifo[256];
	MSG my_msg ;
	char * pstr ;
	memset(&my_msg , 0 , sizeof(MSG));
	mkfifo("SERVER_FIFO_NAME",0777);
	server_fifo_fd = open("./SERVER_FIFO_NAME",O_RDONLY);
	if(server_fifo_fd == -1)
	{
		perror("server_fifo_fd");
		exit(-1);
	}
	int iret ;
	while( (iret = read(server_fifo_fd , &my_msg ,sizeof(MSG))>0))
	{
		pstr = my_msg.my_data ;
		printf("%s\n",my_msg.my_data);
		while(*pstr != '\0')
		{
			*pstr = toupper(*pstr);
			pstr ++ ;
		}
		memset(client_fifo , 0 , 256);
		sprintf(client_fifo , "CLIENT_FIFO_%d" , my_msg.client_pid);
		client_fifo_fd = open(client_fifo , O_WRONLY);
		if(client_fifo_fd == -1)
		{
			perror("client_fifo_fd");
			exit(-1);
		}
		write(client_fifo_fd , &my_msg, sizeof(MSG));
		printf("%s\n", my_msg.my_data);
		printf("OVER!\n");
		close(client_fifo_fd);
	}
	return 0 ;
}

客戶端代碼:client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
typedef struct tagmag
{
	int client_pid ;
	char my_data[512] ;
}MSG;
int main()
{
	int server_fifo_fd,client_fifo_fd ;
	char client_fifo[256]={0};
	sprintf(client_fifo,"CLIENT_FIFO_%d",getpid());
	MSG my_msg ;
	memset(&my_msg , 0 , sizeof(MSG));
	my_msg.client_pid = getpid();
	server_fifo_fd = open("./SERVER_FIFO_NAME",O_WRONLY);
	mkfifo(client_fifo , 0777);
	while(1)
	{
		int n = read(STDIN_FILENO, my_msg.my_data, 512);
		my_msg.my_data[n] = '\0' ;
		write(server_fifo_fd , &my_msg , sizeof(MSG));
		client_fifo_fd = open(client_fifo , O_RDONLY);
		//memset(&my_msg , 0 , sizeof(MSG));
		n = read(client_fifo_fd, &my_msg , sizeof(MSG));
		my_msg.my_data[n]= 0 ;
		write(STDOUT_FILENO, my_msg.my_data ,strlen(my_msg.my_data) );
		close(client_fifo_fd);
	}
	unlink(client_fifo);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章