跳到主要内容

TCP 的连接管理

一、TCP 连接建立相关问题

问题1:TCP三次握手的过程是什么?为什么需要三次握手而不是两次?

考察点:TCP协议基础、网络编程理论

回答要点:

TCP建立连接需要解决三个问题:

  1. 使双方知道对方的存在
  2. 协商参数(最大报文字段长度、窗口大小等)
  3. 分配和初始化资源(缓存、状态量、连接表等)

三次握手过程:

为什么需要三次握手:

防止已失效的请求报文突然传到服务器引起错误。

问题2:在Go中如何设置TCP连接的超时时间?

考察点:Go网络编程实践

// 连接超时
conn, err := net.DialTimeout("tcp", "localhost:8080", 5*time.Second)

// 读写超时
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

// 或者同时设置读写超时
conn.SetDeadline(time.Now().Add(10 * time.Second))

二、TCP 连接释放相关问题

问题3:TCP四次挥手的过程是什么?为什么需要四次而不是三次?

考察点:TCP协议完整性理解

四次挥手过程:

为什么需要四次挥手:

  • TCP是全双工通信,每个方向都必须单独关闭
  • 服务端收到客户端的FIN后,可能还有数据要发送,所以ACK和FIN要分开发送

问题4:TIME_WAIT状态的作用是什么?在Go程序中如何处理?

考察点:TCP状态机理解、Go网络编程优化

TIME_WAIT作用:

  1. 确保最后的ACK能够到达服务端
  2. 避免新连接收到旧连接的延迟数据包

Go中的处理:

// 设置SO_REUSEADDR选项
listener, err := net.Listen("tcp", ":8080")
if err != nil {
return err
}

// 或者使用更底层的控制
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
})
},
}
listener, err := lc.Listen(context.Background(), "tcp", ":8080")

三、TCP 连接维持相关问题

问题5:长连接和短连接的区别是什么?在什么场景下使用?

考察点:网络编程架构设计

Go中的实现示例:

// 短连接示例
func shortConnection() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
return
}
defer conn.Close() // 使用完立即关闭

// 进行通信
conn.Write([]byte("hello"))
}

// 长连接示例 - 连接池
type ConnectionPool struct {
conns chan net.Conn
factory func() (net.Conn, error)
}

func (p *ConnectionPool) Get() (net.Conn, error) {
select {
case conn := <-p.conns:
return conn, nil
default:
return p.factory()
}
}

func (p *ConnectionPool) Put(conn net.Conn) {
select {
case p.conns <- conn:
default:
conn.Close() // 池满时关闭连接
}
}

问题6:TCP KeepAlive和应用层心跳的区别是什么?Go中如何实现?

考察点:网络保活机制、Go实践应用

Go中的实现:

// TCP KeepAlive设置
func setKeepAlive(conn net.Conn) error {
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
return fmt.Errorf("not a TCP connection")
}

// 开启KeepAlive
if err := tcpConn.SetKeepAlive(true); err != nil {
return err
}

// 设置KeepAlive间隔
if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil {
return err
}

return nil
}

// 应用层心跳实现
type HeartbeatClient struct {
conn net.Conn
interval time.Duration
timeout time.Duration
stop chan struct{}
}

func (h *HeartbeatClient) startHeartbeat() {
ticker := time.NewTicker(h.interval)
defer ticker.Stop()

for {
select {
case <-ticker.C:
if err := h.ping(); err != nil {
log.Printf("heartbeat failed: %v", err)
h.conn.Close()
return
}
case <-h.stop:
return
}
}
}

func (h *HeartbeatClient) ping() error {
h.conn.SetWriteDeadline(time.Now().Add(h.timeout))
_, err := h.conn.Write([]byte("PING"))
if err != nil {
return err
}

h.conn.SetReadDeadline(time.Now().Add(h.timeout))
buf := make([]byte, 4)
_, err = h.conn.Read(buf)
if err != nil {
return err
}

if string(buf) != "PONG" {
return fmt.Errorf("invalid heartbeat response")
}

return nil
}

问题7:在Go中如何优雅地关闭TCP连接?

考察点:Go并发编程、资源管理

type Server struct {
listener net.Listener
quit chan interface{}
wg sync.WaitGroup
}

func (s *Server) Start() error {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
return err
}
s.listener = listener
s.quit = make(chan interface{})

s.wg.Add(1)
go s.serve()

return nil
}

func (s *Server) serve() {
defer s.wg.Done()

for {
conn, err := s.listener.Accept()
if err != nil {
select {
case <-s.quit:
return
default:
log.Printf("accept error: %v", err)
continue
}
}

s.wg.Add(1)
go s.handleConnection(conn)
}
}

func (s *Server) handleConnection(conn net.Conn) {
defer s.wg.Done()
defer conn.Close()

for {
select {
case <-s.quit:
return
default:
conn.SetReadDeadline(time.Now().Add(time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
return
}

// 处理数据
conn.Write(buf[:n])
}
}
}

func (s *Server) Stop() {
close(s.quit)
s.listener.Close()
s.wg.Wait()
}

这些问题涵盖了TCP协议的核心概念和Go语言网络编程的实践应用,可以很好地考察候选人的理论基础和实际编程能力。

Reference

一条视频讲清楚TCP协议与UDP协议-什么是三次握手与四次挥手