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

在讲这个之前我们先要了解一点就是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#中:

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中:

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类

public class LengthDecoder extends LengthFieldBasedFrameDecoder{

	public LengthDecoder(int maxFrameLength, int lengthFieldOffset,
int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment,
initialBytesToStrip);
} }

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

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之间的转化。

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:

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中,

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的代码中我们加入接收和发送消息的方法。

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里面开始异步接收消息
}

  

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

  

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

Netty游戏服务器之四protobuf编解码和黏包处理的更多相关文章

  1. (中级篇 NettyNIO编解码开发)第八章-Google Protobuf 编解码-2

    8.1.2    Protobuf编解码开发 Protobuf的类库使用比较简单,下面我们就通过对SubscrjbeReqProto进行编解码来介绍Protobuf的使用. 8-1    Protob ...

  2. (中级篇 NettyNIO编解码开发)第八章-Google Protobuf 编解码-1

    Google的Protobuf在业界非常流行,很多商业项目选择Protobuf作为编解码框架,这里一起回顾一下Protobuf    的优点.(1)在谷歌内部长期使用,产品成熟度高:(2)跨语言,支持 ...

  3. Netty游戏服务器之一

    所谓磨刀不误砍柴工,所以在搭建netty游戏服务器之前,我们先要把要准备的东西做好. 首先进入netty的官网下载最新版本的netty的jar包,http://netty.io/downloads.h ...

  4. Netty--Google Protobuf编解码

    Google Protobuf是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化.它很适合做数据存储或 RPC 数据交换格式.可用于通讯协议.数据存储等领域的语言无关.平台无关.可扩展的序列 ...

  5. Netty游戏服务器之六服务端登录消息处理

    客户端unity3d已经把消息发送到netty服务器上了,那么ServerHandler类的public void channelRead(ChannelHandlerContext ctx, Obj ...

  6. Netty游戏服务器之三搭建Unity客户端

    既然已经写完了相关的服务器处理类,那么我们就来搭建客户端测试一下. 打开我们的unity3d,然后新建一个c#脚本,取名为MainClient. public class MainClient : M ...

  7. Netty游戏服务器之五Unity3d登陆消息

    今天我们来讲客户端Unity和服务器收发消息的具体过程. 首先,我们要在unity上搭建登陆界面的UI,这里呢,我用的是NGUI插件. 相信做过unity3d前端的都对这个非常的熟悉,最近官方的UGU ...

  8. Netty游戏服务器二

    上节我们写个server主类,那么发现什么事情都干不了,是的,我们还没有做任何的业务处理. 接着我们开始写处理客户端连接,发送接收数据的类ServerHandler. public class Ser ...

  9. Netty 编解码技术 数据通信和心跳监控案例

    Netty 编解码技术 数据通信和心跳监控案例 多台服务器之间在进行跨进程服务调用时,需要使用特定的编解码技术,对需要进行网络传输的对象做编码和解码操作,以便完成远程调用.Netty提供了完善,易扩展 ...

随机推荐

  1. FineReport——JS二次开发(工具栏按钮事件及说明)

    首先获取到这个模板对象: document.getElementById('reportFrame').contentWindow.contentPane.方法名称(); 方法以及说明:

  2. [ python ] 下划线的意义和一些特殊方法

    Python用下划线 Python用下划线为变量前缀和后缀制定特殊变量 _xxx 不能用 'from module import *' 导入__xxx__ 系统定义名字__xxx 类中的私有变量名 核 ...

  3. [PAT] 1141 PAT Ranking of Institutions(25 分)

    After each PAT, the PAT Center will announce the ranking of institutions based on their students' pe ...

  4. ReentrantLock 学习

    Java接口Lock有三个实现类:ReentrantLock.ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock.Lock ...

  5. (翻译)与.NET容器映像保持同步

    原文:https://blogs.msdn.microsoft.com/dotnet/2018/06/18/staying-up-to-date-with-net-container-images/ ...

  6. 关于在C#中对抽象类的理解

    先说一下自己对类的理解吧.类就是指将一系列具有一些共同特性的事物归纳起来,按照不同的特性分为不同的类.比如在现实世界中人是一类,动物是一类.植物 又是一类.但他们都是生命这一类的派生类.他们都继承了生 ...

  7. file '/grub/i386-pc/normal.mod' not found.解决方案

    前言: 因为之前装的Ubuntu出了点问题,本想直接清除Ubuntu数据重新装一下,结果蹦出这么个BUG来,揪心,弄了大半天终于弄好了. 废话不多说,直接按教程走吧. GRUB启动: 在grub启动界 ...

  8. 将excel中的sheet1导入到sqlserver中

    原文地址:C#将Excel数据表导入SQL数据库的两种方法作者:windream 方式一: 实现在c#中可高效的将excel数据导入到sqlserver数据库中,很多人通过循环来拼接sql,这样做不但 ...

  9. POJ 1797 Heavy Transportation 【最大生成树的最小边/最小瓶颈树】

    Background Hugo Heavy is happy. After the breakdown of the Cargolifter project he can now expand bus ...

  10. 配置文件报错:元素类型 "XXX" 必须后跟属性规范 ">" 或 "/>"

    这是一个比较常见的配置错误,一般分两步. 有的情况的确是xml文件出现了语法问题,可以让IDE来帮我们检查xml文件是否真的出现了语法错误.把xml内容复制进IDE中,让IDE帮我们检查错误. 如上图 ...