【问题标题】:non-blocking read with select使用 select 进行非阻塞读取
【发布时间】:2016-03-24 07:36:04
【问题描述】:

假设我们对服务器有以下抽象(XMPP,但在这里并不重要):

type Server struct {
    Addr   string
    Conn   net.Conn
    tlsCon *tls.Conn
    R      *bufio.Reader
    SSL    bool
    reader chan string
}

还有一个辅助函数来初始化它:

func createServer(domain string) (s *Server, err error) {
    conn, err := net.Dial("tcp", domain+":5222")
    if err != nil {
        return nil, err
    }
    s = &Server{domain, conn, nil, bufio.NewReader(conn), false, make(chan string, 8)}
    go func(t *Server) {
        for {
            buf := bufsPool.Get().([]byte)
            n, err := s.R.Read(buf)
            if err == nil {
                s.reader <- string(buf[:n])
            } else {
                fmt.Println(err)
            }
        }
    }(s)

    return s, err
}

这个想法很简单:创建一个连接,如果一切顺利,为它获取一个缓冲读取器(我不使用 conn.read 函数只是因为,如果服务器需要,我启动 TLS 连接并将 R 重新分配给reader 基于它创建的,但现在情况并非如此)。

现在我们有了两个函数,写和读:

func (s *Server) read() (t string) {
    t = ""
Inn:
    for {
        select {
        case u := <-s.reader:
            fmt.Println("debug", u)
            t += u
        default:
            break Inn
        }
    }
    return t
}

所以我希望 read 函数接收 goroutine 发送的数据,该数据从 chan 的套接字(在 createServer() 中启动的那个)读取。这个想法是调用 write 而不是读取响应。我创建了所有这些,因为服务器有时将响应分为两部分,例如我必须做传统的 read() 2 次。 但这不起作用,我的读取函数(见上文)根本不返回任何内容。很可能,那是因为服务器无法发送回数据并且我的函数退出,因为 chan 中没有任何内容。但一个问题是,虽然我多次调用 write 和 read,但 read 总是什么都不返回。

所以我想我有一些一般的设计错误,问题是社区是否可以帮助我找到它。谢谢。

【问题讨论】:

  • 有关如何从 Go 读取 XMPP 流的信息,请参阅 stackoverflow.com/questions/36105199/…
  • 哦,你问了这个问题。您需要使用 XML 解析器从 XMPP 流中正确解析节。您的问题意味着您依赖于数据流的缓冲和碎片。这通常不起作用。

标签: sockets go concurrency


【解决方案1】:

问题是您的select 选择了默认分支,因为阅读器通道中还没有任何内容,因此它立即中断了 for 。 (https://golang.org/ref/spec#Select_statements)

您希望 read 阻塞,直到您收到足够的数据。例如。如果您知道您的响应需要以“\n”结尾,请继续阅读,在收到“\n”或频道关闭之前不要中断。

也许更好的解决方案是在 goroutine 中与 reader 一起使用 bufio.Scanner,如果传入的数据是换行符分隔的,并使用 chan string 将整个字符串传递给另一个 goroutine。

您也可以使用Scanner.Split 设置不同的拆分器功能。

(另请参阅有关 tcp 和分隔符的问题和答案:Golang: TCP client/server data delimiter

编辑:使用xml.Decoder.Token,您可以继续从流中读取令牌,并适当地处理它们。您可以将它与Decode(这将解码下一个令牌)或DecodeElement(这允许您解码刚刚读取的令牌)结合起来解码xml。

【讨论】:

  • 感谢您的回复。分隔符的问题在于 XMPP 服务器不发送任何类型的分隔符。它发送 XML 并以“>”结尾,没有换行符等(我们显然不能使用“>”作为分隔符)。我最初认为处理 EOF 可能会有所帮助,但服务器并不总是发送它,只有几次。因此,当我收到数据时,我不知道是否需要再次读取。我可以凭经验确定何时将数据作为两部分发送,但我怀疑它不能从服务器更改为服务器,尽管我在 10 多个服务器上尝试了我的代码。
  • 我不熟悉 XMPP,但看起来你必须检查结束根标签,并且可能也处理格式错误的 xml(例如,如果你得到一个 &lt;?xml 或另一个根标记,扔掉之前的部分 xml 并重新开始)。看看这个问题的答案:stackoverflow.com/questions/10205760/…
猜你喜欢
  • 1970-01-01
  • 2014-11-17
  • 1970-01-01
  • 2016-04-02
  • 2010-09-27
相关资源
最近更新 更多