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

  

就行告一段落,太長了不好,讀者可能吃不消。但我不鄙視長不好,終究長還是最有用的 =_=!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM