看着net.TCPListener。鉴于Go并发范例,人们可能希望将此系统功能实现为一个渠道,以便您chan *net.Conn从某个Listen()功能或类似的功能中获得一个。
net.TCPListener
chan *net.Conn
Listen()
但是似乎Accept()是这样,而且就像系统接受一样,它只是阻塞。除非它残废,是因为:
所以我正在做类似的事情:
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吗?这是要使用的正确习惯用法,还是有更好的方法?
您的代码很好。您甚至可以进一步替换:
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 } }