奇怪的recv函數

一直有個錯覺,以爲recv的函數返回值>0是有數據讀到,=0是無數據,<0是連接關閉等錯誤。結果最近做了個server,發現檢測不到對端連接關閉,才知道犯了個天大的錯誤,而原因就是recv的返回值的怪異。

從下文才知道,即使前面select read句柄返回大於0,recv函數返回0竟然是代表連接關閉。而且recv返回小於0也不一定是出錯,而根據errno判斷還有可能只是沒有讀到數據~!!!!

 

1、阻塞模式與非阻塞模式下recv的返回值各代表什麼意思?有沒有區別?(就 我目前瞭解阻塞與非阻塞recv返回值沒有區分,都是 <0:出錯,=0:連接關閉,>0接收到數據大小,特別:返回值 <0時並且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認爲連接是正常的,繼續接收。只是阻塞模式下recv會阻塞着接收數據,非阻塞模式下如果沒有數據會返回,不會阻塞着讀,因此需要循環讀取)。

2、阻塞模式與非阻塞模式下write的返回值各代表什麼意思?有沒有區別?(就我目前瞭解阻塞與非阻塞write返回值沒有區分,都是 <0:出錯,=0:連接關閉,>0發送數據大小,特別:返回值 <0時並且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認爲連接是正常的,繼續發送。只是阻塞模式下write會阻塞着發送數據,非阻塞模式下如果暫時無法發送數據會返回,不會阻塞着 write,因此需要循環發送)。

3、阻塞模式下read返回值 < 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN時,連接異常,需要關閉,read返回值 < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)時表示沒有數據,需要繼續接收,如果返回值大於0表示接送到數據。
非阻塞模式下read返回值 < 0表示沒有數據,= 0表示連接斷開,> 0表示接收到數據。
這2種模式下的返回值是不是這麼理解,有沒有跟詳細的理解或跟準確的說明?

4、阻塞模式與非阻塞模式下是否send返回值 < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)表示暫時發送失敗,需要重試,如果send返回值 <= 0, && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN時,連接異常,需要關閉,如果send返回值 > 0則表示發送了數據?send的返回值是否這麼理解,阻塞模式與非阻塞模式下send返回值=0是否都是發送失敗,還是那個模式下表示暫時不可發送,需要重發?

5、很多人說阻塞模式下read會阻塞着讀,是否這樣?我和同事試了不會阻塞read。

6、網絡上找了很多資料,說的都很籠統,就分大於0,小於0,等於0,並沒有區分阻塞與非阻塞,更沒有區分一個錯誤號,希望哪位高手能按上面的問題逐條回答一下,越詳細越好,平時少上CSDN,分少,見諒。
select():

select()的機制中提供一fd_set的數據結構,實際上是一long類型的數組,每一個數組元素都能與一打開的

文件句柄(不管是Socket句柄,還是其他 文件或命名管道或設備句柄)建立聯繫,建立聯繫的工作由程序員

完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一

Socket或文件可讀,下面具體解釋:

int select(nfds, readfds, writefds, exceptfds, timeout)

int nfds;

fd_set *readfds, *writefds, *exceptfds;

struct tim *timeout;

ndfs:select監視的文件句柄數,視進程中打開的文件數而定,一般設爲你要監視各文件中的最大文件號

加一。

readfds:select監視的可讀文件句柄集合。

writefds: select監視的可寫文件句柄集合。

exceptfds:select監視的異常文件句柄集合

timeout:本次select()的超時結束時間。(見/usr/include/sys/select.h,可精確至百萬分之一

秒!)

當readfds或writefds中映象的文件可讀或可寫或超時,本次select() 就結束返回。程序員利用一組系

統提供的宏在select()結束時便可判 斷哪一文件可讀或可寫。對Socket編程特別有用的就是readfds。

相關的宏解釋如下:

FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯繫。

FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯繫。

FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯繫。

FD_ISSET(int fd, fdset *fdset):檢查fdset聯繫的文件句柄fd是否可讀寫,>0表示可讀寫。
(關於fd_set及相關宏的定義見/usr/include/sys/types.h)

這樣,你的socket只需在有東東讀的時候纔讀入,大致如下:
...
int sockfd;
fd_set fdR;
struct tim timeout = ..;
...
for(;;) {
    FD_ZERO(&fdR);
    FD_SET(sockfd, &fdR);
    switch (select(sockfd + 1, &fdR, NULL, &timeout)) {
        case -1:
            error handled by u;
        case 0:
            timeout hanled by u;
        default:
            if (FD_ISSET(sockfd)) {
            now u read or recv something;
           
            }
    }
}

所以一個FD_ISSET(sockfd)就相當通知了sockfd可讀。

至於struct tim在此的功能,請man select。不同的tim設置使使select()表現出超時結束、

無超時阻塞和輪詢三種特性。由於tim可精確至百萬分之一秒,所以Windows的SetTimer()根本不算什麼。你可以用select()做一個超級時鐘

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章