TCP協議下的recv函式
阿新 • • 發佈:2019-02-18
recv函式
函式原型:int recv( SOCKET s, char *buf, int len, int flags)
功能:不論是客戶還是伺服器應用程式都用recv函式從TCP連線的另一端接收資料。
引數一:指定接收端套接字描述符;
引數二:指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;
引數三:指明buf的長度;
引數四 :一般置為0。
同步Socket的recv函式的執行流程:當應用程式呼叫recv函式時,recv先等待s的傳送緩衝中的資料被協議傳送完畢,
如果協議在傳送s的傳送緩衝中的資料時出現網路錯誤,那麼recv函式返回SOCKET_ERROR;
如果s的傳送緩衝中沒有資料或者資料被協議成功傳送完畢後,recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一直
等待,直到協議把資料接收完畢;
當協議把資料接收完畢,recv函式就把s的接收緩衝中的資料copy到buf中(注意協議接收到的資料可能大於buf的長度,所以在這種情況下要呼叫幾次recv函式才能把s的接收緩衝中的資料copy完。recv函式僅僅是copy資料,真正的接收資料是協議來完成的),recv函式返回其實際copy的位元組數;
如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。
讀資料的時候需要考慮的是當recv()返回的大小如果等於請求的大小,那麼很有可能是緩衝區還有資料未讀完,也意味著該次事件還沒有處理完,所以還需要再次讀取:
while(rs)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
// 由於是非阻塞的模式,所以當buflen為EAGAIN時,表示當前緩衝區已無資料可讀
// 在這裡就當作是該次事件已處理
if(buflen == EAGAIN)
break;
else
return;
}
else if(buflen == 0)
{
// 這裡表示對端的socket已正常關閉.
}
if(buflen != sizeof(buf))
rs = 0;
else
rs = 1;// 需要再次讀取
}
recv函式僅僅是copy資料,真正的接收資料是協議來完成的), recv函式返回其實際copy的位元組數。
如果recv在copy時出錯,那麼它返回SOCKET_ERROR;
如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。
預設 socket 是阻塞的 解阻塞與非阻塞recv返回值沒有區分,都是 <0 出錯 =0 連線關閉 >0 接收到資料大小,
特別:
返回值<0時並且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認為連線是正常的,繼續接收。
只是阻塞模式下recv會阻塞著接收資料,非阻塞模式下如果沒有資料會返回,不會阻塞著讀,因此需要迴圈讀取)。
從TCP協議角度來看,一個已建立的TCP連線有兩種關閉方式,一種是正常關閉,即四次揮手關閉連線;還有一種則是異常關閉,我們通常稱之為連線重置(RESET)。
首先說一下正常關閉時四次揮手的狀態變遷,關閉連線的主動方狀態變遷是FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT,而關閉連線的被對方的狀態變遷是CLOSE_WAIT->LAST_ACK->TIME_WAIT。在四次揮手過程中ACK包都是協議棧自動完成的,而FIN包則必須由應用層通過closesocket或shutdown主動傳送,通常連線正常關閉後,recv會得到返回值0,send會得到錯誤碼10058。
除此之外,在我們的日常應用中,連線異常關閉的情況也很多。比如應用程式被強行關閉、本地網路突然中斷(禁用網絡卡、網線拔出)、程式處理不當等都會導致連線重置,連線重置時將會產生RST包,同時網路絡緩衝區中未接收(傳送)的資料都將丟失。連線重置後,本方send或recv會得到錯誤碼10053(closesocket時是10038),對方recv會得到錯誤碼10054,send則得到錯誤碼10053(closesocket時是10054)。
作業系統為我們提供了兩個函式來關閉一個TCP連線,分別是closesocket和shutdown。通常情況下,closesocket會向對方傳送一個FIN包,但是也有例外。比如有一個工作執行緒正在呼叫recv接收資料,此時外部呼叫closesocket,會導致連線重置,同時向對方傳送一個RST包,這個RST包是由本方主動產生的。
shutdown可以用來關閉指定方向的連線,該函式接收兩個引數,一個是套接字,另一個是關閉的方向,可用值為SD_SEND,SD_RECEIVE和SD_BOTH。方向取值為SD_SEND時,無論socket處於什麼狀態(recv阻塞,或空閒狀態),都會向對方傳送一個FIN包,注意這點與closesocket的區別。此時本方進入FIN_WAIT_2狀態,對方進入CLOSE_WAIT狀態,本方依然可以呼叫recv接收資料;方向取值為SD_RECEIVE時,雙發連線狀態沒有改變,依然處於ESTABLISHED狀態,本方依然可以send資料,但是,如果對方再呼叫send方法,連線會被立即重置,同時向對方傳送一個RST包,這個RST包是被動產生的,這點注意與closesocket的區別。
recv函式
int recv( SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是伺服器應用程式都用recv函式從TCP連線的另一端接收資料。
該函式的第一個引數指定接收端套接字描述符;
第二個引數指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;
第三個引數指明buf的長度;
第四個引數一般置0。
這裡只描述同步Socket的recv函式的執行流程。當應用程式呼叫recv函式時,recv先等待s的傳送緩衝中的資料被協議傳送完畢,如果協議在傳送s的傳送緩衝中的資料時出現網路錯誤,那麼recv函式返回SOCKET_ERROR,如果s的傳送緩衝中沒有資料或者資料被協議成功傳送完畢後,recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一直等待,只到協議把資料接收完畢。當協議把資料接收完畢,recv函式就把s的接收緩衝中的資料copy到buf中(注意協議接收到的資料可能大於buf的長度,所以在這種情況下要呼叫幾次recv函式才能把s的接收緩衝中的資料copy完。recv函式僅僅是copy資料,真正的接收資料是協議來完成的),recv函式返回其實際copy的位元組數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。
注意:在Unix系統下,如果recv函式在等待協議接收資料時網路斷開了,那麼呼叫recv的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。