压测发现有长连接问题,深入学习了一下。

TIME_WAIT
1
2
netstat -nat | grep :8080 | grep TIME_WAIT | wc -l   
17731
TIME_WAITTCP

TCP频繁的建立连接,会有一些问题:

2MSLTIME_WAIT3.155KB
TCP 相关
TCPHTTPTCP
1
func (c *TCPConn) SetKeepAlive(keepalive bool) error

SetKeepAlive sets whether the operating system should send keepalive messages on the connection.

这个方法比较简单,设置是否开启长连接。

1
func (c *TCPConn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future Read calls and any currently-blocked Read call. A zero value for t means Read will not time out.

这个函数就很讲究了。我之前的理解是设置读取超时时间,这个方法也有这个意思,但是还有别的内容。它设置的是读取超时的绝对时间。

1
func (c *TCPConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future Write calls and any currently-blocked Write call. Even if write times out, it may return n > 0, indicating that some of the data was successfully written. A zero value for t means Write will not time out.

这个方法是设置写超时,同样是绝对时间。

HTTP 包如何使用 TCP 长连接?

http 服务器启动之后,会循环接受新请求,为每一个请求(连接)创建一个协程。

1
2
3
4
5
// net/http/server.go L1892
for {
	rw, e := l.Accept()
	go c.serve()
}
servefor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// net/http/server.go L1320
func (c *conn) serve() {
	defer func() {
		if !c.hijacked() {
			c.close()
		}
	}()

	for {
		w, err := c.readRequest()
		
		if err != nil {
		}
		
        serverHandler{c.server}.ServeHTTP(w, w.req)
        
        ...   // 后面会根据是否设置了 keepalive 等判断是否跳出循环
	}
}
deferTCP
readRequestHTTP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// net/http/server.go
func (c *conn) readRequest() (w *response, err error) {
	if d := c.server.ReadTimeout; d != 0 {
		c.rwc.SetReadDeadline(time.Now().Add(d)) // 设置read 超时时间
	}
	if d := c.server.WriteTimeout; d != 0 {
		defer func() {
			c.rwc.SetWriteDeadline(time.Now().Add(d))   // readRequest 执行完成后设置Write 超时时间
		}()
	}
	
	if req, err = ReadRequest(c.buf.Reader); err != nil {
		if c.lr.N == 0 {
			return nil, errTooLarge
		}
		return nil, err
	}
}

func ReadRequest(b *bufio.Reader) (req *Request, err error) {
	// First line: GET /index.html HTTP/1.0
	var s string
	if s, err = tp.ReadLine(); err != nil {
		return nil, err
	}
	
	req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
	
	mimeHeader, err := tp.ReadMIMEHeader()
}
HTTPReadRequesttime.Now().Add(d)defer
为啥长连接失效?

通过源码我们能大概知道程序流程了,按道理是支持长连接的。为啥我们的程序不行呢?

beego1
beego.HttpServerTimeOut = 1
读超时写超时都不对
写超时
ReadRequestReadRequest11
如何解决问题?
TIME_WAIT
beego
IdleTimeout
后续

其实服务端最合理的超时控制需要这几个方面:

读超时写超时空闲超时逻辑超时
TimeoutHandlerbeegobeego
1
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
转发小结

本文比较清晰的介绍了 http 连接的对请求的处理流程及部分的参数设置:

  • http服务启动监听后,循环接收连接请求,并为每个连接建立了一个协程来进行处理
  • 每个连接对应的协程中,都默认是长连接,在协程中循环接受连接的请求,然后根据相应的条件来判断究竟是跳出循环还是继续循环的接收请求
  • 超时时间设置的含义
References