Linux學習之程序通訊(一)
言之者無罪,聞之者足以戒。 ——《詩序》
ctrl+alt+t 開啟終端
一、程序間通訊和執行緒間通訊:
程序通訊:在使用者空間實現程序通訊是不可能的,通過Linux核心通訊
執行緒通訊:可以在使用者空間就可以實現,可以通過全域性變數通訊
二、通訊方式:
管道通訊:無名管道、有名管道(檔案系統中有名)
訊號通訊:訊號(通知)通訊包括:訊號的傳送、訊號的接收和訊號的處理
IPC(Inyer-Process Communication) 通訊:共享記憶體、訊息佇列和訊號燈。
以上是單機模式下的程序通訊(只有一個Linux核心)
Socket通訊:存在於一個網路中兩個程序之間的通訊(兩個Linux核心)。
三、程序通訊學習思路:
每一種通訊方式都是基於檔案IO的思想
open:功能:建立或開啟程序通訊物件。函式的形式不一樣,有的是多個函式完成
write:功能:向程序通訊物件中寫入內容。函式形式可能不一樣
read:功能:從程序通訊物件中讀取內容。函式形式可能不一樣
close:功能:關閉或刪除程序通訊物件。形式可能不一樣
四、無名管道:
通訊原理:
管道檔案是一個特殊的檔案,是由佇列來實現的。(佇列一端入隊,一端出對)
在檔案IO中建立一個檔案或開啟一個檔案是由open函式來實現的,但open函式不能建立管道檔案。
只能用pipe函式來建立管道。
函式形式:int pipe(int fd[2])
功能:建立管道,為系統呼叫:unistd.h
引數:就是得到的檔案描述符。可見有兩個檔案描述符:fd[0]和fd[1],管道有一個讀端fd[0]用來讀和一個寫端fd[1]用來寫,這個規矩不能改變
返回值:成功是0,出錯是-1
注意:
管道中的東西,讀完了就刪除了,就像佇列中的出對一樣
如果管道中沒有東西可讀,則會出現讀阻塞
如果管道已經被寫滿,還要寫東西就會出現寫阻塞
下面寫一個程式實現:
(1)建立一個管道
(2)向管道中寫資料
(3)讀取管道中的資料
(4)驗證管道的讀阻塞
程式如下:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int fd[2]; int ret; char writebuf[]="hello linux"; char readbuf[128]={0}; ret=pipe(fd); if(ret < 0) { printf("creat pipe failure\n"); return -1; } printf("creat pipe sucess fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]); write(fd[1],writebuf,sizeof(writebuf)); read(fd[0],readbuf,128); printf("readbuf=%s\n",readbuf); //validate read block memset(readbuf,0,128);//clear readbuf read(fd[0],readbuf,128); printf("read again sucess\n"); close(fd[0]); close(fd[1]); return 0; }
命令:ps -axj 可以檢視程序的狀態
下面寫一個程式實現:
(1)建立一個管道
(2)向管道中寫資料
(3)驗證寫阻塞
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
int ret;
int i=0;
char writebuf[]="hello linux";
char readbuf[128]={0};
ret=pipe(fd);
if(ret < 0)
{
printf("creat pipe failure\n");
return -1;
}
printf("creat pipe sucess fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]);
while(i < 5457)
{
write(fd[1],writebuf,sizeof(writebuf));
i++;
}
printf("write is sucess\n");
close(fd[0]);
close(fd[1]);
return 0;
}
通過程式的驗證:我們發現了阻塞空間的大小,當i<=5456時不阻塞,當i>=5457時阻塞
無名管道實現程序通訊:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int fd[2];
int ret;
char process_inter=0;
ret=pipe(fd);
if(ret < 0)
{
printf("creat pipe failure\n");
return -1;
}
printf("creat pipe sucess\n");
pid = fork();
if(pid==0)
{
int i=0;
read(fd[0],&process_inter,1);
while(process_inter==0);
for(i=0;i<5;i++)
{
printf("this is child process i=%d\n",i);
usleep(100);
}
}
if(pid>0)
{
int i=0;
for(i=0;i<5;i++)
{
printf("this is parent process i=%d\n",i);
usleep(100);
}
process_inter=1;
sleep(5);
write(fd[1],&process_inter,1);
}
while(1);
return 0;
}
無名管道的缺點:只能實現父子程序(有親緣關係程序)之間的通訊。
五、有名管道:
正是由於無名管道的這一缺點,我們對無名管道進行改進:有名管道
所謂的有名,即檔案系統中存在這樣一個檔案節點,每一個檔案都有一個inode號,而且這是一個特殊的檔案型別:p管道型別
1、建立這個檔案節點,不可以通過open函式,open函式只能建立普通檔案,不能建立特殊檔案(管道-mkfifo、套接字-socket、字元裝置檔案-mknod、塊裝置檔案-mknod、符號連結檔案-ln -s、目錄檔案mkdir)
2、管道檔案只有inode號,和套接字、字元裝置檔案、塊裝置檔案一樣都不佔用磁碟空間。普通檔案和符號檔案以及目錄檔案,不僅有inode 號,而且還佔用磁碟空間。
3、mkfifo 用來建立管道檔案的節點,沒有在核心中建立管道,只是通過open函式開啟這個檔案時才會在核心空間建立管道
4、mkfifo
函式形式:int mkfifo(const *filename , mode_t mode)
功能:建立管道檔案
引數:管道檔名,許可權,建立的檔案許可權仍然和umask有關係
返回值:建立成功返回0,建立失敗返回-1
下面看一下mkfifo的用法:
include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
ret=mkfifo("./myfifo",0777);
if(ret < 0)
{
printf("creat myfifo failure\n");
return -1;
}
printf("creat myfifo sucess\n");
return 0;
}
下面我們就通過有名管道實現無親緣關係程序間的通訊:
(1)第一步:建立有名管道:
include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
ret=mkfifo("./myfifo",0777);
if(ret < 0)
{
printf("creat myfifo failure\n");
return -1;
}
printf("creat myfifo sucess\n");
return 0;
}
(2)第二步:建立第一個程序:
include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd;
int i;
char process_inter1=0;
fd=open("./myfifo",O_WRONLY);
if(fd < 0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo sucess\n");
for(i=0;i<5;i++)
{
printf("this is first process i=%d\n",i);
usleep(100);
}
process_inter1=1;
sleep(5);
write(fd,&process_inter1,1);
while(1);
return 0;
}
(3)第三步:建立第二個程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd;
int i;
char process_inter2=0;
fd=open("./myfifo",O_RDONLY);
if(fd < 0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo sucess\n");
read(fd,&process_inter2,1);
while(process_inter2==0);
for(i=0;i<5;i++)
{
printf("this is second process i=%d\n",i);
usleep(100);
}
while(1);
return 0;
}
(4)第四步:首先編譯執行第一個程序,緊接著編譯執行第二個程序;當我們執行第一個程序之後不會看到列印資訊,那是因為只有讀寫兩端都被啟動,程序才會被啟動,當我們執行第二個程序之後就可以看到第一個程序的列印資訊,隨後會看到第二個程序的列印資訊(用兩個終端執行兩個程序)。