tcp socket 錯誤篇2 - golang

     先讓我們看一段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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章