golang socket中tcp中的ioutil.ReadAll阻塞的問題

最近有寫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結束符或者加入其他邏輯判斷區分。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。