java游戏架构那点事儿(二) 博客分类: java 游戏 游戏框架netty
程序员文章站
2024-03-06 19:06:50
...
从本节开始,我将逐步对架构的各个环节进行展开讲解。这一节主要讲消息的接收与发送 。
在说具体的消息接收发送前,先说明一下netty的工作流程(请不熟悉netty的同学参考使用netty+spring搭建游戏框架)。
Netty是一个基于事件的NIO框架。在Netty中,一切网络动作都是通过事件来传播并处理的。Netty的流处理模型:
而本节要讲的就是如何将收到的客户端消息交给线程池处理的细节。
我们收到客户端发送的消息,一般以二进制流的格式传输(当然也可以是字符串或者其他格式),而服务器端接收到该消息后,需要经过消息验证、消息封装、消息分发等过程。
1、消息验证
这里的验证主要是验证消息的一般合法性,例如长度、标识头规范、加密算法等等。如果此处验证未通过,就不需要后续的处理环节了。需要注意的一点是,如果采用长连接方式,不能单纯的判断消息长度不足就将消息直接丢弃,而应该将消息挂起直到再次接收到消息后继续做验证。当然,netty也提供了相应的decoder方法来解决分包、组包、粘包的问题,例如LengthFieldBasedFrameDecoder。
2、消息封装
如果验证消息是合法的,我们将消息体进行格式转换为我们自定义的GameRequest,我们称之为消息封装。
一般来讲,GameRequest中至少要包含netty的渠道信息(channel)、协议体标识(commandId)、协议体内容(commandData)三部分。当然也可以将消息验证中的加密算法部分放到这部分进行验证。
为了方便消息扩展,我们在实际应用中设计的GameRequest采用的是多协议合并处理模式。即一个请求可包含多个协议,将这些协议按照顺序存放在一个有序集合中按顺序处理。
3、消息分发
将封装好的消息体添加到消息队列的过程。其实消息分发并没有什么特别之处,特别的是在如何保证消息处理的高效性和有序性上。从我了解到的市面上的消息分发模式主要有三种:按照玩家分发、按照模块分发、按照执行效率分发。
3-1、按照玩家分发
优点:每个玩家一个独立的消息队列,玩家间不会互相影响,能有效保证独立玩家的消息处理顺序
缺点:如果采用关系型数据库,处理过程中容易出现锁竞争情况。所以这种方式更适合非关系型数据库模型的架构
3-2、按照模块分发
优点:每个模块一个独立的消息队列,模块间不会相互影响;大大降低锁竞争情况,提升关系型数据的处理效率
缺点:无法有效保证玩家消息的处理顺序;由于不同模块的执行效率不同,可能会出现因某一模块执行效率过低或死锁而影响所有玩家的情况
3-3、按照执行效率分发
其实是按照模块分发的一个变种,也就是将执行效率较低的模块单独提出来组成一个独立的处理器,这样执行效率低的模块对游戏的影响就会降低一些。
在实际的生产环境中,我们采用的是按照玩家分发的模式,即每个玩家有一个自己的消息队列。而在使用这种模式时,一开始我们为每个玩家起了一个线程,后来发现这种做法太浪费资源;后期采取了一种线程池轮询策略,合理节省了资源,提升了执行效率。有关细节将在线程池相关章节进行阐述。
当消息进入到消息队列后,netty就完成了他的历史使命。而消息的发送工作主要是依靠netty提供的channel。
4、消息发送
消息的发送可能有两种情况:
1、消息处理完成后发送GameResponse
2、消息处理过程中遇到异常,推送提示给客户端
对于第一种情况,无论是长连接还是短连接,处理方法都是一样的,即执行完handler.excute()方法返回GameResponse,并将GameResponse内容推送给客户端即可。对于第二种情况,如果采用的是长连接模式可能会遇到。此时只需要调用netty提供的channel,直接write相应的信息,然后关闭channel就可以了。
至此,消息的接收与发送模块就完成啦!后面的内容稍后奉上!
在说具体的消息接收发送前,先说明一下netty的工作流程(请不熟悉netty的同学参考使用netty+spring搭建游戏框架)。
Netty是一个基于事件的NIO框架。在Netty中,一切网络动作都是通过事件来传播并处理的。Netty的流处理模型:
启动Boss线程---接收到客户端连接---生成Channel---交给Work线程池处理
而本节要讲的就是如何将收到的客户端消息交给线程池处理的细节。
我们收到客户端发送的消息,一般以二进制流的格式传输(当然也可以是字符串或者其他格式),而服务器端接收到该消息后,需要经过消息验证、消息封装、消息分发等过程。
1、消息验证
这里的验证主要是验证消息的一般合法性,例如长度、标识头规范、加密算法等等。如果此处验证未通过,就不需要后续的处理环节了。需要注意的一点是,如果采用长连接方式,不能单纯的判断消息长度不足就将消息直接丢弃,而应该将消息挂起直到再次接收到消息后继续做验证。当然,netty也提供了相应的decoder方法来解决分包、组包、粘包的问题,例如LengthFieldBasedFrameDecoder。
2、消息封装
如果验证消息是合法的,我们将消息体进行格式转换为我们自定义的GameRequest,我们称之为消息封装。
一般来讲,GameRequest中至少要包含netty的渠道信息(channel)、协议体标识(commandId)、协议体内容(commandData)三部分。当然也可以将消息验证中的加密算法部分放到这部分进行验证。
为了方便消息扩展,我们在实际应用中设计的GameRequest采用的是多协议合并处理模式。即一个请求可包含多个协议,将这些协议按照顺序存放在一个有序集合中按顺序处理。
3、消息分发
将封装好的消息体添加到消息队列的过程。其实消息分发并没有什么特别之处,特别的是在如何保证消息处理的高效性和有序性上。从我了解到的市面上的消息分发模式主要有三种:按照玩家分发、按照模块分发、按照执行效率分发。
3-1、按照玩家分发
优点:每个玩家一个独立的消息队列,玩家间不会互相影响,能有效保证独立玩家的消息处理顺序
缺点:如果采用关系型数据库,处理过程中容易出现锁竞争情况。所以这种方式更适合非关系型数据库模型的架构
3-2、按照模块分发
优点:每个模块一个独立的消息队列,模块间不会相互影响;大大降低锁竞争情况,提升关系型数据的处理效率
缺点:无法有效保证玩家消息的处理顺序;由于不同模块的执行效率不同,可能会出现因某一模块执行效率过低或死锁而影响所有玩家的情况
3-3、按照执行效率分发
其实是按照模块分发的一个变种,也就是将执行效率较低的模块单独提出来组成一个独立的处理器,这样执行效率低的模块对游戏的影响就会降低一些。
在实际的生产环境中,我们采用的是按照玩家分发的模式,即每个玩家有一个自己的消息队列。而在使用这种模式时,一开始我们为每个玩家起了一个线程,后来发现这种做法太浪费资源;后期采取了一种线程池轮询策略,合理节省了资源,提升了执行效率。有关细节将在线程池相关章节进行阐述。
当消息进入到消息队列后,netty就完成了他的历史使命。而消息的发送工作主要是依靠netty提供的channel。
4、消息发送
消息的发送可能有两种情况:
1、消息处理完成后发送GameResponse
2、消息处理过程中遇到异常,推送提示给客户端
对于第一种情况,无论是长连接还是短连接,处理方法都是一样的,即执行完handler.excute()方法返回GameResponse,并将GameResponse内容推送给客户端即可。对于第二种情况,如果采用的是长连接模式可能会遇到。此时只需要调用netty提供的channel,直接write相应的信息,然后关闭channel就可以了。
至此,消息的接收与发送模块就完成啦!后面的内容稍后奉上!