socket tcp 如何维护长连接
操作系统实现:
tcp的keepalive机制开启后,在一定时间内,一般为7200s(参数tcp_keepalive_time)在链路上没有数据传送的情况下,tcp层将发送相应的keepalive探针以确定连接的可用性,探测失败后重试9次(tcp_keepalive_probes),每次间隔时间75s(tcp_keepalive_intvl),所有探测失败后,才认为当前连接已经不可用。
这些参数是机器级别的,可以调整。
keepalive保活机制只在链路空闲的情况下才会起作用。
应用层实现:
如果客户端已经消失而连接未断开,则会使得服务器上保留一个半开放的连接。保活功能就是试图在服务器端检测到这种半开放的连接。
1)客户端依然正常运行且可达。此时客户端正常响应,服务器将保活定时器复位。
2)客户端主机已经崩溃,并且已经重新启动。服务端会收到一个对其保活探测的响应,这个响应是一个复位(RST),使得服务器终止这个连接。
3)客户端主机已经崩溃,并且关闭或者正在重启。客户端无法响应,服务端将无法收到客户端的探测响应,服务端连续发送多个探测,每个间隔指定时间,若服务器没有收到响应,就认为客户端已经关闭并终止连接。
4)客户端正常,服务端不可达,同第3点
net包的Dial源码
func (d *Dialer) Dial(network, address string) (Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
...
var c Conn
if len(fallbacks) > 0 {
c, err = sd.dialParallel(ctx, primaries, fallbacks)
} else {
c, err = sd.dialSerial(ctx, primaries)
}
if err != nil {
return nil, err
}
if tc, ok := c.(*TCPConn); ok && d.KeepAlive >= 0 {
setKeepAlive(tc.fd, true) //
ka := d.KeepAlive
if d.KeepAlive == 0 {
ka = 15 * time.Second
}
setKeepAlivePeriod(tc.fd, ka) //
testHookSetKeepAlive(ka)
}
return c, nil
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
c, err = sd.dialSerial(ctx, primaries)
}
func (sd *sysDialer) dialSerial(ctx context.Context, ras addrList) (Conn, error) {
c, err := sd.dialSingle(dialCtx, ra)
}
func (sd *sysDialer) dialSingle(ctx context.Context, ra Addr) (c Conn, err error) {
c, err = sd.dialTCP(ctx, la, ra)
}
func (sd *sysDialer) dialTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
return sd.doDialTCP(ctx, laddr, raddr)
}
func (sd *sysDialer) doDialTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
fd, err := internetSocket(ctx, sd.network, laddr, raddr, syscall.SOCK_STREAM, 0, "dial", sd.Dialer.Control) //
for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ {
if err == nil {
fd.Close()
}
fd, err = internetSocket(ctx, sd.network, laddr, raddr, syscall.SOCK_STREAM, 0, "dial", sd.Dialer.Control)
}
if err != nil {
return nil, err
}
return newTCPConn(fd), nil
}
net包的listen
由图可以看出,http的监听是对net.Listen()的封装,而net.Listen()是对Tcp和Unix的封装。而http的监听传入的都是tcp。所有http的监听最终都是实现的net.ListenTcp()。
其中net.ListenTcp,net.ListenUdp(),net.ListenIP()最终走的都是internetSocket()。
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
...
switch la := la.(type) {
case *TCPAddr:
l, err = sl.listenTCP(ctx, la)
case *UnixAddr:
l, err = sl.listenUnix(ctx, la)
}
...
}
func (sl *sysListener) listenTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
fd, err := internetSocket(ctx, sl.network, laddr, nil, syscall.SOCK_STREAM, 0, "listen", sl.ListenConfig.Control) //
if err != nil {
return nil, err
}
return &TCPListener{fd}, nil
}
net/http/server.go
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
tc, err := ln.AcceptTCP()
if err != nil {
return nil, err
}
tc.SetKeepAlive(true) //
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
net.TCPConn 的 SetKeepAlive 用来启用TCP keepalive, SetKeepAlivePeriod
参考:
net.Dail 源码分析 blog.csdn.net/yyj18085644…
net.Listen 源码 blog.csdn.net/aixinaxc/ar…