TCP接受Go并发模型



查看net.TCPListener。在给定Go并发范式的情况下,人们会期望将该系统功能实现为通道,从而从Listen()函数或类似函数中获得chan *net.Conn

但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, .... }
}
}()

这样我就可以在select中使用多个服务器套接字,或者将Accept()上的等待与其他通道多路复用。我是不是错过了什么?我是Go的新手,所以我可能忽略了一些事情——但Go真的没有用自己的并发范式实现自己的阻塞系统功能吗?我真的需要为我想要监听的每个套接字(可能有数百或数千个)单独的goroutine吗?这是正确的成语吗?或者有更好的方法吗?

您的代码很好。你甚至可以更进一步,取代:

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
}
}

最新更新