英文:
non-blocking read with select
问题
假设我们有以下关于服务器的抽象(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
}
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
}
这个设计的思路很简单:创建一个连接,如果一切顺利,为它获取一个带缓冲的读取器(我不使用conn.read
函数,只是因为如果服务器要求,我会启动TLS连接并重新分配R为基于它创建的读取器,但现在不是这种情况)。
现在我们有了两个函数,write
和read
:
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(在createServer()
中启动的那个)发送的数据。我的想法是调用write
然后读取响应。我创建了所有这些是因为服务器有时会将响应作为两部分发送,例如,我必须传统地调用read()
两次。但是这并没有起作用,我的read
函数(见上文)根本没有返回任何内容。最有可能的原因是服务器没有来得及发送数据,而我的函数因为通道中没有数据而退出。但一个问题是,尽管我多次调用write
和read
,read
始终返回空值。
所以我猜我可能有一些一般性的设计错误,问题是社区能否帮助我找到它。谢谢。
英文:
Let's say we have the following abstraction for a server (XMPP, but here it doesn't matter a lot):
type Server struct {
Addr string
Conn net.Conn
tlsCon *tls.Conn
R *bufio.Reader
SSL bool
reader chan string
}
And a helper function to init it:
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
}
The idea is simple: create a connection and, if everything went well, get a buffered reader for it (I don't use the conn.read function just because, if server requires, I start TLS connection and reassign R to reader created based of it, but now this isn't the case).
Now we have the two functions, write and read:
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
}
So I want the read function to receive data sent by the goroutine which reads from socket (the one started in createServer()) from chan . The idea is to call write and than read the response. I created all of this because the server some times sends response as two parts, e.g. I have to do traditional read() 2 times.
But this didn't work, my read function (see above) simply returns nothing. Most probably, that's because the server doesn't manage to send back the data and my function exits because there's nothing in the chan. But one concern is that although I call write and read multiple times, read always returns nothing.
So I guess I have some general design error and the question is if the community can help me find it. Thanks.
答案1
得分: 1
问题在于你的select
选择了默认分支,因为读取通道中还没有任何内容,所以它立即中断了循环。(https://golang.org/ref/spec#Select_statements)
你希望读取操作阻塞,直到接收到足够的数据。例如,如果你知道响应以"\n"结尾,那么就继续读取并且在接收到"\n"或通道关闭之前不中断。
也许一个更好的解决方案是在goroutine中使用bufio.Scanner与读取器一起使用,如果传入的数据以换行符分隔,并使用chan string
将整个字符串传递给另一个goroutine。
你还可以使用Scanner.Split来设置不同的分割函数。
(参见关于tcp和分隔符的这个问题和答案: https://stackoverflow.com/questions/25766193/golang-tcp-client-server-data-delimiter)
编辑: 使用xml.Decoder.Token可以从流中持续读取标记,并适当处理它们。你可以将其与Decode (这将解码下一个标记)或DecodeElement (这允许你解码刚刚读取的标记)结合使用来解码xml。
英文:
The problem is that your select
selects the default branch because there's nothing in the reader channel yet, so it breaks the for immediately. (https://golang.org/ref/spec#Select_statements)
You want read to block until you receive enough data for it. E.g. if you know that your response needs to end with a "\n", keep reading and don't break until you get a "\n", or the channel is closed.
Perhaps a better solution would be to use bufio.Scanner in goroutine with the reader, if the incoming data is newline delimited, and use a chan string
to pass the whole string to the other goroutine.
You can also use Scanner.Split to set a different splitter function.
(see also this question and answer about tcp and delimiters: https://stackoverflow.com/questions/25766193/golang-tcp-client-server-data-delimiter)
Edit: Using xml.Decoder.Token you can keep reading the tokens from the stream, and handle them appropriately. You can combine this with Decode (this will decode the next token) or DecodeElement (this allows you to decode the just read token) to decode the xml.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论