I'll show you everything about [Http persistent connection]

My conclusion above is that HTTP keep alive is a sliding renewal reuse of TCP connections at the application layer. If the client / server renews the contract stably, it will become a real long connection.

At present, all HTTP network libraries enable HTTP keep alive by default. Today, we tear up HTTP persistent connections from the perspective of underlying TCP connections and troubleshooting.

"I'm just an ape who writes web programs. Why should I know so much?".

Use the go language to toss an httpServer/httpClient and talk about the usage style of go.

Use go language net/http package to quickly build httpserver and inject Handler for recording request log

package main

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

// IndexHandler records the basic information of the request: please pay attention to 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 enables persistent connections by default
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 creates the default httpServer server. go controls the access permissions through the case of the initial letter. If the initial letter is capitalized, it can be accessed by external packages, which is similar to C# global functions and static functions.
func ListenAndServe(addr string, handler Handler) error {
 server := &Server{Addr: addr, Handler: handler}
 return server.ListenAndServe()
}
  1. net/http Server enables keep alive by default, which is reflected by the Server's private variable disableKeepAlives.
type  Server  struct {
  ...
  disableKeepAlives int32     // accessed atomically. 
  ...
}

Users can also turn off keep alive manually. Setkeepaliveenabled() will modify the value of the private variable 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)
 }

The above is also the basic production / use style of go language pack.

  1. Please note that I have inserted an indexhandler into the httpserver to record the basic information of the httpclient. Here's a knowledge point: if httpclient establishes a new TCP connection, the system will assign you a random port according to certain rules.

Start the server program and visit localhost:8081 from the browser,

The server will receive the following log. The red circle in the figure indicates that the browser uses the random fixed port of the system to establish a tcp connection.

Write the client using net/http: send HTTP requests to the server at an interval of 1s
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))
}

The request log received by the server is as follows:

The red box in the figure shows that the http client initiated an http request using the fixed port 61799, and the client / server maintained http keep alive.

Use netstat -an | grep 127.0.0.1:8081 to watch the TCP connection of the system for a specific ip:

In the client system, only one tcp connection is established for the server. The port of tcp connection is 61799, which echoes the above.

Use Wireshark to view the tcp connection of the localhost network card

  • You can see that there is no tcp handshake three times before each http request / response
  • After tcp contracts each time, the opposite end needs to return the ACK confirmation packet

Negative textbook - high energy early warning

go's net/http clearly states:

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.

That is to say, if the httpclient does not finish reading the body or does not close the body after each request, it may lead to keep alive failure and goroutine disclosure.

//  The following code does not finish reading the body, resulting in keep alive failure
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")
}

The server log is as follows:

The red box in the figure above shows that the client continues to establish a TCP connection using the new random port.

To view the tcp connections established by the client system:

Wireshark packet capture results:

The red box in the figure shows three handshakes and four waves before and after each HTTP request / response.

Full text combing

  1. Currently known httpclient and httpServer all enable keep alive by default
  2. Disabling keep alive or keeping alive failure will cause clients in specific scenarios to frequently establish tcp connections. You can view the tcp connections established on the client through netstat -an | grep {ip}
  3. Wireshark captures packets to clarify the packet capturing effects of keep alive and non keep alive

Posted by FURQAN on Sat, 04 Dec 2021 21:38:29 -0800