上文中我的结论是: 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的抓包效果

最新文章

  1. js给DropdownList赋值
  2. 多线程中使用CheckForIllegalCrossThreadCalls = false访问窗口-转
  3. sublime text3 快捷键设置
  4. html 特殊字符 fmt table A
  5. ios--socket
  6. php_curl扩展在WINDOWS2003上如何添加
  7. 后缀自动机(SAM)模板
  8. delphi对ini文件的操作(转载 万一)
  9. linux分区工具fdisk的使用
  10. PHP学习资源
  11. 谈谈事件对象-event
  12. mysqldump备份还原mysql
  13. C语言实现ifconfig获取网卡接收和发送流量统计
  14. Java与.net的选择和比较
  15. OpenUDID 和 IDFA 比较
  16. ISP PIPLINE (七) gamma
  17. linux /dev/mapper/centos-root目录莫名其妙被占满
  18. python-day18--匿名函数
  19. 纯css3实现的超炫checkbox复选框和radio单选框
  20. Could not calculate build plan: Plugin org.apache.maven.plugins:maven-war-plugin:2.3

热门文章

  1. 【UE4 C++ 基础知识】&lt;8&gt; Delegate 委托
  2. LiveVideoStackCon2021 北京站专访:从上云到创新,视频云的新技术、新场景
  3. Java:包装类小记
  4. [对对子队]会议记录4.10(Scrum Meeting 1)
  5. BUAA SE 软件案例分析-CSDN
  6. HZOI帝国2019欢乐时刻
  7. 为什么用于开关电源的开关管一般用MOS管而不是三极管
  8. Nginx(二):Nginx的四层(L4)和七层(L7)负载均衡
  9. Github图床设置
  10. go闭包使用