日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

浅谈基于TCP和UDP的协议设计

發(fā)布時(shí)間:2024/7/23 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈基于TCP和UDP的协议设计 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

From:http://blog.sina.com.cn/s/blog_48d4cf2d0101859x.html

一個(gè)基于TCP/WebSockets的超級(jí)精簡的長連接消息協(xié)議:https://studygolang.com/articles/10506

github 上 一個(gè)簡單的消息協(xié)議:https://github.com/acrazing/stmp

google protobuf:https://github.com/google/protobuf

google ProtoBuf開發(fā)者指南:http://blog.csdn.net/henryzhang2009/article/details/40508413

知乎關(guān)于QQ使用 TCP 和 UDP 的討論:https://www.zhihu.com/question/20292749

網(wǎng)絡(luò)協(xié)議設(shè)計(jì):https://www.cnblogs.com/youxin/p/4050394.html

簡書 游戲開發(fā)—協(xié)議設(shè)計(jì):https://www.jianshu.com/p/c5cc603e60a3

WinSock網(wǎng)絡(luò)編程經(jīng)絡(luò)_源碼:http://download.csdn.net/download/geoff08zhang/4571358


----------------------------------------------------------------------------------------------------------------


通信協(xié)議:就是通信時(shí)所遵守的規(guī)則,只有雙方按照這個(gè)規(guī)則“說話”,對(duì)方才能理解或?yàn)橹?wù)。



1. 基于TCP的協(xié)議設(shè)計(jì)


TCP是基于流的協(xié)議。但大部分網(wǎng)絡(luò)應(yīng)用一般會(huì)有個(gè)更小的處理單元,我們稱之為幀(FRAME)。

是否分幀
大部分網(wǎng)絡(luò)應(yīng)用是需要分幀的。舉IM為例,用戶登錄是一個(gè)幀,用戶發(fā)送文本信息是一個(gè)幀。少部分應(yīng)用可以不需要分幀,比如:echo服務(wù)器,接收到什么直接回復(fù)即可;轉(zhuǎn)發(fā)服務(wù)器,同樣是接收到數(shù)據(jù)直接轉(zhuǎn)給目標(biāo)機(jī)器;更常見的情況是一個(gè)TCP連接只發(fā)送/處理一個(gè)請(qǐng)求之后就直接關(guān)閉,這種也就沒必要分幀了。考慮到除了學(xué)習(xí)網(wǎng)絡(luò)編程,沒人做echo server。所以只要服務(wù)端不是一次連接只處理一個(gè)請(qǐng)求,或者純轉(zhuǎn)發(fā),就應(yīng)該采用分幀的設(shè)計(jì)。


如何分幀?

注意:幀是業(yè)務(wù)處理的單元,是具體應(yīng)用Care的,但這不關(guān)TCP的事情!初學(xué)者往往認(rèn)為tcp這端 write一次,tcp那端就會(huì)read一次,然后驚呼“粘包”、“丟包”,其實(shí)這都是程序處理不當(dāng)。在這邊推薦一本書籍《TCP/IP協(xié)議詳解 卷1》,挺薄的,看完可以減少很多對(duì)TCP的錯(cuò)誤認(rèn)識(shí)。實(shí)際上發(fā)送方發(fā)送一幀,接收方可能要N次才能讀取完成,而且可能同時(shí)讀到下幀的數(shù)據(jù)。那要怎么在接收方把一幀數(shù)據(jù)不多不少的讀取出來呢?

常用做法有兩個(gè)基于長度基于終結(jié)符(Delimiter)

基于長度:就是在幀前先發(fā)送幀的長度,一般用固定長度的字節(jié)來發(fā)送此長度,比如2個(gè)字節(jié)(最大幀長不能大于65535),4個(gè)字節(jié)。(ps:我也見過使用可變長度的字節(jié)來發(fā)送此長度,比如netty中的ProtobufVarint32FrameDecoder,看代碼那是相當(dāng)?shù)牡疤?#xff0c;我覺得完全是折騰自己,強(qiáng)烈不推薦。)使用基于長度的分幀方式,接受方處理流程一般是這樣:“讀取固定長度的字節(jié) -> 解析出幀長 -> 讀取幀長字節(jié) -> 處理幀”。

基于終結(jié)符(Delimiter):最典型的應(yīng)用就是HTTP協(xié)議了,使用/r/n/r/n作為終結(jié)符。使用基于終結(jié)符的分幀方式,接收方的處理流程一般是這樣:“讀數(shù)據(jù) -> 在讀取的數(shù)據(jù)中定位終結(jié)符 -> 沒找到,將數(shù)據(jù)緩存 -> 繼續(xù)讀數(shù)據(jù) -> 定位終結(jié)符 -> 找到終結(jié)符,將終結(jié)符之前的數(shù)據(jù)作為一幀進(jìn)行處理”。

使用終結(jié)符的方式務(wù)必要考慮轉(zhuǎn)義問題,不然在幀的數(shù)據(jù)中出現(xiàn)終結(jié)符,樂子就大了。

注意不管采用哪種方式,在開發(fā)的時(shí)候都需要考慮最大幀長的問題。不然如果對(duì)方說要發(fā)送4G長度的幀(惡意or程序錯(cuò)誤),真的去new 4G字節(jié)的緩存;或者對(duì)方一直發(fā)送數(shù)據(jù),沒有終結(jié)符。都可能造成程序內(nèi)存耗盡。

一般來說,基于長度的分幀方式。開發(fā)更簡單,程序執(zhí)行效率也更高,使用更廣泛些。基于終結(jié)符也不是一無是處:可讀性更好,容易模擬和測(cè)試(如用telnet)。下面重點(diǎn)討論基于長度的分幀方式。

基于長度的的幀設(shè)計(jì)(length based frame design)
一般來說,我們會(huì)將幀分為幀頭(frame header,一般是固定長度)和幀體(frame body,一般是可變長度,也有固定長度的)。如上所述,最簡單的幀頭只要一個(gè)字段——幀長。但在實(shí)際應(yīng)用中,一個(gè)典型的幀頭可能還有以下字段:
a)消息類型(message type):在一個(gè)網(wǎng)絡(luò)應(yīng)用中,往往有多種類型的幀。比如對(duì)于IM,有登陸/登出/發(fā)送消息/……。接收方需要根據(jù)幀頭的消息類型字段,解碼出不同種類的消息,交給相應(yīng)處理模塊進(jìn)行處理。也就是幀的結(jié)構(gòu)是Length-Type-Message,Length-Type可以視為幀頭,Message是幀體。消息類型一般也是使用固定長度,比如Length 4個(gè)字節(jié),Type 4個(gè)字節(jié),那么幀頭的長度就是8個(gè)字節(jié)。接收方處理流程:“讀幀頭長度字節(jié)數(shù)據(jù) - 解碼幀頭獲得長度和消息類型 - 讀幀體長度字節(jié)數(shù)據(jù) - 根據(jù)消息類型解碼消息 - 處理消息”。Length-Type-Message結(jié)構(gòu)的幀設(shè)計(jì)是使用最廣泛的,普適性最好也最精簡的設(shè)計(jì)。
b)請(qǐng)求序列號(hào)(serials):這個(gè)不是必選項(xiàng),但我覺得對(duì)于非echo式的服務(wù)(echo式的服務(wù):總是客戶端發(fā)送請(qǐng)求-服務(wù)端針對(duì)該請(qǐng)求應(yīng)答,應(yīng)答保證嚴(yán)格按照請(qǐng)求順序),加上這個(gè)字段肯定不后悔。這樣對(duì)于亂序(如果有消息隊(duì)列后臺(tái)線程池,很正常)的執(zhí)行結(jié)果,才能夠和請(qǐng)求對(duì)上號(hào),從而做出正確的處理。一般來說,高性能的服務(wù)端要保證響應(yīng)的嚴(yán)格有序,是比較麻煩和影響性能的。
c)版本號(hào)(version):很多人這么用,但我覺得大部分情況下這不是個(gè)好主意。幀頭應(yīng)該放大部分/全部幀都需要的字段。而版本號(hào)可能只有少數(shù)包如登錄會(huì)用到,所以放到登錄包體里可能更合適。單獨(dú)維護(hù)每個(gè)協(xié)議的版本工作量會(huì)比較大,開發(fā)起來會(huì)比較繁瑣易錯(cuò)。至于擔(dān)心解碼失敗,更好的方式是采用類似Protobuf這種可以向下兼容的編解碼方案。
注意:在幀頭設(shè)計(jì)時(shí)應(yīng)該要盡可能的精簡和通用,因?yàn)閹^長度是每個(gè)幀都需要的額外開銷。如果某個(gè)字段(如序列號(hào))只有少數(shù)幀會(huì)使用到,完全可以放在幀體里去。反之,如果某個(gè)字段大部分包都有,卻不定義在包頭,會(huì)導(dǎo)致難以統(tǒng)一處理,增加開發(fā)工作量。這些需要根據(jù)具體業(yè)務(wù)需求來進(jìn)行權(quán)衡,沒有統(tǒng)一的答案。舉個(gè)例子,Length-Type-Message結(jié)構(gòu)適用于大部分情況,但如果業(yè)務(wù)要求每個(gè)幀都需要表明操作者,在幀頭增加UID字段變成Length-Type-UID-Message,程序的開發(fā)會(huì)更簡單。
幀體的設(shè)計(jì)
幀體就是字段的集合,舉個(gè)例子,登錄幀體包含用戶名、密碼這兩個(gè)字段(只是舉例,現(xiàn)實(shí)的登錄包往往復(fù)雜得多)。在幀體設(shè)計(jì)上,大家往往也是八仙過海各顯神通。比如基于XML、json,基于字段Pos(舉登錄包為例,就先寫/讀用戶名,再寫/讀密碼。這種方式不是太好,很難向下兼容:比如登錄包需要在用戶名和密碼間加一個(gè)用戶狀態(tài),如果服務(wù)端/客戶端沒有同步升級(jí),就會(huì)斯巴達(dá))。我甚至見過狂野得離譜的直接使用C struct的,這種腦殘到爆:兼容性渣不說,類對(duì)齊(可以用pragma pack避免不一致)、byte order、機(jī)器字長都會(huì)造成麻煩。

比較推薦的做法:使用 Google Protobuf ,如果要可讀性好,json 相比 XML 更省帶寬。


2. 基于UDP的協(xié)議設(shè)計(jì)


一般來說,UDP的服務(wù)器要比TCP簡單得多。而且udp本來就是基于數(shù)據(jù)包的協(xié)議。write/read是可以一一對(duì)應(yīng)的(不考慮丟包),所以不需要有長度字段/終結(jié)符。 但是要注意:為了避免丟包率過高,udp包的長度一般不應(yīng)該大于1500字節(jié)(大概,為了安全起見,我一般保證小于1K),如果數(shù)據(jù)量較大,就需要分包了,這是比TCP麻煩的地方。
典型的UDP的協(xié)議設(shè)計(jì)就是:Type-Message。Type長度固定,用于說明消息類型;Message是消息體,和tcp的幀體設(shè)計(jì)同樣即可。




------------------------------------------------------------------------------------------------------------------

總結(jié)

以上是生活随笔為你收集整理的浅谈基于TCP和UDP的协议设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。