/** 刚开始学习游戏开发时想找一个基于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 的 protoc 工具, protoc-2.6.1-win32.zip
通过学习netty + protobuf 开发小项聊天程序的例子,可以掌握 netty 的开发 和 协议栈的设计等
通信与协议Netty+Protobuf-游戏设计与开发(1)配套代码
1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...
cdeer-im, 基于Netty+Redis+protobuf开发的即时通讯服务器
基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...
基于Netty+TCP+Protobuf实现的Android IM库,包含Protobuf序列化、TCP拆包与粘包、长连接握手认证、心跳机制、断线重连机制、消息重发机制、读写超时机制、离线消息、线程池等功能
毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...
绍如何搭建一个准实时聊天问答程序,包括微信小程序和H5网页版。...该项目服务端主要使用了Java + Spring Boot + Netty + WebSocket等技术栈,聊天客户端使用的是UniApp来轻松搭建微信小程序和H5网页端。
从网友那里拿到的共享代码,也没怎么细看,希望可以帮助到初学的朋友们。
netty服务器源代码,通信协议使用protobuf
客户端与服务端通信,协议用protoBuf。maven项目,其中有startClient与startServer两个mainClass。不懂的可以留言
使用java语言编写的,基于netty和protobuf的聊天系统,有客户端和服务器。
springboot集成netty,使用protobuf作为数据交换格式,可以用于智能终端云端服务脚手架。
iot_push基于netty + mqtt 3.1.1协议开发的物联网消息推送框架(此项目不维护了)请参考新项目( )项目目录更新日志基于netty4.1-final + springboot实现的Mqtt 3.1.1物联网标准推送协议MQTT协议是IBM开发的即时...
java netty 服务端 + unity客户端 +protobuf3 实现网游通讯demo
springboot+netty+websocket+redis 分布式聊天,实现简单的聊天功能