一、 項目背景
? ? ?一個物聯(lián)網項目,公司要求降低硬件成本,真可謂窮公司練技術,硬件資源有限制,最終技術選型使用單片機,因資源受限,硬件sdk中沒有現(xiàn)成的http可使用,因此,最終嵌軟同學直接在TCP socket基礎上,構造了Http請求頭,仿造http與物聯(lián)網平臺進行交互。
? ? 因此,有意思的事情發(fā)生了,嵌軟同學仿造http的過程如下:
1、初始化tcp client,然后構造http請求,通過tcp send發(fā)送http請求,接著recv等待對端的返回;
2、收到recv報文之后,讀取整個response報文,然后轉化為string,從string中匹配content-length字段,從而獲取response的body體大小。
看似一切正常,而且嵌軟同學之前也這么做過,為啥這次就不行了那,一切都是chunk(這里就不解釋啥是chunk了,不知的同學自行百度吧)搞的鬼,有的時候server會使用chunk方式進行數據返回,chunk模式下,是可以不存在content-length字段的(嚴格上來說是沒有content-length),因此這種情況下,按照上面的流程就跑不通了。
二、content-length和chunk是如何工作的?
? ? 這里百度了一些文章,說是chunk和gzip有關,但是百思不得其解,沒有縷出來兩者的關聯(lián)關系,實驗了一些demo,請求和返回頭里都含有gzip的壓縮方式,但是content-lenght字段還是存在,所以,索性認為沒有必然聯(lián)系。
? ? 再繼續(xù)百度了下,說是chunk和keep-alive相關,看到這我想是因為http1.1支持了連接服用,因此才支持了chunk,但是并不是說一旦開啟了keep-alive所有交互都是chunk模式,事實自己也寫了個小demo試了下,確實如此。
?繼續(xù)百度之旅,最后看到chunk和flush函數有關,正常我們server端在返回數據的時候,是使用write函數,而write函數并不是直接給用戶返回數據,而是先降數據寫回緩沖區(qū),直到連接close之前,會先將緩沖區(qū)的數據buffer發(fā)送過去,然后close連接。而flash函數則是手動將緩沖區(qū)的數據發(fā)送到客戶端,然后server端可以繼續(xù)將數據寫到緩沖區(qū),然后再調用flush函數,而還是之前的那一個連接(我想這也是之前百度到的一些文章說chunk和keepalive有關的原因吧),這樣收到的傳輸方式就是chunk的方式。而我們調用write函數,然后等到hander結束,close連接之前發(fā)送數據這個流程,也就是我們說的非chunk方式吧(除buff中數據超過buff大小的情況)。
三、demo驗證
? ? 這里采用golang代碼進行了過程驗證。
1、使用gin router寫了一個簡單的請求,如下:
????r :=gin.Default()
????r.GET("/ping", func(c *gin.Context) {
????????c.JSON(200, gin.H{
????????????"message":"pong",
? ? ????? })
})
r.Run()
請求結果如下所示:

2、修改flush發(fā)送數據方式,如下:
????r :=gin.Default()
????r.GET("/ping", func(c *gin.Context) {
????w := c.Writer
????flusher, ok := w.(http.Flusher)
? ? if !ok {
????????panic("expected http.ResponseWriter to be an http.Flusher")
????}
????fmt.Fprintf(w,string("hello chunk!"))
????flusher.Flush()
? ?})
r.Run()
結果如下圖:

四、總結
? ?1、通過理論+實驗來看,chunk和keepalive有關系,chunk是基于keekalive基礎實現(xiàn)的,但是他們倆者之間并不是必然的關系。
? ? 2、server端如果不想采取chunk方式回復數據,以免有些客戶端對chunk支持性不好,通過server端可以對傳輸方式進行控制,比如通過避免使用flush函數回復數據的方式。
? ? 3、至于其他語言例如java如何控制傳輸方式,后面我們再完善補充,多謝。