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

golang模擬TCP粘包和拆包

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

1. 什么是 TCP 粘包與拆包

1.粘包(Sticky Packet)

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

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

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

2.拆包(Packet Fragmentation)

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

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

假設(shè)發(fā)送端發(fā)送了一個(gè)大的消息:“Hello, this is a long message.”;但是在傳輸過(guò)程中,網(wǎng)絡(luò)層可能會(huì)將該消息拆分成多個(gè)小包,接收端可能先收到一部分?jǐn)?shù)據(jù):“Hello, this”,然后再收到另外一部分:“is a long message.”;這樣接收端就會(huì)得到多個(gè)數(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() {
	// 啟動(dòng)服務(wù)器,監(jiān)聽(tīng) 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ā)送粘包情況:多個(gè)小消息一次發(fā)送
		message := fmt.Sprintf("Message %d\n", i+1)
		conn.Write([]byte(message))
	}

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

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

可以看到接收端收到的消息并非都是一條,說(shuō)明發(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() {
	// 啟動(dòng)服務(wù)器,監(jiān)聽(tīng) 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)造一個(gè)超過(guò)默認(rèn) 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é)果分析

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

4. 如何解決 TCP 粘包與拆包問(wèn)題

4.1 自定義協(xié)議

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

server.go(接收端)

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

package main

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

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

	// 解析消息長(zhǎng)度
	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ā)來(lái)的消息
	for {
		msg, err := readMessage(conn)
		if err != nil {
			log.Printf("Error reading message: %v", err)
			break
		}
		fmt.Println("Received message:", msg)
	}
}

func main() {
	// 啟動(dòng)監(jiān)聽(tīng)服務(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
		}
		// 啟動(dòng)新的 Goroutine 處理客戶端請(qǐng)求
		go handleConnection(conn)
	}
}

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

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

package main

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

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

	// 將消息長(zhǎng)度轉(zhuǎn)換為4字節(jié)的二進(jìn)制數(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ā)送多個(gè)消息
	sendMessage(conn, "Hello, Server!")
	sendMessage(conn, "This is a second message.")
	sendMessage(conn, "Goodbye!")
}

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

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

server.go(接收端)

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

package main

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

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

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

	for {
		// 每次讀取固定長(zhǎng)度的消息
		_, 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() {
	// 啟動(dòng) TCP 監(jiān)聽(tīng)
	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
		}
		// 啟動(dòng)新的 Goroutine 處理每個(gè)客戶端的連接
		go handleConnection(conn)
	}
}

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

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

package main

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

// sendFixedLengthMessage 函數(shù)向服務(wù)端發(fā)送固定長(zhǎng)度的消息
func sendFixedLengthMessage(conn net.Conn, msg string) {
	// 確保消息長(zhǎng)度為 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ā)送固定長(zhǎng)度的消息
	sendFixedLengthMessage(conn, "Hello, Server!")
	sendFixedLengthMessage(conn, "This is a second message.")
	sendFixedLengthMessage(conn, "Goodbye!")
}

4.3 特殊字符來(lái)標(biāo)識(shí)消息邊界

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

server.go(接收端)

服務(wù)端會(huì)監(jiān)聽(tīng)端口,并按行讀取客戶端發(fā)送的消息。每個(gè)消息的末尾會(huì)有一個(gè) \n 來(lái)標(biāo)識(shí)消息的結(jié)束

package main

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

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

	// 創(chuàng)建一個(gè)帶緩沖的讀取器
	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() {
	// 啟動(dòng) TCP 監(jiān)聽(tīng)
	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
		}
		// 啟動(dòng)新的 Goroutine 處理每個(gè)客戶端的連接
		go handleConnection(conn)
	}
}

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

客戶端向服務(wù)端發(fā)送消息,每條消息末尾都會(huì)加上一個(gè) \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)缺點(diǎn)對(duì)比

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

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

相關(guān)文章

  • Go學(xué)習(xí)筆記之Zap日志的使用

    Go學(xué)習(xí)筆記之Zap日志的使用

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中Zap日志的使用以及安裝,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下
    2022-07-07
  • golang gin框架獲取參數(shù)的操作

    golang gin框架獲取參數(shù)的操作

    這篇文章主要介紹了golang gin框架獲取參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 使用go進(jìn)行云存儲(chǔ)上傳實(shí)現(xiàn)實(shí)例

    使用go進(jìn)行云存儲(chǔ)上傳實(shí)現(xiàn)實(shí)例

    這篇文章主要為大家介紹了使用go進(jìn)行云存儲(chǔ)上傳實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2024-01-01
  • GO語(yǔ)言中接口和接口型函數(shù)的具體使用

    GO語(yǔ)言中接口和接口型函數(shù)的具體使用

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

    Golang實(shí)現(xiàn)內(nèi)網(wǎng)穿透詳解

    這篇文章主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)內(nèi)網(wǎng)穿透的相關(guān)知識(shí),包括原理和代碼實(shí)現(xiàn),文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下
    2024-11-11
  • 淺談Go語(yǔ)言多態(tài)的實(shí)現(xiàn)與interface使用

    淺談Go語(yǔ)言多態(tài)的實(shí)現(xiàn)與interface使用

    如果大家系統(tǒng)的學(xué)過(guò)C++、Java等語(yǔ)言以及面向?qū)ο蟮脑?,相信?yīng)該對(duì)多態(tài)不會(huì)陌生。多態(tài)是面向?qū)ο蠓懂牣?dāng)中經(jīng)常使用并且非常好用的一個(gè)功能,它主要是用在強(qiáng)類(lèi)型語(yǔ)言當(dāng)中,像是Python這樣的弱類(lèi)型語(yǔ)言,變量的類(lèi)型可以隨意變化,也沒(méi)有任何限制,其實(shí)區(qū)別不是很大
    2021-06-06
  • Go語(yǔ)言通過(guò)chan進(jìn)行數(shù)據(jù)傳遞的方法詳解

    Go語(yǔ)言通過(guò)chan進(jìn)行數(shù)據(jù)傳遞的方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何通過(guò)chan進(jìn)行數(shù)據(jù)傳遞的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-06-06
  • Go語(yǔ)言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例

    Go語(yǔ)言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go函數(shù)使用(函數(shù)定義、函數(shù)聲明、函數(shù)調(diào)用等)

    Go函數(shù)使用(函數(shù)定義、函數(shù)聲明、函數(shù)調(diào)用等)

    本文主要介紹了Go函數(shù)使用,包括函數(shù)定義、函數(shù)聲明、函數(shù)調(diào)用、可變參數(shù)函數(shù)、匿名函數(shù)、遞歸函數(shù)、高階函數(shù)等,感興趣的可以了解一下
    2023-11-11
  • Systemd集成Golang二進(jìn)制程序的方法

    Systemd集成Golang二進(jìn)制程序的方法

    這篇文章主要介紹了Systemd集成Golang二進(jìn)制程序的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-10-10

最新評(píng)論