英文:
TCP accept and Go concurrency model
问题
观察net.TCPListener
,鉴于Go语言的并发编程范式,人们可能期望这个系统功能被实现为一个通道,这样你可以从Listen()
函数中得到一个chan *net.Conn
,或者类似的东西。
但是看起来Accept()
是唯一的方式,而且它就像系统的accept一样阻塞。只是它有一些缺点,因为:
- 没有适用于它的合适的select()函数,因为Go语言更喜欢通道。
- 没有办法为服务器套接字设置阻塞选项。
所以我正在做这样的事情:
acceptChannel = make(chan *Connection)
go func() {
for {
rw, err := listener.Accept()
if err != nil { ... 处理错误 ... close(acceptChannel) ... return }
s.acceptChannel <- &Connection{tcpConn: rw, .... }
}
}()
这样我就可以在select语句中使用多个服务器套接字,或者将对Accept()的等待与其他通道进行多路复用。我是否遗漏了什么?我对Go语言还不熟悉,所以可能有些事情我没有注意到 - 但是Go语言真的没有用自己的并发范式实现自己的阻塞系统函数吗?我真的需要为每个套接字(可能是数百个或数千个)单独创建一个goroutine吗?这是正确的惯用法,还是有更好的方法?
英文:
Looking at net.TCPListener
. One would expect, given the Go concurrency paradigm, for this system functionality to be implemented as a channel, so that you got a chan *net.Conn
from a Listen()
function, or something similar to that.
But it seems Accept() is the way, and that just blocks, just like the system accept. Except it's crippled, because:
- There is no proper select() you can use with it, because go prefers channels
- There is no way to set the blocking options for the server sockets.
So I'm doing something like:
acceptChannel = make(chan *Connection)
go func() {
for {
rw, err := listener.Accept()
if err != nil { ... handle error ... close(acceptChannel) ... return }
s.acceptChannel <-&Connection{tcpConn: rw, .... }
}
}()
Just so that I can use multiple server sockets in a select, or multiplex the wait on Accept() with other channels. Am I missing something? I'm new to Go, so I might be overlooking things - but did Go really not implement its own blocking system functions with its own concurrency paradigm? Do I really need a separate goroutine for every socket (possibly hundreds or thousands) I want to listen with? Is this the correct idiom to be using, or is there a better way?
答案1
得分: 5
你的代码很好。你甚至可以进一步替换:
s.acceptChannel <- &Connection{tcpConn: rw, .... }
为:
go handleConnection(&Connection{tcpConn: rw, .... })
如评论中所述,协程不是系统线程,它们是由Go运行时管理的轻量级线程。当你为每个连接创建一个协程时,你可以轻松地使用阻塞操作,这样更容易实现。Go运行时会为你选择协程,所以你要寻找的行为实际上是在语言中的其他地方隐藏着。你看不到它,但它无处不在。
现在,如果你需要更复杂的东西,并且根据我们的对话,实现类似于带有超时的select,你可以按照你的建议做:将所有新连接推送到一个通道,并与定时器进行多路复用。这似乎是在Go中前进的方式。
请注意,如果其中一个接收器失败,你不能关闭接收通道,因为另一个接收器在向其写入时会发生恐慌。
以下是我更完整的示例:
newConns := make(chan net.Conn)
// 对于每个监听器,生成以下协程
go func(l net.Listener) {
for {
c, err := l.Accept()
if err != nil {
// 处理错误(然后例如指示接收器已关闭)
newConns <- nil
return
}
newConns <- c
}
}(listener)
for {
select {
case c := <-newConns:
// 新连接或nil(如果接收器已关闭),在这种情况下我们应该
// 做一些事情(重新生成,当所有人都关闭时停止,或者只是爆炸)
case <-time.After(time.Minute):
// 超时分支,一分钟内没有连接
}
}
英文:
Your code is just fine. You could even go further and replace:
s.acceptChannel <-&Connection{tcpConn: rw, .... }
with:
go handleConnection(&Connection{tcpConn: rw, .... })
As mentioned in the comments, routines are not system threads, they are lightweight threads managed by Go runtime. When you create a routine for every connection, you can easily use blocking operations, which are easier to implement. Go runtime is then selecting the routines for you, so the behaviour you're looking for is simply somewhere else, buried into the language. You can't see it, but it's everywhere.
Now, if you need something more sophisticated and, per our conversation, implement something similar to select with a timeout, you would do exactly what you're suggesting: push all the new connection to a channel and multiplex it with a timer. This seems the way to go in Go.
Take a note you can't close the accept channel if one of you acceptors fails as another one would panic while writing to it.
My (fuller) example:
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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论