第八章:L2JMobius学习 – 游戏服务GameServer讲解

本章节我们来讲解GameServer服务,首先来查看它的文件结构

ai:游戏角色自动化处理,比如说,自动攻击。
cache:数据缓存,里面就一个HtmCache.java类,缓存HTML文件内容。
communitybbs:bbs的管理。
data:对游戏数据的管理,涉及文件和数据库的操作。
enums:枚举类型,都是与游戏数据相关的,例如职业枚举数据。
geoengine:地图数据管理,例如三维场景下高度数据的获取。
handler:处理器,例如使用技能的效果处理。
instancemanager:各种类实例化管理,比如游戏对象实例化。
model:游戏业务模型,比如各种游戏对象类的实现。
network:网络数据通信,继承commons\network下的接口或父类
script:游戏脚本的管理
scripting:游戏脚本的管理
taskmanager:定时任务的管理
ui:图形界面目录,我们一般不使用它。
util:工具栏,一些辅助性功能的实现。
GameServer.java:GameServer服务启动入口,我们之前就是其他的它。
LoginServerThread.java:与LoginServer保持通信的线程
Shutdown.java:关闭GameServer服务

接下来,我们还是先研究network网络数据通信部分。

clientpackets:玩家客户端发送过来的数据包(继承ClientPacket接口)。
serverpackets:发送给玩家客户端的数据包(继承ServerPacket,继承WritablePacket)。

ClientPackets.java:客户端数据包数据枚举(根据ID实例化clientpackets数据包)。
ExClientPackets.java:客户端数据包数据枚举(解决ID数值超出byte范围问题)
ServerPackets.java:服务器数据包枚举(用来标记serverpackets的ID)
SystemMessageId.java:发送给客户端的消息ID

PacketLogger.java:数据包日志类。
PacketHandler.java:数据包处理器,继承PacketHandlerInterface接口。

BlowFishKeygen.java:秘钥生成(加密和解密数据包)
Encryption.java:加密解密类,继承EncryptionInterface接口。
GameClient.java:玩家客户端类,继承NetClient类。

ClientString.java:一个自定义的注解
ConnectionState.java:玩家客户端连接状态枚举。

loginserverpackets:发送给LoginServer服务的数据包(不讲解)。

接下来,我们介绍GameServer与玩家客户端之间的通信。当我们启动GameServer服务的时候,他会启动一个NetServer服务类,这个类我们之前已经讲过了。它里面有一个ServerSocketChannel,用来监听本机的7777端口。当有玩家客户端连接到GameServer的时候,我们就创建一个GameClient类,它代表了玩家客户端,继承自NetClient类。这个类非常重要,它会持有加密和解密的Encryption类,当然我们依然可以通过他的sendPacket方法向玩家客户端发送数据包。NetServer收到客户端发送的数据包之后,就会调用PacketHandler的handle方法来进行处理,这个类继承自PacketHandlerInterface接口。处理的方式就是,读取数据包的第一个字节,它就代表了数据包的ID。每一个数据包都有一个唯一标识ID,根据这个ID我们就能与真正的“游戏业务数据包”对应上了。这个对应关系是在ClientPackets和ServerPackets两个枚举类里面实现的。

这里,我们简单列举几个clientpackets游戏业务数据包

ProtocolVersion.java:客户端请求密钥,ID为0x00
AuthLogin.java:登录游戏服务器,返回玩家的角色列表,ID为0x08
NewCharacter.java:进入创建角色界面,发送角色模板数据,ID为0x0E(14)
CharacterCreate.java:创建并保存新角色,ID为0x0B (11)
CharacterSelected.java:选择角色,ID为0x0D (13)
EnterWorld.java:进入游戏世界,ID为0x03
Logout.java:退出游戏,ID为0x09

我们再来看一些serverpackets游戏业务数据包吧。

KeyPacket.java:向客户端发送密钥,ID为0x00
CharSelectInfo.java:返回玩家的角色列表,ID为0x13(19)
---------------------------------------------------------------------------
CharTemplates.java:角色模板,用于新建角色,ID为0x17(23)
CharCreateOk.java:创建角色成功,ID为0x19(25)
CharCreateFail.java:创建角色失败,ID为0x1A(26)
---------------------------------------------------------------------------
CharSelected.java:返回选中的角色信息,准备进入游戏世界,ID为0x15(21)
UserInfo.java:进入游戏世界,发送角色主要信息,ID为0x04
LeaveWorld.java:退出游戏世界,ID为0x7E(126)

我们上面已经说明了,每一个数据包的第一个字节代表了该数据包的唯一标识ID(上展示的ID都是十六进制)。当然,还有很多很多的数据包。我们获取这个ID之后,就能知道他对应的是哪个“游戏业务数据包”。这个是根据ClientPackets.java,ExClientPackets.java和ServerPackets.java枚举类型来定义对应关系的。这里需要单独说明一下ClientPackets.java和ExClientPackets.java的关系,他们两个都是clientpackets游戏业务数据包的ID。只不过后者是为了解决ID数值超出Byte字节大小的问题。因为我们的clientpackets非常的多,以至于它必然会超出byte自己的大小范围。于是,我们就规定当ID = 0xD0的时候,我们就继续读取数据包中下一个ID的数值,使用它来继续确定是哪个clientpackets游戏业务数据包。那么这个第二个ID数值中个对应的数据包就由ExClientPackets.java枚举来确定了。

接下来,我们继续研究PacketHandler的handle方法,如何处理clientpackets游戏业务数据包。首先是读取数据包中的第一个字节数据

final int packetId;
packetId = packet.readByte();

根据ID找到枚举类型,也就是对应的clientpackets游戏业务数据包

final ClientPackets packetEnum = ClientPackets.PACKET_ARRAY[packetId];

然后就可以实例化了

final ClientPackets packetEnum = ClientPackets.PACKET_ARRAY[packetId];

虽然声明的是ClientPacket接口类型,但是实际上就是clientpackets游戏业务数据包。接下来,我们就使用线程池技术来执行ClientPacket里面的read和run方法。这两个方法,前者是进行Byte数组数据转化类属性变量的,后者则是执行具体的游戏业务代码。如果需要向客户端发送数据包的话,也是在这个run方法中执行的。

ThreadPool.execute(new ExecuteTask(client, packet, newPacket, packetId));

我们可以查看一下ExecuteTask任务内容

_newPacket.read(_packet);
_newPacket.run(_client);

就是依次执行了read方法和run方法。当ID值为0xD0的时候,我们就会实例化ExPacket这个数据包。它不是一个游戏业务数据包。我们查看它的read方法

final int exPacketId = packet.readShort() & 0xFFFF;
_packetEnum = ExClientPackets.PACKET_ARRAY[exPacketId];
_newPacket = _packetEnum.newPacket();
_newPacket.read(packet);

看到了吧,它实际是继续读取下一个ID数值,在根据这个ID数值去ExClientPackets.java枚举中找真正的游戏业务数据包。找到之后,就实例化newPacket,然后执行实例化后newPacket的read方法。然后在run方法中,也是同样执行实例化后newPacket的run方法。也就是说,我们对应游戏的处理,就重点查看clientpackets游戏业务数据包中的run方法就行了。如果需要向客户端返回serverpackets游戏业务数据包,也是在这里执行的。这里需要注意的是,serverpackets中的数据包是直接实例化的,而它的ID则是由ServerPackets.java枚举来提供的。这一点大家要明白。

接下来,我们就来根据数据包来大致介绍一下GameServer与玩家客户端的数据通信。首先,我们仍然是先对GameClient进行实例化,这个没有太多的业务代码。然后,玩家客户端会请求ProtocolVersion数据包,该数据包中包含了客户端的版本号,然后我们在run方法中向客户端返回KeyPacket数据包,这个数据包里面包含的就是加密和解密的秘钥。有了秘钥,客户端和服务器端才能进行数据通信。

client.setProtocolVersion(_version);
client.sendPacket(new KeyPacket(client.enableCrypt(), 1));

这个KeyPacket数据包还是比较简单的,首先是他的构造方法

public KeyPacket(byte[] key, int result)
{
    _key = key;
    _result = result;
}

就是将传递过来的数据,赋值给自己类的属性变量上面。接下来就是write方法。

ServerPackets.KEY_PACKET.writeId(this);
writeByte(_result); 
for (int i = 0; i < 8; i++)
{
    writeByte(_key[i]);
}
writeInt(Config.PACKET_ENCRYPTION);
writeInt(Config.SERVER_ID);
writeByte(1);

我们不用过多的理解返回客户端的Byte数据中的所有详细内容。因为这些数据是让客户端程序来解读的。我们看到的第一句代码,就是从ServerPackets.java枚举中获取ID。

接下来,客户端获取了秘钥之后,就会继续发送AuthLogin请求数据包,里面包含了会话SessionKey数据对象和账号信息。然后在run方法中,会与LoginServer进行通信,告诉有玩家登录游戏了。然后GameServer收到LoginServer的回复之后,会想客户端发送CharSelectInfo数据包。这个数据包就是从数据库中查询玩家的所有游戏角色,然后玩家选择其中一个角色,就可以进入游戏世界了。当然,由于我们是第一次运行程序,因此,我们是没有角色的。所以,我们需要创建游戏角色。因此,我们需要在游戏客户端里面点击“创建角色”的按钮,进入到创建角色的界面。此时,客户端会向服务端发送NewCharacter数据包,这个数据包会返回客户端CharTemplates数据包,这个数据包包含了游戏的基础职业信息(人类法师和战士,精灵法师和战士等等)。关于如何创建角色,我们下一个章节介绍。

本章节涉及的内容均已上传百度网盘:

https://pan.baidu.com/s/1XdlcCFPvXnzfwFoVK7Sn7Q?pwd=avd4

欢迎加企鹅交流裙:874700842(裙文件里面也可以下载所有内容)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咆哮的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值