Netty游戏服务器之四protobuf编解码和黏包处理

我们还没讲客户端怎么向服务器发送消息,服务器怎么接受消息。

 

在讲这个之前我们先要了解一点就是tcp底层存在粘包和拆包的机制,所以我们在进行消息传递的时候要考虑这个问题。

 

看了netty权威这里处理的办法:

我决定netty采用自带的半包解码器LengthDecoder()的类处理粘包的问题,客户端我是用这里的第三种思路。

消息的前四个字节是整个消息的长度,客户端接收到消息的时候就将前4个字节解析出来,然后再根据长度接收消息。

 

那么消息的编解码我用的是google的protobuf,这个在业界也相当有名,大家可以百度查查。不管你们用不用,反正我是用了。

 

在了解完之后,我们就来搭建这个消息编解码的框架(当然这个只是我个人的想法,可能有很多不好的地方,你们可以指正)

 

首先需要下载的是支持c#的protobuf-net插件,注意google官方的是不支持c#的。

 

http://pan.baidu.com/s/1eQdFTmU

 

打开压缩包,找到Full/Unity/protobuf-net.dll复制到我们的unity中。

 

在服务端呢,我用的是protobuff,这处理速度听说和原生的相差不大。

 

和之前的一样,吧这些jar包都添加到eclipse的build-path中。

 

好了,消息我服务器和客户端都写一个统一的协议SocketModel类,这样传送消息的时候就不会有歧义。

C#中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using ProtoBuf;//注意要用到这个dll
[ProtoContract]
public class SocketModel{
    [ProtoMember(1)]
    private int type;//消息类型
    [ProtoMember(2)]
    private int area;//消息区域码
    [ProtoMember(3)]
    private int command;//指令
    [ProtoMember(4)]
    private List<string> message;//消息
    public SocketModel()
    {
 
    }
    public SocketModel(int type, int area, int command,List<string> message)
    {
        this.type = type;
        this.area = area;
        this.command = command;
        this.message = message;
    }
    public int GetType()
    {
        return type;
    }
    public void SetType(int type)
    {
        this.type = type;
    }
    public int GetArea()
    {
        return this.area;
    }
    public void SetArea(int area)
    {
        this.area = area;
    }
    public int GetCommand()
    {
        return this.command;
    }
    public void SetCommand(int command)
    {
        this.command = command;
    }
    public List<string> GetMessage()
    {
        return message;
    }
    public void SetMessage(List<string> message)
    {
        this.message = message;
    }
}

  java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SocketModel {
    private int type;
    private int area;
    private int command;
    private List<String> message;
 
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public int getArea() {
        return area;
    }
    public void setArea(int area) {
        this.area = area;
    }
    public int getCommand() {
        return command;
    }
    public void setCommand(int command) {
        this.command = command;
    }
    public List<String> getMessage() {
        return message;
    }
    public void setMessage(List<String> message) {
        this.message = message;
    }
}

  好了,制定好协议后,我们来动手在服务器搞出点事情来。

首先,打个包com.netty.decoder,在里面我们创建我们的解码器类,LengthDecode和MessageDecode类

1
2
3
4
5
6
7
8
9
public class LengthDecoder extends LengthFieldBasedFrameDecoder{
 
    public LengthDecoder(int maxFrameLength, int lengthFieldOffset,
            int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment,
                initialBytesToStrip);
    }
     
}

  这个功能你们可以去百度查,主要是吧接收到的二进制消息的前四个字节干掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MessageDecoder extends ByteToMessageDecoder{
    private Schema<SocketModel> schema = RuntimeSchema.getSchema(SocketModel.class);//protostuff的写法
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in,
            List<Object> obj) throws Exception {
        byte[] data = new byte[in.readableBytes()];
        in.readBytes(data);
        SocketModel message = new SocketModel();
        ProtobufIOUtil.mergeFrom(data, message, schema);
        obj.add(message);
    }
     
}

  这个主要是吧接收的二进制转化成我们的协议消息SocketModel类型。

接着是编码器类,我们也打一个包,com.netty.encoder,里面创建一个MessageEncoder

在写这个之前我们写个工具类,com.netty.util,里面我么创建一个CoderUtil类,主要处理int和byte之间的转化。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CoderUtil {
    /**
     * 将字节转成整形
     * @param data
     * @param offset
     * @return
     */
    public static int bytesToInt(byte[] data, int offset) {
           int num = 0;
           for (int i = offset; i < offset + 4; i++) {
            num <<= 8;
            num |= (data[i] & 0xff);
           }
           return num;
        }
    /**
     * 将整形转化成字节
     * @param num
     * @return
     */
    public static byte[] intToBytes(int num) {  
        byte[] b = new byte[4];
           for (int i = 0; i < 4; i++) {
            b[i] = (byte) (num >>> (24 - i * 8));
           }
           return b;
    }
 
}

  MessageEncoder:

1
2
3
4
5
6
7
8
9
10
11
12
public class MessageEncoder extends MessageToByteEncoder<SocketModel>{
    private Schema<SocketModel> schema = RuntimeSchema.getSchema(SocketModel.class);
    @Override
    protected void encode(ChannelHandlerContext ctx, SocketModel message,
            ByteBuf out) throws Exception {
        //System.out.println("encode");
        LinkedBuffer buffer = LinkedBuffer.allocate(1024);
        byte[] data = ProtobufIOUtil.toByteArray(message, schema, buffer);
        ByteBuf buf = Unpooled.copiedBuffer(CoderUtil.intToBytes(data.length),data);//在写消息之前需要把消息的长度添加到投4个字节
        out.writeBytes(buf);
    }
}

  在写完这些编解码,我们需要将他们加到channel的pipeline中,

1
2
3
4
5
6
protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LengthDecoder(1024,0,4,0,4));
                    ch.pipeline().addLast(new MessageDecoder());
                    ch.pipeline().addLast(new MessageEncoder());
                    ch.pipeline().addLast(new ServerHandler());
                }

  

 

————————————————————————服务器告一段落,接着写客户端————————————————————————————

在我们之前写的MainClient的代码中我们加入接收和发送消息的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private byte[] recieveData;
 
private int len;
 
private bool isHead;
 
void Start()
{
  if (client == null)
  {
    Connect();
  }
  isHead = true;
  recieveData = new byte[800];
  client.GetStream().BeginRead(recieveData,0,800,ReceiveMsg,client.GetStream());//在start里面开始异步接收消息
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public void SendMsg(SocketModel socketModel)
    {
        byte[] msg = Serial(socketModel);
        //消息体结构:消息体长度+消息体
        byte[] data = new byte[4 + msg.Length];
        IntToBytes(msg.Length).CopyTo(data, 0);
        msg.CopyTo(data, 4);
        client.GetStream().Write(data, 0, data.Length);
        //print("send");
    }
    public void ReceiveMsg(IAsyncResult ar)//异步接收消息
    {
        NetworkStream stream = (NetworkStream)ar.AsyncState;
        stream.EndRead(ar);
        //读取消息体的长度
        if (isHead)
        {
            byte[] lenByte = new byte[4];
            System.Array.Copy(recieveData,lenByte,4);
            len = BytesToInt(lenByte, 0);
            isHead = false;
        }
        //读取消息体内容
        if (!isHead)
        {
            byte[] msgByte = new byte[len];
            System.Array.ConstrainedCopy(recieveData,4,msgByte,0,len);
            isHead = true;
            len = 0;
            message = DeSerial(msgByte);
        }
        stream.BeginRead(recieveData,0,800,ReceiveMsg,stream);
    }  
    private byte[] Serial(SocketModel socketModel)//将SocketModel转化成字节数组
    {
        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.Serialize<SocketModel>(ms, socketModel);
            byte[] data = new byte[ms.Length];
            ms.Position= 0;
            ms.Read(data, 0, data.Length);
            return data;
        }
    }
    private SocketModel DeSerial(byte[] msg)//将字节数组转化成我们的消息类型SocketModel
    {
        using(MemoryStream ms = new MemoryStream()){
            ms.Write(msg,0,msg.Length);
            ms.Position = 0;
            SocketModel socketModel = Serializer.Deserialize<SocketModel>(ms);
            return socketModel;
        }
    }
    public static int BytesToInt(byte[] data, int offset)
    {
        int num = 0;
        for (int i = offset; i < offset + 4; i++)
        {
            num <<= 8;
            num |= (data[i] & 0xff);
        }
        return num;
    }
    public static byte[] IntToBytes(int num)
    {
        byte[] bytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            bytes[i] = (byte)(num >> (24 - i * 8));
        }
        return bytes;
    }

  

就行告一段落,太长了不好,读者可能吃不消。但我不鄙视长不好,终究长还是最有用的 =_=!

posted @   草帽领  阅读(9421)  评论(10编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示