网络基础知识-面试
常用的三個狀態是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。
主動關閉的一方在發送最后一個 ack 后,就會進入 TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間,這個是TCP/IP必不可少的,也就是“解決”不了的。
為什么要TIME_WAIT等待呢?
1. 如果 【最后一步】A 響應ACK 包丟失,B 會以為 A 沒有收到自己的關閉請求,然后B會重試向 A 再發 FIN 包。如果沒有 TIME_WAIT 狀態,A 不再保存這個連接的信息,收到一個不存在的連接的包,A 會響應 RST 包,導致 B 端異常響應。
此時, TIME_WAIT 是為了保證 TCP 連接正常終止。
2. TCP 下的 IP 層協議是無法保證包傳輸的先后順序的。如果雙方揮手之后,一個網絡四元組(src/dst ip/port)被回收,而此時網絡中還有一個【遲到的數據包】沒有被 B 接收,A 應用程序又立刻使用了同樣的四元組再創建了一個新的連接后,這個遲到的數據包才到達 B,那么這個數據包就會讓 B 以為是 A 剛發過來的。
此時, TIME_WAIT 是為了保證迷失的數據包正常過期。
對于基于TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態。
TIME_WAIT場景
1.客戶端連接服務器的80服務,這時客戶端會啟用一個本地的端口訪問服務器的80,訪問完成后關閉此連接,立刻再次訪問服務器的80,
? 這時客戶端會啟用另一個本地的端口,而不是剛才使用的那個本地端口。原因就是剛才的那個連接【四元組決定】還處于TIME_WAIT狀態。
2.客戶端連接服務器的80服務,這時服務器關閉80端口,立即再次重啟80端口的服務,這時可能不會成功啟動,原因也是服務器的連接
? 還處于TIME_WAIT狀態。(如:nginx服務器)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Q:問一下TIME_WAIT有什么問題,是閑置而且內存不回收嗎?
A:是的,這樣的現象實際是正常的,有時和訪問量大有關,設置 /etc/sysctl.conf 這兩個參數:
net.ipv4.tcp_tw_reuse? ?是表示是否允許處于TIME-WAIT狀態的socket重新應用于新的TCP連接;
net.ipv4.tcp_tw_recycle 是加速TIME-WAIT sockets回收
Q: 我正在寫一個unix server程序,不是daemon,經常需要在命令行上重啟它,絕大多數時候工作正常,但是某些時候會報告"bind: address in use",于是重啟失敗。
A: server程序總是應該在調用 bind()之前 設置SO_REUSEADDR套接字選項。至于TIME_WAIT狀態,你無法避免,那是TCP協議的一部分。
Q: 如何避免等待60秒之后才能重啟服務
A: 使用setsockopt,比如 ?
int option = 1;
if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,sizeof( option ) ) < 0 )
{
die( "setsockopt" );
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TIME_WAIT在web server中的場景
再引用網絡資源的一段話:
? 1.值得一說的是,對于基于TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可想而知,對于訪問量大的Web Server,會存在大量的TIME_WAIT狀態,
? 假如server一秒鐘接收1000個請求,那么就會積壓 240*1000=240,000個 TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代操作系統都會用快速的查找算法來管理這些
? TIME_WAIT,所以對于新的TCP連接請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這么多狀態要維護總是不好。 ?
? 2.HTTP協議1.1版規定default行為是Keep-Alive,也就是會重用TCP連接傳輸多個 request/response,一個主要原因就是發現了這個問題。 ?
也就是說HTTP的交互跟上面畫的那個圖是不一樣的,關閉連接的不是客戶端,而是服務器,所以web服務器也是會出現大量的TIME_WAIT的情況的。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
方法一
解決思路很簡單,就是讓服務器能夠快速回收和重用那些TIME_WAIT的資源。
/etc/sysctl.conf文件的修改:
#表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0,表示關閉 ?
net.ipv4.tcp_tw_reuse = 1 ?
#表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉 ?
net.ipv4.tcp_tw_recycle = 1 ?
修改完之后執行/sbin/sysctl -p讓參數生效。
方法二
在高并發短連接的server端,當server處理完client的請求后立刻closesocket此時會出現大量time_wait狀態,導致連接不上,
用linger強制關閉可以解決此問題,但是linger可能會導致數據丟失,linger值為0時是強制關閉,無論并發多少都能正常連接上,如果非0會發生部分連接不上的情況!
(可調用setsockopt設置套接字的linger延時標志,同時將延時時間設置為0。)
這里有一個基本原則:
設置SO_LINGER選項后,close的成功返回只是告訴我們先前發送的數據和FIN已由對端TCP確認,而不能告訴我們對端應用程序是否已成功接收數據。
但是如果我們不設置這個選項,我們連TCP是否確認了數據都不知道。
int option=0;
if ( setsockopt ( mastersocket, SOL_SOCKET, SO_LINGER, &option,sizeof(option)) < 0)
{
??? die("setsockopt");
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
必看:待處理錯誤? 保活探測分節、http://blog.csdn.net/u012062760/article/details/45173351
?默認情況下,服務器通過socket、bind和listen重新啟動時,由于它試圖捆綁一個現有連接(即正由早先派生的那個子進程處理著連接)的端口,bind會失敗。但是如果在socket和bind之間調用SO_REUSEADDR選項,那么bind會成功
?以下為使用SO_REUSEADDR建議:
?????????? 1、在所有TCP服務器中,在調用bind之前設置SO_REUSEADDR選項
?????????? 2、當編寫一個可在同一時刻在同一主機上運行多次的多播應用程序時,設置SO_REUSEADDR選項,并將所參加多播組的地址作為本地IP地址捆綁
以下我們對幾種close返回做一個總結:
1、 close立即返回,根本不等待
2、 close拖延到接收到對端對FIN的ACK才返回
3、 后跟一個read調用的shutdown一直等到接收對端的FIN才返回
另一種可以讓客戶知道服務器應用已經接受數據的方法是應用級ACK。
客戶端在發送完數據后調用read讀取一個字節的數據:
char ack; ?
write();??? ??? ???? //客戶端寫數據給服務器 ?
read();???????????? //準備接收一個結束數據
而服務器在接受完數據后發送一個字節的應用級ACK:
nbytes = read();??? //接收完客戶端的數據 ?
write();??????????? //向客戶端寫一個應用級ACK ?
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
recv和send的阻塞和非阻塞
這里只描述同步Socket的send函數的執行流程。當調用該函數時:
(1)send先比較待發送數據的長度len和套接字socket發送緩沖區的長度, 如果len大于socket的發送緩沖區的長度,該函數返回SOCKET_ERROR;
(2)如果len小于或者等于socket的發送緩沖區的長度,那么send先檢查協議是否正在發送socket的發送緩沖區中的數據【鎖住發送緩沖區】,如果是就等待協議把數據發送完,
如果協議 還沒有開始發送socket的發送緩沖中的數據或者s的發送緩沖中沒有數據,那么send就比較socket發送緩沖區的剩余空間和len
(3)如果len大于剩余空間大小,send就一直等待協議把socket的發送緩沖中的數據發送完
(4)如果len小于剩余 空間大小,send就僅僅把buf中的數據copy到剩余空間里(注意并不是send把socket的發送緩沖中的數據傳到連接的另一端的,
而是協議傳的,send僅僅是把buf中的數據copy到socket的發送緩沖區的剩余空間里)。
這里只描述同步Socket的recv函數的執行流程。當應用程序調用recv函數時:
(1)recv先等待socket發送緩沖中的數據被協議傳送完畢,如果協議在傳送socket發送緩沖中的數據時出現網絡錯誤,那么recv函數返回SOCKET_ERROR,
(2)如果socket發送緩沖中沒有數據或者數據被協議成功發送完畢后,recv先檢查套接字socket接收緩沖區,如果socket接收緩沖區中沒有數據或者協議正在接收數據,
那么recv就一直等待,直到協議把數據接收完畢。當協議把數據接收完畢,recv函數就把socket接收緩沖中的數據copy到buf中(注意協議接收到的數據可能大于buf的長度,
所以 在這種情況下要調用幾次recv函數才能把socket接收緩沖中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),
recv函數返回其實際copy的字節數。如果recv在copy時出錯,那么它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那么它返回0。
注意:在Unix系統下,如果recv函數在等待協議接收數據時網絡斷開了,那么調用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
select的幾大缺點:
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數量太小了,默認是1024
poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多。
? ? ? ?對于第一個缺點,epoll的解決方案在epoll_ctl函數中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),
會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。
對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)
并為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。
epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。
對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左右,
具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。
select和epoll 原理概述&優缺點比較
就像收本子的班長,以前得一個個學生地去問有沒有本子,如果沒有,它還得等待一段時間而后又繼續問,現在好了,只走一次,如果沒有本子,班長就告訴大家去那里交本子,當班長想起要取本子,就去那里看看或者等待一定時間后離開,有本子到了就叫醒他,然后取走。
也許在細節方面不是特別恰當,但是總的來說,比較形象地說出了select和epoll的區別。
下面我將簡單明了地概述下兩者的原理,并概況兩者的優缺點。
select原理概述
調用select時,會發生以下事情:
從用戶空間拷貝fd_set到內核空間;
注冊回調函數__pollwait;
遍歷所有fd,對全部指定設備做一次poll(這里的poll是一個文件操作,它有兩個參數,一個是文件fd本身,一個是當設備尚未就緒時調用的回調函數__pollwait,這個函數把設備自己特有的等待隊列傳給內核,讓內核把當前的進程掛載到其中);
當設備就緒時,設備就會喚醒在自己特有等待隊列中的【所有】節點,于是當前進程就獲取到了完成的信號。poll文件操作返回的是一組標準的掩碼,其中的各個位指示當前的不同的就緒狀態(全0為沒有任何事件觸發),根據mask可對fd_set賦值;
如果所有設備返回的掩碼都沒有顯示任何的事件觸發,就去掉回調函數的函數指針,進入有限時的睡眠狀態,再恢復和不斷做poll,再作有限時的睡眠,直到其中一個設備有事件觸發為止。
只要有事件觸發,系統調用返回,將fd_set從內核空間拷貝到用戶空間,回到用戶態,用戶就可以對相關的fd作進一步的讀或者寫操作了。
epoll原理概述
調用epoll_create時,做了以下事情:
內核幫我們在epoll文件系統里建了個file結點;
在內核cache里建了個紅黑樹用于存儲以后epoll_ctl傳來的socket;
建立一個list鏈表,用于存儲準備就緒的事件。
調用epoll_ctl時,做了以下事情:
把socket放到epoll文件系統里file對象對應的紅黑樹上;
給內核中斷處理程序注冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表里。
調用epoll_wait時,做了以下事情:
觀察list鏈表里有沒有數據。有數據就返回,沒有數據就sleep,等到timeout時間到后即使鏈表沒數據也返回。而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的準備就緒句柄而已,所以,epoll_wait僅需要從內核態copy少量的句柄到用戶態而已。
總結如下:
一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,解決了大并發下的socket處理問題。
執行epoll_create時,創建了紅黑樹和就緒鏈表;?
執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,用于當中斷事件來臨時向準備就緒鏈表中插入數據;?
執行epoll_wait時立刻返回準備就緒鏈表里的數據即可。
兩種模式的區別:
LT模式下,只要一個句柄上的事件一次沒有處理完,會在以后調用epoll_wait時重復返回這個句柄,而ET模式僅在第一次返回。
兩種模式的實現:
當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,然后清空準備就緒list鏈表,最后,epoll_wait檢查這些socket,如果是LT模式,并且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表。所以,LT模式的句柄,只要它上面還有事件,epoll_wait每次都會返回。
對比
select缺點:
最大并發數限制:使用32個整數的32位,即32*32=1024來標識fd,雖然可修改,但是有以下第二點的瓶頸;
效率低:每次都會線性掃描整個fd_set,集合越大速度越慢;
內核/用戶空間內存拷貝問題。
epoll的提升:
本身沒有最大并發連接的限制,僅受系統中進程能打開的最大文件數目限制;
效率提升:只有活躍的socket才會主動的去調用callback函數;
省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
當然,以上的優缺點僅僅是特定場景下的情況:高并發,且任一時間只有少數socket是活躍的。
如果在并發量低,socket都比較活躍的情況下,select就不見得比epoll慢了(就像我們常常說快排比插入排序快,但是在特定情況下這并不成立)。
?
總結
- 上一篇: mysql一个用户SQL慢查询分析,原因
- 下一篇: 数据库基础-面试