由一道面試題來了解進程間的通訊

    週末面試碰到一個面試題,題目是:linux

在MMO遊戲中,服務器採用Linux操做系統,網絡通訊與遊戲邏輯處理進程通常是分離的。
例如:GameSvr進程處理遊戲邏輯,TCPSvr進程處理網絡通訊。Linux操做系統提供了不少機制能夠實現GameSvr和TCPSvr進程之間的數據通訊。請您列出兩種你認爲最好的機制來,併爲主(最好)次(次佳)描述他們實現的框架,優缺點對比和應用中的注意事項。ios

答案:Linux下進程通訊

1、進程間通訊概述
進程通訊有以下一些目的:
A、數據傳輸:一個進程須要將它的數據發送給另外一個進程,發送的數據量在一個字節到幾M字節之間
B、共享數據:多個進程想要操做共享數據,一個進程對共享數據的修改,別的進程應該馬上看到。
C、通知事件:一個進程須要向另外一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
D、資源共享:多個進程之間共享一樣的資源。爲了做到這一點,須要內核提供鎖和同步機制。
E、進程控制:有些進程但願徹底控制另外一個進程的執行(如Debug進程),此時控制進程但願可以攔截另外一個進程的全部陷入和異常,並可以及時知道它的狀態改變。
Linux 進程間通訊(IPC)如下以幾部分發展而來:
早期UNIX進程間通訊、基於System V進程間通訊、基於Socket進程間通訊和POSIX進程間通訊。
UNIX進程間通訊方式包括:管道、FIFO、信號。
System V進程間通訊方式包括:System V消息隊列、System V信號燈、System V共享內存、
POSIX進程間通訊包括:posix消息隊列、posix信號燈、posix共享內存。
如今linux使用的進程間通訊方式:
(1)管道(pipe)和有名管道(FIFO)
(2)信號(signal)
(3)消息隊列
(4)共享內存
(5)信號量
(6)套接字(socket)程序員

2、管道通訊面試

普通的Linux shell都容許重定向,而重定向使用的就是管道。例如:
ps | grep vsftpd .管道是單向的、先進先出的、無結構的、固定大小的字節流,它把一個進程的標準輸出和另外一個進程的標準輸入鏈接在一塊兒。寫進程在管道的尾端寫入數據,讀進程在管道的道端讀出數據。數據讀出後將從管道中移走,其它讀進程都不能再讀到這些數據。管道提供了簡單的流控制機制。進程試圖讀空管道時,在有數據寫入管道前,進程將一直阻塞。一樣,管道已經滿時,進程再試圖寫管道,在其它進程從管道中移走數據以前,寫進程將一直阻塞。管道主要用於不一樣進程間通訊。shell


管道建立與關閉
建立一個簡單的管道,可使用系統調用pipe()。它接受一個參數,也就是一個包括兩個整數的數組。若是系統調用成功,此數組將包括管道使用的兩個文件描述符。建立一個管道以後,通常狀況下進程將產生一個新的進程。
系統調用:pipe();
原型:int pipe(int fd[2]);
返回值:若是系統調用成功,返回0。若是系統調用失敗返回-1:
errno=EMFILE(沒有空親的文件描述符)
      EMFILE(系統文件表已滿)
      EFAULT(fd數組無效)
注意:fd[0]用於讀取管道,fd[1]用於寫入管道。
圖見附件
管道的建立
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>編程

int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}數組

管道的讀寫
管道主要用於不一樣進程間通訊。實際上,一般先建立一個管道,再經過fork函數建立一個子進程。圖見附件。安全

子進程寫入和父進程讀的命名管道:圖見附件服務器

管道讀寫注意事項:
能夠經過打開兩個管道來建立一個雙向的管道。但須要在子理程中正確地設置文件描述符。必須在系統調用fork()中調用pipe(),不然子進程將不會繼承文件描述符。當使用半雙工管道時,任何關聯的進程都必須共享一個相關的祖先進程。由於管道存在於系統內核之中,因此任何不在建立管道的進程的祖先進程之中的進程都將沒法尋址它。而在命名管道中卻不是這樣。管道實例見:pipe_rw.c網絡

#include<unistd.h>
#include<memory.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;

memset(buf_r,0,sizeof(buf_r));數組中的數據清0;

if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0){
printf("\n");
close(pipe_fd[1]);
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0){
printf("%d numbers read from be pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}else if(pid>0){
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write success!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent wirte2 succes!\n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
}


標準流管道
與linux中文件操做有文件流的標準I/O同樣,管道的操做也支持基於文件流的模式。接口函數以下:
庫函數:popen();
原型:FILE *open (char *command,char *type);
返回值:若是成功,返回一個新的文件流。若是沒法建立進程或者管道,返回NULL。管道中數據流的方向是由第二個參數type控制的。此參數能夠是r或者w,分別表明讀或寫。但不能同時爲讀和寫。在Linux 系統下,管道將會以參數type中第一個字符表明的方式打開。因此,若是你在參數type中寫入rw,管道將會以讀的方式打開。

使用popen()建立的管道必須使用pclose()關閉。其實,popen/pclose和標準文件輸入/輸出流中的fopen()/fclose()十分類似。
庫函數:pclose();
原型:int pclose(FILE *stream);
返回值:返回系統調用wait4()的狀態。
若是stream無效,或者系統調用wait4()失敗,則返回-1。注意此庫函數等待管道進程運行結束,而後關閉文件流。庫函數pclose()在使用popen()建立的進程上執行wait4()函數,它將破壞管道和文件系統。
流管道的例子。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#define BUFSIZE 1024
int main(){
FILE *fp;
char *cmd="ps -ef";
char buf[BUFSIZE];
buf[BUFSIZE]='\0';
if((fp=popen(cmd,"r"))==NULL)
 perror("popen");
while((fgets(buf,BUFSIZE,fp))!=NULL)
 printf("%s",buf);
pclose(fp);
exit(0);
}

命名管道(FIFO)
基本概念
命名管道和通常的管道基本相同,但也有一些顯著的不一樣:
A、命名管道是在文件系統中做爲一個特殊的設備文件而存在的。
B、不一樣祖先的進程之間能夠經過管道共享數據。
C、當共享管道的進程執行完全部的I/O操做之後,命名管道將繼續保存在文件系統中以便之後使用。
管道只能由相關進程使用,它們共同的祖先進程建立了管道。可是,經過FIFO,不相關的進程也能交換數據。

命名管道建立與操做
命名管道建立
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
返回:若成功則爲0,若出錯返回-1
一旦已經用mkfifo建立了一個FIFO,就可用open打開它。確實,通常的文件I/O函數(close,read,write,unlink等)均可用於FIFO。當打開一個FIFO時,非阻塞標(O_NONBLOCK)產生下列影響:
(1)在通常狀況中(沒有說明O_NONBLOCK),只讀打開要阻塞到某個其餘進程爲寫打開此FIFO。相似,爲寫而打開一個FIFO要阻塞到某個其餘進程爲讀而打開它。
(2)若是指一了O_NONBLOCK,則只讀打開當即返回。可是,若是沒有進程已經爲讀而打開一個FIFO,那麼只寫打開將出錯返回,其errno是ENXIO。相似於管道,若寫一個尚無進程爲讀而打開的FIFO,則產生信號SIGPIPE。若某個FIFO的最後一個寫進程關閉了該FIFO,則將爲該FIFO的讀進程產生一個文件結束標誌。
FIFO相關出錯信息:
EACCES(無存取權限)
EEXIST(指定文件不存在)
ENAMETOOLONG(路徑名太長)
ENOENT(包含的目錄不存在)
ENOSPC(文件系統餘空間不足)
ENOTDIR(文件路徑無效)
EROFS(指定的文件存在於只讀文件系統中)

fifo_write.c 
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define FIFO "/tmp/myfifo"

main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes....\n");
memset(buf_r,0,sizeof(buf_r));
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1){
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}

fifo_read.c
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
if(fd==-1)
if(errno==ENXIO)
printf("open error;no reading process\n");
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
printf("Please send something\n");
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet. Please try later\n");
}
else 
printf("write %s to the FIFO\n",w_buf);
}


3、信號
信號概述
信號是軟件中斷。信號(signal)機制是Unix系統中最爲古老的進程之間的能信機制。它用於在一個或多個進程之間傳遞異步信號。不少條件能夠產生一個信號。
A、當用戶按某些終端鍵時,產生信號。在終端上按DELETE鍵一般產生中斷信號(SIGINT)。這是中止一個已失去控制程序的方法。
B、硬件異常產生信號:除數爲0、無效的存儲訪問等等。這些條件一般由硬件檢測到,並將其通知內核。而後內核爲該條件發生時正在運行的進程產生適當的信號。例如,對於執行一個無效存儲訪問的進程產生一個SIGSEGV。
C、進程用kill(2)函數可將信號發送給另外一個進程或進程組。天然,有些限制:接收信號進和發送信號進程的全部都必須相同,或發送信號進程的的全部者必須是超級用戶。
D、用戶可用Kill(ID 值)命令將信號發送給其它進程。此程序是Kill函數的界面。經常使用此命令終止一個失控的後臺進程。
E、當檢測到某種軟件條件已經發生,並將其通知有關進程時也產生信號。這裏並非指硬件產生條件(如被0除),而是軟件條件。例如SIGURG(在網絡鏈接上傳來非規定波特率的數據)、SIGPIPE(在管道的讀進程已終止後一個進程寫此管道),以及SIGALRM(進程所設置的鬧鐘時間已經超時)。

內核爲進程生產信號,來響應不一樣的事件,這些事件就是信號源。主要信號源以下:
(1)異常:進程運行過程當中出現異常;
(2)其它進程:一個進程能夠向另外一個或一組進程發送信號;
(3)終端中斷:Ctrl-c,Ctro-\等;
(4)做業控制:前臺、後臺進程的管理;
(5)分配額:CPU超時或文件大小突破限制;
(6)通知:通知進程某事件發生,如I/O就緒等;
(7)報警:計時器到期;

Linux中的信號
一、SIGHUP 二、SIGINT(終止) 三、SIGQUIT(退出) 四、SIGILL 五、SIGTRAP 六、SIGIOT  七、SIGBUS   八、SIGFPE   九、SIGKILL 十、SIGUSER 十一、 SIGSEGV SIGUSER 十二、 SIGPIPE 1三、SIGALRM 1四、SIGTERM 1五、SIGCHLD 1六、SIGCONT 1七、SIGSTOP 1八、SIGTSTP 1九、SIGTTIN 20、SIGTTOU 2一、SIGURG 2二、SIGXCPU 2三、SIGXFSZ 2四、SIGVTALRM 2五、SIGPROF 2六、SIGWINCH 2七、SIGIO 2八、SIGPWR

經常使用的信號:
SIGHUP:從終端上發出的結束信號;
SIGINT:來自鍵盤的中斷信號(Ctrl+c)
SIGQUIT:來自鍵盤的退出信號;
SIGFPE:浮點異常信號(例如浮點運算溢出);
SIGKILL:該信號結束接收信號的進程;
SIGALRM:進程的定時器到期時,發送該信號;
SIGTERM:kill命令生出的信號;
SIGCHLD:標識子進程中止或結束的信號;
SIGSTOP:來自鍵盤(Ctrl-Z)或調試程序的中止掃行信號

能夠要求系統在某個信號出現時按照下列三種方式中的一種進行操做。
(1)忽略此信號。大多數信號均可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL和SIGSTOP。這兩種信號不能被忽略的,緣由是:它們向超級用戶提供一種使進程終止或中止的可靠方法。另外,若是忽略某些由硬件異常產生的信號(例如非法存儲訪問或除以0),則進程的行爲是示定義的。
(2)捕捉信號。爲了作到這一點要通知內核在某種信號發生時,調用一個用戶函數。在用戶函數中,可執行用戶但願對這種事件進行的處理。若是捕捉到SIGCHLD信號,則表示子進程已經終止,因此此信號的捕捉函數能夠調用waitpid以取得該子進程的進程ID以及它的終止狀態。
(3)執行系統默認動做。對大多數信號的系統默認動做是終止該進程。每個信號都有一個缺省動做,它是當進程沒有給這個信號指定處理程序時,內核對信號的處理。有5種缺省的動做:
(1)異常終止(abort):在進程的當前目錄下,把進程的地址空間內容、寄存器內容保存到一個叫作core的文件中,然後終止進程。
(2)退出(exit):不產生core文件,直接終止進程。
(3)忽略(ignore):忽略該信號。
(4)中止(stop):掛起該進程。
(5)繼續(contiune):若是進程被掛起,剛恢復進程的動行。不然,忽略信號。

信號的發送與捕捉
kill()和raise()
kill()不只能夠停止進程,也能夠向進程發送其餘信號。
與kill函數不一樣的是,raise()函數運行向進程自身發送信號
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
兩個函數返回:若成功則爲0,若出錯則爲-1。
kill的pid參數有四種不一樣的狀況:
(1)pid>0將信號發送給進程ID爲pid的進程。
(2)pid==0將信號發送給其進程組ID等於發送進程的進程組ID,並且發送進程有許可權向其發送信號的全部進程。
(3)pid<0將信號發送給其進程組ID等於pid絕對值,並且發送進程有許可權向其發送信號的全部進程。如上所述同樣,「全部進程」並不包括系統進程集中的進程。
(4)pid==-1 POSIX.1未定義種狀況
kill.c 
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0){
perro("fork");
exit(1);
}
if(pid==0){
raise(SIGSTOP);
exit(0);
}else {
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0){
if((ret=kill(pid,SIGKILL))==0)
printf("kill %d\n",pid);
else{
perror("kill");
}
}
}
}

alarm和pause函數
使用alarm函數能夠設置一個時間值(鬧鐘時間),在未來的某個時刻時間值會被超過。當所設置的時間被超事後,產生SIGALRM信號。若是不忽略或不捕捉引信號,則其默認動做是終止該進程。
#include<unistd.h>
unsigned int alarm(unsigned int secondss);
返回:0或之前設置的鬧鐘時間的餘留秒數。

參數seconds的值是秒數,通過了指定的seconds秒後產生信號SIGALRM。每一個進程只能有一個鬧鐘時間。若是在調用alarm時,之前已爲該進程設置過鬧鐘時間,並且它尚未超時,則該鬧鐘時間的餘留值做爲本次alarm函數調用的值返回。之前登記的鬧鐘時間則被新值代換。
若是有之前登記的還沒有超過的鬧鐘時間,並且seconds值是0,則取消之前的鬧鐘時間,其他留值仍做爲函數的返回值。


pause函數使用調用進程掛起直至捕捉到一個信號
#include<unistd.h>
int pause(void);
返回:-1,errno設置爲EINTR
只有執行了一信號處理程序並從其返回時,pause才返回。

alarm.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int ret;
ret=alarm(5);
pause();
printf("I have been waken up.\n",ret);
}

信號的處理
當系統捕捉到某個信號時,能夠忽略誰信號或是使用指定的處理函數來處理該信號,或者使用系統默認的方式。信號處理的主要方式有兩種,一種是使用簡單的signal函數,別一種是使用信號集函數組。
signal()
#include<signal.h>
void (*signal (int signo,void (*func)(int)))(int)
返回:成功則爲之前的信號處理配置,若出錯則爲SIG_ERR
func的值是:(a)常數SIGIGN,或(b)常數SIGDFL,或(c)當接到此信號後要調用的的函數的地址。若是指定SIGIGN,則向內核表示忽略此信號(有兩個信號SIGKILL和SIGSTOP不能忽略)。若是指定SIGDFL,則表示接到此信號後的動做是系統默認動做。當指定函數地址時,咱們稱此爲捕捉此信號。咱們稱此函數爲信號處理程序(signal handler)或信號捕捉函數(signal-catching funcgion).signal函數原型太複雜了,若是使用下面的typedef,則可使其簡化。
type void sign(int);
sign *signal(int,handler *);
實例見:mysignal.c
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
 printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
 printf("I have get SIGQUIT\n");
}
int main()
{
 printf("Waiting for signal SIGINT or SIGQUTI\n");
 signal(SIGINT,my_func);
 signal(SIGQUIT,my_func);
 pasue();
 exit(0);
}
信號集函數組
咱們須要有一個能表示多個信號——信號集(signal set)的數據類型。將在sigprocmask()這樣的函數中使用這種數據類型,以告訴內核不容許發生該信號集中的信號。信號集函數組包含水量幾大模塊:建立函數集、登記信號集、檢測信號集。
圖見附件。

建立函數集
#include<signal.h>
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo );
int sigdelset(sigset_t* set,int signo);
四個函數返回:若成功則爲0,若出錯則爲-1
int sigismember(const sigset_t* set,int signo);
返回:若真則爲1,若假則爲0;
signemptyset:初始化信號集合爲空。
sigfillset:初始化信號集合爲全部的信號集合。
sigaddset:將指定信號添加到現存集中。
sigdelset:從信號集中刪除指定信號。
sigismember:查詢指定信號是否在信號集中。

登記信號集
登記信號處理機主要用於決定進程如何處理信號。首先要判斷出當前進程阻塞能不能傳遞給該信號的信號集。這首先使用sigprocmask函數判斷檢測或更改信號屏蔽字,而後使用sigaction函數改變進程接受到特定信號以後的行爲。
一個進程的信號屏蔽字能夠規定當前阻塞而不能遞送給該進程的信號集。調用函數sigprocmask能夠檢測或更改(或二者)進程的信號屏蔽字。
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功則爲0,若出錯則爲-1
oset是非空指針,進程是當前信號屏蔽字經過oset返回。其次,若set是一個非空指針,則參數how指示如何修改當前信號屏蔽字。
用sigprocmask更改當前信號屏蔽字的方法。how參數設定:
SIG_BLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set指向信號集的並集。set包含了咱們但願阻塞的附加信號。
SIG_NUBLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set所指向信號集的交集。set包含了咱們但願解除阻塞的信號。
SIG_SETMASK該進程新的信號屏蔽是set指向的值。若是set是個空指針,則不改變該進程的信號屏蔽字,how的值也無心義。
sigaction函數的功能是檢查或修改(或二者)與指定信號相關聯的處理動做。此函數取代了UNIX早期版本使用的signal函數。
#include<signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
返回:若成功則爲0,若出錯則爲-1
參數signo是要檢測或修改具體動做的信號的編號數。若act指針非空,則要修改其動做。若是oact指針爲空,則系統返回該信號的原先動做。此函數使用下列結構:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
sa_handler是一個函數指針,指定信號關聯函數,能夠是自定義處理函數,還能夠SIG_DEF或SIG_IGN;
sa_mask是一個信號集,它能夠指定在信號處理程序執行過程當中哪些信號應當被阻塞。
sa_flags中包含許多標誌位,是對信號進行處理的各類選項。具體以下:
SA_NODEFER\SA_NOMASK:當捕捉到此信號時,在執行其信號捕捉函數時,系統不會自動阻塞此信號。
SA_NOCLDSTOP:進程忽略子進程產生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTOU信號
SA_RESTART:可以讓重啓的系統調用從新起做用。
SA_ONESHOT\SA_RESETHAND:自定義信號只執行一次,在執行完畢後恢復信號的系統默認動做。
檢測信號是信號處理的後續步驟,但不是必須的。sigpending函數運行進程檢測「未決「信號(進程不清楚他的存在),並進一步決定對他們作何處理。
sigpending返回對於調用進程被阻塞不能遞送和當前未決的信號集。
#include<signal.h>
int sigpending(sigset_t * set);
返回:若成功則爲0,若出錯則爲-1
信號集實例見:sigaction.c
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void my_func(int signum){
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror("sigemptyset");
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprcmask");
esle{
printf("blocked\n");
sleep(5);
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)
perror("sigprocmask");
else
printf("unblock\n");
while(1){
if(sigismember(&set,SIGINT)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
}else if(sigismember(&set,SIGQUIT)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DEL;
sigaction(SIGTERM,&action2,NULL);
}
}
}

答案:Windows下進程通訊

1   文件映射
  文件映射(Memory-Mapped Files)能使進程把文件內容看成進程地址區間一塊內存那樣來對待。所以,進程沒必要使用文件I/O操做,只需簡單的指針操做就可讀取和修改文件的內容。

  Win32 API容許多個進程訪問同一文件映射對象,各個進程在它本身的地址空間裏接收內存的指針。經過使用這些指針,不一樣進程就能夠讀或修改文件的內容,實現了對文件中數據的共享。
  應用程序有三種方法來使多個進程共享一個文件映射對象。
  (1)繼承:第一個進程創建文件映射對象,它的子進程繼承該對象的句柄。
  (2)命名文件映射:第一個進程在創建文件映射對象時能夠給該對象指定一個名字(可與文件名不一樣)。第二個進程可經過這個名字打開此文件映射對象。另外,第一個進程也能夠經過一些其它IPC機制(有名管道、郵件槽等)把名字傳給第二個進程。
  (3)句柄複製:第一個進程創建文件映射對象,而後經過其它IPC機制(有名管道、郵件槽等)把對象句柄傳遞給第二個進程。第二個進程複製該句柄就取得對該文件映射對象的訪問權限。
  文件映射是在多個進程間共享數據的很是有效方法,有較好的安全性。但文件映射只能用於本地機器的進程之間,不能用於網絡中,而開發者還必須控制進程間的同步。
2  共享內存
  Win32 API中共享內存(Shared Memory)實際就是文件映射的一種特殊狀況。進程在建立文件映射對象時用0xFFFFFFFF來代替文件句柄(HANDLE),就表示了對應的文件映射對象是從操做系統頁面文件訪問內存,其它進程打開該文件映射對象就能夠訪問該內存塊。因爲共享內存是用文件映射實現的,因此它也有較好的安全性,也只能運行於同一計算機上的進程之間。

  注意點: 要控制同步,並且CString、list、arry、map等的collect class都不能安全的使用於共享內存中

      不要把擁有虛函數之C++類放到共享內存中

      不要把CObject派生類之MFC對象放到共享內存中

      不要使用"point within the shared memory"的指針

      不要使用"point outside of the shared memory"的指針

      使用"based"指針是安全的,但要當心使用
3  匿名管道

  管道(Pipe)是一種具備兩個端點的通訊通道:有一端句柄的進程能夠和有另外一端句柄的進程通訊。管道能夠是單向-一端是隻讀的,另外一端點是隻寫的;也能夠是雙向的一管道的兩端點既可讀也可寫。
  匿名管道(Anonymous Pipe)是 在父進程和子進程之間,或同一父進程的兩個子進程之間傳輸數據的無名字的單向管道。一般由父進程建立管道,而後由要通訊的子進程繼承通道的讀端點句柄或寫 端點句柄,而後實現通訊。父進程還能夠創建兩個或更多個繼承匿名管道讀和寫句柄的子進程。這些子進程可使用管道直接通訊,不須要經過父進程。
  匿名管道是單機上實現子進程標準I/O重定向的有效方法,它不能在網上使用,也不能用於兩個不相關的進程之間。
4  命名管道
  命名管道(Named Pipe)是服務器進程和一個或多個客戶進程之間通訊的單向或雙向管道。不一樣於匿名管道的是命名管道能夠在不相關的進程之間和不一樣計算機之間使用,服務器創建命名管道時給它指定一個名字,任何進程均可以經過該名字打開管道的另外一端,根據給定的權限和服務器進程通訊。
  命名管道提供了相對簡單的編程接口,使經過網絡傳輸數據並不比同一計算機上兩進程之間通訊更困難,不過若是要同時和多個進程通訊它就力不從心了。
5  郵件槽
  郵件槽(Mailslots)提 供進程間單向通訊能力,任何進程都能創建郵件槽成爲郵件槽服務器。其它進程,稱爲郵件槽客戶,能夠經過郵件槽的名字給郵件槽服務器進程發送消息。進來的消 息一直放在郵件槽中,直到服務器進程讀取它爲止。一個進程既能夠是郵件槽服務器也能夠是郵件槽客戶,所以可創建多個郵件槽實現進程間的雙向通訊。
  經過郵件槽能夠給本地計算機上的郵件槽、其它計算機上的郵件槽或指定網絡區域中全部計算機上有一樣名字的郵件槽發送消息。廣播通訊的消息長度不能超過400字節,非廣播消息的長度則受郵件槽服務器指定的最大消息長度的限制。
  郵件槽與命名管道類似,不過它傳輸數據是經過不可靠的數據報(如TCP/IP協議中的UDP包)完成的,一旦網絡發生錯誤則沒法保證消息正確地接收,而命名管道傳輸數據則是創建在可靠鏈接基礎上的。不過郵件槽有簡化的編程接口和給指定網絡區域內的全部計算機廣播消息的能力,因此郵件槽不失爲應用程序發送和接收消息的另外一種選擇。
6  剪貼板
  剪貼板(Clipped Board)實質是Win32 API中一組用來傳輸數據的函數和消息,爲Windows應用程序之間進行數據共享提供了一箇中介,Windows已創建的剪切(複製)-粘貼的機制爲不一樣應用程序之間共享不一樣格式數據提供了一條捷徑。當用戶在應用程序中執行剪切或複製操做時,應用程序把選取的數據用一種或多種格式放在剪貼板上。而後任何其它應用程序均可以從剪貼板上拾取數據,從給定格式中選擇適合本身的格式。

  剪貼板是一個很是鬆散的交換媒介,能夠支持任何數據格式,每一格式由一無符號整數標識,對標準(預約義)剪貼板格式,該值是Win32 API定義的常量;對非標準格式可使用Register Clipboard Format函數註冊爲新的剪貼板格式。利用剪貼板進行交換的數據只需在數據格式上一致或均可以轉化爲某種格式就行。但剪貼板只能在基於Windows的程序中使用,不能在網絡上使用
7 動態數據交換
  動態數據交換(DDE)是使用共享內存在應用程序之間進行數據交換的一種進程間通訊形式。應用程序可使用DDE進行一次性數據傳輸,也能夠當出現新數據時,經過發送更新值在應用程序間動態交換數據。
  DDE和剪貼板同樣既支持標準數據格式(如文本、位圖等),又能夠支持本身定義的數據格式。但它們的數據傳輸機制卻不一樣,一個明顯區別是剪貼板操做幾乎老是用做對用戶指定操做的一次性應答-如從菜單中選擇Paste命令。儘管DDE也能夠由用戶啓動,但它繼續發揮做用通常沒必要用戶進一步干預。DDE有三種數據交換方式:
  (1) 冷鏈:數據交換是一次性數據傳輸,與剪貼板相同。
  (2) 溫鏈:當數據交換時服務器通知客戶,而後客戶必須請求新的數據。
  (3) 熱鏈:當數據交換時服務器自動給客戶發送數據。
  DDE交換能夠發生在單機或網絡中不一樣計算機的應用程序之間。開發者還能夠定義定製的DDE數據格式進行應用程序之間特別目的IPC,它們有更緊密耦合的通訊要求。大多數基於Windows的應用程序都支持DDE。
8 對象鏈接與嵌入
  應用程序利用對象鏈接與嵌入(OLE)技術管理複合文檔(由多種數據格式組成的文檔),OLE提供使某應用程序更容易調用其它應用程序進行數據編輯的服務。例如,OLE支持的字處理器能夠嵌套電子表格,當用戶要編輯電子表格時OLE庫可自動啓動電子表格編輯器。當用戶退出電子表格編輯器時,該表格已在原始字處理器文檔中獲得更新。在這裏電子表格編輯器變成了字處理器的擴展,而若是使用DDE,用戶要顯式地啓動電子表格編輯器。
  同DDE技術相同,大多數基於Windows的應用程序都支持OLE技術。
9 動態鏈接庫
  Win32動態鏈接庫(DLL)中的全局數據能夠被調用DLL的全部進程共享,這就又給進程間通訊開闢了一條新的途徑,固然訪問時要注意同步問題。
  雖然能夠經過DLL進行進程間數據共享,但從數據安全的角度考慮,咱們並不提倡這種方法,使用帶有訪問權限控制的共享內存的方法更好一些。
10 遠程過程調用
  Win32 API提供的遠程過程調用(RPC)使應用程序可使用遠程調用函數,這使在網絡上用RPC進行進程通訊就像函數調用那樣簡單。RPC既能夠在單機不一樣進程間使用也能夠在網絡中使用。
  因爲Win32 API提供的RPC服從OSF-DCE(Open Software Foundation Distributed Computing Environment)標準。因此經過Win32 API編寫的RPC應用程序能與其它操做系統上支持DEC的RPC應用程序通訊。使用RPC開發者能夠創建高性能、緊密耦合的分佈式應用程序。
11 NetBios函數
  Win32 API提供NetBios函數用於處理低級網絡控制,這主要是爲IBM NetBios系統編寫與Windows的接口。除非那些有特殊低級網絡功能要求的應用程序,其它應用程序最好不要使用NetBios函數來進行進程間通訊。
12 Sockets
  Windows Sockets規範是以U.C.Berkeley大學BSD UNIX中流行的Socket接口爲範例定義的一套Windows下的網絡編程接口。除了Berkeley Socket原有的庫函數之外,還擴展了一組針對Windows的函數,使程序員能夠充分利用Windows的消息機制進行編程。
  如今經過Sockets實現進程通訊的網絡應用愈來愈多,這主要的緣由是Sockets的跨平臺性要比其它IPC機制好得多,另外WinSock 2.0不只支持TCP/IP協議,並且還支持其它協議(如IPX)。Sockets的惟一缺點是它支持的是底層通訊操做,這使得在單機的進程間進行簡單數據傳遞不太方便,這時使用下面將介紹的WM_COPYDATA消息將更合適些。
13 WM_COPYDATA消息
  WM_COPYDATA是一種很是強大卻不爲人知的消息。當一個應用向另外一個應用傳送數據時,發送方只需使用調用SendMessage函數,參數是目的窗口的句柄、傳遞數據的起始地址、WM_COPYDATA消息。接收方只需像處理其它消息那樣處理WM_COPY DATA消息,這樣收發雙方就實現了數據共享。
  WM_COPYDATA是一種很是簡單的方法,它在底層其實是經過文件映射來實現的。它的缺點是靈活性不高,而且它只能用於Windows平臺的單機環境下。 

相關文章
相關標籤/搜索