文章目錄
- 1.延遲確認應答
- 2.捎帶應答
- 3.面向位元組流
- 4.粘包問題
- 5.TCP例外情況
- 6.理解listen的第二個引數
- 6.1全鏈接佇列和半鏈接佇列
- 6.2為什么需要全鏈接佇列和半鏈接佇列
- 6.3實驗驗證
- 6.4原碼驗證
- 7.TCP小結
- 7.1為什么TCP這么復雜?
- 7.2基于TCP應用層協議
1.延遲確認應答

接收資料的主機如果每次都立刻回復確認應答的話,可能會回傳一個較小的視窗,那是因為剛接收完資料,緩沖區已滿,
當某個接收端收到這個小視窗的通知以后,會以它為上限發送資料,從而又降低了網路的利用率(這其實是視窗控制特有的問題,專門術語叫做糊涂視窗綜合征(SWS:Silly Window Syndrome),) ,為此,引入了一個方法,那就是收到資料以后并不立即回傳確認應答,而是延遲一段時間的機制,
視窗越大(接識訓沖區大小),網路的吞吐量就越大,效率就越高,我們的目標是保證在網路不擁塞的情況下,盡量的提高傳輸的效率
延遲應答的兩種方法:
1.數量限制:每隔N個包就應答一次
2.時間限制:超過最大延遲時間就應答一次
具體時間和超時時間,依據作業系統的不同也有差異;
一般N取2,超時時間為0.2s(這個時間越小、CPU的負荷會越高,性能也下降,反之,這個時間越長,越有可能觸發發送主機的重發處理,而視窗為只有1個資料段的時候,性能也會下降)
事實上,大可不必為每一個資料段都進行一次確認應答,TCP采用滑動視窗的控制機制,因此通常確認應答少一些也無妨,TCP檔案傳輸中,絕大多數是每兩個資料段回傳一次確認應答

2.捎帶應答
根據應用層協議,發送出去的訊息到達對端,對端進行處理以后,會回傳一個回執
在此類通信當中,TCP的確認應答(將ACK標志位置為1即可)和回執資料可以通過一個包發送,這種方式叫做捎帶應答,通過這種機制,可以使收發的資料量減少,
捎帶應答是指在同一個TCP包中既發送資料又發送確認應答的一種機制,由此,網路的利用率會提高,計算機的負荷也會減輕,不過,確認應答必須得等到應用處理完資料并將作為回執的資料回傳為止,才能進行捎帶應答,
接收資料以后如果立刻回傳確認應答,就無法實作捎帶應答,而是將所接收的資料傳給應用處理生成回傳資料以后進再進行發送請求為止,必須一直等待確認應答的發送,也就是說,如果沒有啟用延遲確認應答就無法實作捎帶應答,延遲確認應答是能夠提高網路利用率從而降低計算機處理負荷的一種較優的處理機制,
由于捎帶應答的存在,四次揮手,可以合并成三次揮手

3.面向位元組流
TCP發送和接收資料是通過緩沖區來完成的
發送:
發送的時候,并不是直接將資料發送至網路之中,而是通過系統調send(write)用將資料寫入發送快取區之中,然后作業系統將緩沖區的內容通過網卡驅動程式發送出去
如果一個TCP資料包太長,那么這個資料包會被拆分成多次發送,如果太短,會被寫入緩沖區之中,等待達到緩沖區的最低水位線,再進行發送,所以發送的資料沒有邊界,不一定是完整的
接收:
接收的時候,也不是直接從網路之中接收資料,而是通過網卡驅動程式從網路中接收資料,然后寫入緩沖區之中,再呼叫recv(read)從緩沖區中讀取資料,因此讀取的時候并不一定一次將一個資料讀完,而是有可能一個資料包分多次讀完
由于緩沖區的存在,讀和寫不需要匹配進行,這種就叫做面向位元組流
UDP需要保證資料的完整性(16位UDP長度,如果不完整會被直接丟棄),一個資料要么發,要么不發,所以叫做面向資料報
4.粘包問題
是什么:
在傳輸層的緩沖區之中資料都是一個個的位元組,TCP不關心這些位元組代表什么意思
如果上層(應用層)讀取資料的時候,多讀了,或者少讀了,對下一次的資料產生影響的情況就叫做粘包問題
怎么避免:
應用層做了明確報文與報文邊界的作業:
第一個讀上去的肯定是請求的的第一個位元組,一直往后面讀取,讀到空行,說明請求的報頭讀取完畢了
再讀有可能是下一個報文的開始,也有可能是當前報文的正文,報頭之中包含了Content-Length,標識了正文的長度,通過這個長度即可完整的讀取一數個完整的資料報
HTTP之中的空行,Content-Length本質是在應用層明確報文以報文之間的邊界
UDP是否存在粘包?
不存在,這是因為UDP報頭之中,包含了一個首部長度(定長)和總長度
5.TCP例外情況
1.行程終止:套接字在內核之中對應的是一個檔案描述符,即套接字本質上也是一個檔案,檔案的宣告周期是隨行程的,行程終止會釋放檔案描述符,相對應的套接字也會被關閉,并且自動觸發四次揮手,斷開連接
2.機器重啟:和行程終止的情況相同
3.機器掉電/網線斷開:一瞬間網線斷開,客戶端沒有機會和服務器進行四次揮手,
此時接收端還處在連接的狀態,當接收端進行寫入的時候,一旦接收端發現連接已經不在了,就會進行reset
即是接收端沒有進行寫入操作,TCP也內置了一個保活定時器,會定期的詢問對方是否還在,如果對方不在,也會將鏈接給釋放到
除此之外,應用層的某些協議,也有一些這樣的檢測機制,比如,HTTP長連接中,也會定期檢測對方的狀態
6.理解listen的第二個引數
6.1全鏈接佇列和半鏈接佇列
listen的第二個引數為鏈接佇列長度,分為全鏈接佇列和半鏈接佇列
1.半鏈接佇列:用來保存SYN_SENT和SYN_RECV狀態的請求(三次握手沒有完成)
2.全鏈接佇列:accpetd佇列,用來保存established狀態,但是應用層沒有調accept取走請求
全鏈接佇列長度為listen第二個引數+1,全鏈接佇列滿了,就無法繼續讓當前連接的狀態進入established狀態了
6.2為什么需要全鏈接佇列和半鏈接佇列
全鏈接佇列:
全鏈接佇列的存在,保證作業系統隨時有任務可以執行,即當前任務執行完畢,隨時可以從鏈接佇列之中提取任務進行補充,提高執行緒的利用率,以及效率
但是鏈接佇列的長度也不能太長,因為保存這些鏈接也是需要消耗資源的,如果鏈接的長度太長,資源消耗的太多,那不如多創建一些執行緒來執行這些任務
半鏈接佇列:
半鏈接佇列保存的是三次握手沒有完成的一些狀態,這些鏈接是允許丟失的,不同的系統設定的是不一樣的
6.3實驗驗證
#include <iostream>
using namespace std;
#include <string>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class Server
{
private:
int port;
int lsock;
public:
Server(int _port)
:port(_port)
,lsock(-1)
{}
void InitServer()
{
signal(SIGCHLD,SIG_IGN);
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0)
{
cerr<<"sock error"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
int opt=1;
setsockopt(lsock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,0) < 0)//全鏈接佇列長度設定為0
{
cerr<<"listen error"<<endl;
}
}
void start()//不進行任何操作
{
while(true)
{
sleep(100);
}
}
~Server()
{
if(lsock!=-1)
close(lsock);
}
};

6.4原碼驗證

7.TCP小結
7.1為什么TCP這么復雜?
因為要保證可靠,同時又盡可能的提高效率問題
可靠性:
校驗和
序列號(按序到達)
確認應答(只有確認了應答,才保證資料可靠到達了)
超時重發
連接管理
流量控制
擁塞控制
提高效率:
滑動視窗
快速重傳
延遲應答
捎帶應答
其它:
定時器(超時重傳定時器、保活定時器(鏈接之后什么都不干,超過一段時間之后自動斷開)、TIME_WAIT定時器)

7.2基于TCP應用層協議
HTTP、HTTPS、SSH、Telnet、FTP、SMTP
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/287419.html
標籤:其他
