程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> socketpair理解

socketpair理解

編輯:關於C語言

 今天跟人談到socketpair的問題,晚上回來寫了個程序驗證下自己的猜測!

     先說說我的理解:socketpair創建了一對無名的套接字描述符只能在AF_UNIX域中使用),描述符存儲於一個二元數組,eg. s[2] .這對套接字可以進行雙工通信,每一個描述符既可以讀也可以寫。這個在同一個進程中也可以進行通信,向s[0]中寫入,就可以從s[1]中讀取只能從s[1]中讀取),也可以在s[1]中寫入,然後從s[0]中讀取;但是,若沒有在0端寫入,而從1端讀取,則1端的讀取操作會阻塞,即使在1端寫入,也不能從1讀取,仍然阻塞;反之亦然......

      驗證所用代碼:

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <unistd.h> 
  4. #include <sys/types.h> 
  5. #include <error.h> 
  6. #include <errno.h> 
  7. #include <sys/socket.h> 
  8. #include <stdlib.h> 
  9.  
  10. #define BUF_SIZE 30 
  11.  
  12. int main(){ 
  13.         int s[2]; 
  14.         int w,r; 
  15.         char * string = "This is a test string"; 
  16.         char * buf = (char*)calloc(1 , BUF_SIZE); 
  17.  
  18.         if( socketpair(AF_UNIX,SOCK_STREAM,0,s) == -1 ){ 
  19.                 printf("create unnamed socket pair failed:%s\n",strerror(errno) ); 
  20.                 exit(-1); 
  21.         } 
  22.  
  23.         /*******test in a single process ********/ 
  24.         if( ( w = write(s[0] , string , strlen(string) ) ) == -1 ){ 
  25.                 printf("Write socket error:%s\n",strerror(errno)); 
  26.                 exit(-1); 
  27.         } 
  28.         /*****read*******/ 
  29.         if( (r = read(s[1], buf , BUF_SIZE )) == -1){ 
  30.                 printf("Read from socket error:%s\n",strerror(errno) ); 
  31.                 exit(-1); 
  32.         } 
  33.         printf("Read string in same process : %s \n",buf); 
  34.           if( (r = read(s[0], buf , BUF_SIZE )) == -1){ 
  35.                           printf("Read from socket s0 error:%s\n",strerror(errno) ); 
  36.                                           exit(-1); 
  37.                                                   } 
  38.                                                   printf("Read from s0 :%s\n",buf); 
  39.  
  40.         printf("Test successed\n"); 
  41.         exit(0); 

 

     若fork子進程,然後在服進程關閉一個描述符eg. s[1] ,在子進程中再關閉另一個 eg. s[0]    ,則可以實現父子進程之間的雙工通信,兩端都可讀可寫;當然,仍然遵守和在同一個進程之間工作的原則,一端寫,在另一端讀取;

     這和pipe有一定的區別,pipe是單工通信,一端要麼是讀端要麼是寫端,而socketpair實現了雙工套接字,也就沒有所謂的讀端和寫端的區分

驗證代碼:

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <unistd.h> 
  4. #include <sys/types.h> 
  5. #include <error.h> 
  6. #include <errno.h> 
  7. #include <sys/socket.h> 
  8. #include <stdlib.h> 
  9.  
  10. #define BUF_SIZE 30 
  11.  
  12. int main(){ 
  13.         int s[2]; 
  14.         int w,r; 
  15.         char * string = "This is a test string"; 
  16.         char * buf = (char*)calloc(1 , BUF_SIZE); 
  17.         pid_t pid; 
  18.  
  19.         if( socketpair(AF_UNIX,SOCK_STREAM,0,s) == -1 ){ 
  20.                 printf("create unnamed socket pair failed:%s\n",strerror(errno) ); 
  21.                 exit(-1); 
  22.         } 
  23.  
  24.         /***********Test : fork but don't close any fd in neither parent nor child process***********/ 
  25.         if( ( pid = fork() ) > 0 ){ 
  26.                 printf("Parent process's pid is %d\n",getpid()); 
  27.              close(s[1]); 
  28.                 if( ( w = write(s[0] , string , strlen(string) ) ) == -1 ){ 
  29.                         printf("Write socket error:%s\n",strerror(errno)); 
  30.                         exit(-1); 
  31.                 } 
  32.         }else if(pid == 0){ 
  33.                 printf("Fork child process successed\n"); 
  34.                 printf("Child process's pid is :%d\n",getpid()); 
  35.                 close(s[0]); 
  36.         }else{ 
  37.                 printf("Fork failed:%s\n",strerror(errno)); 
  38.                 exit(-1); 
  39.         } 
  40.  
  41.         /*****read***In parent and child****/ 
  42.         if( (r = read(s[1], buf , BUF_SIZE )) == -1){ 
  43.                 printf("Pid %d read from socket error:%s\n",getpid() , strerror(errno) ); 
  44.                 exit(-1); 
  45.         } 
  46.         printf("Pid %d read string in same process : %s \n",getpid(),buf); 
  47.         printf("Test successed , %d\n",getpid()); 
  48.         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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved