`
朋在无锡
  • 浏览: 34837 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java游戏开发入门2_基于netty+protobuf的游戏框架

阅读更多
/**
刚开始学习游戏开发时想找一个基于netty的游戏demo十分困难,工作一段时间后了解框架后将其分享出来;
该框架是从别人框架移植修改完善而来,不是我一个人写,算是借花献佛;
实际业务开发比此框架要复杂得多,去繁从简主在体现核心思想;
这是游戏开发入门的第2篇,如果有不完善的地方请多多指导.
*/
 框架示意图如下,源代码参看:github:

  1.  客户端连接进来,由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();
    		}
    	}
    
    }
    
     
  2. 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);
    		}
    
    	}
    }
    
     
  3. 每个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("玩家不存在");
    		}
    
    	}
    }
    
     
  4. 业务逻辑处理后,按照相反的路径,先构件protobuf的消息对象,在编码成二进制,计算出长度,加上命令码分别写入byteBuf中,传递给客户端.完成一个业务的处理操作.

实际业务中添加了spring以及mybatis持久化,redis来处理缓存,这里暂时略去,可以根据自身需求慢慢添加;

protobuf配置参看链接

解码操作可以设计为自动完成,参看protobuf解码

 

 

2.
  • 大小: 53.6 KB
分享到:
评论
2 楼 zcqshine 2015-10-16  
protobuf 的编解码为什么没直接采用 netty 提供的ProtobufEncoder 和 ProtobufDecoder呢?
1 楼 liyuzhel 2015-08-17  
为什么需要把登陆队列与逻辑处理队列分开呢

相关推荐

    netty+protobuf (整合源代码)

    在《netty+protobuf 整合实战》中,作者通过实际的源代码展示了如何将这两个技术结合使用。首先,我们需要理解 Protobuf 的工作原理。 Protobuf 提供了语言无关的 .proto 文件来定义数据结构,然后通过 protoc ...

    基于netty+websocket+springboot的实时聊天系统项目源码.zip

    1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...

    netty+protobuf入门案例

    在"Netty+Protobuf入门案例"中,我们可以学习到如何结合这两个强大的工具进行高效的数据交换。首先,理解 Protobuf 的基本概念至关重要。 Protobuf 提供了一种定义数据结构的语言,通过编译器将这些结构转换为各种...

    基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究

    基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...

    基于Netty+Redis+protobuf开发的即时通讯服务器

    基于Netty+Redis+protobuf开发的即时通讯服务器 第一版是一个简版,只实现了很少的功能,现在已经迁移到 [v1.0](https://github.com/linyu19872008/cdeer-im/tree/v1.0) 第二版的目标有以下几点: 1 工程用...

    通信与协议Netty+Protobuf-游戏设计与开发(1)配套代码

    总结来说,"通信与协议Netty+Protobuf-游戏设计与开发(1)配套代码"是一个关于如何利用Netty进行网络通信以及如何使用Protobuf进行数据序列化的实例。通过学习这个资源,开发者可以更好地理解和掌握这两种技术在游戏...

    Netty4+ProtoBuf通信框架

    Netty4+ProtoBuf通信框架是一种高效的网络应用框架,它结合了Netty的高性能和Google的Protocol Buffers(ProtoBuf)的数据序列化能力,用于构建可伸缩、高并发的网络应用程序。在这个项目中,客户端和服务端之间的...

    基于Netty+TCP+Protobuf实现的Android IM库

    基于Netty+TCP+Protobuf实现的Android IM库,包含Protobuf序列化、TCP拆包与粘包、长连接握手认证、心跳机制、断线重连机制、消息重发机制、读写超时机制、离线消息、线程池等功能

    netty+protobuf开发一个聊天室实例

    在本文中,我们将深入探讨如何使用Netty和Protobuf来开发一个实时聊天室实例。Netty是一个高性能、异步事件驱动的网络应用框架,适用于Java平台,它简化了TCP、UDP和HTTP等协议的服务器和客户端应用开发。而Protobuf...

    基于Netty+Redis+protobuf开发的即时通讯服务器.zip

    本项目"基于Netty+Redis+protobuf开发的即时通讯服务器"利用了三种技术:Netty作为网络通信库,Redis作为数据缓存与消息中间件,以及protobuf作为序列化协议,实现了高性能的实时通信解决方案。 首先,Netty是一个...

    毕设项目:基于netty+websocket+springboot的实时聊天系统.zip

    毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...

    springboot集成netty,使用protobuf作为数据交换格式,可以用于智能终端云端服务脚手架

    首先,Spring Boot 是一个基于 Spring 框架的轻量级开发工具,它简化了初始化和配置过程,使得开发者能够更快地启动和运行项目。通过集成 Spring Boot,我们可以利用其强大的依赖注入机制、自动配置特性以及丰富的...

    netty+protobuf入门案例.

    Netty和Protobuf是两种在IT领域中广泛使用的开源技术,尤其在开发高效、高性能的网络应用程序时。本文将深入探讨这两个技术,并结合一个入门案例,帮助初学者理解如何将它们结合起来使用。 Netty是一个高性能、异步...

    Unity与Netty进行ProtoBuf通信__

    Netty则是一个高性能、异步的Java网络应用框架,常用于服务器端开发,提供了高度定制化的网络通信能力。在Unity中,由于Unity本身是用C#编写的,我们通常需要通过WebSocket、TCP/IP或其他方式与服务器进行通信。这时...

    cdeer-im, 基于Netty+Redis+protobuf开发的即时通讯服务器.zip

    Netty是一个高性能、异步事件驱动的网络应用程序框架,适用于开发可维护的高性能协议服务器和客户端。Netty的核心优势在于其非阻塞I/O模型,能够处理大量并发连接,降低了系统资源的消耗。通过使用Channel、...

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式即时聊天系统.zip基于springcloud+Netty+MQ+mysql的分布式...

    采用netty与protobuf进行文件传输

    Netty是一个开源的异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它提供了高度灵活的网络编程模型,支持多种传输协议,如TCP、UDP,以及HTTP、FTP等。Netty的优势在于其高效的...

Global site tag (gtag.js) - Google Analytics