• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

GoTCP粘包

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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)
	}
}

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Sentinel-Go 源码系列(三)滑动时间窗口算法的工程实现发布时间:2022-07-10
下一篇:
Eclipse配置开发Go的插件——Goclipse发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap