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