Golang超时设置

本文介绍在Golang网络编程中,为HTTP请求或者TCP请求设置超时。这篇文章的内容来自PPT,所以文字不多,都是图片或者代码。

  • Go网络编程基础
  • 超时设置
    • HTTP client/server
    • TCP client/server
  • KeepAlive
    • TCP KeepAlive
    • HTTP KeepAlive
  • 调试方法


  1. Go网络编程基础

Socker编程:如下图所示

Golang超时设置_第1张图片go socket编程


HTTP编程:

Client示例如下:

transport := &http.Transport{
         Dial: (&net.Dialer{
               Timeout: 10 * time.Second,
         }).Dial,
         MaxIdleConns: 2,
}
client := &http.Client{
        Timeout:   time.Second * 30,
        Transport: transport,
}
resp, _:= client.Get("http://localhost:8888/hello")
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()

Server示例如下

http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request){
                            fmt.Printf("receive msg form: %v", req.RemoteAddr)
                            _, _ := io.WriteString(w, "hello world!\n")               
})
server := &http.Server{Addr: ":8888"}
_ := server.ListenAndServe()


2. 超时设置

首先介绍HTTP超时设置,Server端,主要关注四个参数:ReadTimeout、ReadHeaderTimeout、WriteTimeout、TimeoutHandler,代码示例如下

type EchoHandler struct {}

func (handler *EchoHandler)ServeHTTP(w http.ResponseWriter, req *http.Request) {
	n, _ := io.WriteString(w, "echo !\n")
}

func main() {
	timeoutHandler := http.TimeoutHandler(&EchoHandler{}, 5 * time.Second, "echo timed out!")
	http.Handle("/echo", timeoutHandler)
	listener, _ := net.Listen("tcp", ":8888")
	server := &http.Server{
		ReadTimeout: time.Second * 5,
		WriteTimeout: time.Second * 10,
		ReadHeaderTimeout: time.Second * 3,
	}
	_  := server.Serve(listener)
}

下面一张图形象地显示了HTTP在服务端的参数设置,来源:the complete guide to golang net http timeouts。

Golang超时设置_第2张图片HTTP服务端超时设置

我们举一个TimeoutHandler参数的例子,这个参数是指从读取request body到返回Response的时间(即处理请求的时间),我们设置超时时间为5秒,并在函数处理时Sleep 1分钟,抓包观察连接变化。代码如下:

func (handler *EchoHandler)ServeHTTP(w http.ResponseWriter, req *http.Request) {
	time.Sleep(time.Second * 60)   
	n, _ := io.WriteString(w, "echo ! \n")
}
timeoutHandler := http.TimeoutHandler(&EchoHandler{}, 5 * time.Second, "echo timed out!")

使用tcpdump结合wireshark抓包分析如下:

Golang超时设置_第3张图片TimeoutHandler超时

可见,在读取Request body 5秒之后,(第18秒到23秒),服务端向客户端发送了statuscode为503(服务不可用)的报文,客户端收到的信息如下:

  err: nil
  statuscode: 503
  body: "echo timed out!"


HTTP client端设置超时代码片段如下:

transport := &http.Transport{
	Dial: (&net.Dialer{
		Timeout: 10 * time.Second,
	}).Dial,
	IdleConnTimeout:     10 * time.Second,
	TLSHandshakeTimeout:   5 * time.Second,
	ResponseHeaderTimeout: 10 * time.Second,
}

client := &http.Client{
	Timeout:   30 * time.Second,
	Transport: transport,
}

下面图片显示了各个参数起作用的时间。

Golang超时设置_第4张图片HTTP client设置

我们看一下Dialer.Timeout,这个参数限制TCP建立连接的时间,在TCP建立连接时,主动发起请求的一方发送syn报文,如果syn报文没有得到回应,就会对这个syn报文进行重传。在linux中,会对syn报文进行6次重传,第i次重传之后等待2^(i-1)秒,共计等待127秒作用(这个只是一个估计值,不是绝对的)。linux与此相关的两个参数为:

net.ipv4.tcp_syn_retries     默认为6
net.ipv4.tcp_synack_retries  默认为5,即对synack报文进行5次重传

下面几个是对连接进行抓包的示例,分别对不同情况下的报文进行抓包分析。

示例一:一次正常的HTTP请求流程

Golang超时设置_第5张图片一次正常的HTTP请求过程


从报文中可以看到,首先是TCP三次握手的过程 ,然后主动发起连接的一方发送了一个create volume的请求,server对此请求进行了回应(发送了一个ack),此后的时间,链路进入idle状态(双方都没有等待ACK),在这段时间内,服务器在处理请求,请求处理完成之后,向客户端发送数据

示例二:Server在处理请求过程中宕机。

如果未为客户端设置超时或者KeepAlive, 如果服务端在处理请求的时候宕机(此时链路处于idle状态),那么这个连接在客户端将一直保持,如果不进行处理这个无效的连接将一直占用文件描述符(可能会导致无法建立新连接或者打开文件),用netstat命令查看连接状态如下,

连接一直保持

示例三:Server未宕机,Client处理请求超时

这种情况下,为客户端设置了超时时间(http.Client.Timeout=600s),那么客户端在超时时间到达时发送fin报文给服务端,服务端对这个fin报文进行回应,但是因为服务端还没处理完,服务端并不会发送fin报文,此时客户端直接从fin_wait2状态到close状态。linux中控制fin_wait2时间的变量为net.ipv4.tcp_fin_time,默认为60秒,抓包分析如下

Golang超时设置_第6张图片Client超时

示例四,Server宕机、Client超时

在这种情况下,client会发送fin报文给server,跟示例三一样,但是server并不会对这个fin进行回应,因为它宕机了,这个时候client会对这个fin报文进行重传,重传到一定次数就断开连接,linux控制重传的参数为: tcp_retries1, tcp_retries2,默认情况下,普通报文重传16左右(整个过程持续14分钟)fin报文的重传次数我不确定,持续时间差不多。重传等待时间是上次等待时间的两倍。

Golang超时设置_第7张图片fin报文重传

TCP超时设置

TCP超时设置比较简单,主要是三个参数,这三个方法每次读写前都需要重置一下,因为是设置绝对时间。

Conn.SetDeadline(t time.Time)      
Conn.SetReadDeadline(t time.Time) 
Conn.SetWriteDeadline(t time.Time)

示例代码如下,参考 Tcp timeout example

bufReader := bufio.NewReader(conn)
timeoutDuration := 5 * time.Second

for {
// Set a deadline for reading. Read operation will fail if no data is received after deadline.
conn.SetReadDeadline(time.Now().Add(timeoutDuration))

	bytes, err := bufReader.ReadBytes('\n')
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s", bytes)
}


3. KeepAlive介绍

首先介绍TCP keepAlive,TCP的keepAlive作用如下

  • 判断连接是否断开(由中间设备、对端故障引起)
  • 为连接保持数据流

KeepAlive在linux中有三个参数,分别表示开始发送探测报文之前的等待时间,发送探测报文的间隔,以及发送探测报文的此时。

    net.ipv4.tcp_keepalive_time
    net.ipv4.tcp_keepalive_intvl
    net.ipv4.tcp_keepalive_probes

查看修改配置方法的方法为:

查看 sysctl -a | grep tcp_keepalive

修改配置文件/etc/sysctl.conf,执行sysctl -p生效

关于TCP keepAlive可以参考TCP Keepalive HOWTO,下面是TCP keepalive的示例

Golang超时设置_第8张图片


Golang TCP KeepAlive的设置很简单,在Socket情况下,仅需要设置两个参数:

TCPConn.SetKeepAlive(true)
TCPConn.SetKeepAlivePeriod(3 * time.Minute)

在HTTP情况下设置分为Client和Server,Client设置如下:

netTransport := &http.Transport{
	Dial: (&net.Dialer{
		KeepAlive: 3 * time.Minute,
	}).Dial,
}

Server设置如下:

Server.ListenAndServe()             // enable    
Server.ListenAndServeTLS()        // enable
Server.Serve()                              // disable

Golang的实现为,probes使用linux系统的默认值,intvl的值与time值相等,为参数设置的值,关于golang tcp keepalive可以参考Using TCP keepalive with Go

再介绍HTTP KeepAlive,HTTP keepAlive是一种连接复用技术,意在减少连接建立和关闭所消耗的时间。

Golang HTTP keepalive设置如下,client:

http.Transport{
    DisableKeepAlives: false, 
    MaxIdleConnsPerHost: 2,       // Per proxy, scheme, addr
    MaxIdleConns:        2,
    IdleConnTimeout:     10 * time.Second,
}

Server:

Server.SetKeepAlivesEnabled(true)    // Server disable + Client enable = disable


4. 问题调试方法

netstat 命令

netstat no | grep 8808

-o: 查看状态与计时器

tcpdump 命令

tcpdump -i enp0s31f6 host 172.16.3.113 -w tcpdump.cap

wireshark 软件

使用过滤条件

tcp.port == 8808

ip.addr == 172.16.3.11

你可能感兴趣的:(Golang超时设置)