08listen佇列剖析、connect函式、accept函式
阿新 • • 發佈:2022-03-05
一、listen()佇列剖析
listen():監聽埠,用在 TCP連線 中的 伺服器端 角色;
listen()函式呼叫格式:
int listen(int sockfd, int backlog);
要理解好backlog這個引數, “監聽套接字 佇列”;
監聽套接字的佇列
對於一個呼叫 listen()進行監聽的套接字,作業系統會給這個套接字 維護兩個佇列; a)未完成連線佇列 【儲存連線用的】 當客戶端 傳送tcp連線三次握手的第一次【syn包】給伺服器的時候,伺服器就會在未完成佇列中建立一個 跟這個 syn包對應的一項, 其實,我們可以把這項看成是一個半連線【因為連線還沒建立起來呢】,這個半連線的狀態會從LISTEN變成SYN_RCVD狀態,同時給客戶端返回第二次握手包【syn,ack】 這個時候,其實伺服器是在等待完成第三次握手; b)已完成連線佇列 【儲存連線用的】 當第三次握手完成了,這個連線就變成了ESTABLISHED狀態,每個已經完成三次握手的客戶端 都放在這個佇列中作為一項(如圖所示) backlog“曾經”的含義:已完成佇列和未完成佇列裡邊條目之和 不能超過 backlog;
客戶端connect()函式
(1)客戶端這個connect()什麼時候返回,其實是收到三次握手的第二次握手包(也就是收到伺服器發回來的syn/ack)之後就返回了; (2)RTT是未完成佇列中 任意一項 在未完成佇列中 留存的時間,這個時間取決於客戶端和伺服器; 對於客戶端,這個RTT時間是第一次和第二次握手加起來的時間; 對於伺服器,這個RTT時間實際上是第二次和第三次握手加起來的時間; 如果這三次握手包傳遞速度特別快的話,大概187毫秒能夠建立起來這個連線;這個時間挺慢,所以感覺建立TCP連線的成本挺高;【短連線遊戲-挺噁心的】 (3)如果一個惡意客戶,遲遲不傳送三次握手的第三個包。那麼這個連線就建立不起來,那麼這個處於SYN_RCVD的這一項【伺服器端的未完成佇列中】, 就會一致停留在伺服器的未完成佇列中,這個停留時間大概是75秒,如果超過這個時間,這一項會被作業系統幹掉;
accept()函式
accept()函式,就使用來 從 已完成連線佇列 中 的隊首【隊頭】位置取出來一項【每一項都是一個已經完成三路握手的TCP連線】,返回給程序; 如果已完成連線佇列是空的呢?那麼咱們這個範例中 accept()會一直卡在這裡【休眠】等待,直到已完成佇列中有一項時才會被喚醒; 所以,從程式設計角度,我們要儘快的用 accept()把已完成佇列中的資料【TCP連線】取走,大家必須有這個認識; accept()返回的是個套接字(客戶端的套接字),這個套接字就代表那個已經用三次握手建立起來的那個tcp連線,因為accept()是從 已完成佇列中取的資料; 換句話來說,我們伺服器程式,必須要嚴格區分兩個套接字: a)監聽9000埠這個套接字,這個東西叫“監聽套接字【listenfd】”,只要伺服器程式在執行,這個套接字就應該一直存在; b)當客戶端連線進來,作業系統會為每個成功建立三次握手的客端再建立一個套接字【當然是一個已經連線套接字】,accept()返回的就是這種套接字connfd(客戶端套接字); 也就是從已完成連線佇列中取得的一項。隨後,伺服器使用這個 accept()返回的套接字和客戶端通訊的;
問題:
(1)如果兩個佇列之和【已完成連線佇列,和未完成連線佇列】達到了listen()所指定的第二引數,也就是說佇列滿了;此時,再有一個客戶傳送syn請求,伺服器怎麼反應?
----實際上伺服器會忽略這個syn,不給迴應; 客戶端這邊,發現syn沒回應,過一會會重發syn包;
(2)從連線被扔到已經完成佇列中去,到accept()從已完成佇列中把這個連線取出這個之間是有個時間差的,如果還沒等accept()從已完成佇列中把這個連線取走的時候,客戶端如果傳送來資料,這個資料就會被儲存再已經連線的套接字的接收緩衝區裡,這個緩衝區有多大,最大就能接收多少資料量;
syn攻擊
syn攻擊【syn flood】:典型的利用TCP/IP協議涉及弱點進行坑爹的一種行為;
拒絕服務攻擊 (DOS/DDOS):發生在第一次握手和第二次握手,不斷偽造syn包,不停的發第一次握手報。監聽佇列被惡意裝滿了
backlog:進一步明確和規定了:指定給定套接字上核心為之排隊的最大已完成連線數【已完成連線佇列中最大條目數】;
在寫程式碼時儘快用 accept()把已完成佇列裡邊的連線取走,儘快 留出空閒為止給後續的已完成三路握手的條目用,那麼這個已完成佇列一般不會滿;(一般這個backlog值給300左右)