一尘不染

TCP接受和并发模型

go

看着net.TCPListener。鉴于Go并发范例,人们可能希望将此系统功能实现为一个渠道,以便您chan *net.Conn从某个Listen()功能或类似的功能中获得一个。

但是似乎Accept()是这样,而且就像系统接受一样,它只是阻塞。除非它残废,是因为:

  • 您无法使用适当的select(),因为go偏爱频道
  • 无法为服务器套接字设置阻止选项。

所以我正在做类似的事情:

    acceptChannel = make(chan *Connection)
    go func() {
      for {
       rw, err := listener.Accept()
       if err != nil { ... handle error ... close(acceptChannel) ... return }
       s.acceptChannel <-&Connection{tcpConn: rw, .... }
      }
    }()

只是为了使我可以在选择中使用多个服务器套接字,或者将对Accept()的等待与其他通道复用。我想念什么吗?我是Go的新手,所以我可能会忽略一些东西-
但是Go真的不使用自己的并发范例来实现自己的阻塞系统功能吗?我真的想为每个要监听的套接字(可能是数百个或数千个)使用一个单独的goroutine吗?这是要使用的正确习惯用法,还是有更好的方法?


阅读 234

收藏
2020-07-02

共1个答案

一尘不染

您的代码很好。您甚至可以进一步替换:

s.acceptChannel <-&Connection{tcpConn: rw, .... }

与:

go handleConnection(&Connection{tcpConn: rw, .... })

如注释中所述,例程不是系统线程,而是由Go运行时管理的轻量级线程。当为每个连接创建例程时,可以轻松地使用阻塞操作,这些操作更易于实现。然后,运行时程序将为您
选择 例程,因此,您正在寻找的行为只是存在于该语言中的其他地方。您看不到它,但是无处不在。

现在,如果您需要更复杂的功能,并且根据我们的对话,实现与超时类似的选择,您将完全按照您的建议进行操作:将所有新连接推送到一个通道,并使用计时器对其进行多路复用。这似乎是Go中要走的路。

请注意,如果 其中一个 接收器发生故障,您将无法关闭接收通道,因为另一个接收器在写入时会出现恐慌。

我的(更完整的)示例:

newConns := make(chan net.Conn)

// For every listener spawn the following routine
go func(l net.Listener) {
    for {
        c, err := l.Accept()
        if err != nil {
            // handle error (and then for example indicate acceptor is down)
            newConns <- nil
            return
        }
        newConns <- c
    }
}(listener)

for {
    select {
    case c := <-newConns:
        // new connection or nil if acceptor is down, in which case we should
        // do something (respawn, stop when everyone is down or just explode)
    case <-time.After(time.Minute):
        // timeout branch, no connection for a minute
    }
}
2020-07-02