TCP accept and Go concurrency model

Looking at net.TCPListener

. One would expect, given the Go concurrency paradigm, for this system functionality to be implemented as a pipe, so what you get chan *net.Conn

from a function Listen()

or something like that.

But it looks like Accept () is the way to go and it just blocks like the system does. Except that he is crippled because:

  • There is no suitable select () you can use with it because go prefers channels
  • Unable to set blocking options for server sockets.

So I am 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 the select or multiplex wait on Accept () with other pipes. Am I missing something? I'm new to Go, so I could ignore things, but has Go really not implemented its own blocking system functions with its own w390 paradigm? Do I really need a separate goroutine for each socket (maybe hundreds or thousands) that I want to listen to? Is this the correct idiom to use, or is there a better way?

-2


source to share


1 answer


Your code is fine. You can even go ahead and replace:

s.acceptChannel <-&Connection{tcpConn: rw, .... }

      

from:

go handleConnection(&Connection{tcpConn: rw, .... })

      

As mentioned in the comments, subroutines are not system threads, they are lightweight threads controlled by the Runtime. When you create a routine for each connection, you can easily use blocking operations that are easier to implement. Go into runtime, then pick out routines for you, so the behavior you're looking for is just somewhere else buried in the language. You cannot see it, but it is everywhere.



Now, if you need something a little more complex and, in our conversation, implement something similar to the choice with a timeout, you would do exactly what you suggest: push the entire new channel connection and multiplex it with a timer This is similar to the Go way.

Note that you cannot close the receive channel if one of your acceptors fails, as the other will panic when writing to it.

My (more complete) 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
    }
}

      

+3


source







All Articles