Go如何实现简单TCP服务器_Go TCP服务端基础实现

10次阅读

go标准库无listenAndServe,须手动net.Listen+for循环Accept;每个conn需goroutine处理并设Read/Write deadline,Read/Write要检查n和err,避免阻塞与泄漏。

Go如何实现简单TCP服务器_Go TCP服务端基础实现

listenAndServe 不存在,得用 net.Listen + accept 循环

Go 标准库没有类似 http.ListenAndServe 那样开箱即用的 TCP 服务启动函数。你必须手动调用 net.Listen 创建监听套接字,再用 Accept 持续接收连接——这是最基础、也最容易漏掉循环控制的地方。

  • net.Listen("tcp", ":8080") 返回 net.Listener,只负责监听,不处理连接
  • 必须显式写 for 循环调用 listener.Accept(),否则程序启动后立即退出
  • 每个 connnet.Conn 接口,需单独 goroutine 处理,否则会阻塞下一个连接
  • 忘记关闭 conn 会导致文件描述符泄漏,尤其在高并发时很快 hit ulimit

读写要用 conn.Read/Write,不是 fmt.Scan/Fprintln

TCP 连接是字节流,fmt 包的函数默认操作 os.Stdin/os.Stdout,不能直接用于 net.Conn。必须用 conn.Readconn.Write,且要处理返回的 n(实际读写字节数)和 err

  • conn.Read([]byte) 是阻塞调用,直到有数据或连接关闭;返回 n, errn == 0 && err == nil 表示对端关闭写入(EOF
  • conn.Write([]byte) 不保证一次发完,但标准实现中对小包通常能全写,仍建议检查 n 是否等于预期长度
  • 不要用 fmt.Fscanf(conn, "%s", &buf):它依赖空格/换行分隔,而 TCP 无消息边界,容易卡住或错读
  • 简单回显场景可用 io.copy(conn, conn),但生产环境务必加超时和缓冲控制

必须设 Read/Write deadline,否则连接卡死无法释放

没有超时的 TCP 服务在客户端异常断连、网络中断或发送半截数据时,conn.Read 会永久阻塞,goroutine 泄漏,最终耗尽资源。

  • accept 后立刻调用 conn.SetReadDeadlineconn.SetWriteDeadline
  • 时间点用 time.Now().Add(30 * time.Second),不是固定间隔;每次读写前都需重设
  • 超时错误是 net.ErrDeadlineExceeded,可据此主动关闭连接
  • 如果用 io.ReadFullbufio.Reader,deadline 仍需手动设置,它们不自动继承
package main 

import ( "log" "net" "time" )

func handleConn(conn net.Conn) { defer conn.Close() conn.SetReadDeadline(time.Now().Add(30 time.Second)) conn.SetWriteDeadline(time.Now().Add(30 time.Second))

buf := make([]byte, 1024) for {     n, err := conn.Read(buf)     if err != nil {         if netErr, ok := err.(net.Error); ok && netErr.Timeout() {             log.Println("read timeout", conn.RemoteAddr())         }         return     }     if n == 0 {         return     }     _, _ = conn.Write(buf[:n])     conn.SetReadDeadline(time.Now().Add(30 * time.Second))     conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) }

}

func main() { lis, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } defer lis.Close()

log.Println("TCP server listening on :8080") for {     conn, err := lis.Accept()     if err != nil {         log.Println("accept error:", err)         continue     }     go handleConn(conn) }

}

真正麻烦的从来不是“怎么启动”,而是连接生命周期管理:什么时候关、谁来关、超时怎么判、错误怎么分。哪怕只是 echo 服务,SetDeadlinedefer conn.Close() 漏掉任何一个,跑两天就出事。

text=ZqhQzanResources