golang tcp 服务

88 阅读1分钟

启动 TCP 服务器

zhuanlan.zhihu.com/p/609629545 启动 tcp 服务的代码框架,伪代码如下:

// 启动一个 tcp 服务端代码示例
func main(){
   // 创建一个 tcp 端口监听器
   l,_ := net.Listen("tcp",":8080")
   // 主动轮询模型
   for{
       // 等待 tcp 连接到达
       conn,_ := l.Accept()     
       // 开启一个 goroutine 负责一笔客户端请求的处理
       go serve(conn)
   }
}


// 处理一笔 tcp 连接
func serve(conn net.Conn){
    defer conn.Close()
    var buf []byte
    // 读取连接中的数据
    _,_ = conn.Read(buf)    
    // ...
}

listen && bind

sysSocket 中

  • syscall.Socket 创建套接字
  • syscall.SetNonblock 将 socket 设置为非阻塞模式

listenStream 中

  • 发起系统调用 syscall.Bind 实现 socket fd 和端口的绑定
  • 发起系统调用,实现对 fd 的监听
  • 调用 netFD.init 方法对 socket fd 进行初始化, 在其中执行了 epoll 操作
var serverInit sync.Once

func (pd *pollDesc) init(fd *FD) error {
    serverInit.Do(runtime_pollServerInit)
    ctx, errno := runtime_pollOpen(uintptr(fd.Sysfd))
    // ...
}

从 netFD.init 方法出发,历经 netFD.init -> FD.Init -> pollDesc.init 的链路,

  • 通过 sync.Once 保证全局只执行一次 runtime_pollServerInit 方法, 调用 epollcreate1

  • 调用 runtime_pollOpen 方法将当前 fd 添加到 epoll 池中.

func (fd *netFD) init() error {
    return fd.pfd.Init(fd.net, true)
}

accept

历经 TCPListener.Accept -> TCPListener.accept -> netFD.accept -> FD.Accept 的辗转,最终获取 tcp 连接及阻塞处理的核心逻辑实现于 internal/poll/fd_unix.go 的 FD.Accept 方法.

func (fd *netFD) accept() (netfd *netFD, err error) {
    // 倘若有 tcp 连接到达,则成功取出并返回
    // 倘若没有 tcp 连接到达,会 gopark 进入被动阻塞,等待被唤醒
    d, rsa, errcall, err := fd.pfd.Accept()
    // ...
}