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


    上文中我的结论是: 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连接:

    啰嗦一下: 上图显示2条记录,是由于客户端/服务器在一台机器上,netstat显示了双方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客户端在每次请求结束后,如果resp的body不为空,而客户端不读完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的抓包效果

    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15634909.html

    欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化
    上海鲜花港 - 郁金香
  • 相关阅读:
    win10磁盘碎片整理
    Windows10系统一键结束所有运行程序
    win10关闭后台应用程序进程的方法
    第一章 进化的分子基础
    xshell分隔符及全路径提示
    GEOquery
    Gviz
    用R包来下载sra数据
    Analyzing Microarray Data with R
    IRanges package
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15634909.html
Copyright © 2020-2023  润新知