上文中我的结论是: HTTP Keep-Alive 是在应用层对TCP连接进行滑动续约复用, 如果客户端/服务器稳定续约,就成了名副其实的长连接。



目前所有的Http网络库都默认开启了HTTP Keep-Alive,今天我们从底层TCP连接和排障角度撕碎HTTP持久连接。

使用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连接,系统会按照一定规则给你分配随机端口。

go run main.go 启动之后,浏览器访问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失效。

//  下面的代码没有读完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的抓包效果

有关[Http持久连接]的一切,撕碎给你看的更多相关文章

  1. asp.net signalR 专题—— 第二篇 对PersistentConnection持久连接的快速讲解

    上一篇我们快速的搭建了一个小案例,但是并没有对其中的方法进行介绍,这一篇我来逐一解析下. 一:从override的那些方法说起 不管怎么样,我们先上代码,如下: public class MyConn ...

  2. LVS持久连接

    LVS持久连接 源地址HASH ipvs的连接模板 可以通过ipvsadm -L -c 持久连接持久客户端连接 PCC:在固定时间内将来自于同一个客户端发往VIP的所有请求统统定向至同一个RS0表示所 ...

  3. php开发客服系统(持久连接+轮询+反向ajax 转载 http://www.tuicool.com/articles/2mU7v2R)

    php开发客服系统( 下载源码 ) 用户端(可直接给客户发送消息) 客服端(点击用户名.即可给该用户回复消息) 讲两种实现方式: 一:iframe + 服务器推技术comet(反向ajax,即服务器向 ...

  4. WebSocket 是什么原理?为什么可以实现持久连接?

    https://www.zhihu.com/question/20215561   作者:Ovear链接:https://www.zhihu.com/question/20215561/answer/ ...

  5. HTTP - 持久连接

    Web 客户端经常会打开到同一个站点的连接.比如,一个 Web 页面上的大部分内嵌图片通常都是来自同一个 Web 站点,而且相当一部分指向其他对象的超链接通常都指向同一个站点.因此,初始化了对某服务器 ...

  6. ASP.NET SignalR2持久连接层解析

    越是到年底越是感觉浑身无力,看着啥也不想动,只期盼着年终奖的到来以此来给自己打一针强心剂.估摸着大多数人都跟我一样犯着这样浑身无力的病,感觉今年算是没挣到啥钱,但是话也不能这么说,搞得好像去年挣到钱了 ...

  7. php开发客服系统(持久连接+轮询+反向ajax)

    欢迎在php严程序 - php教程学习AJAX教程, 本节课讲解:php开发客服系统(持久连接+轮询+反向ajax) php开发客服系统(下载源码) 用户端(可直接给客户发送消息)客服端(点击用户名. ...

  8. HTTP实现长连接(TTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持Connection: keep-alive)

    HTTP实现长连接 HTTP是无状态的 也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接.如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web ...

  9. HTTP协议----URI,URL,持久连接,管道与Cookie

    URI与URL有什么不同呢? URI:Universal Resource Identifier统一资源标志符 URL:Universal Resource Locator统一资源定位器 URI是用来 ...

随机推荐

  1. go-zero 实战之 blog 系统

    go-zero 实战项目:blog 本文以 blog 的网站后台为例,着重介绍一下如何使用 go-zero 开发 blog 的用户模块. 本文涉及的所有资料都已上传 github 仓库 kougazh ...

  2. LeetCode:回溯算法

    回溯算法 这部分主要是学习了 labuladong 公众号中对于回溯算法的讲解 刷了些 leetcode 题,在此做一些记录,不然没几天就忘光光了 总结 概述 回溯是 DFS 中的一种技巧.回溯法采用 ...

  3. 用cmd命令行创建vue项目模板

    1.进入cmd命令行 输入存放项目的位置 2.通过vue create 项目名称 创建项目 3.选择Manually select features 4.通过空格选中第1.2.5.6.7.去掉8 4. ...

  4. 树链剖分好(du)题(liu)选做

    1.luogu P4315 月下"毛景树" 题目链接 前言: 这大概是本蒟蒻A掉的题里面码量最大的一道题了.我自认为码风比较紧凑,但还是写了175行. 从下午2点多调到晚上8点.中 ...

  5. 六个好习惯让你的PCB设计更优

    PCB layout工程师每天对着板子成千上万条走线,各种各样的封装,重复着拉线的工作,也许很多人会觉得是很枯燥无聊的工作内容.看似软件操作搬运工,其实设计人员在过程中要在各种设计规则之间做取舍,兼顾 ...

  6. Abp Vnext Vue3 的版本实现

    基于ABP Vnext的二次开发,前端 vue3.0,Typescript,Ant Design Vue ,Vben Admin 的后台管理框架. 技术点 Net Core5.0 ABP Vnext ...

  7. Jenkins执行 remote SSH 命令

    1.安装 SSH Pipeline Steps 插件 2.在凭据中添加remote server凭据,如下 3.Pipeline编写: def GetRemoteServer(ip){ def rem ...

  8. elasticsearch在postman中创建复杂索引

    body,所选类型为raw和JSON,写的代码为 { "settings":{ "number_of_shards":1, "number_of_re ...

  9. logstash写入kakfa数据丢失的问题

    metricbeat采集系统指标,发送到logstash,再写入kafka,发现kafka中的数据不完整,只有某一个指标, 查找原因发现是logstash配置编码问题,如下: input { beat ...

  10. ubuntu图标

    linux桌面图标跟windows系统一样,只是个快捷方式,在/usr/share/applications/目录下面有应用程序的启动图标,可以直接复制到桌面,如果这个文件夹下没有的话,可以自己新建一 ...