◆劉文杰
(榮成市教育教學(xué)研究中心 山東 264300)
在企業(yè)級局域網(wǎng)的應(yīng)用場景中,經(jīng)常需要開發(fā)CS架構(gòu)的程序,這類程序的穩(wěn)定性和安全性都是非常重要,服務(wù)器端的程序如果穩(wěn)定性不強則容易崩潰,安全性不好則容易受到攻擊。網(wǎng)絡(luò)通信部分涉及的知識點多,開發(fā)難度高,并且需要進行長期的測試,普通的開發(fā)者很難獨立開發(fā),我們對一款國外的名稱為Networkcomms開源的通信框架進行了深入的研究,這個通信框架面向微軟.net平臺,以開源形式基于Apache License v2協(xié)議發(fā)布,我們使用這個通信框架開發(fā)了多款產(chǎn)品,經(jīng)過實踐驗證,這個通信框架性能突出,穩(wěn)定性好,安全性高,值得深入研究。
CS架構(gòu)的程序,分為客戶端與服務(wù)器端,服務(wù)器端我們一般以Windows服務(wù)的形式部署在服務(wù)器上,客戶端可以通過TCP協(xié)議與服務(wù)器端進行連接,程序的架構(gòu)如圖1。
圖1 程序架構(gòu)
客戶端與服務(wù)器端之間的數(shù)據(jù)需要進行傳輸,數(shù)據(jù)的傳輸必須穩(wěn)定高效,通過Networkcomms框架可以輕松的做到這一點,下面我們對框架的數(shù)據(jù)傳輸部分進行介紹。
數(shù)據(jù)通過TCP協(xié)議傳輸時,是以二進制的形式在網(wǎng)絡(luò)上傳輸,我們在發(fā)送數(shù)據(jù)之前,需要按照一定的格式對數(shù)據(jù)進行封裝,接收的一方收到數(shù)據(jù)后,按照預(yù)設(shè)的格式對數(shù)據(jù)進行解析。在Networkcomms通信框架中,數(shù)據(jù)包(Packet)的格式如下:
PacketHeader是數(shù)據(jù)包頭,PacketData是數(shù)據(jù)包體,序列化之后的PacketHeader中的第一個字節(jié),存放PacketHeader的數(shù)據(jù)長度,PacketHeader中的 TotalPayloadSize屬性存放數(shù)據(jù)包體的數(shù)據(jù)長度,接收端的程序收到數(shù)據(jù)包(packet)后,首先根據(jù)第一個字節(jié)解析出PacketHeader的數(shù)據(jù)長度,根據(jù)數(shù)據(jù)長度接收數(shù)據(jù),進而解析出PacketHeader,并根據(jù)PacketHeader中數(shù)據(jù)包體的數(shù)據(jù)長度接收數(shù)據(jù),解析數(shù)據(jù)包體。
在.Networkcomms中,數(shù)據(jù)包頭和數(shù)據(jù)包體的序列化方案有所不同,數(shù)據(jù)包頭使用.net框架自帶的BitConverter類對消息頭進行序列化,沒有使用第三方的序列化組件,數(shù)據(jù)包頭通常不進行壓縮,使用Networkcomms框架進行通信時,數(shù)據(jù)包包頭常見的是40字節(jié),所以一般沒有對數(shù)據(jù)包頭進行壓縮的必要,如果壓縮很小的數(shù)據(jù)反而會使數(shù)據(jù)變大,所以我們一般不壓縮數(shù)據(jù)包包頭。
數(shù)據(jù)包體支持多種序列化的方式,NetworkComms框架內(nèi)置的序列化器有基于josn的JSONSerializer序列化器,基于谷歌序列化方案protobuf實現(xiàn)的ProtobufSerializer序列化器,也可以使用基于微軟BinaryFormatter實現(xiàn)的二進制序列化器,通常我們使用最多的是ProtobufSerializer序列化器,也是Networkcomms推薦我們使用的序列化器。
圖2 數(shù)據(jù)序列化器結(jié)構(gòu)
我們在使用時可以非常方便的指定和切換序列化器,使用networkComms通信框架,在數(shù)據(jù)傳遞之前需要指定序列化器,我們可以進行統(tǒng)一的配置,不需要每次通信時都進行指定,指定默認序列化器的方式如下:
上面的代碼中SendReceiveOptions是數(shù)據(jù)傳輸時存放相關(guān)收發(fā)數(shù)據(jù)參數(shù)的類,序列化器是參數(shù)中的一種,指定了網(wǎng)絡(luò)通信時使用ProtobufSerializer作為序列化器。我們在編寫程序時,服務(wù)器端與客戶端的序列化器需要一致,否則不能正常的通信。
Networkcomms框架支持對數(shù)據(jù)進行壓縮或者加密處理,這個主要依托數(shù)據(jù)處理器進行,內(nèi)置的用于數(shù)據(jù)壓縮的處理器有SharpZipLibCompressor,QuickLZCompressor,用于數(shù)據(jù)填充的處理器有DataPadder,DataPadder處理器支持填充隨機數(shù)據(jù),數(shù)據(jù)填充的目的是增加數(shù)據(jù)被截取后破譯的難度。所有的數(shù)據(jù)處理器都繼承自DataProcessor類。用戶也可以根據(jù)需要實現(xiàn)自定義的數(shù)據(jù)處理器,自定義的數(shù)據(jù)處理器也需要繼承自DataProcessor類。
圖3 數(shù)據(jù)處理器結(jié)構(gòu)
不同于數(shù)據(jù)的序列化,數(shù)據(jù)處理器在非必要的情況下,可以不使用。也可以根據(jù)需要同時使用多個數(shù)據(jù)處理器,比如即使用SharpZipLibCompressor數(shù)據(jù)處理器對數(shù)據(jù)進行壓縮,也使用DataPadder數(shù)據(jù)處理器對數(shù)據(jù)進行填充。啟動數(shù)據(jù)處理器需要在TCP連接之前進行配置,配置只需要通過簡單的幾行代碼便可完成,如下:
通常在使用處理器時,客戶端與服務(wù)器都需要配置相同的數(shù)據(jù)處理器來對數(shù)據(jù)進行處理。
.net框架中用于數(shù)據(jù)傳輸?shù)膕tream類并沒有實現(xiàn)線程安全,而線程安全在很多時候是很重要的,能夠確保程序的穩(wěn)定性。Networkcomms框架對stream類進行了進一步的封裝,創(chuàng)建了ThreadSafeStream類,ThreadSafeStream類繼承自Stream類,擴展了該類的功能,并且解決了多線程訪問時的數(shù)據(jù)流的安全問題。
在安全性要求較高的場景下,可以啟用SSL加強數(shù)據(jù)的安全性。SSL也稱為安全套接字協(xié)議,能夠提升數(shù)據(jù)傳輸時的安全性,防止數(shù)據(jù)在傳輸時被截取。使用SSL傳輸數(shù)據(jù),能夠有效維護數(shù)據(jù)的完整性。Networkcomms框架創(chuàng)建了SSLTools類用于支持SSL。
拒絕服務(wù)攻擊是一種常見的網(wǎng)絡(luò)攻擊方式,攻擊者可以頻繁的與服務(wù)器進行連接,消耗服務(wù)器的帶寬和算力,對服務(wù)器端程序造成較大的影響,嚴重的情況下,可以使服務(wù)器無法提供正常的服務(wù)。
Networkcomms通信框架內(nèi)置了應(yīng)對拒絕服務(wù)攻擊的方法,可以有效的應(yīng)對拒絕服務(wù)攻擊。當服務(wù)器端程序在監(jiān)聽端口上接收到的數(shù)據(jù)無法進行正常的解析時,可以判定為異常數(shù)據(jù),通過DOSProtection類進行跟蹤和處理。
當服務(wù)器收到異常數(shù)據(jù),即收到不能正常解析的不規(guī)則的數(shù)據(jù)后,DOSProtection對相關(guān)數(shù)據(jù)的IP進行記錄,默認如果在指定時間段內(nèi)收到異常的連接請求超過100個,則對相應(yīng)的IP地址進行查封。我們可對查封的時間長度進行設(shè)置,過了這段時間之后可自動解封。
使用Networkcomms通信框架,我們可以傳送文件,既可以從客戶端把文件發(fā)送到服務(wù)器端,也可以從服務(wù)器端把文件發(fā)送到客戶端。小的文件可以一次性發(fā)送,大的文件一般需要分塊發(fā)送,每一個文件發(fā)送時都會分配一個獨特的文件ID,程序支持同時發(fā)送多個文件,客戶端和服務(wù)器端都能夠發(fā)送和接收文件。
圖4 文件發(fā)送接收過程
在文件發(fā)送時,首先使用FileIDCreator類為文件分配一個文件ID,這個文件ID是獨一無二的,接收端在接收文件數(shù)據(jù)時,會把文件ID相同的數(shù)據(jù)派發(fā)給同一個文件接收器,文件接收器對數(shù)據(jù)進行組裝。根據(jù)經(jīng)驗,文件分塊的大小我們設(shè)置為40960字節(jié),大于40960字節(jié)的文件,一般需要分塊發(fā)送。文件發(fā)送部分我們設(shè)計了文件發(fā)送器和文件發(fā)送管理器以及顯示文件傳輸進度的文件傳輸控件。
圖5 文件發(fā)送架構(gòu)
3.1.1 文件發(fā)送器
文件發(fā)送器主要用于發(fā)送文件,發(fā)送器根據(jù)文件地址在磁盤上找到文件生成FileStream文件流。然后把文件流封裝成支持線程安全的ThreadSafeStream數(shù)據(jù)流。在文件發(fā)送器內(nèi)對文件進行分塊發(fā)送,每一個文件塊都有一個“文件塊順序號”,接收端在組裝文件時需要根據(jù)這個順序號按照順序組裝文件。文件發(fā)送器在發(fā)送文件數(shù)據(jù)時,同時發(fā)送當前文件塊的相關(guān)信息,相關(guān)信息主要包括文件ID、文件名、流的長度、發(fā)送的字節(jié)數(shù)、文件包的順序號。服務(wù)器端收到這些數(shù)據(jù)后,將根據(jù)數(shù)據(jù)信息組裝文件,顯示接收進度,并把文件保存到相應(yīng)的目錄中。NetworkComms框架在發(fā)送消息時,數(shù)據(jù)包中帶有消息類型,服務(wù)器端將會根據(jù)消息類型把消息分給不同的消息處理器進行處理。文件數(shù)據(jù)的消息類型為PartialFileData,文件信息的數(shù)據(jù)類型是PartialFileDataInfo。
文件發(fā)送器中定義了3個事件,分別是FileTransProgress(文件傳輸進度)、FileTransCompleted(文件傳輸完成)、FileTransDisruptted(文件傳輸中斷)。這3個事件與文件傳輸窗體控件關(guān)聯(lián)。每收到一個文件塊,都會觸發(fā)FileTransProgress事件,然后控件中的文件傳輸過程就會更新。如果文件傳輸完成或者文件傳輸中斷,也會觸發(fā)相應(yīng)的事件,窗體控件中會有彈窗顯示文件傳輸完成或者文件傳輸中斷的信息。
3.1.2 文件發(fā)送管理器
每當發(fā)送一個新文件時,文件發(fā)送管理器都會創(chuàng)建一個文件發(fā)送器,其能夠?qū)ξ募l(fā)送器進行管理,文件發(fā)送完成后,相應(yīng)的文件發(fā)送器會注銷。文件發(fā)送管理器中有一個類型為 Dictionary<string,SendFile>的字典類,當有新文件開始發(fā)送時,文件ID作為關(guān)鍵詞加入到字典類中。當某文件傳輸完成時,從字典類中刪除該文件ID以及相關(guān)信息??梢愿鶕?jù)文件ID在文件發(fā)送管理器中找到相應(yīng)的文件發(fā)送器。文件發(fā)送器中的3個事件,即FileTransProgress(文件傳輸進度)、FileTransCompleted(文件傳輸完成)、FileTransDisruptted(文件傳輸中斷)也會傳遞給文件發(fā)送管理器。
相比于文件發(fā)送,文件接收會更加復(fù)雜一點,在文件接收端,需要對“PartialFileData”,“PartialFileDataInfo”這兩種類型的消息進行處理,“PartialFileData”對應(yīng)于文件數(shù)據(jù),“PartialFileDataInfo”對應(yīng)于文件信息。接收端接收文件時需要這2個消息都收齊后才能開始文件的接收。我們知道數(shù)據(jù)包的傳輸是有先后順序的,接收端有可能先收到“PartialFileData”類型的數(shù)據(jù)包,也有可能先收到“PartialFileDataInfo”類型的數(shù)據(jù)包。NetworkComms框架在接收端設(shè)計了兩個字典類型的容器用于緩存接收到的數(shù)據(jù)包。
當先收到文件數(shù)據(jù),相應(yīng)的文件信息還沒有收到時,把文件數(shù)據(jù)加入到incomingDataCache容器中進行緩存,當先收到文件信息時,把文件信息加入到incomingDataInfoCache容器中進行緩存。加入相應(yīng)的緩存容器時,都以文件的順序號作為關(guān)鍵詞。
如果文件數(shù)據(jù)和文件信息都已經(jīng)收到,那么就可以啟動文件接收管理器創(chuàng)建文件接收器開始接收文件。
文件接收與文件發(fā)送相對應(yīng),所以文件接收的實現(xiàn)與文件發(fā)送過程有類似的地方,文件接收部分的架構(gòu)如圖6。
圖6 文件接收架構(gòu)
3.2.1 文件接收管理器
文件接收管理器對所有需要接收的文件數(shù)據(jù)進行管理,為每一個需要接收的文件都創(chuàng)建一個文件接收器,把文件ID相同的文件數(shù)據(jù)都交給同一個文件處理器進行處理,如果兩個文件數(shù)據(jù)的文件ID相同,說明這兩個文件數(shù)據(jù)屬于同一個文件,文件處理器將會根據(jù)收到的文件信息類進行組裝即可。
文件接收管理器聲明了4個事件,分別是FileTransProgress(文件傳輸進度)、FileTransCompleted(文件傳輸完成)、FileTransDisruptted(文件傳輸中斷)、FileCancelRecv(主動取消文件傳輸)。文件接收管理器在創(chuàng)建文件接收器時會主動的關(guān)聯(lián)文件接收器的相關(guān)事件。
如果文件接收完成,文件接收管理器會注銷相關(guān)的文件接收器,這樣能夠節(jié)省資源。
3.2.2 文件接收器
文件接收器負責接收文件,文件接收管理器根據(jù)文件ID把屬于同一個文件的數(shù)據(jù)派發(fā)給同一個文件處理器,所以每個文件接收器接收到的數(shù)據(jù)都屬于同一個文件,文件接收器根據(jù)文件信息類進行組裝即可,組裝完成后,當前文件接收器注銷。當有收文端接收到新文件后,文件接收管理器會創(chuàng)建新的文件接收器來接收新的文件。
文件接收器重新組裝好文件之后,把文件保存在服務(wù)器上。
分塊接收的代碼如下:
程序中的文件進行傳輸時,可以實時的顯示文件傳輸進度,用戶可以隨時取消文件的發(fā)送,當文件傳輸完成時,彈出一個窗口顯示文件發(fā)送完成,如果文件發(fā)送中斷,也會彈出窗口顯示,傳輸控件與文件發(fā)送器或文件接收器中的事件相關(guān)聯(lián),可以實時的顯示出文件傳輸?shù)倪M度,如果用戶需要取消文件的發(fā)送或者接收,可以點擊取消按鈕進行取消。文件傳輸時的效果如圖7。
圖7 文件傳輸效果圖
當客戶端與服務(wù)器端的TCP連接建立完成之后,客戶端與服務(wù)器端就可以開始通信了,客戶端可以發(fā)送數(shù)據(jù)給服務(wù)器端,也可以從服務(wù)器端獲取數(shù)據(jù)。在一般的使用場景中,CS架構(gòu)的程序的第一個窗體通常是用于輸入用戶名和密碼的登錄窗口,把用戶輸入的用戶名和密碼發(fā)送給服務(wù)器端,服務(wù)器端程序連接數(shù)據(jù)庫,對客戶端發(fā)來的用戶名和密碼進行驗證,對不同角色的用戶賦予不同的權(quán)限,并返回驗證信息給客戶端。
客戶端發(fā)送用戶名和密碼給服務(wù)器端,服務(wù)器端進行驗證,如果驗證成功返回一個消息給客戶端,客戶端接收服務(wù)器返回的驗證消息,如果認證成功,程序跳轉(zhuǎn)到主窗口,就可以在主窗口中同服務(wù)器進行通信了。對于簡單的,對安全性要求不高的程序,這樣是可以的。但存在一個問題,就是程序的客戶端可能被反編譯,攻擊者可以直接修改登錄窗體的代碼,跳過用戶驗證這一步,直接進入到主窗體同服務(wù)器進行通信。為了解決這個問題,我們可以使用令牌認證機制。
具體做法是,客戶端發(fā)送用戶名和密碼給服務(wù)器端,服務(wù)器端認證通過后,生成一串字符串作為令牌,并傳遞給客戶端,客戶端以后的每個請求都需要攜帶令牌信息。服務(wù)器端接收到消息后,首先解析出數(shù)據(jù)的類型,如果是請求登錄的消息,則連接數(shù)據(jù)庫進行認證,其他類型的消息都需要對消息攜帶的令牌消息進行認證,如果缺失令牌,或者令牌認證不通過,服務(wù)器端不返回消息給客戶端,或者主動關(guān)閉與客戶端的TCP連接。令牌具有時效性,如果客戶端用戶退出登錄,或者客戶端長時間沒有請求消息,令牌都會失效,客戶端再次請求數(shù)據(jù)時需要重新登錄。
CS結(jié)構(gòu)的程序,我們通常在服務(wù)器端實現(xiàn)一個用戶管理器,用戶管理器可以對所有在線用戶進行管理。當用戶認證成功后,服務(wù)器端把當前用戶加入到在線用戶管理器中。有了用戶管理器的支持,不同的客戶端之間可以進行端到端的聊天,服務(wù)器可以轉(zhuǎn)發(fā)用戶上線下線的消息給其他用戶,服務(wù)器端可以主動發(fā)消息給選定的客戶端,用戶管理器我們用一個字典類實現(xiàn),代碼如下:
在企業(yè)級的場景中,我們經(jīng)常需要開發(fā)cs架構(gòu)的程序,開發(fā)這類程序,NetworkComms是一個成熟的通信框架,值得認真研究。研究這個框架的過程中可以學(xué)到很多的知識,特別是網(wǎng)絡(luò)通信方面的知識,并對如何應(yīng)對網(wǎng)絡(luò)安全問題有更加深入的認識。