Linux socket 編程中存在的五個隱患

前言:
        Socket API 是網絡應用程序開發中實際應用的標準 API。儘管該 API 簡單,可是 
 
開發新手可能會經歷一些常見的問題。本文識別一些最多見的隱患並向您顯示如何避免它們。 相關文檔:《 linux socket 編程》 在 4.2 BSD UNIX® 操做系統中首次引入,Sockets API 如今是任何操做系統的標準特性。事實上,很難找到一種不支持 Sockets API 的現代語言。該 API 至關簡單,但新的開發人員仍然會遇到一些常見的隱患。
  本文識別那些隱患並向您顯示如何避開它們。
隱患 1.忽略返回狀態
第一個隱患很明顯,但它是開發新手最容易犯的一個錯誤。若是您忽略函數的返回狀態,當它們失敗或部分紅功的時候,您也許會迷失。反過來,這可能傳播錯誤,使定位問題的源頭變得困難。
捕獲並檢查每個返回狀態,而不是忽略它們。考慮清單 1 顯示的例子,一個套接字 send 函數。
清單 1. 忽略 API 函數返回狀態
int status, sock, mode;
 
/* Create a new stream (TCP) socket */
sock = socket( AF_INET, SOCK_STREAM, 0 );
...status = send( sock, buffer, buflen, MSG_DONTWAIT );
if (status == -1)
{ /* send failed */ 
printf( "send failed: %s/n",?
 strerror(errno) );
}
else
{ /* send succeeded -- or did it? */}           
 
  清單 1 探究一個函數片段,它完成套接字 send 操做(經過套接字發送數據)。函數的錯誤狀態被捕獲並測試,但這個例子忽略了 send 在無阻塞模式(由 MSG_DONTWAIT 標誌啓用)下的一個特性。
  send API 函數有三類可能的返回值:
  • 若是數據成功地排到傳輸隊列,則返回 0。
  • 若是排隊失敗,則返回 -1(經過使用 errno 變量能夠了解失敗的緣由)。
  • 若是不是全部的字符都可以在函數調用時排隊,則最終的返回值是發送的字符數。
  因爲 send 的 MSG_DONTWAIT 變量的無阻塞性質,函數調用在發送完全部的數據、一些數據或沒有發送任何數據後返回。在這裏忽略返回狀態將致使不徹底的發送和隨後的數據丟失。
 
隱患 2.對等套接字閉包
UNIX 有趣的一面是您幾乎能夠把任何東西當作是一個文件。文件自己、目錄、管道、設備和套接字都被看成文件。這是新穎的抽象,意味着一整套的 API 能夠用在普遍的設備類型上。
考慮 read API 函數,它從文件讀取必定數量的字節。read 函數返回讀取的字節數(最高爲您指定的最大值);或者 -1,表示錯誤;或者 0,若是已經到達文件末尾。
若是在一個套接字上完成一個 read 操做並獲得一個爲 0 的返回值,這代表遠程套接字端的對等層調用了 close API 方法。該指示與文件讀取相同 —— 沒有多餘的數據能夠經過描述符讀取(參見 清單 2)。
清單 2.適當處理 read API 函數的返回值
int sock, status;
sock = socket( AF_INET, SOCK_STREAM, 0 );
...status = read( sock, buffer, buflen );
if (status > 0)
{ /* Data read from the socket */}
else if (status == -1)
/* Error, check errno, take action... */
}
else if (status == 0)
/* Peer closed the socket, finish the close */ 
close( sock );
/* Further processing... */
}
  一樣,能夠用 write API 函數來探測對等套接字的閉包。在這種狀況下,接收 SIGPIPE 信號,或若是該信號阻塞,write 函數將返回 -1 並設置 errno 爲 EPIPE。
 
隱患 3.地址使用錯誤(EADDRINUSE)
  您可使用 bind API 函數來綁定一個地址(一個接口和一個端口)到一個套接字端點。能夠在服務器設置中使用這個函數,以便限制可能有鏈接到來的接口。也能夠在客戶端設置中使用這個函數,以便限制應當供出去的鏈接所使用的接口。bind 最多見的用法是關聯端口號和服務器,並使用通配符地址(INADDR_ANY),它容許任何接口爲到來的鏈接所使用。
  bind 廣泛遭遇的問題是試圖綁定一個已經在使用的端口。該陷阱是也許沒有活動的套接字存在,但仍然禁止綁定端口(bind 返回 EADDRINUSE),它由 TCP 套接字狀態 TIME_WAIT 引發。該狀態在套接字關閉後約保留 2 到 4 分鐘。在 TIME_WAIT 狀態退出以後,套接字被刪除,該地址才能被從新綁定而不出問題。
等待 TIME_WAIT 結束多是使人惱火的一件事,特別是若是您正在開發一個套接字服務器,就須要中止服務器來作一些改動,而後重啓。幸運的是,有方法能夠避開 TIME_WAIT 狀態。能夠給套接字應用 SO_REUSEADDR 套接字選項,以便端口能夠立刻重用。
考慮清單 3 的例子。在綁定地址以前,我以 SO_REUSEADDR 選項調用 setsockopt。爲了容許地址重用,我設置整型參數(on)爲 1 (否則,能夠設爲 0 來禁止地址重用)。
清單 3.使用 SO_REUSEADDR 套接字選項避免地址使用錯誤
int sock, ret, on;struct sockaddr_in servaddr;
/* Create a new stream (TCP) socket */
sock = socket( AF_INET, SOCK_STREAM, 0 ):
/* Enable address reuse */
on = 1;
ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR,
 &on, sizeof(on) );
/* Allow connections to port 8080 from any available interface
*/
memset( &servaddr, 0, sizeof(servaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
servaddr.sin_port = htons( 45000 );
/* Bind to the address (interface/port) */
ret = bind( sock, (struct sockaddr *)&servaddr, sizeof(servaddr) );     
在應用了 SO_REUSEADDR 選項以後,bind API 函數將容許地址的當即重用。
 
隱患 4.發送結構化數據
套接字是發送無結構二進制字節流或 ASCII 數據流(好比 HTTP 上的 HTTP 頁面,或 SMTP 上的電子郵件)的完美工具。可是若是試圖在一個套接字上發送二進制數據,事情將會變得更加複雜。
  好比說,您想要發送一個整數:您能夠確定,接收者將使用一樣的方式來解釋該整數嗎?運行在同一架構上的應用程序能夠依賴它們共同的平臺來對該類型的數據作出相同的解釋。可是,若是一個運行在高位優先的 IBM PowerPC 上的客戶端發送一個 32 位的整數到一個低位優先的 Intel x86,那將會發生什麼呢?字節排列將引發不正確的解釋。
  經過套接字發送一個 C 結構會怎麼樣呢?這裏,也會遇到麻煩,由於不是全部的編譯器都以相同的方式排列一個結構的元素。結構也可能被壓縮以便使浪費的空間最少,這進一步使結構中的元素錯位。
  幸虧,有解決這個問題的方案,可以保證兩端數據的一致解釋。過去,遠程過程調用(Remote Procedure Call,RPC)套裝工具提供所謂的外部數據表示(External Data Representation,XDR)。XDR 爲數據定義一個標準的表示來支持異構網絡應用程序通訊的開發。
如今,有兩個新的協議提供類似的功能。可擴展標記語言/遠程過程調用(XML/RPC)以 XML 格式安排 HTTP 上的過程調用。數據和元數據用 XML 進行編碼並做爲字符串傳輸,並經過主機架構把值和它們的物理表示分開。SOAP 跟隨 XML-RPC,以更好的特性和功能擴展了它的思想。參見 參考資料小節,獲取更多關於每一個協議的信息。
 
隱患 5.TCP 中的幀同步假定
TCP 不提供幀同步,這使得它對於面向字節流的協議是完美的。這是 TCP 與 UDP(User Datagram Protocol,用戶數據報協議)的一個重要區別。UDP 是面向消息的協議,它保留髮送者和接收者之間的消息邊界。TCP 是一個面向流的協議,它假定正在通訊的數據是無結構的,如圖 1 所示。


圖 1.UDP 的幀同步能力和缺少幀同步的 TCP
linux

.UDP 的幀同步能力和缺少幀同步的 TCP
  圖 1 的上部說明一個 UDP 客戶端和服務器。左邊的對等層完成兩個套接字的寫操做,每一個 100 字節。協議棧的 UDP 層追蹤寫的數量,並確保當右邊的接收者經過套接字獲取數據時,它以一樣數量的字節到達。換句話說,爲讀者保留了寫者提供的消息邊界。
如今,看圖 1 的底部.它爲 TCP 層演示了相同粒度的寫操做。兩個獨立的寫操做(每一個 100 字節)寫入流套接字。但在本例中,流套接字的讀者獲得的是 200 字節。協議棧的 TCP 層聚合了兩次寫操做。這種聚合能夠發生在 TCP/IP 協議棧的發送者或接收者中任何一方。重要的是,要注意到聚合也許不會發生 —— TCP 只保證數據的有序發送。
  對大多數開發人員來講,該陷阱會引發困惑。您想要得到 TCP 的可靠性和 UDP 的幀同步。除非改用其餘的傳輸協議,好比流傳輸控制協議(STCP),不然就要求應用層開發人員來實現緩衝和分段功能。
  調試套接字應用程序的工具
  GNU/Linux 提供幾個工具,它們能夠幫助您發現套接字應用程序中的一些問題。此外,使用這些工具還有教育意義,並且可以幫助解釋應用程序和 TCP/IP 協議棧的行爲。在這裏,您將看到對幾個工具的概述。查閱下面的 參考資料 瞭解更多的信息。
  查看網絡子系統的細節
  netstat 工具提供查看 GNU/Linux 網絡子系統的能力。使用 netstat,能夠查看當前活動的鏈接(按單個協議進行查看),查看特定狀態的鏈接(好比處於監聽狀態的服務器套接字)和許多其餘的信息。清單 4 顯示了 netstat 提供的一些選項和它們啓用的特性。

清單 4.netstat 實用程序的用法模式
View all TCP sockets currently active$ netstat
 --tcpView all UDP sockets$ netstat
 --udpView all TCP sockets in the listening state$ netstat
 --listeningView the multicast group membership information$ netstat
--groupsDisplay the list of masqueraded connections$ netstat
--masqueradeView statistics for each protocol$ netstat
 --statistics
 
  儘管存在許多其餘的實用程序,但 netstat 的功能很全面,它覆蓋了 route、ifconfig 和其餘標準 GNU/Linux 工具的功能。
  監視流量
  可使用 GNU/Linux 的幾個工具來檢查網絡上的低層流量。tcpdump 工具是一個比較老的工具,它從網上「嗅探」網絡數據包,打印到 stdout 或記錄在一個文件中。該功能容許查看應用程序產生的流量和 TCP 生成的低層流控制機制。一個叫作 tcpflow 的新工具與 tcpdump 相輔相成,它提供協議流分析和適當地重構數據流的方法,而無論數據包的順序或重發。清單 5 顯示 tcpdump 的兩個用法模式。

  清單 5.tcpdump 工具的用法模式
Display all traffic on the eth0 interface for
the local host$ tcpdump -l -i eth0Show all traffic
on the network coming from or going
to host plato$ tcpdump host platoShow all HTTP traffic
for host camus$ tcpdump host camus and (port http)View
traffic coming from or going
to TCP port 45000 on the local host$ tcpdump tcp port 45000
tcpdump 和 tcpflow 工具備大量的選項,包括建立複雜過濾表達式的能力。查閱下面的參考資料 獲取更多關於這些工具的信息。
tcpdump 和 tcpflow 都是基於文本的命令行工具。若是您更喜歡圖形用戶界面(GUI),有一個開放源碼工具 Ethereal 也許適合您的須要。Ethereal 是一個專業的協議分析軟件,它能夠幫助調試應用層協議。它的插入式架構(plug-in architecture)能夠分解協議,好比 HTTP 和您能想到的任何協議(寫本文的時候共有 637 個協議)。
https://blog.csdn.net/hairetz/article/details/4223234
相關文章
相關標籤/搜索