這裡不是說用System.Web.Hosting.ApplicationHost和System.Net.HttpListener做的那種web server ,而是直接用socket api做一個簡單的能收發HTTP包的網絡服務器,當然也不會完全實現RFC 2616,主要 學習探索用。
我們先來看HTTP協議解析部分,做一個HTTP協議棧-HttpStatck,大概看一下HTTP協議基礎,
1、消息頭和消息體中間用兩個\r\n(0x0d0x0a)來分割,
2、消息頭之間用\r\n分割,
3、消息頭的個數不定,但有最大數,
4、消息體的大小根據Content-Length頭來確定,
5、消息頭的名字和值用英文半角冒號分割
6、消息頭的第一行用來標識協議是request還是response,及協議的版本,請求的方法,應答碼,應 答描述
協議了解了,協議棧就好寫了,如果我們能一次讀取一個完整的包,那我們把整個包讀出來,解析成 字符串,然後用IndexOf,Split等函數很快的就能解析出一個個都HttpRequest和HttpResponse,但是真是 的網絡中,你可能只能解析到半個半個多包,沒准連消息頭的第一行都分兩次才能接受到,甚至像一個中 文字符也有可能會收兩次才能包才能解析成字符串。我們要想提高效率,盡量避免把bytes解析成字符串 ,另外我們只解析出header給上層應用就行了,body的話暴露成一個Stream就行了,因為你不知道Body的 格式,由應用去做處理吧,asp.net也是這樣的,有對應的InputStream和OutStream。
下面是具體的性能方面的分析。
1、在Stack收到異步讀取的網絡包後,首先繼續調用BeginReceive方法,然後再解析收到的包,這是 為了防止在解析包的時候出錯,或者線程掛起而造成無法接受剩下的包,當然每次盡量多讀取一些字節, 讀取次數多也會降低性能,buffer可以設置的稍微大一些,這個可能要經過具體平台的測試才能確定最合 適的值。這點有不同意見,說不要在剛收到異步讀取回調後就先BeginReceive,應該把包收完再 BeginReceive,否則如果本次沒收完包,剩下的包只能在其它的IOCP線程裡接收,影響性能,這個我不確 認,但是一次接受完緩沖區的所有數據是可以做到的,用Socket.IOControl(FIONREAD, null, outValue) 或者socket.Available可以獲取接受緩沖區有多少數據,然後把這些數據收完;但是微軟反對使用這些方 法去探察socket的接受數據大小,因為執行這個方法系統需要內部使用鎖鎖定數據計算這個值,降低 socket效率。關於接受包這裡的最佳實踐,歡迎大家討論。
2、按理說收到包後先放隊列裡,再調用解析包方法,解析包的方法順序從隊列裡取包解析,但解析包 和接受包可以都在一個線程裡,沒有必要引入單獨的解析包線程,最後還是考慮不使用隊列,每次直接把 收到的字節數組進行解析。原則是我們盡量讓一個線程只適用本線程的私有數據,而不去用全局共享的數 據,如果要使用別的線程的數據,就給那個線程發個消息,讓那個線程自己去處理自己線程的數據,而不 要直接操作不屬於自己的數據,那樣的話那個數據就得用加鎖之類的線程同步了。線程模型的確定很重要 。