国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

TCP的粘包、拆包、解決方案以及Go語言實(shí)現(xiàn)

這篇具有很好參考價值的文章主要介紹了TCP的粘包、拆包、解決方案以及Go語言實(shí)現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

什么是粘包,拆包?

  • TCP的粘包和拆包問題往往出現(xiàn)在基于TCP協(xié)議的通訊中,比如RPC框架
  • 在使用TCP進(jìn)行數(shù)據(jù)傳輸時,由于TCP是基于字節(jié)流的協(xié)議,而不是基于消息的協(xié)議,可能會出現(xiàn)粘包(多個消息粘在一起)和拆包(一個消息被拆分成多個部分)的問題。這些問題可能會導(dǎo)致數(shù)據(jù)解析錯誤或數(shù)據(jù)不完整。

為什么UDP沒有粘包?

  • 由于UDP沒有像TCP那樣的流控制和擁塞控制機(jī)制,它不會對數(shù)據(jù)進(jìn)行緩沖或重組。因此,在UDP中,每個數(shù)據(jù)報都是獨(dú)立傳輸?shù)模ń邮斩艘淮沃荒芙邮芤粭l獨(dú)立的消息),不存在多個消息粘在一起的問題,也就沒有粘包的概念。
  • 由于UDP是不可靠的傳輸協(xié)議,它無法保證數(shù)據(jù)的可靠傳輸和順序傳輸。數(shù)據(jù)包可能會丟失、重復(fù)或亂序到達(dá)。在使用UDP時,應(yīng)該自行處理這些問題,比如使用應(yīng)答機(jī)制、超時重傳等手段來保證數(shù)據(jù)的可靠性和正確性。

粘包拆包發(fā)生場景

因?yàn)門CP是面向流,沒有邊界,而操作系統(tǒng)在發(fā)送TCP數(shù)據(jù)時,會通過緩沖區(qū)來進(jìn)行優(yōu)化,例如緩沖區(qū)為1024個字節(jié)大小。

  • 如果一次請求發(fā)送的數(shù)據(jù)量比較小,沒達(dá)到緩沖區(qū)大小,TCP則會將多個請求合并為同一個請求進(jìn)行發(fā)送,這就形成了粘包問題。
  • 如果一次請求發(fā)送的數(shù)據(jù)量比較大,超過了緩沖區(qū)大小,TCP就會將其拆分為多次發(fā)送,這就是拆包。

關(guān)于粘包和拆包可以參考下圖的幾種情況:

  • 理想狀況:兩個數(shù)據(jù)包逐一分開發(fā)送
  • 粘包:兩個包一同發(fā)送,
  • 拆包:Server接收到不完整的或多出一部分的數(shù)據(jù)包

TCP的粘包、拆包、解決方案以及Go語言實(shí)現(xiàn),Golang,tcp/ip,golang,網(wǎng)絡(luò)

常見的解決方案

  • 固定長度:發(fā)送端將每個消息固定為相同的長度,接收端按照固定長度進(jìn)行拆包。這樣可以確保每個消息的長度是一致的,但是對于不同長度的消息可能會浪費(fèi)一些空間。
  • 分隔符:發(fā)送端在每個消息的末尾添加一個特殊的分隔符(比如換行符或特殊字符),接收端根據(jù)分隔符進(jìn)行拆包。這種方法適用于消息中不會出現(xiàn)分隔符的情況。
  • 消息長度前綴:發(fā)送端在每個消息前面添加一個固定長度的消息長度字段,接收端先讀取消息長度字段,然后根據(jù)長度讀取相應(yīng)長度的數(shù)據(jù)。這種方法可以準(zhǔn)確地拆分消息,但需要保證消息長度字段的一致性。

代碼實(shí)現(xiàn)

固定長度

發(fā)送端將每個包都封裝成固定的長度,比如20字節(jié)大小。如果不足20字節(jié)可通過補(bǔ)0或空等進(jìn)行填充到指定長度;

服務(wù)端

package main

import (
	"fmt"
	"log"
	"net"
)

func main() {
	// 監(jiān)聽指定的TCP端口
	listener, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()

	fmt.Println("Server started. Listening on localhost:8080...")

	// 接收客戶端連接
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
		}

		// 啟動一個并發(fā)的goroutine來處理連接
		go handleConnection(conn)
	}
}

// 處理連接
func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 讀取固定長度的數(shù)據(jù)
	fixedLength := 20 // 假設(shè)要讀取的數(shù)據(jù)固定長度為20字節(jié)
	buffer := make([]byte, fixedLength)

	_, err := conn.Read(buffer)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Received data: %s\n", string(buffer))

	// 可以在這里對接收到的數(shù)據(jù)進(jìn)行處理和響應(yīng)
	// ...

	// 發(fā)送響應(yīng)給客戶端
	response := "Hello, Client!"
	_, err = conn.Write([]byte(response))
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Response sent successfully!")
}
 

客戶端

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    // 建立TCP連接
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // 發(fā)送固定長度的數(shù)據(jù)
    message := "Hello, Server!"
    fixedLength := 20 // 假設(shè)要發(fā)送的數(shù)據(jù)固定長度為20字節(jié)

    // 如果消息長度小于固定長度,則使用空字符填充
    if len(message) < fixedLength {
        padding := make([]byte, fixedLength-len(message))
        message += string(padding)
    }

    _, err = conn.Write([]byte(message))
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Data sent successfully!")
}
 

分隔符

發(fā)送端在每個包的末尾使用固定的分隔符,例如\n

服務(wù)端

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
)

func main() {
    // 監(jiān)聽指定的TCP端口
    listener, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()

    fmt.Println("Server started. Listening on localhost:8080...")

    // 接收客戶端連接
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal(err)
        }

        // 啟動一個并發(fā)的goroutine來處理連接
        go handleConnection(conn)
    }
}

// 處理連接
func handleConnection(conn net.Conn) {
    defer conn.Close()

    reader := bufio.NewReader(conn)

    for {
        // 讀取一行數(shù)據(jù),以分隔符"\n"作為結(jié)束標(biāo)志
        message, err := reader.ReadString('\n')
        if err != nil {
            log.Println(err)
            break
        }

        // 去除消息中的換行符
        message = strings.TrimRight(message, "\n")

        fmt.Printf("Received message: %s\n", message)

        // 可以在這里對接收到的消息進(jìn)行處理和響應(yīng)
        // ...

        // 發(fā)送響應(yīng)給客戶端
        response := "Hello, Client!\n"
        _, err = conn.Write([]byte(response))
        if err != nil {
            log.Println(err)
            break
        }
    }

    fmt.Println("Connection closed.")
}
 

客戶端

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

func main() {
    // 建立TCP連接
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    reader := bufio.NewReader(os.Stdin)

    for {
        // 讀取用戶輸入的消息
        fmt.Print("Enter message: ")
        message, err := reader.ReadString('\n')
        if err != nil {
            log.Println(err)
            break
        }

        // 發(fā)送消息給服務(wù)器
        _, err = conn.Write([]byte(message))
        if err != nil {
            log.Println(err)
            break
        }

        // 讀取服務(wù)器的響應(yīng)
        response, err := bufio.NewReader(conn).ReadString('\n')
        if err != nil {
            log.Println(err)
            break
        }

        fmt.Printf("Server response: %s", response)
    }

    fmt.Println("Connection closed.")
}
 

消息長度前綴

將消息分為頭部和消息體,頭部中保存整個消息的長度,只有讀取到足夠長度的消息之后才算是讀到了一個完整的消息;文章來源地址http://www.zghlxwxcb.cn/news/detail-616076.html

代碼實(shí)現(xiàn)

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
)

const headerSize = 4 // 頭部長度的字節(jié)數(shù)

func main() {
	// 啟動服務(wù)器
	go startServer()

	// 連接到服務(wù)器
	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		fmt.Println("連接服務(wù)器失敗:", err)
		return
	}
	defer conn.Close()

	// 發(fā)送消息
	message := "Hello, Server!"
	sendMessage(conn, message)

	// 讀取服務(wù)器響應(yīng)
	response, err := readMessage(conn)
	if err != nil {
		fmt.Println("讀取消息失敗:", err)
		return
	}
	fmt.Println("服務(wù)器響應(yīng):", response)
}

func startServer() {
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("啟動服務(wù)器失敗:", err)
		return
	}
	defer listener.Close()

	fmt.Println("服務(wù)器已啟動,等待連接...")

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("接受連接失敗:", err)
			return
		}
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	fmt.Printf("客戶端 %s 已連接\n", conn.RemoteAddr().String())

	defer conn.Close()

	// 讀取消息
	message, err := readMessage(conn)
	if err != nil {
		fmt.Println("讀取消息失敗:", err)
		return
	}
	fmt.Println("收到消息:", message)

	// 發(fā)送響應(yīng)
	response := "Hello, Client!"
	sendMessage(conn, response)
}

func sendMessage(conn net.Conn, message string) error {
	// 計算消息長度
	messageLength := len(message)

	// 將消息長度寫入頭部
	header := make([]byte, headerSize)
	binary.BigEndian.PutUint32(header, uint32(messageLength))
	if _, err := conn.Write(header); err != nil {
		return fmt.Errorf("寫入消息頭部失敗: %v", err)
	}

	// 寫入消息體
	if _, err := conn.Write([]byte(message)); err != nil {
		return fmt.Errorf("寫入消息體失敗: %v", err)
	}

	return nil
}

func readMessage(conn net.Conn) (string, error) {
	// 讀取消息頭部
	header := make([]byte, headerSize)
	if _, err := io.ReadFull(conn, header); err != nil {
		return "", fmt.Errorf("讀取消息頭部失敗: %v", err)
	}

	// 解析消息長度
	messageLength := binary.BigEndian.Uint32(header)

	// 讀取消息體
	message := make([]byte, messageLength)
	if _, err := io.ReadFull(conn, message); err != nil {
		return "", fmt.Errorf("讀取消息體失敗: %v", err)
	}

	return string(message), nil
}

  • 這段代碼中,我們啟動了一個TCP服務(wù)器,等待客戶端連接。客戶端在連接成功后,發(fā)送消息給服務(wù)器,服務(wù)器接收到消息后,返回一個響應(yīng)。
  • 在發(fā)送消息時,我們首先計算消息的長度,并將長度以4字節(jié)的大端字節(jié)序?qū)懭氲筋^部。然后,將消息體寫入

總結(jié)

  • TCP 不管發(fā)送端要發(fā)什么,都基于字節(jié)流把數(shù)據(jù)發(fā)到接收端。這個字節(jié)流里可能包含上一次想要發(fā)的數(shù)據(jù)的部分信息。接收端根據(jù)需要在消息里加上識別消息邊界的信息。不加就可能出現(xiàn)粘包問題
  • UDP 是基于數(shù)據(jù)報的傳輸協(xié)議,每個數(shù)據(jù)報都是獨(dú)立傳輸?shù)模ń邮斩艘淮沃荒芙邮芤粭l獨(dú)立的消息),不會有粘包問題。

參考

  • 硬核圖解|tcp為什么會粘包?背后的原因讓人暖心

到了這里,關(guān)于TCP的粘包、拆包、解決方案以及Go語言實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【計網(wǎng)】一起聊聊TCP的粘包拆包問題吧

    【計網(wǎng)】一起聊聊TCP的粘包拆包問題吧

    在TCP中,粘包和拆包問題是十分常見的,如 基于TCP協(xié)議 的RPC框架、Netty等。 粘包(Packet Stickiness) 指的是在網(wǎng)絡(luò)通信中,發(fā)送方連續(xù)發(fā)送的多個小數(shù)據(jù)包被接收方一次性接收的現(xiàn)象。這可能是因?yàn)榈讓觽鬏攲訁f(xié)議(如TCP)會將 多個小數(shù)據(jù)包合并成一個大的數(shù)據(jù)塊 進(jìn)行傳輸,導(dǎo)

    2024年04月12日
    瀏覽(16)
  • 網(wǎng)絡(luò)編程:TCP粘包問題——各層粘包/拆包、Nagle 算法、Go實(shí)現(xiàn)長度字段協(xié)議解決TCP粘包、使用TCP的應(yīng)用層協(xié)議設(shè)計

    網(wǎng)絡(luò)編程:TCP粘包問題——各層粘包/拆包、Nagle 算法、Go實(shí)現(xiàn)長度字段協(xié)議解決TCP粘包、使用TCP的應(yīng)用層協(xié)議設(shè)計

    1.1 TCP介紹 如上圖,TCP具有面向連接、可靠、基于字節(jié)流三大特點(diǎn)。 字節(jié)流可以理解為一個雙向的通道里流淌的數(shù)據(jù),這個數(shù)據(jù)其實(shí)就是我們常說的二進(jìn)制數(shù)據(jù),簡單來說就是一大堆 01 串。純裸TCP收發(fā)的這些 01 串之間是沒有任何邊界的,你根本不知道到哪個地方才算一條完

    2024年02月04日
    瀏覽(24)
  • Netty自定義應(yīng)用層協(xié)議逃不開的粘包和拆包處理

    Netty自定義應(yīng)用層協(xié)議逃不開的粘包和拆包處理

    導(dǎo)致一次發(fā)送的數(shù)據(jù)被分成多個數(shù)據(jù)包進(jìn)行傳輸,或者多次發(fā)送的數(shù)據(jù)被粘成一個數(shù)據(jù)包進(jìn)行傳輸 使用TCP進(jìn)行數(shù)據(jù)傳輸時,TCP是一種有序的字節(jié)流,其中是一個一個的數(shù)據(jù)報文發(fā)送到系統(tǒng)的緩沖區(qū)中。因此在發(fā)送端和接收端之間無法保證數(shù)據(jù)的分割和邊界。這就可能導(dǎo)致數(shù)據(jù)

    2023年04月23日
    瀏覽(31)
  • 解決TCP粘包/拆包問題的方法及示例

    TCP粘包和拆包是網(wǎng)絡(luò)編程中常見的問題,特別是在數(shù)據(jù)傳輸?shù)倪^程中,可能會發(fā)生將多個數(shù)據(jù)包粘在一起或?qū)⒁粋€數(shù)據(jù)包拆成多個數(shù)據(jù)包的情況,這可能會導(dǎo)致應(yīng)用程序無法正確解析數(shù)據(jù),從而造成數(shù)據(jù)錯誤或系統(tǒng)故障。本文將介紹TCP粘包和拆包的原因、解決方案以及兩個示

    2024年02月10日
    瀏覽(17)
  • TCP粘包和拆包問題及其解決方法

    TCP粘包和拆包問題及其解決方法

    含義: TCP 傳輸協(xié)議是面向流的,沒有數(shù)據(jù)包界限,也就是說消息無邊界。客戶端向服務(wù)端發(fā)送數(shù)據(jù)時,可能將一個完整的報文拆分成多個小報文進(jìn)行發(fā)送,也可能將多個報文合并成一個大的報文進(jìn)行發(fā)送。(TCP協(xié)議的底層,并不了解上層業(yè)務(wù)的具體定義,它會根據(jù)TCP緩沖區(qū)

    2023年04月21日
    瀏覽(19)
  • Socket TCP/IP協(xié)議數(shù)據(jù)傳輸過程中的粘包和分包問題

    Socket TCP/IP協(xié)議數(shù)據(jù)傳輸過程中的粘包和分包問題

    一:通過圖解法來描述一下分包和粘包,這樣客戶更清晰直觀的了解: 下面對上面的圖進(jìn)行解釋: 1.正常情況:如果Socket Client 發(fā)送的數(shù)據(jù)包,在Socket Server端也是一個一個完整接收的,那個就不會出現(xiàn)粘包和分包情況,數(shù)據(jù)正常讀取。 2.粘包情況:Socket Client發(fā)送的數(shù)據(jù)包,

    2024年02月12日
    瀏覽(22)
  • 粘包/拆包問題一直都存在,只是到TCP就拆不動了。

    粘包/拆包問題一直都存在,只是到TCP就拆不動了。

    OSI open-system-Interconnection TCP/IP 5層協(xié)議棧 應(yīng)用層和操作系統(tǒng)的邊界是 系統(tǒng)調(diào)用 ,對應(yīng)到網(wǎng)絡(luò)編程是socket api TCP/UDP 概況 TCP粘包問題 TCP/IP報頭深思 定義了網(wǎng)絡(luò)框架,以層為單位實(shí)現(xiàn)協(xié)議,同時控制權(quán)逐層傳遞。 OSI實(shí)際并沒有落地,TCP/IP 5層協(xié)議棧是目前主流的落地實(shí)現(xiàn) 。 TC

    2024年02月03日
    瀏覽(95)
  • C++ Qt TCP協(xié)議,處理粘包、拆包問題,加上數(shù)據(jù)頭來處理

    目錄 前言: 場景: 原因: 解決: 方案2具體細(xì)節(jié): 純C++服務(wù)端處理如下: Qt客戶端處理如下: ? ? ? ? tcp協(xié)議里面,除了心跳檢測是關(guān)于長連接操作的處理,這個在前一篇已經(jīng)提到過了,這一篇將會對tcp本身的一個問題,進(jìn)行處理:那就是做網(wǎng)絡(luò)通信大概率會遇到的問題

    2024年02月04日
    瀏覽(22)
  • Unity-TCP-網(wǎng)絡(luò)聊天功能(一):?API、客戶端服務(wù)器、數(shù)據(jù)格式、粘包拆包

    Unity-TCP-網(wǎng)絡(luò)聊天功能(一):?API、客戶端服務(wù)器、數(shù)據(jù)格式、粘包拆包

    TCP是面向連接的。因此需要創(chuàng)建監(jiān)聽器,監(jiān)聽客戶端的連接。當(dāng)連接成功后,會返回一個TcpClient對象。通過TcpClient可以接收和發(fā)送數(shù)據(jù)。 VS創(chuàng)建C# .net控制臺應(yīng)用 項(xiàng)目中創(chuàng)建文件夾Net,Net 下添加TCPServer.cs類,用來創(chuàng)建TCPListener和Accept客戶端連接,實(shí)例化一個TCPServcer放在Main函數(shù)

    2024年02月07日
    瀏覽(129)
  • go中for range的坑以及解決方案

    相信小伙伴都遇到過以下的循環(huán)變量的問題,那是因?yàn)檠h(huán)的val變量是重復(fù)使用的,即僅有一份。也就是說,每次循環(huán)后賦給val的值就會把前面循環(huán)賦給val的值替換掉,所以打印出來的值都是最后一次循環(huán)賦給val的值。 使用局部變量/臨時變量,即可解決 ? ? ? ? 可以設(shè)置

    2024年01月25日
    瀏覽(16)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包