golang socket中tcp中的ioutil.ReadAll阻塞的问题

哆啦在这A梦在哪 · · 1525 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

最近有写socket相关的东西,刚好有人问我这个问题,我就记录一下,发现这个问题还是有很多人不了解的。
在socket-tcp的代码中大致这两种接受数据的方法,con.Read以及ioutil.ReadAll,这两种方法的区别是什么,以及他们的使用方法。

区别

func (c *conn) Read(b []byte) (int, error)
在socket中,Read方法直接读取一段指定长度的字节,当然这个方法也是阻塞的,如果对面没有发送数据,他会一直等到超时,
ioutil.ReadAll
他一直读取直到读取到err或eof,就是出错或者到文件结束标志,一般用来一次性获取所有的流数据。

问题发生

先看这段代码read部分,双方建立了长连接后,如果conn.Read(request)如果换成ioutil.ReadAll方法来读取,每次读取到这个ReadAll的时候,居然阻塞了???这是为什么。一直等到超时断开连接,才能收到这个数据。

解决过程

使用read方法输出每次的字节流内容,发现并没有EOF方法,只有当连接断开,他才读取到,或者说读取到长度为0,这就明白了,这里在连接中,ioutil.ReadAll并没有接受到结束标志,也就是说这个for中客户端可能会一直在发数据,所以ioutil.ReadAll会一直读取,直到他断开连接或者手动写入结束标识。
如果要使用ioutil.ReadAll,那就不能这么使用长连接,要一次性使用的话连接后发送完数据就断开即可,类似于http请求一次,看实际需求。
服务端代码如下

package main
import (
    "encoding/json"
    "fmt"
    "net"
    "os"
    "time"
)
func main() {
    tcpAddr, err := net.ResolveTCPAddr("tcp4", ":1200")
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        go handleClient(conn)
    }
}
func handleClient(conn net.Conn) {
    conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout
    log.Println("adress:", conn.LocalAddr().String())
    defer conn.Close() // close connection before exit
    for {
        conn.Write([]byte("123"))
        request := make([]byte, 128)
        readLen, err := conn.Read(request)
        if readLen == 0 {
            break // connection already closed by client
        }
        log.Println(string(request))
        if err != nil {
            break
        }
    }
}

func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

客户端代码如下

package main
import (
    "fmt"
    "net"
    "os"
    "time"
)
func main() {
    tcpAddr, err := net.ResolveTCPAddr("tcp4", ":1200")
    checkError(err)
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    conn.Write([]byte("fi:1234"))
    checkError(err)
    for {
        result := make([]byte, 256)
        _, err = conn.Read(result)
        checkError(err)
        fmt.Println("get mes:", string(result))
        time.Sleep(time.Second * 1)
        conn.Write([]byte("this is fi" + time.Now().Format("2006-01-02 15:04:05")))
    }
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

总结

注意:理论上con.Read和ioutil.ReadAll都是可以读取,但是ioutil.ReadAll要收到error或者EOF才会返回,也就是说如果发送端,发送数据后,调用 Close 关闭连接,不等待服务端的返回数据,服务端可 以用 ioutil.ReadAll 来读取数据,这时可以判断出 EOF,读取结束。但如果客户端发送数据 后,没有关闭,而是等待服务端的数据返回,用 ReadAll 是不行的,你们长连接交互的话,就用read。一定要用ioutil.ReadAll的话,试着手动写入EOF结束符或者加入其他逻辑判断区分。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:哆啦在这A梦在哪

查看原文:golang socket中tcp中的ioutil.ReadAll阻塞的问题

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1525 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传