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

6行代碼快速解決golang TCP粘包問題

 更新時(shí)間:2018年03月09日 10:23:53   作者:xialeistudio  
在用golang開發(fā)人工客服系統(tǒng)的時(shí)候碰到了粘包問題,那么什么是粘包呢?下面這篇文章主要給大家介紹了關(guān)于如何通過6行代碼快速解決golang TCP粘包問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。

前言

什么是TCP粘包問題以及為什么會(huì)產(chǎn)生TCP粘包,本文不加討論。本文使用golang的bufio.Scanner來實(shí)現(xiàn)自定義協(xié)議解包。

下面話不多說了,來一起看看詳細(xì)的介紹吧。

協(xié)議數(shù)據(jù)包定義

本文模擬一個(gè)日志服務(wù)器,該服務(wù)器接收客戶端傳到的數(shù)據(jù)包并顯示出來

type Package struct {
 Version  [2]byte // 協(xié)議版本,暫定V1
 Length   int16 // 數(shù)據(jù)部分長(zhǎng)度
 Timestamp  int64 // 時(shí)間戳
 HostnameLength int16 // 主機(jī)名長(zhǎng)度
 Hostname  []byte // 主機(jī)名
 TagLength  int16 // 標(biāo)簽長(zhǎng)度
 Tag   []byte // 標(biāo)簽
 Msg   []byte // 日志數(shù)據(jù)
}

協(xié)議定義部分沒有什么好講的,根據(jù)具體的業(yè)務(wù)邏輯定義即可。

數(shù)據(jù)打包

由于TCP協(xié)議是語(yǔ)言無關(guān)的協(xié)議,所以直接把協(xié)議數(shù)據(jù)包結(jié)構(gòu)體發(fā)送到TCP連接中也是不可能的,只能發(fā)送字節(jié)流數(shù)據(jù),所以需要自己實(shí)現(xiàn)數(shù)據(jù)編碼。所幸golang提供了binary來幫助我們實(shí)現(xiàn)網(wǎng)絡(luò)字節(jié)編碼。

func (p *Package) Pack(writer io.Writer) error {
 var err error
 err = binary.Write(writer, binary.BigEndian, &p.Version)
 err = binary.Write(writer, binary.BigEndian, &p.Length)
 err = binary.Write(writer, binary.BigEndian, &p.Timestamp)
 err = binary.Write(writer, binary.BigEndian, &p.HostnameLength)
 err = binary.Write(writer, binary.BigEndian, &p.Hostname)
 err = binary.Write(writer, binary.BigEndian, &p.TagLength)
 err = binary.Write(writer, binary.BigEndian, &p.Tag)
 err = binary.Write(writer, binary.BigEndian, &p.Msg)
 return err
}

Pack方法的輸出目標(biāo)為io.Writer,有利于接口擴(kuò)展,只要實(shí)現(xiàn)了該接口即可編碼數(shù)據(jù)寫入。binary.BigEndian是字節(jié)序,本文暫時(shí)不討論,有需要的讀者可以自行查找資料研究。

數(shù)據(jù)解包

解包需要將TCP數(shù)據(jù)包解析到結(jié)構(gòu)體中,接下來會(huì)講為什么需要添加幾個(gè)數(shù)據(jù)無關(guān)的長(zhǎng)度字段。

func (p *Package) Unpack(reader io.Reader) error {
 var err error
 err = binary.Read(reader, binary.BigEndian, &p.Version)
 err = binary.Read(reader, binary.BigEndian, &p.Length)
 err = binary.Read(reader, binary.BigEndian, &p.Timestamp)
 err = binary.Read(reader, binary.BigEndian, &p.HostnameLength)
 p.Hostname = make([]byte, p.HostnameLength)
 err = binary.Read(reader, binary.BigEndian, &p.Hostname)
 err = binary.Read(reader, binary.BigEndian, &p.TagLength)
 p.Tag = make([]byte, p.TagLength)
 err = binary.Read(reader, binary.BigEndian, &p.Tag)
 p.Msg = make([]byte, p.Length-8-2-p.HostnameLength-2-p.TagLength)
 err = binary.Read(reader, binary.BigEndian, &p.Msg)
 return err
}

由于主機(jī)名、標(biāo)簽這種數(shù)據(jù)是不固定長(zhǎng)度的,所以需要兩個(gè)字節(jié)來標(biāo)識(shí)數(shù)據(jù)長(zhǎng)度,否則讀取的時(shí)候只知道一個(gè)總的數(shù)據(jù)長(zhǎng)度是無法區(qū)分主機(jī)名、標(biāo)簽名、日志數(shù)據(jù)的。

數(shù)據(jù)包的粘包問題解決

上文只是解決了編碼/解碼問題,前提是收到的數(shù)據(jù)包沒有產(chǎn)生粘包問題,解決粘包就是要正確分割字節(jié)流中的數(shù)據(jù)。一般有以下做法:

  • 定長(zhǎng)分隔(每個(gè)數(shù)據(jù)包最大為該長(zhǎng)度) 缺點(diǎn)是數(shù)據(jù)不足時(shí)會(huì)浪費(fèi)傳輸資源
  • 特定字符分隔(如rn) 缺點(diǎn)是如果正文中有rn就會(huì)導(dǎo)致問題
  • 在數(shù)據(jù)包中添加長(zhǎng)度字段(本文采用的)

golang提供了bufio.Scanner來解決粘包問題。

scanner := bufio.NewScanner(reader) // reader為實(shí)現(xiàn)了io.Reader接口的對(duì)象,如net.Conn
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
 if !atEOF && data[0] == 'V' { // 由于我們定義的數(shù)據(jù)包頭最開始為兩個(gè)字節(jié)的版本號(hào),所以只有以V開頭的數(shù)據(jù)包才處理
  if len(data) > 4 { // 如果收到的數(shù)據(jù)>4個(gè)字節(jié)(2字節(jié)版本號(hào)+2字節(jié)數(shù)據(jù)包長(zhǎng)度)
   length := int16(0)
   binary.Read(bytes.NewReader(data[2:4]), binary.BigEndian, &length) // 讀取數(shù)據(jù)包第3-4字節(jié)(int16)=>數(shù)據(jù)部分長(zhǎng)度
   if int(length)+4 <= len(data) { // 如果讀取到的數(shù)據(jù)正文長(zhǎng)度+2字節(jié)版本號(hào)+2字節(jié)數(shù)據(jù)長(zhǎng)度不超過讀到的數(shù)據(jù)(實(shí)際上就是成功完整的解析出了一個(gè)包)
    return int(length) + 4, data[:int(length)+4], nil
   }
  }
 }
 return
})
// 打印接收到的數(shù)據(jù)包
for scanner.Scan() {
 scannedPack := new(Package)
 scannedPack.Unpack(bytes.NewReader(scanner.Bytes()))
 log.Println(scannedPack)
}

本文的核心就在于scanner.Split方法,該方法用來解析TCP數(shù)據(jù)包

完整源碼

package main
import (
 "bufio"
 "bytes"
 "encoding/binary"
 "fmt"
 "io"
 "log"
 "os"
 "time"
)

type Package struct {
 Version  [2]byte // 協(xié)議版本
 Length   int16 // 數(shù)據(jù)部分長(zhǎng)度
 Timestamp  int64 // 時(shí)間戳
 HostnameLength int16 // 主機(jī)名長(zhǎng)度
 Hostname  []byte // 主機(jī)名
 TagLength  int16 // Tag長(zhǎng)度
 Tag   []byte // Tag
 Msg   []byte // 數(shù)據(jù)部分長(zhǎng)度
}

func (p *Package) Pack(writer io.Writer) error {
 var err error
 err = binary.Write(writer, binary.BigEndian, &p.Version)
 err = binary.Write(writer, binary.BigEndian, &p.Length)
 err = binary.Write(writer, binary.BigEndian, &p.Timestamp)
 err = binary.Write(writer, binary.BigEndian, &p.HostnameLength)
 err = binary.Write(writer, binary.BigEndian, &p.Hostname)
 err = binary.Write(writer, binary.BigEndian, &p.TagLength)
 err = binary.Write(writer, binary.BigEndian, &p.Tag)
 err = binary.Write(writer, binary.BigEndian, &p.Msg)
 return err
}
func (p *Package) Unpack(reader io.Reader) error {
 var err error
 err = binary.Read(reader, binary.BigEndian, &p.Version)
 err = binary.Read(reader, binary.BigEndian, &p.Length)
 err = binary.Read(reader, binary.BigEndian, &p.Timestamp)
 err = binary.Read(reader, binary.BigEndian, &p.HostnameLength)
 p.Hostname = make([]byte, p.HostnameLength)
 err = binary.Read(reader, binary.BigEndian, &p.Hostname)
 err = binary.Read(reader, binary.BigEndian, &p.TagLength)
 p.Tag = make([]byte, p.TagLength)
 err = binary.Read(reader, binary.BigEndian, &p.Tag)
 p.Msg = make([]byte, p.Length-8-2-p.HostnameLength-2-p.TagLength)
 err = binary.Read(reader, binary.BigEndian, &p.Msg)
 return err
}

func (p *Package) String() string {
 return fmt.Sprintf("version:%s length:%d timestamp:%d hostname:%s tag:%s msg:%s",
  p.Version,
  p.Length,
  p.Timestamp,
  p.Hostname,
  p.Tag,
  p.Msg,
 )
}

func main() {
 hostname, err := os.Hostname()
 if err != nil {
  log.Fatal(err)
 }

 pack := &Package{
  Version:  [2]byte{'V', '1'},
  Timestamp:  time.Now().Unix(),
  HostnameLength: int16(len(hostname)),
  Hostname:  []byte(hostname),
  TagLength:  4,
  Tag:   []byte("demo"),
  Msg:   []byte(("現(xiàn)在時(shí)間是:" + time.Now().Format("2006-01-02 15:04:05"))),
 }
 pack.Length = 8 + 2 + pack.HostnameLength + 2 + pack.TagLength + int16(len(pack.Msg))

 buf := new(bytes.Buffer)
 // 寫入四次,模擬TCP粘包效果
 pack.Pack(buf)
 pack.Pack(buf)
 pack.Pack(buf)
 pack.Pack(buf)
 // scanner
 scanner := bufio.NewScanner(buf)
 scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
  if !atEOF && data[0] == 'V' {
   if len(data) > 4 {
    length := int16(0)
    binary.Read(bytes.NewReader(data[2:4]), binary.BigEndian, &length)
    if int(length)+4 <= len(data) {
     return int(length) + 4, data[:int(length)+4], nil
    }
   }
  }
  return
 })
 for scanner.Scan() {
  scannedPack := new(Package)
  scannedPack.Unpack(bytes.NewReader(scanner.Bytes()))
  log.Println(scannedPack)
 }
 if err := scanner.Err(); err != nil {
  log.Fatal("無效數(shù)據(jù)包")
 }
}

寫在最后

golang作為一門強(qiáng)大的網(wǎng)絡(luò)編程語(yǔ)言,實(shí)現(xiàn)自定義協(xié)議是非常重要的,實(shí)際上實(shí)現(xiàn)自定義協(xié)議也不是很難,以下幾個(gè)步驟:

  • 數(shù)據(jù)包編碼
  • 數(shù)據(jù)包解碼
  • 處理TCP粘包問題
  • 斷線重連(可以使用心跳實(shí)現(xiàn))(非必須)

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 關(guān)于golang test緩存問題

    關(guān)于golang test緩存問題

    這篇文章主要介紹了關(guān)于golang test緩存問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • 自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    這篇文章主要為大家介紹了自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 提升Go語(yǔ)言開發(fā)效率的小技巧實(shí)例(GO語(yǔ)言語(yǔ)法糖)匯總

    提升Go語(yǔ)言開發(fā)效率的小技巧實(shí)例(GO語(yǔ)言語(yǔ)法糖)匯總

    這篇文章主要介紹了提升Go語(yǔ)言開發(fā)效率的小技巧匯總,也就是Go語(yǔ)言的語(yǔ)法糖,掌握好這些可以提高我們的開發(fā)效率,需要的朋友可以參考下
    2022-11-11
  • Go語(yǔ)言學(xué)習(xí)之時(shí)間函數(shù)使用詳解

    Go語(yǔ)言學(xué)習(xí)之時(shí)間函數(shù)使用詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中時(shí)間函數(shù)的使用方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下
    2022-04-04
  • go實(shí)現(xiàn)一個(gè)內(nèi)存緩存系統(tǒng)的示例代碼

    go實(shí)現(xiàn)一個(gè)內(nèi)存緩存系統(tǒng)的示例代碼

    本文主要介紹了go實(shí)現(xiàn)一個(gè)內(nèi)存緩存系統(tǒng)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • Go語(yǔ)言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解

    Go語(yǔ)言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)切片的創(chuàng)建及初始化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • golang的基礎(chǔ)語(yǔ)法和常用開發(fā)工具詳解

    golang的基礎(chǔ)語(yǔ)法和常用開發(fā)工具詳解

    這篇文章主要介紹了golang的基礎(chǔ)語(yǔ)法和常用開發(fā)工具,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • 使用Go語(yǔ)言實(shí)現(xiàn)接口繼承的方式

    使用Go語(yǔ)言實(shí)現(xiàn)接口繼承的方式

    在Go語(yǔ)言中,接口(interface)是一種定義方法集合的類型,它并不包含方法的具體實(shí)現(xiàn),只是規(guī)定實(shí)現(xiàn)該接口的類型必須提供這些方法的實(shí)現(xiàn),下面我將通過示例代碼來詳細(xì)解釋如何使用Go語(yǔ)言實(shí)現(xiàn)接口組合,以及為什么這種方式可以看作是實(shí)現(xiàn)接口繼承的一種方式
    2024-05-05
  • Go 代碼生成工具詳解

    Go 代碼生成工具詳解

    這篇文章主要介紹了Go 代碼生成工具詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • go的websocket實(shí)現(xiàn)原理與用法詳解

    go的websocket實(shí)現(xiàn)原理與用法詳解

    這篇文章主要介紹了go的websocket實(shí)現(xiàn)原理與用法,詳細(xì)分析了websocket的功能、原理及Go語(yǔ)言實(shí)現(xiàn)websocket的相關(guān)技巧,需要的朋友可以參考下
    2016-07-07

最新評(píng)論