日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现

發布時間:2025/3/15 linux 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、《UNIX網絡編程》-非阻塞connect

在一個TCP套接口被設置為非阻塞之后調用connect,connect會立即返回EINPROGRESS錯誤,表示連接操作正在進行中,但是仍未完成;同時TCP的三路握手操作繼續進行;在這之后,我們可以調用select來檢查這個鏈接是否建立成功;

非阻塞connect有三種用途:

1.我們可以在三路握手的同時做一些其它的處理.connect操作要花一個往返時間完成,而且可以是在任何地方,從幾個毫秒的局域網到幾百毫秒或幾秒的廣域網.在這段時間內我們可能有一些其他的處理想要執行;

2.可以用這種技術同時建立多個連接.在Web瀏覽器中很普遍;

3.由于我們使用select來等待連接的完成,因此我們可以給select設置一個時間限制,從而縮短connect的超時時間.在大多數實現中,connect的超時時間在75秒到幾分鐘之間.有時候應用程序想要一個更短的超時時間,使用非阻塞connect就是一種方法;

非阻塞connect聽起來雖然簡單,但是仍然有一些細節問題要處理:

1.即使套接口是非阻塞的,如果連接的服務器在同一臺主機上,那么在調用connect建立連接時,連接通常會立即建立成功.我們必須處理這種情況;

2.源自Berkeley的實現(和Posix.1g)有兩條與select和非阻塞IO相關的規則:

A:當連接建立成功時,套接口描述符變成可寫;

B:當連接出錯時,套接口描述符變成既可讀又可寫;

注意:當一個套接口出錯時,它會被select調用標記為既可讀又可寫;

當發現套接口描述符可讀或可寫時,可進一步判斷是連接成功還是出錯。這里必須將B)和另外一種連接正常的情況區分開,就是連接建立好了之后,服務器端發送了數據給客戶端,此時select同樣會返回非阻塞socket描述符既可讀又可寫。

因此,僅從socket可讀或可寫無法判斷socket連接的狀態。

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

在處理非阻塞connect時,在不同的套接口實現的平臺中存在的移植性問題,首先,有可能在調用select之前,連接就已經建立成功,而且對方的數據已經到來.在這種情況下,連接成功時套接口將既可讀又可寫.這和連接失敗時是一樣的.這個時候我們還得通過getsockopt來讀取錯誤值;這是第二個可移植性問題;

移植性問題總結:

1.對于出錯的套接口描述符,getsockopt的返回值源自Berkeley的實現是返回0,待處理的錯誤值存儲在errno中;而源自Solaris的實現是返回0,待處理的錯誤存儲在errno中;(套接口描述符出錯時調用getsockopt的返回值不可移植)

2.有可能在調用select之前,連接就已經建立成功,而且對方的數據已經到來,在這種情況下,套接口描述符是既可讀又可寫;這與套接口描述符出錯時是一樣的;(怎樣判斷連接是否建立成功的條件不可移植)

這樣的話,在我們判斷連接是否建立成功的條件不唯一時,我們可以有以下的方法來解決這個問題:

1.調用getpeername代替getsockopt.如果調用getpeername失敗,getpeername返回ENOTCONN,表示連接建立失敗,我們必須以SO_ERROR調用getsockopt得到套接口描述符上的待處理錯誤; 此為《Unix Network Programming》一書中提供的方法,該方法在Linux環境上測試,發現是無效的

2.調用read,讀取長度為0字節的數據.如果read調用失敗,則表示連接建立失敗,而且read返回的errno指明了連接失敗的原因.如果連接建立成功,read應該返回0;

3.再調用一次connect.它應該失敗,如果錯誤errno是EISCONN,就表示套接口已經建立,而且第一次連接是成功的;否則,連接就是失敗的;

被中斷的connect(這是說明使用非阻塞connect的必要性):

如果在一個阻塞式套接口上調用connect,在TCP的三路握手操作完成之前被中斷了,比如說,被捕獲的信號中斷,將會發生什么呢?假定connect不會自動重啟,它將返回EINTR.那么,這個時候,我們就不能再調用connect等待連接建立完成了,如果再次調用connect來等待連接建立完成的話,connect將會返回錯誤值EADDRINUSE.在這種情況下,應該做的是調用select,就像在非阻塞式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設置為非阻塞

4. 建立connect連接,此時socket設置為非阻塞,connect調用后,無論連接是否建立立即返回-1,同時將errno(包含errno.h就可以直接使用)設置為EINPROGRESS, 表示此時tcp三次握手仍舊進行,如果errno不是EINPROGRESS,則說明連接錯誤,程序結束。

當客戶端和服務器端在同一臺主機上的時候,connect回馬上結束,并返回0;無需等待,所以使用goto函數跳過select等待函數,直接進入連接后的處理部分。

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.設置等待時間,使用select函數等待正在后臺連接的connect函數,這里需要說明的是使用select監聽socket描述符是否可讀或者可寫,如果只可寫,說明連接成功,可以進行下面的操作。如果描述符既可讀又可寫,分為兩種情況,第一種情況是socket連接出現錯誤(不要問為什么,這是系統規定的,可讀可寫時候有可能是connect連接成功后遠程主機斷開了連接close(socket)),第二種情況是connect連接成功,socket讀緩沖區得到了遠程主機發送的數據。需要通過connect連接后返回給errno的值來進行判定,或者通過調用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函數返回值來判斷是否發生錯誤,這里存在一個可移植性問題,在solaris中發生錯誤返回-1,但在其他系統中可能返回0.我首先按unix網絡編程的源碼進行實現。如下:

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;

}

對錯誤的處理:

這里我測試了一下,按照unix網絡編程的描述,當網絡發生錯誤的時候,getsockopt返回-1,return -1,程序結束。網絡正常時候返回0,程序繼續執行。

可是我在linux下,無論網絡是否發生錯誤,getsockopt始終返回0,不返回-1,說明linux與unix網絡編程還是有些細微的差別。就是說當socket描述符可讀可寫的時候,這段代碼不起作用。不能檢測出網絡是否出現故障。

我測試的方法是,當調用connect后,sleep(2)休眠2秒,借助這兩秒時間將網絡助手斷開連接,這時候select返回2,說明套接口可讀又可寫,應該是網絡連接的出錯情況。

此時,getsockopt返回0,不起作用。獲取errno的值,指示為EINPROGRESS,沒有返回unix網絡編程中說的ENOTCONN,EINPROGRESS表示正在試圖連接,不能表示網絡已經連接失敗。

針對這種情況,unix網絡編程中提出了另外3種方法,這3種方法,也是網絡上給出的常用的非阻塞connect示例:

a.再調用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

");

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

",errno);

connect_ok = -1;

break;

}

如程序所示,根據再次調用的errno返回值將connect_ok的值,來進行下面的處理,connect_ok為1繼續執行其他操作,否則程序結束。

但這種方法我在linux下測試了,當發生錯誤的時候,socket描述符(我的程序里是sockfd)變成可讀且可寫,但第二次調用connect 后,errno并沒有返回EISCONN,,也沒有返回連接失敗的錯誤,仍舊是EINPROGRESS,而當網絡不發生故障的時候,第二次使用 connect連接也返回EINPROGRESS,因此也無法通過再次connect來判斷連接是否成功。

在這種情況下的測試還有一個問題需要提出:

一次select之后,發現此時套接口描述字可讀或可寫,再次執行connect,此時errno始終不變,仍未EINPROGRESS,增加select的超時時間結果也一樣。

之后嘗試在select返回值為0,或返回值為1,且connect后errno仍為EINPROGRESS(115)時,再次執行select+connect,即再次檢測連接狀態。此時errno被置為EISCONN(106),connect成功。(也就是說使用這種判斷可以解決移植問題)

b.unix網絡編程中說使用read函數,如果失敗,表示connect失敗,返回的errno指明了失敗原因,但這種方法在linux上行不通,linux在socket描述符為可讀可寫的時候,read返回0,并不會置errno為錯誤。

c.unix網絡編程中說使用getpeername函數,如果連接失敗,調用該函數后,通過errno來判斷第一次連接是否成功,但我試過了,無論網絡連接是否成功,errno都沒變化,都為EINPROGRESS,無法判斷。

即使調用getpeername函數,getsockopt函數仍舊不行。

綜上方法,既然都不能確切知道非阻塞connect是否成功,所以我直接當描述符可讀可寫的情況下進行發送,通過能否獲取服務器的返回值來判斷是否成功。(如果服務器端的設計不發送數據,那就悲哀了。)

程序的書寫形式出于可移植性考慮,按照unix網絡編程推薦寫法,使用getsocketopt進行判斷,但不通過返回值來判斷,而通過函數的返回參數來判斷。

6. 用select查看接收描述符,如果可讀,就讀出數據,程序結束。在接收數據的時候注意要先對先前的rset重新賦值為描述符,因為select會對 rset清零,當調用select后,如果socket沒有變為可讀,則rset在select會被置零。所以如果在程序中使用了rset,最好在使用時候重新對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 ",recvbytes);

printf("%s ",buf);

*/

綜上所述一種更有效的判斷方法,經測試驗證,在Linux環境下是有效的:

再次調用connect,相應返回失敗,如果錯誤errno是EISCONN,表示socket連接已經建立,否則認為連接失敗。

三、非阻塞connect的實現

#include

#include

#include

#include

#include

#include

#include

#include

#include//inet_addr()

#include

#include

#include

#define PEER_IP "192.254.1.1"

#define PEER_PORT 7008

int main(int argc, char **argv)

{

int ret = 0;

int sock_fd;

int flags;

struct sockaddr_in addr;

/* obtain a socket */

sock_fd = socket(AF_INET, SOCK_STREAM, 0);

/* set non-blocking mode on socket*/

#if 1

flags = fcntl(sock_fd, F_GETFL, 0);

fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);

#else

int imode = 1;

ioctl(sock_fd, FIONBIO, &imode);

#endif

/* connect to server */

addr.sin_family = AF_INET;

addr.sin_port = htons(PEER_PORT);

addr.sin_addr.s_addr = inet_addr(PEER_IP);

int res = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

if (0 == res)

{

printf("socket connect succeed immediately.

");

ret = 0;

}

else

{

printf("get the connect result by select().

");

if (errno == EINPROGRESS)

{

int times = 0;

while (times++ < 5)

{

fd_set rfds, wfds;

struct timeval tv;

printf("errno = %d

", errno);

FD_ZERO(&rfds);

FD_ZERO(&wfds);

FD_SET(sock_fd, &rfds);

FD_SET(sock_fd, &wfds);

/* set select() time out */

tv.tv_sec = 10;

tv.tv_usec = 0;

int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);

switch (selres)

{

case -1:

printf("select error

");

ret = -1;

break;

case 0:

printf("select time out

");

ret = -1;

break;

default:

if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))

{

#if 0 // not useable in linux environment, suggested in <>

int errinfo, errlen;

if (-1 == getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &errinfo, &errlen))

{

printf("getsockopt return -1.

");

ret = -1;

break;

}

else if (0 != errinfo)

{

printf("getsockopt return errinfo = %d.

", errinfo);

ret = -1;

break;

}

ret = 0;

printf("connect ok?

");

#else

#if 1

connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

int err = errno;

if (err == EISCONN)

{

printf("connect finished 111.

");

ret = 0;

}

else

{

printf("connect failed. errno = %d

", errno);

printf("FD_ISSET(sock_fd, &rfds): %d

FD_ISSET(sock_fd, &wfds): %d

", FD_ISSET(sock_fd, &rfds) , FD_ISSET(sock_fd, &wfds));

ret = errno;

}

#else

char buff[2];

if (read(sock_fd, buff, 0) < 0)

{

printf("connect failed. errno = %d

", errno);

ret = errno;

}

else

{

printf("connect finished.

");

ret = 0;

}

#endif

#endif

}

else

{

printf("haha

");

}

}

if (-1 != selres && (ret != 0))

{

printf("check connect result again... %d

", times);

continue;

}

else

{

break;

}

}

}

else

{

printf("connect to host %s:%d failed.

", PEER_IP, PEER_PORT);

ret = errno;

}

}

if (0 == ret)

{

send(sock_fd, "12345", sizeof("12345"), 0);

}

else

{

printf("connect to host %s:%d failed.

", PEER_IP, PEER_PORT);

}

close(sock_fd);

return ret;

}

上述代碼可以解決問題了

Linux下常見的socket錯誤碼:

EACCES, EPERM:用戶試圖在套接字廣播標志沒有設置的情況下連接廣播地址或由于防火墻策略導致連接失敗。

EADDRINUSE 98:Address already in use(本地地址處于使用狀態)

EAFNOSUPPORT 97:Address family not supported by protocol(參數serv_add中的地址非合法地址)

EAGAIN:沒有足夠空閑的本地端口。

EALREADY 114:Operation already in progress(套接字為非阻塞套接字,并且原來的連接請求還未完成)

EBADF 77:File descriptor in bad state(非法的文件描述符)

ECONNREFUSED 111:Connection refused(遠程地址并沒有處于監聽狀態)

EFAULT:指向套接字結構體的地址非法。

EINPROGRESS 115:Operation now in progress(套接字為非阻塞套接字,且連接請求沒有立即完成)

EINTR:系統調用的執行由于捕獲中斷而中止。

EISCONN 106:Transport endpoint is already connected(已經連接到該套接字)

ENETUNREACH 101:Network is unreachable(網絡不可到達)

ENOTSOCK 88:Socket operation on non-socket(文件描述符不與套接字相關)

ETIMEDOUT 110:Connection timed out(連接超時)

總結

以上是生活随笔為你收集整理的linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。