該系列文章總綱連結:專題分綱目錄 linuxc 系統程式設計
本章節思維導圖如下所示(思維導圖會持續疊代):
第一層:
第二層:
1 套接字程式設計深入
套接字程式設計中有許多進階技巧,使用這些技巧可以更好地操作套接字,完成網絡通信的任務;掌握這些技巧,可以更好地開發高品質的網絡應用程式。
1.1 bind函數的重要作用
伺服器端程式與用戶端程式的顯著特點就是用戶端不需要bind監聽函數。bind函數是将套接字綁定一個ip位址和端口号;如果沒有使用bind函數綁定位址和端口,則在調用listen和connect函數時核心會自動為套接字綁定,是以,理論上調用bind函數是可以省略的;但事實上listen和connect綁定的形式是不一樣的,如下所示:
listen綁定:listen函數中沒有位址結構這個參數,是以隻能由系統設定ip位址 和端口号。
connect綁定:使用一個設定好的結構(sockaddr_in)作為參數,結構中指定了要綁定的伺服器。
伺服器端程式不關心用戶端的ip位址,核心會為其綁定為任意值(inaddr_any),端口号也會由核心指派一個可用的端口。(由于是臨時指派,是以會導緻每次執行伺服器程式時使用的端口不一樣,這就要求用戶端程式每次都要更改端口号,而這并不現實)
由此可知,bind函數對于伺服器而言很重要。
1.2 并發伺服器
前面面向連接配接的伺服器有一個弊端,那就是一次隻能處理一個用戶端請求,但是如果有一個用戶端程式占用伺服器不放,則其他的客戶機将被“餓死”而不能工作;是以,在現實當中一個面向連接配接的伺服器不會使用循環架構,取而代之的是使用對程序處理的方式來處理多個請求,即并發伺服器,并發伺服器執行流程(僞代碼)如下:
并發伺服器解決了循環伺服器客戶機獨占伺服器的情況,但也要注意兩個新問題:
建立子程序非常消耗資源,為提高效率必須使用優秀的算法。
子程序結束後,要注意對資源進行回收,這是一個可以使系統崩潰的潛在問題。
1.3 udp協定的connect函數應用
connect函數也可以用于udp連接配接,雖然udp屬于面向無連接配接通信協定,但如果通信時資料報的目的位址總是固定的,那麼每次都這樣操作有些不必要,這時使用connect來就愛你裡一個連接配接反而使通信變得更有效率。此時用戶端的執行流程與面向連接配接的客戶機一緻;了不同指出在于一個是面向連接配接,一個不面向連接配接。流程(僞代碼)如下:
當資料通信量很大時,這種發方法的效率将高于傳統的無連接配接通信方法。
2 多路選擇i/o
多路i/o是另一種處理i/o的方法,比傳統的i/o更有效率,是一種充分利用時間的典型,在網絡應用中很常用。
2.1 多路選擇i/o概念
多路選擇i/o主要是為了防止i/o阻塞,避免程序陷入僵死狀态。這種方法的思想就是構造一張需要讀取資料的裝置表(通常是檔案描述),調用一個函數輪詢這個表中的裝置,直到有一個裝置可以讀寫,該函數才傳回。多路i/o模型如圖所示:
多路選擇i/o需要使用兩個系統調用:
負責檢查并傳回可用裝置的檔案描述符。
負責對該檔案描述符進行讀寫。
2.2 實作多路選擇i/o
linux下使用select函數實作所路選擇i/o,函數原型如下:
詳細見linux函數參考手冊。注意:預設情況下,一個程序最多有1024個檔案描述符。
2.3 屏蔽信号的多路選擇i/o
pselect函數與select函數的差別:
pselect最後一個參數是一個信号屏蔽的選項,即擁有信号屏蔽的功能;其中不可以屏蔽的信号有sigkill和sigstop。(防止惡意程式攻擊計算機)
pselect中的時間參數用的結構是timespec,timespec結構所能表示的最小精度是納秒;如果将其作為定時器使用,将是linux中最精确的定時器。
linux下使用pselect函數來實作屏蔽信号的多路選擇,函數原型如下:
詳細見linux函數參考手冊。
2.4 多路選擇i/o的伺服器端流程
2.5 輪詢i/o
select函數在使用中是不支援streams的,是以系統在select的基礎上又添加了poll函數,poll函數支援各種類型檔案描述符做i/o多路轉換;但是與select不同的是,poll函數獨立為每個要監控的檔案描述符建立一個操作結構體pollfd,結構中描述了監控的行為和目标檔案描述符發生的事件,結構體定義如下:
events和revents成員設定以下标志之一/多個标志的組合,如表所示:
注意:成員events不可以被設定為異常标志;成員revents為調用poll函數時傳回的資料,是以在調用前不需要設定。linux下poll函數原型如下:
3 非網絡通信套接字
非網絡通信套接字主要用于本機内的程序通信;由于本機内程序的ip位址相同,是以隻需要程序号來确定通信的雙方。
3.1 非命名unix域套接字
linux下使用socketpair函數創造一對未命名的、互相連接配接的unix域套接字,函數原型如下:
詳細見linux函數參考手冊。socketpair函數建立一對未命名的unix域套接字,由于沒有命名,是以其他程序不能使用該套接字進行通信,也就是說,隻有儲存了未命名的unix域套接字的檔案描述符的程序才可以使用它。這一點有點類似于管道,父程序建立一個子程序,二者都儲存管道兩端的檔案描述符,之後各自關閉管道的一端開始通信。
3.2 命名unix域套接字
未命名unix域套接字限制了通信的範圍,不夠靈活;而命名套接字可以解決這個問題。與網絡通信套接字一樣,unix域套接字也需要綁定位址,不過由于二者所使用的域不一樣,是以其位址族也不相同。在linux系統中,unix域套接字使用sockaddr_un結構存儲位址,其結構原型如下:
該位址綁定到一個unix域套接字時系統會建立一個檔案,其路徑名為sun_path中所表示的路徑名,類型為s_ifsock。這個檔案不能打開,需要進行通信的程序都綁定到這個檔案上去,該檔案的作用類似一個資訊的中轉站。在調用bind函數進行位址綁定時,需要将字元數組sun_path的大小作為參數傳遞給核心,通常的寫法是:
詳細見linux函數參考手冊。其中,sun_path的大小為sizeof(struct scokaddr_un) - sizeof(sa_family_t),這樣做是為了提高代碼的移植性。注意:
在用戶端建立本地套接字時需調用bind與相應的路徑相綁定,這是必須的一步,不同于網絡套接字,其實網絡套接字程式設計也是需要bind,隻不過核心自動隐式進行綁定;
在建立套接字綁定之前應先unlink預綁定的路徑套接字檔案,否則bind将出錯;
在将套接字與相應的路徑綁定之後,若不再需要通過該有名路徑與其它套接字相連,可以unlink該名字路徑。
由此可知,程序使用套接字檔案的磁盤檔案進行通信,但每次進行位址綁定時不希望該檔案的目錄項存在。通常伺服器的程序套接字磁盤檔案會長時間存在,其目錄項也會不斷地被删除和建立。每一次用戶端連接配接,都會在綁定伺服器端位址時建立一次,接着就删除該檔案,負責下一個用戶端的連接配接将無法綁定伺服器的位址。用戶端程序的套接字檔案在通信結束後就删除了,每一個用戶端程序都必須擁有一個套接字檔案;為保證該檔案在系統的唯一性,通常的做法是使用程序的id作為檔案名,這樣就可以保證檔案名不會沖突。
3.3 unix域套接字的伺服器端與用戶端流程以及注意事項
unix域套接字的使用方法和網絡通信套接字的使用方法一樣,也分為伺服器程序和用戶端程序。伺服器的執行流程和用戶端的執行流程與網絡通信套接字的執行流程一緻。