要点:
查找客户端发送逻辑
- 定位创建按钮,并追踪其关联的函数:
hall.js
中的onCreateRoomClicked
。
btn_create_room
组件按钮 —— 对应的 JS 函数为 onCreateRoomClicked
onCreateRoomClicked:function(){
if(cc.vv.gameNetMgr.roomId != null){
cc.vv.alert.show("提示","房间已经创建!\n必须解散当前房间才能创建新的房间");
return;
}
console.log("onCreateRoomClicked");
this.createRoomWin.active = true;
},
查找 this.createRoomWin.active = true;
的用途 —— 该语句用于 UI 界面的显示控制,但其具体逻辑未在代码中实现处理。
其作用是激活创建房间的 UI 组件。
- 显示创建房间界面,并查找该界面对应的挂载脚本:
createRoom.js
。
CreateRoom.js的onBtnOK();
3创建房间列表,并配置房间参数。createRoom
函数将这些参数通过 http
请求发送至大厅服务器。
createRoom:function(){
var self = this;
var onCreate = function(ret){
if(ret.errcode !== 0){
cc.vv.wc.hide();
//console.log(ret.errmsg);
if(ret.errcode == 2222){
cc.vv.alert.show("提示","房卡不足,创建房间失败!");
}
else{
cc.vv.alert.show("提示","创建房间失败,错误码:" + ret.errcode);
}
}
else{
cc.vv.gameNetMgr.connectGameServer(ret);
}
};
var difen = 0;
for(var i = 0; i < self._difenxuanze.length; ++i){
if(self._difenxuanze[i].checked){
difen = i;
break;
}
}
var zimo = 0;
for(var i = 0; i < self._zimo.length; ++i){
if(self._zimo[i].checked){
zimo = i;
break;
}
}
var huansanzhang = self._wanfaxuanze[0].checked;
var jiangdui = self._wanfaxuanze[1].checked;
var menqing = self._wanfaxuanze[2].checked;
var tiandihu = self._wanfaxuanze[3].checked;
var type = 0;
for(var i = 0; i < self._leixingxuanze.length; ++i){
if(self._leixingxuanze[i].checked){
type = i;
break;
}
}
if(type == 0){
type = "xzdd";
}
else{
type = "xlch";
}
var zuidafanshu = 0;
for(var i = 0; i < self._zuidafanshu.length; ++i){
if(self._zuidafanshu[i].checked){
zuidafanshu = i;
break;
}
}
var jushuxuanze = 0;
for(var i = 0; i < self._jushuxuanze.length; ++i){
if(self._jushuxuanze[i].checked){
jushuxuanze = i;
break;
}
}
var dianganghua = 0;
for(var i = 0; i < self._dianganghua.length; ++i){
if(self._dianganghua[i].checked){
dianganghua = i;
break;
}
}
var conf = {
type:type,
difen:difen,
zimo:zimo,
jiangdui:jiangdui,
huansanzhang:huansanzhang,
zuidafanshu:zuidafanshu,
jushuxuanze:jushuxuanze,
dianganghua:dianganghua,
menqing:menqing,
tiandihu:tiandihu,
};
var data = {
account:cc.vv.userMgr.account,
sign:cc.vv.userMgr.sign,
conf:JSON.stringify(conf)
};
console.log(data);
cc.vv.wc.show("正在创建房间");
cc.vv.http.sendRequest("/create_private_room",data,onCreate);
}
将参数组合成一个完整的配置对象 conf
,并发送至服务器,请求创建房间。
cc.vv.http.sendRequest("/create_private_room", data, onCreate);
将配置转换为 JSON 字符串格式:conf: JSON.stringify(conf)
请求提交至:客户端 Client
——> 大厅服务器的 HTTP client_service
服务。
4:响应的地址为:/create_private_room
,数据内容为房间创建参数,返回结果通过 onCreate
函数进行回调处理。
服务器接收到 client
发送的 data
后,根据请求内容执行相应的逻辑处理操作。
查找服务器响应处理:
- hall_server:配置的
"/create_private_room"
路由;
- 从数据库中查询玩家数据:
db.get_user_data(account, function(data) {
- 处理步骤:
(1) 查询数据库,获取账号信息:db.js
中的get_user_data
函数。
gems:房卡数据
(2)检测玩家是否已经在一个房间游戏: get_room_id_of_user,只有没有在游戏才可以创建;
//验证玩家状态
db.get_room_id_of_user(userId,function(roomId){
if(roomId != null){
http.send(res,-1,"user is playing in room now.");
return;
}
//创建房间
(3)room_service.js 的createRoom:
1>找到最小的负载的 server ;
2> 获取用户的gems房卡的数目;
优化点:这里经过了2次查询房卡,可以进行系统优化;
然后执行room_service.js的createRoom函数:
//创建房间
room_service.createRoom(account,userId,conf,function(err,roomId){
然后消息又发送到http服
http.get(serverinfo.ip,serverinfo.httpPort,“/create_room”,reqdata,function(ret,data){
流程描述:通过最小负载的游戏服务器进行房间创建请求,并通过大厅服务器的 room_service
定位到相应的游戏服务器,由其 http_service
进行开房处理。
3>麻将服务器http_server, create_room URL响应;
4> roomMgr创建一个createRoom:
roomMgr.createRoom(userId,conf,gems,serverIp,config.CLIENT_PORT,function(errcode,roomId){
/游戏服开房逻辑
exports.createRoom = function(creator,roomConf,gems,ip,port,callback){
if(
roomConf.type == null
|| roomConf.difen == null
|| roomConf.zimo == null
|| roomConf.jiangdui == null
|| roomConf.huansanzhang == null
|| roomConf.zuidafanshu == null
|| roomConf.jushuxuanze == null
|| roomConf.dianganghua == null
|| roomConf.menqing == null
|| roomConf.tiandihu == null){
callback(1,null);
return;
}
if(roomConf.difen < 0 || roomConf.difen > DI_FEN.length){
callback(1,null);
return;
}
if(roomConf.zimo < 0 || roomConf.zimo > 2){
callback(1,null);
return;
}
if(roomConf.zuidafanshu < 0 || roomConf.zuidafanshu > MAX_FAN.length){
callback(1,null);
return;
}
if(roomConf.jushuxuanze < 0 || roomConf.jushuxuanze > JU_SHU.length){
callback(1,null);
return;
}
//需要消耗多少房卡
var cost = JU_SHU_COST[roomConf.jushuxuanze];
if(cost > gems){
callback(2222,null);
return;
}
var fnCreate = function(){
var roomId = generateRoomId();
if(rooms[roomId] != null || creatingRooms[roomId] != null){
fnCreate();
}
else{
creatingRooms[roomId] = true;
db.is_room_exist(roomId, function(ret) {
if(ret){
delete creatingRooms[roomId];
fnCreate();
}
else{
var createTime = Math.ceil(Date.now()/1000);
var roomInfo = {
uuid:"",
id:roomId,
numOfGames:0,
createTime:createTime,
nextButton:0,
seats:[],
conf:{
type:roomConf.type,
baseScore:DI_FEN[roomConf.difen],
zimo:roomConf.zimo,
jiangdui:roomConf.jiangdui,
hsz:roomConf.huansanzhang,
dianganghua:parseInt(roomConf.dianganghua),
menqing:roomConf.menqing,
tiandihu:roomConf.tiandihu,
maxFan:MAX_FAN[roomConf.zuidafanshu],
maxGames:JU_SHU[roomConf.jushuxuanze],
creator:creator,
}
};
if(roomConf.type == "xlch"){
roomInfo.gameMgr = require("./gamemgr_xlch");
}
else{
roomInfo.gameMgr = require("./gamemgr_xzdd");
}
console.log(roomInfo.conf);
for(var i = 0; i < 4; ++i){
roomInfo.seats.push({
userId:0,
score:0,
name:"",
ready:false,
seatIndex:i,
numZiMo:0,
numJiePao:0,
numDianPao:0,
numAnGang:0,
numMingGang:0,
numChaJiao:0,
});
}
//写入数据库
var conf = roomInfo.conf;
db.create_room(roomInfo.id,roomInfo.conf,ip,port,createTime,function(uuid){
delete creatingRooms[roomId];
if(uuid != null){
roomInfo.uuid = uuid;
console.log(uuid);
rooms[roomId] = roomInfo;
totalRooms++;
callback(0,roomId);
}
else{
callback(3,null);
}
});
}
});
}
}
fnCreate();
};
a: 检查并验证创建房间的参数;
支持两种麻将玩法:血战到底 和 血流成河;
b: 检查房卡余额:
var cost = JU_SHU_COST[roomConf.jushuxuanze];
c: 执行房间创建:
db.create_room(roomInfo.id, roomInfo.conf, ip, port, createTime, function(uuid) {
- URL 响应返回后进入
enterRoom
:
roommgr.js
中的 exports.createRoom
将通过 callback(0, roomId);
将 roomId
传递给 http_service
;
随后将 roomId
返回:
http.send(res, 0, "ok", { roomid: roomId });
console.log
进行日志打印操作;频繁的日志输出会占用服务器资源,可能导致服务器出现卡顿情况。建议使用异步日志系统,将日志处理独立出来,避免阻塞服务器的主进程,从而提升服务器性能。(4) room_service.js 的enterRoom:
exports.enterRoom = function(userId,name,roomId,fnCallback){
var reqdata = {
userid:userId,
name:name,
roomid:roomId
};
reqdata.sign = crypto.md5(userId + name + roomId + config.ROOM_PRI_KEY);
var checkRoomIsRuning = function(serverinfo,roomId,callback){
var sign = crypto.md5(roomId + config.ROOM_PRI_KEY);
http.get(serverinfo.ip,serverinfo.httpPort,"/is_room_runing",{roomid:roomId,sign:sign},function(ret,data){
if(ret){
if(data.errcode == 0 && data.runing == true){
callback(true);
}
else{
callback(false);
}
}
else{
callback(false);
}
});
}
var enterRoomReq = function(serverinfo){
http.get(serverinfo.ip,serverinfo.httpPort,"/enter_room",reqdata,function(ret,data){
console.log(data);
if(ret){
if(data.errcode == 0){
db.set_room_id_of_user(userId,roomId,function(ret){
fnCallback(0,{
ip:serverinfo.clientip,
port:serverinfo.clientport,
token:data.token
});
});
}
else{
console.log(data.errmsg);
fnCallback(data.errcode,null);
}
}
else{
fnCallback(-1,null);
}
});
};
var chooseServerAndEnter = function(serverinfo){
serverinfo = chooseServer();
if(serverinfo != null){
enterRoomReq(serverinfo);
}
else{
fnCallback(-1,null);
}
}
db.get_room_addr(roomId,function(ret,ip,port){
if(ret){
var id = ip + ":" + port;
var serverinfo = serverMap[id];
if(serverinfo != null){
checkRoomIsRuning(serverinfo,roomId,function(isRuning){
if(isRuning){
enterRoomReq(serverinfo);
}
else{
chooseServerAndEnter(serverinfo);
}
});
}
else{
chooseServerAndEnter(serverinfo);
}
}
else{
fnCallback(-2,null);
}
});
};
1> 麻将服务器http_server, enter_room URL响应
//加入房间
app.get('/enter_room',function(req,res){
var userId = parseInt(req.query.userid);
var name = req.query.name;
var roomId = req.query.roomid;
var sign = req.query.sign;
if(userId == null || roomId == null || sign == null){
http.send(res,1,"invalid parameters");
return;
}
var md5 = crypto.md5(userId + name + roomId + config.ROOM_PRI_KEY);
console.log(req.query);
console.log(md5);
if(md5 != sign){
http.send(res,2,"sign check failed.");
return;
}
//安排玩家坐下
roomMgr.enterRoom(roomId,userId,name,function(ret){
if(ret != 0){
if(ret == 1){
http.send(res,4,"room is full.");
}
else if(ret == 2){
http.send(res,3,"can't find room.");
}
return;
}
var token = tokenMgr.createToken(userId,5000);
http.send(res,0,"ok",{token:token});
});
});
大厅服room_service.js拿到token,游戏服http_service发过来的;
3> 大厅服务组合麻将服务器的ip, port, token, 房间ID 回给客户端
查找客户端处理返回结果:
- 获取到麻将服务器的
ip
、port
、token
、time
参数;
对应客户端的创建消息回调函数进行处理。
获取服务器返回信息后,启动游戏连接操作:
cc.vv.gameNetMgr.connectGameServer(ret);
GameNetManager.js
中的 connectGameServer
方法。
connectGameServer:function(data){
this.dissoveData = null;
cc.vv.net.ip = data.ip + ":" + data.port;
console.log(cc.vv.net.ip);
var self = this;
var onConnectOK = function(){
console.log("onConnectOK");
var sd = {
token:data.token,
roomid:data.roomid,
time:data.time,
sign:data.sign,
};
cc.vv.net.send("login",sd);
};
var onConnectFailed = function(){
console.log("failed.");
cc.vv.wc.hide();
};
cc.vv.wc.show("正在进入房间");
cc.vv.net.connect(onConnectOK,onConnectFailed);
}
- 连接成功后,向游戏服务器发送登录指令:通过
socket.io
发送登录命令login
:
javascript
socket.emit('login_result',ret);
//通知其它客户端
userMgr.broacastInRoom('new_user_comes_push',userData,userId);
- 服务器发送登录结果命令
login_result
到客户端,并由客户端进行保存。
GameNetMgr.js
中使用 initHandlers
方法处理:
cc.vv.net.addHandler("login_result", function(data) {
cc.vv.net.addHandler("login_finished", function(data) {
- 发送登录结束命令
login_finished
,客户端接收后进入游戏场景:
socket.emit('login_finished');
cc.vv.net.addHandler("login_finished",function(data){
console.log("login_finished");
cc.director.loadScene("mjgame",function(){
cc.vv.net.ping();
cc.vv.wc.hide();
});
self.dispatchEvent("login_finished");
});
服务器的扩展:
心跳/服务器负载:
通过 load
属性来记录当前服务器的负载状态: