1. 程式人生 > >記一次資料採集軟體(伺服器)開發經歷(c#,socket,TCP)

記一次資料採集軟體(伺服器)開發經歷(c#,socket,TCP)

這次經歷大致分為以下幾個階段:一、C#基本操作學習和簡單TCP通訊實現    先前有使用MFC的經歷,因此對於C#的控制元件使用比較容易上手,每次使用之前可通過網路查詢到控制元件使用方法,直接拖拽就OK,唯一不適應的就是C#全部都是類,不過習慣後會覺得比C++更方便呼叫。TCP的實現主要還是以網路部落格為主,這類的部落格很多,通過簡單的搭建,就可以實現一個伺服器。有同事的前車之鑑,建議我們使用非同步。我使用的方法來自於這篇部落格。裡面使用List來維護所連線的客戶端,TcpClientState這個類中封裝每個客戶端的連線狀態,buff快取等。       https://blog.csdn.net/zhujunxxxxx/article/details/44258719二、資料快取和解析的實現     關於資料解析這一部分,首先做了一個大的佇列,Queue<byte[]>,之前寫過一個佇列的類,直接將c++改成c#類,來呼叫,內部也做了執行緒安全,我的想法是每一幀資料都是一個byte陣列,新開執行緒,在佇列非空的情況下,每次取一個byte[]陣列,進行解析。使用Queue的時候,有時取不到資料,沒找到原因,那時候我也是too young too simple,後來覺得list更順手,就改為list實現了。解析沒什麼好說的,按照專案協議解析出每個欄位,解析不出來的記錄下來哪裡有問題就可以了。三、異常捕捉    之前沒用過try{}catch{},在同事的幫助下開始對可能存在問題的地方進行異常記錄。使用不難。四、資料入庫   我們採用的是mysql,具體入庫之前,先學習了下C#和資料庫是怎麼連線起來的,C#要呼叫MySql.Date.dll,這些需要在網上下載,另外還需要下載一個MySQL Connector Net,需要安裝。然後就可以參考網上的教程,連線資料庫,寫sql語句,然後資料入庫了。具體實現時,根據資料庫表的設計生成了n個datatable,存入欄名,然後解析結果存入datatable中,然後從datatable中取資料,生成sql語句,進行入庫。實現過程也較為順利。之所以使用datatable作為快取是因為我們資料結構的特殊性,裡面有些欄位有,有些欄位沒有,因此不能寫統一的sql語句,只能先放在統一的datatable中,使用同一個函式生成sql,而不是每個表單獨生成語句,這樣會減少程式碼量。五、日誌的實現    整個系統需要記錄的日誌較多,而且檔案種類的不同,位置的不同,管理起來較為複雜,直接記錄檔案比較麻煩,說白了,就是懶。。。於是乎在網上搜索如何記錄日誌,c#下有個框架log4net,這是我第一次接觸所謂的框架,入門較容易,理解起來有點難,因為裡面使用了app.config這些配置檔案,配置檔案放置的位置,還有怎麼載入,都不知道。關於配置檔案,裡面有很多配置引數不在贅述,網上一找一大堆,裡面主要就是講這個檔名、檔案位置、檔案格式、裡面的內容排版什麼的,還有一些比較高階的用法,比如檔案最大可為多少M,如果超了,就自動新建一個檔案,原來的日誌檔名後面加個.1,一直可以.2等等等等。      詳細記錄日誌時又出現另一個坑,那就是每次記錄日誌,我要根據日誌的類別,記錄在不同的路徑下,比如一個上報資料幀是來自於某個裝置的,我要記錄在裝置資料夾下,對應的日期檔案,另一個數據幀是告警資料幀,要直接記錄在日期檔案內,不對裝置編號做區分。找了許久不知道該怎麼實現,後來找到了配置檔案中的某個引數是記錄檔名的,只要每次記錄前把檔名進行配置,就可以記錄到對應的路徑中,採用的是讀xml檔案,然後更改相應欄位,重新載入一下配置檔案就可以。六、池的概念      開發到這一步,似乎基本的東西都弄好了,只待繼續優化。這裡我使用了一個測試軟體TCP/UDP Performance,可以在這個網址www.ikende.com中下載到。這個測試軟體可以同時模擬n個客戶端傳送資料。但只能傳送同一條內容,而且間隔時間固定。開客戶端數量較多的時候,服務端處理資料很慢,畢竟我這個也屬於高併發了。如果採用先前入庫那一套,每次插入時,先開啟連線,插入,然後關閉連線,效率可想而知會比較慢,這時我考慮用連結池,並且為了保障整個程式只有一個連線池,這裡第一次接觸了所謂的設計模式----單例模式,當然,目前為止,我也就接觸過這麼一種。每次要入庫時,先從池中取一個物件,然後使用完歸還就ok,大大減少了開關連線時消耗的時間。當中有個小插曲,就是使用池的效果沒那麼好,修改了一個引數,具體哪個引數忘記了,之後效率提升很快。六、TCP協議的再理解      整個系統進行到這裡,似乎只待完善了,然後使用實際裝置上手一測試,我傻掉了。資料來的超級快,也超級多。當然這點後期我們會進行相同資料過濾,暫且不表。之前處理資料,我採用的是所有客戶端發過來的資料包放在一個大的list中,理所當然的想,來一幀,我解一幀資料。但我忽略了TCP協議,TCP是以流的形式傳遞資料,並不是我們以為的一幀來傳遞,它會自己根據網路情況來進行資料傳輸,因此,我以為的一幀,有可能是好幾幀,這還只是一個客戶端的情況,如果是n個客戶端同時發,那就會出現客戶端1的半幀+客戶端2的半幀,這種粘包現象,因此我的資料快取完全失效。有兩個想法,從客戶端接收到資料後,對資料新增客戶端標記,比如IP之類的,解析的時候,如果幀完整,就解析,不完整就一直取,等另一半,想法挺好,但我不知道怎麼做。另一個想法,就是在客戶端類中加執行緒,各自處理各自的資料,這樣就不會出現串臺問題了。每個客戶端處理每個客戶端的事兒,聽起來不錯。於是乎,上手做了,開了幾個客戶端來測,記憶體卒。      原因顯而易見,來一個客戶端就開一個執行緒,消耗太大,而且我們的伺服器是要以1000臺為基準,幾臺就不行了。遂尋求大佬幫助,還有個坑事兒,就是一般來講,資料幀都是通過幀頭幀尾校驗來判斷,我們資料幀的格式是定長訊息頭+不定長訊息體,so,如果串臺,根本不知道自己該從哪裡繼續讀。      大佬直接甩了個supersocket框架給我。。。七、投入supersocket的懷抱     直接看官網文件,理解每個類的意義,重點是理解它是怎麼使用session來管理客戶端的,另外就是對於TCP下的資料是如何解析的,supersocket的確很super啊,裡面封裝了很多東西,接收到的資料直接使用過濾器,就可以將每個幀取出,而且不會串臺!它提供了好幾種過濾器的方式,對於我這種頭部格式固定,不定長訊息體的資料幀,它也有一個相應的類可以使用,就是FixedHeaderReceiveFilter,通過過濾器,你可以將你接收的資料組合成你想要的Info格式,這個Info要繼承IRequestInfo,像我的的話,為了方便使用,裡面寫了訊息頭結構體、訊息體陣列、還有整個資料幀的陣列。然後再在MyAppServer中新增三個事件就ok,一個連線、一個接收到資料、一個斷開,接收到資料的事件函式裡,直接寫,你是怎麼操作你的Info就行了。其他的,supersocket都替你做好了。本以為學一個框架會比較麻煩,等真正使用了,就會覺得便利了。後面關於客戶端的管理,直接使用SessionList就可以,實質上就是一個鍵值對,我使用了兩個list來維護,一個是IP,一個是裝置編號,IP用來維護裝置的連線,裝置編號是為了查詢線上裝置時,直接在介面顯示裝置編號的,實質都一樣。八、其他      除了上面講的幾個大的部分,還剩一些比如入庫時,將代號與實際意義相關聯,http的實現,supersocket的一些配置,還有服務端傳送給指定客戶端訊息,客戶端和服務端心跳的維護,入庫時使用事務,還有子表和主表關係複雜,主外來鍵的坑事兒,GUID,SVN的學習和使用,這裡不做贅述。九、後記      實際開發大致從三月多開始,到現在六月底結束,一直在不斷摸索。我也是第一次做這種比較有實際意義的開發,每次使用新的東西,我都是先寫一個小的test,測試通過後,再加到專案中,收穫頗多。當然,這個採集軟體還是存在很多問題,只是我現在可能沒發現,在以後的日子,逐漸改進吧。