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

golang模擬TCP粘包和拆包

 更新時間:2024年12月31日 08:58:32   作者:赴前塵  
粘包是指在發(fā)送多個小的數(shù)據(jù)包時,接收端會將這些數(shù)據(jù)包合并成一個數(shù)據(jù)包接收,拆包是指發(fā)送的數(shù)據(jù)包在傳輸過程中被分割成多個小包,下面我們來看看go如何模擬TCP粘包和拆包吧

1. 什么是 TCP 粘包與拆包

1.粘包(Sticky Packet)

粘包是指在發(fā)送多個小的數(shù)據(jù)包時,接收端會將這些數(shù)據(jù)包合并成一個數(shù)據(jù)包接收。由于 TCP 是面向流的協(xié)議,它并不會在每次數(shù)據(jù)發(fā)送時附加邊界信息。所以當(dāng)多個數(shù)據(jù)包按順序發(fā)送時,接收端可能會一次性接收多個數(shù)據(jù)包的數(shù)據(jù),造成數(shù)據(jù)被粘在一起。

粘包一般發(fā)生在發(fā)送端每次寫入的數(shù)據(jù) < 接收端套接字(Socket)緩沖區(qū)的大小。

假設(shè)發(fā)送端發(fā)送了兩個消息:消息1:“Hello”,消息2:“World”;由于 TCP 是流協(xié)議,接收端可能會接收到如下數(shù)據(jù):“HelloWorld”。這種情況就是粘包,接收端就無法準確區(qū)分這兩個消息。

2.拆包(Packet Fragmentation)

拆包是指發(fā)送的數(shù)據(jù)包在傳輸過程中被分割成多個小包。盡管發(fā)送端可能發(fā)送了一個完整的消息,但由于 TCP 協(xié)議在網(wǎng)絡(luò)傳輸時可能會對數(shù)據(jù)進行分段,接收端可能接收到的是多個小數(shù)據(jù)包。

拆包一般發(fā)生在發(fā)送端每次寫入的數(shù)據(jù) > 接收端套接字(Socket)緩沖區(qū)的大小。

假設(shè)發(fā)送端發(fā)送了一個大的消息:“Hello, this is a long message.”;但是在傳輸過程中,網(wǎng)絡(luò)層可能會將該消息拆分成多個小包,接收端可能先收到一部分數(shù)據(jù):“Hello, this”,然后再收到另外一部分:“is a long message.”;這樣接收端就會得到多個數(shù)據(jù)包,且它們并不代表單一的邏輯消息。

2. go 模擬TCP粘包

server.go(接收端)

package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 創(chuàng)建緩沖讀取器,讀取客戶端數(shù)據(jù)
	reader := bufio.NewReader(conn)
	var buffer [1024]byte

	for {
		// 持續(xù)讀取數(shù)據(jù)
		n, err := reader.Read(buffer[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("Error reading data:", err)
			break
		}
		recvStr := string(buffer[:n])

		// 打印接收到的數(shù)據(jù)
		fmt.Println("Received:", recvStr)
	}
}

func main() {
	// 啟動服務(wù)器,監(jiān)聽 8080 端口
	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("Error starting server:", err)
		return
	}
	defer ln.Close()

	fmt.Println("Server started on port 8080...")

	for {
		// 等待客戶端連接
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err)
			continue
		}

		// 處理連接
		go handleConnection(conn)
	}
}

client.go(發(fā)送端)

package main

import (
	"fmt"
	"net"
	"time"
)

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

	// 模擬粘包和拆包
	for i := 0; i < 100; i++ {
		// 發(fā)送粘包情況:多個小消息一次發(fā)送
		message := fmt.Sprintf("Message %d\n", i+1)
		conn.Write([]byte(message))
	}

	// 等待服務(wù)器輸出接收到的消息
	time.Sleep(2 * time.Second)
}

執(zhí)行結(jié)果分析

可以看到接收端收到的消息并非都是一條,說明發(fā)生了粘包

3. go模擬TCP拆包

server.go(接收端)

package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 創(chuàng)建緩沖讀取器,讀取客戶端數(shù)據(jù)
	reader := bufio.NewReader(conn)
	var buffer [18]byte

	for {
		// 持續(xù)讀取數(shù)據(jù)
		n, err := reader.Read(buffer[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("Error reading data:", err)
			break
		}
		recvStr := string(buffer[:n])

		// 打印接收到的數(shù)據(jù)
		fmt.Println("Received message :", recvStr)
	}
}

func main() {
	// 啟動服務(wù)器,監(jiān)聽 8080 端口
	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("Error starting server:", err)
		return
	}
	defer ln.Close()

	fmt.Println("Server started on port 8080...")

	for {
		// 等待客戶端連接
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err)
			continue
		}

		// 處理連接
		go handleConnection(conn)
	}
}

client.go(發(fā)送端)

package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

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

	// 構(gòu)造一個超過默認 MTU 的大數(shù)據(jù)包(32 字節(jié))
	message := strings.Repeat("A", 32)

	// 模擬發(fā)送大量數(shù)據(jù)
	for i := 0; i < 100; i++ {
		fmt.Printf("Sending message : %s\n", message)
		conn.Write([]byte(message))
	}

	// 等待服務(wù)器輸出
	time.Sleep(2 * time.Second)
}

執(zhí)行結(jié)果分析

可以看到接收端對接收到的數(shù)據(jù)進行了拆分,說明發(fā)生了拆包

4. 如何解決 TCP 粘包與拆包問題

4.1 自定義協(xié)議

發(fā)送端將請求的數(shù)據(jù)封裝為兩部分:消息頭(發(fā)送數(shù)據(jù)大?。?消息體(發(fā)送具體數(shù)據(jù));接收端根據(jù)消息頭的值讀取相應(yīng)長度的消息體數(shù)據(jù)

server.go(接收端)

服務(wù)端接收到數(shù)據(jù)時,首先讀取前4個字節(jié)來獲取消息的長度,然后再根據(jù)該長度讀取完整的消息體

package main

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

// readMessage 函數(shù)根據(jù)長度字段讀取消息
func readMessage(conn net.Conn) (string, error) {
	// 讀取4個字節(jié)的長度字段
	lenBytes := make([]byte, 4)
	_, err := io.ReadFull(conn, lenBytes)
	if err != nil {
		return "", fmt.Errorf("failed to read length field: %v", err)
	}

	// 解析消息長度
	msgLength := binary.BigEndian.Uint32(lenBytes)

	// 讀取消息體
	msgBytes := make([]byte, msgLength)
	_, err = io.ReadFull(conn, msgBytes)
	if err != nil {
		return "", fmt.Errorf("failed to read message body: %v", err)
	}

	return string(msgBytes), nil
}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 一直循環(huán)接收客戶端發(fā)來的消息
	for {
		msg, err := readMessage(conn)
		if err != nil {
			log.Printf("Error reading message: %v", err)
			break
		}
		fmt.Println("Received message:", msg)
	}
}

func main() {
	// 啟動監(jiān)聽服務(wù)
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
	defer listener.Close()

	fmt.Println("Server is listening on port 8080...")

	// 接受客戶端連接并處理
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Error accepting connection: %v", err)
			continue
		}
		// 啟動新的 Goroutine 處理客戶端請求
		go handleConnection(conn)
	}
}

client.go(發(fā)送端)

客戶端將連接到服務(wù)端,并發(fā)送多個消息。每個消息的前4字節(jié)表示消息的長度,隨后是消息體

package main

import (
	"bytes"
	"encoding/binary"
	"log"
	"net"
)

// sendMessage 函數(shù)將消息和長度一起發(fā)送給服務(wù)端
func sendMessage(conn net.Conn, msg string) {
	// 計算消息的長度
	msgLen := uint32(len(msg))
	buf := new(bytes.Buffer)

	// 將消息長度轉(zhuǎn)換為4字節(jié)的二進制數(shù)據(jù)
	binary.Write(buf, binary.BigEndian, msgLen)
	// 將消息體內(nèi)容添加到緩沖區(qū)
	buf.Write([]byte(msg))

	// 發(fā)送緩沖區(qū)數(shù)據(jù)到服務(wù)端
	conn.Write(buf.Bytes())
}

func main() {
	// 連接到服務(wù)端
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Fatalf("Error connecting to server: %v", err)
	}
	defer conn.Close()

	// 發(fā)送多個消息
	sendMessage(conn, "Hello, Server!")
	sendMessage(conn, "This is a second message.")
	sendMessage(conn, "Goodbye!")
}

4.2 固定長度數(shù)據(jù)包

每個消息的長度是固定的(例如 1024 字節(jié))。如果客戶端發(fā)送的數(shù)據(jù)長度不足指定長度,則會使用空格填充,確保每個數(shù)據(jù)包的大小一致

server.go(接收端)

服務(wù)端接收到的數(shù)據(jù)是固定長度的。每次接收 1024 字節(jié)的數(shù)據(jù),并將其打印出來。如果數(shù)據(jù)不足 1024 字節(jié),服務(wù)端會讀取并處理這些數(shù)據(jù)。

package main

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

// handleConnection 函數(shù)處理每個客戶端的連接
func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 設(shè)定每個消息的固定長度
	const messageLength = 1024
	buf := make([]byte, messageLength)

	for {
		// 每次讀取固定長度的消息
		_, err := io.ReadFull(conn, buf)
		if err != nil {
			if err.Error() == "EOF" {
				// 客戶端關(guān)閉連接
				break
			}
			log.Printf("Error reading message: %v", err)
			break
		}

		// 將讀取的字節(jié)轉(zhuǎn)換為字符串并打印
		msg := string(buf)
		// 去除空格填充
		fmt.Println("Received message:", strings.TrimSpace(msg))
	}
}

func main() {
	// 啟動 TCP 監(jiān)聽
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
	defer listener.Close()

	fmt.Println("Server is listening on port 8080...")

	// 等待客戶端連接
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Error accepting connection: %v", err)
			continue
		}
		// 啟動新的 Goroutine 處理每個客戶端的連接
		go handleConnection(conn)
	}
}

client.go(發(fā)送端)

客戶端會向服務(wù)器發(fā)送固定長度的消息,如果消息長度不足 1024 字節(jié),則會填充空格

package main

import (
	"log"
	"net"
	"strings"
)

// sendFixedLengthMessage 函數(shù)向服務(wù)端發(fā)送固定長度的消息
func sendFixedLengthMessage(conn net.Conn, msg string) {
	// 確保消息長度為 1024 字節(jié),不足部分用空格填充
	if len(msg) < 1024 {
		msg = msg + strings.Repeat(" ", 1024-len(msg))
	}

	// 發(fā)送消息到服務(wù)端
	_, err := conn.Write([]byte(msg))
	if err != nil {
		log.Fatalf("Error sending message: %v", err)
	}
}

func main() {
	// 連接到服務(wù)端
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Fatalf("Error connecting to server: %v", err)
	}
	defer conn.Close()

	// 發(fā)送固定長度的消息
	sendFixedLengthMessage(conn, "Hello, Server!")
	sendFixedLengthMessage(conn, "This is a second message.")
	sendFixedLengthMessage(conn, "Goodbye!")
}

4.3 特殊字符來標識消息邊界

通過在發(fā)送端每條消息的末尾加上 \n,然后接收端使用 ReadLine() 方法按行讀取數(shù)據(jù)來區(qū)分每個數(shù)據(jù)包的邊界

server.go(接收端)

服務(wù)端會監(jiān)聽端口,并按行讀取客戶端發(fā)送的消息。每個消息的末尾會有一個 \n 來標識消息的結(jié)束

package main

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

func handleConnection(conn net.Conn) {
	defer conn.Close()

	// 創(chuàng)建一個帶緩沖的讀取器
	reader := bufio.NewReader(conn)

	for {
		// 讀取客戶端發(fā)送的一行數(shù)據(jù),直到遇到 '\n' 為止
		line, err := reader.ReadString('\n')
		if err != nil {
			log.Printf("Error reading from client: %v", err)
			break
		}

		// 去掉結(jié)尾的換行符
		line = strings.TrimSpace(line)
		fmt.Printf("Received message: %s\n", line)
	}
}

func main() {
	// 啟動 TCP 監(jiān)聽
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
	defer listener.Close()

	fmt.Println("Server is listening on port 8080...")

	// 等待客戶端連接
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Error accepting connection: %v", err)
			continue
		}
		// 啟動新的 Goroutine 處理每個客戶端的連接
		go handleConnection(conn)
	}
}

client.go(發(fā)送端)

客戶端向服務(wù)端發(fā)送消息,每條消息末尾都會加上一個 \n,然后發(fā)送到服務(wù)器

package main

import (
	"log"
	"net"
)

func sendMessage(conn net.Conn, message string) {
	// 將消息添加換行符并發(fā)送
	message = message + "\n"
	_, err := conn.Write([]byte(message))
	if err != nil {
		log.Fatalf("Error sending message: %v", err)
	}
}

func main() {
	// 連接到服務(wù)端
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Fatalf("Error connecting to server: %v", err)
	}
	defer conn.Close()

	// 發(fā)送幾條消息
	sendMessage(conn, "Hello, Server!")
	sendMessage(conn, "How are you?")
	sendMessage(conn, "Goodbye!")
}

5. 三種方式的優(yōu)缺點對比

特性固定長度方式特殊字符分隔方式自定義協(xié)議方式
實現(xiàn)簡單
帶寬效率低(需要填充)高(僅傳輸有效數(shù)據(jù))高(僅傳輸有效數(shù)據(jù),且靈活處理)
靈活性
易于調(diào)試高(每包大小固定)中(需解析換行符等)低(需要解析協(xié)議頭和體)
性能開銷中等(需要額外解析消息頭)
適用場景長度固定的消息消息大小可變但有清晰的分隔符復(fù)雜協(xié)議、支持多類型消息的場景

以上就是golang模擬TCP粘包和拆包的詳細內(nèi)容,更多關(guān)于go TCP粘包和拆包的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go語言語法基礎(chǔ)的全面梳理(適合入門)

    go語言語法基礎(chǔ)的全面梳理(適合入門)

    這篇文章主要介紹了go語言語法基礎(chǔ)的相關(guān)資料,從基礎(chǔ)語法到并發(fā)編程,涵蓋變量聲明、數(shù)據(jù)類型、流程控制、函數(shù)、接口、錯誤處理及并發(fā)編程,全面覆蓋Go語言核心特性,需要的朋友可以參考下
    2026-06-06
  • Golang并發(fā)編程深入分析

    Golang并發(fā)編程深入分析

    golang中的并發(fā),是函數(shù)相互獨立運行的能力,goroutines是并發(fā)運行的函數(shù)。golang提供了goroutines作為并發(fā)處理的一種方式
    2022-11-11
  • golang string、int、int64 float 互相轉(zhuǎn)換方式

    golang string、int、int64 float 互相轉(zhuǎn)換方式

    這篇文章主要介紹了golang string、int、int64 float 互相轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 一文帶你掌握go中的異常處理

    一文帶你掌握go中的異常處理

    代碼在執(zhí)行的過程中可能因為一些邏輯上的問題而出現(xiàn)錯誤,我們稱之為異常,那么如何處理這些異常呢,下面小編就來和大家詳細聊聊golang中的異常處理機制吧
    2023-09-09
  • Go語言設(shè)計實現(xiàn)在任務(wù)欄里提醒你喝水的兔子

    Go語言設(shè)計實現(xiàn)在任務(wù)欄里提醒你喝水的兔子

    這篇文章主要為大家介紹了Go語言設(shè)計實現(xiàn)在任務(wù)欄里提醒你喝水的兔子示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • GO語言文件的創(chuàng)建與打開實例分析

    GO語言文件的創(chuàng)建與打開實例分析

    這篇文章主要介紹了GO語言文件的創(chuàng)建與打開的具體用法,實例分析了GO語言文件創(chuàng)建與打開操作中所涉及的函數(shù)具體用法,具有一定的參考借鑒價值,需要的朋友可以參考下
    2014-12-12
  • Go語言設(shè)置JSON的默認值操作

    Go語言設(shè)置JSON的默認值操作

    這篇文章主要介紹了Go語言設(shè)置JSON的默認值操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言包和包管理詳解

    Go語言包和包管理詳解

    這篇文章主要為大家介紹了Go語言包和包管理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • Golang實現(xiàn)四種負載均衡的算法(隨機,輪詢等)

    Golang實現(xiàn)四種負載均衡的算法(隨機,輪詢等)

    本文介紹了示例介紹了Golang 負載均衡的四種實現(xiàn),主要包括了隨機,輪詢,加權(quán)輪詢負載,一致性hash,感興趣的小伙伴們可以參考一下
    2021-06-06
  • Goland遠程連接Linux進行項目開發(fā)的實現(xiàn)

    Goland遠程連接Linux進行項目開發(fā)的實現(xiàn)

    有的時候我們的開發(fā)代碼要在linux服務(wù)器上運行,本文主要介紹了Goland遠程連接Linux進行項目開發(fā)的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-06-06

最新評論

嘉黎县| 拜城县| 西丰县| 鸡西市| 施甸县| 靖边县| 垫江县| 商南县| 沐川县| 阳谷县| 闸北区| 高雄市| 乐东| 明星| 吴旗县| 九龙坡区| 井研县| 莆田市| 株洲县| 威海市| 胶南市| 中西区| 凤山县| 德格县| 岳西县| 出国| 得荣县| 尉氏县| 阿图什市| 腾冲县| 斗六市| 理塘县| 温泉县| 府谷县| 绥化市| 西藏| 穆棱市| 梧州市| 海原县| 射阳县| 韶关市|