首页 > 解决方案 > 如何使用 context.Context 与 tcp 连接读取

问题描述

我正在尝试在用户和 tcp 之间创建一个中间层,其中包含SendReceive功能。目前,我正在尝试整合上下文,以便SendandReceive尊重上下文。但是,我不知道如何让他们尊重上下文的取消。到目前为止,我得到了以下内容。

// c.underlying is a net.Conn

func (c *tcpConn) Receive(ctx context.Context) ([]byte, error) {
    if deadline, ok := ctx.Deadline(); ok {
        // Set the read deadline on the underlying connection according to the
        // given context. This read deadline applies to the whole function, so
        // we only set it once here. On the next read-call, it will be set
        // again, or will be reset in the else block, to not keep an old
        // deadline.
        c.underlying.SetReadDeadline(deadline)
    } else {
        c.underlying.SetReadDeadline(time.Time{}) // remove the read deadline
    }

    // perform reads with
    // c.underlying.Read(myBuffer)

    return frameData, nil
}

但是,据我了解该代码,这仅尊重 a context.WithTimeoutor context.WithDeadline,而不是 a context.WithCancel。如果可能的话,我想以某种方式将其传递给连接,或者实际上中止读取过程。

我怎样才能做到这一点?

注意:如果可能的话,我想避免另一个函数读取另一个 goroutine 并将结果推送回通道,因为那样,当调用 时cancel,我正在通过网络读取 2GB,这实际上并没有取消读取,并且资源仍在使用中。但是,如果不能以另一种方式实现,我想知道是否有比具有两个通道的函数更好的方法,一个用于[]byte结果,一个用于error.

编辑: 使用以下代码,我可以尊重取消,但它不会中止读取。

    // apply deadline ...

    result := make(chan interface{})
    defer close(result)
    go c.receiveAsync(result)
    select {
    case res := <-result:
        if err, ok := res.(error); ok {
            return nil, err
        }
        return res.([]byte), nil
    case <-ctx.Done():
        return nil, ErrTimeout
    }
}

func (c *tcpConn) receiveAsync(result chan interface{}) {
    // perform the reads and push either an error or the
    // read bytes to the result channel

标签: gonetworking

解决方案


如果连接可以在取消时关闭,您可以设置一个 goroutine 以在Receive方法内取消时关闭连接。如果稍后必须再次重用连接,则无法取消Read正在进行的连接。

recvDone := make(chan struct{})
defer close(recvDone)
// setup the cancellation to abort reads in process
go func() {
    select {
    case <-ctx.Done():
        c.underlying.CloseRead()
        // Close() can be used if this isn't necessarily a TCP connection
    case <-recvDone:
    }
}()

如果您想返回取消错误,这将需要更多的工作,但是这CloseRead将提供一种干净的方法来停止任何挂起的 TCPRead调用。


推荐阅读