一:TCP狀態轉換
//同一個IP(INADDR_ANY),同一個端口SERV_PORT,只能被成功的bind()一次,若再次bind()就會失敗,並且顯示:Address already in use
//就好像一個班級裏不能有兩個人叫張三;
//結論:相同IP地址的相同端口,只能被bind一次;第二次bind會失敗;
//介紹命令netstat:顯示網絡相關信息
//-a:顯示所有選項
//-n:能顯示成數字的內容全部顯示成數字
//-p:顯示段落這對應程序名
//netstat -anp | grep -E 'State|9000'
//我們用兩個客戶端連接到服務器,服務器給每個客戶端發送一串字符"I sent sth to client!\n",並關閉客戶端;
//我們用netstat觀察,原來那個監聽端口 一直在監聽【listen】,但是當來了兩個連接之後【連接到服務器的9000端口】,
//雖然這兩個連接被close掉了,但是產生了兩條TIME_WAIT狀態的信息【因爲你有兩個客戶端連入進來】
//只要客戶端 連接到服務器,並且 服務器把客戶端關閉,那麼服務器端就會產生一條針對9000監聽端口的 狀態爲 TIME_WAIT 的連接;
//只要用netstat看到 TIME_WAIT狀態的連接,那麼此時, 你殺掉服務器程序再重新啓動,就會啓動失敗,bind()函數返回失敗:
//bind返回的值爲-1,錯誤碼爲:98,錯誤信息爲:Address already in use
//TIME_WAIT:涉及到TCP狀態轉換這個話題了;
//《Unix網絡編程 第三版 卷1》有第二章第六節,2.6.4小節,裏邊就有一個TCP狀態轉換圖;
//第二章第七節,專門介紹了 TIME_WAIT狀態;
//TCP狀態轉換圖【11種狀態】 是 針對“一個TCP連接【一個socket連接】”來說的;
//客戶端: CLOSED ->SYN_SENT->ESTABLISHED【連接建立,可以進行數據收發】
//服務端: CLOSED ->LISTEN->【客戶端來握手】SYN_RCVD->ESTABLISHED【連接建立,可以進行數據收發】
//誰主動close連接,誰就會給對方發送一個FIN標誌置位的一個數據包給對方;【服務器端發送FIN包給客戶端】
//服務器主動關閉連接:ESTABLISHED->FIN_WAIT1->FIN_WAIT2->TIME_WAIT
//客戶端被動關閉:ESTABLISHED->CLOSE_WAIT->LAST_ACK
二:TIME_WAIT狀態
//具有TIME_WAIT狀態的TCP連接,就好像一種殘留的信息一樣;當這種狀態存在的時候,服務器程序退出並重新執行會失敗,會提示:
//bind返回的值爲-1,錯誤碼爲:98,錯誤信息爲:Address already in use
//所以,TIME_WAIT狀態是一個讓人不喜歡的狀態;
//連接處於TIME_WAIT狀態是有時間限制的(1-4分鐘之間) = 2 MSL【最長數據包生命週期】;
//引入TIME_WAIT狀態【並且處於這種狀態的時間爲1-4分鐘】 的原因:
//(1)可靠的實現TCP全雙工的終止
//如果服務器最後發送的ACK【應答】包因爲某種原因丟失了,那麼客戶端一定 會重新發送FIN,這樣
//因爲服務器端有TIME_WAIT的存在,服務器會重新發送ACK包給客戶端,但是如果沒有TIME_WAIT這個狀態,那麼
//無論客戶端收到ACK包,服務器都已經關閉連接了,此時客戶端重新發送FIN,服務器給回的就不是ACK包,
//而是RST【連接復位】包,從而使客戶端沒有完成正常的4次揮手,不友好,而且有可能造成數據包丟失;
//也就是說,TIME_WAIT有助於可靠的實現TCP全雙工連接的終止;
//(二.一)RST標誌
//對於每一個TCP連接,操作系統是要開闢出來一個收緩衝區,和一個發送緩衝區 來處理數據的收和發;
//當我們close一個TCP連接時,如果我們這個發送緩衝區有數據,那麼操作系統會很優雅的把發送緩衝區裏的數據發送完畢,然後再發fin包表示連接關閉;
//FIN【四次揮手】,是個優雅的關閉標誌,表示正常的TCP連接關閉;
//反觀RST標誌:出現這個標誌的包一般都表示 異常關閉;如果發生了異常,一般都會導致丟失一些數據包;
//如果將來用setsockopt(SO_LINGER)選項要是開啓;發送的就是RST包,此時發送緩衝區的數據會被丟棄;
//RST是異常關閉,是粗暴關閉,不是正常的四次揮手關閉,所以如果你這麼關閉tcp連接,那麼主動關閉一方也不會進入TIME_WAIT;
//(2)允許老的重複的TCP數據包在網絡中消逝;
三:SO_REUSEADDR選項
//setsockopt(SO_REUSEADDR)用在服務器端,socket()創建之後,bind()之前
//SO_REUSEADDR的能力:
//(1)SO_REUSEADDR允許啓動一個監聽服務器並捆綁其端口,即使以前建立的將端口用作他們的本地端口的連接仍舊存在;
//【即便TIME_WAIT狀態存在,服務器bind()也能成功】
//(2)允許同一個端口上啓動同一個服務器的多個實例,只要每個實例捆綁一個不同的本地IP地址即可;
//(3)SO_REUSEADDR允許單個進程捆綁同一個端口到多個套接字,只要每次捆綁指定不同的本地IP地址即可;
//(4)SO_REUSEADDR允許完全重複的綁定:當一個IP地址和端口已經綁定到某個套接字上時,如果傳輸協議支持,
//同樣的IP地址和端口還可以綁定到另一個套接字上;一般來說本特性僅支持UDP套接字[TCP不行];
//***************
//所有TCP服務器都應該指定本套接字選項,以防止當套接字處於TIME_WAIT時bind()失敗的情形出現;
//試驗程序nginx5_3_2_server.c
//(3.1)兩個進程,綁定同一個IP和端口:bind()失敗[一個班級不能有兩個人叫張三]
//(3.2)TIME_WAIT狀態時的bind綁定:bind()成功
//SO_REUSEADDR:主要解決TIME_WAIT狀態導致bind()失敗的問題;