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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

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

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

一、《UNIX網(wǎng)絡(luò)編程》-非阻塞connect

在一個(gè)TCP套接口被設(shè)置為非阻塞之后調(diào)用connect,connect會(huì)立即返回EINPROGRESS錯(cuò)誤,表示連接操作正在進(jìn)行中,但是仍未完成;同時(shí)TCP的三路握手操作繼續(xù)進(jìn)行;在這之后,我們可以調(diào)用select來(lái)檢查這個(gè)鏈接是否建立成功;

非阻塞connect有三種用途:

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

2.可以用這種技術(shù)同時(shí)建立多個(gè)連接.在Web瀏覽器中很普遍;

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

非阻塞connect聽起來(lái)雖然簡(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)記為既可讀又可寫;

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

因此,僅從socket可讀或可寫無(wú)法判斷socket連接的狀態(tài)。

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

在處理非阻塞connect時(shí),在不同的套接口實(shí)現(xiàn)的平臺(tái)中存在的移植性問題,首先,有可能在調(diào)用select之前,連接就已經(jīng)建立成功,而且對(duì)方的數(shù)據(jù)已經(jīng)到來(lái).在這種情況下,連接成功時(shí)套接口將既可讀又可寫.這和連接失敗時(shí)是一樣的.這個(gè)時(shí)候我們還得通過(guò)getsockopt來(lái)讀取錯(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)到來(lái),在這種情況下,套接口描述符是既可讀又可寫;這與套接口描述符出錯(cuò)時(shí)是一樣的;(怎樣判斷連接是否建立成功的條件不可移植)

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

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

2.調(diào)用read,讀取長(zhǎng)度為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(這是說(shuō)明使用非阻塞connect的必要性):

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

上述是書中的內(nèi)容。

二、非阻塞connect的實(shí)現(xiàn)

在一個(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)過(guò)大量測(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)用后,無(wú)論連接是否建立立即返回-1,同時(shí)將errno(包含errno.h就可以直接使用)設(shè)置為EINPROGRESS, 表示此時(shí)tcp三次握手仍舊進(jìn)行,如果errno不是EINPROGRESS,則說(shuō)明連接錯(cuò)誤,程序結(jié)束。

當(dāng)客戶端和服務(wù)器端在同一臺(tái)主機(jī)上的時(shí)候,connect回馬上結(jié)束,并返回0;無(wú)需等待,所以使用goto函數(shù)跳過(guò)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ù),這里需要說(shuō)明的是使用select監(jiān)聽socket描述符是否可讀或者可寫,如果只可寫,說(shuō)明連接成功,可以進(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ù)。需要通過(guò)connect連接后返回給errno的值來(lái)進(jìn)行判定,或者通過(guò)調(diào)用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函數(shù)返回值來(lái)判斷是否發(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;

}

對(duì)錯(cuò)誤的處理:

這里我測(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ú)論網(wǎng)絡(luò)是否發(fā)生錯(cuò)誤,getsockopt始終返回0,不返回-1,說(shuō)明linux與unix網(wǎng)絡(luò)編程還是有些細(xì)微的差別。就是說(shuō)當(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,說(shuō)明套接口可讀又可寫,應(yīng)該是網(wǎng)絡(luò)連接的出錯(cuò)情況。

此時(shí),getsockopt返回0,不起作用。獲取errno的值,指示為EINPROGRESS,沒有返回unix網(wǎng)絡(luò)編程中說(shuō)的ENOTCONN,EINPROGRESS表示正在試圖連接,不能表示網(wǎng)絡(luò)已經(jīng)連接失敗。

針對(duì)這種情況,unix網(wǎng)絡(luò)編程中提出了另外3種方法,這3種方法,也是網(wǎng)絡(luò)上給出的常用的非阻塞connect示例:

a.再調(diào)用connect一次。失敗返回errno是EISCONN說(shuō)明連接成功,表示剛才的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;

}

如程序所示,根據(jù)再次調(diào)用的errno返回值將connect_ok的值,來(lái)進(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,因此也無(wú)法通過(guò)再次connect來(lái)判斷連接是否成功。

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

一次select之后,發(fā)現(xiàn)此時(shí)套接口描述字可讀或可寫,再次執(zhí)行connect,此時(shí)errno始終不變,仍未EINPROGRESS,增加select的超時(shí)時(shí)間結(jié)果也一樣。

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

b.unix網(wǎng)絡(luò)編程中說(shuō)使用read函數(shù),如果失敗,表示connect失敗,返回的errno指明了失敗原因,但這種方法在linux上行不通,linux在socket描述符為可讀可寫的時(shí)候,read返回0,并不會(huì)置errno為錯(cuò)誤。

c.unix網(wǎng)絡(luò)編程中說(shuō)使用getpeername函數(shù),如果連接失敗,調(diào)用該函數(shù)后,通過(guò)errno來(lái)判斷第一次連接是否成功,但我試過(guò)了,無(wú)論網(wǎng)絡(luò)連接是否成功,errno都沒變化,都為EINPROGRESS,無(wú)法判斷。

即使調(diào)用getpeername函數(shù),getsockopt函數(shù)仍舊不行。

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

程序的書寫形式出于可移植性考慮,按照unix網(wǎng)絡(luò)編程推薦寫法,使用getsocketopt進(jìn)行判斷,但不通過(guò)返回值來(lái)判斷,而通過(guò)函數(shù)的返回參數(shù)來(lái)判斷。

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 ",recvbytes);

printf("%s ",buf);

*/

綜上所述一種更有效的判斷方法,經(jīng)測(cè)試驗(yàn)證,在Linux環(huán)境下是有效的:

再次調(diào)用connect,相應(yīng)返回失敗,如果錯(cuò)誤errno是EISCONN,表示socket連接已經(jīng)建立,否則認(rèn)為連接失敗。

三、非阻塞connect的實(shí)現(xiàn)

#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錯(cuò)誤碼:

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

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

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

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

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

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

ECONNREFUSED 111:Connection refused(遠(yuǎn)程地址并沒有處于監(jiān)聽狀態(tài))

EFAULT:指向套接字結(jié)構(gòu)體的地址非法。

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

EINTR:系統(tǒng)調(diào)用的執(zhí)行由于捕獲中斷而中止。

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

ENETUNREACH 101:Network is unreachable(網(wǎng)絡(luò)不可到達(dá))

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

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

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。