今天跟人談到socketpair的問題,晚上回來寫了個程序驗證下自己的猜測!
先說說我的理解:socketpair創建了一對無名的套接字描述符只能在AF_UNIX域中使用),描述符存儲於一個二元數組,eg. s[2] .這對套接字可以進行雙工通信,每一個描述符既可以讀也可以寫。這個在同一個進程中也可以進行通信,向s[0]中寫入,就可以從s[1]中讀取只能從s[1]中讀取),也可以在s[1]中寫入,然後從s[0]中讀取;但是,若沒有在0端寫入,而從1端讀取,則1端的讀取操作會阻塞,即使在1端寫入,也不能從1讀取,仍然阻塞;反之亦然......
驗證所用代碼:
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <error.h>
- #include <errno.h>
- #include <sys/socket.h>
- #include <stdlib.h>
- #define BUF_SIZE 30
- int main(){
- int s[2];
- int w,r;
- char * string = "This is a test string";
- char * buf = (char*)calloc(1 , BUF_SIZE);
- if( socketpair(AF_UNIX,SOCK_STREAM,0,s) == -1 ){
- printf("create unnamed socket pair failed:%s\n",strerror(errno) );
- exit(-1);
- }
- /*******test in a single process ********/
- if( ( w = write(s[0] , string , strlen(string) ) ) == -1 ){
- printf("Write socket error:%s\n",strerror(errno));
- exit(-1);
- }
- /*****read*******/
- if( (r = read(s[1], buf , BUF_SIZE )) == -1){
- printf("Read from socket error:%s\n",strerror(errno) );
- exit(-1);
- }
- printf("Read string in same process : %s \n",buf);
- if( (r = read(s[0], buf , BUF_SIZE )) == -1){
- printf("Read from socket s0 error:%s\n",strerror(errno) );
- exit(-1);
- }
- printf("Read from s0 :%s\n",buf);
- printf("Test successed\n");
- exit(0);
- }
若fork子進程,然後在服進程關閉一個描述符eg. s[1] ,在子進程中再關閉另一個 eg. s[0] ,則可以實現父子進程之間的雙工通信,兩端都可讀可寫;當然,仍然遵守和在同一個進程之間工作的原則,一端寫,在另一端讀取;
這和pipe有一定的區別,pipe是單工通信,一端要麼是讀端要麼是寫端,而socketpair實現了雙工套接字,也就沒有所謂的讀端和寫端的區分
驗證代碼:
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <error.h>
- #include <errno.h>
- #include <sys/socket.h>
- #include <stdlib.h>
- #define BUF_SIZE 30
- int main(){
- int s[2];
- int w,r;
- char * string = "This is a test string";
- char * buf = (char*)calloc(1 , BUF_SIZE);
- pid_t pid;
- if( socketpair(AF_UNIX,SOCK_STREAM,0,s) == -1 ){
- printf("create unnamed socket pair failed:%s\n",strerror(errno) );
- exit(-1);
- }
- /***********Test : fork but don't close any fd in neither parent nor child process***********/
- if( ( pid = fork() ) > 0 ){
- printf("Parent process's pid is %d\n",getpid());
- close(s[1]);
- if( ( w = write(s[0] , string , strlen(string) ) ) == -1 ){
- printf("Write socket error:%s\n",strerror(errno));
- exit(-1);
- }
- }else if(pid == 0){
- printf("Fork child process successed\n");
- printf("Child process's pid is :%d\n",getpid());
- close(s[0]);
- }else{
- printf("Fork failed:%s\n",strerror(errno));
- exit(-1);
- }
- /*****read***In parent and child****/
- if( (r = read(s[1], buf , BUF_SIZE )) == -1){
- printf("Pid %d read from socket error:%s\n",getpid() , strerror(errno) );
- exit(-1);
- }
- printf("Pid %d read string in same process : %s \n",getpid(),buf);
- printf("Test successed , %d\n",getpid());
- exit(0);
- }
以上代碼中在父子進程之間各關閉了一個描述符,則在父進程寫可從子進程讀取,反之若子進程寫,父進程同樣可以讀取;大家可以驗證下
另外,我也測試了在父子進程中都不close(s[1]),也就是保持兩個讀端,則父進程能夠讀到string串,但子進程讀取空串,或者子進程先讀了數據,父進程阻塞於read操作!
之所以子進程能讀取父進程的string,是因為fork時,子進程繼承了父進程的文件描述符的,同時也就得到了一個和父進程指向相同文件表項的指針;若父子進程均不關閉讀端,因為指向相同的文件表項,這兩個進程就有了競爭關系,爭相讀取這個字符串.父進程read後將數據轉到其應用緩沖區,而子進程就得不到了,只有一份數據拷貝若將父進程阻塞一段時間,則收到數據的就是子進程了,已經得到驗證,讓父進程sleep(3),子進程獲得string,而父進程獲取不到而是阻塞)
有網友"笨笨"回復:
“若將父進程阻塞一段時間,則收到數據的就是子進程了,已經得到驗證,讓父進程sleep(3),子進程獲得string,而父進程獲取不到”
我驗證的情況是,父進程一直阻塞在read上。我想不明白,為什麼這時候父進程不能讀取數據呢。
而上一種情況,父進程先讀取數據,子進程仍然可以讀取數據數據為空),但子進程不會阻塞在read上。
關於這個問題,解釋如下:
1.該網友說的情況的確存在,如果先讓子進程sleep,此時父進程獲得數據,子進程被喚醒之後讀到EOF返回;若是讓父進程sleep先,子進程先獲取數據,之後父進程被喚醒卻是一直阻塞不能返回.按理來說這兩種情況應該沒差別,這個區別下文描述.
2.對於網友提到問題的這個測試,我最初的目的是想說明如果通過產生子進程的方式,對一個寫端同時有多個讀端,這這些讀端之間相互競爭.我們可以用個更有說服力的測試方法來看出這個問題.原來的測試是讓一個進程sleep然後另一個進程讀完所有字符,可以看到之後醒來的進程就讀不到任何字符了.更好的方法是先有一個進程讀取一部分的字符,然後第二個進程被喚醒,會發現這第二個進程還能讀到一些字符,而這些字符是第一個進程讀完剩下的.
3.第一條中的遺留問題,為什麼這兩種情況有不同的表現.
原因是:如果子進程先sleep,父進程讀取完數據之後,父進程退出,此時寫端s[0]的引用計數變為0(之前子進程已主動close了一次),被系統釋放,根據read的語義,當子進程被喚醒後會讀取到EOF;但是當我們先讓父進程sleep的時候,子進程讀取完後退出,由於寫端在父進程,沒有被釋放,所以父進程此時阻塞在讀操作上.
用另外一個測試來證明,我們在子進程中不主動執行close[0],也就是有兩個寫端,然後其他不變,子進程先sleep,父進程先讀取到數據然後退出,但此時更剛剛有個區別,父進程退出的時候s[0]這個寫端的描述符並不會減到0,因為子進程中還持有一個引用,所以寫端健在,子進程被喚醒之後不會讀到EOF返回,而是阻塞在讀操作上
最後,有關socketpair在內核中實現的一點點描述:
socketpair會創建兩個描述符,但改描述符不屬於任何的實際文件系統,而是網絡文件系統,虛擬的.同時內核會將這兩個描述符彼此設為自己的peer即對端這裡即解決了如何標識讀寫端,可以想象,兩個描述符互為讀寫緩沖區,即解決了這個問題).然後應用相應socket家族裡的read/write函數執行讀寫操作.
有了這個基礎,即可明白為什麼試用fork產生的兩個子進程都不關閉讀端的時候會競爭,如上所述,他們共享相同的文件表項,有相同的inode和偏移量,兩個進程的操作當然是相互影響的.
本文出自 “流離and逍遙” 博客,請務必保留此出處http://liulixiaoyao.blog.51cto.com/1361095/533469