c++後臺開發面試常見知識點總結(二)網絡編程

1TCPUDP有什麼區別?html

 TCP是傳輸控制協議,提供的是面向鏈接的,可靠地字節流服務。使用三次握手創建鏈接,四次揮手釋放鏈接。UDP是用戶數據報協議,傳輸的是UDP數據報,是無鏈接的,並且沒有超時重發機制。 TCP保證數據按序到達,提供流量控制和擁塞控制,在網絡擁堵的時候會減慢發送字節數,而UDP無論網絡是否擁堵。  TCP是鏈接的,因此服務是一對一服務,而UDP能夠11,也能夠1對多(多播),也能夠多對多。linux

TCP可靠傳輸的保證:web

中止等待協議:每發送完一個分組就中止發送,等待對方確認。收到確認後再發送下一個分組。無差錯狀況下收到確認再發送。若是發送方超過一段時間仍沒有收到確認就會從新發送(超時重傳)。所以發送完必須保留分組副本,分組和確認分組都必須編號,超時計時器應該比平均往返時間長一點。 算法

連續ARQ協議:發送方每收到一個按序到達的確認,就把發送窗口向前移動一個分組的位置。shell

滑動窗口機制:窗口是緩存的一部分,用來暫時存放字節流。發送方和接收方各有一個窗口,接收方經過 TCP 報文段中的窗口字段告訴發送方本身的窗口大小,發送方根據這個值和其它信息設置本身的窗口大小。若是發送窗口左部的字節已經發送而且收到了確認,那麼就將發送窗口向右滑動必定距離,直到左部第一個字節不是已發送而且已確認的狀態;接收窗口的滑動相似,接收窗口左部字節已經發送確認並交付主機,就向右滑動接收窗口。接收窗口只會對窗口內最後一個按序到達的字節進行確認,例如接收窗口已經收到的字節爲 {31, 34, 35},其中 {31} 按序到達,而 {34, 35} 就不是,所以只對字節 31 進行確認。發送方獲得一個字節的確認以後,就知道這個字節以前的全部字節都已經被接收。編程

UDP如何實現可靠傳輸:windows

在應用層模仿傳輸層TCP的可靠性傳輸。不考慮擁塞處理的簡單設計。1、添加seq/ack機制,確保數據發送到對端。2、添加發送和接收緩衝區,主要是用戶超時重傳。3、添加超時重傳機制。數組

注:1、發送端發送數據時,生成一個隨機seq=x,而後每一片按照數據大小分配seq。數據到達接收端後接收端放入緩存,併發送一個ack=x+1的包,表示對方已經收到了數據。發送端收到了ack包後,刪除緩衝區對應的數據。2、時間到後,定時任務檢查是否須要重傳數據。瀏覽器

 

2創建和釋放TCP/IP過程。緩存

三次握手過程:TCP提供的可靠數據傳輸服務,是依靠接收端TCP軟件按序號對收到的數據分組進行逐一確認實現的。三次握手協議指的是在發送數據的準備階段,服務器端和客戶端之間須要進行三次交互:

第一次握手:客戶端發送syn(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;

第二次握手:服務器收到syn包,必須確認客戶的synack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,服務器收到此包,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

三次握手的緣由:第三次握手是爲了防止失效的鏈接請求到達服務器,讓服務器錯誤打開鏈接。客戶端發送的鏈接請求若是在網絡中滯留,那麼就會隔很長一段時間才能收到服務器端發回的鏈接確認。客戶端等待一個超時重傳時間以後,就會從新請求鏈接。可是這個滯留的鏈接請求最後仍是會到達服務器,若是不進行三次握手,那麼服務器就會打開兩個鏈接。若是有第三次握手,客戶端會忽略服務器以後發送的對滯留鏈接請求的鏈接確認,不進行第三次握手,所以就不會再次打開鏈接。

四次揮手: 數據傳輸結束後,通訊雙方均可以釋放鏈接。如今客戶端和服務器都處於ESTABLISHED狀態,客戶端應用進程向其TCP發出鏈接釋放報文段,主動關閉TCP鏈接。客戶端進入FIN_WAIT1(終止等待1)狀態。而後服務器馬上確認,服務器進入CLOSE_WAIT(關閉等待)狀態。此時TCP處於半關閉狀態,客戶端已經沒有數據要發送了,若是服務器仍要發送數據,客戶端仍然接收。客戶端收到服務器的確認後,就進入FIN_WAIT2(終止等待2)狀態,等待服務器發出鏈接釋放報文。 若是服務器已經沒有向客戶端發送的數據,則服務器發送請求釋放報文,服務器進入LAST_ACK(最後確認)階段,等待客戶端的最後確認。客戶端在收到服務器的請求後,要發出確認,而後進入TIME_WAIT(時間等待)狀態。此時,鏈接還未釋放,必須等待2MSL後,客戶端才進入CLOSED狀態。服務器收到客戶端最後的確認,進入CLOSED狀態,鏈接釋放。

次揮手的緣由:客戶端發送了 FIN 鏈接釋放報文以後,服務器收到了這個報文,就進入了 CLOSE-WAIT 狀態。這個狀態是爲了讓服務器端發送還未傳送完畢的數據,傳送完畢以後,服務器會發送 FIN 鏈接釋放報文。

TIME_WAIT客戶端接收到服務器端的 FIN 報文後進入此狀態,此時並非直接進入 CLOSED 狀態,還須要等待一個時間計時器設置的時間 2MSL。這麼作有兩個理由:

1. 確保最後一個確認報文段可以到達。若是 服務端 沒收到 客戶端 發送來的確認報文段,那麼就會從新發送鏈接釋放請求報文段,客戶端 等待一段時間就是爲了處理這種狀況的發生。

2. 等待一段時間是爲了讓本鏈接持續時間內所產生的全部報文段都從網絡中消失,使得下一個新的鏈接不會出現舊的鏈接請求報文段。

 

3)抓包分析發現服務器保持了大量CLOSE_ WAIT TIME_WAIT套接字

 

在服務器的平常維護過程當中,會常常用到下面的netstat命令查看TCP套接字的狀態:

TIME_WAIT 814    CLOSE_WAIT 1   FIN_WAIT1 1   ESTABLISHED 634
SYN_RECV 2     LAST_ACK 1

經常使用的三個狀態是:ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。

由於linux分配給一個用戶的文件句柄是有限的,而TIME_WAITCLOSE_WAIT兩種狀態若是一直被保持,那麼意味着對應數目的通道就一直被佔着,一旦達到句柄數上限,新的請求就沒法被處理。

 

服務器保持了大量TIME_WAIT狀態的套接字:TIME_WAIT是主動關閉鏈接的一方保持的狀態。 對於基於TCPHTTP協議,關閉TCP鏈接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可 想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,維護這些狀態給Server帶來負擔。

緣由:大量出現這個狀態每每說明系統的併發比較高,大量的鏈接創建和釋放,

處理:優化系統內核參數讓服務器可以快速回收和重用那些TIME_WAIT的資源。

 

服務器保持了大量CLOSE_WAIT狀態的套接字:

緣由:Server 程序處於CLOSE_WAIT狀態,而沒有跳轉到LAST_ACK狀態,說明Server尚未發FINClient,那麼多是在關閉鏈接以前還有許多數據要發送或者其餘事要作,致使沒有發這個FIN package。一般一個CLOSE_WAIT會維持至少2個小時的時間。若是有個惡意程序,給服務器形成一堆的CLOSE_WAIT,一般是等不到釋放那一刻,系統就已經崩潰了。 

解決:修改TCP/IP的參數,來縮短2個小時這個時間:修改tcp_keepalive_*系列參數有助於解決這個問題。

 

5.IO模型:

阻塞IO,非阻塞IOIO複用,信號驅動式IO,異步IO

同步IO和異步IO 

阻塞IO和非阻塞IO

對於一次IO訪問(以read舉例),數據會先被拷貝到操做系統內核的緩衝區中(),而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。因此說,當一個read操做發生時,它會經歷兩個階段:1. 等待數據準備  2.將數據從內核拷貝到進程中 

當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據(對於網絡IO來講,不少時候數據在一開始尚未到達。好比,尚未收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程須要等待,也就是說數據被拷貝到操做系統內核的緩衝區中是須要一個過程的。而在用戶進程這邊,整個進程會被阻塞(固然,是進程本身選擇的阻塞)。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除block的狀態,從新運行起來。

5.1. 阻塞IO的特色就是在IO執行的兩個階段都被block了。

 

5.2.非阻塞 IO的特色是用戶進程須要不斷的主動詢問kernel數據好了沒有。若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system callread),那麼它(read)立刻就將數據拷貝到了用戶內存,而後返回。

5.3. I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。阻塞IO第一階段是阻塞在recvfrom, . I/O 多路複用第一階段是阻塞在select.

5.4. 異步 I/O不阻塞,用戶進程發起read操做以後,當即返回,馬上就能夠開始去作其它的事。不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

5.5. 信號驅動式IO

 

同步I/O和異步I/O: 同步I/O 須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需本身負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

 

6. selectpollepoll的實現和區別

select:用戶進程調用select函數後會阻塞,直到有描述符就緒(有數據可讀、可寫、或者有except),或者超時(timeout指定等待時間,若是當即返回設爲null便可)函數返回。當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符。

優勢:select目前幾乎在全部的平臺上都支持,有良好的跨平臺支持。

缺點:1. 單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制,但 是這樣也會形成效率的下降。2. 輪尋排查當文件描述符個數不少時,效率很低。poll經過一個可變長度的數組解決了select文件描述符受限的問題,但輪尋排查的問題未解決。epoll採用只返回狀態發生變化的文件描述符,便解決了輪尋的瓶頸。

 

Poll: poll經過一個可變長度的數組解決了select文件描述符受限的問題,但輪尋排查的問題未解決。

 

epoll: 沒有描述符限制。採用只返回狀態發生變化的文件描述符,便解決了輪尋的瓶頸。

epoll對文件描述符的操做有兩種模式:LTlevel triggerETedge trigger

LT模式是默認模式,而且同時支持blockno-block socket.在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你的。

ET模式是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,直到你作了某些操做致使那個文件描述符再也不爲就緒狀態了。可是請注意,若是一直不對這個fdIO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once)ET模式在很大程度上減小了epoll事件被重複觸發的次數,所以效率要比LT模式高。epoll工做在ET模式的時候,必須使用非阻塞套接口,以免因爲一個文件句柄的阻塞讀/阻塞寫操做把處理多個文件描述符的任務餓死。

Epoll的優點:1. 監視的描述符數量不受限制。2. IO的效率不會隨着監視fd的數量的增加而降低。epoll不一樣於selectpoll輪詢的方式,而是經過爲每一個fd定義的回調函數來實現的。只有就緒的fd纔會執行回調函數。因此epoll只返回狀態發生變化的文件描述符。每一個描述符就緒時本身調用回調函數上報給epoll

epoll實現源碼:(紅黑樹+就緒隊列),爲何用紅黑樹實現,有哪些好處

紅黑樹存儲epoll所監聽的套接字。當添加,刪除或者修改(查找)一個套接字時(epoll_ctl),都在紅黑樹上去處理,紅黑樹自己插入和刪除性能比較好,時間複雜度O(logN)

List鏈表用於存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表裏有沒有數據便可。有數據就返回,沒有數據就sleep,等到timeout時間到後即便鏈表沒數據也返回。因此,epoll_wait很是高效。

EPOLLONESHOT:

背景:多線程環境下,一個SOCKET事件到來,數據開始解析,這時候這個SOCKET又來了一樣一個這樣的事件,而你的數據解析還沒有完成,那麼程序會自動調度另一個線程或者進程來處理新的事件,這形成一個很嚴重的問題,不一樣的線程或者進程在處理同一個SOCKET的事件,這會使程序的健壯性大下降而編程的複雜度大大增長!!即便在ET模式下也有可能出現這種狀況。若是對描述符socket註冊了EPOLLONESHOT事件,那麼操做系統最多觸發其上註冊的一個可讀、可寫或者異常事件,且只觸發一次。。想要下次再觸發則必須使用epoll_ctl重置該描述符上註冊的事件,包括EPOLLONESHOT 事件。

EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏 

 

7.流量控制和擁塞控制

流量控制是爲了控制發送方發送速率,保證接收方來得及接收。擁塞控制是爲了控制發送方發送速率下降整個網絡的擁塞程度。擁塞控制4種算法:1.慢開始與擁塞避免 2.快重傳與快恢復。

 

8. TCP協議是如何保證可靠傳輸的?如何用udp封裝實現tcp

TCP經過序列號、檢驗和、確認應答信號、重發控制、鏈接管理、窗口控制、流量控制、擁塞控制實現可靠性。

具體表現:針對發送端發出的數據包序列號的確認應答信號ACK;針對數據包丟失或者出現定時器超時的重發機制;針對數據包到達接收端主機順序亂掉的順序控制;針對避免網絡擁堵時候的流量控制;針對剛開始啓動的時候避免一會兒發送大量數據包而致使網絡癱瘓的擁塞控制。

此外,TCP做爲一種面向有鏈接的控制傳輸協議,只有在確認對端主機存在時纔會發送數據,從而能夠控制通訊流量的浪費。

udp封裝實現tcp:

UDP不提供複雜的控制機制,不提供可靠傳輸機制,是盡最大能力交付的。在傳輸過程當中若是出現丟包,UDP也不負責重發,甚至當數據包的到達順序亂掉以後也沒有糾正順序的功能。但具備資源消耗小,處理速度快的優勢,因此一般音頻、視頻和普通數據在傳送時使用UDP較多,由於它們即便偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。

若是須要這些細節控制的話,就須要在採用UDP協議的應用層去做出處理。在應用層模仿傳輸層TCP的可靠性傳輸。目前有開源程序利用udp實現了可靠的數據傳輸。如UDT(基於UDP的數據傳輸協議(UDP-basedData Transfer Protocol,簡稱UDT))

 

 

9.瀏覽器訪問網站的一次完整過程?瀏覽器中輸入一個URL發生什麼,用到哪些協議?

 

1.DHCP 配置主機信息:

   若是主機最開始沒有 IP 地址以及其它信息,那麼就須要先使用 DHCP 來獲取。DHCP是基於UDP的,DHCP服務器是鏈接在交換機上的,不跨路由;在返回DHCP應答報文的時候通過交換機,用到了交換機的自學習能力。DHCP 配置了主機的IP 地址、子網掩碼和 DNS 服務器的 IP 地址,並在其 IP 轉發表中安裝默認網關。

2DNS 解析域名

   瀏覽器中輸入URL,首先瀏覽器要將URL解析爲IP地址,解析域名就要用到DNS協議,首先主機會查詢DNS的緩存,若是沒有就給本地DNS發送查詢請求。DNS查詢分爲兩種方式,一種是遞歸查詢,一種是迭代查詢。若是是迭代查詢,本地的DNS服務器,向根域名服務器發送查詢請求,根域名服務器告知該域名的一級域名服務器,而後本地服務器給該一級域名服務器發送查詢請求,而後依次類推直到查詢到該域名的IP地址。

DNS服務器是基於UDP的。DNS協議是跨路由的(DNS服務器可能會在路由外)。域名解析過程會須要網關路由器的 MAC 地址,而DHCP過程當中只知道網關路由器的IP地址,因此會用到ARP協議,解析IP地址得網關路由器的MAC地址(廣播的方式)

 3. HTTP 請求頁面

有了 HTTP 服務器的 IP 地址以後,主機就可以生成 TCP 套接字,該套接字將用於向 Web 服務器發送 HTTP GET 報文。在生成 TCP 套接字以前,必須先與 HTTP 服務器進行三次握手來創建鏈接。鏈接創建以後,瀏覽器生成 HTTP GET 報文,並交付給 HTTP 服務器。HTTP 服務器從 TCP 套接字讀取 HTTP GET 報文,生成一個 HTTP 響應報文,將 Web 頁面內容放入報文主體中,發回給主機。瀏覽器收到 HTTP 響應報文後,抽取出 Web 頁面內容,以後進行渲染,顯示 Web 頁面。

TCP的數據包會發送給IP層,用到IP協議。IP層經過路由選路,一跳一跳發送到目的地址。固然在一個網段內的尋址是經過以太網協議實現(也能夠是其餘物理層協議,好比PPPSLIP),以太網協議須要知道目的IP地址的物理地址(MAC地址),有須要ARP協議。

 

10.DDOS,怎麼解決,如何讓Server端收到ACK後再分配資源,不改變Client,不封裝IP數據包?

DDOS SYN Flood是經典的DDoS攻擊方式,SYN Flood攻擊利用了TCP三次握手的缺陷,可以以較小代價使目標服務器沒法響應。

TCP協議爲了實現可靠傳輸,在三次握手的過程當中設置了一些異常處理機制。第三步中若是服務器沒有收到客戶端的最終ACK確認報文,會一直處於SYN_RECV狀態,將客戶端IP加入等待列表,並重發第二步的SYN+ACK報文。重發通常進行3-5次,大約間隔30秒左右輪詢一次等待列表重試全部客戶端。另外一方面,服務器在本身發出了SYN+ACK報文後,會預分配資源爲即將創建的TCP鏈接儲存信息作準備,這個資源在等待重試期間一直保留。更爲重要的是,服務器資源有限,能夠維護的SYN_RECV狀態超過極限後就再也不接受新的SYN報文,也就是拒絕新的TCP鏈接創建。

SYN Flood正是利用了上文中TCP協議的設定,達到攻擊的目的。攻擊者假裝大量的IP地址給服務器發送SYN報文,因爲僞造的IP地址幾乎不可能存在,也就幾乎沒有設備會給服務器返回任何應答了。所以,服務器將會維持一個龐大的等待列表,不停地重試發送SYN+ACK報文,同時佔用着大量的資源沒法釋放。更爲關鍵的是,被攻擊服務器的SYN_RECV隊列被惡意的數據包占滿,再也不接受新的SYN請求,合法用戶沒法完成三次握手創建起TCP鏈接。也就是說,這個服務器被SYN Flood拒絕服務了。

解決:修改內核參數:啓用SYN Cookie;設置SYN最大隊列長度;設置SYN+ACK最大重試次數;

啓用SYN CookieSYN Cookie的做用是緩解服務器資源壓力。啓用以前,服務器在接到SYN數據包後,當即分配存儲空間,並隨機化一個數字做爲SYN號發送SYN+ACK數據包。而後保存鏈接的狀態信息等待客戶端確認。啓用SYN Cookie以後,服務器再也不分配存儲空間,並且經過基於時間種子的隨機數算法設置一個SYN號,替代徹底隨機的SYN號。發送完SYN+ACK確認報文以後,清空資源不保存任何狀態信息。直到服務器接到客戶端的最終ACK包,經過Cookie檢驗算法鑑定是否與發出去的SYN+ACK報文序列號匹配,匹配則經過完成握手,失敗則丟棄。

設置SYN最大隊列長度是使用服務器的內存資源,換取更大的等待隊列長度,讓攻擊數據包不至於佔滿全部鏈接而致使正經常使用戶沒法完成握手。

設置SYN+ACK最大重試次數是下降服務器SYN+ACK報文重試次數,儘快釋放等待資源。

這三種措施與攻擊的三種危害一一對應,完徹底全地對症下藥。但這些措施也是雙刃劍,可能消耗服務器更多的內存資源,甚至影響正經常使用戶創建TCP鏈接,須要評估服務器硬件資源和攻擊大小謹慎設置。

 

11.blocking(默認)和nonblock模式下read/write行爲的區別:

socket fd設置爲nonblock(非阻塞)是在服務器編程中常見的作法,採用blocking IO併爲每個client建立一個線程的模式開銷巨大且可擴展性不佳(帶來大量的切換開銷),更爲通用的作法是採用線程池+Nonblock I/O+Multiplexingselect/poll,以及Linux上特有的epoll)。

 結論:

1. read老是在接收緩衝區有數據時當即返回,而不是等到給定的read buffer填滿時返回。只有當receive buffer爲空時,blocking模式纔會等待,而nonblock模式下會當即返回-1errno = EAGAINEWOULDBLOCK

2. blockingwrite只有在緩衝區足以放下整個buffer時才返回(與blocking read並不相同)nonblock write則是返回可以放下的字節數,以後調用則返回-1errno = EAGAINEWOULDBLOCK

3.  對於blockingwrite有個特例:當write正阻塞等待時對面關閉了socket,則write則會當即將剩餘緩衝區填滿並返回所寫的字節數,再次調用則write失敗。這是read/write對鏈接異常的一種反饋行爲。

read/write對鏈接異常的反饋行爲:

對應用程序來講,與另外一進程的TCP通訊實際上是徹底異步的過程:1. 我並不知道對面何時、可否收到個人數據也不知道何時可以收到對面的數據。2. 我不知道何時通訊結束(主動退出或是異常退出、機器故障、網絡故障等等)

對於1採用write() -> read() -> write() -> read() ->...的序列,經過blocking read或者nonblock read+輪詢的方式,應用程序能夠保證正確的處理流程。

對於2kernel將這些事件的通知經過read/write的結果返回給應用層。

 

12.socket網絡編程有哪些系統調用?

TCP客戶:socket() ->connect()->write()->read()->close()

TCP服務器:socket()->bind()->listen()->accept()->fork()->read()/write()->close()

 

socket():

int socket(AF_INEF,SOCK_STREAM,0);//建立一個IPv4/ IPv6TCP套接字。

 

connect()客戶用connect()創建與TCP服務器的鏈接。

int  connect(int sockfd,const struct sockaddr *seraddr,socklen_t  addrlen)

在建立套接字,用對端服務器的IP地址和端口號(經過main命令行參數指定)填充struct sockaddr以後。客戶用connect()創建與TCP服務器的鏈接。

TCP鏈接的過程是由內核完成,connect()的做用僅僅是通知 Linux 內核,讓 Linux 內核自動完成 TCP 三次握手鍊接. 鏈接的結果返回給這個函數的返回值(成功鏈接爲0 失敗爲-1)客戶端的 connect() 函數默認會一直阻塞(在鏈接期間),直到三次握手成功或超時失敗才返回。

 

Connect()失敗緣由:1. TCP客戶在屢次鏈接請求分節後一直接受不到服務器對SYN分節的響應,則返回ETIMEOUT錯誤。

2.若對客戶的SYN的響應是RST(表示復位),則代表該服務器主機在咱們指定的端口上沒有進程在等待與之鏈接(服務器也許尚未運行)。這是一種硬錯誤

3.若客戶的SYN在中間的某個路由器上引起一個路由不可達ICMP錯誤,客戶主機內保存該消息,並重發SYN分節。

 

Connect()失敗後套接字不可再用,必須關閉,不能再對這樣的套接字調用Connect()從新鏈接請求。

 

bind()

int  bind(int sockfd,const struct sockaddr *seraddr,socklen_t  addrlen)

   bind()把填入到套接字地址結構中的本地協議地址(好比IP地址+TCP端口)賦予一個套接字.

   若是一個TCP客戶或服務器不曾用bind()捆綁一個端口,當調用Connect()Listen()時內核就爲相應的套接字選擇一個臨時端口。客戶端一般都是由內核就爲相應的套接字選擇一個臨時端口。服務器端一般須要用bind()捆綁一個衆所周知端口。

   進程能夠把一個特定的IP地址綁定到它的套接字上,可是這個IP地址必須屬於其所在主機的網絡接口之一。對於TCP客戶,就爲該套接字上發送的數據報指派了源IP地址。對於TCP服務器,這就限制了該套接字只接受那些目的地址爲指定IP地址的客戶鏈接。TCP客戶一般不把IP地址捆綁到它的套接字上。當鏈接套接字時內核將根據所用外出網絡接口來選擇源IP地址,所用外出接口取決於到達服務器所需的路徑。若是TCP服務器沒有吧IP地址捆綁到它的套接字上,內核就把客戶發送的SYN的目的IP地址做爲服務器的源IP地址。

 

Listen():

int Listen(int sockfd,int backlog) 

listen函數僅由服務器調用,它作兩件事情:1.socket函數建立一個套接字時,它被假設成一個主動套接字,是一個將調用connect發起鏈接的客戶套接字。Listen函數把一個未鏈接的套接字轉換成一個被動等待鏈接的監聽套接字,指示內核應接受指向該套接字的鏈接請求。調用listen致使套接字從CLOSED狀態轉化爲LISTEN轉態。

2.本函數第二個參數backlog規定了內核應該爲相應套接字隊列的最大鏈接數目。

內核爲一個監聽套接字維護兩個隊列;

未完成鏈接隊列:客戶發送SYN請求分節,服務器接受後這些套接字處於SYN_RCVD狀態,等待三次握手的完成。Listen函數不會阻塞。

已完成鏈接隊列:每一個已完成鏈接的客戶對應其中的一項。套接字處於ESTABLISHED狀態。兩隊列之和不超過backlog

在三次握手創建鏈接的過程當中,服務器收到客戶端的鏈接請求分節SYN就在未完成鏈接隊列中建立一項,當鏈接完成後,未完成鏈接的對應條目轉移到已鏈接隊列中。accept可以返回。

Ddos攻擊原理和listen(): .listen()有一個隊列,處理鏈接請求。在收到匿名IP發過來的SYN以後,會在listen隊列中存放一個記錄,可是隊列容量是有限的,當這樣的惡意請求過多的時候,listen隊列裏就塞滿了這些無效的鏈接請求,而後裝不下更多的鏈接記錄了,因此就拒絕其餘請求了。

 

accept ()

int accept (int sockfd, struct sockaddr *cliaddr,socklen_t * addrlen);

當進程調用accept時,已完成鏈接隊列中的對頭項將返回給進程,或者若是該項爲空,那麼進程將被投入睡眠,直到TCP在已鏈接隊列中放入一項才喚醒它。

調用accept內核將已鏈接的對端進程(客戶)的協議地址裝填進地址結構中,利用值結果返回參數返回客戶端的協議地址。

若是acccept成功,那麼其返回值是由內核自動生成的一個全新套裝字描述符,套接字綁定到裝填了對端協議地址的套接字地址結構上。在accept中,它的第一個參數是監聽套接字描述符,它的返回值爲已鏈接套接字。

 監聽套接字與已鏈接套接字:一個服務器一般只建立一個監聽套接字,它在該服務器的生命週期內一直存在。內核爲每一個由服務器進程接受的客戶鏈接建立一個已鏈接套接字。當服務器完成對某個給定的服務時,相應的已鏈接套接字就被關閉。

 

 

fork ()exec()

pid_t fork (void);

fork()是進程建立的惟一方式。TCP中在父進程調用accept()得到一個已鏈接套接字,而後調用fork()建立一個子進程,用來處理每一個鏈接。父進程用來繼續監聽其餘的客戶鏈接。

父進程中調用fork以前打開的全部的描述符在fork返回以後,複製到子進程中由子進程共享。子進程關閉複製過來的監聽套接字(此時父進程中的監聽套接字並無關閉),接着讀寫這個已鏈接套接字。父進程關閉已複製給子進程的已鏈接套接字。子進程中調用相應的函數處理客戶的鏈接請求。

fork()的兩個典型用法:1.一個進程建立自身的副本,這樣每一個副本均可以在另外一個副本執行其餘任務的同時處理各自的操做。這是網絡服務器的典型用法。2.一個進程想要執行另外一個程序。既然建立新進程的惟一辦法是調用fork(),該進程因而首先調用fork()建立一個自身的副本,而後其中一個副本(一般是子進程)調用exec

把自身替換成新的程序。這是諸如shell之類程序的典型用法。

exec():

存放在硬盤上的可執行文件可以被Unix執行的惟一方法是:由一個現有進程調用6exec中的某一個。exec把當前進程映象替換成新的程序文件,並且該新程序一般是從main函數開始執行。進程ID並不改變。

 

close()shutdown()

close:  close一個套接字的默認行爲是把該套接字標記成已關閉,而後當即返回到調用進程。該套接字描述符不能再由調用進程使用,也就是說它不能再做爲read()write()的第一個參數。而後TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢後發生的是正常的TCP鏈接終止序列。

描述符引用計數:併發服務器中父進程關閉已鏈接套接字只是致使相應描述符的引用計數減1,若是仍然引用計數大於0,這個close調用並不會引起TCP終止序列。若是確實想在某個鏈接上發送一個FIN,而無論引用計數,能夠改用shutdown函數代替close函數。

父進程對每一個由accept返回的已鏈接套接字必須調用close,不然父進程最終將耗盡可用描述符。更重要的是,沒有一個客戶鏈接會被終止,由於當子進程關閉已鏈接套接字,它的引用計數將由2減到1,且保持爲1,由於父進程永不關閉任何已鏈接套接字了,這將妨礙TCP終止序列的發生,致使鏈接一直打開着。

 

Shutdown: 

調用close終止TCP鏈接有兩個限制:1Close只是把套接字的引用計數減1,若是須要馬上關閉套接字,無論引用計數,能夠改用shutdown函數代替close函數。

2.close一個套接字後,該套接字描述符不能再由調用進程使用,不能再做爲read()write()的第一個參數。即close同時終止讀和寫兩個方向的數據傳遞。既然TCP鏈接是全雙工的,那常常須要在不須要發送數據關閉發送這一端的時候仍然能夠接收對方的數據。使用shutdown函數能夠達到。

 

int shutdown(int sockfd, int howto)

shutdown函數的行爲依賴於howto的值;

SHUT_WR 關閉鏈接的寫這一半。對於TCP套接字這稱爲半關閉狀態。當前留在套接字發送緩衝區中的數據將被髮送,後跟TCP的正常終止序列。無論套接字的引用計數是否爲0,這樣的半關閉照樣執行。進程不能再對這樣的套接字調用任何函數。

SHUT_RD關閉鏈接的讀這一半。套接字中再也不有數據能夠接收,並且套接字接收緩衝區現有的數據所有被丟棄。套接字接收的來自對端的任何數據都被確認,而後丟棄。進程不能載對這樣的套接字調用任何函數。

 

getsockname()getpeername()

返回與某個套接字關聯的本地協議地址或者外地協議地址。

適用場景:

1.在一個沒有調用bindTCP客戶上,connect成功返回後,getsockname()用於返回由內核賦予該鏈接的本地IP地址和本地端口號。

2.在以端口號0調用bind(告知內核去選擇本地端口號)getsockname()用於返回由內核賦予的本地端口號。

3.getsockname()用於獲取某個鏈接的地址族。

4.在一個以通配IP地址調用bindTCP服務器上,與某個客戶的鏈接一旦創建,(accept 成功返回)getsockname()能夠用於返回由內核賦予本鏈接的本地IP地址。

在這樣的調用中,套接字描述符必須是已鏈接套接字描述符而不能是監聽套接字描述符。

5. 當一個服務器程序是由調用過accept的某個進程經過exec執行程序時,它可以獲取客戶身份的惟一途徑即是調用getpeername()Inned超級服務器forkexec某個服務器程序時就是如此情形。 

    Inned 調用accept返回已鏈接套接字描述符和客戶的IP地址端口號。    Inned 隨後調用fork,建立一個子進程,子進程起源於父進程的內存映像的一個副本,父進程中的那個套接字地址結構和已鏈接描述符都複製到子進程中,而後當子進程exec執行某個服務器程序時,子進程的內存映像就被替換成服務器程序文件,包含以前的對端地址的那個套接字地址結構就此丟失。此時調用getpeername()能夠得到客戶的IP地址和端口號。

   

read()write()

   

write成功返回只是buf中的數據被複制到了kernel中的TCP發送緩衝區。至於數據何時被髮往網絡,何時被對方主機接收,何時被對方進程讀取,系統調用層面不會給予任何保證和通知。

write在什麼狀況下會阻塞?

對於每一個socket,擁有本身的send bufferreceive buffer,kernel的該socket的發送緩衝區已滿時,write會阻塞。

已經發送到網絡的數據依然須要暫存在send buffer中,只有收到對方的ack後,kernel才從buffer中清除這一部分數據,爲後續發送數據騰出空間。接收端將收到的數據暫存在receive buffer中,自動進行確認。但若是socket所在的進程不及時將數據從receive buffer中取出,最終致使receive buffer填滿,因爲TCP的滑動窗口和擁塞控制,接收端會阻止發送端向其發送數據。

通常來講,因爲接收端進程從socket讀數據的速度跟不上發送端進程向socket寫數據的速度,最終致使發送端write調用阻塞。

read調用從socketreceive buffer中拷貝數據到應用程序的buffer中。read調用阻塞,一般是發送端的數據沒有到達。

 

forkvfork以及clone的區別,exec的做用
forkvforkcloneLinux用來建立新的進程(線程)而設計的。exec()系列函數則是用指定的程序替換當前進程的全部內容。exec()系列函數常常在前三個函數使用以後調用,來建立一個全新的程序運行環境Linuxinit進程啓動其餘進程的過程通常都是經過exec()系列函數實現的。forkvforkclone分別調用了sys_forksys_vforksys_clone,最終都調用了do_fork函數,差異在於參數的傳遞和一些基本的準備工做不一樣。可見這三者最終達到的最本質的目的都是建立一個新的進程。Linux內核中沒有獨立的線程結構,Linux的線程就是輕量級進程,線程的基本控制結構和Linux的進程是同樣的(都是經過struct task_struct管理)。 

 fork是最簡單的調用,不須要任何參數,僅僅是在建立一個子進程併爲其建立一個獨立於父進程的空間。fork使用COW(寫時拷貝)機制,而且COW了父進程的棧空間。

vfork是一個過期的應用,vfork也是建立一個子進程,可是子進程共享父進程的空間。在vfork建立子進程以後,父進程阻塞,直到子進程執行了exec()或者exit()vfork最初是由於fork沒有實現COW機制,而不少狀況下fork以後會緊接着exec,而exec的執行至關於以前fork複製的空間所有變成了無用功,因此設計了vfork。而如今fork使用了COW機制,惟一的代價僅僅是複製父進程頁表的代價,因此vfork不該該出如今新的代碼之中。

cloneLinux爲建立線程設計的(雖然也能夠用clone建立進程)。因此能夠說clonefork的升級版本,不只能夠建立進程或者線程,還能夠指定建立新的命名空間(namespace)、有選擇的繼承父進程的內存、甚至能夠將建立出來的進程變成父進程的兄弟進程等等。clonefork的調用方式也很不相同,clone調用須要傳入一個函數,該函數在子進程中執行。此外,clonefork最大不一樣在於clone再也不復制父進程的棧空間,而是本身建立一個新的。

 

循環fork,問有多少個輸出行的題

 

總打印出來了6A,由於咱們是先fork後打印。因此,在第1次循環,第1次打印A的時候,已經有一個增生的進程了,因此第1次打印共打印出2A。在第2次循環的時候,最開始的進程與增生的進程各自又增生了一個進程,因此當前共有4個進程,打印出來了4A。共有6A

 

網絡通訊出故障如何排查,講講fiddler,tcpdump

使用tcpdump抓包,並用WiresharkFiddler工具分析。排查問題的時候,進程要遇到抓包,若是是在windows環境,可使用wireshark直接抓包,若是是在linux環境下,可使用tcpdump命令進行抓包,而後取下來用wireshark或者Fiddler進行分析。tcpdump也能夠用來抓udp的包。

https://blog.csdn.net/u014209205/article/details/81104908

 

緊急指針

TCP僅支持一個字節的帶外(OOB)緊急數據。

TCP報文首部的緊急指針(urgent pointer)指向該帶外緊急數據偏移的下一字節。

如發送方欲發送多字節的帶外緊急數據,其結果是:緊急指針指向最後一個數據偏移的下一字節,而以前全部數據被看成普通數據處理。

即:發送端只把提供數據的最後一個字節看成帶外緊急數據;接收端只會接收到一個字節的帶外緊急數據

 

如何監聽tcp丟包問題。(細節知識點)。分析丟包緣由

tcpdump抓包分析能夠發現tcp丟包問題,如:

 

 

網絡丟包緣由分析:

1. 網絡鏈路阻塞引發丟包。數據在網絡傳輸的過程當中會通過不少設備和網路連接。只要其中一個網路連接在數據傳輸過來以前已經滿負載了,那麼數據將會在這裏阻塞一段時間,而後在通過網絡鏈路傳送(這也就是所謂的排隊)。 若是說網絡設備很是落後於這個網路連接的話,那麼網路連接沒有足夠給新數據來等待的空間,只能將信息丟掉,發生丟包。

解決:增長阻塞連接的帶寬;

2.網絡設備(路由器,交換機)性能不夠。當大量網絡數據包傳送到達網絡設備,可是此時網絡設備的CPU,或者內存滿載了,並無能力來處理其餘的數據包。這致使設備不能處理的數據包都被丟棄。

解決:必須更換吞吐量更大,性能更好的網絡硬件,或者構建集羣來提升吞吐量。

 

tcp粘包問題,怎麼處理?udp會粘包嗎?爲何?

TCP粘包問題:TCP粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。粘包可能由發送方形成,也可能由接收方形成。TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一包數據,形成多個數據包的粘連。若是接收進程不及時接收數據,已收到的數據就放在系統接收緩衝區,用戶進程讀取數據時就可能同時讀到多個數據包。由於系統傳輸的數據是帶結構的數據,須要作分包處理。

TCP粘包處理-RingBuf方法https://blog.csdn.net/hik_zxw/article/details/48398935 

UDP不存在粘包問題,是因爲UDP發送的時候,沒有通過Negal算法優化,不會將多個小包合併一次發送出去。另外,在UDP協議的接收端,採用了鏈式結構來記錄每個到達的UDP包,這樣接收端應用程序一次recv只能從socket接收緩衝區中讀出一個數據包。也就是說,發送端send了幾回,接收端必須recv幾回(不管recv時指定了多大的緩衝區)。

 

在三次握手過程當中第二條包丟了會怎麼樣?第三條丟了會怎樣?有什麼現象?

第二個ACK包丟了:客戶端重發鏈接請求;

第三個ACK包丟了:客戶端認爲鏈接創建,寫數據時,會觸發RST

 

服務器listen後不accept,客戶端connect會返回嗎。【能夠,內核負責三次握手,維護一個已完成連接的隊列,聊了一下已完成鏈接和未完成連接隊列的問題】,

相關文章
相關標籤/搜索