TCP 接受和 Go 并发模型

看着net.TCPListener。考虑到 Go 并发范式,人们会期望将这个系统功能实现为通道,以便您chan *net.Conn从Listen()函数或类似的东西中获得 a 。


但似乎 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 吗?这是要使用的正确习语,还是有更好的方法?


海绵宝宝撒
浏览 179回答 1
1回答

暮色呼如

你的代码很好。您甚至可以更进一步并替换:s.acceptChannel <-&Connection{tcpConn: rw, .... }和:go handleConnection(&Connection{tcpConn: rw, .... })正如评论中提到的,例程不是系统线程,它们是由 Go 运行时管理的轻量级线程。当您为每个连接创建一个例程时,您可以轻松地使用更容易实现的阻塞操作。Go 运行时然后为您选择例程,因此您正在寻找的行为只是其他地方,埋在语言中。你看不到它,但它无处不在。现在,如果您需要更复杂的东西,并且根据我们的对话,实现类似于带有超时选择的东西,您将完全按照您的建议执行:将所有新连接推送到一个通道并使用计时器对其进行多路复用。这似乎是 Go 的方式。请注意,如果您中的一个接受者失败,则您无法关闭接受通道,因为另一个接受者在写入时会感到恐慌。我的(更完整的)示例:newConns := make(chan net.Conn)// For every listener spawn the following routinego func(l net.Listener) {&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; c, err := l.Accept()&nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // handle error (and then for example indicate acceptor is down)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newConns <- nil&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; newConns <- c&nbsp; &nbsp; }}(listener)for {&nbsp; &nbsp; select {&nbsp; &nbsp; case c := <-newConns:&nbsp; &nbsp; &nbsp; &nbsp; // new connection or nil if acceptor is down, in which case we should&nbsp; &nbsp; &nbsp; &nbsp; // do something (respawn, stop when everyone is down or just explode)&nbsp; &nbsp; case <-time.After(time.Minute):&nbsp; &nbsp; &nbsp; &nbsp; // timeout branch, no connection for a minute&nbsp; &nbsp; }}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go