管道是最初的Unix IPC形式,它們的最大局限是沒有名字,所以,管道只能用於有親緣關系的進程只見使用。之後,慢慢隨著FIFO的加入,這點才有所改觀。FIFO也成為又名管道。管道和FIFO的共同點就是它們都是通過read和write函數進行訪問的。
管道:
管道時有pipe函數創建,提供一個單路數據流。也就是說,所有的管道都是半雙工。
管道創建方法:
#include
int pipe(int fd[2]);
該函數返回兩個文件描述符:fd[0] (用來打開讀)、fd[1] (用來打開寫)。管道只是它形象的叫法,它的本質實際上就是文件。
單個進程中的管道模式:
一般管道很少只在單個進程中進行使用。管道最常用於兩個不同但有親緣關系的進程(一個父進程,一個子進程。或兩個有共同祖先的進程)中,提供進程間的通信。
進程間通信模式:
只要有親緣關系的兩個進程都可以用管道進行通信。這裡我們用父進程和子進程進行介紹。
首先,我們有主進程創建一個管道後,調用fork()函數派生一個自身的副本。此時主進程將成為父進程,它的副本將成為子進程。完成這些預備操作後,父進程將關閉相應管道的讀出端(fd[0]),子進程將關閉該管道的寫入端(fd[1])。這樣父進程可以通過write函數寫入數據,而子進程通過read函數讀出數據【必須先寫入數據才能讀出】。
進程間雙向數據流:
雙向數據與進程間單向數據流十分相似。只是它是創建了兩個管道。父進程關閉了管道1的讀端口(fd1[0])和管道2的寫端口(fd2[1]),子進程則恰好相反,它關閉的是管道2的讀端口(fd2[0])和管道1的寫端口(fd1[1])。這樣,兩個管道可以保證數據的雙向流動。父進程由管道2進行讀數據,由管道1進行寫數據,而子進程則由管道2寫數據,由管道1讀數據。
下面是進程間雙向數據流的實現代碼:
步驟:
(1)、創建管道1和管道2(利用pipe函數)
(2)、fork一個子進程
(3)、父進程關閉管道1的讀端口(fd1[0])和管道2的寫端口(fd2[1])
(4)、子進程關閉管道1的寫端口(fd1[1])和管道2的讀端口(fd2[1])
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
//分別定義一個字符串數組記錄父進程和子進程所傳數據,最後一個為NULL
char* parent_talk[] = {"Hello",
"can you tell me current data and time?",
"I have to go, Bye",
NULL};
char* child_talk[] = {"Hi",
"No problem:",
"Bye.",NULL};
int fd1[2], fd2[2]; //創建兩個管道
//檢測管道是否創建成功,如果創建成功會返回0,否則返回-1
if(pipe(fd1)<0)
{
printf("create pipe1 error.\n");
exit(1);
}
if(pipe(fd2)<0)
{
printf("create pipe2 error.\n");
exit(1);
}
pid_t pid;
pid = fork(); //fork一個子進程,並將子進程的id號符給父進程的pid
if(pid == 0) //子進程沒有自己的子進程,所以子進程pid = 0
{
char buffer[256];
//關閉子進程需要關閉的端口
close(fd1[1]);
close(fd2[0]);
int i=0;
char *child = child_talk[i];
while(child != NULL)
{ //只要子進程字符串數組不為NULL,就說明通信為及未結束
//從管道1中讀出數據,並打印出來
read(fd1[0],buffer,256);
printf("Parent:>%s\n",buffer);
//給管道2中寫入數據
if(i == 1)
{
time_t t;
time(&t);
sprintf(buffer,"%s%s",child,ctime(&t));
write(fd2[1],buffer,strlen(buffer)+1);
}else{
write(fd2[1],child,strlen(child)+1);
}
i++;
child = child_talk[i];
}
//數據傳輸結束後,關閉所有端口
close(fd1[0]);
close(fd2[1]);
}
//父進程
else if(pid > 0)
{
char buffer[256];
close(fd1[0]);
close(fd2[1]);
int i = 0;
char *parent = parent_talk[i];
//父進程的字符串數組中數據不為NULL時,繼續寫入數據
while(parent != NULL)
{
//將數據寫入管道1
write(fd1[1],parent,strlen(parent)+1);
//從管道2中讀出子進程發送的數據
read(fd2[0],buffer,256);
printf("Child:>%s\n",buffer);
i++;
parent = parent_talk[i];
}
//通信結束後,關閉所有端口
close(fd1[1]);
close(fd2[0]);
//等待子進程結束,然後回收它的空間,防止它成為孤兒進程
int status;
wait(&status);
}
//如果pid不滿足上述條件,則說明fork子進程失敗
else
{
printf("Create child process error!\n");
}
return 0;
}
FIFO(有名管道):
FIFO即先進先出,每個FIFO有一個路徑名與之相關聯,所以它可以實現無親緣關系的進程之間進行通信訪問同一個FIFO。FIFO又稱為又名管道。與管道不同的是,FIFO時有mkfifo函數創建,創建成功則返回0,失敗則返回1。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
其中pathname是一個普通的路徑名,它將是該FIFO的名字;mode則指定文件權限位,一般使用的權限位參數為:O_CREAT|O_EXCL,意思為,它要麼創建一個新的FIFO,要麼返回一個EEXIST(已存在錯誤)。在使用mkfifo函數時,它會檢測是否返回EEXIST錯誤,如果返回該錯誤,則直接調用open函數打開即可。
在創建出一個FIFO後,必須打開讀或寫,但不能同時打開讀和寫,因為它和管道一樣也是半雙工。
對於管道和FIFO而言,write是往末尾添加數據,而read則是從頭部返回數據。
用兩個FIFO實現客戶-服務器:
它的原理和用管道實現雙向數據流相似,它是用FIFO1來進行服務器給客戶端發送數據,而用FIFO2來實現客戶端給服務器傳送數據。
實現程序:
utili.h :頭文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//創建兩個路徑名(mkfifo函數中pathname參數)
const char *write_fifo_name = "write_fifo";
const char *read_fifo_name = "read_fifo";
ser.cpp:服務端程序:
#include"utili.h"
int main()
{
int write_fd;
int read_fd;
//創建一個write_fifo_name的FIFO
int res = mkfifo(write_fifo_name,O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
if(res == -1) //如果返回值為-1,則創建FIFO失敗
{
printf("make write fifo error.\n");
exit(1);
}
//創建成功後,以只寫方式打開write_fifo_name管道
write_fd = open(write_fifo_name,O_WRONLY);
//如果返回-1則表明打開失敗
if(write_fd == -1)
{
printf("open write fifo error.\n");
unlink(write_fifo_name);
exit(1);
}
//打開成功後等待客戶端
printf("Wait Client Connect......\n");
//以只讀方式打開read_fifo_name,並等待客戶端的連接
while((read_fd = open(read_fifo_name, O_RDONLY)) == -1)
{
sleep(1);
}
printf("Client Connect Ok.\n");
定義一個發送數組和接收數組
char sendbuf[256];
char recvbuf[256];
while(1)
{
//服務器從write_fifo_name寫入數據
printf("Ser:>");
scanf("%s",sendbuf);
write(write_fd,sendbuf,strlen(sendbuf)+1);
//服務器從read_fifo_name讀出來自客戶端的數據
read(read_fd,recvbuf,256);
printf("Cli:>%s\n",recvbuf);
}
return 0;
}
cli.cpp:客戶端程序
#include"utili.h"
int main()
{
int write_fd, read_fd;
//創建一個名為read_fifo_name的FIFO,如果創建失敗則返回-1,成功則返回0
int res = mkfifo(read_fifo_name, O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
if(res == -1)
{
printf("make read fifo error.\n");
exit(1);
}
//客戶端以只讀的形式打開write_fifo_name
read_fd = open(write_fifo_name, O_RDONLY);
if(read_fd == -1) //如果返回值為-1則表明打開失敗
{
printf("Server Error.\n");
unlink(read_fifo_name);
exit(1);
}
//客戶端以只寫方式打開read_fifo_name
write_fd = open(read_fifo_name,O_WRONLY);
if(write_fd == -1) //如果返回值為-1,則打開失敗
{
printf("Client Connect Server Error.\n");
exit(1);
}
//定義兩個字符串數組,分別用來存放客戶端發送數據和接收的數據
char sendbuf[256];
char recvbuf[256];
while(1)
{
//客戶端通過write_fifo_name來讀取來自服務器的數據
read(read_fd,recvbuf,256);
printf("Ser:>%s\n",recvbuf);
//客戶端通過write_fifo_name寫入數據
printf("Cli:>");
scanf("%s",sendbuf);
write(write_fd,sendbuf,strlen(sendbuf)+1);
}
return 0;
}