服务器#
我们先从最简单的服务器端开始研究,其定义于game/server.js
。其整体上为一个WebSocket服务器,在运行上其通过http协议进行握手连接,并通过tcp进行数据传输。
服务端主要监听三个事件,分别是连接connection、收到数据message和关闭连接close,后两个事件定义在连接事件回调函数的内部,这并不影响什么,但可以减少资源的消耗。
连接成功以后,首先其会bannedIps(保存被禁止的IP)中进行查找,若连接的IP在其中,则直接关闭连接,并禁止信息。
接着随机生成一个id用于表示此连接,并以键值对的方式存储在clients(保存连接到的客户端)中,并发送房间列表信息。
最后监听message事件和close事件。
在前两步中,服务端都会执行ws.sendl()
来发送反馈,其实际就是将参数格式化成一个json数组,并通过socket将数据发送会客户端,两者有着对应关系,对此我们需要考察mode/connect.js
文件
connect#
其内部大部分为创建控件的代码,包括连接界面和联机大厅界面,连接的代码转交给game.connect()
来执行,参数为ip地址和回调函数。连接的过程实际就是创建一个WebSocket对象,并保存在game.ws中,而相应的回调函数在lib.element.ws
中。
可以看到connect处的回调只是一些状态值的调整,而在lib中我们主要关心onmessage。其先将收到的数据,解json化还原为数组,然后调用lib.message.client
中名字为第一个参数,参数为后面元素的函数。说得有些抽象了,举个例子,在服务端连接因ip禁止失败后,其会发送ws.sendl("denied", "banned");
,也就是说此时客户端会调用函数lib.message.client.denied(banned)
,此时其作用是提示”加入失败:房间拒绝你加入”。类似的,连接成功以后服务端会发送ws.sendl("roomlist", util.getroomlist(), util.checkevents(), util.getclientlist(ws), ws.wsid);
,即执行函数lib.message.client.roomlist(arg)
,此时的三个参数已经数据化。
roomlist的内容比较多,我们先继续考察服务端的内容。
onmessage#
断开onclose回调没啥好说的,就是先遍历房间看断开的是否为房主,是的话再找出房间中所有的其它用户,各自执行selfclose的函数,第二步则是将自己退出,并将自己从服务端的clients中删除,最后更新房间数据和客户端数据。
在message回调中,其接受三种类型的数据。
第一种是字符串“heartbeat”,其实际是用于确定是否在连接中用的。对于客户端其收到此消息则立刻转发回去,而服务端则将连接的客户端进行beat标记,如果一次发送后,在60秒内未接收到回应,则服务端认定已经断开并关闭连接。
第二种实际是一个代理转发的类型,
this.owner
表示自己是否属于某个房主,是的话则直接将数据转发到房主的lib.message.client.onmessage
函数中,也就是说当你处于某个房间时,一切数据处理均由房主完成,服务器只是充当中转站。第三种是在上述两种情况之外时,同样接收json类型的数组,执行的过程和服务器执行sendl类似,即第一个为函数名,后面的表示参数。
这样服务器的作用就挺明显了,当我们连接上服务器时,根据服务器传回的参数绘制房间样式;当我们不在某个房间时,可以向服务器传递messages中的指令来使服务器执行;当我们在某个房间中时,服务器做为代理把我们发送的信息转发给房主进行响应。
客户端指令#
接下来,我们来看一下服务器可以接受的指令函数。
create(key,nickname,avatar,config,mode)
表示创建一个房间。此处的key表示你的唯一联机id(运行时保存在game.onlineKey
中),没有时通过get.id()
生成,并且生成以后会存储在数据库中永久固定;nickname表示联机名;avatar表示联机头像,后面的参数都没有用。此时服务器会创建一个房间,owner指向房主的ws对象,key指向房主的唯一联机id,并将其添加到rooms(保存服务器中的房间)中,最后再令房主执行自己的createroom
函数。所以服务端实际只是保存了一个指向。
enter(key,nickname,avatar)
表示进入一个房间。参数含义完全同上,但key表示房主的唯一联机id。首先服务器会根据id找出这个房间,若不存在令客户端执行enterroomfailed
,否则将用户ws的room指向这个房间,表示其属于这个房间,然后一堆无所谓的判定,最后反正会执行最后一个else,此时将用户ws的owner指向房主,并让房主执行onconnection
的操作将此用户的wsid连接上房间。最后服务器更新房间数据。所以进入房间,相当于房主,在本地保存其在服务端生成的wsid。
changeAvatar
将客户端的名称和头像更新到服务器。
server
废弃函数。
key(id)
其用于将本地的唯一联机id,更新到服务端ws的onlineKey中,总之就是为了唯一标识能传递。
events
服务器约战相关,保存在服务器变量events中,用处不大,没必要关注。
config(config)
修改房间设置,接收的config来自房主的lib.configOL,其在服务器中对应房间room的config属性。
status(str)
更新连接用户的状态,和约战一样用处不大,虽然两者看起来都能传递信息,但通常都没有人看。
send(id,message)
向wsid为id的服务端用户直接发送message。其主要用于房主向房间成员发送数据。
close(id)
断开wsid为id的服务端用户。
服务端函数#
最后我们再看看服务端自己的一些函数吧。大部分都可以望文生义,所以我们重点关注几个常见的。
isBanned(str)
查看字符串str是否包含服务端禁用词(保存在bannedKeyWords中)。
sendl
见过多次的老朋友,即让客户端执行lib.message.client
下的指令。
getroomlist
获得房间列表,但它不是直接传送rooms,而是首先在room的_num中统计房间人数,然后把数组[房主,房主头像,房间配置,房间人数,房间key(即房主的唯一联机id)]放入返回的房间列表中。也就是说我们只能通过位置来定位房间数据了。
getclientlist
获得联机服务器的客户端列表,和上面一样是数组的形式[名称,头像,是否在房间,状态,wsid,唯一联机id]。
updaterooms
获取房间列表和客户端列表并向所有不在房间中的客户端发送updaterooms的指令。
updateclients
同上,只不过少更新了房间数据。
checkevents
、updateevents
约战相关,无所谓的东西。
总结:稍微总结一下,1.客户端自己有一个唯一联机id,我们记为onlineKey,2.客户端连上服务器会获得一个ws连接对象,并持有唯一连接id,我们记为wsid,3.房间的key简单来说就是房主的唯一连接id。