《Unix網絡編程》讀書筆記

UDP和TCP

UDP(User Datagram Protocol,用戶數據報協議)是一個無鏈接協議,不保證UDP數據報會到達其最終目的地,不保證各數據報的前後順序跨網絡後保持不變,也不保證每一個數據報只到達一次。算法

UDP提供無鏈接的服務,由於UDP客戶與服務器之間沒必要存在任何長期的關係。一個UDP客戶可使用一個套接字發送數據報給多個服務器,一個UDP服務器也能夠用同一個套接字從不一樣的客戶接收數據報。編程

每一個UDP數據報都一個長度,數據報的長度會隨數據一同傳遞給接收端進程;而TCP是一個字節流協議,沒有任何記錄邊界。安全

TCP(Transmission Control Protocl,傳輸控制協議)是一個面向鏈接的協議,爲用戶進程提供可靠的全雙工字節流。服務器

TCP提供客戶與服務器之間的鏈接。TCP客戶先與某個給定的服務器創建鏈接,再跨該鏈接與那個服務器交換數據,而後終止這個鏈接。網絡

TCP提供了可靠性。當TCP向另外一端發送數據時,它要求對端返回一個確認。若是沒有收到確認,TCP就自動重傳數據並等待更長時間,在數次重傳失敗後才放棄。TCP含有用於動態估算客戶和服務器之間的往返時間(round-trip time,RTT)的算法,以便知道等待一個確認須要多少時間。TCP經過給其中每一個字節關聯一個序列號對所發送的數據進行排序,接收端根據收到的分節的序列號從新排序,而且根據序列號判斷並丟棄重複數據。併發

TCP提供流量控制。TCP老是告知對端在任什麼時候刻它一次可以從對端接收多少字節的數據,這稱爲通告窗口,該窗口指出接收緩衝區當前可用的空間量,從而確保發送端發送的數據不會使接收緩衝區溢出。異步

TCP鏈接是全雙工的,這意味着在一個給定的鏈接上應用能夠在任什麼時候刻在進出兩個方向上既發送數據又接收數據。socket

TCP鏈接創建和終止

TCP創建一個鏈接須要3個分節,稱爲TCP的三路握手,而終止一個鏈接則須要4個分節。下圖展現了一個完整的TCP鏈接所發生的實際分組交換狀況,包括鏈接創建、數據傳送和連接終止3個階段,還展現了每一個端點所歷經的TCP狀態。函數

每個SYN選項能夠含有多個TCP選項,下面是經常使用的選項。大數據

  • MSS選項。發送SYN的TCP一端使用本選項通告對端它的最大分節大小,即MSS(Maximum Segment Size),也就是它在本鏈接的每一個TCP分節中願意接受的最大數據量。
  • 窗口規模選項。TCP能通告的最大窗口大小是65535,由於TCP首部中相應的字段佔16位。
  • 時間戳選項。它能夠防止失而復現的分組可能形成的數據損壞。

TCP涉及鏈接創建和鏈接終止的操做能夠用狀態轉換圖來講明。

TIME_WAIT狀態有兩個存在的理由:

  1. 可靠地實現TCP全雙工鏈接的終止。假設最終的ACK丟失了,服務器將從新發送它的最終那個FIN,所以客戶必須維護狀態信息,以容許它從新發送最終的那個ACK。
  2. 容許老的重複分節在網絡中消逝。假設在關閉一個鏈接一段時間後在相同的IP地址和端口之間創建了另外一個鏈接,即前一個鏈接的化身,TCP必須防止來自某個鏈接的老的重複分組在該鏈接已終止後再現,從而被誤解爲屬於其化身的分組。所以TCP將不給處於TIME_WAIT狀態的鏈接發起新的化身,TIME_WAIT狀態持續時間爲2MSL(Maximum Segment Lifetime,最長分節生命期),就足以讓某個方向上的分組和另外一方向上的應答最多存活MSL被丟棄。

一個TCP套接字對是一個定義該鏈接的兩個端點的四元組:本地IP地址、本地TCP端口號、外地IP地址、外地TCP端口號,套接字對惟一標識一個網絡上的每一個TCP鏈接。

緩衝區大小及限制

許多網絡有一個可由硬件規定的MTU(Maximum Transmission Unit,最大傳輸單元),如以太網的MTU是1500字節。在兩個主機之間的路徑最小MTU稱爲路徑MTU,兩個主機之間相反的兩個方向上路徑MTU能夠不一致,由於因特網中路由選擇每每是不對稱的。當一個IP數據報將從某個接口發出時,若是它的大小超過相應鏈路的MTU,IP將執行分片,這些分片在到達最終目的地以前一般不會被重組。

IPv4和IPv6都定義了最小重組緩衝區大小,它是IPv4或IPv6的任何實現都必須保證支持的最小數據報大小,其值對於IPv4爲576字節。

TCP的MSS用於向對端TCP通告對端在每一個分節中能發送的最大TCP數據量。MSS的目的是告訴對端其重組緩衝區大小的實際值,從而視圖避免分片。MSS一般設置成MTU減去IP和TCP首部固定長度(都爲20字節),如在以太網中使用IPv4的MSS爲1460(1500-20-20)。

每個TCP套接字有一個發送緩衝區,當某個應用進程調用write時,內核從該應用進程的緩衝區複製全部數據到所寫套接字的發送緩衝區。對於阻塞套接字,若是發送緩衝區容不下該應用進程的全部數據,進程將被投入睡眠,直到應用進程緩衝區中的全部數據都複製到套接字發送緩衝區。所以,從寫一個TCP套接字的write調用成功返回僅僅表示咱們能夠從新使用原來的應用進程緩衝區,並不代表對端的TCP或應用進程已接收到數據。TCP提取套接字發送緩衝區中的數據並把它發送給對端TCP,在對端ACK到達後,本端TCP才能從發送緩衝區中丟棄已確認的數據。TCP數據經由IP傳遞給數據鏈路,每一個數據鏈路都有一個輸出隊列,若是該隊列已滿,新的分組將被丟棄,並沿協議棧向上返回一個錯誤到TCP,TCP將注意到這個錯誤,並在之後某個時刻重傳相應的分節,這個過程對應用進程透明。

UDP是不可靠的,它沒必要保存應用進程數據的副本,所以UDP套接字沒有發送緩衝區,但有發送緩衝區大小,表示可寫到改套接字的UDP數據報大小的上限,若是一個應用進程寫一個大於套接字發送緩衝卻大小的數據報,內核將返回給進程一個EMSGSIZE錯誤。因爲沒有相似TCP的MSS,UDP應用進程在發送大數據報比TCP更可能被分片。從寫一個UDP套接字的write調用成功返回表示所寫的數據報或其全部片斷已被繳入數據鏈路層輸出隊列,若是該隊列沒有足夠的空間存放改數據報或它的某個片斷,內核一般會返回一個ENOBUFS給它的應用進程。

套接字地址結構

IPv4套接字結構爲sockaddr_in,定義在頭文件<netinet/in.h>中。

套接字函數爲了能支持任何協議族的套接字地址結構,使用了一個通用套接字地址結構的指針做爲參數,頭文件<sys/socket.h>中定義了這個通用的套接字地址結構sockaddr。

下面是網絡地址在點分十進制數串和網絡字節序二進制值之間轉換的函數。

inet_addr出錯時返回INADDR_NONE(一般是一個32爲均爲1的值),這意味着255.255.255.255不能由該函數處理。現在inet_addr已被廢棄,新的代碼應該改用inet_aton函數。

inet_ntoa返回值所指向的字符串駐留在靜態內存中,這意味着該函數是不可重入的。

TCP套接字編程

基本TCP客戶/服務器程序的套接字函數使用以下:

socket函數

爲了執行網絡IO,一個進程必須作的第一件事就是調用socket函數。

其中family指明協議域:

type參數指明套接字類型:

connect函數

TCP客戶用connect函數來創建與TCP服務器的連接。

若是是TCP套接字,調用connect函數將觸發TCP的三路握手過程,並且僅在鏈接創建成功或出錯時才返回,出錯返回多是如下幾種狀況。

  1. 若TCP客戶沒有收到SYN分節的響應,通過必定的重試後返回ETIMEDOUT錯誤。
  2. 若對客戶的SYN的響應是RST,則代表該服務器主機在咱們指定的端口上沒有進程在等待與之鏈接,客戶收到RST返回ECONNREFUSED錯誤。
  3. 若客戶發出的SYN在中間的某個路由器上引起一個"destination unreachable"ICMP錯誤,通過必定的重試後返回EHOSTUNREACH或ENETUNREACH錯誤。

bind函數

bind函數把一個本地協議地址賦予一個套接字。

bind能夠指定一個也能夠端口號,或指定一個IP地址,也能夠二者都指定,或者都不指定。

若一個TCP客戶或服務器未調用bind綁定一個端口,當調用connect或listen時,內核就要爲相應的套接字選擇一個臨時端口。

TCP服務器綁定到某個IP,這就限定該套接字只接收目的地址爲該IP的鏈接。TCP客戶一般不綁定IP,鏈接套接字時,內核將根據外出網絡接口來選擇源IP。若TCP服務器未綁定IP,內核把客戶發送的SYN的目的地址做爲服務器的源IP。

從bind函數返回的一個常見錯誤是EADDRINUSE("Address aready in use",地址已使用)。

listen函數

listen函數把一個未鏈接的套接字轉換成一個被動套接字,指示內核應接受指向該套接字的鏈接請求。

backlog參數規定了內核應該爲相應套接字排隊的最大鏈接數。內核爲任何一個給定的監聽套接字維護隊列:

  1. 未完成鏈接隊列,每一個這樣的SYN分節對應其中一項:已由某個客戶發出併到達服務器,而服務器正在等待完成相應的TCP三路握手過程。
  2. 已完成連接隊列,每一個已完成TCP三路握手過程的客戶對應其中一項。

當一個客戶SYN到達時,若這些隊列是滿的,TCP就忽略該分節,也就是不發送RST。

accept函數

accept函數用於從已完成鏈接隊列對頭返回下一個已完成鏈接。

若是accpet成功,其返回已鏈接套接字的描述符,這是一個不一樣於監聽套接字的新套接字。咱們稱它的第一個參數爲監聽套接字描述符,稱它的返回值爲已鏈接套接字描述符。

若是咱們對返回客戶協議地址不感興趣,能夠把cliaddr和addrlen均置爲空指針。

close函數

close函數用來關閉套接字,並終止TCP鏈接。

close一個TCP套接字的默認行爲是把該套接字標記成已關閉,而後當即返回到調用進程。TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢後發生的是正常的TCP鏈接終止序列。

關閉已鏈接套接字只是致使相應描述符的引用計數減1,若是引用計數值仍大於0,close調用並不引起TCP四分組鏈接終止序列。若是確實想在某個TCP鏈接上發送一個FIN,能夠改用shutdown函數代替close。

recv和send函數(305)

 

getsockname和getpeernanme函數

這兩個函數或者返回與某個套接字關聯的本地協議地址(getsockname),或者返回與某個套接字關聯的外地協議地址(getpeername)。

recv和send函數

TCP異常狀況

服務器進程終止

服務器進程終止後,進程中全部打開的描述符都被關閉,這就致使向客戶發送一個FIN,而客戶TCP響應一個ACK。

若是客戶繼續發送數據給服務器,服務器收到來自客戶的數據時,因爲先前打開那個套接字的進程已經終止,因而響應一個RST。

然而客戶端進程看不到這個RST,因爲前面接收到的FIN,調用read會當即返回0。若是進程忽略該錯誤,繼續發送數據到服務器,將返回EPIPE錯誤。

當一個進程向某個已收到RST的套接字執行寫操做時,內核向進程發送一個SIGPIPE信號,寫操做會分會EPIPE錯誤。

服務器主機崩潰

當服務器主機崩潰時,客戶TCP持續重傳數據分節,試圖從服務器上接收一個ACK。當客戶TCP最終放棄時,給客戶進程返回一個錯誤。假設服務器主機已崩潰,從而對客戶的數據分節沒有響應,那麼所返回的錯誤是ETIMEDOUT;若是某個中間路由器斷定服務器主機不可達,從而響應一個"destination unreachable"ICMP消息,那麼返回的錯誤是EHOSTUNREACH或ENETUNREACH。

服務器主機崩潰後重啓

當服務器主機崩潰後重啓時,它的TCP丟失了崩潰錢的全部鏈接信息,所以服務器TCP對於所收到的來自客戶的數據分節響應一個RST。

當客戶TCP收到該RST時,客戶的read調用返回ECONNRESET錯誤。

服務器主機關機

系統關機時,init進程一般先給全部進程發送SIGTERM信號,而後給全部仍在運行的進程發送SIGKILL信號。當服務器進程終止時,它的全部打開着的描述符都被關閉。

I/O模型

  • 阻塞式I/O。默認情形下全部套接字都是阻塞的。
  • 非阻塞式I/O。進程把一個套接字設置成非阻塞是在通知內核:當全部請求的I/O操做非得把本進程投入睡眠才能完成時,不要把本進程投入睡眠,而是返回一個錯誤。
  • I/O複用模型。I/O複用是調用select或poll,阻塞在這兩個系統調用中的某一個之上,而不是阻塞在真正的I/O系統調用上。
  • 信號驅動式I/O模型。讓內核在描述符就緒時發送SIGIO信號通知進程。
  • 異步I/O模型。函數的工做機制是:告知內核啓動某個操做,並讓內核在整個操做完成後通知咱們。

套接字選項

有如下方法來獲取和設置影響套接字的選項:

  • getsockopt和setsockopt函數
  • fcntl函數
  • ioctl函數

getsockopt和setsockopt函數

getsockopt和setsockopt僅用於套接字。

可獲取和設置的套接字選項以下:

SO_KEEPALIVE

給一個TCP套接字設置保持存活選項後,若是2小時內在該套接字的任一方向上都沒有數據交換,TCP就自動給對端發送一個保持存活探測分節。

SO_LINGER

本選項指定close函數對面向鏈接的協議如何操做。默認操做是close當即返回,可是若是有數據殘留在套接字發送緩衝區中,系統將試着把這些數據發送給對端。本選項在用戶進程和內核間傳遞以下結構,它在頭文件<sys/socket.h>中定義:

本選項有如下情形:

  1. 若是l_onoff爲0,那麼關閉本選項,TCP默認設置生效,即close當即返回。
  2. 若是l_onoff爲非0且l_linger爲0,那麼當close某個鏈接時TCP將終止該鏈接。就是說TCP將丟棄保留在套接字發送緩衝區中的任何數據,併發送一個RST給對端,而沒有一般的四分組鏈接終止序列,這樣避免了TCP的TIME_WAIT狀態。
  3. 若是l_onoff爲非0且l_linger爲非0,那麼當關閉套接字時內核將拖延一段時間。就是說若是在套接字發送緩衝區中仍殘留數據,那麼進程將被投入睡眠,直到數據都已發送且被對端確認或延滯時間到。

SO_RCVBUF和SO_SNDBUF

對於TCP來講,套接字接收緩衝區中可用空間的大小限定了TCP通告對端的窗口大小。對於UDP來講,當接收到的數據報裝不進套接字接收緩衝區時,改數據報就被丟棄。

因爲TCP的窗口規模選項是在創建鏈接時用SYN分節與對端互換獲得的。對於客戶端,SO_RCVBUF選項必須在調用connect以前設置;對於服務器,該選項必須在調用listen以前給監聽套接字設置。

SO_RCVTIMEO和SO_SNDTIMEO

這兩個選項容許咱們給套接字的接收和發送設置一個超時值。

SO_REUSEADDR

本選項能起到如下4個不一樣的做用:

  1. 容許啓動一個監聽服務器並綁定某個端口,即便之前創建的將該端口用做他們的本地端口的鏈接仍存在。
  2. 容許在同一端口上啓動同一服務器的多個實例,只要每一個實例綁定一個不一樣的本地IP地址便可。
  3. 容許單進程綁定同一端口到多個套接字上,只要每次綁定不一樣的本地IP地址便可。
  4. 容許徹底重複的綁定,即一樣的IP地址和端口能夠綁定到多個套接字上,本特性僅支持UDP套接字。

TCP_NODELAY

開啓本選項將禁止TCP的Nagle算法,默認狀況下該算法是啓動的。Nagle算法的目的是爲了減小廣域網上小分組的數目。該算法的思想是:若是某個給定的鏈接上有待確認的數據,那麼本來應該做爲用戶寫操做之響應的在該鏈接上當即發送相應小分組的行爲就不會發生。

fcntl函數

fcntl函數可執行各類描述符控制操做。

fcntl提供的與網絡編程相關的特性主要是設置非阻塞式I/O,經過使用F_SETFL命令設置O_NONBLOCK文件狀態標誌,能夠把一個套接字設置爲非阻塞型。典型代碼以下:

UDP套接字編程

UDP客戶/服務器程序程序所用的套接字函數以下:

recvfrom和sendto函數

flags老是置爲0。

寫一個長度爲0的數據報是可行的,這會造成一個只包含IP首部和UDP首部而沒有數據的IP數據報。recvfrom返回0是可接受的:它並不像TCP套接字上read返回0表示對端已關閉鏈接。

若是recvfrom的from參數是一個空指針,那麼相應的addrlen也必須是一個空指針,表示不關心數據發送者的協議地址。

客戶的臨時端口是在第一次調用sendto時一次性選定的,不能改變;然而客戶的IP地址卻能夠隨客戶發送的每一個UDP數據報而變更。

服務器進程未運行

若是服務器進程未運行,客戶數據報發出,服務器主機響應一個"port unreachable"ICMP消息,不過這個ICMP錯誤不返回給客戶進程。咱們稱這個ICMP錯誤爲異步錯誤,該錯誤由sendto引發,但sendto自己卻成功返回。一個基本規則是:對於一個UDP套接字,由它引起的異步錯誤卻並不返回給它,除非它已鏈接。

UDP的connect函數

咱們能夠給UDP套接字調用connect,內核只是檢查是否存在當即可知的錯誤(例如一個顯然不可達的目的地),記錄對端的IP地址和端口號,而後當即返回到調用進程。

UDP客戶進程或服務器進程只有在使用本身的UDP套接字與肯定的惟一對端進行通訊時,才能夠調用connect。

對於已鏈接UDP套接字,與默認的未鏈接UDP套接字相比,有如下不一樣:

  1. 不能給輸出操做指定目的IP地址和端口號,即不使用sendto而改用write或send。
  2. 沒必要使用recvfrom以獲悉數據報的發送者,而改用read、recv或recvmsg。在一個已鏈接UDP套接字上,由內核爲輸入操做返回的數據報只有那些來自connect所指定協議地址的數據報。
  3. 由已鏈接UDP套接字引起的異步錯誤會返回給它們所在的進程,而未鏈接UDP套接字不接收任何異步錯誤。

對於TCP套接字,connect只能調用一次。對於一個已鏈接的UDP套接字出於這兩個目的之一能夠再次調用connect:指定新的IP地址和端口號;斷開套接字。

Unix域協議

Unix域協議並非一個實際的協議族,而是單個主機上執行客戶/服務器通訊的一種方法。Unix域提供兩類套接字:字節流套接字(相似TCP)和數據報套接字(相似UDP)。有如下理由使用Unix域套接字:

  1. 在某些系統中,Unix域套接字比位於同一主機的TCP套接字通訊要快。
  2. Unix域套接字可用於在同一主機上的不一樣進程之間傳遞描述符。
  3. Unix域套接字吧客戶的憑證(用戶ID和組ID)提供給服務器,從而可以提供額外的安全檢查措施。

地址結構

Unix域套接字地址結構在頭文件<sys/un.h>中定義:

socketpair函數

socketpair函數建立兩個鏈接起來的套接字。

family參數必須爲AF_LOCAL,protocol參數必須爲0,type參數既能夠是SOCK_STREAM,也能夠是SOCK_DGRAM,新建立的兩個套接字描述符做爲sockfd[0]和sockfd[1]返回。

套接字函數

在connect調用中指定的路徑名必須是一個當前綁定在某個打開的Unix域套接字上的路徑名,並且它們套接字類型也必須一致。

Unix域字節流套接字相似TCP套接字:它們都爲進程提供一個無記錄邊界的字節流接口;Unix域數據報套接字相似UDP套接字:它們都提供一個保留記錄邊界的不可靠的數據報服務。

在一個未綁定的Unix域套接字上發送數據報不會自動給這個套接字捆綁一個路徑名,這一點不一樣於UDP套接字。

相關文章
相關標籤/搜索