非阻IO与EWOULDBLOCK EAGAIN
生活随笔
收集整理的這篇文章主要介紹了
非阻IO与EWOULDBLOCK EAGAIN
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
非阻塞讀寫
默認(rèn) socket 是阻塞的,讀寫函數(shù)?read,?readv,?recv,?recvfrom,?recvmsg 以及?write,?writev,?send,?sendto,?sendmsg 都有可能會(huì)阻塞??梢詫?socket 描述字設(shè)為非阻塞,這樣,當(dāng) socket 描述字未就緒時(shí),調(diào)用以上讀寫函數(shù)將會(huì)返回 EWOULDBLOCK 或 EAGAIN 。
UNPv1 給出了一個(gè)?非阻塞socket + select?的例子。有人對(duì)此提出疑問:
假如fd1是一個(gè)阻塞socket,我將它加入select的readset中,然后用select去偵聽fd1上是否有數(shù)據(jù)到來。我感覺這和非阻塞 socket的性質(zhì)是一樣的,因?yàn)樗粫?huì)阻塞在fd1的recv 函數(shù)上,因?yàn)橹皊elect已經(jīng)判定到fd1可讀,所以recv 就會(huì)返回不會(huì)阻塞。 那么為什么大家總要還要?jiǎng)?chuàng)建一個(gè)非阻塞的socket加入select中呢?
有人給出的答案是:
select 只能說明 socket 可讀或者可寫,不能說明能讀入或者能寫出多少數(shù)據(jù)。比如,socket 的寫緩沖區(qū)有 10 個(gè)字節(jié)的空閑空間,這時(shí)監(jiān)視的 select 返回,然后在該 socket 上進(jìn)行寫操作。但是如果要寫入 100 字節(jié),如果 socket 沒有設(shè)置非阻塞,調(diào)用 write 就會(huì)阻塞在那里。而更為要緊的是,在多個(gè) socket 的情況下,讀寫一個(gè)socket 時(shí)阻塞,會(huì)影響到其他的 socket 。
UNPv1 上的例子如下:
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 int maxfdp1, val, stdineof;
6 ssize_t n, nwritten;
7 fd_set rset, wset;
8 char to[MAXLINE], fr[MAXLINE];
9 char *toiptr, *tooptr, *friptr, *froptr;
10 val = Fcntl(sockfd, F_GETFL, 0);
11 Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
12 val = Fcntl(STDIN_FILENO, F_GETFL, 0);
13 Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
14 val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
15 Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
16 toiptr = tooptr = to; /* initialize buffer pointers */
17 friptr = froptr = fr;
18 stdineof = 0;
19 maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
20 for ( ; ; ) {
21 FD_ZERO(&rset);
22 FD_ZERO(&wset);
23 if (stdineof == 0 && toiptr < &to[MAXLINE])
24 FD_SET(STDIN_FILENO, &rset); /* read from stdin */
25 if (friptr < &fr[MAXLINE])
26 FD_SET(sockfd, &rset); /* read from socket */
27 if (tooptr != toiptr)
28 FD_SET(sockfd, &wset); /* data to write to socket */
29 if (froptr != friptr)
30 FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */
31 Select(maxfdp1, &rset, &wset, NULL, NULL);
32 if (FD_ISSET(STDIN_FILENO, &rset)) {
33 if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
34 if (errno != EWOULDBLOCK)
35 err_sys("read error on stdin");
36 } else if (n == 0) {
37 fprintf(stderr, "%s: EOF on stdin\n", gf_time());
38 stdineof = 1; /* all done with stdin */
39 if (tooptr == toiptr)
40 Shutdown(sockfd, SHUT_WR); /* send FIN */
41 } else {
42 fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(),
43 n);
44 toiptr += n; /* # just read */
45 FD_SET(sockfd, &wset); /* try and write to socket below */
46 }
47 }
48 if (FD_ISSET(sockfd, &rset)) {
49 if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
50 if (errno != EWOULDBLOCK)
51 err_sys("read error on socket");
52 } else if (n == 0) {
53 fprintf(stderr, "%s: EOF on socket\n", gf_time());
54 if (stdineof)
55 return; /* normal termination */
56 else
57 err_quit("str_cli: server terminated prematurely");
58 } else {
59 fprintf(stderr, "%s: read %d bytes from socket\n",
60 gf_time(), n);
61 friptr += n; /* # just read */
62 FD_SET(STDOUT_FILENO, &wset); /* try and write below */
63 }
64 }
65 if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {
66 if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
67 if (errno != EWOULDBLOCK)
68 err_sys("write error to stdout");
69 } else {
70 fprintf(stderr, "%s: wrote %d bytes to stdout\n",
71 gf_time(), nwritten);
72 froptr += nwritten; /* # just written */
73 if (froptr == friptr)
74 froptr = friptr = fr; /* back to beginning of buffer */
75 }
76 }
77 if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)) {
78 if ( (nwritten = write(sockfd, tooptr, n)) < 0) {
79 if (errno != EWOULDBLOCK)
80 err_sys("write error to socket");
81 } else {
82 fprintf(stderr, "%s: wrote %d bytes to socket\n",
83 gf_time(), nwritten);
84 tooptr += nwritten; /* # just written */
85 if (tooptr == toiptr) {
86 toiptr = tooptr = to; /* back to beginning of buffer */
87 if (stdineof)
88 Shutdown(sockfd, SHUT_WR); /* send FIN */
89 }
90 }
91 }
92 }
93 }
1 #include "unp.h"
2 #include <time.h>
3 char *
4 gf_time(void)
5 {
6 struct timeval tv;
7 static char str[30];
8 char *ptr;
9 if (gettimeofday(&tv, NULL) < 0)
10 err_sys("gettimeofday error");
11 ptr = ctime(&tv.tv_sec);
12 strcpy(str, &ptr[11]);
13 /* Fri Sep 13 00:00:00 1986\n\0 */
14 /* 0123456789012345678901234 5 */
15 snprintf(str + 8, sizeof(str) - 8, ".%06ld", tv.tv_usec);
16 return (str);
17 }這個(gè)例子中用到的兩個(gè) buffer 如下:
由于上面這個(gè)例子 buffer 管理 太過復(fù)雜,作者又給出了?多進(jìn)程 方式來替代 上面的 非阻塞 + select 。
對(duì)于同一個(gè) socket ,一個(gè)進(jìn)程讀,另一個(gè)進(jìn)程寫。
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 pid_t pid;
6 char sendline[MAXLINE], recvline[MAXLINE];
7 if ( (pid = Fork()) == 0) { /* child: server -> stdout */
8 while (Readline(sockfd, recvline, MAXLINE) > 0)
9 Fputs(recvline, stdout);
10 kill(getppid(), SIGTERM); /* in case parent still running */
11 exit(0);
12 }
13 /* parent: stdin -> server */
14 while (Fgets(sendline, MAXLINE, fp) != NULL)
15 Writen(sockfd, sendline, strlen(sendline));
16 Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */
17 pause();
18 return;
19 }
非阻塞 connect
TCP socket 被設(shè)為非阻塞后調(diào)用 connect ,connect 函數(shù)會(huì)立即返回 EINPROCESS ,但 TCP 的 3 次握手繼續(xù)進(jìn)行。之后可以用 select 檢查 連接是否建立成功。非阻塞 connect 有3 種用途:
1. 在3 次握手的同時(shí)做一些其他的處理。
2. 可以同時(shí)建立多個(gè)連接。
3. 在利用 select 等待的時(shí)候,可以給 select 設(shè)定一個(gè)時(shí)間,從而可以縮短 connect 的超時(shí)時(shí)間。
使用非阻塞 connect 需要注意的問題是:
1. 很可能 調(diào)用 connect 時(shí)會(huì)立即建立連接(比如,客戶端和服務(wù)端在同一臺(tái)機(jī)子上),必須處理這種情況。
2. Posix 定義了兩條與 select 和 非阻塞 connect 相關(guān)的規(guī)定:
1)連接成功建立時(shí),socket 描述字變?yōu)榭蓪憽?#xff08;連接建立時(shí),寫緩沖區(qū)空閑,所以可寫)
2)連接建立失敗時(shí),socket 描述字既可讀又可寫。 (由于有未決的錯(cuò)誤,從而可讀又可寫)
UNPv1 給出的 非阻塞 connect 的例子:
1 #include "unp.h"
2 int
3 connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
4 {
5 int flags, n, error;
6 socklen_t len;
7 fd_set rset, wset;
8 struct timeval tval;
9 flags = Fcntl(sockfd, F_GETFL, 0);
10 Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
11 error = 0;
12 if ( (n = connect(sockfd, saptr, salen)) < 0)
13 if (errno != EINPROGRESS)
14 return (-1);
15 /* Do whatever we want while the connect is taking place. */
16 if (n == 0)
17 goto done; /* connect completed immediately */
18 FD_ZERO(&rset);
19 FD_SET(sockfd, &rset);
20 wset = rset;
21 tval.tv_sec = nsec;
22 tval.tv_usec = 0;
23 if ( (n = Select(sockfd + 1, &rset, &wset, NULL,
24 nsec ? &tval : NULL)) == 0) {
25 close(sockfd); /* timeout */
26 errno = ETIMEDOUT;
27 return (-1);
28 }
29 if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
30 len = sizeof(error);
31 if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
32 return (-1); /* Solaris pending error */
33 } else
34 err_quit("select error: sockfd not set");
35 done:
36 Fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
37 if (error) {
38 close(sockfd); /* just in case */
39 errno = error;
40 return (-1);
41 }
42 return (0);
43 }
注意事項(xiàng):
1. 如果在調(diào)用 select 之前,連接已經(jīng)建立成功,并且有數(shù)據(jù)發(fā)送過來了,這時(shí)套接字將是即可讀又可寫,和連接失敗時(shí)是一樣的。所以我們必須用 getsockopt 來檢查套接字的狀態(tài)。
2. 由于 socket 可寫并不能說明連接是否成功建立,可以用以下幾種方法取代 getsockopt 來檢查連接到底是不是成功建立。
1)調(diào)用 getpeername ,如果調(diào)用失敗,返回 ENOTCONN ,表示連接失敗??梢杂?getsockopt (SO_ERROR) 獲取 socket 上待處理的錯(cuò)誤。
2)調(diào)用 read ,長(zhǎng)度參數(shù)為 0 , 如果read 失敗,表明 connect 失敗,而且 read 返回的 errno 指明了連接失敗的原因。如果連接成功,read 返回 0 。
3)在調(diào)用 connect 一次,這是應(yīng)該失敗,如果錯(cuò)誤號(hào)為 EISCONN ,表明連接已經(jīng)成功建立。
被中斷的 connect
在阻塞的 socket 上 調(diào)用 connect ,在 TCP 3 次握手完成之前被信號(hào)中斷,如果 connect 不被重啟,將返回 EINTR 。但是不能再調(diào)用 connect 來完成連接,這樣做會(huì)返回 EADDRINUSE 。
這時(shí)需要做的是調(diào)用 select ,如同非阻塞 socket 上調(diào)用 select 一樣。select 返回時(shí)表明連接成功(socket 可寫)或連接失敗(socket 可讀可寫)。
非阻塞 accept
當(dāng)用 select 監(jiān)視 listening socket 時(shí), 如果有新連接到來,select 返回, 該 listening socket 變?yōu)榭勺x。然后我們 accept 接收該連接。
問題是:accept 時(shí),將 listening socket 設(shè)置為 非阻塞 的必要性是什么?
首先說明一下 已完成3次握手的連接在 accept 之前 被 異常終止(Aborted )時(shí)發(fā)生的情況,如下圖:
一個(gè)連接被異常終止時(shí)執(zhí)行的動(dòng)作取決于實(shí)現(xiàn):
1. 基于 Berkeley 的實(shí)現(xiàn)完全由內(nèi)核處理該異常終止的連接, 應(yīng)用進(jìn)程看不到。
2. 基于 SVR4 的實(shí)現(xiàn),在連接異常終止后調(diào)用 accept 時(shí),通常會(huì)給應(yīng)用進(jìn)程返回 EPROTO 錯(cuò)誤。但是 Posix 指出應(yīng)該返回 ECONNABORTED 。Posix 認(rèn)為當(dāng)發(fā)生致命的協(xié)議相關(guān)的錯(cuò)誤時(shí),返回 EPROTO 錯(cuò)誤。而 異常終止一個(gè)連接并非致命錯(cuò)誤,從而返回 ECONNABORTED ,與 EPROTO 區(qū)分開來,這樣隨后可以繼續(xù)調(diào)用 accept 。?
現(xiàn)在假設(shè)是基于 Berkeley 的實(shí)現(xiàn),在 select 返回后,accept 調(diào)用之前,如果連接被異常終止,這時(shí) accept 調(diào)用可能會(huì)由于沒有已完成的連接而阻塞,直到有新連接建立。對(duì)于服務(wù)進(jìn)程而言,在被 accept 阻塞的這一段時(shí)間內(nèi),將不能處理其他已就緒的 socket 。
解決上面這個(gè)問題有兩種方法:
1. 在用 select 監(jiān)視 listening socket 時(shí),總是將 listening socket 設(shè)為非阻塞模式。
2. 忽略 accept 返回的以下錯(cuò)誤:
??? EWOULDBLOCK(基于 berkeley 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))、ECONNABORTED(基于 posix 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))、EPROTO(基于 SVR4 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))以及 EINTR 。
默認(rèn) socket 是阻塞的,讀寫函數(shù)?read,?readv,?recv,?recvfrom,?recvmsg 以及?write,?writev,?send,?sendto,?sendmsg 都有可能會(huì)阻塞??梢詫?socket 描述字設(shè)為非阻塞,這樣,當(dāng) socket 描述字未就緒時(shí),調(diào)用以上讀寫函數(shù)將會(huì)返回 EWOULDBLOCK 或 EAGAIN 。
UNPv1 給出了一個(gè)?非阻塞socket + select?的例子。有人對(duì)此提出疑問:
假如fd1是一個(gè)阻塞socket,我將它加入select的readset中,然后用select去偵聽fd1上是否有數(shù)據(jù)到來。我感覺這和非阻塞 socket的性質(zhì)是一樣的,因?yàn)樗粫?huì)阻塞在fd1的recv 函數(shù)上,因?yàn)橹皊elect已經(jīng)判定到fd1可讀,所以recv 就會(huì)返回不會(huì)阻塞。 那么為什么大家總要還要?jiǎng)?chuàng)建一個(gè)非阻塞的socket加入select中呢?
有人給出的答案是:
select 只能說明 socket 可讀或者可寫,不能說明能讀入或者能寫出多少數(shù)據(jù)。比如,socket 的寫緩沖區(qū)有 10 個(gè)字節(jié)的空閑空間,這時(shí)監(jiān)視的 select 返回,然后在該 socket 上進(jìn)行寫操作。但是如果要寫入 100 字節(jié),如果 socket 沒有設(shè)置非阻塞,調(diào)用 write 就會(huì)阻塞在那里。而更為要緊的是,在多個(gè) socket 的情況下,讀寫一個(gè)socket 時(shí)阻塞,會(huì)影響到其他的 socket 。
UNPv1 上的例子如下:
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 int maxfdp1, val, stdineof;
6 ssize_t n, nwritten;
7 fd_set rset, wset;
8 char to[MAXLINE], fr[MAXLINE];
9 char *toiptr, *tooptr, *friptr, *froptr;
10 val = Fcntl(sockfd, F_GETFL, 0);
11 Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
12 val = Fcntl(STDIN_FILENO, F_GETFL, 0);
13 Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
14 val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
15 Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
16 toiptr = tooptr = to; /* initialize buffer pointers */
17 friptr = froptr = fr;
18 stdineof = 0;
19 maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
20 for ( ; ; ) {
21 FD_ZERO(&rset);
22 FD_ZERO(&wset);
23 if (stdineof == 0 && toiptr < &to[MAXLINE])
24 FD_SET(STDIN_FILENO, &rset); /* read from stdin */
25 if (friptr < &fr[MAXLINE])
26 FD_SET(sockfd, &rset); /* read from socket */
27 if (tooptr != toiptr)
28 FD_SET(sockfd, &wset); /* data to write to socket */
29 if (froptr != friptr)
30 FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */
31 Select(maxfdp1, &rset, &wset, NULL, NULL);
32 if (FD_ISSET(STDIN_FILENO, &rset)) {
33 if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
34 if (errno != EWOULDBLOCK)
35 err_sys("read error on stdin");
36 } else if (n == 0) {
37 fprintf(stderr, "%s: EOF on stdin\n", gf_time());
38 stdineof = 1; /* all done with stdin */
39 if (tooptr == toiptr)
40 Shutdown(sockfd, SHUT_WR); /* send FIN */
41 } else {
42 fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(),
43 n);
44 toiptr += n; /* # just read */
45 FD_SET(sockfd, &wset); /* try and write to socket below */
46 }
47 }
48 if (FD_ISSET(sockfd, &rset)) {
49 if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
50 if (errno != EWOULDBLOCK)
51 err_sys("read error on socket");
52 } else if (n == 0) {
53 fprintf(stderr, "%s: EOF on socket\n", gf_time());
54 if (stdineof)
55 return; /* normal termination */
56 else
57 err_quit("str_cli: server terminated prematurely");
58 } else {
59 fprintf(stderr, "%s: read %d bytes from socket\n",
60 gf_time(), n);
61 friptr += n; /* # just read */
62 FD_SET(STDOUT_FILENO, &wset); /* try and write below */
63 }
64 }
65 if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {
66 if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
67 if (errno != EWOULDBLOCK)
68 err_sys("write error to stdout");
69 } else {
70 fprintf(stderr, "%s: wrote %d bytes to stdout\n",
71 gf_time(), nwritten);
72 froptr += nwritten; /* # just written */
73 if (froptr == friptr)
74 froptr = friptr = fr; /* back to beginning of buffer */
75 }
76 }
77 if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)) {
78 if ( (nwritten = write(sockfd, tooptr, n)) < 0) {
79 if (errno != EWOULDBLOCK)
80 err_sys("write error to socket");
81 } else {
82 fprintf(stderr, "%s: wrote %d bytes to socket\n",
83 gf_time(), nwritten);
84 tooptr += nwritten; /* # just written */
85 if (tooptr == toiptr) {
86 toiptr = tooptr = to; /* back to beginning of buffer */
87 if (stdineof)
88 Shutdown(sockfd, SHUT_WR); /* send FIN */
89 }
90 }
91 }
92 }
93 }
1 #include "unp.h"
2 #include <time.h>
3 char *
4 gf_time(void)
5 {
6 struct timeval tv;
7 static char str[30];
8 char *ptr;
9 if (gettimeofday(&tv, NULL) < 0)
10 err_sys("gettimeofday error");
11 ptr = ctime(&tv.tv_sec);
12 strcpy(str, &ptr[11]);
13 /* Fri Sep 13 00:00:00 1986\n\0 */
14 /* 0123456789012345678901234 5 */
15 snprintf(str + 8, sizeof(str) - 8, ".%06ld", tv.tv_usec);
16 return (str);
17 }這個(gè)例子中用到的兩個(gè) buffer 如下:
由于上面這個(gè)例子 buffer 管理 太過復(fù)雜,作者又給出了?多進(jìn)程 方式來替代 上面的 非阻塞 + select 。
對(duì)于同一個(gè) socket ,一個(gè)進(jìn)程讀,另一個(gè)進(jìn)程寫。
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 pid_t pid;
6 char sendline[MAXLINE], recvline[MAXLINE];
7 if ( (pid = Fork()) == 0) { /* child: server -> stdout */
8 while (Readline(sockfd, recvline, MAXLINE) > 0)
9 Fputs(recvline, stdout);
10 kill(getppid(), SIGTERM); /* in case parent still running */
11 exit(0);
12 }
13 /* parent: stdin -> server */
14 while (Fgets(sendline, MAXLINE, fp) != NULL)
15 Writen(sockfd, sendline, strlen(sendline));
16 Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */
17 pause();
18 return;
19 }
非阻塞 connect
TCP socket 被設(shè)為非阻塞后調(diào)用 connect ,connect 函數(shù)會(huì)立即返回 EINPROCESS ,但 TCP 的 3 次握手繼續(xù)進(jìn)行。之后可以用 select 檢查 連接是否建立成功。非阻塞 connect 有3 種用途:
1. 在3 次握手的同時(shí)做一些其他的處理。
2. 可以同時(shí)建立多個(gè)連接。
3. 在利用 select 等待的時(shí)候,可以給 select 設(shè)定一個(gè)時(shí)間,從而可以縮短 connect 的超時(shí)時(shí)間。
使用非阻塞 connect 需要注意的問題是:
1. 很可能 調(diào)用 connect 時(shí)會(huì)立即建立連接(比如,客戶端和服務(wù)端在同一臺(tái)機(jī)子上),必須處理這種情況。
2. Posix 定義了兩條與 select 和 非阻塞 connect 相關(guān)的規(guī)定:
1)連接成功建立時(shí),socket 描述字變?yōu)榭蓪憽?#xff08;連接建立時(shí),寫緩沖區(qū)空閑,所以可寫)
2)連接建立失敗時(shí),socket 描述字既可讀又可寫。 (由于有未決的錯(cuò)誤,從而可讀又可寫)
UNPv1 給出的 非阻塞 connect 的例子:
1 #include "unp.h"
2 int
3 connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
4 {
5 int flags, n, error;
6 socklen_t len;
7 fd_set rset, wset;
8 struct timeval tval;
9 flags = Fcntl(sockfd, F_GETFL, 0);
10 Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
11 error = 0;
12 if ( (n = connect(sockfd, saptr, salen)) < 0)
13 if (errno != EINPROGRESS)
14 return (-1);
15 /* Do whatever we want while the connect is taking place. */
16 if (n == 0)
17 goto done; /* connect completed immediately */
18 FD_ZERO(&rset);
19 FD_SET(sockfd, &rset);
20 wset = rset;
21 tval.tv_sec = nsec;
22 tval.tv_usec = 0;
23 if ( (n = Select(sockfd + 1, &rset, &wset, NULL,
24 nsec ? &tval : NULL)) == 0) {
25 close(sockfd); /* timeout */
26 errno = ETIMEDOUT;
27 return (-1);
28 }
29 if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
30 len = sizeof(error);
31 if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
32 return (-1); /* Solaris pending error */
33 } else
34 err_quit("select error: sockfd not set");
35 done:
36 Fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
37 if (error) {
38 close(sockfd); /* just in case */
39 errno = error;
40 return (-1);
41 }
42 return (0);
43 }
注意事項(xiàng):
1. 如果在調(diào)用 select 之前,連接已經(jīng)建立成功,并且有數(shù)據(jù)發(fā)送過來了,這時(shí)套接字將是即可讀又可寫,和連接失敗時(shí)是一樣的。所以我們必須用 getsockopt 來檢查套接字的狀態(tài)。
2. 由于 socket 可寫并不能說明連接是否成功建立,可以用以下幾種方法取代 getsockopt 來檢查連接到底是不是成功建立。
1)調(diào)用 getpeername ,如果調(diào)用失敗,返回 ENOTCONN ,表示連接失敗??梢杂?getsockopt (SO_ERROR) 獲取 socket 上待處理的錯(cuò)誤。
2)調(diào)用 read ,長(zhǎng)度參數(shù)為 0 , 如果read 失敗,表明 connect 失敗,而且 read 返回的 errno 指明了連接失敗的原因。如果連接成功,read 返回 0 。
3)在調(diào)用 connect 一次,這是應(yīng)該失敗,如果錯(cuò)誤號(hào)為 EISCONN ,表明連接已經(jīng)成功建立。
被中斷的 connect
在阻塞的 socket 上 調(diào)用 connect ,在 TCP 3 次握手完成之前被信號(hào)中斷,如果 connect 不被重啟,將返回 EINTR 。但是不能再調(diào)用 connect 來完成連接,這樣做會(huì)返回 EADDRINUSE 。
這時(shí)需要做的是調(diào)用 select ,如同非阻塞 socket 上調(diào)用 select 一樣。select 返回時(shí)表明連接成功(socket 可寫)或連接失敗(socket 可讀可寫)。
非阻塞 accept
當(dāng)用 select 監(jiān)視 listening socket 時(shí), 如果有新連接到來,select 返回, 該 listening socket 變?yōu)榭勺x。然后我們 accept 接收該連接。
問題是:accept 時(shí),將 listening socket 設(shè)置為 非阻塞 的必要性是什么?
首先說明一下 已完成3次握手的連接在 accept 之前 被 異常終止(Aborted )時(shí)發(fā)生的情況,如下圖:
一個(gè)連接被異常終止時(shí)執(zhí)行的動(dòng)作取決于實(shí)現(xiàn):
1. 基于 Berkeley 的實(shí)現(xiàn)完全由內(nèi)核處理該異常終止的連接, 應(yīng)用進(jìn)程看不到。
2. 基于 SVR4 的實(shí)現(xiàn),在連接異常終止后調(diào)用 accept 時(shí),通常會(huì)給應(yīng)用進(jìn)程返回 EPROTO 錯(cuò)誤。但是 Posix 指出應(yīng)該返回 ECONNABORTED 。Posix 認(rèn)為當(dāng)發(fā)生致命的協(xié)議相關(guān)的錯(cuò)誤時(shí),返回 EPROTO 錯(cuò)誤。而 異常終止一個(gè)連接并非致命錯(cuò)誤,從而返回 ECONNABORTED ,與 EPROTO 區(qū)分開來,這樣隨后可以繼續(xù)調(diào)用 accept 。?
現(xiàn)在假設(shè)是基于 Berkeley 的實(shí)現(xiàn),在 select 返回后,accept 調(diào)用之前,如果連接被異常終止,這時(shí) accept 調(diào)用可能會(huì)由于沒有已完成的連接而阻塞,直到有新連接建立。對(duì)于服務(wù)進(jìn)程而言,在被 accept 阻塞的這一段時(shí)間內(nèi),將不能處理其他已就緒的 socket 。
解決上面這個(gè)問題有兩種方法:
1. 在用 select 監(jiān)視 listening socket 時(shí),總是將 listening socket 設(shè)為非阻塞模式。
2. 忽略 accept 返回的以下錯(cuò)誤:
??? EWOULDBLOCK(基于 berkeley 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))、ECONNABORTED(基于 posix 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))、EPROTO(基于 SVR4 實(shí)現(xiàn),當(dāng)客戶端異常終止連接時(shí))以及 EINTR 。
總結(jié)
以上是生活随笔為你收集整理的非阻IO与EWOULDBLOCK EAGAIN的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日本版GPS助力自动驾驶:行驶不用摄像头
- 下一篇: 平板电脑应用_什么是机房巡检AI机器人?