最近學習進程通信方式網上查找資料學習,根據自己的理解加上網上的參考資料進行了一些總結,其中有些寫的好的我直接拿來使用了。
進程通信方式有:
無名管道、有名管道、信號、3種系統IPC(信號量、消息隊列、共享內存)、套接字(socket),共7種。
無名管道(pipe):無名管道數據只能單向流動,具有固定的讀端和寫端,而且只能在具有親緣關係的進程間使用。
有名管道(fifo) :有名管道數據只能單向流動,但是它允許無親緣關係進程間的通信。
信號(signal):信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。
信號量(sempore):是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
消息隊列(msgget):就是一個消息的鏈表。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息;對消息隊列有讀權限的進程則可以從消息隊列中讀走消息。
共享內存(share memory):共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
套接字( socket ) :套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。
是否能傳遞消息
其中消息隊列,共享內存,套接字,有名管道,無名管道可以傳遞信息,信號,信號量主要用來同步,不能傳遞消息。
通信範圍
有名管道,共享內存、消息隊列、套接字、信號量可以跨進程,不僅可以跨父子進程,還可以在不相關的進程間進行通信。
無名管道只能在具有親緣關係的進程間進行通信、進程的親緣關係通常是指父子進程關係。
信號是可以跨進程的但必須知道進程號纔可以,大多數發送信號的函數不能跨進程(父子進程都不行),kill(pid_t,sig)函數可以跨進程但必須知道進程號。
使用函數
共享內存:
int shmget(key_t key, int size, int shmflg);//創建共享內存,也可用ftok()函數來創建key_t值。
void *shmat(int shmid, const void *shmaddr, int shmflg);//共享內存映射
int shmdt(const void *shmaddr);取消共享內存映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//操作共享內存標識符,設置、獲取、刪除、內存映射對象
消息隊列:
int msgget(key_t key, int flag);//創建消息隊列,也可用ftok()函數來創建key_t值。
int msgsnd(int msqid, const void *msgp, size_t size, int flag);//發送消息
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);//接收消息
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );//操作消息隊列標識符,設置、獲取、刪除、對象
信號量:
int semget(key_t key, int nsems, int semflg);//創建信號量,也可用ftok()函數來創建key_t值。
int semop ( int semid, struct sembuf *opsptr, size_t nops);//進行V操作,P操作
int semctl ( int semid, int semnum, int cmd…/*union semun arg/);//設置、獲取、刪除信號燈集
信號:
int sigemptyset (sigset_t *set);//清空此信號集
int sigaction (int signum, const struct sigaction *restrict action, struct sigaction *restrict old-
action);//重新定義信號處理函數
sighandler_t signal (int signum, sighandler_t action);//重新定義信號處理函數
unsigned int alarm (unsigned int seconds);發送SIGALRM(鬧鐘信號)不能跨進程
int raise (int signum);給當前進程發送指定的信號。不能跨進程
int kill (pid_t pid, int signum);給進程號爲pid的進程發送指定的信號。可以跨進程
void abort(void);發送結束信號,一般用於結束該進程,因爲該函數總是會成功所以沒有返回值
int pause ();等待知道收到一個信號
有名管道:
int mkfifo (const char *filename, mode_t mode);//創建管道
int open (const char *filename, int flags);//打開管道
ssize_t write (int filedes, const void *buffer, size_t size);//寫入數據
ssize_t read (int filedes, void *buffer, size_t size);//讀取數據
無名管道:
int pipe (int filedes[2]);//創建管道文件,一個讀端filedes[0]和一個寫端filedes[1],這個規則不能變。
ssize_t write (int filedes, const void *buffer, size_t size);//寫入數據,一個寫端filedes[1]
ssize_t read (int filedes, void *buffer, size_t size);//讀取數據,filedes=filedes[0]
無名管道實例:
#include "unistd.h"
#include "stdio.h"
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <sys/stat.h>
#include<sys/types.h>
#include <fcntl.h>
using namespace std;
int main() {
int fd[2];
int ret;
char readbuf[128];
ret = pipe(fd);//創建管道文件
//無名管道,只能用於具有親緣關係的進程之間的通信
//半雙工的通信模式,具有固定的讀端和寫端
if (ret < 0) {
cout << "pipe error\n";
return -1;
}
if (fork() == 0) {//創建子進程
char writebuf[128];
while (1) {
cin >> writebuf;
cin.get();
//向管道中寫入數據時,linux將不保證寫入的原子性
//只有在管道的讀端存在時,向管道中寫入數據纔有意義。
//否則,向管道中寫入數據的進程將收到內核傳來的SIFPIPE信號(通常Broken pipe錯誤)
write(fd[1], writebuf, sizeof(writebuf));
if (strcmp(writebuf, "quit") == 0) {
break;
}
}
close(fd[1]);//關閉寫端
exit(0);//退出子進程
}else
{
while (1) {
//1.管道中的東西,讀完了就刪除了;2.沒有東西可讀,則會阻塞
read(fd[0], readbuf, sizeof(readbuf));
cout << "read:" << readbuf << endl;
if (strcmp(readbuf, "quit") == 0) {
break;
}
}
close(fd[0]);//關閉讀端
}
return 0;
}
有名管道實例:
//寫端
int main()
{
int pfp;
char buf[128];
//linux從命令行上創建管道,使用下面這個命令:mkfifo pipefile
//./表示在當前目錄下創建,pipefile爲管道文件的文件名,0644是文件的權限
// if(mkfifo("./pipefile",0644)<0)//如果已經有文件了,創建失敗,創建一次就可以了
// {
// cout<<"create fifo failed\n";
// return -1;
// }
cout<<"write\n";
pfp = open("./pipefile",O_WRONLY);//以只寫方式打開,注意讀端和寫端打開的文件名字要一樣
while (1) {
cin >> buf;
cin.get();
//向管道中寫入數據時,linux將不保證寫入的原子性
//只有在管道的讀端存在時(不一定是打開的),向管道中寫入數據纔有意義。
//否則,向管道中寫入數據的進程將收到內核傳來的SIFPIPE信號(通常Broken pipe錯誤)
if(write(pfp, buf, sizeof(buf))>0)//會阻塞直到數據被讀取完
{
cout<<"write sucess\n";
}else
{
cout<<"write falied\n";
}
if (strcmp(buf, "quit") == 0) {
break;
}
}
close(pfp);
return 0;
}
//讀端
int main()
{
int pfp;
pfp = open( "./pipefile",O_RDONLY);//以只讀取方式打開,注意讀端和寫端打開的文件名字要一樣
while (1) {
//1.管道中的東西,讀完了就刪除了;2.沒有東西可讀,則會阻塞
if(read(pfp, buf, sizeof(buf))>0)
{
cout << "read:" << buf << endl;
if (strcmp(buf, "quit") == 0) {
break;
}
}
}
close(pfp);
return 0;
}
消息隊列實例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
int main()
{
int msgid;
msgbuf1 buf1,buf2;
//key:和消息隊列關聯的key值,0(IPC_PRIVATE):會建立新的消息隊列,不同進程通信可以根據ftok()函數創建key
/*
int msgid;
int key ;
//ftok根據路徑名,提取文件信息,再根據這些文件信息及project ID合成key.
//該路徑是必須存在的,文件也必須存在,ftok只是根據文件inode在系統內的唯一性來取一個數值,和文件的權限無關。
//proj_id是可以根據自己的約定,隨意設置。這個數字,有的稱之爲project ID; 在UNIX系統上,它的取值是1到255
//誤解,即只要文件的路徑,名稱和子序列號不變,那麼得到的key值永遠就不會變。
//假如存在這樣一種情況:在訪問同一共享內存的多個進程先後調用ftok()時間段中,如果fname指向的文件或者目錄被刪除而且又重新創建,那麼文件系統會賦予這個同名文件新的i節點信息,
//於是這些進程調用的ftok()都能正常返回,但鍵值key卻不一定相同了, 這是一個很重要的問題,希望能謹記!!
key = ftok("./msg.tmp", 0x01 ) ;
if(key<0)
{
printf("get key error\n");
return -1;
}
msgid=msgget(key,0777|IPC_CREAT);
*/ //大於0的32位整數:視參數msgflg來確定操作。通常要求此值來源於ftok返回的IPC鍵值
//flag:消息隊列的訪問權限,0:取消息隊列標識符,若不存在則函數會報錯
//IPC_CREAT:當msgflg&IPC_CREAT爲真時,如果內核中不存在鍵值與key相等的消息隊列,則新建一個消息隊列;如果存在這樣的消息隊列,返回此消息隊列的標識符
//IPC_CREAT|IPC_EXCL:如果內核中不存在鍵值與key相等的消息隊列,則新建一個消息隊列;如果存在這樣的消息隊列則報錯
//成功:消息隊列ID,error:-1
msgid=msgget(IPC_PRIVATE,0666);
// msgget(IPC_PRIVATE,IPC_CREAT|0777);
if(fork()==0)
{
strcpy(buf1.mtext,"hello msg");
buf1.mtype=1L;
//msgid:消息隊列的ID
/*buf1:指向消息的指針。
常用消息結構msgbuf
struct msgbuf{
long mtype;// 消息類型,必須大於0
char mtext[N]; //消息正文,可以是其他任何類型
};
*/
//TEXT_SIZE:發送的消息正文的字節數,不含消息類型佔用的4個字節,
/* IPC_NOWAIT :消息沒有發送完成函數也會立即返回。
0:直到發送完成函數才返回*/
//成功0;出錯:-1
for(int i=0;i<3;i++)
{
msgsnd(msgid,&buf1,TEXT_SIZE,0);
sleep(1);
}
exit(1);
}
//msgid:消息隊列的ID
//buf2:接收消息的緩衝區,結構體類型要與msgsnd函數發送的類型相同
//TEXT_SIZE:要接收的消息的字節數,不含消息類型佔用的4個字節
/* 0:接收消息隊列中第一個消息;
* >0:接收類型等於msgtyp的第一個消息
* 小於0:接收類型等於或者小於msgtyp絕對值的第一個消息
*/
/* 0:若無消息函數會一直阻塞
IPC_NOWAIT:若沒有消息,進程會立即返回ENOMSG。
*/
for(int i=0;i<3;i++)
{
msgrcv(msgid,&buf2,TEXT_SIZE,1L,0);
//成功:接收到的消息的長度,出錯:-1
printf("recv:%s\n",buf2.mtext);
}
int flag;
struct msqid_ds info ;
//msgid:消息隊列標識符
//IPC_STAT:獲得msgid的消息隊列頭數據到buf中
//IPC_RMID:從系統中刪除消息隊列。
/*IPC_SET:設置消息隊列的屬性,要設置的屬性需先存儲在buf中,
可設置的屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
*/
flag = msgctl(msgid, IPC_RMID, &info ) ;//刪除消息隊列。
if ( flag < 0 )
{
perror("get message status error") ;
return -1 ;
}
return 0;
}
共享內存實例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
struct sharedata //定義共享內存傳遞消息的結構體
{
int flag;
char buf[128];
};
int main()
{
int shmid;
void *shm = NULL;
sharedata *sharebuf=NULL;
//有一個特殊的鍵值IPC_PRIVATE, 它用於創建一個只屬於創建進程的共享內存,通常不會用到。
//key_t=ftok("./msg.tmp", 0x01 )
//由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或才能創建一個新的共享內存段。
shmid=shmget((key_t)1234,sizeof(sharedata),0666|IPC_CREAT);
if(shmid<0)
{
printf("shmget error\n");
return -1;
}
//第一次創建共享內存段時,它不能被任何進程訪問。要想啓動對該內存的訪問,
//必須將其連接到一個進程的地址空間。這個工作由shmat函數完成:
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
printf("shmat error\n");
return -1;
}
sharebuf=(sharedata*)shm;
sharebuf->flag=0;//初始化可寫
while(1)
{
if(sharebuf->flag==1)
{
printf("shmdata=%s\n",sharebuf->buf);
if(strncmp(sharebuf->buf,"quit",4)==0)
{
break;
}
sharebuf->flag=0;
}else
{
printf(" no data to read.\n");
sleep(3);
}
}
if(shmdt(shm)<0)
{
printf("shmdt error\n");
return -1;
}
return 0;
}
//讀端
int main()
{
int shmid;
void *shm = NULL;
sharedata *sharebuf=NULL;
//有一個特殊的鍵值IPC_PRIVATE, 它用於創建一個只屬於創建進程的共享內存,通常不會用到。
//key_t=ftok("./msg.tmp", 0x01 )
//由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或才能創建一個新的共享內存段。
shmid=shmget((key_t)1234,sizeof(sharedata),0666|IPC_CREAT);
if(shmid<0)
{
printf("shmget error\n");
return -1;
}
//第一次創建共享內存段時,它不能被任何進程訪問。要想啓動對該內存的訪問,
//必須將其連接到一個進程的地址空間。這個工作由shmat函數完成:
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
printf("shmat error\n");
return -1;
}
sharebuf=(sharedata*)shm;
sharebuf->flag=0;//初始化可寫
while(1)
{
if(sharebuf->flag==0)
{
printf("please write:\n");
fgets(sharebuf->buf,sizeof(sharebuf->buf),stdin);
sharebuf->flag=1;
printf("write shmdata=%s\n",sharebuf->buf);
if(strncmp(sharebuf->buf,"quit",4)==0)
{
break;
}
}else
{
printf("data not read yet!\n");
sleep(1);
}
}
if(shmdt(shm)<0)//取消映射
{
printf("shmdt error\n");
return -1;
}
if(shmctl(shmid,IPC_RMID,NULL)<0)//刪除就只能有一個,讀端和寫端
{
printf("shmctl error\n");
return -1;
}
return 0;
}
信號實例:
#include <signal.h>
#include <sys/types.h>
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//發送信號的四個函數
//int kill(pid_t pid, int sig);
//int raise(int sig);給當前進程發送指定的信號。
//unsigned int alarm(unsigned int seconds);
//在程序運行多少秒之後向所在的進程發送一個鬧鐘信號SIGALRM也就是14號信號。
//void abort(void);用於結束該進程,因爲該函數總是會成功所以沒有返回值
//pause()函數是用於將調用進程掛起直到收到信號爲止。
//要想使接收的進程能收到信號,這個進程不能結束
//sigaction()函數是POSIX(可移植操作系統接口)的信號接口,而signal()是標準C的信號接口;
void sigactionfun(int sig)//自定義信號處理函數
{
cout<<"sigaction recv singal:"<<sig<<endl;
}
int main()
{
struct sigaction sa,oact;
sa.sa_handler = sigactionfun;//指定新的信號處理函數
sigemptyset(&sa.sa_mask); //清空此信號集
sa. sa_flags = 0;
//參數1:signum可以指定SIGKILL和SIGSTOP以外的所有信號。
sigaction(SIGALRM, &sa,&oact);
//原來的處理函數備份到oact裏面,如果不需要重置該給定信號的處理函數爲缺省值oact換成NULL
int i=3;
while(i)
{
i--;
alarm(2);//2s後發送SIGALRM信號,向所在的進程(子進程),不會跨進程
pause();
}
sigaction(SIGALRM, &oact, NULL); //恢復成原始處理方式
alarm(2);//2s後發送SIGALRM信號,向所在的進程,不會跨父子進程
pause();
return 0;
}
int testmain()
{
alarm(3);
signal(SIGALRM,sigactionfun);//SIG_IGN:忽略該信號。SIG_DFL:採用系統默認方式處理信號
int i=3;
while(i)
{
i--;
alarm(3);
pause();
}
return 0;
}
信號量實例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main()
{
pid_t pd;
int semid;
int key;
key=ftok(".",'a');
//和信號燈集關聯的key值
//信號燈集中包含的信號燈數目
//信號燈集的訪問權限,通常爲IPC_CREAT | 0666
//return -1 error,成功:信號燈集ID
semid=semget(key,1,0666|IPC_CREAT);
union semun nsem;
nsem.val=0;//初始值0
//要修改的信號燈編號
//GETVAL:獲取信號燈的值;SETVAL:設置信號燈的值;IPC_RMID:從系統中刪除信號燈集合
//成功:0,eoore:-1
if(semctl(semid,0,SETVAL,nsem)==-1)//設置信號燈的值
{
perror("semctl error\n");
return -1;
}
if(fork()==0)
{
sembuf semb;
semb.sem_num=0;//信號量編號,
semb.sem_op=-1;
//信號操作,-1爲分配資源,P操作;0等待,直到信號燈的值變成0;//1釋放資源,V操作
semb.sem_flg=SEM_UNDO;// 0, IPC_NOWAIT,SEM_UNDO
//SEM_UNDO:在進程沒有釋放信號量而退出時,系統自動釋放該進程中沒釋放的信號量
//信號燈集ID
//要操作的信號燈,struct sembuf
//要操作的信號燈的個數
//success:0;error:-1
if(semop(semid,&semb,1)==-1)//進行p操作
{
perror("semop error\n");
return -1;
}
printf("p\n");
exit(1);
}
sembuf semb;
semb.sem_num=0;//信號量編號,
semb.sem_op=1;//信號操作,+1爲v操作
semb.sem_flg=SEM_UNDO;
//在進程沒有釋放信號量而退出時,系統自動釋放該進程中沒釋放的信號量
if(semop(semid,&semb,1)==-1)//進行v操作
{
perror("semop error\n");
return -1;
}
printf("v\n");
sleep(1);
union semun semn;
//從系統刪除信號量
if(semctl(semid,0,IPC_RMID,semn)==-1)
{
perror("semctl error\n");
return -1;
}
return 0;
}