/** 刚开始学习游戏开发时想找一个基于netty的游戏demo十分困难,工作一段时间后了解框架后将其分享出来; 该框架是从别人框架移植修改完善而来,不是我一个人写,算是借花献佛; 实际业务开发比此框架要复杂得多,去繁从简主在体现核心思想; 这是游戏开发入门的第2篇,如果有不完善的地方请多多指导. */框架示意图如下,源代码参看:github:
-
客户端连接进来,由acceptor负责接入验证,创立channel后再转发给从线程池(workerReactor);
package com.server.java.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer implements Runnable { private int port; public NettyServer(int port) { super(); this.port = port; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } @Override public void run() { try {// netty支持3种reactor,netty推荐主从(boss和worker) EventLoopGroup bossGroup = new NioEventLoopGroup(4);// acceptor,负责tcp接入,接入认证,创立socketChannel等; EventLoopGroup workerGroup = new NioEventLoopGroup();// netty默认设置:Runtime.getRuntime().availableProcessors() // 负责io的读写和编码 try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new NettyServerInitializer()); ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed. // In this example, this does not happen, but you can do that to // gracefully // shut down your server. f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
worker Reactor 按照指定协议解码,构件出msgEntity对象,触发对应事件,在根据不同命令码(cmdCode)传递给对应的保存队列(blockqueue)
package com.server.java.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import java.nio.ByteOrder; import com.server.java.entity.MsgEntity; /** * 定长解码器 * */ public class NettyMsgDecoder extends LengthFieldBasedFrameDecoder { /** * @param byteOrder * @param maxFrameLength * 字节最大长度,大于此长度则抛出异常 * @param lengthFieldOffset * 开始计算长度位置,这里使用0代表放置到最开始 * @param lengthFieldLength * 描述长度所用字节数 * @param lengthAdjustment * 长度补偿,这里由于命令码使用2个字节.需要将原来长度计算加2 * @param initialBytesToStrip * 开始计算长度需要跳过的字节数 * @param failFast */ public NettyMsgDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) { super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast); } public NettyMsgDecoder() { this(ByteOrder.BIG_ENDIAN, 100000, 0, 4, 2, 4, true); } /** * 根据构造方法自动处理粘包,半包.然后调用此decode * */ @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { ByteBuf frame = (ByteBuf) super.decode(ctx, byteBuf); if (frame == null) { return null; } short cmd = frame.readShort();// 先读取两个字节命令码 byte[] data = new byte[frame.readableBytes()];// 其它数据为实际数据 frame.readBytes(data); MsgEntity msgVO = new MsgEntity(); msgVO.setCmdCode(cmd); msgVO.setData(data); return msgVO; } }
package com.server.java.netty; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import com.server.java.entity.MsgEntity; import com.server.java.queue.CommonQueue; import com.server.java.queue.LoginQueue; @Sharable public class ServerHanlder extends SimpleChannelInboundHandler<MsgEntity> { @Override protected void messageReceived(ChannelHandlerContext ctx, MsgEntity msg) throws Exception { if (msg == null) { return; } msg.setChannel(ctx.channel()); // int playerid = ServerCache.get(ctx.channel()); int csCommondCode = msg.getCmdCode(); if (csCommondCode < 100) {// 100以内暂时不用 } else if (csCommondCode >= 100 && csCommondCode < 200) { // 100-200用于注册 LoginQueue.getInstance().put(msg); } else {// 消息可能较多,可以分几个队列,这里先放一个 CommonQueue.getInstance().put(msg); } } }
-
每个blockQueue负责执行的线程异步取出msgEntity,通过cmdCode获得解码protobuf的GeneralMessage,解码出请求数据;
package com.server.java.handler; import java.util.List; import com.google.protobuf.InvalidProtocolBufferException; import com.server.java.CmdHandler; import com.server.java.cache.ServerCache; import com.server.java.constants.CmdConstant; import com.server.java.entity.MsgEntity; import com.server.java.entity.PlayerEntity; import com.server.proto.demo.DemoProto.NameCheckReq; import com.server.proto.demo.DemoProto.NameCheckResp; import com.server.proto.demo.DemoProto.SayHelloReq; import com.server.proto.demo.DemoProto.SayHelloResp; public class DemoHanlder extends CmdHandler { @Override public void handleMsg(MsgEntity msgEntity, List<MsgEntity> commandList) { switch (msgEntity.getCmdCode()) {// 根据命令码对应找到对应处理方法 case CmdConstant.NAME_CHECK: handleNameCheck(msgEntity, commandList); break; case CmdConstant.SAY_HELLO: handleSayHello(msgEntity, commandList); break; default: System.out.println("找不到对应的命令码"); } } private void handleNameCheck(MsgEntity msgEntity, List<MsgEntity> commandList) { // com.server.proto.demo. NameCheckReq req = null; try {// 按照与客户端约定,指定命令码使用指定的解码class解码 // 这里可以通过反射做成自动解码,参考:http://vincepeng.iteye.com/blog/2171310 req = NameCheckReq.parseFrom(msgEntity.getData()); } catch (InvalidProtocolBufferException e) { System.out.println("protobuf解码错误"); e.printStackTrace(); return; } String name = req.getName(); if (name == null || name.isEmpty()) { return; } boolean isExist = ServerCache.CheckName(name); if (!isExist) {// 如果没有存在/则模拟注册 ServerCache.addNewPlayer(name, msgEntity.getChannel());// 由于是单线程操作,无需加锁.参考:实战1的第2条 } NameCheckResp.Builder resp = NameCheckResp.newBuilder(); resp.setIsExist(isExist); msgEntity.setData(resp.build().toByteArray());// 将原来的消息内容替换为回包,命令码无需变化 commandList.add(msgEntity);// 加入到发送数组 if (!isExist) { SayHelloResp helloResp = SayHelloResp.newBuilder().setContent("欢迎" + name + "的到来").setSpeaker("系统").build(); MsgEntity helloMsg = new MsgEntity(); helloMsg.setCmdCode(CmdConstant.SAY_HELLO); helloMsg.setData(helloResp.toByteArray()); ServerCache.sendToAll(helloMsg);// 此操作开销较大,一般不要如此或者分离到其它服务器 } } private void handleSayHello(MsgEntity msgEntity, List<MsgEntity> commandList) { SayHelloReq req = null; try { req = SayHelloReq.parseFrom(msgEntity.getData()); } catch (InvalidProtocolBufferException e) { System.err.println("protobuf解码错误"); e.printStackTrace(); return; } // 关键词过滤 // 发言频率检测 int playerId = ServerCache.get(msgEntity.getChannel()); PlayerEntity pe = ServerCache.getPlayerById(playerId); if (pe != null) { SayHelloResp resp = SayHelloResp.newBuilder().setContent(req.getContent()).setSpeaker(pe.getName()).build(); msgEntity.setData(resp.toByteArray()); ServerCache.sendToAll(msgEntity); } else { System.err.println("玩家不存在"); } } }
- 业务逻辑处理后,按照相反的路径,先构件protobuf的消息对象,在编码成二进制,计算出长度,加上命令码分别写入byteBuf中,传递给客户端.完成一个业务的处理操作.
实际业务中添加了spring以及mybatis持久化,redis来处理缓存,这里暂时略去,可以根据自身需求慢慢添加;
protobuf配置参看链接
解码操作可以设计为自动完成,参看protobuf解码
相关推荐
在《netty+protobuf 整合实战》中,作者通过实际的源代码展示了如何将这两个技术结合使用。首先,我们需要理解 Protobuf 的工作原理。 Protobuf 提供了语言无关的 .proto 文件来定义数据结构,然后通过 protoc ...
1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...
在"Netty+Protobuf入门案例"中,我们可以学习到如何结合这两个强大的工具进行高效的数据交换。首先,理解 Protobuf 的基本概念至关重要。 Protobuf 提供了一种定义数据结构的语言,通过编译器将这些结构转换为各种...
基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...
基于Netty+Redis+protobuf开发的即时通讯服务器 第一版是一个简版,只实现了很少的功能,现在已经迁移到 [v1.0](https://github.com/linyu19872008/cdeer-im/tree/v1.0) 第二版的目标有以下几点: 1 工程用...
总结来说,"通信与协议Netty+Protobuf-游戏设计与开发(1)配套代码"是一个关于如何利用Netty进行网络通信以及如何使用Protobuf进行数据序列化的实例。通过学习这个资源,开发者可以更好地理解和掌握这两种技术在游戏...
Netty4+ProtoBuf通信框架是一种高效的网络应用框架,它结合了Netty的高性能和Google的Protocol Buffers(ProtoBuf)的数据序列化能力,用于构建可伸缩、高并发的网络应用程序。在这个项目中,客户端和服务端之间的...
基于Netty+TCP+Protobuf实现的Android IM库,包含Protobuf序列化、TCP拆包与粘包、长连接握手认证、心跳机制、断线重连机制、消息重发机制、读写超时机制、离线消息、线程池等功能
在本文中,我们将深入探讨如何使用Netty和Protobuf来开发一个实时聊天室实例。Netty是一个高性能、异步事件驱动的网络应用框架,适用于Java平台,它简化了TCP、UDP和HTTP等协议的服务器和客户端应用开发。而Protobuf...
本项目"基于Netty+Redis+protobuf开发的即时通讯服务器"利用了三种技术:Netty作为网络通信库,Redis作为数据缓存与消息中间件,以及protobuf作为序列化协议,实现了高性能的实时通信解决方案。 首先,Netty是一个...
毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...
首先,Spring Boot 是一个基于 Spring 框架的轻量级开发工具,它简化了初始化和配置过程,使得开发者能够更快地启动和运行项目。通过集成 Spring Boot,我们可以利用其强大的依赖注入机制、自动配置特性以及丰富的...
Netty和Protobuf是两种在IT领域中广泛使用的开源技术,尤其在开发高效、高性能的网络应用程序时。本文将深入探讨这两个技术,并结合一个入门案例,帮助初学者理解如何将它们结合起来使用。 Netty是一个高性能、异步...
Netty则是一个高性能、异步的Java网络应用框架,常用于服务器端开发,提供了高度定制化的网络通信能力。在Unity中,由于Unity本身是用C#编写的,我们通常需要通过WebSocket、TCP/IP或其他方式与服务器进行通信。这时...
Netty是一个高性能、异步事件驱动的网络应用程序框架,适用于开发可维护的高性能协议服务器和客户端。Netty的核心优势在于其非阻塞I/O模型,能够处理大量并发连接,降低了系统资源的消耗。通过使用Channel、...
基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式...
Netty是一个开源的异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它提供了高度灵活的网络编程模型,支持多种传输协议,如TCP、UDP,以及HTTP、FTP等。Netty的优势在于其高效的...