1. 程式人生 > >關於程序間通訊的學習總結

關於程序間通訊的學習總結

linux的程序間通訊:


一、半雙工的管道:
1、半雙工unix管道:
pipe(fd)
程序建立管道時,核心建立兩個檔案描述符。一個用於寫,另一個用於讀。建立管道的程序只能用該管道與自己通訊。
而父程序建立子程序後,子程序會繼承父程序的檔案描述符,也就可以進行和父程序通訊了。(限於父子程序的通訊,而且管道通訊直接與核心打交道)


舉例:
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>


using namespace std;


int main()
{
int fd[2];
pipe(fd); //必須在fork()之前,否則子程序沒法繼承父程序的檔案描述符
pid_t pid;
pid = fork();
if(pid == -1)
cout<<"fork ocur error"<<endl;
if(0 == pid)
{
char str[] = "child connect parent process";


cout<<"This is child process"<<endl;
close(fd[0]);//關閉fd[0]
write(fd[1],str,strlen(str));
exit(0);
}
else
{
char rstr[1024];
memset(rstr,0,1024);
cout<<"This is parent process"<<endl;
close(fd[1]);
read(fd[0],rstr,strlen(rstr));
cout<<rstr<<endl;
}
return 0;
}


2、使用簡便的高階管道:
FILE *fp = popen("str","r/w")

說明:使用popen直接使用管道開啟另一個程序,可以直接使用shell命令。

引數:第一個是執行的操作命令字元創,第二個是操作的流型別(如果是r,返回的是stdin流型別,w返回的是stdout流型別)

舉例:

#include<stdio.h>
#include<string>
int main()
{
string str = "ps -ef |grep -w `whoami`";


char outbuf[1024];
memset(outbuf,0,1024);


FILE* fstdin =NULL;


if(fstdin = popen(str.c_str,"r") == -1);
{
perror("popen ocurs error");
exit(1);
}
else
{
while(fgets(outbuf,1024,fstdin))
{
if(strncmp(outbuf,"oraname",sizeof("oraname"))==0)
{
printf("The value eque :%s",outbuf);
break;
}


}
pclose(fstdin);
exit(0);
}


return 0;
}

3、管道的原子操作
管道的原子操作的最大快取是有限制的,linux的配置在 linux/limits.h中的
#define PIPE_BUF4096


二、命名管道:


1、命名管道與半雙工管道的區別:
在檔案系統中命名管道時以裝置特殊檔案的形式存在的(半雙工是核心直接呼叫的)
在不同家族的程序可以通過命名管道共享資料(也就是不僅僅父子程序)

mknodMYFIFO  p
或者
mkfifo a=rwMYFIFO

在c語言中使用:mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

舉例:
建立命名管道,然後阻塞讀取管道檔案的內容
服務端程序:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<linux/stat.h>


#define FIFO_FILE"MYFIFO"
int main()
{
FILE* fp =NULL;
char readbuf[256];
memset(readbuf,0,256);


umask(0);
mknod(FIFO_FILE,S_IFIFO|0666,0);


while(1)
{
fp = fopen(FIFO_FILE,"r");
if(fp ==NULL)
{
perror("fopen ocurs error");
fclose(fp);
exit(1);
}
fgets(readbuf,256,fp);
printf("service readbuf is: %s",readbuf);
fclose(fp);
}
return 0;
}




客戶端程序:
#include<stdio.h>
#include<stdlib.h>


#define FIFO_FILE"MYFIFO"


int main(int argc,char* argv[])
{
FILE* fd = NULL;
//char writebuf[256];
//memset(writebuf,0,256);


fd = fopen(FIFO_FILE,"w");
if(fd == NULL)
{
perror("open file ocurs error");
exit(1);
}
fputs(argv[1],fd);
fclose(fd);
return 0;
}




三、訊息佇列:核心地址空間中的內部連結串列

1、使用IPC識別符號,此識別符號在訊息佇列中是獨一無二的,這樣通過識別符號就可以確定訪問

2、使用關鍵字key_t,可以獲取IPC識別符號。

(ipcs命令可以顯示當前所有的IPC資訊,ipcrm刪除一個IPC資訊)

一般使用步驟:

a、獲取可用的關鍵字

key_t key = ftok();
(也可以不使用這個函式,我們自己定義一個訊息型別,然後由msgget檢查並建立)
b、獲取關鍵字對應的msgid(識別符號)

int msgget(key_t key,int msgflag)
其中的msgflag主要引數:IPC_CREAT、IPC_EXECL
失敗返回-1
如:
int msgid = msgget(key,IPC_CREAT|0660)

c、向訊息佇列中傳送訊息,這樣記憶體中就有傳送的資料了(一般是傳送的程序)

int msgsnd(int msgid,struct msgbuf* msg,int msgsz,int msgflag)

其中struct msgbuf結構體是我們過載的訊息結構體
msgflag表示是否需要阻塞(0表示忽略,IPC_NOWAIT表示立即中斷,直到可以寫訊息)
如:
msgsnd(msgid,mymsgbuf,sizeof(mymsgbuf),0)

d、從訊息佇列中獲取訊息(一般是接收的程序)

int msgrcv(int msgid,struct msgbuf* msg,int msgsize,long mtype,int msgflag)



常見的幾個重要的msg的struct:

a.訊息的記憶體結構,使用者自己可定義buf
struct msgbuf
{
longmtype;
charmsg_text[1];
};
b.訊息核心處理的基本結構(類似於一個節點):
struct msg
{
struct msg* msg_next;
long msg_type;
char*msg_spot;//訊息體的指標頭
shortmsg_ts;//訊息體的大小
};

c.msgid_ds結構體,存放訊息處理過程的資料(使用msgctl(int msgid,int cmd(IPC_STAT/IPC_SET/IPC_RMID),struct msgid_ds* buf)來獲取,修改,刪除等操作)
struct msgid_ds
{
struct ipc_permmsg_perm;
struct msg*msg_first;
struct msg*msg_last;
time_t msg_stime;
time_t msg_rtime;
time_t msg_ctime;
****
};
d.ipc_perm結構體,描述核心ipc物件的許可資訊。
struct ipc_perm
{
key_tkey;
ushort uid;
ushort gid;
ushort cuid;
ushort cgid;
ushort mode;
ushort seq;
};

舉例:
傳送端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/ipc.h>


#define80
#defineMSGKEY1024


struct mymsgbuf
{
long mtype;
char mtext[MAX_SEND_SIZE];
};


int main()
{
key_t key;
int msgid;
long msgtype;
char msgtext[MAX_SEND_SIZE];



struct mymsgbuf msgbuf;
/*
msgbuf.mtype = 0;
strcpy(msgbuf.mtext,"simple txt");
*/
//key = ftok(".",'m');




msgid = msgget(MSGKEY,IPC_EXECL);
if(msgid < 0)
{
msgid = msgget(MSGKEY,IPC_CREAT|0660);
if(msgid <0)
{
perror("msgget ocurs error");
exit(1);
}
}


while(1)
{
printf("input message type:");
scanf("%d",&msgtype);
if(msgtype = 0)
break;
printf("input message text:");
scanf("%s",msgtext);
msgbuf.mtype = msgtype;
strcpy(msgbuf.mtext,msgtext);


msgsnd(msgid,&msgbuf,sizeof(msgbuf)-sizeof(long),IPC_NOWAIT);
}

msgctl(msgid,IPC_RMID,0);


return 0;
}


接收端:


#include<stdio.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/ipc.h>


#define MAX_RECV_SIZE80
#defineMSGKEY1024


structmymsgbuf
{
longmtype;
charmsgtext[MAX_RECV_SIZE];
};


int main()
{
int msgid;
struct mymsgbufrecvbuf;
int size = sizeof(recvbuf) - sizeof(long);
msgid  = msgget(MSGKEY,IPC_EXECL);
if(msgid <0)
{
msgid= msgget(MSGKEY,IPC_CREAT|0660);
if(msgid <0)
{
perror("client create msg occurs error");
exit(1);
}
}
msgrcv(msgid,&recvbuf,size,0);
printf("msg text is :%s",recvbuf.msgtext);


return 0;
}












四、訊號量:它是一種計數器,用來控制對多個程序共享的資源所進行的訪問


程式設計的步驟與訊息佇列幾乎一致,需要和訊息佇列程序區別:訊息佇列是核心的一個連結串列序列結構,我們將需要傳送和接受核心連結串列的訊息,來進行IPC通訊。
而訊號量是在共享的資源進行控制。

1、步驟:

獲取訊號量的識別符號
int semget(key_t key,int nsems,int semflag) ---很好理解:第一個key值,表示哪個資訊量,第二個是訊號量集合的大小(相當於幾個印表機),第三個獲取訊號量的方式(IPC_CREAT/IPC_EXCL)
當我們使用IPC_EXCL判斷某個訊號量是否存在時,那麼訊號量的大小就沒有意義了。

初始化訊號量的buf內容
unionsemunsem_union;
sem_union.val = 0;//初始化訊號量的值為0
int semctl(int sem_id,0,SETVAL,sem_union)

進行訊號量處理:
struct sembuf sem_b;
sem_b.sem_num =0;
sem_b.sem_op = -1;
sem_b.sem_flg =SEM_UNDO;
int semop(int sem_id,&sem_b,1);

釋放訊號量:
semctl(sem_id,0,IPC_RMID,sem_union)



例項:


#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#include<unistd.h>


#defineDELAY_TIME3


unionsemun
{
int val;
struct semid_ds* buf;
unsigned short*array;
};


intinit_sem(int sem_id,int init_value);
int del_sem(int sem_id);
intsem_p(int sem_id);
int sem_v(int sem_id);


int main()
{
pid_tresult;
intsem_id;
sem_id = semget(ftok(".",'a'),1,0666|IPC_CREAT);
init_sem(sem_id,0);


result = fork();
if(result == -1)
{
perror("fork occurs error\n");
}
else if(result == 0)
{
printf("child process will exec....\n");
sleep(DELAY_TIME);
sem_v(sem_id);
}
else
{
printf("parent process will exec...\n");
sem_p(sem_id);
printf("parent process exec");
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}


intinit_sem(int sem_id,int init_value)
{
unionsemunsem_union;
sem_union.val = init_value;
if(semctl(sem_id,0,SETVAL,sem_union))
{
perror("Initialize semaphore");
return -1;
}
return 0;
}
int del_sem(int sem_id)
{
unionsemunsem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)
{
perror("Delete Semaphore");
return -1;
}
return 0;
}


int sem_p(int sem_id)
{
struct sembufsem_b;
sem_b.sem_num = 0;
sem_b.sem_op  = -1;
sem_b.sem_flg = SEM_UNDO;


if(semop(sem_id,&sem_b,1) == -1)//對一個訊號量程序-1操作
{
perror("p operation");
return -1;
}
return 0;
}


int sem_v(int sem_id)
{
struct sembufsem_b;
sem_b.sem_num =0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;


if(semop(sem_id,&sem_b,1) == -1)
{
perror("V operation");
return -1;
}
return 0;
}




五、共享記憶體
1、概念:記憶體對映的共享區域,是最快的IPC方式,對映的記憶體段可以被某個程序建立,其他程序可以隨意讀隨意寫,但是在寫時最好是用訊號量的方式進行鎖住,防止交叉感染。


2、簡單的幾個記憶體結構:
struct shmid_ds
{
struct ipc_perm shm_perm;
int shm_segsz;
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
unsigned short shm_cpid;//create pid
unsigned short shm_lpid;//pid of last operator
****
}
這個結構體的重要性不用說了,如何獲取或者設定對應的值呢?和前面一樣也是通過shmctl()函式進行處理

這個結構體也就不說了,基本都是共享記憶體的許可資訊
struct ipc_perm
{
}

int shmctl(int shmid,int cmd,struct shmid_ds* buf);

使用的方法:
IPC_STAT 獲取buf的資訊
IPC_SET 設定shmid_ds結構體的ipc_perm的成員值
IPC_RMID 標記shmid用於刪除,此時不會刪除,需要呼叫shmdt(char* shmaddr)來刪除。


相對於前面的幾種,共享記憶體其實是最方便的一個,僅僅在沒有程序競爭讀寫修改時最好。

3、舉例:

建立共享記憶體的程序:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>


#define MAX_SHM_SZIE1024
#define SHM_KEY888
int main()
{
int shm_id;
char* shmadd = NULL;
char  shmstr[64] = "sbdcsb";
//key_t key;
//key = ftok(".",'m');
if(shm_id = shmget(SHM_KEY,MAX_SHM_SZIE,IPC_CREAT|0660|IPC_EXCL) < 0)
{
if(shm_id = shmget(SHM_KEY,MAX_SHM_SZIE,0))
{
perror("shmget occours error");
exit(1);
}
}
shmadd = shmat(shm_id,0,0);//掛接地址系統自己選取,方式預設
if(shmadd == NULL)
{
perror("shmat occours error");
exit(1);
}

//沒有進行共享記憶體的刪除,因為其他程序需要使用
//可以通過判斷shm的記憶體資料來判斷是否需要刪除,
//比如記憶體的第一個字元表示是否刪除標識

shmadd[0] = 'N';//初始化不刪除,後面其他程序可以設定是否刪除


strcpy(shmadd+1,shmstr);//進行記憶體賦值操作
while(1)
{
if(shmadd[0] == 'Y')
{
shmctl(shm_id,RMID,0)
shmdt(shmadd);
break;
}
}


return 0;
}

使用共享記憶體的程序:



#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>


#define MAX_SHM_SZIE1024
#define SHM_KEY888
int main()
{
int shm_id;
char* shmadd = NULL;
char  shmstr[64] = "sbdcsb";
//key_t key;
//key = ftok(".",'m');
if(shm_id = shmget(SHM_KEY,0,IPC_EXCL) < 0)
{
perror("shmget occurs error");
exit(1);
}
shmadd = shmat(shm_id,0,0);//掛接地址系統自己選取,方式預設
if(shmadd == NULL)
{
perror("shmat occours error");
exit(1);
}

//沒有進行共享記憶體的刪除,因為其他程序需要使用
//可以通過判斷shm的記憶體資料來判斷是否需要刪除,
//比如記憶體的第一個字元表示是否刪除標識

printf("shm content is :%s",shmadd+1);


//將記憶體頭設定為Y

sleep(1);
shmadd[0] = 'Y';


return 0;
}