先讓我們看一段tcp 的socket代碼:
l, e := net.Listen("tcp", ":9090") //監聽
if e != nil {
fmt.Println(e)
return
}
defer l.Close()
for {
c, e := l.Accept()
if e != nil {
fmt.Println(e)
c.Close()
break
}
buf := make([]byte, 10)
for {
//t := make([]byte, 10)
_, e := c.Read(buf)
//t=t[:n]
//buf = append(buf,t...)
if e == io.EOF {//對端關閉退出
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
}
這段代碼是我們在用go做socket的時候經常能百度出的一種,基本上搜出來的和這個都大同小異。
這段代碼是一個服務端部分的代碼,但在讀寫上無論在服務端還是客戶端基本都是一致的。
這段代碼大致意思是,建立一個socket監聽,同時循環讀取客戶端發來的消息。(因爲我們並不知道客戶端會發來多少數據)
首先讓我們看第一個錯誤,也是一個忽視點。重點就在這句:
if e == io.EOF {//對端關閉退出
break
}
這裏說的很清楚,對端關閉後,纔會EOF。那麼問題來了,如果對端不關閉會怎麼樣呢?
首先讓我們看看不關閉會輸出什麼:
可以看到他陷入了死循環永遠也出不來了。那如何解決這個問題呢?
其實很簡單,read方法會返回讀取的字節和錯誤,我們只需要判斷當讀取字節數爲0就讓爲是一次讀取完畢就可以了。代碼如下:
for {
//t := make([]byte, 10)
n, e := c.Read(buf) //這裏爲修改的地方
//t=t[:n]
//buf = append(buf,t...)
if n==0 || e == io.EOF {//這裏爲修改的地方
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
修改成這樣後再讓我們看一下輸出:
我在客戶端發送的是,我是 tcp socket 測試。但是我們看到接收的字符不全還有亂碼。這是爲什麼呢?讓我們該一句代碼再看一下,這次我們把buf的長度輸出一下:
可以看到還是最初的10,並沒有像我們想的一樣進行擴容。這就是爲什麼我們會有亂碼,因爲我們3次數據都是在一個上面進行覆蓋,並沒有在後面繼續寫。大家也應該注意到了,我上面有2行註釋的代碼:
buf := make([]byte, 0)
for {
//t := make([]byte, 10)
_, e := c.Read(buf)
//t=t[:n]
//buf = append(buf,t...)
if e == io.EOF {//對端關閉退出
break
}
fmt.Println("===")
}
fmt.Println(string(buf))
我們只需要把註釋打開,同時每次讀的數據放到t中即可。這就相當於我們用t作爲一個緩存區,每次緩存的數據放到buf中,最終拼接爲一次的數據,同時把buf的初始0個值。
最終代碼爲:
l, e := net.Listen("tcp", ":9090")
if e != nil {
fmt.Println(e)
return
}
defer l.Close()
for {
c, e := l.Accept()
if e != nil {
fmt.Println(e)
c.Close()
break
}
buf := make([]byte, 0)
for {
t := make([]byte, 10)
n, e := c.Read(t)
t=t[:n]
buf = append(buf,t...)
if n == 0 || e == io.EOF {
break
}
fmt.Println("===")
}
fmt.Println(string(buf),len(buf))
}
輸出結果爲 :
但是這樣就完了嗎?上述的結果只是建立在,客戶端發送一次數據後中斷的情況下。其實服務端在read拿不到數據就是阻塞。等客戶端異常中斷後就會拋出wsarecv: An existing connection was forcibly closed by the remote host.
當我們客戶端循環發送數據就會出現這種情況:
也就是你永遠也拿不到0,你的數據會無限的拼到一個buf,
這樣我就拿到了一個預期中的結果。下面讓我們總結一下,這期注意說了3個問題:
1.當對端不關閉時,異常退出,服務端判斷爲e==io.EOF時會出現無法退出循環的問題。
2.放一個buf會出現數據被覆蓋問題。解決方法是:建立一個臨時的byte數組作爲緩存區,然後統一放到buf中。同時要對緩存區做處理,因爲最後一次取出的數據一般都放不滿,會多很多0。
3..如果客戶端一直保持連接,會出現一直阻塞不退出問題。在這點上可以設置超時時間,
注: 在使用過程中,e拋出的錯誤可能不是eof,如果客戶端異常斷開可能是wsarecv: An existing connection was forcibly closed by the remote host.
所以 e == io.EOF 最好換成 e != nil