有关[Http持久连接]的一切,卷给你看

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有关[Http持久连接]的一切,卷给你看相关的知识,希望对你有一定的参考价值。

上文中我的结论是:  HTTP Keep-Alive 是在应用层对TCP连接进行滑动续约复用, 如果客户端/服务器稳定续约,就成了名副其实的长连接
目前所有的Http网络库都默认开启了HTTP Keep-Alive,今天我们从底层TCP连接和排障角度撕碎HTTP持久连接。

“我只是一个写web程序的猿,我为什么要知道这么多😂😂😂”。

使用go语言倒腾一个httpServer/httpClient,粗略聊一聊go的使用风格。


使用go语言net/http包快速搭建httpserver,注入用于记录请求日志的Handler

package main

import (
 "fmt"
 "log"
 "net/http"
)

// IndexHandler记录请求的基本信息: 请关注r.RemoteAddr
func Index(w http.ResponseWriter, r *http.Request) 
 fmt.Println("receive a request from:", r.RemoteAddr, r.Header)
 w.Write([]byte("ok"))


// net/http 默认开启持久连接
func main()  
 fmt.Printf("Starting server at port 8081\\n")
 if err := http.ListenAndServe(":8081", http.HandlerFunc(Index)); err != nil 
  log.Fatal(err)
 
  1. ListenAndServe创建了默认的httpServer服务器,go通过首字母大小写来控制访问权限,如果首字母大写,则可以被外部包访问, 类比C#全局函数、静态函数。

func ListenAndServe(addr string, handler Handler) error 
 server := &ServerAddr: addr, Handler: handler
 return server.ListenAndServe()
  1. net/http服务器默认开启了Keep-Alive, 由Server的私有变量disableKeepAlives体现。

type  Server  struct 
  ...
  disableKeepAlives int32     // accessed atomically. 
  ...

使用者也可以手动关闭Keep-Alive, SetKeepAlivesEnabled()会修改私有变量disableKeepAlives的值

s := &http.Server
  Addr:           ":8081",
  Handler: http.HandlerFunc(Index),
  ReadTimeout:    10 * time.Second,
  WriteTimeout:   10 * time.Second,
  MaxHeaderBytes: 1 << 20,
 
 s.SetKeepAlivesEnabled(true)
 if err := s.ListenAndServe(); err != nil 
  log.Fatal(err)
 

以上也是go语言包的基本制作/使用风格。

  1. 请注意我在httpserver插入了IndexHander,记录httpclient的基本信息。
    这里有个知识点:如果httpclient建立新的TCP连接,系统会按照一定规则给你分配随机端口。

启动服务器程序,浏览器访问localhost:8081,

服务器会收到如下日志, 图中红圈处表明浏览器使用了系统随机的固定端口建立tcp连接。


使用net/http编写客户端:间隔1s向服务器发起HTTP请求

package main

import (
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "time"
)

func main() 
 client := &http.Client
  Timeout: 10 * time.Second,
 
 for 
  requestWithClose(client)
  time.Sleep(time.Second * 1)
 


func requestWithClose(client *http.Client) 

 resp, err := client.Get("http://127.0.0.1:8081")

 if err != nil 
  fmt.Printf("error occurred while fetching page, error: %s", err.Error())
  return
 
 defer resp.Body.Close()

 c, err := ioutil.ReadAll(resp.Body)
 if err != nil 
  log.Fatalf("Couldn't parse response body. %+v", err)
 

 fmt.Println(string(c))

服务器收到的请求日志如下:

图中红框显示httpclient使用固定端口61799发起了http请求,客户端/服务器维持了HTTP Keep-alive。

使用netstat -an | grep 127.0.0.1:8081可围观系统针对特定ip的TCP连接:客户端系统中针对 服务端也只建立了一个tcp连接,tcp连接的端口是61799,与上文呼应。

使用Wireshark查看localhost网卡发生的tcp连接

  • 可以看到每次http请求/响应之前均没有tcp三次握手

  • tcp每次发包后,对端需要回ACK确认包

反面教材-高能预警

go的net/http明确提出:

If the Body is not both read to EOF and closed, the Client's underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent "keep-alive" request.

也就是说:httpclient客户端在每次请求结束后,如果不读完body或者没有关闭body, 可能会导致Keep-alive失效,也会导致goroutine泄露。

//  下面的代码没有读完body,导致Keep-alive失效
func requestWithClose(client *http.Client) 
   resp, err := client.Get("http://127.0.0.1:8081")
   if err != nil 
    fmt.Printf("error occurred while fetching page, error: %s", err.Error())
    return
   
   defer resp.Body.Close()
   //_, err = ioutil.ReadAll(resp.Body)
   fmt.Println("ok")

此次服务端日志如下:上图红框显示客户端持续使用新的随机端口建立了TCP连接。

查看客户端系统建立的tcp连接:

Wireshark抓包结果:图中红框显示每次HTTP请求/响应 前后均发生了三次握手、四次挥手。

全文梳理

  1. 目前已知的httpclient、httpServer均默认开启keep-alive

  2. 禁用keep-alive或者keep-alive失效,会导致特定场景客户端频繁建立tcp连接, 可通过 netstat -an | grep ip 查看客户机上建立的tcp连接

  3. Wireshark抓包, 明确keep-alive和非Keep-alive的抓包效果


HTTP1.1 Keep-Alive到底算不算长连接?

宝藏好物gRPCurl

SignalR 开发到生产部署闭坑指南

SignalR在React/Go技术栈的实践

我是状态机, 一颗永远骚动的机器引擎

大揭秘| 我司项目组Gitlab Flow && DevOps流程

你怕是对MD5算法有误解

点个在看你最好看

仅代表此刻认知,文章永久更新地址,请移步原文!!

以上是关于有关[Http持久连接]的一切,卷给你看的主要内容,如果未能解决你的问题,请参考以下文章

持久片段和查看器

人生苦短,我用Python--分分钟下载知乎美图给你看

有关http可不可以在服务器端设置是不是支持keep-alive

片段中的Firebase数据不是持久的,会重新下载

给你看个宝贝:GitHub 最野的开源库,把你拿捏的死死的。。。

与 TCPServer 保持另一个持久的 TCP 连接