羅顯庭 黎艷群

摘要:TCP服務器程序在現今網絡發達的儀器儀表中應用廣泛。本文以降低設計人員開發難度為目的,從模塊化的設計思想出發,提出一種支持多客戶端的TCP服務器模型。
關鍵詞:TCP服務器單線程多客戶端類 回調函數
在嵌入式儀器儀表中,經常會遇到需要通過TCP和第三方設備進行通信。開發人員每天忙碌在重復繁瑣的編碼工作中,為儀表編寫不同的應用層協議以適配現場的工作需要。雖然編寫TCP服務器程序有很多種形式,例如為每個客戶端fork出單獨的進程或者為每個客戶端創建單獨的線程,這樣勢必增加系統的開銷,占用過多的資源。本文從通用性和可移植性等方便考慮,實現一種支持多客戶端的TCP服務器模型,減少后期代碼的重復編寫。
TCP服務器模型首先抽象為類的方式,具體數據的實時處理則交由回調函數去完成,將數據收發和協議解析部分分離,減輕開發人員的編碼工作、增強程序的穩定性。
抽象出的類主要有以下幾個方法:服務器打開、關閉、線程啟動、數據發送和接收等,具體實現接口如下
服務器打開方法Open:
該方法主要用于定義監聽服務器的IP地址和端口號,同時設置為非阻塞模式以及限制最大連接數量。調用成功返回服務器的套接字句柄,否則返回無效句柄。具體實現流程如下:
服務器關閉方法Close
主要用來關閉已連接或未釋放的客戶端套接字,同時在應用程序退出前關閉服務器自身用于監聽的套接字。
客戶端數據發送的方法SendData
此方法由兩個函數組成,一個函數用于在已經連接上的客戶端隊列中搜索,當前是和哪個客戶端套接字進行數據通信。另一個函數直接向對應的套接字發送數據(注意在搜索套接字、發送數據失敗時需做重發或關閉等異常處理)。
服務器線程啟動方法StartThread
該方法主要用來定義兩個回調函數,一個用于通知客戶端已連接的事件,另一個用于通知客戶端已接收到數據的事件。同時創建一個線程,用于服務器接受連接、客戶端的數據接收和關閉等操作。
服務器接收連接或客戶端數據接收的方法ThreadFun
該方法主要實現不斷地監測服務器監聽端口是否有新的接收連接,已連接的客戶端是否有數據發送過來,以及是否有客戶端申請關閉斷開等操作。
在一個線程中實現多個客戶端連接和服務器監聽功能,主要使用到了select系統調用。Select系統調用允許程序同時在多個底層文件描述符上等待輸入的到達。這意味著服務器可以同時在多個打開的套接字上處理多個客戶端的請求動作。具體處理流程如下:
服務器讓select同時指向檢查監聽套接字和客戶的連接套接字的Fd_set集合,通過用select調用同時處理多個客戶就不需再依賴多進程或多線程了。一旦select有活動發生,就可以用FD_ISSET來遍歷所有可能的文件描述符,以檢查是哪個套接字有活動發生。
如果是監聽套接字可讀,這說明當前有一個客戶試圖建立連接,此時就可以調用accept而不用擔心發生阻塞的可能,同時利用客戶端連接回調函數通知應用層有新的客戶連接。
如果是某個客戶描述符準備好,說明該描述符上有一個客戶請求需要我們讀取和處理。如果讀操作返回零字節,表示有客戶已結束,就從客戶端隊列中搜索關閉對應的套接字,并把它從文件符集合中刪除。如果返回的數據大于零,就直接讀取數據,同時通過客戶端數據接收回調函數將接收到的數據通知上層進行數據解析和處理。
模型的實例化使用整個操作如下,定義一個服務器對象,打開對應的監聽端口號,定義兩個回調函數,啟動線程處理函數。
在上層的調用程序中只要定義兩個回調函數就可以,tcp_accept函數實現有新客戶連接需要處理的功能,custom_recv函數實現客戶端數據接收時需要進行的數據解析和處理功能即可。
在實際的工程應用中,此模型可以監聽多個設備獲取不同設備的運行狀態數據,功能強大。采用盡量少的線程數量和限制客戶端的連接數量,系統開銷小。同時采用非阻塞模式防止程序干擾其他設備的請求響應,響應快速。TCP服務實現和協議解析部分分離,代碼可移植性強,性能穩定。
參考文獻
[1]陳健、宋健健譯.《Linux程序設計》(第3版)人民郵電出版社.