APP開發教程之HTTP和WebSocket協議


APP開發教程之HTTP和WebSocket協議


在做手機app開發的時候,那天和boss聊天,不經意間提到了Meteor,然後聊到了WebSocket,然後就有了以下對話,不得不說,看問題的方式不同,看到的東西也會大不相同。

A:Meteor是一個很新的開發框架,我覺得它設計得十分巧妙。

B:怎麼個巧妙之處?

A:它的前後端全部使用JS,做到了真正的前後端統一;前端瀏覽器裏存有一份後臺開放出來的數據庫的拷貝,快;使用WebSocket協議來做數據傳輸協議,來同步前後端的數據庫,實現了真正的實時同步。

B:哦?WebSocket是什麼東西?真實時?那底層是不是還是輪訓?和HTTP的長連接有什麼不同?

A:(開始心虛)它是一個新的基於TCP的應用層協議,只需要一次連接,以後的數據不需要重新建立連接,可以直接發送,它是基於TCP的,屬於和HTTP相同的地位(呃,開始胡謅了),底層不是輪訓,和長連接的區別……這個就不清楚了。

B:它的傳輸過程大致是什麼樣子的呢?

A:首先握手連接(又是胡謅),好像可以基於HTTP建立連接(之前用過Socket.io,即興胡謅),建立了連接之後就可以傳輸數據了,還包括斷掉之後重連等機制。

B:看起來和HTTP長連接做的事情差不多嘛,好像就是一種基於HTTP和Socket的協議啊。

A:呃……(我還是回去看看書吧)

有時候看事情確實太流於表面,瞭解到了每個事物的大致輪廓,但不求甚解,和朋友聊天說出來也鮮有人會刨根問底,導致了很多基礎知識並不牢靠,於是回來大致把HTTP和WebSocket協議的RFC文檔(RFC2616 和 RFC6455),剛好對HTTP的傳輸過程一直有點模糊,這裏把兩個協議的異同總結一下。

協議基礎

仔細去看這兩個協議,其實都非常簡單,但任何一個事情想做到完美都會慢慢地變得異常複雜,各種細節。這裏只會簡單地描述兩個協議的結構,並不會深入到很深的細節之處,對於理解http已經足夠了。

HTTP

HTTP的地址格式如下:

http_URL = "http:""//" host [ ":" port ] [ abs_path [ "?" query ]] 協議和host不分大小寫 HTTP消息

一個HTTP消息可能是request或者response消息,兩種類型的消息都是由開始行(start-line),零個或多個header域,一個表示header域結束的空行(也就是,一個以CRLF爲前綴的空行),一個可能爲空的消息主體(message-body)。一個合格的HTTP客戶端不應該在消息頭或者尾添加多餘的CRLF,服務端也會忽略這些字符。

header的值不包括任何前導或後續的LWS(線性空白),線性空白可能會出現在域值(filed-value)的第一個非空白字符之前或最後一個非空白字符之後。前導或後續的LWS可能會被移除而不會改變域值的語意。任何出現在filed-content之間的LWS可能會被一個SP(空格)代替。header域的順序不重要,但建議把常用的header放在前邊(協議裏這麼說的)。

Request消息

RFC2616中這樣定義HTTP Request 消息:

Request = Request-Line*(( general-header | request-header(跟本次請求相關的一些header) | entity-header ) CRLF)(跟本次請求相關的一些header) CRLF [ message-body ]

一個HTTP的request消息以一個請求行開始,從第二行開始是header,接下來是一個空行,表示header結束,最後是消息體。

請求行的定義如下:

//請求行的定義 Request-Line = Method SP Request-URL SP HTTP-Version CRLF //方法的定義Method = "OPTIONS" | "GET" | "HEAD" |"POST" |"PUT" |"DELETE" |"TRACE" |"CONNECT" | extension-method //資源地址的定義 Request-URI ="*" | absoluteURI | abs_path | authotity(CONNECT)這5種類別的定義,意思就是,返回碼的第一位要嚴格按照文檔中所述的來,其他的隨便定義。

Request消息中使用的header可以是general-header或者request-header,request-header(後邊會講解)。其中有一個比較特殊的就是Host,Host會與reuqest Uri一起來作爲Request消息的接收者判斷請求資源的條件,方法如下:

如果Request-URI是絕對地址(absoluteURI),這時請求裏的主機存在於Request-URI裏。任何出現在請求裏Host頭域值應當被忽略。

假如Request-URI不是絕對地址(absoluteURI),並且請求包括一個Host頭域,則主機由該Host頭域值決定。

假如由規則1或規則2定義的主機是一個無效的主機,則應當以一個400(錯誤請求)錯誤消息返回。

Response消息

響應消息跟請求消息幾乎一模一樣,定義如下:

Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ]

可以看到,除了header不使用request-header之外,只有第一行不同,響應消息的第一行是狀態行,其中就包含大名鼎鼎的返回碼

Status-Line的內容首先是協議的版本號,然後跟着返回碼,最後是解釋的內容,它們之間各有一個空格分隔,行的末尾以一個回車換行符作爲結束。定義如下:

Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF 返回碼

返回碼是一個3位數,第一位定義的返回碼的類別,總共有5個類別,它們是:

- 1xx: Informational - Request received, continuing process - 2xx: Success - The action was successfully received, understood, and accepted - 3xx: Redirection - Further action must be taken in order to complete the request - 4xx: Client Error - The request contains bad syntax or cannot be fulfilled - 5xx: Server Error - The server failed to fulfill an apparently valid request

RFC2616中接着又給出了一系列返回碼的擴展,這些都是我們平時會用到的,但是那些只是示例,HTTP1.1不強制通信各方遵守這些擴展的返回碼,通信各方在返回碼的實現上只需要遵守以上邊定義的這5種類別的定義,意思就是,返回碼的第一位要嚴格按照文檔中所述的來,其他的隨便定義。

任何人接收到一個不認識的返回碼xyz,都可以把它當做x00來對待。對於不認識的返回碼的響應消息,不可以緩存。

Header

RFC2616中定義了4種header類型,在通信各方都認可的情況下,請求頭可以被擴展的(可信的擴展只能等到協議的版本更新),如果接收者收到了一個不認識的請求頭,這個頭將會被當做實體頭。4種頭類型如下:

通用頭(General Header Fields):可用於request,也可用於response的頭,但不可作爲實體頭,只能作爲消息的頭。

general-header = Cache-Control ; Section 14.9| Connection ; Section 14.10| Date ; Section 14.18| Pragma ; Section 14.32| Trailer ; Section 14.40| Transfer-Encoding ; Section 14.41| Upgrade ; Section 14.42| Via ; Section 14.45| Warning ; Section 14.46

請求頭(Request Header Fields):被請求發起端用來改變請求行爲的頭。

request-header = Accept ; Section 14.1| Accept-Charset ; Section 14.2| Accept-Encoding ; Section 14.3| Accept-Language ; Section 14.4| Authorization ; Section 14.8| Expect ; Section 14.20| From ; Section 14.22| Host ; Section 14.23| If-Match ; Section 14.24| If-Modified-Since ; Section 14.25| If-None-Match ; Section 14.26| If-Range ; Section 14.27| If-Unmodified-Since ; Section 14.28| Max-Forwards ; Section 14.31| Proxy-Authorization ; Section 14.34| Range ; Section 14.35| Referer ; Section 14.3| TE ; Section 14.39| User-Agent ; Section 14.43

響應頭(Response Header Fields):被服務器用來對資源進行進一步的說明。

response-header = Accept-Ranges ; Section 14.5 | Age ; Section 14.6 | ETag ; Section 14.19 | Location ; Section 14.30 | Proxy-Authenticate ; Section 14.33 | Retry-After ; Section 14.37 | Server ; Section 14.38 | Vary ; Section 14.44 | WWW-Authenticate ; Section 14.47

實體頭(Entity Header Fields):如果消息帶有消息體,實體頭用來作爲元信息;如果沒有消息體,就是爲了描述請求的資源的信息。

entity-header = Allow ; Section 14.7 | Content-Encoding ; Section 14.11 | Content-Language ; Section 14.12 | Content-Length ; Section 14.13 | Content-Location ; Section 14.14 | Content-MD5 ; Section 14.15 | Content-Range ; Section 14.16 | Content-Type ; Section 14.17 | Expires ; Section 14.21 | Last-Modified ; Section 14.29 | extension-header 消息體(Message Body)和實體主體(Entity Body)

如果有Transfer-Encoding頭,那麼消息體解碼完了就是實體主體,如果沒有Transfer-Encoding頭,消息體就是實體主體。

message-body = entity-body | <entity-bodyencodedasperTransfer-Encoding>

在request消息中,消息頭中含有Content-Length或者Transfer-Encoding,標識會有一個消息體跟在後邊。如果請求的方法不應該含有消息體(如OPTION),那麼request消息一定不能含有消息體,即使客戶端發送過去,服務器也不會讀取消息體。

在response消息中,是否存在消息體由請求方法和返回碼來共同決定。像1xx,204,304不會帶有消息體。

消息體的長度

消息體長度的確定有一下幾個規則,它們順序執行:

所有不應該返回內容的Response消息都不應該帶有任何的消息體,消息會在第一個空行就被認爲是終止了。

如果消息頭含有Transfer-Encoding,且它的值不是identity,那麼消息體的長度會使用chunked方式解碼來確定,直到連接終止。

如果消息頭中有Content-Length,那麼它就代表了entity-lengthtransfer-length。如果同時含有Transfer-Encoding,則entity-lengthtransfer-length可能不會相等,那麼Content-Length會被忽略。

如果消息的媒體類型是multipart/byteranges,並且transfer-length也沒有指定,那麼傳輸長度由這個媒體自己定義。通常是收發雙發定義好了格式, HTTP1.1客戶端請求裏如果出現Range頭域並且帶有多個字節範圍(byte-range)指示符,這就意味着客戶端能解析multipart/byteranges響應。

如果是Response消息,也可以由服務器來斷開連接,作爲消息體結束。

從消息體中得到實體主體,它的類型由兩個header來定義,Content-TypeContent-Encoding(通常用來做壓縮)。如果有實體主體,則必須有Content-Type,如果沒有,接收方就需要猜測,猜不出來就是用application/octet-stream

HTTP連接

HTTP1.1的連接默認使用持續連接(persistent connection),持續連接指的是,有時是客戶端會需要在短時間內向服務端請求大量的相關的資源,如果不是持續連接,那麼每個資源都要建立一個新的連接,HTTP底層使用的是TCP,那麼每次都要使用三次握手建立TCP連接,將造成極大的資源浪費。

持續連接可以帶來很多的好處:

使用更少的TCP連接,對通信各方的壓力更小。

可以使用管道(pipeline)來傳輸信息,這樣請求方不需要等待結果就可以發送下一條信息,對於單個的TCP的使用更充分。

流量更小

順序請求的延時更小。

不需要重新建立TCP連接就可以傳送error,關閉連接等信息。

HTTP1.1的服務器使用TCP的流量控制來控制HTTP的流量,HTTP1.1的客戶端在收到服務器連接中發過來的error信息,就要馬上關閉此鏈接。關於HTTP連接還有很多細節,之後再詳述。

WebSocket

只從RFC發佈的時間看來,WebSocket要晚近很多,HTTP 1.1是1999年,WebSocket則是12年之後了。WebSocket協議的開篇就說,本協議的目的是爲了解決基於瀏覽器的程序需要拉取資源時必鬚髮起多個HTTP請求和長時間的輪訓的問題……而創建的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章