該系列文章總綱連結:專題分綱目錄 LinuxC 系統程式設計
本章節思維導圖如下所示(思維導圖會持續疊代):
第一層:

第二層:
1 套接字概念
linux使用套接字進行程序間的通信;通過套接字,其他程序的位置對于應用程式來講是透明的;套接字代表通信的端點,必須保證2個端點各有一個套接字才可以。套接字的通信過程如下:
套接字實作了一層抽象,讓使用者感覺在操作檔案一樣。抽象過程如下:
2 準備工作
2.1 位元組序
在網絡環境中,程序間通信是跨主機的,是以就有了位元組序不統一的問題。為解決這個問題,網絡協定提供一種位元組序,當跨主機的兩個程序進行通信時,先将需要傳輸的資料轉換成網絡位元組序,待接收方接收資料後,将其轉換為本機的位元組序。位元組序轉換流程如下:
linux環境下使用4個函數進行位元組序的轉換,函數原型如下:
詳細見linux函數參考手冊。網絡位元組序就是大端位元組序,但是網絡的情況是複雜的,為了保證代碼的可移植性,不論在哪種位元組序的主機上都要做位元組轉換處理。
2.2 位址格式
網絡環境中每台計算機都有一個IP位址。(對于IPv4協定來講,是一個32位無符号整數;對于IPv6協定來講,是一個128位無符号整數)。linux中使用in_addr結構表示一個IP位址,結構定義如下:
當确定了目标機後,還需要通過端口号來确定主機中哪個程序需要通信(每個程序對應一個16位的端口号)。是以,在網絡中,一個IP位址與一個端口号連在一起就可以确定一台主機的一個程序。當唯一的兩點已經确認後,通信就開始了。linux中位址結構的定義如下:
結構socketaddr_in與socketaddr等長,是以可以很容易地互相轉換。
2.3 位址形式轉換
IP位址是以二進制的形式存儲在位址結構中的,直接觀察有些不便,用點分十進制(xxx.xxx.xxx.xxx)表示才直覺。linux下提供的IP位址轉換函數如下:
詳細見linux函數參考手冊。
2.4 獲得主機資訊
一台主機和網絡相關的資訊一般存放在系統中的某個檔案裡(例如/etc/hosts),使用者可以通過系統函數讀取檔案上的内容,在linux下使用gethostent函數讀取和主機有關的資訊:
詳細見linux函數參考手冊。其中,hostent結構體的定義如下:
注意:調用gethostent兩次,則第一次host指針指向的緩沖區内容會被沖掉。
2.5 位址映射
對于使用者而言,套接字的位址結構資訊是不必要的,使用者隻需傳遞一個sockaddr_in位址結構的位址,之後由系統來填充其中的内容即可。網絡環境中的伺服器需要提供一個唯一位址的IP和主機名(域名);對于大部分伺服器來講,用戶端不知道其IP位址,但知道其域名。DNS可以将域名轉換為IP位址,轉換過程如下:
轉換後的IP位址和端口号存儲在addr_info資訊結構中。linux下提供一個函數,即根據伺服器的域名和服務名稱即可得到伺服器的IP位址和端口号;并将其填寫到一個sockaddr_in位址結構中,該函數内部通路了DNS伺服器,進而得到需要通路主機的IP号和端口号,函數原型如下:
詳細見linux函數參考手冊。
3 套接字基礎程式設計
套接字技術對大部分通信細節做了隐藏,使得操作類似于檔案,也正因為這樣,是以很多檔案操作函數也可以用在套接字上。(linux将裝置抽象為檔案的政策使得程式設計簡單很多)
3.1 建立和銷毀套接字描述符
linux環境下建立一個套接字和取消一個套接字的函數原型如下:
詳細見linux函數參考手冊。
3.2 位址綁定
建立一個套接字以後需要綁定位址的套接字才能夠進行通信。linux下使用bind函數将一個套接字綁定在一個位址上,函數原型如下:
詳細見linux函數參考手冊。注意:sockaddr_in結構中不能指定協定為IPv6,即通信域不能指定為AF_INET6。其中,第二個參數在實際當中需要先對參數進行初始化,過程如下:
3.3 建立一個連接配接
在綁定一個套接字後,用戶端就可以建立一個連接配接,對于面向服務的套接字類型,必須指定;對于無連接配接服務,這一步是沒有必要的。linux環境下使用connect函數建立一個主動建立一個連接配接,函數原型如下:
詳細見linux函數參考手冊。注意:對于網絡而言,應用程式一定要能處理連接配接時可能發生的錯誤;失敗原因有很多,一旦失敗就要考慮重新嘗試,不過嘗試一般都需要有一段時間的延遲,以保證網絡有時間自動恢複。
使用connect函數的機制如圖所示:
用戶端建立一個連接配接,伺服器端就要監聽并接受這樣一個連接配接,進而對其進行處理,linux下使用的listen函數監聽用戶端的連接配接請求;使用accept函數接受一個連接配接的請求,函數原型如下:
詳細見linux函數參考手冊。注意:對于套接字描述符,不可使用lseek函數對其進行重定位。
3.4 使用檔案讀寫函數讀寫套接字
在網絡中使用read/write函數容易出現問題,原因如下:
延時問題:對于本地檔案夾,位元組流在本地傳輸的延時可以忽略不計,但是在網絡中傳輸的時間可能會很長;是以會造成I/O的阻塞;解決方法隻能是非阻塞/使用多路I/O。
網絡應用程式要能夠處理因為中斷/網絡連接配接問題造成的讀寫操作異常傳回,但是這樣會讓程式變得更阿基複雜和不好控制。
注意:close函數在網絡環境下出錯的原因并不是檔案本身的問題,而是由于“緩輸出”導緻了異常;write函數隻是将要寫入檔案的内容放到緩存中,真正寫到外存上是需要時間的,對于本地檔案,幾乎不會出錯,但是在網絡環境下,出錯的機率就大了;是以,在網絡環境下,調用write函數并不能保證檔案内已經準确到達對端。
3.5 面向連接配接的資料傳輸
linux環境下用read/write函數進行網絡通信很容易出問題,但是linux下有專門用于面向連接配接的套接字的函數,這兩個函數分别是send和recv,其函數原型如下:
3.6 面向連接配接的最簡單伺服器端與用戶端流程
@1 伺服器端執行流程(僞代碼)如下:
@2 用戶端執行流程(僞代碼)如下:
注意:
在實際當中,如果不知道伺服器的IP位址,可以使用getaddrinfo函數,通過DNS伺服器将伺服器的域名轉換為伺服器主機的IP;如果連域名也不知道,那就無法通信。
對于一般可間的區域網路,伺服器和用戶端大都屬于一個使用者組,其IP位址是互相可見的;但是對于網際網路環境中,伺服器的IP往往是對客戶隐藏的。
3.7 面向無連接配接的資料傳輸
用于面向無連接配接套接字的讀寫函數要複雜一點,由于沒有建立一個連接配接,是以每次發送資料的過程都要明确指出該資料包的目的位址;在接收資料包時,接收程序可以得到發送該資料包的位址。linux環境下提供專門對無連接配接套接字進行讀寫的函數,分别是sendto和recvfrom函數,函數原型如下:
3.8 面向無連接配接的最簡單伺服器端與用戶端流程
@1 伺服器端執行流程(僞代碼)如下:
@2 用戶端執行流程(僞代碼)如下:
4 非阻塞套接字
當程序需要對套接字進行讀寫操作,而套接字的資料尚未準備好,則進行讀寫套接字操作的函數将會阻塞,使程序進入休眠狀态等待,其後面的操作也就無法進行了,非阻塞I/O将解決這種問題。由于套接字屬于一種特殊的檔案,是以,可以使用更改檔案阻塞的方式來修改套接字的阻塞狀态。在伺服器端執行流程(僞代碼)如下:
非阻塞網絡應用程式的用戶端流程與之前的可以一緻,也可以将其做成輸入阻塞,以便于驗證服務端的正确性。