欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang通過包長協(xié)議處理TCP粘包的問題解決

 更新時間:2022年06月23日 09:27:59   作者:地下十一樓的森琦  
本文主要介紹了Golang通過包長協(xié)議處理TCP粘包的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

tcp粘包產生的原因這里就不說了,因為大家能搜索TCP粘包的處理方法,想必大概對TCP粘包有了一定了解,所以我們直接從處理思路開始講起

在這里插入圖片描述

tcp粘包現象代碼重現

首先,我們來重現一下TCP粘包,然后再此基礎之上解決粘包的問題,這里給出了client和server的示例代碼如下

/*
    文件名:client.go
    client客戶端的示例代碼(未處理粘包問題)
    通過無限循環(huán)無時間間隔發(fā)送數據給server服務器
    server將會不間斷的出現TCP粘包問題
*/
package main
import (
    "fmt"
    "net"
)
func main() {
    conn, err := net.Dial("tcp", ":9000")
    if err != nil {
        return
    }
    defer conn.Close()
    for {
        s := "Hello, Server!"
        n, err := conn.Write([]byte(s))
        if err != nil {
            fmt.Println("Error:", err)
            fmt.Println("Error N:", n)
            return
        }
        // 這里通過限制發(fā)送頻率和時間間隔來解決TCP粘包
        // 雖然能夠實現,但是頻率被限制,效率也會被限制
        // time.Sleep(time.Second * 1)
    }
}
/*
    文件名:server.go
    server服務端的示例代碼(未處理粘包問題)
    服務端接收到數據后立即打印
    此時將會不間斷的出現TCP粘包問題
*/
package main
import (
    "fmt"
    "net"
)
func main() {
    ln, err := net.Listen("tcp", ":9000")
    if err != nil {
        return
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            continue
        }
        go handleConnection(conn)
    }
}
func handleConnection(conn net.Conn) {
    defer conn.Close()
    tmp := []byte{}
    for {
        buf := make([]byte, 1024)
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("Read Error:", err)
            fmt.Println("Read N:", n)
            return
        }
        fmt.Println(string(buf))
    }
}

按順序啟動server.go和client.go,正常情況下每行會輸出Hello, World!字樣,出現TCP粘包后,將會出現類似Hello, World!Hello之類的字樣,后一個包粘到前一個包了

解決TCP粘包有很多種方法,歸結起來就是通過自定義通訊協(xié)議來解決,例如分隔符協(xié)議、MQTT協(xié)議、包長協(xié)議等等,而我們這里介紹的就是通過包長協(xié)議來解決問題的,當然包長協(xié)議也有很多種自定義的方法

通過演示的結果,我們可以看出來,后一個包粘到了前一個包,而且后一個包不一定是一個完整的包,也很有可能第一次收到的數據包也不是完整的數據包

tcp粘包問題處理方法

這樣我們就有必要校驗每次收到的數據包是否是我們期望收到的,比較直觀的,客戶端和服務端雙方協(xié)商某種協(xié)議,例如包長協(xié)議,在客戶端發(fā)送數據時,先計算一下數據的長度(假設用2字節(jié)的uint16表示),然后將計算得到的長度和實際的數據組裝成一個包,最后發(fā)送給服務端;而服務端接收到數據時,先讀取2字節(jié)的數據長度信息(可能不足2字節(jié),程序需要針對這種情況設計),然后根據數據長度來讀取后邊的數據(可能會存在數據過剩、數據剛好、數據不足等情況,程序需要針對這些情況設計)

有了思路之后,我們就需要對發(fā)送端和接收端的數據進行處理了,因為發(fā)送端較為簡單,不需要考慮其他情況,只管封裝數據包發(fā)送,所以這里我們先對發(fā)送端client進行處理

/*
    文件名:client.go
    使用包長協(xié)議,封裝TCP包并循環(huán)發(fā)送給server服務端
*/
package main
import (
    "encoding/binary"
    "fmt"
    "net"
)
func main() {
    conn, err := net.Dial("tcp", ":9000")
    if err != nil {
        return
    }
    defer conn.Close()
    for {
        s := "Hello, Server!"
        sbytes := make([]byte, 2+len(s))
        binary.BigEndian.PutUint16(sbytes, uint16(len(s)))
        copy(sbytes[2:], []byte(s))
        n, err := conn.Write(sbytes)
        if err != nil {
            fmt.Println("Error:", err)
            fmt.Println("Error N:", n)
            return
        }
        // time.Sleep(time.Second * 1)
    }
}

按照我們的思路,首先使用len()函數計算出待發(fā)送字符串的長度,然后使用make()函數創(chuàng)建一個[]byte切片作為待組裝發(fā)送的數據包緩存sbyte,長度就是2字節(jié)的包頭+字符串的長度,接著通過binary.BigEndian.PutUint16()函數來對數據包緩存sbyte進行操作,將字符串的長度信息寫入2字節(jié)的包頭中,緊接著又通過copy()完成封包組裝,最后通過conn.Write()將封包發(fā)送出去,這樣子發(fā)送出去的數據大概長成下面的樣子

[0][14][H][e][l][l][o][,][ ][S][e][r][v][e][r][!]

其中,封包整體長16bytes,Hello, Server!則長14bytes

好了,至此數據將會循環(huán)不簡短的發(fā)送給服務端,接下來我們就要對服務端server.go進行處理了,先上代碼

/*
    文件名:server.go
    使用包長協(xié)議,處理接收到的封包數據
    收到的封包數據,可能存在幾種情況:
    1、封包總長度不足2字節(jié)(這種情況不能完整獲取包頭),緩存起來與下次獲取的數據拼接
    2、封包總長度剛好2字節(jié),數據長度信息讀出來是0,這種情況可以正常處理并清空緩存
    3、封包總長度大于2字節(jié),數據長度信息大于封包數據實際長度,表示數據包不完整,需要等到下一次讀取再拼接起來
    4、封包總長度大于2字節(jié),數據長度信息等于封包數據實際長度,這種情況(理想情況)可以正常處理并清空緩存
    5、封包總長度大于2字節(jié),數據長度信息小于封包實際長度,表示數據包發(fā)生TCP粘包了,讀取實際數據后,將剩余部分緩存起來等待下次拼接
    PS:這里只總結出了這幾種情況,其他未發(fā)現的情況還需另外處理
*/
package main
import (
    "encoding/binary"
    "fmt"
    "net"
)
func main() {
    ln, err := net.Listen("tcp", ":9000")
    if err != nil {
        return
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            continue
        }
        go handleConnection(conn)
    }
}
func handleConnection(conn net.Conn) {
    defer conn.Close()
    tmp := []byte{}
    for {
        buf := make([]byte, 1024)
        // fmt.Println("len:", len(buf), " cap:", cap(buf))
        n, err := conn.Read(buf)
        if err != nil {
            if e, ok := err.(*net.OpError); ok {
                fmt.Println(e.Source, e.Addr, e.Net, e.Op, e.Err)
                if e.Timeout() {
                    fmt.Println("Timeout Error")
                }
            }
            fmt.Println("Read Error:", err)
            fmt.Println("Read N:", n)
            return
        }
        if n == 0 {
            fmt.Println("Read N:", n)
            return
        }
        tmp = append(tmp, buf[:n]...)
        length := len(tmp)
        if length < 2 {
            continue
        }
        if length >= 2 {
            head := make([]byte, 2)
            copy(head, tmp[:2])
            dataLength := binary.BigEndian.Uint16(head)
            data := make([]byte, dataLength)
            copy(data, tmp[2:dataLength+2])
            fmt.Println(string(data)) // 得到數據
            if uint16(length) == 2+dataLength {
                tmp = []byte{}
            } else if uint16(length) > 2+dataLength {
                tmp = tmp[dataLength+2:]
            }
        }
        // fmt.Println(string(buf))
    }
}

ps:這里的示例代碼不能直接用于生產環(huán)境,只是提供tcp粘包處理的思路過程,代碼還是存在一些問題的,例如server.go服務端還沒有對第3種情況進行處理,封包總長度大于2字節(jié),數據長度信息大于封包數據實際長度,表示數據包不完整,需要等到下一次讀取再拼接起來

 到此這篇關于Golang通過包長協(xié)議處理TCP粘包的問題解決的文章就介紹到這了,更多相關Golang TCP粘包內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go指針內存與安全性深入理解

    Go指針內存與安全性深入理解

    這篇文章主要為大家介紹了Go指針內存與安全性深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • GO中高效的將int轉換為string的方法與源碼

    GO中高效的將int轉換為string的方法與源碼

    本文將從逐步介紹幾種在?Go?中將?int?轉換為?string?的常見方法,并重點剖析這幾種方法在性能上的特點,另外,還會重點介紹?FormatInt?高效的算法實現,需要的朋友可以參考下
    2024-01-01
  • Golang開發(fā)gRPC服務入門介紹

    Golang開發(fā)gRPC服務入門介紹

    這篇文章主要介紹了Golang開發(fā)gRPC服務,Golang開發(fā)gRPC應用程序的套路也已經很清晰,這篇文章就來做一個簡單的介紹,算是入門,需要的朋友可以參考下
    2022-04-04
  • Go語言中CGO的使用實踐

    Go語言中CGO的使用實踐

    本文主要介紹了Go語言中CGO的使用實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • 詳解如何利用Golang泛型提高編碼效率

    詳解如何利用Golang泛型提高編碼效率

    Golang的泛型已經出來有一段時間了,大家應該或多或少對它有所了解。雖然Golang的泛型在功能上確實比較簡單,而且確實可能會增加代碼的復雜度,過度使用可能還會降低代碼可讀性。本文就來介紹一下Golang泛型的相關知識吧
    2023-04-04
  • golang讀取文件的常用方法總結

    golang讀取文件的常用方法總結

    今天小編就為大家分享一篇關于golang讀取文件的常用方法總結,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • go 代碼的調試---打印調用堆棧的實例

    go 代碼的調試---打印調用堆棧的實例

    下面小編就為大家?guī)硪黄猤o 代碼的調試---打印調用堆棧的實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • Go語言基礎結構體用法及示例詳解

    Go語言基礎結構體用法及示例詳解

    這篇文章主要為大家介紹了Go語言基礎結構體的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2021-11-11
  • 使用golang開發(fā)一個curl命令行工具

    使用golang開發(fā)一個curl命令行工具

    這篇文章主要為大家詳細介紹了如何使用golang開發(fā)一個簡單的curl命令行工具,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-11-11
  • 詳解Golang五種原子性操作的用法

    詳解Golang五種原子性操作的用法

    本文主要介紹了詳解Golang五種原子性操作的用法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評論