前言
- 之前我们使用Request来保存服务器的数据,很显然使用[]byte来接收数据,没有长度也没有消息类型,接下来就要针对这个消息进行封装
一、创建消息类型
- 消息思路:一个基本的message包,会包含消息ID、数据、数据长度三个成员,并提供基本的setter和getter方法
- zinx/ziface/imessage.go接口
package ziface
type IMessage interface {
GetMsgId() uint32
GetMsgLen() uint32
GetData() []byte
SetMsgId(uint32)
SetData([]byte)
SetDataLen(uint32)
}
package znet
type Message struct {
Id uint32
DataLen uint32
Data []byte
}
func NewMsgPackage(id uint32, data []byte) *Message {
return &Message{
Id: id,
DataLen: uint32(len(data)),
Data: data,
}
}
func (m *Message) GetMsgId() uint32 {
return m.Id
}
func (m *Message) GetMsgLen() uint32 {
return m.DataLen
}
func (m *Message) GetData() []byte {
return m.Data
}
func (m *Message) SetMsgId(id uint32) {
m.Id = id
}
func (m *Message) SetData(data []byte) {
m.Data = data
}
func (m *Message) SetDataLen(len uint32) {
m.DataLen = len
}
二、消息的粘包
- 什么是粘包:可以查看我写的另外一篇关于粘包的问题 —— 十、粘包(一)网络缓冲区
- zinx如何处理粘包:由于Zinx也是TCP流的形式传播数据,难免会出现消息1和消息2⼀同发送,那么zinx就需要有能⼒区分两个消息的边界,所以Zinx此时应该提供⼀个统⼀的拆包和封包的⽅法。在发包之前打包成如上图这种格式的有head和body的两部分的包,在收到数据的时候分两次进⾏读取,先读取固定⻓度的head部分,得到后续Data的⻓度,再根据DataLen读取之后的body。这样就能够解决粘包的问题了
![TLV(Type-Len-Value)封包](https://img-blog.csdnimg.cn/fe142198c3b742be90b808ecce46ebd2.png)
三、封包拆包的实现
1 - 接口:ziface/idatapack.go
package ziface
type IDataPack interface {
GetHeadLen() uint32
Pack(msg IMessage) ([]byte, error)
Unpack([]byte) (IMessage, error)
}
2 - 实现:znet/datapack.go
package znet
import (
"bytes"
"encoding/binary"
"errors"
"zinx/utils"
"zinx/ziface"
)
type DataPack struct{}
func NewDataPack() *DataPack {
return &DataPack{}
}
func (dp *DataPack) GetHeadLen() uint32 {
return 8
}
func (dp *DataPack) Pack(msg ziface.IMessage) ([]byte, error) {
dataBuff := bytes.NewBuffer([]byte{})
if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgLen()); err != nil {
return nil, err
}
if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgId()); err != nil {
return nil, err
}
if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetData()); err != nil {
return nil, err
}
return dataBuff.Bytes(), nil
}
func (dp *DataPack) Unpack(binaryData []byte) (ziface.IMessage, error) {
dataBuff := bytes.NewReader(binaryData)
msg := &Message{}
if err := binary.Read(dataBuff, binary.LittleEndian, &msg.DataLen); err != nil {
return nil, err
}
if err := binary.Read(dataBuff, binary.LittleEndian, &msg.Id); err != nil {
return nil, err
}
if utils.GlobalObject.MaxPackageSize > 0 && msg.DataLen > utils.GlobalObject.MaxPackageSize {
return nil, errors.New("too Large msg data recv!")
}
return msg, nil
}
3 - 单元测试:znet/datapack_test.go
- 单元测试思路
- server使用go程接受数据实现拆包
- client启动go程序,将2个包组合发送模拟粘包
- 注意:注释掉zinx/utils/globalobj.go的
GlobalObject.Reload()
否则会报错(单元测试结束后记得修改回来)
package znet
import (
"fmt"
"io"
"net"
"testing"
)
func TestDataPack(t *testing.T) {
listenner, err := net.Listen("tcp", "127.0.0.1:7777")
if err != nil {
fmt.Println("server listen err: ", err)
return
}
go func() {
for {
conn, err := listenner.Accept()
if err != nil {
fmt.Println("server accept error", err)
}
go func(conn net.Conn) {
dp := NewDataPack()
for {
headData := make([]byte, dp.GetHeadLen())
_, err := io.ReadFull(conn, headData)
if err != nil {
fmt.Println("read head error")
break
}
msgHead, err := dp.Unpack(headData)
if err != nil {
fmt.Println("server unpacke err ", err)
return
}
if msgHead.GetMsgLen() > 0 {
msg := msgHead.(*Message)
msg.Data = make([]byte, msg.GetMsgLen())
_, err := io.ReadFull(conn, msg.Data)
if err != nil {
fmt.Println("server unpack data err: ", err)
return
}
fmt.Println("---> Recv MsgID: ", msg.Id, ", datalen = ", msg.DataLen, "data = ", string(msg.Data))
}
}
}(conn)
}
}()
conn, err := net.Dial("tcp", "127.0.0.1:7777")
if err != nil {
fmt.Println("client dial err: ", err)
return
}
dp := NewDataPack()
msg1 := &Message{
Id: 1,
DataLen: 4,
Data: []byte{'z', 'i', 'n', 'x'},
}
sendData1, err := dp.Pack(msg1)
if err != nil {
fmt.Println("client pack msg1 error", err)
return
}
msg2 := &Message{
Id: 2,
DataLen: 7,
Data: []byte{'n', 'i', 'h', 'a', 'o', '!', '!'},
}
sendData2, err := dp.Pack(msg2)
if err != nil {
fmt.Println("client pack msg1 error", err)
return
}
sendData1 = append(sendData1, sendData2...)
conn.Write(sendData1)
select {}
}
- 测试结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/683d7d5e05b1406c8b0a6f9920ee7271.png)
四、消息封装集成到Zinx框架
- 实现步骤
- ①.将Message添加到Request属性中
- ②.修改链接读取数据的机制,将之前的单纯的读取byte改成拆包形式的读取按照TLV形式读取
- ③.给链接提供一个发包机制: 将发送的消息进行打包,再发送
1 - ziface/irequest.go
package ziface
type IRequest interface {
GetConnection() IConneciton
GetData() []byte
GetMsgID() uint32
}
2 - znet/request.go
package znet
import (
"zinx/ziface"
)
type Request struct {
conn ziface.IConneciton
msg ziface.IMessage
}
func (r *Request) GetConnection() ziface.IConneciton {
return r.conn
}
func (r *Request) GetData() []byte {
return r.msg.GetData()
}
func (r *Request) GetMsgID() uint32 {
return r.msg.GetMsgId()
}
3 - znet/connection.go
func (c *Connection) StartReader() {
fmt.Println(" Reader Goroutine is running...")
defer fmt.Println("connID = ", c.ConnID, " Reader is exit, remote addr is ", c.RemoteAddr().String())
defer c.Stop()
for {
dp := NewDataPack()
headData := make([]byte, dp.GetHeadLen())
if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil {
fmt.Println("read msg head error", err)
break
}
msg, err := dp.Unpack(headData)
if err != nil {
fmt.Println("unpack error", err)
break
}
var data []byte
if msg.GetMsgLen() > 0 {
data = make([]byte, msg.GetMsgLen())
if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil {
fmt.Println("read msg data error ", 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)
}
}
4 - ziface/iconnection.go
package ziface
import "net"
type IConneciton interface {
Start()
Stop()
GetTCPConnection() *net.TCPConn
GetConnID() uint32
RemoteAddr() net.Addr
SendMsg(msgId uint32, data []byte) error
}
type HandleFunc func(*net.TCPConn, []byte, int) error
5 - znet/connection.go
func (c *Connection) SendMsg(msgId uint32, data []byte) error {
if c.isClosed == true {
return errors.New("Connection closed when send msg")
}
dp := NewDataPack()
binaryMsg, err := dp.Pack(NewMsgPackage(msgId, data))
if err != nil {
fmt.Println("Pack error msg id = ", msgId)
return errors.New("Pack error msg")
}
if _, err := c.Conn.Write(binaryMsg); err != nil {
fmt.Println("Write msg id ", msgId, " error :", err)
return errors.New("conn Write error")
}
return nil
}
6 - znet/message.go
func NewMsgPackage(id uint32, data []byte) *Message {
return &Message{
Id: id,
DataLen: uint32(len(data)),
Data: data,
}
}
7 - 测试类server.go
package main
import (
"fmt"
"zinx/ziface"
"zinx/znet"
)
type PingRouter struct {
znet.BaseRouter
}
func (this *PingRouter) Handle(request ziface.IRequest) {
fmt.Println("Call Router Handle...")
fmt.Println("recv from client: msgID = ", request.GetMsgID(),
", data = ", string(request.GetData()))
err := request.GetConnection().SendMsg(1, []byte("ping...ping...ping"))
if err != nil {
fmt.Println(err)
}
}
func main() {
s := znet.NewServer("[zinx V0.5]")
s.AddRouter(&PingRouter{})
s.Serve()
}
8 - 测试类client.go
package main
import (
"fmt"
"io"
"net"
"time"
"zinx/znet"
)
func main() {
fmt.Println("client start...")
time.Sleep(1 * time.Second)
conn, err := net.Dial("tcp", "127.0.0.1:8999")
if err != nil {
fmt.Println("client start err, exit!")
return
}
for {
dp := znet.NewDataPack()
binaryMsg, err := dp.Pack(znet.NewMsgPackage(0, []byte("ZinxV0.5 client Test Message")))
if err != nil {
fmt.Println("Pack error:", err)
return
}
if _, err := conn.Write(binaryMsg); err != nil {
fmt.Println("write error", err)
return
}
binaryHead := make([]byte, dp.GetHeadLen())
if _, err := io.ReadFull(conn, binaryHead); err != nil {
fmt.Println("read head error ", err)
break
}
msgHead, err := dp.Unpack(binaryHead)
if err != nil {
fmt.Println("client unpack msgHead error ", err)
break
}
if msgHead.GetMsgLen() > 0 {
msg := msgHead.(*znet.Message)
msg.Data = make([]byte, msg.GetMsgLen())
if _, err := io.ReadFull(conn, msg.Data); err != nil {
fmt.Println("read msg data error , ", err)
return
}
fmt.Println("---> Recv Server Msg : ID = ", msg.Id, ", len = ", msg.DataLen, ", data = ", string(msg.Data))
}
time.Sleep(1 * time.Second)
}
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/8e21ee1f7c7d4282b5a05fe47e03a623.png)
五、项目目录结构
![在这里插入图片描述](https://img-blog.csdnimg.cn/40841189354d47bab0fcd2a4a7cc4547.png)
六、消息封装实现总结
![在这里插入图片描述](https://img-blog.csdnimg.cn/dc1c6968ee254351ac5b072a4eb2510b.png)
七、完整源码
点击下载:zinxV0.5