(1)TCP和UDP有什么區別?
TCP是傳輸控制協議,提供的是面向連接的,可靠地字節流服務。使用三次握手建立連接,四次揮手釋放連接。UDP是用戶數據報協議,傳輸的是UDP數據報,是無連接的,而且沒有超時重發機制。 TCP保證數據按序到達,提供流量控制和擁塞控制,在網絡擁堵的時候會減慢發送字節數,而UDP不管網絡是否擁堵。 TCP是連接的,所以服務是一對一服務,而UDP可以1對1,也可以1對多(多播),也可以多對多。
TCP可靠傳輸的保證:
停止等待協議:每發送完一個分組就停止發送,等待對方確認。收到確認后再發送下一個分組。無差錯情況下收到確認再發送。如果發送方超過一段時間仍沒有收到確認就會重新發送(超時重傳)。因此發送完必須保留分組副本,分組和確認分組都必須編號,超時計時器應該比平均往返時間長一點。
連續ARQ協議:發送方每收到一個按序到達的確認,就把發送窗口向前移動一個分組的位置。
滑動窗口機制:窗口是緩存的一部分,用來暫時存放字節流。發送方和接收方各有一個窗口,接收方通過 TCP 報文段中的窗口字段告訴發送方自己的窗口大小,發送方根據這個值和其它信息設置自己的窗口大小。如果發送窗口左部的字節已經發送並且收到了確認,那么就將發送窗口向右滑動一定距離,直到左部第一個字節不是已發送並且已確認的狀態;接收窗口的滑動類似,接收窗口左部字節已經發送確認並交付主機,就向右滑動接收窗口。接收窗口只會對窗口內最后一個按序到達的字節進行確認,例如接收窗口已經收到的字節為 {31, 34, 35},其中 {31} 按序到達,而 {34, 35} 就不是,因此只對字節 31 進行確認。發送方得到一個字節的確認之后,就知道這個字節之前的所有字節都已經被接收。
UDP如何實現可靠傳輸:
在應用層模仿傳輸層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包,必須確認客戶的syn(ack=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_WAIT和CLOSE_WAIT兩種狀態如果一直被保持,那么意味着對應數目的通道就一直被占着,一旦達到句柄數上限,新的請求就無法被處理。
服務器保持了大量TIME_WAIT狀態的套接字:TIME_WAIT是主動關閉連接的一方保持的狀態。 對於基於TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可 想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,維護這些狀態給Server帶來負擔。
原因:大量出現這個狀態往往說明系統的並發比較高,大量的連接建立和釋放,
處理:優化系統內核參數讓服務器能夠快速回收和重用那些TIME_WAIT的資源。
服務器保持了大量CLOSE_WAIT狀態的套接字:
原因:Server 程序處於CLOSE_WAIT狀態,而沒有跳轉到LAST_ACK狀態,說明Server還沒有發FIN給Client,那么可能是在關閉連接之前還有許多數據要發送或者其他事要做,導致沒有發這個FIN package。通常一個CLOSE_WAIT會維持至少2個小時的時間。如果有個惡意程序,給服務器造成一堆的CLOSE_WAIT,通常是等不到釋放那一刻,系統就已經崩潰了。
解決:修改TCP/IP的參數,來縮短2個小時這個時間:修改tcp_keepalive_*系列參數有助於解決這個問題。
5.IO模型:
阻塞IO,非阻塞IO,IO復用,信號驅動式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 call(read),那么它(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. select,poll,epoll的實現和區別
select:用戶進程調用select函數后會阻塞,直到有描述符就緒(有數據可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可)函數返回。當select函數返回后,可以通過遍歷fdset,來找到就緒的描述符。
優點:select目前幾乎在所有的平台上都支持,有良好的跨平台支持。
缺點:1. 單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但 是這樣也會造成效率的降低。2. 輪尋排查當文件描述符個數很多時,效率很低。poll通過一個可變長度的數組解決了select文件描述符受限的問題,但輪尋排查的問題未解決。epoll采用只返回狀態發生變化的文件描述符,便解決了輪尋的瓶頸。
Poll: poll通過一個可變長度的數組解決了select文件描述符受限的問題,但輪尋排查的問題未解決。
epoll: 沒有描述符限制。采用只返回狀態發生變化的文件描述符,便解決了輪尋的瓶頸。
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。
LT模式是默認模式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的。
ET模式是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)。ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
Epoll的優勢:1. 監視的描述符數量不受限制。2. IO的效率不會隨着監視fd的數量的增長而下降。epoll不同於select和poll輪詢的方式,而是通過為每個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 轉發表中安裝默認網關。
2.DNS 解析域名
瀏覽器中輸入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層通過路由選路,一跳一跳發送到目的地址。當然在一個網段內的尋址是通過以太網協議實現(也可以是其他物理層協議,比如PPP,SLIP),以太網協議需要知道目的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 Cookie:SYN 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+Multiplexing(select/poll,以及Linux上特有的epoll)。
結論:
1. read總是在接收緩沖區有數據時立即返回,而不是等到給定的read buffer填滿時返回。只有當receive buffer為空時,blocking模式才會等待,而nonblock模式下會立即返回-1(errno = EAGAIN或EWOULDBLOCK)
2. blocking的write只有在緩沖區足以放下整個buffer時才返回(與blocking read並不相同)nonblock write則是返回能夠放下的字節數,之后調用則返回-1(errno = EAGAIN或EWOULDBLOCK)
3. 對於blocking的write有個特例:當write正阻塞等待時對面關閉了socket,則write則會立即將剩余緩沖區填滿並返回所寫的字節數,再次調用則write失敗。這是read/write對連接異常的一種反饋行為。
read/write對連接異常的反饋行為:
對應用程序來說,與另一進程的TCP通信其實是完全異步的過程:1. 我並不知道對面什么時候、能否收到我的數據也不知道什么時候能夠收到對面的數據。2. 我不知道什么時候通信結束(主動退出或是異常退出、機器故障、網絡故障等等)
對於1采用write() -> read() -> write() -> read() ->...的序列,通過blocking read或者nonblock read+輪詢的方式,應用程序可以保證正確的處理流程。
對於2,kernel將這些事件的“通知”通過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/ IPv6的TCP套接字。
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執行的唯一方法是:由一個現有進程調用6個exec中的某一個。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連接有兩個限制:1。Close只是把套接字的引用計數減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.在一個沒有調用bind的TCP客戶上,connect成功返回后,getsockname()用於返回由內核賦予該連接的本地IP地址和本地端口號。
2.在以端口號0調用bind后(告知內核去選擇本地端口號),getsockname()用於返回由內核賦予的本地端口號。
3.getsockname()用於獲取某個連接的地址族。
4.在一個以通配IP地址調用bind的TCP服務器上,與某個客戶的連接一旦建立,(accept 成功返回),getsockname()可以用於返回由內核賦予本連接的本地IP地址。
在這樣的調用中,套接字描述符必須是已連接套接字描述符而不能是監聽套接字描述符。
5. 當一個服務器程序是由調用過accept的某個進程通過exec執行程序時,它能夠獲取客戶身份的唯一途徑便是調用getpeername()。Inned超級服務器fork並exec某個服務器程序時就是如此情形。
Inned 調用accept返回已連接套接字描述符和客戶的IP地址端口號。 Inned 隨后調用fork,創建一個子進程,子進程起源於父進程的內存映像的一個副本,父進程中的那個套接字地址結構和已連接描述符都復制到子進程中,然后當子進程exec執行某個服務器程序時,子進程的內存映像就被替換成服務器程序文件,包含之前的對端地址的那個套接字地址結構就此丟失。此時調用getpeername()可以獲得客戶的IP地址和端口號。
read()和write()
write成功返回只是buf中的數據被復制到了kernel中的TCP發送緩沖區。至於數據什么時候被發往網絡,什么時候被對方主機接收,什么時候被對方進程讀取,系統調用層面不會給予任何保證和通知。
write在什么情況下會阻塞?
對於每個socket,擁有自己的send buffer和receive buffer,當kernel的該socket的發送緩沖區已滿時,write會阻塞。
已經發送到網絡的數據依然需要暫存在send buffer中,只有收到對方的ack后,kernel才從buffer中清除這一部分數據,為后續發送數據騰出空間。接收端將收到的數據暫存在receive buffer中,自動進行確認。但如果socket所在的進程不及時將數據從receive buffer中取出,最終導致receive buffer填滿,由於TCP的滑動窗口和擁塞控制,接收端會阻止發送端向其發送數據。
一般來說,由於接收端進程從socket讀數據的速度跟不上發送端進程向socket寫數據的速度,最終導致發送端write調用阻塞。
read調用從socket的receive buffer中拷貝數據到應用程序的buffer中。read調用阻塞,通常是發送端的數據沒有到達。
fork、vfork以及clone的區別,exec的作用
fork、vfork、clone是Linux用來創建新的進程(線程)而設計的。exec()系列函數則是用指定的程序替換當前進程的所有內容。exec()系列函數經常在前三個函數使用之后調用,來創建一個全新的程序運行環境。Linux用init進程啟動其他進程的過程一般都是通過exec()系列函數實現的。fork、vfork、clone分別調用了sys_fork、sys_vfork、sys_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不應該出現在新的代碼之中。
clone是Linux為創建線程設計的(雖然也可以用clone創建進程)。所以可以說clone是fork的升級版本,不僅可以創建進程或者線程,還可以指定創建新的命名空間(namespace)、有選擇的繼承父進程的內存、甚至可以將創建出來的進程變成父進程的兄弟進程等等。clone和fork的調用方式也很不相同,clone調用需要傳入一個函數,該函數在子進程中執行。此外,clone和fork最大不同在於clone不再復制父進程的棧空間,而是自己創建一個新的。
循環fork,問有多少個輸出行的題
總打印出來了6個A,因為我們是先fork后打印。所以,在第1次循環,第1次打印A的時候,已經有一個增生的進程了,所以第1次打印共打印出2個A。在第2次循環的時候,最開始的進程與增生的進程各自又增生了一個進程,所以當前共有4個進程,打印出來了4個A。共有6個A。
網絡通信出故障如何排查,講講fiddler,tcpdump
使用tcpdump抓包,並用Wireshark和Fiddler工具分析。排查問題的時候,進程要遇到抓包,如果是在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會返回嗎。【可以,內核負責三次握手,維護一個已完成鏈接的隊列,聊了一下已完成連接和未完成鏈接隊列的問題】,