【7】万魂杀服务器开发方面之老版GM工具

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

老版GM是使用java的jdesktop组建来进行界面开发,在本文中将重点放在逻辑上,对于界面开发不做详述。

    老版GM和游戏服务器公用一套底层Socket框架(即原生NIO),关于网络框架模块这里也不做详述。具体的资料会在游戏服务器相关章节进行讲解。

    入口类GmChatApp

 1. 为方便讲解流程,这里GM发出的消息以“发送邮件为例”

    发送邮件过程中,当gm点解发送之后,函数会响应到FunctionPanel.java中的SendMail函数,该函数中进行了字符处理后转交到真正的sendMail逻辑。

    

    真正发送gm指令的类是GameServerInstance单例类。其中各个方法基本上按照如下模板进行。生成一个CmdPkg 填入消息号、收信人、发送者、标题、内容、附件,最后调用网络接口发送出去。伪代码如下:

            CmdPkg cmd = new CmdPkg(Const.C_GMTOOL_SEND_MAIL_BY_NAME);
            cmd.writeUTF(s);//收信人呢称
            cmd.writeLong(0);//发送者
            cmd.writeUTF(title);
            cmd.writeUTF(content);
            cmd.writeUTF(attachment);
            commProcessor.send(cmd);

    自此发送gm指令模块完成,这里有几点需要说明一下:

        CmdPkg简要分析

/**
 * 指令数据结构<br />
 * Server/Client之间以Cmd方式交互<br />
 * 指令 = 压缩标记(byte) + 指令类型(commandId,必须) + 参数(params,可选)<br />
 *
 * @see 《指令格式文档》
 *
 */

CmdPkg具有读和写两个成员变量,对于发送的CmdPkg而言时写生效 相反是读生效。同时为了方便统计游戏中的流量消耗,在CmdPkg中会进行流量统计(字节数统计).

CmdPkg中有2中构造方法

    /**
     * Create a new Cmd
     *
     * @param id
     *            Cmd id
     */
    public CmdPkg(short id) {
        data = new byte[64];

        writeShort(id);
        commandID = id;

    }

    /**
     * read pkg from net
     *
     * @param dataPkg
     */
    public CmdPkg(byte[] dataPkg) {
        data = dataPkg;
        commandID = readShort();
        writePos = dataPkg.length;
        String name = Integer.toHexString(commandID);
        if (name != null) {
            log = new StringBuilder(name).append(DELIMITER);
        }
    }

CmdPkg的操作是基于流的,因此对于顺序特别敏感。如果客户端依次写入了 int string bool 那么服务器必须按照这个流程进行读取

客户端
CmdPkg sendCmd = new CmdPkg(Const.C_S_LOGIN);
sendCmd.writeUTF("userName");
sendCmd.writeUTF("password");
sendCmd.writeInt(1);//version
net.send(sendCmd);

服务器
CmdPkg recvCmd = Net.RecvCmdPkg(Const.C_S_LOGIN);
String userName = recvCmd.readUTF();
String password = recvCmd.readUTF();
int version = recvCmd.readInt();

注意写入和读取的顺序敏感

 2. Gm工具接受服务器的反馈数据

    当gm网络底层接受到数据后,会在tick中进行处理

    public void tick() {
        try {
            //从client中取数据并组数据包
            pkgProtocol.rcvAndDecode();
            //处理数据包
            pkgProcessor.process();
            //发数据
            pkgProtocol.sndAndEncode();
            long curMils = System.currentTimeMillis();
            if (curMils - lastSnd > Config.connTimeout || curMils - lastRcv > Config.connTimeout) {
                GmControl.appendConsoleInfo(LanguageToken.getString("netconn_time_out_tip", getClient().getRemoteIp(), getClient().getRemotePort()));
                close();
            }
        } catch (Exception e) {
            GmControl.appendConsoleError(LanguageToken.getString("netconn_tick_error_tip"), e);
            close();
        }
    }

最后真正的CmdPkg数据回被发送到GameServerInstance的ProcessCommand函数中。该函数中采取了switch case的方案来进行简单的处理

 public void processCommand(CmdPkg cmd) {
        switch (cmd.getCommandID()) {
            case Const.S_GMTOOL_TICK: {//心跳
                break;
            }
            case Const.S_GMTOOL_ONLINE_WORLD: {//地图信息
                int total = cmd.readInt();//总数
                int len = cmd.readInt();
                String[] mapId = new String[len];
                String[] mapName = new String[len];
                int[] mapPlayerCount = new int[len];
                int[] mapMonsterCount = new int[len];
                for (int i = 0; i < len; i++) {
                    mapId[i] = cmd.readUTF();
                    mapName[i] = cmd.readUTF();
                    mapPlayerCount[i] = cmd.readInt();
                    mapMonsterCount[i] = cmd.readInt();
                }
                int playerCount = cmd.readInt();
                int monsterCount = total - playerCount;
                onlineInfoBean.setTotal(total);
                onlineInfoBean.setPlayerCount(playerCount);
                onlineInfoBean.setMonsterCount(monsterCount);
                onlineInfoBean.setMapMonsterCount(mapMonsterCount);
                onlineInfoBean.setMapId(mapId);
                onlineInfoBean.setMapName(mapName);
                onlineInfoBean.setMapPlayerCount(mapPlayerCount);
                Statistics.getInstance().postData(Statistics.PLAYER_COUNT_TYPE, prototype.srvName, onlineInfoBean.getPlayerCount());
                onlineInfoBean.setUseable(true);
                CtrlFunctionLogicAndUI.getInstance().fireOnlineInfo(prototype.srvName, onlineInfoBean);
                break;
            }
}

在case语句中进行数据解析和转发到界面上。 当然这里的实现其实很死板或者说错误。更好的方法时将switch case改造 以实现更强的健壮性。

gm图中的所有数据基本上都来源于gameserver 其他消息逻辑过程类似,这里不做详细描述了

老版GM在以下地方值得借鉴。

1.玩家在线情况及分布,对于查找游戏中内存瓶颈有很好的用处(之前出现jvm内存不足,但没有排查出内存泄漏,最后通过玩家在线分布统计出,在玩家家园中内存分配特别巨大,进而发现家园副本为单人副本,同时很多建筑的功能是依赖于NPC 因此对于机器人4000同时在线,此时的内存相当夸张。后需策划优化家园副本后 4000机器人成功通过该瓶颈)。

2.服务器运行状态统计。可以统计出服务器运行时间jvm内存分配,游戏网络周期和世界周期,这些对于压力测试特别有用。根据各个参数的表现可以推测出当前系统的瓶颈定位。

3.数据库处理状态。可以实时展现数据库当前处理队列的大小.(玩家登录很慢。最后排查出来是因为数据线程太小。导致数据库请求在队列中没有及时处理,调大线程后,成功处理)

4.控制台&公告,对开发人员特别友好。gameserver开发一个trigger后,通过控制台就可进行触发。不需等待界面资源。

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看