1. 基礎知識
(1) fcntl函數
fcntl函數可執行各種描述符的控制操作,對於socket描述符,常用應用是將其設置爲阻塞式IO,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
int
flags; if ((flags = fcntl(fd, F_GETFL)) < 0)
//獲取當前的flags標誌 err_sys(“F_GETFL error!”);
flags |= O_NONBLOCK;
//修改非阻塞標誌位 if (fcntl(fd, F_SETFL, flags) < 0)
err_sys(“F_SETFL error!”); |
(2) connect函數
對於阻塞式套接字,調用connect函數將激發TCP的三次握手過程,而且僅在連接建立成功或者出錯時才返回;對於非阻塞式套接字,如果調用connect函數會之間返回-1(表示出錯),且錯誤爲EINPROGRESS,表示連接建立,建立啓動但是尚未完成;如果返回0,則表示連接已經建立,這通常是在服務器和客戶在同一臺主機上時發生。
1
2
3
4
5
6
7
8
9
10
11
12
|
if
(connect(fd, ( struct
sockaddr*)&sa, sizeof (sa)) == -1)
if
( errno
!= EINPROGRESS) { return
-1; }
if (n == 0)
goto
done; |
(3) select函數
select是一種IO多路複用機制,它允許進程指示內核等待多個事件的任何一個發生,並且在有一個或者多個事件發生或者經歷一段指定的時間後才喚醒它。
connect本身並不具有設置超時功能,如果想對套接字的IO操作設置超時,可使用select函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(fd, &wfd);
if
(select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) { __redisSetError(c,REDIS_ERR_IO,
sdscatprintf(sdsempty(),
"select(2): %s" ,
strerror ( errno )));
close(fd);
return
REDIS_ERR; } |
對於select和非阻塞connect,注意兩點:
[1] 當連接成功建立時,描述符變成可寫; [2] 當連接建立遇到錯誤時,描述符變爲即可讀,也可寫,遇到這種情況,可調用getsockopt函數。
(4) getsockopt函數
可獲取影響套接字的選項,比如SOCKET的出錯信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
err = 0; errlen = sizeof (err);
if
(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { sprintf ( "getsockopt(SO_ERROR): %s" ,
strerror ( errno )));
close(fd);
return
ERR; } if
(err) { errno
= err; close(fd);
return
ERR; } |
2. 實現非阻塞式connect
分以下幾步:
(1) 創建socket,並利用fcntl將其設置爲非阻塞
(2) 調用connect函數,如果返回0,則連接建立;如果返回-1,檢查errno ,如果值爲 EINPROGRESS,則連接正在建立。
(3) 爲了控制連接建立時間,將該socket描述符加入到select的可寫集合中,採用select函數設定超時。
(4) 如果規定時間內成功建立,則描述符變爲可寫;否則,採用getsockopt函數捕獲錯誤信息
(5) 恢復套接字的文件狀態並返回。
3. 應用實例
(1)實例一
《unix網絡編程》卷1的16.5節有一個Netscape 的web客戶端的程序實例,客戶端先建立一個與某個web服務器的HTTP連接,然後獲取該網站的主頁。該主頁往往含有多個對於其他網頁的引用,客戶可以使用非阻塞connect同時獲取多個網頁,以此取代每次只獲取一個網頁的串行獲取手段。
(2)實例二
Redis客戶端CLI (command line interface),位於源代碼的src/deps/hiredis下面。實際上,不僅是Redis客戶端,其他類似的client/server架構中,client均可採用非阻塞式connect實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
int
redisContextConnectTcp(redisContext *c, const
char *addr, int
port, struct
timeval *timeout) { int
s; int
blocking = (c->flags & REDIS_BLOCK); struct
sockaddr_in sa; if
((s = redisCreateSocket(c,AF_INET)) < 0) return
REDIS_ERR; if
(redisSetBlocking(c,s,0) != REDIS_OK) return
REDIS_ERR; sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if
(inet_aton(addr, &sa.sin_addr) == 0) { struct
hostent *he; he = gethostbyname(addr);
if
(he == NULL) { __redisSetError(c,REDIS_ERR_OTHER, sdscatprintf(sdsempty(), "Can't resolve: %s" ,addr));
close(s);
return
REDIS_ERR; } memcpy (&sa.sin_addr, he->h_addr,
sizeof ( struct
in_addr)); }
if
(connect(s, ( struct
sockaddr*)&sa, sizeof (sa)) == -1) {
if
( errno
== EINPROGRESS && !blocking) { /* This is ok. */ }
else {
if
(redisContextWaitReady(c,s,timeout) != REDIS_OK) return
REDIS_ERR; }
}
/* Reset socket to be blocking after connect(2). */ if
(blocking && redisSetBlocking(c,s,1) != REDIS_OK)
return
REDIS_ERR; if
(redisSetTcpNoDelay(c,s) != REDIS_OK) return
REDIS_ERR; c->fd = s;
c->flags |= REDIS_CONNECTED;
return
REDIS_OK; } static
int redisContextWaitReady(redisContext *c,
int fd,
const struct
timeval *timeout) { struct
timeval to; struct
timeval *toptr = NULL; fd_set wfd;
int
err; socklen_t errlen;
/* Only use timeout when not NULL. */ if
(timeout != NULL) { to = *timeout;
toptr = &to;
}
if
( errno
== EINPROGRESS) { FD_ZERO(&wfd);
FD_SET(fd, &wfd);
if
(select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {
__redisSetError(c,REDIS_ERR_IO,
sdscatprintf(sdsempty(),
"select(2): %s" ,
strerror ( errno )));
close(fd);
return
REDIS_ERR; }
if
(!FD_ISSET(fd, &wfd)) { errno
= ETIMEDOUT; __redisSetError(c,REDIS_ERR_IO,NULL);
close(fd);
return
REDIS_ERR; }
err = 0;
errlen =
sizeof (err);
if
(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
__redisSetError(c,REDIS_ERR_IO,
sdscatprintf(sdsempty(),
"getsockopt(SO_ERROR): %s" ,
strerror ( errno )));
close(fd);
return
REDIS_ERR; }
if
(err) { errno
= err; __redisSetError(c,REDIS_ERR_IO,NULL);
close(fd);
return
REDIS_ERR; }
return
REDIS_OK; }
__redisSetError(c,REDIS_ERR_IO,NULL);
close(fd);
return
REDIS_ERR; } |
原創文章,轉載請註明: 轉載自董的博客
本文鏈接地址: http://dongxicheng.org/network/non-block-connect-implemention/
作者:Dong,作者介紹:http://dongxicheng.org/about/
本博客的文章集合:http://dongxicheng.org/recommend/