一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

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

   這里不是說用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í)探索用。

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

   協(xié)議了解了,協(xié)議棧就好寫了,如果我們能一次讀取一個(gè)完整的包,那我們把整個(gè)包讀出來,解析成字符串,然后用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ò),或者線程掛起而造成無法接受剩下的包,當(dāng)然每次盡量多讀取一些字節(jié),讀取次數(shù)多也會(huì)降低性能,buffer可以設(shè)置的稍微大一些,這個(gè)可能要經(jīng)過具體平臺(tái)的測(cè)試才能確定最合適的值。這點(diǎn)有不同意見,說不要在剛收到異步讀取回調(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、按理說收到包后先放隊(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、按理說解析網(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)了性能,所以不考慮了。如果該方法能把解析出來的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í)的過程,像一些解析包的標(biāo)志位如分割消息頭和消息體的/r/n/r/n,分割多個(gè)消息頭的/r/n,其對(duì)應(yīng)的字節(jié)表示值是固定的,如0d0a0d0a,0d0a,我們直接對(duì)字節(jié)數(shù)組進(jìn)行解析就能區(qū)拆出來消息頭字節(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)來按段兒來提取每一行的消息頭。
   6、為了防止對(duì)接受到的字節(jié)數(shù)組進(jìn)行內(nèi)存拷貝,我們應(yīng)該把接受到的字節(jié)數(shù)組放到一個(gè)鏈表里,因?yàn)槲覀兪琼樞虿迦胱止?jié),解析的時(shí)候也是順序訪問字節(jié)數(shù)組,所以我認(rèn)為這里應(yīng)該用鏈表,而且鏈表的API完全滿足消息解析的要求,如果構(gòu)建一個(gè)環(huán)形的字節(jié)數(shù)組,操作起來比鏈表復(fù)雜,而且性能應(yīng)該也不會(huì)比字節(jié)鏈表好。
   7、在字節(jié)鏈表上,我們只要找到對(duì)應(yīng)的包的開頭、結(jié)尾節(jié)點(diǎn),然后我們就可以把這段兒鏈表賦值給包對(duì)象,然后包對(duì)象自己去把這段兒鏈表換算成一個(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è)過程會(huì)無謂的消耗一些性能,所以我又優(yōu)化了一下,把字節(jié)鏈表改成了字節(jié)數(shù)組鏈表,但改成字節(jié)數(shù)組鏈表后,遍歷起來很麻煩,有的鏈表節(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)志位來保存解析狀態(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性能要好不少。

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

Code

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

Code

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

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

主站蜘蛛池模板: 黄色在线免费看 | 日本一区二区高清免费不卡 | 色婷婷欧美 | 国产在线一区精品对白麻豆 | 美女扒开屁股给男人看无遮挡 | 国产亚洲精品97在线观看 | 国产原创视频在线 | 91久久香蕉国产线看 | 久久久久777777人人人视频 | 国产精品四虎在线观看免费 | 久久国产精品视频一区 | 成人午夜免费福利视频 | 国产三区二区 | 一区二区三区四区视频在线 | 亚洲一区二区三区免费 | 四虎影视在线永久免费观看 | 色哟网站| 狠狠色婷婷丁香六月 | 国产高清精品自在线看 | 婷婷亚洲激情 | 国产特黄一级一片免费 | 亚洲精品欧美 | 热综合一本伊人久久精品 | 国产成人综合精品一区 | 野战露脸在线视频国产 | 欧美特黄高清免费观看的 | 亚洲综合影院 | 天天偷窥网 | 国产精品福利资源在线 | 国产自在线观看 | 国产亚洲精彩视频 | 色哟哟在线视频 | 国产日韩一区二区三区在线播放 | 69视频在线观看 | 久久久久久久久影院 | 一级做a爰片性色毛片小说 一级做a爰片性色毛片新版的 | 免费小视频 | 中文字幕 国产 | 综合伊人 | 伊人宗合网 | 2021国产视频 |