c++ 網絡編程(三)TCP/IP 進程間的通信原理與實現代碼
原文作者:aircraft
原文鏈接:https://www.cnblogs.com/DOMLX/p/9613027.html
一.進程間通信的基本概念
進程間通信意味著兩個不同進程間可以交換數據,操作系統中應提供兩個進程可以同時訪問的內存空間。
通過管道實現進程間通信
基於管道(PIPE)的進程間通信結構模型:
通過管道完成進程間通信。管道不是進程的資源,屬於操作系統的。兩個進程通過操作系統提供的內存空間進行通信。
創建管道的函數:
父進程調用該函數時創建管道,同時獲取對應於出入口的文件描述符。父進程的目的是與子進程進行數據交換,因此需要將入口或出口中的1個文件描述符傳遞給子進程。調用fork函數傳遞。
二.進程間通信的單向傳遞
簡單的看一個基礎單向通信實例代碼來理解進程間的通信是怎麽實現的:
#include<stdio.h> #include<unistd.h> #define BUF_SIZE 30 int main(int argc, char *argv[]) { int fds[2]; char str[] = "Who are you?"; char buf[BUF_SIZE]; pid_t pid; pipe(fds); //創建管道,fds數組中保存用於I/O的文件描述符pid = fork(); //子進程將同時擁有管道的I/O文件描述符。 if (pid == 0) { write(fds[1],str,sizeof(str)); //fds[1]為管道入口 } else { read(fds[0],buf,BUF_SIZE); //fds[0]為管道出口 puts(buf); } return 0; }
運行結果:who are you ?
上例中,父子進程都可以訪問管道的I/O路徑,但子進程僅用輸入路徑,父進程僅用輸出路徑。
三.進程間通信的雙向傳遞
管道進行雙向數據交換的通信方式:
接下來看一個簡單的實例代碼:
/* 雙向通信的管道 */ #include<stdio.h> #include<unistd.h> #define BUF_SIZE 30 int main(int argc,char *argv[]) { int fds[2]; char str1[] = "Who are you?"; char str2[] = "Thank you for your message!"; char buf[BUF_SIZE]; pid_t pid; pipe(fds); pid = fork(); if (pid == 0) { write(fds[1],str1,sizeof(str1)); //傳輸數據 sleep(2); //睡眠兩秒,避免被下一行的read函數讀取了數據。 read(fds[0],buf,BUF_SIZE); printf("Child proc output: %s \n",buf); //接收數據 } else { read(fds[0],buf,BUF_SIZE); //接收數據 printf("Parent proc output: %s \n",buf); write(fds[1],str2,sizeof(str2)); //傳輸數據 sleep(3); //睡眠,防止父進程在子進程輸出之前結束,可刪除 //不理解的話你註釋掉這個sleep體會一下就知道了 } return 0; }
這裏為什麽有這麽多個sleep 呢,搖一搖你們的小腦袋----有沒有聽見水聲???hhh
書上有句話“向管道傳遞數據的時候,先讀的程序會把數據先取走”
看到這裏明白了嗎??? 簡而言之就是數據進入管道就變成了無主數據,這時候如果我子進程先寫入數據,在父進程沒有取出數據前又read把自己的數據給讀出來了!!!!大問題!大問題!對吧,這是要搞事情的節奏啊,被誰打死都不知道!!!
那麽如何避免這個問題呢?---一個管道不夠,我建兩個唄---唉,真是的。。。。。
只用1個管道進行雙向通信並非易事,需要預測並控制運行流程。因此創建2個管道完成雙向通信,各自負責不同的數據流動即可:
由上圖可知,是用2個管道可以避免程序流程的預測或控制。
接下來看雙通道實現通信代碼:
/* 雙管道實現進程間通信 */ #include<stdio.h> #include<unistd.h> #define BUF_SIZE 30 int main(int argc,char *argv[]) { int fds1[2],fds2[2]; char str1[] = "Who are you?"; char str2[] = "Thank you for your message!"; char buf[BUF_SIZE]; pid_t pid; pipe(fds1), pipe(fds2); //創建兩個管道 pid = fork(); if (pid == 0) { write(fds1[1],str1,sizeof(str1)); //子進程通過數組fds1傳輸數據 read(fds2[0],buf,BUF_SIZE); printf("Child proc output: %s \n",buf); } else { read(fds1[0],buf,BUF_SIZE); printf("Parent proc output: %s \n",buf); write(fds2[1],str2,sizeof(str2)); //父進程通過數組fds2傳輸數據 sleep(3); } return 0; }
輸入結果:Parent proc output: ”Who are you?";
Child proc output: "Thank you for your message!";
好的基本概念都介紹完了,那我們用一下玩玩唄???
四.基於多進程的回聲服務端實現
註意啦這裏是對我上一章博客代碼的擴充,沒有看我的上一張網絡編程(二)......可以去看看了
這裏對網絡編程(二)加了一個功能,“可以將回聲客戶端傳輸的字符串按序保存到文件中去”
LINUX 下:
/* 實現並發服務器端 */ /* echo_storeserv.c */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<signal.h> #include<sys/wait.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUF_SIZE 100 void error_handling(char *message) { fputs(message,stderr); fputc(‘\n‘,stderr); exit(1); } /* Handler */ void read_childproc(int sig) { pid_t pid; int status; pid = waitpid(-1,&status,WNOHANG); printf("removed proc id: %d \n",pid); } int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; int fds[2]; pid_t pid; struct sigaction act; socklen_t adr_sz; int str_len, state; char buf[BUF_SIZE]; if (argc != 2) { printf("Usage: %s <port> \n",argv[0]); exit(1); } act.sa_handler = read_childproc; //設置信號處理函數 sigemptyset(&act.sa_mask); act.sa_flags = 0; state = sigaction(SIGCHLD,&act,0); //子進程終止時調用Handler serv_sock = socket(PF_INET,SOCK_STREAM,0); memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) error_handling("bind() error"); if (listen(serv_sock,5) == -1) error_handling("listen() error"); pipe(fds);
//這裏創建一個子進程來服務寫入文件數據 pid = fork(); if (pid == 0) { FILE* fp = fopen("echomsg.txt","wt"); char msgbuf[BUF_SIZE]; int i, len; for (i = 0; i < 10; i++ ) { len = read(fds[0],msgbuf,BUF_SIZE); //從管道出口fds[0]讀取數據並保存到文件中 fwrite((void*)msgbuf,1,len,fp); } fclose(fp); return 0; } while (1) { adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&adr_sz); if (clnt_sock == -1) continue; else puts("new client connected..."); //這裏創建一個子進程來將數據寫入管道 pid = fork(); if (pid == 0) //子進程運行區域 { close(serv_sock); while((str_len = read(clnt_sock,buf,BUF_SIZE)) != 0) { write(clnt_sock,buf,str_len); write(fds[1],buf,str_len); //將從客戶端接收到的數據寫入到管道入口fds[1]中 } close(clnt_sock); puts("client disconnected..."); return 0; //調用Handler } else //父進程運行區域 close(clnt_sock); } close(serv_sock); return 0; }
上面處理文件的進程代碼裏,可能有的人會對那個for循環怎麽實現恰好讀十次數據結束,,,有點疑惑------關鍵在於read函數,這個函數如果沒有從管道裏面讀取到數據就會繼續等待!!! 這也是大工程需要註意出現BUG的地方
這裏需要大家多開幾個客戶端來驗證服務端的效果,當10次fwrite函數調用完後,大家就可以打開文件查看結果了,如果沒有客戶端代碼可以參考我上一篇博客。
同時多進程服務端也是有缺點的,每創建一個進程就代表大量的運算與內存空間占用,相互進程數據交換也很麻煩。。。那麽怎麽解決呢,在我後面的博客也許會給出答案-----hhhhhhh
好了今天對網絡編程的學習就到這裏結束了,小飛機我要撤了去吃飯了。,,,很多人大學都很迷茫不知道學點什麽好,,,,,管他的,想那麽多幹嘛,先學了再說,對技術如有偏見,那麽你的領域就局限於此了---《一專多精》
參考書籍:《TCP/IP 網絡編程 --尹聖雨》
c++ 網絡編程(三)TCP/IP 進程間的通信原理與實現代碼