1. 程式人生 > >程序間通訊---管道和訊息佇列

程序間通訊---管道和訊息佇列

程序間通訊的目的:資料傳輸:一個程序需要將它的資料傳送給另一個程序資源共享:對個程序之間共享同樣的資源通知事件:一個程序需要向另一個或一組程序傳送訊息,通知它們發生了什麼事件程序控制:有些程序希望完全控制另一個程序的執行(如:Debug程序)程序間通訊的發展:管道:System V程序間通訊:POSIX程序間通訊:程序間通訊的分類:

管道:我們把從一個程序連線到另一個程序的一個數據流稱為一個“管道”,管道是Unix中最古老的程序間通訊的方式。
  • 匿名管道:
函式原型:
int pipe(int fd[2]);
引數說明:
fd:檔案描述符陣列,fd[0]表示讀端,fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤程式碼。

圖解:

舉一個例子:從鍵盤讀取資料,寫入管道,讀取管道,寫到螢幕。



管道讀寫規則:當沒有資料可讀時:
  • read呼叫阻塞,即程序暫停執行,一直等到有資料來到為止,read呼叫返回-1,errno值為EAGAIN
當管道滿的時候:
  • write呼叫阻塞,直到有程序讀走資料,呼叫返回-1,errno值為EAGAIN。
如果所有管道寫端對應的檔案描述符被關閉,則read返回0如果所有管道讀端對應的檔案描述符被關閉,則write操作會產生訊號SIGIPE,進而可能導致write程序退出。當要寫入的資料量不大於PIPE_BUF時,Linux將保證寫入的原子性當要寫入的資料量大於PIPE_BUF時,Linux將不再保證寫入的原子性
管道特點:只能用於具有共同祖先的程序(具有親緣關係的程序)之間進行通訊。比如,一個管道由一個程序建立,然後該程序呼叫fork,此後父、子程序之間就可以應用該管道。管道提供流式服務程序退出,管道釋放,所以管道的生命週期隨程序。核心會對管道操作進行同步與互斥管道是半雙工的,資料只能向一個方向流動,需要雙方通訊時,需要建立兩個管道命名管道:管道只能在具有共同祖先的程序間通訊,在不相關的程序之間交換資料,可以使用FIFO檔案來實現,被稱為命名管道。命名管道是一種特殊型別的檔案。
建立一個命名管道:函式:
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
 引數說明:pathname:檔名   ,mode:檔案的許可權掩碼,通過umask檢視。

命令列:

$ mkfifo pathname
建立一個名為“my”的命名管道


命名管道的開啟規則:
  • 如果當前開啟操作是為讀而開啟FIFO時,阻塞直到有相應程序為寫而開啟該FIFO,立刻返回成功。
  • 如果當前開啟操作是為寫而開啟FIFO時,阻塞直到有相應程序為讀而開啟該FIFO,立刻返回失敗,錯誤碼為ENXIO
匿名管道和命名管道的區別:匿名管道由pipe函式建立開啟。命名管道由mkfifo函式建立,開啟用open。FIFO(命名管道)與pipe(匿名管道)之間唯一區別在於他們的創建於開啟的方式不同,當這些工作完成之後,他們具有相同的語義。訊息佇列:訊息佇列提供了一個從一個程序向另外一個程序傳送有型別資料塊的方法,每個資料塊都被認為是一個型別,接受者程序接收的資料塊可以有不同的型別值。每個訊息的最大長度是有上限的(MSGMAX),,每個訊息佇列的總位元組數是有上限的(MSGMNB),系統上訊息佇列的總數也有一個上限(MSGMNI)。訊息佇列的結構:在 /usr/include/linux/msg.h中


ipc物件資料結構 /usr/include/linux/ipc.h
核心為每個IPC物件維護一個數據結構。

__kernel_key_t : 其實是內建資料型別的一種重新命名,用在核心中。是一個int訊息佇列在核心中的形式:

訊息佇列其實就是一個訊息的連結串列,是一系列儲存在核心中訊息的列表。使用者程序可以向訊息佇列新增訊息,也可以向訊息佇列讀取訊息。

訊息佇列與管道相比,其優勢是對每個訊息指定特定的訊息型別,接收的時候不需要按照佇列次序進行接收,而是可以根據自定義條件接收特定的訊息。

可以把訊息看做一個記錄,具有特定的格式以及特定的優先順序。對訊息佇列有寫許可權的程序可以向訊息佇列按照一定的規則新增一條訊息,對訊息佇列有讀許可權的程序可以從訊息佇列中讀取訊息。


問題:怎麼保證兩個程序看到的是同一個訊息佇列?

如果給訊息佇列一個編號,這個編號讓兩個程序用某一種特定的方式獲得,計算之後可以得到相同的資料,將該資料寫入訊息佇列,此時作為該訊息佇列的編號。往後就可以保證他倆看到的是同一個訊息佇列。

訊息佇列的相關函式:

msgget函式:用來建立和訪問一個訊息佇列

標頭檔案:

#include <sys/types.h>

#include <sys/msg.h>

#include <sys/ipc.h>

函式原型:
   int msgget(key_t key, int msgflg);
引數:
    key:訊息佇列名字,鍵值,可設定成常數IPC_PRIVATE,或由ftok函式獲取
    msgflg:標誌位
返回值:成功返回一個非負數,是該訊息佇列的標識碼;失敗返回-1.

    標誌位msflg的取值如下:

IPC_CREAT:建立新的訊息佇列

IPC_EXCL:與IPC_CREAT一起使用,表示如果要建立的訊息佇列已經存在,則返回錯誤

IPC_NOWAIT:讀寫訊息佇列要求無法達到滿足時,立即返回,不會出現堵塞

引數key設定成常數IPC_PRIVATE並不意味著其他程序不能訪問該訊息佇列,只是意味著即將參加新的訊息佇列。

ftok函式:將檔名轉換成鍵值

標頭檔案:

#include<sys/types.h>

#include<sys/ipc.h>

函式原型:
    key_t ftok(const char* pathname,int proj_id)
引數:
    pathname:檔案路徑名,這個檔案必須是存在的並且是可以訪問的
    proj_id:子序號,是一個8bits的整數,範圍為0-255
返回值:
    成功返回與檔案相對應的鍵值;失敗返回-1


注意:
  • ftok是根據檔案路徑名,提取檔案資訊,再根據這些資訊和proj_id合成key,該路徑可以隨便設定
  • 該路徑是必須存在的,ftok只是根據檔案inode在系統內的唯一性來取一個數值,和檔案的許可權無關
  • proj_id是可以自己約定,隨意設定的。在Unix系統上,它的取值是1到255

key通過ftok函式獲得,key在使用者和核心層面上達成了共識。 程序給作業系統一個key,作業系統用這個key建立一個訊息佇列,另一個程序通過相同的方式也會生成同樣的key,這樣他們在運算期間後序就可以看到同樣的訊息佇列。

key完成了程序間通訊的第一個步驟,讓兩個程序看到同一份資源。

msgctl函式:訊息佇列控制函式

(當cmd為IPC_RMID時,作為訊息佇列刪除函式)

函式原型:
  int msgctl(int msqid, int cmd, struct msqid_ds *buf);
引數:
   msqid:有msgget函式返回的標識碼
   cmd:將要採取的動作(三個取值)
返回值:
    成功返回0,失敗返回-1

cmd的取值如下:

  • IPC_STAT:把msqid_ds結構體中的資料設定為訊息佇列的當前關聯值
  • IPC_SET:在程序有足夠許可權的前提下,把訊息佇列的當前關聯值設定為msqid_ds資料結構中給出的值
  • IPC_RMID:刪除訊息佇列

buf是執行msqid_ds結構的指標,它指向訊息佇列模式和訪問許可權的結構。msqid_ds結構中至少包括以下成員:

struct msqid_ds{
    uid_t uid;
    gid_t gid;
    mode_t mode;
 };

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);
引數:
    msqid:由msgget函式返回的訊息佇列標識碼
    msgp:指向準備傳送的訊息的指標。指標msgp所指向的資料結構一定要是以一個長整型成員變數開始的結構體,接收函式將用這個成員來確定訊息的型別
    msgsz:是msgp指向的訊息長度,注意這個長度不包含訊息型別的哪個long int長整型的長度
    msgflg:控制著當前訊息佇列滿或者到達系統上限時將要發生的事情
    msgflg=IPC_NOWAIT表示佇列滿不等待,非阻塞,返回EAGAIN錯誤。
    msgflg=0 ,表示阻塞
返回值:成功,訊息資料的一份副本將被放到訊息佇列中,並返回0,失敗返回-1

說明:

1. 訊息結構在兩方面受到限制:

首先,它必須小於系統規定的上限值;

其次,它必須以一個long int長整型開始,接受者函式將利用這個長整型確定訊息的型別    

2. 訊息結構的參考形式如下:                                  

struct msgbuf{
    long int type;
    char text[1];
};

msgrcv函式:從一個訊息佇列接收函式

標頭檔案:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函式原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
引數:
    msqid:由msgget函式返回的訊息佇列標識碼
    msgp:指向準備接收訊息的佇列
    msgsz:msgp指向的訊息長度,不包含儲存訊息型別的哪個long int
    msgtyp:可以實現接收優先順序的簡單形式。
            若msgtyp==0,則返回佇列的第一條訊息
            若msgtyp > 0,返回佇列第一條型別等於msgtyp的訊息
            若msgtyp < 0,返回佇列第一條型別小於等於msgtyp絕對值的訊息,並且是滿足條件的訊息型別最小的訊息
    msgflg: 控制著佇列中沒有相應型別的訊息可供接收時將要發生的事
            msgflg=IPC_NOWAIT,佇列沒有可讀訊息不等待,返回ENOMSG錯誤
            msgflg=MSG_NOERROR,訊息大小超過msgsz時被截斷
            msgflg=0表示阻塞
            msgtyp>0 且 msgflg=MSG_EXCEPT,接收型別不等於msgtype的第一條訊息

一個程序間通訊的例子:

客戶端傳送一條訊息,伺服器端接收從客戶端傳送的訊息;伺服器向客戶端傳送一條訊息(應答),客戶端接收訊息。

comm.h

#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 "msg.tmp"  //用msg.tmp檔案的索引結點號和Proj_id 來生成鍵值
#define Proj_id 0x20  //可以隨意指定

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

struct msgbuf{
	long mtype;
	char mtext[1024];
};

int CreateMsgQueue();
int GetMsgQueue();
int DestroyMsgQueue(int msqid);
int SendMsg(int msqid,int who,char *msg);
int RecvMsg(int msqid,int recvType,char out[]);

#endif

comm.c

#include "comm.h"
static int CommMsgQueue(int flags){
	key_t key = ftok(PATHNAME,Proj_id);
	if(key < 0){
		perror("ftok");
		return -1;
	}

	int msqid = msgget(key,flags);
	if(msqid < 0){
		perror("msgget");
		//失敗返回-1
	}
	return msqid;
}

int CreateMsgQueue(){
	return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
	//引數含義:建立訊息佇列,如果該訊息佇列存在,就返回錯誤,
	//許可權為可讀可寫
}

int GetMsgQueue(){
	return CommMsgQueue(IPC_CREAT);
}

int DestroyMsgQueue(int msqid){
	if(msgctl(msqid,IPC_RMID,NULL) < 0){
		perror("msgctl");
		return -1;
	}
	return 0;
}

int SendMsg(int msqid,int who,char* msg){
	struct msgbuf buf;
	buf.mtype = who;
	strcpy(buf.mtext,msg);

	if(msgsnd(msqid,(void*)&buf,sizeof(buf.mtext),0) < 0){
		perror("msgsnd");
		return -1;
	}
	return 0;
}

int RecvMsg(int msqid,int recvType,char out[]){
	struct msgbuf buf;
	if(msgrcv(msqid,(void*)&buf,sizeof(buf.mtext),recvType,0) < 0){
		perror("msgrcv");
		return -1;
	}
	strcpy(out , buf.mtext);
	return 0;
}

server.c

#include "comm.h"

int main()
{
	int msqid = CreateMsgQueue();
	char buf[1024];
	while(1){
		buf[0] = 0;
		RecvMsg(msqid,CLIENT_TYPE,buf);
		printf("client: %s\n",buf);
		
		printf("Please Enter: ");
		fflush(stdout);
		//從標準輸入中讀,fd=0
		ssize_t read_size = read(0,buf,sizeof(buf)-1);
		if(read_size > 0){
			buf[read_size] = '\0';
			SendMsg(msqid,SERVER_TYPE,buf);
			printf("send done,wait recv...\n");
		}
	}
	DestroyMsgQueue(msqid);
	return 0;
}

client.c

#include "comm.h"

int main()
{
	int msqid = GetMsgQueue();
	char buf[1024];
	while(1){
		buf[0] = 0;
		printf("Please Enter# ");
		fflush(stdout);
		ssize_t read_size = read(0,buf,sizeof(buf)-1);
		if(read_size > 0){
			buf[read_size] = '\0';
			SendMsg(msqid,CLIENT_TYPE,buf);
			printf("send done,wait recv...\n");
		}
		RecvMsg(msqid,SERVER_TYPE,buf);
		printf("server# %s\n ",buf);
	}
	return 0;
}

Makefile

all:client server

client:client.c comm.c
	gcc -o [email protected] $^

server:server.c comm.c
	gcc -o [email protected] $^

.PHONY:clean
clean:
	rm -f client server

執行結果:客戶端與伺服器之間進行的通訊。


ftok參考自:https://blog.csdn.net/u013485792/article/details/50764224

相關推薦

程序程式設計之程序通訊-管道訊息佇列

1.程序間通訊 Linux作為一種新興的作業系統,幾乎支援所有的Unix下常用的程序間通訊方法:管道、訊息佇列、共享記憶體、訊號量、套介面等等。 2.管道 管道是程序間通訊中最古老的方式,它包括無名管道(或者匿名管道)和有名管道兩種,前者用於父程序和

程序通訊---管道訊息佇列

程序間通訊的目的:資料傳輸:一個程序需要將它的資料傳送給另一個程序資源共享:對個程序之間共享同樣的資源通知事件:一個程序需要向另一個或一組程序傳送訊息,通知它們發生了什麼事件程序控制:有些程序希望完全控制另一個程序的執行(如:Debug程序)程序間通訊的發展:管道:Syste

程序通訊——管道訊息佇列,共享記憶體

程序間通訊的本質是讓兩個不相干的程序看到同一份資源。這個資源是由作業系統提供的一個檔案。程序間通訊的目的:1.資料傳輸:一個程序需要將它 的資料傳送給另一個程序。2.資源共享:多個程序之間共享同樣的資源。3.通知事件:一個程序需要向另一個(組)程序傳送訊息,通知它們發生了

程序通訊方式總結——訊息佇列

        Linux/Unix系統IPC是各種程序間通訊方式的統稱,但是其中極少能在所有Linux/Unix系統實現中進行移植。隨著POSIX和Open Group(X/Open)標準化的推進

Linux程序通訊之POSIX訊息佇列

訊息佇列可認為是一個訊息連結串列,它允許程序之間以訊息的形式交換資料。有足夠寫許可權的程序或執行緒可往佇列中放置訊息,有足夠讀許可權的程序或執行緒可從佇列中取走訊息。每個訊息都是一個記錄,它由傳送者賦予一個優先順序。與管道不同,管道是位元組流模型,沒有訊息邊界。

程序通訊(IPC)之訊息佇列

★IPC方法包括管道(PIPE)、訊息佇列(Message_Queue)、旗語、共用記憶體(ShareMemory)以及套接字(Socket)。進 程間通訊主要包括了管道、系統IPC(包括了訊息佇列、

Linux---程序通訊IPC之訊息佇列

**程序間通訊(IPC):**是指在不同程序之間傳播或交換資訊。 **IPC的方式:**通常有管道(無名管道、命名管道)、訊息佇列、訊號量、共享儲存、Socket、Streams等(Socket和Streams支援不同主機上的兩個程序IPC) 程序間通訊的目

程序通訊---管道訊息佇列、共享記憶體

程序通訊分為低階通訊和高階通訊。 低階通訊是指程序互斥與同步,包括訊號、訊號量、管程等。 高階通訊方式有管道、訊息佇列、共享記憶體以及網路通訊中的套接字。 匿名管道PIPE: 管道是連線兩個程序的檔案,

程序通訊-管道(PIPE)有名管道(FIFO)

1.2有名管道的建立 該函式的第一個引數是一個普通的路勁名,也就是建立後FIFO的名字。第二個引數與開啟普通檔案的open()函式中的mode引數相同。如果mkfifo的一個引數是一個已經存在路勁名時,會返回EEXIST錯誤,所以一般典型的呼叫程式碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,

Linux講解 程序通訊 管道

  今天我們講解程序間的通訊,首先回顧一下程序的概念:程序是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程

33-程序通訊——管道

1. 什麼是管道   管道是unix系統最古老的IPC通訊方式了,適合於有血緣關係的程序之間完成資料傳輸,比如父子程序,兄弟程序。    管道允許一個數據流向另一個程序,管道中的資料流向是單向的。這樣程序可以通過檔案描述符1連線到管道寫入端,另一個程序通過檔案

程序通訊-管道通訊

兩個程序的通訊,每個程序各自有不同的地址空間,每個地址空間的資料資訊是獨立的,任何一個程序的全域性變數在另一個程序中都看不到。例如,父程序中有一個全域性變數a = 0;在子程序中改變a的值不會影響父程序中a值的結果,因為子程序所有的資料資訊都拷貝(寫時拷貝)自父程序,兩個程

程序通訊方式多執行緒同步機制總結

多程序之間通訊方式:           檔案對映:本地之間           共享記憶體:本地之間           匿名管道:本地之間           命名管道:跨伺服器           郵件槽:一對多的傳輸資料,通常通過網路向一臺Windo

程序通訊管道

一、常用的方式 (1) 管道 (2) System V (3) POSIX 二、目的 (1) 資料傳輸 (2) 資源共享 (3) 通知事件 (4) 程序控制 三、 本質 讓兩個不同的程序看到一份公共的資源 四、匿名管道 1. 簡單用法: 

【Qt】Qt之程序通訊(Windows訊息)【轉】

簡述 通過上一節的瞭解,我們可以看出程序通訊的方式很多,今天分享下如何利用Windows訊息機制來進行不同程序間的通訊。 效果 傳送訊息 自定義型別與接收窗體 包含所需庫,定義傳送的自定義型別、接收訊息的窗體標題。自定義型別可以處理訊息過多情況下,對訊息的區分,如果不需要也可以去掉。

Linux程式設計學習筆記----程序通訊——管道

程序通訊概述 在Linux系統中,程序是一個獨立的資源管理單元,但是獨立而不孤立,他們需要之間的通訊,因此便需要一個程序間資料傳遞、非同步、同步的機制,這個機制顯然需要由OS來完成管理和維護。如下: 1、同一主機程序間資料互動機制:無名管道(PIPE),有名管道(FIF

C++程序通訊---自定義訊息

在windows中訊息分為兩種,即系統訊息和使用者自定義訊息,系統訊息定義從0到0x3ff,可以使用0x400到0x7fff定義自己的訊息。windows把0x400定義為WM_USER,如果想定義自己的一個訊息,可以在WM_USER上加上一個值。當然了,有另外

Linux 程序通訊 管道

管道 匿名管道:半雙工,資料單向流動,只能用與有親緣關係的程序間。 pipe_read_buf_small.c #include <unistd.h> #include <sys/t

Qt 之程序通訊(Windows 訊息

簡述 通過上一節的瞭解,我們可以看出程序通訊的方式很多,今天分享下如何利用Windows訊息機制來進行不同程序間的通訊。 | 效果 傳送訊息 自定義型別與接收窗體 包含所需庫,定義傳送的自定義型別、接收訊息的窗體標題。自定義型別可以處理

Linux:程序通訊(匿名管道命名管道)(共享記憶體,訊息佇列,訊號量)

目錄 程序間通訊的介紹 管道 匿名管道 原理: 程式碼實現 匿名管道特性 實現管道符 |  命名管道 命名管道特性 程式碼實現 管道讀寫規則 作業系統中ipc的相關命令 共享記憶體(重點) 生命週期: 程式碼實現 程式碼實現獲