问题
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:
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
}
}
来源:https://stackoverflow.com/questions/29948497/tcp-accept-and-go-concurrency-model