Close the goroutine reading from a TCP connection

2019-07-19 23:15发布

I love the way Go handles I/O multiplexing internally which epoll and another mechanisms and schedules green threads (go-routine here) on its own giving the freedom to write synchronous code.

I know TCP sockets are non-blocking and read will give EAGAIN when no data is available. Given that, conn.Read(buffer) will detect this and blocks the go routine doing a connection read with no data available in the socket buffer. Is there a way to stop such go routine without closing the underlying connection. I am using a connection pool so closing the TCP connection won't make sense for me and want to return that connection back to the pool.

Here is the code to simulate such scenario:

func main() {
    conn, _ := net.Dial("tcp", "127.0.0.1:9090")
    // Spawning a go routine
    go func(conn net.Conn) {
        var message bytes.Buffer
        for {
            k := make([]byte, 255) // buffer
            m, err := conn.Read(k) // blocks here 
            if err != nil {
                if err != io.EOF {
                    fmt.Println("Read error : ", err)
                } else {
                    fmt.Println("End of the file")
                }
                break // terminate loop if error
            }
            // converting bytes to string for printing
            if m > 0 {
                for _, b := range k {
                    message.WriteByte(b)
                }
                fmt.Println(message.String())
            }

        }
    }(conn)

    // prevent main from exiting
    select {}
}

What are the other approaches can I take if it's not possible:

1) Call syscall.Read and handle this manually. In this case, I need a way to check if the socket is readable before calling syscall.Readotherwise I will end up wasting unnecessary CPU cycles. For my scenario, I think I can skip the event based polling thing and keep on calling syscall.Read as there always be data in my use case.

2) Any suggestions :)

1条回答
家丑人穷心不美
2楼-- · 2019-07-19 23:51
func receive(conn net.TCPConn, kill <-chan struct{}) error {
    // Spawn a goroutine to read from the connection.
    data := make(chan []byte)
    readErr := make(chan error)
    go func() {
        for {
            b := make([]byte, 255)
            _, err := conn.Read(b)
            if err != nil {
                readErr <- err
                break
            }
            data <- b
        }
    }()


    for {
        select {
        case b := <-data:
            // Do something with `b`.
        case err := <-readErr:
            // Handle the error.
            return err
        case <-kill:
            // Received kill signal, returning without closing the connection.
            return nil
        }
    }
}

Send an empty struct to kill from another goroutine to stop receiving from the connection. Here's a program that stops receiving after a second:

kill := make(chan struct{})
go func() {
    if err := receive(conn, kill); err != nil {
        log.Fatal(err)
    }
}()
time.Sleep(time.Second)
kill <- struct{}{}

This might not be exactly what you're looking for, because the reading goroutine would still be blocked on Read even after you send to kill. However, the goroutine that handles incoming reads would terminate.

查看更多
登录 后发表回答