Go TCP 粘包


Go語言的TCP示例:

實現功能:客戶端發送"abc",服務端轉為大寫返回"ABC"

服務端

package main

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

func handle(conn net.Conn) {
     defer conn.Close()        // 服務端關閉連接
    for {
        var b [8]byte
         /* 用bufio接收,每次只接收8字節,剩下的扔掉。相當於我緩沖區只有8字節,你發來的大於8字節的我就扔了  */
        reader := bufio.NewReader(conn)
        reader.Read(b[:])
        // conn.Read(b[:])          //如果用conn接收,每次接收8字節,剩下的會存在緩沖區中,下一次會接收到上一次沒收完的,粘包
        ret := strings.ToUpper(string(b[:]))
        conn.Write([]byte(ret))
    }
}

func main() {
    sock, _ := net.Listen("tcp", "127.0.0.1:8000")
    for {
        conn, _ := sock.Accept() //阻塞1
        fmt.Println("來了個連接:", conn)
        go handle(conn)
    }
}

客戶端

package main

import (
    "fmt"
    "net"
)

func main() {
    conn, _ := net.Dial("tcp", "127.0.0.1:8000")
     defer conn.Close()      // 客戶端關閉連接
    for {
        var msg string
        fmt.Printf("請輸入:")
        fmt.Scanln(&msg)
        conn.Write([]byte(msg))
        var ret [128]byte
        conn.Read(ret[:])
        fmt.Println(string(ret[:]))
    }
}

在linux環境下Go使用的epoll多路復用IO模型(與goroutine無關,就算服務端兩個地方都阻塞也是epoll io),所以Go原生就能支持很高的並發

 

 

 

 

 

解決粘包(主要解決接收端的粘包,發送端假設只發一次)

用前4個字節存數據的長度, 剩下的字節存數據

(  大端和小端:數據存取和讀取的順序

16進制數:0x123456 占用3個字節

協議用4字節存數據長度

12 34 56 00 大端,高位在左邊

00 56 34 12 小端 ,高位在右邊)

 

// socket_stick/proto/proto.go
package proto

import (
  "bufio"
  "bytes"
  "encoding/binary"
)

// Encode 將消息編碼
func Encode(message string) ([]byte, error) {
  // 讀取消息的長度,轉換成int32類型(占4個字節)
  var length = int32(len(message))    // 計算msg長度,放入4字節int中
  var pkg = new(bytes.Buffer)
  err := binary.Write(pkg, binary.LittleEndian, length)   //  寫入msg長度。小端的方式存放
  if err != nil {
    return nil, err
  }
  err = binary.Write(pkg, binary.LittleEndian, []byte(message))   // 寫入消息實體。
  if err != nil {
    return nil, err
  }
  return pkg.Bytes(), nil
}

// Decode 解碼消息
func Decode(reader *bufio.Reader) (string, error) {
  // 讀取消息的長度
  lengthByte, _ := reader.Peek(4)                 // 讀取前4個字節byte,存放的是數據長度
  lengthBuff := bytes.NewBuffer(lengthByte)
  var length int32
  err := binary.Read(lengthBuff, binary.LittleEndian, &length)    //  將4個字節的byte,轉為int32,放入length,length是數據長度
  if err != nil {
    return "", err
  }
  if int32(reader.Buffered()) < length+4 {   // 如果reader總長度<4報錯
    return "", err
  }

  // 讀取真正的消息數據
  pack := make([]byte, int(4+length))    // 在reader中讀取length+4長度的字節(length是數據的長度)
  _, err = reader.Read(pack)
  if err != nil {
    return "", err
  }
  return string(pack[4:]), nil
}

 

服務端:

// socket_stick/server2/main.go

func process(conn net.Conn) {
  defer conn.Close()
  reader := bufio.NewReader(conn)
  for {
    msg, err := proto.Decode(reader)
    if err == io.EOF {
      return
    }
    if err != nil {
      fmt.Println("decode msg failed, err:", err)
      return
    }
    fmt.Println("收到client發來的數據:", msg)
  }
}

func main() {

  listen, err := net.Listen("tcp", "127.0.0.1:30000")
  if err != nil {
    fmt.Println("listen failed, err:", err)
    return
  }
  defer listen.Close()
  for {
    conn, err := listen.Accept()
    if err != nil {
      fmt.Println("accept failed, err:", err)
      continue
    }
    go process(conn)
  }
}

客戶端:

// socket_stick/client2/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("dial failed, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` data, err := proto.Encode(msg) if err != nil { fmt.Println("encode msg failed, err:", err) return } conn.Write(data) } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM