四虎精品视频-四虎精品成人免费网站-四虎黄色网-四虎国产视频-国产免费91-国产蜜臀97一区二区三区

蛙蛙推薦:自己寫個IIS玩-協(xié)議解析篇

   這里不是說用System.Web.Hosting.ApplicationHost和System.NET.HttpListener做的那種web server,而是直接用socket api做一個簡單的能收發(fā)HTTP包的網(wǎng)絡(luò)服務(wù)器,當(dāng)然也不會完全實現(xiàn)RFC 2616,主要學(xué)習(xí)探索用。

   我們先來看HTTP協(xié)議解析部分,做一個HTTP協(xié)議棧-HttpStatck,大概看一下HTTP協(xié)議基礎(chǔ),
   1、消息頭和消息體中間用兩個/r/n(0x0d0x0a)來分割,
   2、消息頭之間用/r/n分割,
   3、消息頭的個數(shù)不定,但有最大數(shù),
   4、消息體的大小根據(jù)Content-Length頭來確定,
   5、消息頭的名字和值用英文半角冒號分割
   6、消息頭的第一行用來標(biāo)識協(xié)議是request還是response,及協(xié)議的版本,請求的方法,應(yīng)答碼,應(yīng)答描述

   協(xié)議了解了,協(xié)議棧就好寫了,如果我們能一次讀取一個完整的包,那我們把整個包讀出來,解析成字符串,然后用IndexOf,Split等函數(shù)很快的就能解析出一個個都HttpRequest和HttpResponse,但是真是的網(wǎng)絡(luò)中,你可能只能解析到半個半個多包,沒準(zhǔn)連消息頭的第一行都分兩次才能接受到,甚至像一個中文字符也有可能會收兩次才能包才能解析成字符串。我們要想提高效率,盡量避免把bytes解析成字符串,另外我們只解析出header給上層應(yīng)用就行了,body的話暴露成一個Stream就行了,因為你不知道Body的格式,由應(yīng)用去做處理吧,ASP.NET也是這樣的,有對應(yīng)的InputStream和OutStream。

   下面是具體的性能方面的分析。

   1、在Stack收到異步讀取的網(wǎng)絡(luò)包后,首先繼續(xù)調(diào)用BeginReceive方法,然后再解析收到的包,這是為了防止在解析包的時候出錯,或者線程掛起而造成無法接受剩下的包,當(dāng)然每次盡量多讀取一些字節(jié),讀取次數(shù)多也會降低性能,buffer可以設(shè)置的稍微大一些,這個可能要經(jīng)過具體平臺的測試才能確定最合適的值。這點有不同意見,說不要在剛收到異步讀取回調(diào)后就先BeginReceive,應(yīng)該把包收完再BeginReceive,否則如果本次沒收完包,剩下的包只能在其它的IOCP線程里接收,影響性能,這個我不確認(rèn),但是一次接受完緩沖區(qū)的所有數(shù)據(jù)是可以做到的,用Socket.IOControl(FIONREAD, null, outValue)或者socket.Available可以獲取接受緩沖區(qū)有多少數(shù)據(jù),然后把這些數(shù)據(jù)收完;但是微軟反對使用這些方法去探察socket的接受數(shù)據(jù)大小,因為執(zhí)行這個方法系統(tǒng)需要內(nèi)部使用鎖鎖定數(shù)據(jù)計算這個值,降低socket效率。關(guān)于接受包這里的最佳實踐,歡迎大家討論。 
   2、按理說收到包后先放隊列里,再調(diào)用解析包方法,解析包的方法順序從隊列里取包解析,但解析包和接受包可以都在一個線程里,沒有必要引入單獨的解析包線程,最后還是考慮不使用隊列,每次直接把收到的字節(jié)數(shù)組進(jìn)行解析。原則是我們盡量讓一個線程只適用本線程的私有數(shù)據(jù),而不去用全局共享的數(shù)據(jù),如果要使用別的線程的數(shù)據(jù),就給那個線程發(fā)個消息,讓那個線程自己去處理自己線程的數(shù)據(jù),而不要直接操作不屬于自己的數(shù)據(jù),那樣的話那個數(shù)據(jù)就得用加鎖之類的線程同步了。線程模型的確定很重要。
   3、按理說解析網(wǎng)絡(luò)包推薦用Encoding.UTF8.GetDecoder().GetChars()方法,該方法會維持utf8解析狀態(tài),在收到不能解析成一個完整的unicode字符的包的字節(jié)數(shù)組的時候它可以保存剩下的半截兒包,和下次收到的包一起解析,而不會造成包丟失。但是該方法的參數(shù)只能傳入一個char數(shù)組,然后我們有可能把多個char數(shù)組進(jìn)行內(nèi)存拷貝,這就浪費了性能,所以不考慮了。如果該方法能把解析出來的char數(shù)組自動填充到一個字節(jié)環(huán)形鏈表里,我們就可以考慮用它。我們盡量使用.NET自己提供的功能,但是如果不滿足我們的需求的時候,我們就得自己實現(xiàn)去,當(dāng)然可以反射.NET程序集,借鑒他的做法。
   4、我們應(yīng)該盡量避免把收到的字節(jié)數(shù)組解析成字符串,然后再按包的規(guī)則進(jìn)行解析,因為把字節(jié)數(shù)組轉(zhuǎn)換成字符串也是個耗時的過程,像一些解析包的標(biāo)志位如分割消息頭和消息體的/r/n/r/n,分割多個消息頭的/r/n,其對應(yīng)的字節(jié)表示值是固定的,如0d0a0d0a,0d0a,我們直接對字節(jié)數(shù)組進(jìn)行解析就能區(qū)拆出來消息頭字節(jié)數(shù)組和消息體字節(jié)數(shù)組。
   5、對字符串的操作我們可以用正則表達(dá)式,用string類的方法等,但對字節(jié)數(shù)組就沒這么多的API了,但是我們可以去了解一下正則表達(dá)式的原理,先寫出正則正則表達(dá)式,再推導(dǎo)出對應(yīng)的NFA算法,再推導(dǎo)出對應(yīng)的DFA算法,就可以寫出針對字節(jié)數(shù)組的算法了。典型的場景是我們需要讀取到字節(jié)數(shù)組里的0d0a0d0a的token,或者我們知道了表示消息頭的字節(jié)數(shù)組,我們要把這些字節(jié)數(shù)組按照0d0a分割成多個子數(shù)組,然后再對每個子數(shù)組進(jìn)行utf-8.getstring,這應(yīng)該比把整個header字節(jié)數(shù)組轉(zhuǎn)換成字符串再split性能好一些,因為split會臨時生成多個小字符串,引起很多對象分配操作。其實我們并不應(yīng)該把大字節(jié)數(shù)組分割成小字節(jié)數(shù)組,我們就找到0d0a的位置,然后用utf-8.getstring(bytes,index,length)來按段兒來提取每一行的消息頭。
   6、為了防止對接受到的字節(jié)數(shù)組進(jìn)行內(nèi)存拷貝,我們應(yīng)該把接受到的字節(jié)數(shù)組放到一個鏈表里,因為我們是順序插入字節(jié),解析的時候也是順序訪問字節(jié)數(shù)組,所以我認(rèn)為這里應(yīng)該用鏈表,而且鏈表的API完全滿足消息解析的要求,如果構(gòu)建一個環(huán)形的字節(jié)數(shù)組,操作起來比鏈表復(fù)雜,而且性能應(yīng)該也不會比字節(jié)鏈表好。
   7、在字節(jié)鏈表上,我們只要找到對應(yīng)的包的開頭、結(jié)尾節(jié)點,然后我們就可以把這段兒鏈表賦值給包對象,然后包對象自己去把這段兒鏈表換算成一個字節(jié)數(shù)組,進(jìn)行相應(yīng)的處理,比如轉(zhuǎn)換成字符串,進(jìn)一步解析每行的header,但有的服務(wù)只解析出header就可以處理這個包,比如轉(zhuǎn)發(fā)給另一個服務(wù),那么body就不需要轉(zhuǎn)換成字節(jié)數(shù)組,更不用轉(zhuǎn)換成字符串,直接把屬于Body的那段兒字節(jié)鏈表(可以進(jìn)一步封裝成Stream)傳出去就行了。
   8、剛開始我在收到字節(jié)數(shù)組后要先把字節(jié)數(shù)組fill到字節(jié)鏈表里,這個過程會無謂的消耗一些性能,所以我又優(yōu)化了一下,把字節(jié)鏈表改成了字節(jié)數(shù)組鏈表,但改成字節(jié)數(shù)組鏈表后,遍歷起來很麻煩,有的鏈表節(jié)點上的字節(jié)數(shù)組有半截兒已經(jīng)解析給上個包了,下次解析要接著上次解析的地方去解析,所以每個字節(jié)數(shù)組節(jié)點還要保存一個有效數(shù)組段兒的開始位置和結(jié)束位置,比第一次的代碼更復(fù)雜了一些,但是性能要好于前者,
   9、還有就是在收到一個半截header或者半截body的情況下,下一次收到包解析的時候盡量避免回溯,比較好的算法是盡量遍歷一次就匹配出所有規(guī)則,DFA就是這樣,但得加更多的標(biāo)志位來保存解析狀態(tài)。
   10、在解析header的時候也避免先把字節(jié)數(shù)組鏈表轉(zhuǎn)換成字節(jié)數(shù)組,會造成字節(jié)數(shù)組拷貝,應(yīng)該一次字節(jié)數(shù)組鏈表的遍歷就直接解析出所有header,當(dāng)然可能會跨越多個字節(jié)數(shù)組節(jié)點,但比把多個字節(jié)數(shù)組節(jié)點合并成一個大的字節(jié)數(shù)組再解析header性能要好不少。

   下面來具體看下代碼
   BytesLine,表示header中的一行,因為消息頭不會出現(xiàn)中文,所以直接用ASCII編碼,除了header的第一行,消息頭都分為name,value部分,這里用String1和String2表示

Code

HttpMessage,這里表示一個抽象的Http消息,除了包含消息頭,消息體等屬性外,還負(fù)責(zé)初始化消息頭,解析消息體長度,確認(rèn)消息是Request,Response等功能。

Code

NET技術(shù)蛙蛙推薦:自己寫個IIS玩-協(xié)議解析篇,轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 《欢·爱》郭晓东| 媳妇的全盛时代| 舔了师兄十年| 国庆十点钟 电影| 播放凯登克罗斯演的全部影片| 黄瓜在线| 俗世乐土| 凤凰卫视节目表| 女神学生| 金沙滩秦腔剧情介绍| 风间由美电影影片| 绝顶五秒前| 搜狐网站官网| 神宫寺奈绪从早做到晚上| 北京卫视今天全部节目表| 建设工程监理规范最新版50319-2019| 打字说一句话二年级上册| 嗯啊主人| 欠条怎么写才具有法律效力| 大秦帝国第一部免费版| 小城故事多三观不正| 盲辉| 试看60秒做受小视频| 魔界 电影| 欠条怎么写才有法律效果| call me by your name电影| 北京卫视今晚节目表| 风之谷钢琴谱| 性感瑜伽| 冲天火| 保镖 电影在线观看 完整版| 大胆艺术| 抗日电影完整版| 巴黎宝贝| 小野寺律| 哥哥啊啊啊| 寻宝电影| cctv5+体育节目表| 我在碧桂园的1000天| 农民工野外一级毛片| 乔治克鲁尼电影作品|