美文网首页Java游戏服务器编程
Java游戏跨服实现(Netty)

Java游戏跨服实现(Netty)

作者: 小圣996 | 来源:发表于2020-02-19 23:04 被阅读0次

    真正的大师,永远都怀着一颗学徒的心。--剑圣

    在前面的《Java游戏跨服实现(Hessian+Jetty)》一文中,讲述了用Hessian+Jetty方式实现跨服,其中也讲到了这是基于Http协议的,大家知道,Http协议是一种短连接,无状态的协议,每次请求都会有3次握手,因为无状态,每次包头会带上重复信息,显得冗余占用带宽且影响效率。但是,这在卡牌类的游戏,即对延迟要求不高的场景中它是行得通的,且好用又方便集成到java项目中。

    如果游戏需要使用长连接且实时性高,那么TCP协议将有很大可能纳入考虑之中,本文即以Netty+Protobuf实现游戏跨服TCP通信。在之前的另一篇文章中《使用Netty+Protobuf实现游戏TCP通信》仅讲述了客户端到服务端的通信,即客户端A使用Netty客户端启动类,服务端B使用Netty服务端启动类。但是在游戏跨服中,它是如下的一种通信情形:

    客户端A -> 游戏服B -> 跨服C
    

    另:游戏服架构方案可参考文章《游戏架构方案

    这三个服都以Netty作为网络框架,A为Netty客户端,C为Netty服务端,但是B既要作为A的服务端,又需要是C的客户端,因此B中既需要Netty的服务端启动类,也需要Netty的客户端启动类。

    据说最佳实践是在服务器B的handler中的channelactive方法中,创建一个客户端的引导类,该引导类复用服务器B的worker eventLoop,然后连接C服就差不多了。今天在家办公没什么事,于是自己动手实现了下。

    源码用的是《使用Netty+Protobuf实现游戏TCP通信》中客户端和服务端源码,把服务端项目再复制一个出来,作为中间服(游戏服B),然后在其ServerHandler中,添加如下代码,再做些协议测试,发现是可行的。

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            super.channelActive(ctx);
            
            System.out.println("【B服】 A->B客户端channel:"+ctx.channel().id().asLongText()+"激活!");
            aTobClientsMap.put(ctx.channel(), ctx.channel());
            
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(ctx.channel().eventLoop()) //这里不能用new NioEventLoopGroup(1),否则会多很多线程出来,就不是eventLoop复用了
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();//为每个连接新建ChannelPipeline
                        pipeline.addLast("decoder", new ProtoDecoder(5120));
                        pipeline.addLast("encoder", new ProtoEncoder(2048));
                        pipeline.addLast("serverHandler", new MidClientHandler());
                    }
            });
            
            bootstrap.connect(new InetSocketAddress("192.168.1.2", 38997))
                .addListener(new ChannelFutureListener(){
                    @Override
                    public void operationComplete(ChannelFuture future)
                            throws Exception {
                        aTobClientsMap.put(ctx.channel(), future.channel());
                        System.out.println("【B服】 A->B客户端channel:" + ctx.channel().id().asLongText() 
                                + ",【B服】 B->C客户端channel:"+future.channel().id().asLongText());
                    }
                    
                });
        }
    

    每个客户端激活时都会进来这里,这里执行时还处于worker线程组的IO线程中,利用每个IO线程所在的EventLoop与跨服C新建了一个连接通道ChanneBC,因此每个客户端A除了和游戏服B之间维持了一个ChannelAB之外,还和跨服C维持了另一个ChanneBC,这个ChanneBC和ChannelAB是一一对应的,它们与跨服C的通信便在ChanneBC中进行的。因此,这些ChannelBC通道也需在游戏服B上维护起来。这里的每个IO线程请求与跨服C连接时,在跨服C上,仍然会有一个boss线程组去处理这些连接请求,连接成功后,便会分配跨服C上的worker线程组管理这些新建的Channel。

    注意,使用bootstrap.connect(new InetSocketAddress("192.168.1.2", 38997))时,不能像客户端那样使用bootstrap.connect(new InetSocketAddress("192.168.1.2", 38997)).sync();否则容易发生死锁报错:
    io.netty.util.concurrent.BlockingOperationException
    请见《分析 Netty 死锁异常 BlockingOperationException》分析。
    原因及建议如下:


    其余的测试协议的过程略,感兴趣的请见源码。

    最终A服请求协议及输出为:

    A服协议及请求B服数据.png

    B服协议及输出为(AB服需共有一份协议文件AProto.proto):

    B服转发A服请求数据1001给C服,并将C服返回的数据3001给A服

    C服协议及输出为(BC服需共有一份协议文件BProto.proto):

    C服收到B服转发的数据,并将数据3001再让B服转发给A服

    最终的线程关系为:

    最终线程显示.png

    由此采用Netty+Protobuf实现了跨服游戏TCP通信。
    其中
    跨服C项目为NettyProtobufTcpServer(先启);
    游戏服B项目为NettyProtobufMidTcpServer(次启);
    客户端A项目为NettyProtobufTcpClient(最后启)。
    因为代码中在A服Channel激活后,便会发协议请求B服数据,因此项目的启动顺序如上,如果要下载源码运行的话。

    源码地址:
    https://github.com/zhou-hj/NettyMidServer.git

    相关文章

      网友评论

        本文标题:Java游戏跨服实现(Netty)

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