情怀麻将“开房”
目录
- 情怀麻将“开房”
要点
- 情怀麻将版图概览
- 情怀麻将游戏服务器注册到大厅进行负载配置
- 创建房间的客户端请求
- 创建房间的服务器响应
- 客户端登录到游戏服务器
寻找客户端发送逻辑
- 定位创建按钮,并查找关联的函数 →
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组件。
2.显示创建房间界面,并找到其挂载的脚本: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);
发送给服务器,请求创建房间。
将conf
对象转换为JSON字符串:conf: JSON.stringify(conf)
。
请求被提交至大厅服务器的http client service
服务,即客户端发送请求到服务器。
4.响应地址为create_private_room
,数据为创建参数,返回结果回调至onCreate
函数。
服务器接收到客户端发送的data
后,进行相应的逻辑处理。
寻找服务器响应
-
大厅服务器:配置了
/create_private_room
路径,用于处理客户端请求,通过数据库查询玩家数据。 -
处理步骤:
(1) 从数据库查询账号信息,使用db.js
中的get_user_data
函数。
(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房卡的数目;
db.get_gems(account,function(data){
优化点:房卡查询过程进行了两次,可通过系统优化减少冗余查询。
随后,执行room_service.js
中的createRoom
函数,完成房间创建流程。
接着消息被发送到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
方法:
房间创建参数包括userId
、conf
、gems
、serverIp
和config.CLIENT_PORT
,并执行回调函数处理结果。
//游戏服开房逻辑
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)
完成房间创建。
5.URL响应返回后调用enterRoom
:
roommgr.js
中的exports.createRoom
会回调callback(0, roomId)
,将roomId
返回给http_service
,然后发送房间ID。
http.send(res,0,"ok",{roomid:roomId});
hall_service.js
接收到回调数据roomId
后,准备执行进入房间的逻辑:
调用room_service.enterRoom(userId, name, roomId, function(errcode, enterInfo)
来处理用户进入房间的操作。
优化点:在游戏中尽量减少使用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});
});
});
2.roomMgr
的enterRoom
方法返回一个token
给大厅服务;
大厅服务器room_service.js
接收到游戏服务器http_service
发送过来的token
;
3.大厅服务将麻将服务器的IP、端口、
token
及房间ID组合后返回给客户端
寻找客户端处理返回
- 获取麻将服务器的IP、端口、
token
和时间;
客户端处理创建房间消息的回调函数。
拿到服务器返回的信息后,开始进行游戏连接:
cc.vv.gameNetMgr.connectGameServer(ret)
2.连接麻将服务器: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);
}
3: 连接成功后,发送登陆游戏服务器的命令 socket.io发送登陆login
发送长连接请求 cc.vv.net.send("login",sd);
4.获取房间信息,包括座位详情,并广播给房间内的其他玩家,通知新用户加入。
socket.emit('login_result',ret);
//通知其它客户端
userMgr.broacastInRoom('new_user_comes_push',userData,userId);
5.服务器发送login_result
命令到客户端,客户端接收并保存。随后,在GameNetMgr.js
的initHandlers
函数中处理login_result
和login_finished
消息。
6.服务器发送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
来保存当前服务器的负载信息:
gameServerInfo.load
存储了roomMgr.getTotalRooms()
返回的房间总数。
http.get(config.HALL_IP,config.HALL_PORT,"/register_gs"