進程間通信
—IPC(InterProcess Communication)
進程間通信是不同的進程通過一個或多個文件來傳遞信息。經常用到的通信方式有兩種,一種是通過管道來實現兩個進程間的通信,管道機制是創建的不同進程通過調用管道函數在內核中開闢一塊空間來實現。而還有一種方式就是使用system V標準,來實現不同進程間的通信,下來就淺談一下system
V標準中的第一種通信方式——消息隊列。
一、什麼是消息隊列
消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法。 每個數據塊都被認爲是有一個類型,接收者進程接收的數據塊可以有不同的類型值。我們可以通過發送消息來避免命名管道的同步和阻塞問題。
二、消息隊列的特點
1.消息隊列是消息的鏈表,具有特定的格式,存放在內存中並由消息隊列標識符標識.
2.消息隊列允許一個或多個進程向它寫入與讀取消息.
3.消息隊列的生命週期隨內核。
4.消息隊列可以實現雙向通信。
三、消息隊列的創建,刪除,和屬性控制
1、創建消息隊列
調用的函數msgget()。函數原型 int msgget(key_t key, int msgflg);
第一個參數key:可以認爲是一個端口號,也可以由函數ftok生成。它是一個唯一標識的IPC(相當於身份證號一樣),這裏的key可以有ftok函數調用生成。
第二個參數msgflg:msgflg有兩個標誌,IPC_CREAT和IPC_EXCL。當IPC_CREAT單獨使用的時候,若消息隊列不存在,則創建一支;若存在打開並返回。
下來用代碼來實現
key_t _key = ftok(PATHNAME,PROJ_ID);//ftok參數有兩個,第一個是路徑變量,第二個是projectID
if(_key < 0){
perror("ftok");
return -1;;
}
int msgid = msgget(_key,flags);
if(msgid < 0){
perror("msgget");
return -2;
}
return msgid;
2、刪除消息隊列
刪除所調用的函數是msgctl()。函數原型 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
這裏的msgctl不僅僅是個刪除時用的函數,它是控制型函數。先來看參數,第一個參數msgid一看就是所要刪除的消息隊列ID,第二個參數是IPC_RMID,第三個直接設置爲NULL就行!調用成功返回0,失敗返回-1.
下面用代碼來實現!
if(msgctl(msgid, IPC_RMID, NULL) < 0){
perror("msgctl");
return -1;
}
return 0;
3、消息隊列的屬性控制
既然消息隊列是用來實現進程間通信的,那它必然就會有讀和寫功能。這裏的讀和寫不同,system V是系統提供的第三方接口,和管道不一樣,它的其中之一特點是可以實現雙向通信。
讀寫所調用的函數是msgsnd()和msgrcv
msgsnd將數據放到消息隊列中:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgrcv從隊列中取⽤消息:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int
msgflg);
msqid:消息隊列的標識碼
msgp:指向消息緩衝區的指針,此位置是來暫時存儲發送和接收的消息,是一個用戶可定義的通用結構
msgsz:消息的大小。
msgtyp:從消息隊列內讀取的消息形態。如果值爲零,則表示消息隊列中的所有消息都會被讀取。
函數如果調用成功則返回0,失敗則返回-1;
代碼實現如下:
int sendMsg(int msgid, int type, const char *msg)
{
struct msgbuf _mb;
_mb.mtype = type;
strcpy(_mb.mtext,msg);
if(msgsnd(msgid, &_mb,sizeof(_mb.mtext),0)<0){
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, int type, char *out)
{
struct msgbuf _mb;
if(msgrcv(msgid, &_mb, sizeof(_mb.mtext),type,0)<0){
perror("msgrcv");
return -1;
}
strcpy(out,_mb.mtext);
return 0;
}
接下來就來整體實現一下通過消息隊列兩個進程間的通信,首先消息隊列是調用相關函數來實現不同的功能的,所以可以用接口的封裝來讓消息隊列直接調用。下面是接口封裝函數
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define SERVICE_TYPE 1
#define CLIENT_TYPE 2
struct msgbuf{
long mtype;
char mtext[1024];
};
int creatMsgQueue();
int getMsgQueue();
int destroyMsgQueue(int msgid);
int sendMsg(int msgid, int type, const char *msg);
int recvMsg(int msgid, int type, char *out);
#endif
#include "comm.h"
static int commMsgQueue(int flags)
{
key_t _key = ftok(PATHNAME,PROJ_ID);//ftok參數有兩個,第一個是路徑變量,第二個是projectID
if(_key < 0){
perror("ftok");
return -1;;
}
int msgid = msgget(_key,flags);
if(msgid < 0){
perror("msgget");
return -2;
}
return msgid;
}
int creatMsgQueue()
{
return commMsgQueue(IPC_CREAT | IPC_EXCL|0666);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destroyMsgQueue(int msgid)
{
if(msgctl(msgid, IPC_RMID, NULL) < 0){
perror("msgctl");
return -1;
}
return 0;
}
int sendMsg(int msgid, int type, const char *msg)
{
struct msgbuf _mb;
_mb.mtype = type;
strcpy(_mb.mtext,msg);
if(msgsnd(msgid, &_mb,sizeof(_mb.mtext),0)<0){
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, int type, char *out)
{
struct msgbuf _mb;
if(msgrcv(msgid, &_mb, sizeof(_mb.mtext),type,0)<0){
perror("msgrcv");
return -1;
}
strcpy(out,_mb.mtext);
return 0;
}
接下來就來實現以下不同的進程來調用它了,這point-to-point的方式來實現
#include "comm.h"
int main()
{
int msgid = creatMsgQueue();//service創建消息隊列,client不用繼續創建
char buf[1024];
while(1){
buf[0] = 0;
recvMsg(msgid, CLIENT_TYPE, buf);
printf("client say# %s\n", buf);
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s>0){
buf[s-1] = 0;
sendMsg(msgid, SERVICE_TYPE, buf);//往msgid裏發,發SERVICE_TYPE類型的數據,發BUF
}
}
destroyMsgQueue(msgid);//銷燬消息隊列
return 0;
}
int main()
{
int msgid = getMsgQueue();//service創建消息隊列,client不用繼續創建
char buf[1024];
while(1){
buf[0] = 0;
printf("client Enter#");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s>0){
buf[s-1] = 0;
sendMsg(msgid, CLIENT_TYPE, buf);//往msgid裏發,發SERVICE_TYPE類型的數據,發BUF
}
recvMsg(msgid, SERVICE_TYPE, buf);
printf("server say# %s\n", buf);
}
return 0;
}
當創建一條消息隊列之後,如果想再次使用時,會出錯。因爲在創建消息隊列時用到的是IPC_CREAT和IPC_EXCL
,所以再次創建時會出錯返回。接下來就用要使用一條指令來刪除它
查看系統中的消息隊列的命令:ipcs -q
刪除系統中的消息隊列的命令:ipcrm -q 消息隊列id號