写在前面
TIME_WAIT
netstat -nat | grep :8080 | grep TIME_WAIT | wc -l
17731
TIME_WAIT
TCP频繁的建立连接,会有一些问题:
TIME_WAIT
TCP 相关
长连接的概念包括TCP长连接和HTTP长连接。首先得保证TCP是长连接。我们就从它说起。
func (c *TCPConn) SetKeepAlive(keepalive bool) error
SetKeepAlive sets whether the operating system should send keepalive messages on the connection. 这个方法比较简单,设置是否开启长连接。
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.这个函数就很讲究了。我之前的理解是设置读取超时时间,这个方法也有这个意思,但是还有别的内容。它设置的是读取超时的绝对时间。
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 服务器启动之后,会循环接受新请求,为每一个请求(连接)创建一个协程。
// net/http/server.go L1892
for {
rw, e := l.Accept()
go c.serve()
}
servefor
// 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)
}
}
defer
readRequest
// 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))
}
if d := c.server.WriteTimeout; d != 0 {
defer func() {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}()
}
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()
}
ReadRequesttime.Now().Add(d)defer
我们的程序为啥长连接失效?
通过源码我们能大概知道程序流程了,按道理是支持长连接的。为啥我们的程序不行呢?
我们的程序使用的是 beego 框架,它支持的超时是同时设置读写超时。而我们的设置是1秒。
beego.HttpServerTimeOut = 1
我对读写超时的理解,读超时是收到数据到读取完毕的时间;写超时是从一开始写到写完的时间。我对这两个超时的理解都不对。
实际上,从上面的源码可以发现,写超时是读取完毕之后设置的超时时间。也就是读取完毕之后的时间,加上逻辑执行时间,加上内容返回时间的总和。按照我们的设置,超过1秒就算超时。
ReadRequestReadRequest
如何解决问题?
TIME_WAIT
我们目前使用的是 beego 框架,它并不支持单独设置读写超时,所以我目前的解决方式是将读写超时调整得大一些。
IdleTimeout
后续
其实服务端最合理的超时控制需要这几个方面:
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler