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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 目前所有的Http网络库都默认开启了HTTP Keep-Alive,今天我们从底层TCP连接和排障角度撕碎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 := &Server{Addr: 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连接。


e81839f78f1359e92c2f8595915c3911.png


使用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))
}


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


21bdc3432afa8ea2392f586dd809c29e.png


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


使用netstat -an | grep 127.0.0.1:8081可围观系统针对特定ip的TCP连接:


37cc2b00db94d9664ed8f25ff2b60f04.png


客户端系统中针对 服务端也只建立了一个tcp连接,tcp连接的端口是61799,与上文呼应。


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


82ad3ec4e93bd836922d6ea5b8272617.png


  • 可以看到每次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")
}


此次服务端日志如下:


7b91e1f9fd79423e586d6d46a7bd4f39.png


上图红框显示客户端持续使用新的随机端口建立了TCP连接。


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


e86461abba064a97d1578f3d81bd60f0.png


Wireshark抓包结果:


8832740335038b6217d37057be00a92c.png


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


全文梳理


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


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


  1. Wireshark抓包, 明确keep-alive和非Keep-alive的抓包效果
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
网络协议 Linux iOS开发
推荐:实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求
推荐:实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求
285 1
|
2月前
|
数据采集
Haskell爬虫:连接管理与HTTP请求性能
Haskell爬虫:连接管理与HTTP请求性能
|
3月前
|
移动开发 监控 网络协议
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
|
4月前
|
文字识别 前端开发 API
印刷文字识别操作报错合集之通过HTTPS连接到OCR服务的API时报错,该如何处理
在使用印刷文字识别(OCR)服务时,可能会遇到各种错误。例如:1.Java异常、2.配置文件错误、3.服务未开通、4.HTTP错误码、5.权限问题(403 Forbidden)、6.调用拒绝(Refused)、7.智能纠错问题、8.图片质量或格式问题,以下是一些常见错误及其可能的原因和解决方案的合集。
|
3月前
|
网络协议 Linux
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
|
3月前
|
网络协议 Python
python requests库如何使用http连接池降低延迟 keepalive复用连接
Python的`requests`库通过内置的连接池机制支持HTTP Keep-Alive特性,允许复用TCP连接以发送多个请求,减少连接开销。默认情况下,`requests`不显式禁用Keep-Alive,其行为取决于底层HTTP库(如urllib3)及服务器的支持。通过创建`Session`对象并自定义`HTTPAdapter`,可以调整连接池大小和重试策略,进一步优化连接复用。测试显示,使用`Session`和定制的`HTTPAdapter`比普通请求方法能显著减少连续请求间的时间消耗,体现了Keep-Alive的优势。
|
3月前
|
安全 Nacos 数据安全/隐私保护
【技术干货】破解Nacos安全隐患:连接用户名与密码明文传输!掌握HTTPS、JWT与OAuth2.0加密秘籍,打造坚不可摧的微服务注册与配置中心!从原理到实践,全方位解析如何构建安全防护体系,让您从此告别数据泄露风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其连接用户名和密码的明文传输成为安全隐患。本文探讨加密策略提升安全性。首先介绍明文传输风险,随后对比三种加密方案:HTTPS简化数据保护;JWT令牌减少凭证传输,适配分布式环境;OAuth2.0增强安全,支持多授权模式。每种方案各有千秋,开发者需根据具体需求选择最佳实践,确保服务安全稳定运行。
337 0
|
6月前
|
XML Java 数据库
【后台开发】TinyWebser学习笔记(3)HTTP连接与解析
【后台开发】TinyWebser学习笔记(3)HTTP连接与解析
167 4
|
6月前
|
Java
蓝易云 - HTTP的并发连接限制和连接线程池
这两个概念在网络编程中是相互关联的。如果并发连接数过多,而线程池的大小又不足以处理这些连接,服务器可能会变得不稳定,甚至崩溃。因此,合理地设置并发连接限制和线程池大小对于保持服务器的稳定性和高效性至关重要。
67 0
|
6月前
|
网络安全
socks5代理连接成功无法访问http协议的站点
无法通过SOCKS5代理访问HTTP网站可能由多个原因引起,如代理服务器不支持HTTP、配置错误、防火墙阻拦、连接问题、身份验证失败、浏览器设置不当或服务器被封锁。检查并解决这些因素,若问题持续,需深入排查或联系服务提供商。
下一篇
无影云桌面