TCP是一種流式協議
TCP是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。
流式協議的特點是什么?就像流水連續不斷那樣,消息之間沒有邊界。例如send了3條消息(這里的“消息”是指應用層的一個完整的協議包),分別是100字節、50字節、80字節,recv時可能收到的是230字節,就是說一次recv收到了3條消息,需要應用邏輯自己對recv到的數據進行分析,得出完整的消息。能一次recv到多個消息,也可能一次recv到一個半消息或半個消息,都是有可能的,這就是流式協議的特點。有的文章講的粘包也是這個概念。
消息分包
既然TCP是一種流式協議,需要應用層自己來分析出完整的消息,那有哪些方式來確定一個完整消息呢?這個就是應用層通訊協議設計的工作了。
先看看最常見的HTTP協議是如何來分包的。HTTP協議是一種文本協議(非二進制協議),用\r\n\r\n來分割消息頭和消息體,HTTP請求的消息頭中有Content-Length來告知消息體有多大,如果沒有該字段就表示無消息體,GET請求大多是這樣。HTTP響應的消息頭中,或者有Content-Length,或者有Transfer-Encoding: chunked告知以chunk模式分析消息體。
HTTP請求信息由3部分組成:
1、請求方法(GET/POST)、URI、協議/版本
2、請求頭(Request Header)
3、請求正文,請求正文和請求頭要有空行。這個空行必須存在,說明結束請求頭傳輸,開始傳輸正文請求。
HTTP請求
GET/HTTP/1.1 [請求頭] Accept:image/gif.image/jpeg,*/* Accept-Language:zh-cn Connection:Keep-Alive Host:localhost User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0) Accept-Encoding:gzip,deflate [請求正文] username=ring&password=1234
http響應格式
HTTP應答與HTTP請求相似,HTTP響應也由3個部分構成,分別是:
1、狀態行
2、響應頭(Response Header)
3、響應正文
HTTP響應1:Conetent-Length
HTTP/1.1 200 OK
SERVER: name
DATE: Fri, 22 Dec 2017 10:50:38 GMT
Content-Type: image/gif
Content-Length: 43
Last-Modified: Mon, 1 Dec 2017 13:23:42 GMT
Connection: keep-alive
Expires: Fri, 22 Dec 2017 10:50:38 GMT
Cache-Control: max-age=0
Accept-Ranges: bytes
GIF89a二進制數據
HTTP響應2:Transfer-Encoding
HTTP/1.1 200 OK
DATE: Fri, 22 Dec 2017 10:55:50 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Expires: Fri, 22 Dec 2017 10:55:50 GMT
Cache-Control: max-age=7200
Content-Encoding: gzip
91a
具體的數據
0
HTTP用\r\n\r\n來分割消息頭和消息體,這種用特定字符/字符串來分割或分包的方式,還有不少協議用到。例如FTP/SMTP/POP3都是用\n來作為一個命令結束的標志。這種消息分包的方式,需要應用層去掃描已recv到的數據,性能上還不夠高效,代碼不嚴謹的還容易被攻擊。在需要自定義協議的項目中,不少選擇用二進制協議,解析高效,安全性更好些。
最簡單的二進制協議分包方式是消息的頭4個字節表示消息的總長度。這種方式還需要對最大消息長度做個限制,例如64K或1024K大小,避免超大數據包對接收方緩沖區的破壞。更進一步的,可以加入簡單校驗方法。例如消息頭1個字節固定式0x2,消息的最后1個字節固定式0x3,消息總長度放在第2~5字節。這樣收到完整消息后,如果頭尾不是0x2和0x3,就直接異常處理。
協議設計
消息分包是協議設計的一個工作,協議設計的話題還不少,這里以HTTP協議為例,簡要的說說里面設計的點,自己設計的協議也可以對照着有選擇的使用,原理是共通的。
由消息頭+消息體組成:空行分割HTTP head和body,HTTP頭的每一行以\r\n結尾,空行就是\r\n\r\n
消息分包:如上所述,HTTP用Content-Length和Transfer-Encodeing來分包
消息壓縮:請求中有Accept-Encoding字段,響應中用Content-Encoding字段表明壓縮方式,一般采用gzip壓縮
消息加密:https (SSL: Secure Socket Layer)
消息ID:URL就是消息ID
響應的狀態碼:第一個數字定義了響應的類別。
1xx:指示信息--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操作
4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
5xx:服務器端錯誤--服務器未能實現合法的請求
協議版本號: HTTP/1.1中的1.1就是HTTP 1.1版本
長連接:請求中Connection: keep-alive表示希望服務器保持連接,減少TCP連接的開銷
字符集: Content-Type字段表明了字符集,例如: Content-Type: text/html; charset=gb2312
字符轉義:URL中的參數需要做URL轉義處理,例如http://xx.com/do?name=t%2F%3F%23%3Daa表示name為t/?#=aa
在我們自己設計協議時,可以有選擇的使用,如果消息比較大,可以采用支持壓縮;如果要兼容多個版本的協議,那版本號必不可少。如果采用二進制協議,字符集和字符穿衣的用處不大。