一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - 6行代碼快速解決golang TCP粘包問題

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

2020-05-14 10:02xialeistudio Golang

在用golang開發人工客服系統的時候碰到了粘包問題,那么什么是粘包呢?下面這篇文章主要給大家介紹了關于如何通過6行代碼快速解決golang TCP粘包問題的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒下面

前言

什么是TCP粘包問題以及為什么會產生TCP粘包,本文不加討論。本文使用golang的bufio.Scanner來實現自定義協議解包。

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

協議數據包定義

本文模擬一個日志服務器,該服務器接收客戶端傳到的數據包并顯示出來

?
1
2
3
4
5
6
7
8
9
10
type Package struct {
 Version  [2]byte // 協議版本,暫定V1
 Length   int16 // 數據部分長度
 Timestamp  int64 // 時間戳
 HostnameLength int16 // 主機名長度
 Hostname  []byte // 主機名
 TagLength  int16 // 標簽長度
 Tag   []byte // 標簽
 Msg   []byte // 日志數據
}

協議定義部分沒有什么好講的,根據具體的業務邏輯定義即可。

數據打包

由于TCP協議是語言無關的協議,所以直接把協議數據包結構體發送到TCP連接中也是不可能的,只能發送字節流數據,所以需要自己實現數據編碼。所幸golang提供了binary來幫助我們實現網絡字節編碼。

?
1
2
3
4
5
6
7
8
9
10
11
12
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方法的輸出目標為io.Writer,有利于接口擴展,只要實現了該接口即可編碼數據寫入。binary.BigEndian是字節序,本文暫時不討論,有需要的讀者可以自行查找資料研究。

數據解包

解包需要將TCP數據包解析到結構體中,接下來會講為什么需要添加幾個數據無關的長度字段。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}

由于主機名、標簽這種數據是不固定長度的,所以需要兩個字節來標識數據長度,否則讀取的時候只知道一個總的數據長度是無法區分主機名、標簽名、日志數據的。

數據包的粘包問題解決

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

  • 定長分隔(每個數據包最大為該長度) 缺點是數據不足時會浪費傳輸資源
  • 特定字符分隔(如rn) 缺點是如果正文中有rn就會導致問題
  • 在數據包中添加長度字段(本文采用的)

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scanner := bufio.NewScanner(reader) // reader為實現了io.Reader接口的對象,如net.Conn
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
 if !atEOF && data[0] == 'V' { // 由于我們定義的數據包頭最開始為兩個字節的版本號,所以只有以V開頭的數據包才處理
  if len(data) > 4 { // 如果收到的數據>4個字節(2字節版本號+2字節數據包長度)
   length := int16(0)
   binary.Read(bytes.NewReader(data[2:4]), binary.BigEndian, &length) // 讀取數據包第3-4字節(int16)=>數據部分長度
   if int(length)+4 <= len(data) { // 如果讀取到的數據正文長度+2字節版本號+2字節數據長度不超過讀到的數據(實際上就是成功完整的解析出了一個包)
    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)
}

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

完整源碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package main
import (
 "bufio"
 "bytes"
 "encoding/binary"
 "fmt"
 "io"
 "log"
 "os"
 "time"
)
 
type Package struct {
 Version  [2]byte // 協議版本
 Length   int16 // 數據部分長度
 Timestamp  int64 // 時間戳
 HostnameLength int16 // 主機名長度
 Hostname  []byte // 主機名
 TagLength  int16 // Tag長度
 Tag   []byte // Tag
 Msg   []byte // 數據部分長度
}
 
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(("現在時間是:" + 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("無效數據包")
 }
}

寫在最后

golang作為一門強大的網絡編程語言,實現自定義協議是非常重要的,實際上實現自定義協議也不是很難,以下幾個步驟:

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://segmentfault.com/a/1190000013493942

延伸 · 閱讀

精彩推薦
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
主站蜘蛛池模板: 奇米影视奇米色777欧美 | 全色黄大色黄大片爽一次 | 久久久久久免费高清电影 | chinese老头和老太交hd | 国产在线xvideos | 视频一区在线观看 | 成人精品一区二区三区中文字幕 | 免费永久观看美女视频网站网址 | 99热这里只有精品在线 | 91九色在线视频 | 国产yw193.㎝m在线观看 | 久久青青草原 | 久久视频这有精品63在线国产 | 福利国产精品 | 韩国久播影院理论片不卡影院 | 久久久高清国产999尤物 | 精品一区二区三区五区六区七区 | 日韩香蕉网 | 成品人视频免费观看 | 地址二地址三2021变更 | 成人免费在线视频观看 | 好姑娘完整版在线观看中文 | 午夜精品久久久 | 国产精品毛片va一区二区三区 | 国产精品午夜性视频网站 | 99久久6er热免费精品 | 久久青青草原 | 亚洲一区二区三区福利在线 | 国产亚洲sss在线播放 | 俄罗斯激情性孕妇孕交大全 | 无限资源在线观看高清 | 日本xxx在线观看免费播放 | 福利入口在线观看 | 日本在线观看免费观看完整版 | 精品久久久久久久久久久久久久久 | 成年人视频在线免费观看 | 成人无高清96免费 | 美女把小内内脱个精光打屁屁 | 操破苍穹全文阅读 | 特黄特色大片免费高清视频 | 欧美黑人ⅹxxx片 |