Golang轻量级TCP服务器框架(五)—— 基础request封装以及router模块绑定
原作者视频地址:zinx-Golang轻量级TCP服务器框架
本人为自学整理的文档,梳理思考开发框架的基本思路,方法,以及视频中不理解的地方。
若想学习,强烈建议直接观看原作视频即可。
可在下方留言交流。
1.思路
在第三篇中,我们可以看到,在request中我们仅仅是将[ ]byte封装进去了。那么关于[ ]byte来说,它的维度太小了,也就是说,可以表示的信息少。所以,我们打算将[ ]byte封装为一个message,并在添加一些代表着该条消息的一些信息,比如消息id,消息命令等等。
还有最重要的一点就是为了解决TCP的粘包问题。至于这个问题怎么产生的这里不在叙述。
解决TCP粘包问题:作者采用的是最简单的TLV通信协议。
① 将整个消息体[ ]byte分为两部分:head和body
② 将head的长度固定,其中包括body长度、消息id、命令序号等字段。body就直接是数据
③ 解包的时候,先按照head固定长度解析出body长度字段,再根据该字段读出整个body
2.整体的需求框图
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封包和拆包的方式,进行发送和接收,这里不再赘述。