最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang通過(guò)包長(zhǎng)協(xié)議處理TCP粘包的問(wèn)題解決

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

tcp粘包產(chǎn)生的原因這里就不說(shuō)了,因?yàn)榇蠹夷芩阉鱐CP粘包的處理方法,想必大概對(duì)TCP粘包有了一定了解,所以我們直接從處理思路開(kāi)始講起

在這里插入圖片描述

tcp粘包現(xiàn)象代碼重現(xiàn)

首先,我們來(lái)重現(xiàn)一下TCP粘包,然后再此基礎(chǔ)之上解決粘包的問(wèn)題,這里給出了client和server的示例代碼如下

/*
    文件名:client.go
    client客戶端的示例代碼(未處理粘包問(wèn)題)
    通過(guò)無(wú)限循環(huán)無(wú)時(shí)間間隔發(fā)送數(shù)據(jù)給server服務(wù)器
    server將會(huì)不間斷的出現(xiàn)TCP粘包問(wèn)題
*/
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
        }
        // 這里通過(guò)限制發(fā)送頻率和時(shí)間間隔來(lái)解決TCP粘包
        // 雖然能夠?qū)崿F(xiàn),但是頻率被限制,效率也會(huì)被限制
        // time.Sleep(time.Second * 1)
    }
}
/*
    文件名:server.go
    server服務(wù)端的示例代碼(未處理粘包問(wèn)題)
    服務(wù)端接收到數(shù)據(jù)后立即打印
    此時(shí)將會(huì)不間斷的出現(xiàn)TCP粘包問(wèn)題
*/
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))
    }
}

按順序啟動(dòng)server.go和client.go,正常情況下每行會(huì)輸出Hello, World!字樣,出現(xiàn)TCP粘包后,將會(huì)出現(xiàn)類似Hello, World!Hello之類的字樣,后一個(gè)包粘到前一個(gè)包了

解決TCP粘包有很多種方法,歸結(jié)起來(lái)就是通過(guò)自定義通訊協(xié)議來(lái)解決,例如分隔符協(xié)議、MQTT協(xié)議、包長(zhǎng)協(xié)議等等,而我們這里介紹的就是通過(guò)包長(zhǎng)協(xié)議來(lái)解決問(wèn)題的,當(dāng)然包長(zhǎng)協(xié)議也有很多種自定義的方法

通過(guò)演示的結(jié)果,我們可以看出來(lái),后一個(gè)包粘到了前一個(gè)包,而且后一個(gè)包不一定是一個(gè)完整的包,也很有可能第一次收到的數(shù)據(jù)包也不是完整的數(shù)據(jù)包

tcp粘包問(wèn)題處理方法

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

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

/*
    文件名:client.go
    使用包長(zhǎng)協(xié)議,封裝TCP包并循環(huán)發(fā)送給server服務(wù)端
*/
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()函數(shù)計(jì)算出待發(fā)送字符串的長(zhǎng)度,然后使用make()函數(shù)創(chuàng)建一個(gè)[]byte切片作為待組裝發(fā)送的數(shù)據(jù)包緩存sbyte,長(zhǎng)度就是2字節(jié)的包頭+字符串的長(zhǎng)度,接著通過(guò)binary.BigEndian.PutUint16()函數(shù)來(lái)對(duì)數(shù)據(jù)包緩存sbyte進(jìn)行操作,將字符串的長(zhǎng)度信息寫(xiě)入2字節(jié)的包頭中,緊接著又通過(guò)copy()完成封包組裝,最后通過(guò)conn.Write()將封包發(fā)送出去,這樣子發(fā)送出去的數(shù)據(jù)大概長(zhǎng)成下面的樣子

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

其中,封包整體長(zhǎng)16bytes,Hello, Server!則長(zhǎng)14bytes

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

/*
    文件名:server.go
    使用包長(zhǎng)協(xié)議,處理接收到的封包數(shù)據(jù)
    收到的封包數(shù)據(jù),可能存在幾種情況:
    1、封包總長(zhǎng)度不足2字節(jié)(這種情況不能完整獲取包頭),緩存起來(lái)與下次獲取的數(shù)據(jù)拼接
    2、封包總長(zhǎng)度剛好2字節(jié),數(shù)據(jù)長(zhǎng)度信息讀出來(lái)是0,這種情況可以正常處理并清空緩存
    3、封包總長(zhǎng)度大于2字節(jié),數(shù)據(jù)長(zhǎng)度信息大于封包數(shù)據(jù)實(shí)際長(zhǎng)度,表示數(shù)據(jù)包不完整,需要等到下一次讀取再拼接起來(lái)
    4、封包總長(zhǎng)度大于2字節(jié),數(shù)據(jù)長(zhǎng)度信息等于封包數(shù)據(jù)實(shí)際長(zhǎng)度,這種情況(理想情況)可以正常處理并清空緩存
    5、封包總長(zhǎng)度大于2字節(jié),數(shù)據(jù)長(zhǎng)度信息小于封包實(shí)際長(zhǎng)度,表示數(shù)據(jù)包發(fā)生TCP粘包了,讀取實(shí)際數(shù)據(jù)后,將剩余部分緩存起來(lái)等待下次拼接
    PS:這里只總結(jié)出了這幾種情況,其他未發(fā)現(xiàn)的情況還需另外處理
*/
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)) // 得到數(shù)據(jù)
            if uint16(length) == 2+dataLength {
                tmp = []byte{}
            } else if uint16(length) > 2+dataLength {
                tmp = tmp[dataLength+2:]
            }
        }
        // fmt.Println(string(buf))
    }
}

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

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

相關(guān)文章

  • 詳解如何修改Go結(jié)構(gòu)體的私有字段

    詳解如何修改Go結(jié)構(gòu)體的私有字段

    在 Go 語(yǔ)言中,結(jié)構(gòu)體字段的訪問(wèn)權(quán)限是由字段名的首字母決定的:首字母大寫(xiě)表示公共字段(public),首字母小寫(xiě)表示私有字段(private),本文給大家介紹了如何修改Go結(jié)構(gòu)體的私有字段,需要的朋友可以參考下
    2025-01-01
  • golang結(jié)構(gòu)體與json格式串實(shí)例代碼

    golang結(jié)構(gòu)體與json格式串實(shí)例代碼

    本文通過(guò)實(shí)例代碼給大家介紹了golang結(jié)構(gòu)體與json格式串的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-10-10
  • Go語(yǔ)言讀取,設(shè)置Cookie及設(shè)置cookie過(guò)期方法詳解

    Go語(yǔ)言讀取,設(shè)置Cookie及設(shè)置cookie過(guò)期方法詳解

    這篇文章主要介紹了Go語(yǔ)言讀取,設(shè)置Cookie及設(shè)置cookie過(guò)期方法詳解,需要的朋友可以參考下
    2022-04-04
  • golang中使用proto3協(xié)議導(dǎo)致的空值字段不顯示的問(wèn)題處理方案

    golang中使用proto3協(xié)議導(dǎo)致的空值字段不顯示的問(wèn)題處理方案

    這篇文章主要介紹了golang中使用proto3協(xié)議導(dǎo)致的空值字段不顯示的問(wèn)題處理方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Go語(yǔ)言并發(fā)編程 互斥鎖詳情

    Go語(yǔ)言并發(fā)編程 互斥鎖詳情

    在并發(fā)編程中,多個(gè)Goroutine訪問(wèn)同一塊內(nèi)存資源時(shí)可能會(huì)出現(xiàn)競(jìng)態(tài)條件,我們需要在臨界區(qū)中使用適當(dāng)?shù)耐讲僮鱽?lái)以避免競(jìng)態(tài)條件。Go 語(yǔ)言中提供了很多同步工具,本文將介紹互斥鎖Mutex和讀寫(xiě)鎖RWMutex的使用方法。
    2021-10-10
  • 在Golang中讀寫(xiě)CSV文件的操作指南

    在Golang中讀寫(xiě)CSV文件的操作指南

    CSV(逗號(hào)分隔值)文件是一種常見(jiàn)的數(shù)據(jù)存儲(chǔ)格式,廣泛應(yīng)用于數(shù)據(jù)導(dǎo)入、導(dǎo)出、分析和交換等場(chǎng)景,在Golang中,有許多庫(kù)和工具可以幫助我們讀取和寫(xiě)入CSV文件,使數(shù)據(jù)處理變得簡(jiǎn)單而高效,本文將深入探討如何在Golang中使用標(biāo)準(zhǔn)庫(kù)以及第三方庫(kù)來(lái)讀寫(xiě)CSV文件
    2023-11-11
  • golang執(zhí)行命令獲取執(zhí)行結(jié)果狀態(tài)(推薦)

    golang執(zhí)行命令獲取執(zhí)行結(jié)果狀態(tài)(推薦)

    這篇文章主要介紹了golang執(zhí)行命令獲取執(zhí)行結(jié)果狀態(tài)的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2019-11-11
  • golang敏感詞過(guò)濾的實(shí)現(xiàn)

    golang敏感詞過(guò)濾的實(shí)現(xiàn)

    本文主要介紹了golang敏感詞過(guò)濾的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • GO workPool的線程池實(shí)現(xiàn)

    GO workPool的線程池實(shí)現(xiàn)

    本文主要介紹了GO workPool的線程池實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Go語(yǔ)言sort包函數(shù)使用示例

    Go語(yǔ)言sort包函數(shù)使用示例

    這篇文章主要為大家介紹了Go語(yǔ)言sort包函數(shù)使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論

北碚区| 资阳市| 开江县| 安达市| 兖州市| 寿宁县| 秦安县| 上林县| 江口县| 白朗县| 隆子县| 新化县| 银川市| 崇州市| 刚察县| 荥阳市| 故城县| 会泽县| 辉县市| 疏勒县| 错那县| 霸州市| 贵定县| 延寿县| 鸡东县| 织金县| 奈曼旗| 子长县| 景泰县| 成都市| 吉木萨尔县| 榕江县| 西安市| 大英县| 双江| 互助| 昭觉县| 武隆县| 张北县| 色达县| 芜湖县|