青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

tqsheng

go.....
隨筆 - 366, 文章 - 18, 評(píng)論 - 101, 引用 - 0
數(shù)據(jù)加載中……

linux 客戶端 Socket 非阻塞connect編程(正文)


linux 客戶端 Socket 非阻塞connect編程(正文)/*開發(fā)過程與源碼解析
  開發(fā)測(cè)試環(huán)境:虛擬機(jī)CentOS,windows網(wǎng)絡(luò)調(diào)試助手
  非阻塞模式有3種用途
  1.三次握手同時(shí)做其他的處理。connect要花一個(gè)往返時(shí)間完成,從幾毫秒的局域網(wǎng)到幾百毫秒或幾秒的廣域網(wǎng)。這段時(shí)間可能有一些其他的處理要執(zhí)行,比如數(shù)據(jù)準(zhǔn)備,預(yù)處理等。
  2.用這種技術(shù)建立多個(gè)連接。這在web瀏覽器中很普遍.
  3.由于程序用select等待連接完成,可以設(shè)置一個(gè)select等待時(shí)間限制,從而縮短connect超時(shí)時(shí)間。多數(shù)實(shí)現(xiàn)中,connect的超時(shí)時(shí)間在75秒到幾分鐘之間。有時(shí)程序希望在等待一定時(shí)間內(nèi)結(jié)束,使用非阻塞connect可以防止阻塞75秒,在多線程網(wǎng)絡(luò)編程中,尤其必要。 例如有一個(gè)通過建立線程與其他主機(jī)進(jìn)行socket通信的應(yīng)用程序,如果建立的線程使用阻塞connect與遠(yuǎn)程通信,當(dāng)有幾百個(gè)線程并發(fā)的時(shí)候,由于網(wǎng)絡(luò)延遲而全部阻塞,阻塞的線程不會(huì)釋放系統(tǒng)的資源,同一時(shí)刻阻塞線程超過一定數(shù)量時(shí)候,系統(tǒng)就不再允許建立新的線程(每個(gè)進(jìn)程由于進(jìn)程空間的原因能產(chǎn)生的線程有限),如果使用非阻塞的connect,連接失敗使用select等待很短時(shí)間,如果還沒有連接后,線程立刻結(jié)束釋放資源,防止大量線程阻塞而使程序崩潰。
  目前connect非阻塞編程的普遍思路是:
  在一個(gè)TCP套接口設(shè)置為非阻塞后,調(diào)用connect,connect會(huì)在系統(tǒng)提供的errno變量中返回一個(gè)EINRPOCESS錯(cuò)誤,此時(shí)TCP的三路握手繼續(xù)進(jìn)行。之后可以用select函數(shù)檢查這個(gè)連接是否建立成功。以下實(shí)驗(yàn)基于unix網(wǎng)絡(luò)編程和網(wǎng)絡(luò)上給出的普遍示例,在經(jīng)過大量測(cè)試之后,發(fā)現(xiàn)其中有很多方法,在linux中,并不適用。
  我先給出了重要源碼的逐步分析,在最后給出完整的connect非阻塞源碼。
  1.首先填寫套接字結(jié)構(gòu),包括遠(yuǎn)程的ip,通信端口如下: */
  struct sockaddr_in serv_addr;
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_port=htons(9999);
  serv_addr.sin_addr.s_addr = inet_addr("58.31.231.255"); //inet_addr轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
  bzero(&(serv_addr.sin_zero),8);
  // 2.建立socket套接字:
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
  perror("socket creat error");
  return 1;
  }
  // 3.將socket建立為非阻塞,此時(shí)socket被設(shè)置為非阻塞模式
  flags = fcntl(sockfd,F_GETFL,0);//獲取建立的sockfd的當(dāng)前狀態(tài)(非阻塞)
  fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//將當(dāng)前sockfd設(shè)置為非阻塞
  /*4. 建立connect連接,此時(shí)socket設(shè)置為非阻塞,connect調(diào)用后,無論連接是否建立立即返回-1,同時(shí)將errno(包含errno.h就可以直接使用)設(shè)置為EINPROGRESS, 表示此時(shí)tcp三次握手仍舊進(jìn)行,如果errno不是EINPROGRESS,則說明連接錯(cuò)誤,程序結(jié)束。
  當(dāng)客戶端和服務(wù)器端在同一臺(tái)主機(jī)上的時(shí)候,connect回馬上結(jié)束,并返回0;無需等待,所以使用goto函數(shù)跳過select等待函數(shù),直接進(jìn)入連接后的處理部分。*/
  if ( ( n = connect( sockfd, ( struct sockaddr *)&serv_addr , sizeof(struct sockaddr)) ) < 0 )
  {
  if(errno != EINPROGRESS) return 1;
  }
  if(n==0)
  {
  printf("connect completed immediately");
  goto done;
  }
  /* 5.設(shè)置等待時(shí)間,使用select函數(shù)等待正在后臺(tái)連接的connect函數(shù),這里需要說明的是使用select監(jiān)聽socket描述符是否可讀或者可寫,如果只可寫,說明連接成功,可以進(jìn)行下面的操作。如果描述符既可讀又可寫,分為兩種情況,第一種情況是socket連接出現(xiàn)錯(cuò)誤(不要問為什么,這是系統(tǒng)規(guī)定的,可讀可寫時(shí)候有可能是connect連接成功后遠(yuǎn)程主機(jī)斷開了連接close(socket)),第二種情況是connect連接成功,socket讀緩沖區(qū)得到了遠(yuǎn)程主機(jī)發(fā)送的數(shù)據(jù)。需要通過connect連接后返回給errno的值來進(jìn)行判定,或者通過調(diào)用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函數(shù)返回值來判斷是否發(fā)生錯(cuò)誤,這里存在一個(gè)可移植性問題,在solaris中發(fā)生錯(cuò)誤返回-1,但在其他系統(tǒng)中可能返回0.我首先按unix網(wǎng)絡(luò)編程的源碼進(jìn)行實(shí)現(xiàn)。如下:*/
  FD_ZERO(&rset);
  FD_SET(sockfd,&rset);
  wset = rset;
  tval.tv_sec = 0;
  tval.tv_usec = 300000;
  int error;
  socklen_t len;
  if(( n = select(sockfd+1, &rset, &wset, NULL,&tval)) <= 0)
  {
  printf("time out connect error");
  close(sockfd);
  return -1;
  }
  If ( FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&west) )
  {
  len = sizeof(error);
  if( getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) <0)
  return 1;
  }
  /* 這里我測(cè)試了一下,按照unix網(wǎng)絡(luò)編程的描述,當(dāng)網(wǎng)絡(luò)發(fā)生錯(cuò)誤的時(shí)候,getsockopt返回-1,return -1,程序結(jié)束。網(wǎng)絡(luò)正常時(shí)候返回0,程序繼續(xù)執(zhí)行。
  可是我在linux下,無論網(wǎng)絡(luò)是否發(fā)生錯(cuò)誤,getsockopt始終返回0,不返回-1,說明linux與unix網(wǎng)絡(luò)編程還是有些細(xì)微的差別。就是說當(dāng)socket描述符可讀可寫的時(shí)候,這段代碼不起作用。不能檢測(cè)出網(wǎng)絡(luò)是否出現(xiàn)故障。
  我測(cè)試的方法是,當(dāng)調(diào)用connect后,sleep(2)休眠2秒,借助這兩秒時(shí)間將網(wǎng)絡(luò)助手?jǐn)嚅_連接,這時(shí)候select返回2,說明套接口可讀又可寫,應(yīng)該是網(wǎng)絡(luò)連接的出錯(cuò)情況。
  此時(shí),getsockopt返回0,不起作用。獲取errno的值,指示為EINPROGRESS,沒有返回unix網(wǎng)絡(luò)編程中說的ENOTCONN,EINPROGRESS表示正在試圖連接,不能表示網(wǎng)絡(luò)已經(jīng)連接失敗。
針對(duì)這種情況,unix網(wǎng)絡(luò)編程中提出了另外3種方法,這3種方法,也是網(wǎng)絡(luò)上給出的常用的非阻塞connect示例:
  a.再調(diào)用connect一次。失敗返回errno是EISCONN說明連接成功,表示剛才的connect成功,否則返回失敗。 代碼如下:*/
  int connect_ok;
  connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr) );
  switch (errno)
  {
  case EISCONN: //connect ok
  printf("connect OK \n");
  connect_ok = 1;
  break;
  case EALREADY:
  connect_0k = -1
  break;
  case EINPROGRESS: // is connecting, need to check again
  connect_ok = -1
  break;
  default: 
  printf("connect fail err=%d \n",errno);
  connect_ok = -1;
  break;
  }
  /*如程序所示,根據(jù)再次調(diào)用的errno返回值將connect_ok的值,來進(jìn)行下面的處理,connect_ok為1繼續(xù)執(zhí)行其他操作,否則程序結(jié)束。
  但這種方法我在linux下測(cè)試了,當(dāng)發(fā)生錯(cuò)誤的時(shí)候,socket描述符(我的程序里是sockfd)變成可讀且可寫,但第二次調(diào)用connect 后,errno并沒有返回EISCONN,,也沒有返回連接失敗的錯(cuò)誤,仍舊是EINPROGRESS,而當(dāng)網(wǎng)絡(luò)不發(fā)生故障的時(shí)候,第二次使用 connect連接也返回EINPROGRESS,因此也無法通過再次connect來判斷連接是否成功。
  b.unix網(wǎng)絡(luò)編程中說使用read函數(shù),如果失敗,表示connect失敗,返回的errno指明了失敗原因,但這種方法在linux上行不通,linux在socket描述符為可讀可寫的時(shí)候,read返回0,并不會(huì)置errno為錯(cuò)誤。
   c.unix網(wǎng)絡(luò)編程中說使用getpeername函數(shù),如果連接失敗,調(diào)用該函數(shù)后,通過errno來判斷第一次連接是否成功,但我試過了,無論網(wǎng)絡(luò)連接是否成功,errno都沒變化,都為EINPROGRESS,無法判斷。
  悲哀啊,即使調(diào)用getpeername函數(shù),getsockopt函數(shù)仍舊不行。
  綜上方法,既然都不能確切知道非阻塞connect是否成功,所以我直接當(dāng)描述符可讀可寫的情況下進(jìn)行發(fā)送,通過能否獲取服務(wù)器的返回值來判斷是否成功。(如果服務(wù)器端的設(shè)計(jì)不發(fā)送數(shù)據(jù),那就悲哀了。)
  程序的書寫形式出于可移植性考慮,按照unix網(wǎng)絡(luò)編程推薦寫法,使用getsocketopt進(jìn)行判斷,但不通過返回值來判斷,而通過函數(shù)的返回參數(shù)來判斷。
  6. 用select查看接收描述符,如果可讀,就讀出數(shù)據(jù),程序結(jié)束。在接收數(shù)據(jù)的時(shí)候注意要先對(duì)先前的rset重新賦值為描述符,因?yàn)閟elect會(huì)對(duì) rset清零,當(dāng)調(diào)用select后,如果socket沒有變?yōu)榭勺x,則rset在select會(huì)被置零。所以如果在程序中使用了rset,最好在使用時(shí)候重新對(duì)rset賦值。
  程序如下:*/
  FD_ZERO(&rset);
  FD_SET(sockfd,&rset);//如果前面select使用了rset,最好重新賦值
  if( ( n = select(sockfd+1,&rset,NULL, NULL,&tval)) <= 0 )
  {
  close(sockfd);
  return -1;
  } 
  if ((recvbytes=recv(sockfd, buf, 1024, 0)) ==-1)
  {
  perror("recv error!");
  close(sockfd);
  return 1;
  }
  printf("receive num %d\n",recvbytes);
  printf("%s\n",buf);
  */
非阻塞connect

在一個(gè)TCP套接口被設(shè)置為非阻塞之后調(diào)用connect,connect會(huì)立即返回EINPROGRESS錯(cuò)誤,表示連接操作正在進(jìn)行中,但是仍未完成;同時(shí)TCP的三路握手操作繼續(xù)進(jìn)行;在這之后,我們可以調(diào)用select來檢查這個(gè)鏈接是否建立成功;非阻塞connect有三種用途:
1.我們可以在三路握手的同時(shí)做一些其它的處理.connect操作要花一個(gè)往返時(shí)間完成,而且可以是在任何地方,從幾個(gè)毫秒的局域網(wǎng)到幾百毫秒或幾秒的廣域網(wǎng).在這段時(shí)間內(nèi)我們可能有一些其他的處理想要執(zhí)行;
2.可以用這種技術(shù)同時(shí)建立多個(gè)連接.在Web瀏覽器中很普遍;
3.由于我們使用select來等待連接的完成,因此我們可以給select設(shè)置一個(gè)時(shí)間限制,從而縮短connect的超時(shí)時(shí)間.在大多數(shù)實(shí)現(xiàn)中,connect的超時(shí)時(shí)間在75秒到幾分鐘之間.有時(shí)候應(yīng)用程序想要一個(gè)更短的超時(shí)時(shí)間,使用非阻塞connect就是一種方法;
非阻塞connect聽起來雖然簡(jiǎn)單,但是仍然有一些細(xì)節(jié)問題要處理:
1.即使套接口是非阻塞的,如果連接的服務(wù)器在同一臺(tái)主機(jī)上,那么在調(diào)用connect建立連接時(shí),連接通常會(huì)立即建立成功.我們必須處理這種情況;
2.源自Berkeley的實(shí)現(xiàn)(和Posix.1g)有兩條與select和非阻塞IO相關(guān)的規(guī)則:
  A:當(dāng)連接建立成功時(shí),套接口描述符變成可寫;
  B:當(dāng)連接出錯(cuò)時(shí),套接口描述符變成既可讀又可寫;
  注意:當(dāng)一個(gè)套接口出錯(cuò)時(shí),它會(huì)被select調(diào)用標(biāo)記為既可讀又可寫;

非阻塞connect有這么多好處,但是處理非阻塞connect時(shí)會(huì)遇到很多可移植性問題;

處理非阻塞connect的步驟:
第一步:創(chuàng)建socket,返回套接口描述符;
第二步:調(diào)用fcntl把套接口描述符設(shè)置成非阻塞;
第三步:調(diào)用connect開始建立連接;
第四步:判斷連接是否成功建立;
       A:如果connect返回0,表示連接簡(jiǎn)稱成功(服務(wù)器可客戶端在同一臺(tái)機(jī)器上時(shí)就有可能發(fā)生這種情況);
       B:調(diào)用select來等待連接建立成功完成;
         如果select返回0,則表示建立連接超時(shí);我們返回超時(shí)錯(cuò)誤給用戶,同時(shí)關(guān)閉連接,以防止三路握手操作繼續(xù)進(jìn)行下去;
         如果select返回大于0的值,則需要檢查套接口描述符是否可讀或可寫;如果套接口描述符可讀或可寫,則我們可以通過調(diào)用getsockopt來得到套接口上待處理的錯(cuò)誤(SO_ERROR),如果連接建立成功,這個(gè)錯(cuò)誤值將是0,如果建立連接時(shí)遇到錯(cuò)誤,則這個(gè)值是連接錯(cuò)誤所對(duì)應(yīng)的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
"讀取套接口上的錯(cuò)誤"是遇到的第一個(gè)可移植性問題;如果出現(xiàn)問題,getsockopt源自Berkeley的實(shí)現(xiàn)是返回0,等待處理的錯(cuò)誤在變量errno中返回;但是Solaris會(huì)讓getsockopt返回-1,errno置為待處理的錯(cuò)誤;我們對(duì)這兩種情況都要處理;

這樣,在處理非阻塞connect時(shí),在不同的套接口實(shí)現(xiàn)的平臺(tái)中存在的移植性問題,首先,有可能在調(diào)用select之前,連接就已經(jīng)建立成功,而且對(duì)方的數(shù)據(jù)已經(jīng)到來.在這種情況下,連接成功時(shí)套接口將既可讀又可寫.這和連接失敗時(shí)是一樣的.這個(gè)時(shí)候我們還得通過getsockopt來讀取錯(cuò)誤值;這是第二個(gè)可移植性問題;
移植性問題總結(jié):
1.對(duì)于出錯(cuò)的套接口描述符,getsockopt的返回值源自Berkeley的實(shí)現(xiàn)是返回0,待處理的錯(cuò)誤值存儲(chǔ)在errno中;而源自Solaris的實(shí)現(xiàn)是返回0,待處理的錯(cuò)誤存儲(chǔ)在errno中;(套接口描述符出錯(cuò)時(shí)調(diào)用getsockopt的返回值不可移植)
2.有可能在調(diào)用select之前,連接就已經(jīng)建立成功,而且對(duì)方的數(shù)據(jù)已經(jīng)到來,在這種情況下,套接口描述符是既可讀又可寫;這與套接口描述符出錯(cuò)時(shí)是一樣的;(怎樣判斷連接是否建立成功的條件不可移植)

這樣的話,在我們判斷連接是否建立成功的條件不唯一時(shí),我們可以有以下的方法來解決這個(gè)問題:
1.調(diào)用getpeername代替getsockopt.如果調(diào)用getpeername失敗,getpeername返回ENOTCONN,表示連接建立失敗,我們必須以SO_ERROR調(diào)用getsockopt得到套接口描述符上的待處理錯(cuò)誤;
2.調(diào)用read,讀取長度為0字節(jié)的數(shù)據(jù).如果read調(diào)用失敗,則表示連接建立失敗,而且read返回的errno指明了連接失敗的原因.如果連接建立成功,read應(yīng)該返回0;
3.再調(diào)用一次connect.它應(yīng)該失敗,如果錯(cuò)誤errno是EISCONN,就表示套接口已經(jīng)建立,而且第一次連接是成功的;否則,連接就是失敗的;

被中斷的connect:
如果在一個(gè)阻塞式套接口上調(diào)用connect,在TCP的三路握手操作完成之前被中斷了,比如說,被捕獲的信號(hào)中斷,將會(huì)發(fā)生什么呢?假定connect不會(huì)自動(dòng)重啟,它將返回EINTR.那么,這個(gè)時(shí)候,我們就不能再調(diào)用connect等待連接建立完成了,如果再次調(diào)用connect來等待連接建立完成的話,connect將會(huì)返回錯(cuò)誤值EADDRINUSE.在這種情況下,應(yīng)該做的是調(diào)用select,就像在非阻塞式connect中所做的一樣.然后,select在連接建立成功(使套接口描述符可寫)或連接建立失敗(使套接口描述符既可讀又可寫)時(shí)返回;

 
 

posted on 2012-12-18 11:44 tqsheng 閱讀(5274) 評(píng)論(2)  編輯 收藏 引用

評(píng)論

# re: linux 客戶端 Socket 非阻塞connect編程(正文)  回復(fù)  更多評(píng)論   

對(duì)于面向連接的socket類型(SOCK_STREAM,SOCK_SEQPACKET)在讀寫數(shù)據(jù)之前必須建立連接,首先服務(wù)器端socket必須在一個(gè)客戶端知道的地址進(jìn)行監(jiān)聽,也就是創(chuàng)建socket之后必須調(diào)用bind綁定到一個(gè)指定的地址,然后調(diào)用int listen(int sockfd, int backlog);進(jìn)行監(jiān)聽。此時(shí)服務(wù)器socket允許客戶端進(jìn)行連接,backlog提示沒被accept的客戶連接請(qǐng)求隊(duì)列的大小,系統(tǒng)決定實(shí)際的值,最大值定義為SOMAXCONN在頭文件<sys/socket.h>里面。如果某種原因?qū)е路?wù)器端進(jìn)程未及時(shí)accpet客戶連接而導(dǎo)致此隊(duì)列滿了的話則新的客戶端連接請(qǐng)求被拒絕(在工作中遇到過此情況,IONA ORBIX(CORBA中間件)由于沒有配置超時(shí)時(shí)間結(jié)果在WIFI網(wǎng)絡(luò)中傳輸數(shù)據(jù)出現(xiàn)異常情況一直阻塞而無機(jī)會(huì)調(diào)用accept接受新的客戶請(qǐng)求,于是最終隊(duì)列滿導(dǎo)致新的客戶連接被拒絕)。
  調(diào)用listen之后當(dāng)有客戶端連接到達(dá)的時(shí)候調(diào)用int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);接受客戶端連接建立起連接返回用于連接數(shù)據(jù)傳送的socket描述符,進(jìn)行監(jiān)聽的socket可以用于繼續(xù)監(jiān)聽客戶端的連接請(qǐng)求,返回的socket描述符跟監(jiān)聽的socket類型一致。如果addr不為NULL,則客戶端發(fā)起連接請(qǐng)求的socket地址信息會(huì)通過addr進(jìn)行返回。如果監(jiān)聽的socket描述符為阻塞模式則accept一直會(huì)阻塞直到有客戶發(fā)起連接請(qǐng)求,如果監(jiān)聽的socket描述符為非阻塞模式則如果當(dāng)前沒有可用的客戶連接請(qǐng)求,則返回-1(errno設(shè)置為EAGAIN)。可以使用select函數(shù)對(duì)監(jiān)聽的socket描述符進(jìn)行多路分離,如果有客戶連接請(qǐng)求則select將監(jiān)聽的socket描述符設(shè)置為可讀(注意,如果監(jiān)聽的socket為阻塞模式而使用select進(jìn)行多路分離則可能造成select返回可讀但是調(diào)用accept會(huì)被阻塞住的情況,原因是在調(diào)用accept之前客戶端可能主動(dòng)關(guān)閉連接或者發(fā)送RST異常關(guān)閉連接,因此select最好跟非阻塞socket搭配使用)。
  客戶端調(diào)用int connect(int sockfd, const struct sockaddr *addr, socklen_t len);發(fā)起對(duì)服務(wù)器的socket的連接請(qǐng)求,如果客戶端socket描述符為阻塞模式則會(huì)一直阻塞到連接建立或者連接失敗(注意阻塞模式的超時(shí)時(shí)間可能為75秒到幾分鐘之間),而如果為非阻塞模式,則調(diào)用connect之后如果連接不能馬上建立則返回-1(errno設(shè)置為EINPROGRESS,注意連接也可能馬上建立成功比如連接本機(jī)的服務(wù)器進(jìn)程),如果沒有馬上建立返回,此時(shí)TCP的三路握手動(dòng)作在背后繼續(xù),而程序可以做其他的東西,然后調(diào)用select檢測(cè)非阻塞connect是否完成(此時(shí)可以指定select的超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間可以設(shè)置為比connect的超時(shí)時(shí)間短),如果select超時(shí)則關(guān)閉socket,然后可以嘗試創(chuàng)建新的socket重新連接,如果select返回非阻塞socket描述符可寫則表明連接建立成功,如果select返回非阻塞socket描述符既可讀又可寫則表明連接出錯(cuò)(注意:這兒必須跟另外一種連接正常的情況區(qū)分開來,就是連接建立好了之后,服務(wù)器端發(fā)送了數(shù)據(jù)給客戶端,此時(shí)select同樣會(huì)返回非阻塞socket描述符既可讀又可寫,這時(shí)可以通過以下方法區(qū)分:
  1.調(diào)用getpeername獲取對(duì)端的socket地址.如果getpeername返回ENOTCONN,表示連接建立失敗,然后用SO_ERROR調(diào)用getsockopt得到套接口描述符上的待處理錯(cuò)誤;
  2.調(diào)用read,讀取長度為0字節(jié)的數(shù)據(jù).如果read調(diào)用失敗,則表示連接建立失敗,而且read返回的errno指明了連接失敗的原因.如果連接建立成功,read應(yīng)該返回0;
  3.再調(diào)用一次connect.它應(yīng)該失敗,如果錯(cuò)誤errno是EISCONN,就表示套接口已經(jīng)建立,而且第一次連接是成功的;否則,連接就是失敗的;
  對(duì)于無連接的socket類型(SOCK_DGRAM),客戶端也可以調(diào)用connect進(jìn)行連接,此連接實(shí)際上并不建立類似SOCK_STREAM的連接,而僅僅是在本地保存了對(duì)端的地址,這樣后續(xù)的讀寫操作可以默認(rèn)以連接的對(duì)端為操作對(duì)象。
  當(dāng)對(duì)端機(jī)器crash或者網(wǎng)絡(luò)連接被斷開(比如路由器不工作,網(wǎng)線斷開等),此時(shí)發(fā)送數(shù)據(jù)給對(duì)端然后讀取本端socket會(huì)返回ETIMEDOUT或者EHOSTUNREACH 或者ENETUNREACH(后兩個(gè)是中間路由器判斷服務(wù)器主機(jī)不可達(dá)的情況)。
  當(dāng)對(duì)端機(jī)器crash之后又重新啟動(dòng),然后客戶端再向原來的連接發(fā)送數(shù)據(jù),因?yàn)榉?wù)器端已經(jīng)沒有原來的連接信息,此時(shí)服務(wù)器端回送RST給客戶端,此時(shí)客戶端讀本地端口返回ECONNRESET錯(cuò)誤。
  當(dāng)服務(wù)器所在的進(jìn)程正常或者異常關(guān)閉時(shí),會(huì)對(duì)所有打開的文件描述符進(jìn)行close,因此對(duì)于連接的socket描述符則會(huì)向?qū)Χ税l(fā)送FIN分節(jié)進(jìn)行正常關(guān)閉流程。對(duì)端在收到FIN之后端口變得可讀,此時(shí)讀取端口會(huì)返回0表示到了文件結(jié)尾(對(duì)端不會(huì)再發(fā)送數(shù)據(jù))。 
  當(dāng)一端收到RST導(dǎo)致讀取socket返回ECONNRESET,此時(shí)如果再次調(diào)用write發(fā)送數(shù)據(jù)給對(duì)端則觸發(fā)SIGPIPE信號(hào),信號(hào)默認(rèn)終止進(jìn)程,如果忽略此信號(hào)或者從SIGPIPE的信號(hào)處理程序返回則write出錯(cuò)返回EPIPE。
  可以看出只有當(dāng)本地端口主動(dòng)發(fā)送消息給對(duì)端才能檢測(cè)出連接異常中斷的情況,搭配select進(jìn)行多路分離的時(shí)候,socket收到RST或者FIN時(shí)候,select返回可讀(心跳消息就是用于檢測(cè)連接的狀態(tài))。也可以使用socket的KEEPLIVE選項(xiàng),依賴socket本身偵測(cè)socket連接異常中斷的情況。
  發(fā)送socket數(shù)據(jù)有以下方法:
  調(diào)用ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);,只能用于建立好了連接的socket(面向連接的SOCK_STREAM或者調(diào)用了connect的SOCK_DGRAM)。flags取值如下:
  MSG_DONTROUTE 對(duì)數(shù)據(jù)不進(jìn)行路由
  MSG_DONTWAIT 不等待數(shù)據(jù)發(fā)送完成
  MSG_EOR 數(shù)據(jù)包結(jié)尾
  MSG_OOB 帶外數(shù)據(jù)
  注意send函數(shù)成功返回并不代表對(duì)端一定收到了發(fā)送的消息,另外對(duì)于數(shù)據(jù)報(bào)協(xié)議如果發(fā)送的數(shù)據(jù)大于一個(gè)數(shù)據(jù)報(bào)長度則發(fā)送失敗(errno設(shè)置為EMSGSIZE)。
2012-12-18 11:46 | tqsheng

# re: linux 客戶端 Socket 非阻塞connect編程(正文)  回復(fù)  更多評(píng)論   

如何設(shè)置socket的Connect超時(shí)(linux)

今天發(fā)現(xiàn)自己的系統(tǒng)存在很嚴(yán)重缺陷,當(dāng)前臺(tái)關(guān)閉的時(shí)候后臺(tái)就無法正常工作,原因 很好定位,后臺(tái)的socket連接超時(shí)時(shí)間過長,系統(tǒng)默認(rèn)時(shí)間好像是75秒,于是找資料,根據(jù)下邊文章中的內(nèi)容解決了,把超時(shí)時(shí)間設(shè)為5秒后,感覺好多 了。看來還有好多東西需要慢慢挖掘阿!


如何設(shè)置socket的Connect超時(shí)(linux)

[From]http://dev.cbw.com/c/c/200510195601_4292587.shtml

1. 首先將標(biāo)志位設(shè)為Non-blocking模式,準(zhǔn)備在非阻塞模式下調(diào)用connect函數(shù)
2.調(diào)用connect,正常情況下,因?yàn)門CP三次 握手需要一些時(shí)間;而非阻塞調(diào)用只要不能立即完成就會(huì)返回錯(cuò)誤,所以這里會(huì)返回EINPROGRESS,表示在建立連接但還沒有完成。
3.在讀套 接口描述符集(fd_set rset)和寫套接口描述符集(fd_set wset)中將當(dāng)前套接口置位(用FD_ZERO()、FD_SET()宏),并設(shè)置好超時(shí)時(shí)間(struct timeval *timeout)
4. 調(diào)用select( socket, &rset, &wset, NULL, timeout )
返回0表示connect超 時(shí)
如果你設(shè)置的超時(shí)時(shí)間大于75秒就沒有必要這樣做了,因?yàn)閮?nèi)核中對(duì)connect有超時(shí)限制就是75秒。


[From]http://www.ycgczj.com.cn/34733.html
網(wǎng) 絡(luò)編程中socket的分量我想大家都很清楚了,socket也就是套接口,在套接口編程中,提到超時(shí)的概念,我們一下子就能想到3個(gè):發(fā)送超時(shí),接收超 時(shí),以及select超時(shí)(注: select函數(shù)并不是只用于套接口的,但是套接口編程中用的比較多),在connect到目標(biāo)主機(jī)的時(shí)候,這個(gè)超時(shí)是不由我們來設(shè)置的。不過正常情況下 這個(gè)超時(shí)都很長,并且connect又是一個(gè)阻塞方法,一個(gè)主機(jī)不能連接,等著connect返回還能忍受,你的程序要是要試圖連接多個(gè)主機(jī),恐怕遇到多 個(gè)不能連接的主機(jī)的時(shí)候,會(huì)塞得你受不了的。我也廢話少說,先說說我的方法,如果你覺得你已掌握這種方法,你就不用再看下去了,如果你還不了解,我愿意與 你分享。本文是已在Linux下的程序?yàn)槔樱贿^拿到Windows中方法也是一樣,無非是換幾個(gè)函數(shù)名字罷了。
Linux中要給 connect設(shè)置超時(shí),應(yīng)該是有兩種方法的。一種是該系統(tǒng)的一些參數(shù),這個(gè)方法我不講,因?yàn)槲抑v不清楚:P,它也不是編程實(shí)現(xiàn)的。另外一種方法就是變相 的實(shí)現(xiàn)connect的超時(shí),我要講的就是這個(gè)方法,原理上是這樣的:
1.建立socket
2.將該socket設(shè)置為非阻塞模式
3. 調(diào)用connect()
4.使用select()檢查該socket描述符是否可寫(注意,是可寫)
5.根據(jù)select()返回的結(jié)果 判斷connect()結(jié)果
6.將socket設(shè)置為阻塞模式(如果你的程序不需要用阻塞模式的,這步就省了,不過一般情況下都是用阻塞模式的, 這樣也容易管理)
如果你對(duì)網(wǎng)絡(luò)編程很熟悉的話,其實(shí)我一說出這個(gè)過程你就知道怎么寫你的程序了,下面給出我寫的一段程序,僅供參考。
/******************************
* Time out for connect()
* Write by Kerl W
******************************/
#include <sys/socket.h>
#include <sys/types.h>
#define TIME_OUT_TIME 20 //connect超時(shí)時(shí)間20秒
int main(int argc , char **argv)
{
………………
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) exit(1);
struct sockaddr_in serv_addr;
………//以服務(wù)器地址填充結(jié)構(gòu)serv_addr
int error=-1, len;
len = sizeof(int);
timeval tm;
fd_set set;
unsigned long ul = 1;
ioctl(sockfd, FIONBIO, &ul); //設(shè)置為非阻塞模式
bool ret = false;
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
{
tm.tv_set = TIME_OUT_TIME;
tm.tv_uset = 0;
FD_ZERO(&set);
FD_SET(sockfd, &set);
if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0) ret = true;
else ret = false;
} else ret = false;
}
else ret = true;
ul = 0;
ioctl(sockfd, FIONBIO, &ul); //設(shè)置為阻塞模式
if(!ret)
{
close( sockfd );
fprintf(stderr , "Cannot Connect the server!n");
return;
}
fprintf( stderr , "Connected!n");
//下面還可以進(jìn)行發(fā)包收包操作
……………
}

以上代碼片段, 僅供參考,也是為初學(xué)者提供一些提示,主要用到的幾個(gè)函數(shù),select, ioctl, getsockopt都可以找到相關(guān)資料,具體用法我這里就不贅述了,你只需要在linux中輕輕的敲一個(gè)man <函數(shù)名>就能夠看到它的用法。
此外我需要說明的幾點(diǎn)是,雖然我們用ioctl把套接口設(shè)置為非阻塞模式,不過select本身是阻 塞的,阻塞的時(shí)間就是其超時(shí)的時(shí)間由調(diào)用select 的時(shí)候的最后一個(gè)參數(shù)timeval類型的變量指針指向的timeval結(jié)構(gòu)變量來決定的,timeval結(jié)構(gòu)由一個(gè)表示秒數(shù)的和一個(gè)表示微秒數(shù) (long類型)的成員組成,一般我們?cè)O(shè)置了秒數(shù)就行了,把微妙數(shù)設(shè)為0(注:1秒等于100萬微秒)。而select函數(shù)另一個(gè)值得一提的參數(shù)就是上面 我們用到的fd_set類型的變量指針。調(diào)用之前,這個(gè)變量里面存了要用select來檢查的描述符,調(diào)用之后,針對(duì)上面的程序這里面是可寫的描述符,我 們可以用宏FD_ISSET來檢查某個(gè)描述符是否在其中。由于我這里只有一個(gè)套接口描述符,我就沒有使用FD_ISSET宏來檢查調(diào)用select之后這 個(gè)sockfd是否在set里面,其實(shí)是需要加上這個(gè)判斷的。不過我用了getsockopt來檢查,這樣才可以判斷出這個(gè)套接口是否是真的連接上了,因 為我們只是變相的用select來檢查它是否連接上了,實(shí)際上select檢查的是它是否可寫,而對(duì)于可寫,是針對(duì)以下三種條件任一條件滿足時(shí)都表示可寫 的:
1)套接口發(fā)送緩沖區(qū)中的可用控件字節(jié)數(shù)大于等于套接口發(fā)送緩沖區(qū)低潮限度的當(dāng)前值,且或者i)套接口已連接,或者ii)套接口不要求連接 (UDP方式的)
2)連接的寫這一半關(guān)閉。
3)有一個(gè)套接口錯(cuò)誤待處理。
這樣,我們就需要用getsockopt函數(shù)來獲取套接 口目前的一些信息來判斷是否真的是連接上了,沒有連接上的時(shí)候還能給出發(fā)生了什么錯(cuò)誤,當(dāng)然我程序中并沒有標(biāo)出那么多狀態(tài),只是簡(jiǎn)單的表示可連接/不可連 接。
下面我來談?wù)剬?duì)這個(gè)程序測(cè)試的結(jié)果。我針對(duì)3種情形做了測(cè)試:
1. 目標(biāo)機(jī)器網(wǎng)絡(luò)正常的情況
可以連接到目標(biāo)主機(jī),并能成功以 阻塞方式進(jìn)行發(fā)包收包作業(yè)。
2. 目標(biāo)機(jī)器網(wǎng)絡(luò)斷開的情況
在等待設(shè)置的超時(shí)時(shí)間(上面的程序中為20秒)后,顯示目標(biāo)主機(jī)不能連接。
3. 程序運(yùn)行前斷開目標(biāo)機(jī)器網(wǎng)絡(luò),超時(shí)時(shí)間內(nèi),恢復(fù)目標(biāo)機(jī)器的網(wǎng)絡(luò)
在恢復(fù)目標(biāo)主機(jī)網(wǎng)絡(luò)連接之前,程序一只等待,恢復(fù)目標(biāo)主機(jī)后,程序顯示連接目標(biāo)主 機(jī)成功,并能成功以阻塞方式進(jìn)行發(fā)包收包作業(yè)。
以上各種情況的測(cè)試結(jié)果表明,這種設(shè)置connect超時(shí)的方法是完全可行的。我自己是把這種設(shè)置 了超時(shí)的connect封裝到了自己的類庫,用在一套監(jiān)控系統(tǒng)中,到目前為止,運(yùn)行還算正常。這種編程實(shí)現(xiàn)的connect超時(shí)比起修改系統(tǒng)參數(shù)的那種方 法的有點(diǎn)就在于它只用于你的程序之中而不影響系統(tǒng)。
2012-12-18 11:47 | tqsheng

只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一区2区三区4区公司二百| 欧美午夜精品电影| 欧美国产欧美综合 | 欧美a级一区二区| 久久精品国产一区二区电影| 一区二区高清视频| 在线观看日韩欧美| 在线日韩av永久免费观看| 国产欧美一区二区在线观看| 国产精品成人一区二区三区吃奶| 欧美激情一区二区三区成人 | 欧美成人蜜桃| 欧美福利视频网站| 亚洲精品一区二区网址| 亚洲高清二区| 欧美性开放视频| 欧美四级电影网站| 国产精品午夜电影| 亚洲二区免费| 一区二区三区日韩在线观看| 99re66热这里只有精品4| 亚洲精品一区二区三区四区高清 | 亚洲高清视频的网址| 亚洲精品永久免费精品| 亚洲自拍三区| 欧美电影在线观看| 亚洲欧美另类在线观看| 老鸭窝亚洲一区二区三区| 欧美噜噜久久久xxx| 国产一区二区看久久| 一本色道久久综合亚洲91| 久久黄色级2电影| 一个人看的www久久| 麻豆91精品91久久久的内涵| 国产精品你懂的在线| 洋洋av久久久久久久一区| 久久中文字幕一区| 午夜亚洲性色福利视频| 国产精品一区二区三区观看 | 欧美精品入口| 一本大道av伊人久久综合| 欧美韩日一区二区三区| 久久综合一区| 日韩视频在线一区| 亚洲精品免费电影| 免费看av成人| 老司机一区二区| 一区二区欧美日韩| 一本色道久久综合一区 | 亚洲无限乱码一二三四麻| 欧美天天综合网| 校园激情久久| 欧美一区二区三区四区高清 | 欧美激情在线观看| 一区二区三区毛片| 欧美在线免费视屏| 亚洲精品在线三区| 亚洲在线成人精品| 亚洲国产高清高潮精品美女| 亚洲国产成人在线播放| 欧美午夜不卡| 欧美电影免费观看网站| 国产精品久久久久aaaa| 毛片精品免费在线观看| 欧美网站在线观看| 农村妇女精品| 国产亚洲在线| 亚洲一级影院| 一本综合久久| 国产一级一区二区| 国内精品久久久久久影视8| 欧美激情视频在线播放| 国产精品夜夜夜| 亚洲另类一区二区| 91久久精品国产91性色tv| 亚洲综合精品自拍| 亚洲永久精品大片| 欧美美女日韩| 国产一区二区成人| 亚洲午夜av| 性视频1819p久久| 欧美性天天影院| 亚洲一区二区在线免费观看视频| 日韩午夜视频在线观看| 欧美激情综合色| 中文日韩欧美| 久久理论片午夜琪琪电影网| 久久久久久午夜| 伊人久久久大香线蕉综合直播| 性一交一乱一区二区洋洋av| 久久国产视频网站| 91久久精品视频| 欧美天堂亚洲电影院在线播放| 99天天综合性| 久久九九电影| 99视频精品| 国产欧美日韩激情| 免费日韩精品中文字幕视频在线| 亚洲国内自拍| 欧美在线影院| 日韩亚洲综合在线| 国产亚洲a∨片在线观看| 欧美成人国产va精品日本一级| 亚洲人妖在线| 欧美激情综合| 嫩草伊人久久精品少妇av杨幂| 中文欧美在线视频| 亚洲国产精品欧美一二99| 国产精品久久久久国产a级| 两个人的视频www国产精品| 亚洲永久免费| 亚洲天堂男人| 亚洲一区二区三区在线看 | 欧美一区二区视频观看视频| 一区二区三区 在线观看视频| 国产精品视频一二三| 你懂的国产精品永久在线| 欧美一区二区网站| 亚洲影视在线播放| 亚洲视频大全| 亚洲一区二区三区成人在线视频精品 | 翔田千里一区二区| 国产精品99久久久久久白浆小说| 欧美国产日韩精品| 亚洲精品免费电影| 在线综合亚洲| 欧美影院精品一区| 久久精品亚洲国产奇米99| 久久九九电影| 欧美成人首页| 国产精品久久久久久av福利软件 | 久久婷婷麻豆| 欧美激情一区二区三区高清视频| 欧美电影在线观看完整版| 欧美日韩直播| 亚洲大胆人体在线| 亚洲欧美日韩另类| 欧美韩日精品| 久久久夜夜夜| 国产日产精品一区二区三区四区的观看方式 | 亚洲欧美另类久久久精品2019| 亚洲小说春色综合另类电影| 欧美在线一区二区| 欧美日韩在线观看一区二区| 国产日韩欧美精品在线| 中日韩在线视频| 欧美激情小视频| 亚洲欧美制服另类日韩| 欧美日韩视频在线一区二区观看视频| 欧美午夜精品理论片a级大开眼界| 欧美日韩亚洲一区二区三区| 亚洲国产日韩一区二区| 久久婷婷影院| 午夜伦欧美伦电影理论片| 午夜久久久久久久久久一区二区| 欧美成人黄色小视频| 欧美一级久久久| 国产亚洲女人久久久久毛片| 亚洲一区成人| 亚洲在线免费| 国产一区在线视频| 美女视频网站黄色亚洲| 久久免费精品视频| 亚洲激情综合| 99精品国产在热久久| 国产精品久久久久91| 久久成人精品无人区| 久久精品国产欧美激情| 黄色日韩精品| 亚洲黄色影片| 国产日韩欧美一二三区| 老司机精品视频一区二区三区| 久久久精品视频成人| 亚洲理伦在线| 性久久久久久| 日韩网站在线观看| 先锋亚洲精品| 99成人免费视频| 欧美亚洲视频在线观看| 亚洲福利小视频| 午夜精彩视频在线观看不卡 | 亚洲电影激情视频网站| 免费视频一区| 久久久久久久综合色一本| 欧美激情四色| 欧美黄在线观看| 国产亚洲美州欧州综合国| 99日韩精品| 一区二区不卡在线视频 午夜欧美不卡在| 亚洲一区三区视频在线观看| aa国产精品| 欧美紧缚bdsm在线视频| 久热精品视频在线观看一区| 国产精品国产三级国产专播品爱网 | 亚洲欧美日韩在线高清直播| 亚洲肉体裸体xxxx137| 久热精品视频| 欧美搞黄网站| 亚洲精品综合| 欧美日韩亚洲在线|