天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

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

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

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

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

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

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

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

Code

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

Code

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

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

主站蜘蛛池模板: 世界第一黄暴漫画家 | 亚洲精品不卡在线 | wwwxxc| 国产综合91 | 免费成年人在线视频 | 国产1000部成人免费视频 | 一二三四免费中文在线1 | 亚洲精品在线网址 | 欧美日韩永久久一区二区三区 | 亚洲欧洲日产国码中学 | 人人射人人爱 | 小莹的性荡生活 | 樱桃视频影院在线播放 | 欧美一区二区三区播放 | 国产精品综合AV一区二区国产馆 | 国产AV国片精品无套内谢无码 | 5G在线观看免费年龄确认 | 97视频精品 | 婷婷综合久久狠狠色 | 国产国拍亚洲精品永久软件 | 国产乱妇乱子在线播视频播放网站 | 亚洲 欧美 日本 国产 高清 | 91香蕉福利一区二区三区 | 日韩午夜欧美精品一二三四区 | 久久久精品久久久久久 | 亚洲色欲色欲无码AV | 日产日韩亚洲欧美综合搜索 | 少妇高潮惨叫久久久久久电影 | 风月宝鉴之淫乱英雄传 电影 | 国产东北男同志videos网站 | 国产高清美女一级a毛片久久w | 色一伦一情一区二区三区 | 午夜福利32集云播 | 成人免费在线观看 | 亚洲欧美国产综合在线一区 | 一道本av免费不卡播放 | 久久婷婷国产五月综合色啪最新 | 四虎永久在线精品国产 | 亚洲日韩KKK444KKK聚色 | 精品视频在线观看视频免费视频 | 日本美女毛茸茸 |