• 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)
    	}
    }
  • 相关阅读:
    C# NAudio 变声
    初探Protostuff的使用
    CentOS7 配置阿里云yum源,非常之简单
    javacv 视频增加视频(画中画)
    抓包调试fiddler
    CentOS7 手动编译升级GCC至9.3.0
    C# CefSharp 新版本(83 以后版本) 如何在js中直接调用c#类
    毕业设计——驾驶证理论考试系统的设计与实现 2022年1月2日20:35:14
    送餐机器人乐动雷达记录
    linux下拉取git代码
  • 原文地址:https://www.cnblogs.com/staff/p/13245729.html
Copyright © 2020-2023  润新知