problem
http server:
package main import ( "log" "net/http" ) func main() { // Start HTTP service addr := "127.0.0.1:8080" // websocket processing of adding agent http.HandleFunc("/agent", agentHandler) err := http.ListenAndServe(addr, nil) log.Fatal(err) } func agentHandler(w http.ResponseWriter, r *http.Request) { }
http client is simulated by telnet (it does not involve the sending and receiving of http messages, only the connection)
telnet 127.0.0.1 8080
Using tcpdump and strace, it is found that the server side has enabled tcp keepalive for 15 seconds, but the code only has a default setting of 30 seconds, which is strange:
$ strace ./s 2>&1 | grep setsockopt setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 // Above is the output before the client connects, and below is the output after the client connects setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0 setsockopt(4, SOL_TCP, TCP_KEEPINTVL, [15], 4) = 0 setsockopt(4, SOL_TCP, TCP_KEEPIDLE, [15], 4) = 0
Analysis process
First check the local go source directory:
$ echo `go env GOROOT`/src /usr/lib/go/src
By looking at the source code, it is found that tcp keepalive is finally set in tcpsockopt_unix.go
func setKeepAlivePeriod(fd *netFD, d time.Duration) error { // The kernel expects seconds so round to next highest second. d += (time.Second - time.Nanosecond) secs := int(d.Seconds()) if err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil { return wrapSyscallError("setsockopt", err) } err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs) runtime.KeepAlive(fd) return wrapSyscallError("setsockopt", err) }
So here's the break:
$ gdb ./s // Omit part of the content (gdb) b tcpsockopt_unix.go:16 Breakpoint 1 at 0x55c0ae: file /usr/lib/go/src/net/tcpsockopt_unix.go, line 17. (gdb) r Starting program: /home/chuqq/work/work/temp/codeeveryday/golang/20200213_gorilla_websocket/s // Omit part of the content Thread 1 "s" hit Breakpoint 1, net.setKeepAlivePeriod (fd=0xc0000ea080, d=15000000000, ~r2=...) at /usr/lib/go/src/net/tcpsockopt_unix.go:17 17 d += (time.Second - time.Nanosecond) (gdb) bt #0 net.setKeepAlivePeriod (fd=0xc0000ea080, d=15000000000, ~r2=...) at /usr/lib/go/src/net/tcpsockopt_unix.go:17 #1 0x000000000055bd75 in net.(*TCPListener).accept (ln=0xc00000e260, ~r0=<optimized out>, ~r1=...) at /usr/lib/go/src/net/tcpsock_posix.go:150 #2 0x000000000055aa57 in net.(*TCPListener).Accept (l=0xc00000e260, ~r0=..., ~r1=...) at /usr/lib/go/src/net/tcpsock.go:261 #3 0x000000000065867c in net/http.(*onceCloseListener).Accept (~r0=..., ~r1=...) at <autogenerated>:1 #4 0x0000000000637a50 in net/http.(*Server).Serve (srv=0xc0000e8000, l=..., ~r1=...) at /usr/lib/go/src/net/http/server.go:2896 #5 0x0000000000637777 in net/http.(*Server).ListenAndServe (srv=0xc0000e8000, ~r0=...) at /usr/lib/go/src/net/http/server.go:2825 #6 0x00000000006609b6 in net/http.ListenAndServe (handler=..., addr=...) at /usr/lib/go/src/net/http/server.go:3081 #7 main.main () at /home/chuqq/temp/codeeveryday/golang/20200213_gorilla_websocket/s.go:13
It was found that it was set in accept() in / usr/lib/go/src/net/tcpsock_posix.go:150:
func (ln *TCPListener) accept() (*TCPConn, error) { fd, err := ln.fd.accept() if err != nil { return nil, err } tc := newTCPConn(fd) if ln.lc.KeepAlive >= 0 { setKeepAlive(fd, true) ka := ln.lc.KeepAlive if ln.lc.KeepAlive == 0 { ka = defaultTCPKeepAlive } setKeepAlivePeriod(fd, ka) } return tc, nil }
defaultTCPKeepAlive is defined in dial.go:
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times // See golang.org/issue/31510 const ( defaultTCPKeepAlive = 15 * time.Second )
The version I used is version 1.13.7, which seems to be quite different from the 1.11.x code I saw before. For details, see the above problem: golang.org/issue/31510