天天看點

linux 用戶端 Socket 非阻塞connect getsockopt不可用

轉載自:點選打開連結

開發測試環境:虛拟機CentOS,windows網絡調試助手

        非阻塞模式有3種用途

        1.三次握手同時做其他的處理。connect要花一個往返時間完成,從幾毫秒的區域網路到幾百毫秒或幾秒的廣域網。這段時間可能有一些其他的處理要執行,比如資料準備,預處理等。

        2.用這種技術建立多個連接配接。這在web浏覽器中很普遍.

        3.由于程式用select等待連接配接完成,可以設定一個select等待時間限制,進而縮短connect逾時時間。多數實作中,connect的逾時時間在75秒到幾分鐘之間。有時程式希望在等待一定時間内結束,使用非阻塞connect可以防止阻塞75秒,在多線程網絡程式設計中,尤其必要。   例如有一個通過建立線程與其他主機進行socket通信的應用程式,如果建立的線程使用阻塞connect與遠端通信,當有幾百個線程并發的時候,由于網絡延遲而全部阻塞,阻塞的線程不會釋放系統的資源,同一時刻阻塞線程超過一定數量時候,系統就不再允許建立新的線程(每個程序由于程序空間的原因能産生的線程有限),如果使用非阻塞的connect,連接配接失敗使用select等待很短時間,如果還沒有連接配接後,線程立刻結束釋放資源,防止大量線程阻塞而使程式崩潰。

目前connect非阻塞程式設計的普遍思路是:

在一個TCP套接口設定為非阻塞後,調用connect,connect會在系統提供的errno變量中傳回一個EINRPOCESS錯誤,此時TCP的三路握手繼續進行。之後可以用select函數檢查這個連接配接是否建立成功。以下實驗基于unix網絡程式設計和網絡上給出的普遍示例,在經過大量測試之後,發現其中有很多方法,在linux中,并不适用。

我先給出了重要源碼的逐漸分析,在最後給出完整的connect非阻塞源碼。

        1.首先填寫套接字結構,包括遠端的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轉換為網絡位元組序
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建立為非阻塞,此時socket被設定為非阻塞模式

flags = fcntl(sockfd,F_GETFL,0);//擷取建立的sockfd的目前狀态(非阻塞)
fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//将目前sockfd設定為非阻塞
           
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;
}
           
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;
}
           
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;
}
           
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);
*/
           

繼續閱讀