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

2024-03-08 22:48

本文主要是介绍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的写法@Overrideprotected 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);@Overrideprotected 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;}

  

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

转载于:https://www.cnblogs.com/CaomaoUnity3d/p/4610183.html

这篇关于Netty游戏服务器之四protobuf编解码和黏包处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/788662

相关文章

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

resultMap如何处理复杂映射问题

《resultMap如何处理复杂映射问题》:本文主要介绍resultMap如何处理复杂映射问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录resultMap复杂映射问题Ⅰ 多对一查询:学生——老师Ⅱ 一对多查询:老师——学生总结resultMap复杂映射问题

基于Python打造一个可视化FTP服务器

《基于Python打造一个可视化FTP服务器》在日常办公和团队协作中,文件共享是一个不可或缺的需求,所以本文将使用Python+Tkinter+pyftpdlib开发一款可视化FTP服务器,有需要的小... 目录1. 概述2. 功能介绍3. 如何使用4. 代码解析5. 运行效果6.相关源码7. 总结与展望1

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤