【Linux】程序間通訊之訊息佇列
在上一篇部落格裡,我們學習了程序間通訊的一種方式,那就是管道,今天我們繼續學習另一種方式訊息佇列。
訊息佇列
一. 什麼是訊息佇列?
訊息佇列是訊息的連結串列,存放在核心中並由訊息佇列識別符號表示。
訊息佇列提供了一個從一個程序向另一個程序傳送資料塊的方法,每個資料塊都可以被認為是有一個型別,接受者接受的資料塊可以有不同的型別。
但是同管道類似,它有一個不足就是每個訊息的最大長度是有上限的(MSGMAX),每個訊息佇列的總的位元組數(MSGMNB),系統上訊息佇列的總數上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax檢視具體的資料。
核心為每個IPC物件維護了一個數據結構struct ipc_perm,用於標識訊息佇列,讓程序知道當前操作的是哪個訊息佇列。每一個msqid_ds表示一個訊息佇列,並通過msqid_ds.msg_first、msg_last維護一個先進先出的msg連結串列佇列,當傳送一個訊息到該訊息佇列時,把傳送的訊息構造成一個msg的結構物件,並新增到msqid_ds.msg_first、msg_last維護的連結串列佇列。在核心中的表示如下:
二. 特點:
- 生命週期隨核心,訊息佇列會一直存在,需要我們顯示的呼叫介面刪除或使用命令刪除
- 訊息佇列可以雙向通訊
- 克服了管道只能承載無格式位元組流的缺點
三. 訊息佇列函式
1.msgget
功能:建立和訪問一個訊息佇列
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflag);
引數:
key:某個訊息佇列的名字,用ftok()產生
msgflag:有兩個選項IPC_CREAT和IPC_EXCL,單獨使用IPC_CREAT,如果訊息佇列不存在則建立之,如果存在則開啟返回;單獨使用IPC_EXCL是沒有意義的;兩個同時使用,如果訊息佇列不存在則建立之,如果存在則出錯返回。
返回值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
呼叫成功返回一個key值,用於建立訊息佇列,如果失敗,返回-1
2.msgctl
功能:訊息佇列的控制函式
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
引數:
msqid:由msgget函式返回的訊息佇列標識碼
cmd:有三個可選的值,在此我們使用IPC_RMID
- IPC_STAT 把msqid_ds結構中的資料設定為訊息佇列的當前關聯值
- IPC_SET 在程序有足夠許可權的前提下,把訊息佇列的當前關聯值設定為msqid_ds資料結構中給出的值
- IPC_RMID 刪除訊息佇列
返回值:
成功返回0,失敗返回-1
3.msgsnd
功能:把一條訊息新增到訊息佇列中
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
引數:
msgid:由msgget函式返回的訊息佇列標識碼
msgp:指標指向準備傳送的訊息
msgze:msgp指向的訊息的長度(不包括訊息型別的long int長整型)
msgflg:預設為0
返回值:成功返回0,失敗返回-1
訊息結構一方面必須小於系統規定的上限,另一方面必須以一個long int長整型開始,接受者以此來確定訊息的型別
struct msgbuf
{
long mtye;
char mtext[1];
};
4.msgrcv
功能:是從一個訊息佇列接受訊息
原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
引數:與msgsnd相同
返回值:成功返回實際放到接收緩衝區裡去的字元個數,失敗返回-1
此外,我們還需要學習兩個重要的命令
前面我們說過,訊息佇列需要手動刪除IPC資源
ipcs:顯示IPC資源
ipcrm:手動刪除IPC資源
下面用程式碼模擬實現client與server之間的通訊
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype;
char mtext[1024];
};
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
int createMsgQueue();
int getMsgQueue();
int destoryMsgQueue(int msg_id);
int sendMsgQueue(int msg_id, int who, char* msg);
int recvMsgQueue(int msg_id, int recvType, char out[]);
#endif
comm.c
#include "comm.h"
static int commMsgQueue(int flags)
{
key_t key = ftok("/tmp", 0x6666);
if(key < 0)
{
perror("ftok");
return -1;
}
int msg_id = msgget(key, flags);
if(msg_id < 0)
{
perror("msgget");
}
return msg_id;
}
int createMsgQueue()
{
return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destoryMsgQueue(int msg_id)
{
if(msgctl(msg_id, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsgQueue(int msg_id, int who, char* msg)
{
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext, msg);
if(msgsnd(msg_id, (void*)&buf, sizeof(buf.mtext), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsgQueue(int msg_id, int recvType, char out[])
{
struct msgbuf buf;
int size=sizeof(buf.mtext);
if(msgrcv(msg_id, (void*)&buf, size, recvType, 0) < 0)
{
perror("msgrcv");
return -1;
}
strncpy(out, buf.mtext, size);
out[size] = 0;
return 0;
}
server.c
#include "comm.h"
int main()
{
int msgid = createMsgQueue();
char buf[1024] = {0};
while(1)
{
recvMsgQueue(msgid, CLIENT_TYPE, buf);
if(strcasecmp("quit", buf) == 0)
break;
printf("client# %s\n", buf);
printf("Please enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if(s>0)
{
buf[s-1]=0;
sendMsgQueue(msgid, SERVER_TYPE, buf);
printf("send done, wait recv...\n");
}
}
destoryMsgQueue(msgid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msgid = getMsgQueue();
char buf[1024] = {0};
while(1)
{
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0)
{
buf[s-1]=0;
sendMsgQueue(msgid, CLIENT_TYPE, buf);
if(strcasecmp("quit", buf) == 0)
break;
printf("send done, wait recv...\n");
}
recvMsgQueue(msgid, SERVER_TYPE, buf);
printf("server# %s\n", buf);
}
return 0;
}