美文网首页java&spring社区技术分享
从破亿流水游戏中抽出的聊天室组件

从破亿流水游戏中抽出的聊天室组件

作者: Java面试官 | 来源:发表于2020-06-14 22:45 被阅读0次

    游戏已经上线了一年多了,现在正处于维护期,因此经常研究公司的底层架构和组件,研究透了后自己便会尝试自己写一个,而这个就是研究了相关组件后写的一个通讯组件啦,将通讯模块设计成组件的方案是支持导入到各个工程的,有需要可以自取。
    demo访问地址: http://chatroom.lixifan.cn/index.html
    源码地址: https://github.com/wiatingpub/chatRoom

    技术栈

    • springboot
    • netty,通讯模块使用的是netty,采用的是websocket通讯协议
    • docker,为了方便部署,这里采用的是docker的部署方式
    • nginx,反向代理
    • html&css&js,前段随便撸的一个东西,轻喷

    设计亮点

    • 通讯模块直接做成了组件,和实际工程分离,其他工程随导随用;
    • 使用了协议分发机制,接收到客户端协议后会根据协议id自动找到对应facade下的method,通过反射触发;
    • 新增了Api注解,工程启动时构建协议和对应method的映射,提供协议分发机制使用;
    • 心跳机制监听,客户端定时发送ping协议包,如果异常断开,服务端一定时间没有接受到ping包后会断开连接;

    界面表现

    大概说说界面表现的东西,随便写的html页面。


    1

    登陆界面,这里开了两个端,输入昵称即可登陆,之后开始聊天

    2

    下线后的表现如下

    3

    下线后便监听到啦

    ide如何启动

    • idea导入chatRoom工程,点击File->Project Structure->点击Modules内的+导入lib下的socket组件
    • 点击启动即可,监听的端口可以修改resources下的application.properties
    • 客户端相关的放在chatRoomWeb下,直接访问即可

    工程结构

    image-20200614114451391

    协议设计

    在proto协议包下可以看到有个抽象类AbstractMessage

    image-20200614115619725

    这就是协议的基类,要求所有协议都需要去继承它,并且传入对应的协议id,而协议id由于是和具体工程绑定的东西,因此是放在工程目录下,我这边是采用了一个枚举实现,可以看到

    image-20200614115820219

    协议分发机制的实现

    Api 这是一个注解,主要作用是用于标注那些method要用来和客户端交互。

    ApiInjectProcessor 协议分发机制的核心类,该核心类的主要作用是扫描工程中所有使用了Api注解的method,并将形式参数中的协议类取出组装成MessageBean后缓存起来,等后续分发器调用。

    具体流程如下,首先先将源码贴出来下

    image-20200614114650595

    可以看到该类继承了InstantiationAwareBeanPostProcessorAdapter,实际上InstantiationAwareBeanPostProcessorAdapter的作用在我旧的文章中有多处提及,在我们写组件的时候经常要用到的一个类,相当于在每个bean实例化过程中触发的一个埋点,可以看到我这边是实现了方法postProcessAfterInstantiation,该方法在bean实例化后被调用,调用的时候我这边会先去遍历类里边的方法,逐个判断是否实现了注解Api,如果有则遍历方法的形式参数,取出其中继承了AbstractMessage协议抽象类的参数,之后反射实例化该协议,并且和协议id、该方法所在的bean对象以及method自身组装成一个MessageBean后放入MessageDispatcher分发器中的id2MessageBeanMap中,提供后续调用。

    MessageDispatcher 协议分发器,该类的作用是将对应的协议包通过反射的形式分发到对应的facade和method下执行,具体可以看dispatch方法

    image-20200614121501536

    由于和客户端那边定下来的数据格式是以json形式进行的,因而我这边会先将拿到的数据转换成JSONObject的形式,目前使用的是fastjson,之后拿到里边字段为code的数据,通过该code从协议分发器中拿到对应的MessageBean并且通过反射的方式调用对应facade下的对应方法。

    socket组件相关实现

    直接开启动通讯组件的地方

    image-20200614105606801

    Component注解就不多做说明了,看看SmartInitializingSingleton接口吧,该接口在实现组件方面非常受用,当所有单例bean都初始化完成以后,spring容器会回调该接口的方法afterSingletonsInstantiated,通过这种方式启动了通讯组件。

    关于netty设置的参数直接看start接口的传参即可,各个参数都是从游戏中实际的情况出发设计的,有想了解的可以找我。

    接下来看看我这边放入ChannelPipeline的情况,可以看到

    image-20200614111516496

    HttpServerCodec 看源码不难发现,这是一个复杂类型的handler[CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>],通过其中的泛型也不难猜到这是一个对http协议包进行编码和解码的handler,而websocket本身就是一种建立在http协议上的一种机制,因此我们需要它;

    HttpObjectAggregator 由于http协议get和post方式数据的存放方式是不一样的,而HttpServerCodec只能读取get请求,HttpObjectAggregator才可以处理post请求,因此我们需要它;

    IdleStateHandler 主要用来处理心跳的一个机制,我这边的设计思路是服务器在60秒内内发现连接读空闲,则再移除对应的channel,当然了客户端要在60秒内定时推送ping数据过来,新手可能会问,是否只是new一个对象放入pipeline即可,no,too young too simple,这个handler主要作用是定时触发一个事件,而我们需要在其他handler内做监听,然后做我们的业务,这个后面再说;

    AuthHandler 核心handler,可以看到我这里将ApplicationEventPublisher当做形参传了进去,ApplicationEventPublisher的主要作用了实现事件机制,由于AuthHandler 的实现有点意思,因此我这边将源码贴出详细说说

    image-20200614111814629

    首先是继承了SimpleChannelInboundHandler接口,通过Inbound命名以及FullHttpRequest便可以知道该接口的作用是拦截客户端推送的http协议包,先看看channelRead0方法的实现,可以看到收到http协议请求后会调用ChannelManager.addChannel方法,该方法的实现如下

    image-20200614112619190

    很明显,只是构建一个ChannelSession后放入sessionMap中保存,而为了支持并发,该map自然而然应该是并发包ConcurrentHashMap的形式,而ChannelManager便是管理channel的管理器,移除和添加以及激活都在该manager内进行。

    现在看看具体方法userEventTriggered,上文提到的IdleStateHandler 便是在这里发挥了作用,在上文提到的60秒内如果对应客户端没有发数据包过来,则会调用ChannelManager.removeChannel方法,将该channel移除,并且抛出了一个LogoutEvent事件,我们可以反调该事件可以看到

    image-20200614113006440

    有facade监听了该事件,并且在监听到后广播了一个协议出去,在上面界面的表现中也可以看到,当玩家离线后会有通知xxx已经下线,便是通过该种方式进行的。

    WebSocketServerProtocolHandler 协议升级使用,也就是说将http协议升级成websocket协议,当升级成功后HttpServerCodec和HttpObjectAggregator都会被移除掉,继而替换成websocket相关的handler,因此我们需要它。

    MessageHandler 核心handler,可以看到我这里又将ApplicationEventPublisher当做形参传了进去,

    image-20200614113629192

    我们可以看到该handler是处理websocket协议包的,而最重要的方法是

    image-20200614121312423

    该方法的作用便是通过协议包内的websocket数据调用协议分发器,让协议分发器通过对应的协议id激发对应的业务。

    工程启动

    直接启动ChatRoomApplication,该类配置了扫描com.nuofankj包下的类,启动后可以看到

    image-20200614121853381

    对应协议已经录入协议分发器中啦,到此就可以通过web页面进行正常通讯了,至于web页面直接将chatRoomWeb包下的代码放到nginx或者apache下,然后访问即可。

    image-20200614150536709

    相关文章

      网友评论

        本文标题:从破亿流水游戏中抽出的聊天室组件

        本文链接:https://www.haomeiwen.com/subject/uioxaqtx.html