【zinx】Golang轻量级TCP服务器框架(五)—— 基础message模块以及TCP粘包问题

本文档介绍了使用Golang构建轻量级TCP服务器框架的过程,包括设计基础的request封装,实现IMessage接口,创建TCP封包拆包模块,以及将这些组件集成到服务器中。通过TLV协议解决TCP粘包问题,详细阐述了消息结构、数据打包和解包的实现,并提供了代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Golang轻量级TCP服务器框架(五)—— 基础request封装以及router模块绑定

原作者视频地址:zinx-Golang轻量级TCP服务器框架
本人为自学整理的文档,梳理思考开发框架的基本思路,方法,以及视频中不理解的地方。
若想学习,强烈建议直接观看原作视频即可。
可在下方留言交流。

1.思路

在第三篇中,我们可以看到,在request中我们仅仅是将[ ]byte封装进去了。那么关于[ ]byte来说,它的维度太小了,也就是说,可以表示的信息少。所以,我们打算将[ ]byte封装为一个message,并在添加一些代表着该条消息的一些信息,比如消息id,消息命令等等。
还有最重要的一点就是为了解决TCP的粘包问题。至于这个问题怎么产生的这里不在叙述。
解决TCP粘包问题:作者采用的是最简单的TLV通信协议。
2

① 将整个消息体[ ]byte分为两部分:head和body
② 将head的长度固定,其中包括body长度、消息id、命令序号等字段。body就直接是数据
③ 解包的时候,先按照head固定长度解析出body长度字段,再根据该字段读出整个body

2.整体的需求框图

3

3.IMessage接口的设计

可以根据自己的需求,添加所需要的功能。

/*
	将消息封装到一个Message中
*/

type IMessage interface {
	GetMsgId() uint32
	GetMsgLen() uint32
	GetMsgComm() uint8
	GetData() []byte

	SetMsgId(uint32)
	SetMsgLen(uint32)
	SetMsgComm(uint8)
	SetData([]byte)
}

4.Message接口的实现

这个看起来比较简单,其实就是为IMssage接口将message隐藏起来。

type message struct {
	length uint32
	id uint32
	command uint8
	data []byte
}

func NewMessage (id uint32, comm uint8, data []byte) *message {
	return &message{
		length:  uint32(len(data)),
		id:      id,
		command: comm,
		data:    data,
	}
}

func (m *message) GetMsgId() uint32 {
	return m.id
}

func (m *message) GetMsgLen() uint32 {
	return m.length
}

func (m *message) GetMsgComm() uint8 {
	return m.command
}

func (m *message) GetData() []byte {
	return m.data
}

func (m *message) SetMsgId(id uint32) {
	m.id = id
}
func (m *message) SetMsgLen(len uint32) {
	m.length = len
}

func (m *message) SetMsgComm(comm uint8) {
	m.command = comm
}

func (m *message) SetData(data []byte) {
	m.data = data
}

5.TCP封包拆包模块

package znet

import (
	"bytes"
	"encoding/binary"
	"errors"
	"zinx/utils"
	"zinx/ziface"
)

type DataPack struct { }

func NewDataPack() *DataPack{
	return &DataPack{}
}

func (dp *DataPack) GetHeadLen() uint32 {
	//消息长度4字节+客户端ID4字节+命令1字节
	return 9
}

func (dp *DataPack) Pack(msg ziface.IMessage) ([]byte, error) {
	dataBuf := bytes.NewBuffer([]byte{})
	if err := binary.Write(dataBuf, binary.BigEndian, msg.GetMsgLen()); err != nil {
		return nil, err
	}
	if err := binary.Write(dataBuf, binary.BigEndian, msg.GetMsgId()); err != nil {
		return nil, err
	}
	if err := binary.Write(dataBuf, binary.BigEndian, msg.GetMsgComm()); err != nil {
		return nil, err
	}
	if err := binary.Write(dataBuf, binary.BigEndian, msg.GetData()); err != nil {
		return nil, err
	}
	return dataBuf.Bytes(), nil
}

func (dp *DataPack) UnPack(data []byte) (ziface.IMessage, error) {
	dataBuf := bytes.NewBuffer(data)
	msg := &message{}

	//只解析head信息,获取len、id、command
	if err := binary.Read(dataBuf, binary.BigEndian, &msg.length); err != nil {
		return nil, err
	}
	if err := binary.Read(dataBuf, binary.BigEndian, &msg.id); err != nil {
		return nil, err
	}
	if err := binary.Read(dataBuf, binary.BigEndian, &msg.command); err != nil {
		return nil, err
	}

	if utils.GlobalObject.MaxPackageSize >0 && msg.length > utils.GlobalObject.MaxPackageSize {
		return nil, errors.New("too Large msg data recv!")
	}

	return msg, nil
}

6.server集成

打包部分:

利用message装载数据信息,用Pack进行打包,转为二进制[ ]byte,通过链接发送出去

//发送数据,将数据发送给client
func (c *connection) SendMsg(id uint32, comm uint8, data []byte) error {
	if c.isClosed == true {
		return errors.New("Connection closed when send msg")
	}

	dp := NewDataPack()
	binaryMsg, err := dp.Pack(NewMessage(id, comm, data))
	if err != nil {
		return errors.New("Connection msg pack is err")
	}

	if _, err = c.conn.Write(binaryMsg); err != nil {
		fmt.Printf("send msg id is %d, is err",c.connID)
	}

	return nil
}

拆包部分:

屏蔽的部分是原来之前的方法。
在星1处,解析出头部。在星2处,解析出body。
并将消息的信息装入msg,最后组成request。

	for {
		//buf := make([]byte, 512)
		//cnt , err := c.conn.Read(buf)
		//if err != nil {
		//	fmt.Println("connID = ", c.connID,"recv is err :",err)
		//	continue
		//}
*1		dp := NewDataPack()
		HeadBuf := make([]byte,dp.GetHeadLen())
		n, err := io.ReadFull(c.conn, HeadBuf)
		if err != nil || n != int(dp.GetHeadLen()){
			fmt.Println("server read is err :", err)
			break
		}

		msg, err := dp.UnPack(HeadBuf)
		if err != nil {
			fmt.Println("unpack is err :", err)
			break
		}

*2		if msg.GetMsgLen() > 0 {
			data := make([]byte, msg.GetMsgLen())
			n, err := io.ReadFull(c.conn, data)
			if err != nil || n != int(msg.GetMsgLen()){
				fmt.Println("server read is err :", err)
				break
			}
			msg.SetData(data)
		}

		req := request{
			conn: c,
			msg: msg,
		}

		//执行注册的路由方法
		go func(request ziface.IRequest) {
			c.Router.PreHandle(request)
			c.Router.Handle(request)
			c.Router.PostHandle(request)
		}(&req)

	}

7.代码测试

其实就是把client以及server端的代码,用TCP封包和拆包的方式,进行发送和接收,这里不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辛集电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值