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

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

  

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

时间: 2024-08-22 06:14:07

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

游戏服务器之服务器优化思路

本文只是提供一些游戏服务器优化思路,其中一些思路是用在不同场合的,不是同个架构的.需要根据应用场景选用合适方式. 一.框架设计优化 1.分静态服务器和动态服务器. 2.动态服务器使用两层负载均衡:多网关  和 多场景.网关的选择是登陆服务器根据网关的负载来选择.场景则作为分线和副本等分开. 框架图参考:http://blog.csdn.net/chenjiayi_yun/article/details/18891591 3.中心服务器负责服务器依赖检查和内部消息转发和控制登录流程.中心服务器会主

游戏服务器之多进程架构通信 协程切换只是简单地改变执行函数栈,不涉及内核态与用户态转化,也涉及上下文切换,

游戏服务器之多进程架构通信 https://gameinstitute.qq.com/community/detail/124098 https://www.zhihu.com/question/23508968 游戏服务器与普通服务器有什么区别? 游戏开发中的TCP.UDP.HTTP.WebSocket四种网络通讯协议对比 https://gameinstitute.qq.com/community/detail/127562 https://www.jianshu.com/p/4eb37c1

Netty编解码框架分析

1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decode)/反序列化(deserialization)把从网络.磁盘等读取的字节数组还原成原始对象(通常是原始对象的拷贝),以方便后续的业务逻辑操作. 进行远程跨进程服务调用时(例如RPC调用),需要使用特定的编解码技术,对需要进行网络传输的对象做编码或者解码,以便完成远程调用. 1.2. 常用的编解码框

【转】Netty系列之Netty编解码框架分析

http://www.infoq.com/cn/articles/netty-codec-framework-analyse/ 1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decode)/反序列化(deserialization)把从网络.磁盘等读取的字节数组还原成原始对象(通常是原始对象的拷贝),以方便后续的业务逻辑操作. 进行远程跨进程服务调用时(

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

Netty 编解码技术 数据通信和心跳监控案例 多台服务器之间在进行跨进程服务调用时,需要使用特定的编解码技术,对需要进行网络传输的对象做编码和解码操作,以便完成远程调用.Netty提供了完善,易扩展,易使用的编解码技术.本章除了介绍Marshalling的使用,还会基于编解码技术实现数据通信和心跳检测案例.通过本章,你将学到Java序列化的优缺点,主流编解码框架的特点,模拟特殊长连接通信,心跳监控案例.还在等什么,丰满的知识等你来拿! 技术:编解码,数据通信,心跳监控 说明:github上有完

Google 的Protobuf 的使用方式(序列化和反序列化工具-编解码)

1.google的protobuf是什么? 用于rpc的自定义协议,体积更小,序列化和反序列化的第三方库,和apache thrift是同一种技术. 2.rpc库的介绍? (1) RMI    remote  method  invocation   广泛用于EJB,实际上是一种跨机器的调用,通过网络传输,调用方A调用序列化字节码传输到B机器反序列化,调用B的方法,B回传结果后序列化网路传输,A反序列化成最终结果. 限制 : 只针对于Java语言. 特点 :网络传输代码自动生成   client

Netty入门系列(3) --使用Netty进行编解码的操作

前言 何为编解码,通俗的来说,我们需要将一串文本信息从A发送到B并且将这段文本进行加工处理,如:A将信息文本信息编码为2进制信息进行传输.B接受到的消息是一串2进制信息,需要将其解码为文本信息才能正常进行处理. 上章我们介绍的Netty如何解决拆包和粘包问题,就是运用了解码的这一功能. java默认的序列化机制 使用Netty大多是java程序猿,我们基于一切都是对象的原则,经常会将对象进行网络传输,那么对于序列化操作肯定大家都是非常熟悉的. 一个对象是不能直接进行网络I/O传输的,jdk默认是

java编解码技术,netty nio

对于java提供的对象输入输出流ObjectInputStream与ObjectOutputStream,可以直接把java对象作为可存储的字节数组写入文件,也可以传输到网络上去.对与java开放人员来说,默认的jdk序列化机制可以避免操作底层的字节数组,从而提升开发效率. 1.为什么需要序列化 网络传输与对象序列化 2.java编解码技术指的什么 netty nio是基于网络传输,当进行远程跨进程服务调用时,需要把被传输的对象编码为字节数组或者bytebuffer对象.而当远程服务读取到byt

Netty实战-对象编解码,Netty对象网络传递

书籍推荐:        实例代码 :http://download.csdn.net/detail/jiangtao_st/7677503 Server端代码 <span style="font-size:12px;">/** * * <p> * Netty Server Simple * </p> * * LineBasedFrameDecoder + 消息中得换行符 * * @author 卓轩 * @创建时间:2014年7月7日 * @ver