tcp協議本身是可靠的,並不等於應用程式用tcp傳送資料就一定是可靠的.不管是否阻塞,send傳送的大小,並不代表對端recv到多少的資料.
在阻塞模式
下, send函式的過程是將應用程式請求傳送的資料拷貝到傳送快取中傳送就返回.但由於傳送快取的存在,表現為:如果傳送快取大小比請求傳送的大小要大,那麼send函式立即返回,同時向網路中傳送資料;否則,send會等待接收端對之前傳送資料的確認,以便騰出快取空間容納新的待傳送資料,再返回(接收端協議棧只要將資料收到接收快取中,就會確認,並不一定要等待應用程式呼叫recv),如果一直沒有空間能容納待傳送的資料,則
一直阻塞;在
非阻塞模式
下,send函式的過程僅僅是將資料拷貝到協議棧的快取區而已,如果快取區可用空間不夠,則盡能力的拷貝,
立即返回成功拷貝的大小
;如快取區可用空間為0,則返回-1,同時設定errno為eagain.
linux下可用
sysctl -a | grep net.ipv4.tcp_wmem
檢視系統預設的傳送快取大小:
net.ipv4.tcp_wmem = 4096 16384 81920
這有三個值,第乙個值是socket的傳送快取區分配的最少位元組數,第二個值是預設值(該值會被net.core.wmem_default覆蓋),快取區在系統負載不重的情況下可以增長到這個值,第三個值是傳送快取區空間的最大位元組數(該值會被net.core.wmem_max覆蓋).
根據實際測試,如果手工更改了net.ipv4.tcp_wmem的值,則會按更改的值來執行,否則在預設情況下,協議棧通常是按net.core.wmem_default和net.core.wmem_max的值來分配記憶體的.
應用程式應該根據應用的特性在程式中更改傳送快取大小:
socklen_t sendbuflen = 0;
socklen_t len = sizeof(sendbuflen);
getsockopt(clientsocket, sol_socket, so_sndbuf, (void*)&sendbuflen, &len);
printf("default,sendbuf:%d\n", sendbuflen);
sendbuflen = 10240;
setsockopt(clientsocket, sol_socket, so_sndbuf, (void*)&sendbuflen, len);
getsockopt(clientsocket, sol_socket, so_sndbuf, (void*)&sendbuflen, &len);
printf("now,sendbuf:%d\n", sendbuflen);
需要注意的是,雖然將傳送快取設定成了10k,但實際上,協議棧會將其擴大1倍,設為20k.
應用程式表現如下:
在實際應用中,如果傳送端是非阻塞傳送,由於網路的阻塞或者接收端處理過慢,通常出現的情況是,傳送應用程式看起來傳送了10k的資料,但是只傳送了2k到 對端快取中,還有8k在本機快取中(未傳送或者未得到接收端的確認).那麼此時,接收應用程式能夠收到的資料為2k.假如接收應用程式呼叫recv函式獲 取了1k的資料在處理,在這個瞬間,發生了以下情況之一,雙方表現為:
a. 傳送應用程式認為send完了10k資料,關閉了socket:
傳送主機作為tcp的主動關閉者,連線將處於fin_wait1的半關閉狀態(等待對方的ack),並且,傳送快取中的8k資料並不清除,依然會傳送給對 端.如果接收應用程式依然在recv,那麼它會收到餘下的8k資料(這個前題是,接收端會在傳送端fin_wait1狀態超時前收到餘下的8k資料.), 然後得到乙個對端socket被關閉的訊息(recv返回0).這時,應該進行關閉.
b. 傳送應用程式再次呼叫send傳送8k的資料:
假如傳送快取的空間為20k,那麼傳送快取可用空間為20-8=12k,大於請求傳送的8k,所以send函式將資料做拷貝後,並立即返回8192;
假如傳送快取的空間為12k,那麼此時傳送快取可用空間還有12-8=4k,send()會返回4096,應用程式發現返回的值小於請求傳送的大小值後,可以認 為快取區已滿,這時必須阻塞(或通過select等待下一次socket可寫的訊號),如果應用程式不理會,立即再次呼叫send,那麼會得到-1的值, 在linux下表現為errno=eagain.
c. 接收應用程式在處理完1k資料後,關閉了socket:
接收主機作為主動關閉者,連線將處於fin_wait1的半關閉狀態(等待對方的ack).然後,傳送應用程式會收到socket可讀的訊號(通常是 select呼叫返回socket可讀),但在讀取時會發現recv函式返回0,這時應該呼叫close函式來關閉socket(傳送給對方ack);
如果應用程式通過select()函式僅檢測該socket控制代碼是否
可寫,它會返回應用層可寫.
假設傳送應用程式收到可讀或可寫的訊號後,繼續send,send將返回-1,同時errno設為econnreset表示對端網路已斷開.
但是,當傳送應用程式沒有使用select機制,則可能會收到sigpipe訊號,這取決於send時是發生成rst標誌收到之前還是之後.如果是之後,則會產生sigpipe訊號,該訊號的預設響應動作是退出程序,如果忽略該訊號,那麼send是返回-1,errno為epipe;
如果是在傳送端收到rst標誌之前,則send像往常一樣工作;
以上說的是非阻塞的send情況,假如send是阻塞呼叫,並且正好處於阻塞時(例如一次性傳送乙個巨大的buf,超出了傳送快取),對端socket關閉,那麼send將返回成功傳送的位元組數,如果再次呼叫send,會收到econnreset的錯誤.
d. 交換機或路由器的網路斷開:
接收應用程式在處理完已收到的1k資料後,會繼續從快取區讀取餘下的1k資料,然後就表現為無資料可讀的現象,這種情況需要應用程式來處理超時.一般做法是設定乙個select等待的最大時間,如果超出這個時間依然沒有資料可讀,則認為socket已不可用.
傳送應用程式會不斷的將餘下的資料傳送到網路上,但始終得不到確認,所以快取區的可用空間持續為0,這種情況也需要應用程式來處理.
如果不由應用程式來處理這種情況超時的情況,也可以通過tcp協議本身來處理,具體可以檢視sysctl項中的:
net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time
所以,要想編寫優秀的socket程式也是很不容易的.特別是在為應用做優化時,很多任務作都非常的煩瑣.
關於send函式在阻塞模式和非阻塞模式下的區別
在阻塞模式下,send函式的過程是將應用程式請求傳送的資料拷貝到傳送快取中傳送並得到確認後再返回.但由於傳送快取的存在,表現為 如果傳送快取大小比請求傳送的大小要大,那麼send函式立即返回,同時向網路中傳送資料 否則,send向網路傳送快取中不能容納的那部分資料,並等待對端確認後再返回 接收端只要...
recv 和 send 阻塞和非阻塞的區別
目錄答案 深入說明 在 epoll 中的應用 總結 拓展阻塞,事情幹不完就不要回來了!非阻塞,能幹多少就是多少,趕緊回來!將核心接收緩衝區中的資料 copy 到應用層中使用者的 buffer 中。int recv int sockfd,void buf,size t len,int flag 將應用...
send函式詳解
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!服務端都已經accept了客戶端的請求,於是客戶端與服務端也就勾搭上了,可以曖昧了,可以發資訊了,怎麼傳送呢?用send函式即可,我們來看看send函式的原型 winsock api linkageintwsaapisend socket s,co...