棋牌游戏服务器设计(1)

简介:

一、项目划分

框架依赖的模块

1.高性能webserver--express模块 热更新 语音对话

2.websocket模块--ws

3.mysql模块-->mysql

4.redis模块-->redis


框架划分

1.webserver作用就是用来上传下载,获取配置信息,更新等.

通过web接入第三方的sdk。

2.gateway网关服务器,

(1)用来接收所有用户的长连接,转发用户请求

(2)连接游戏服务器转发服务器回应

(3)安全防护,过滤非法的数据包隔离游戏服务器免受客户攻击.

3.用户中心服务器: 用来管理这个用户的账号信息.这个用户信息

管理号,可以在进行这个平台的其他游戏.

4.系统服务: 处理用户和系统进行交互:每日登录,邮件,兑换东西等等.

比如你要做一个活动,这个就是用户和服务器单独交互.不会涉及其他用户.

5.游戏服务器:他涉及到多个用户之间的交互,处理不同游戏的服务.


每个服务又是一个进程,


数据库划分

1.后台数据库,分为两个.第一个数据库用来存放用户数据,大家公用的,

第二个是每个游戏都有一个游戏数据库。

2.为了访问速度的提高,把常用的数据缓存到redis服务器,

redis缓冲中心:也是两个:用户redis,和游戏数据redis.

3.数据库是所有进程都公用的.



3rd/utils/netbus模块

1.3rd存放第三方的js代码库

2.utils存放所有的公共模块

3.netbus模块,为所有长连接服务器所公用,支持websocket

TCP socket 二进制和json协议。




二、日志 TCPsocket和websocket模块封装支持


Log日志代码

1.log日志是重要的服务器手段

2.log写日志必须是异步的

3.log日志能够方便的定向到对应的服务器里去

4.log日志分登记和颜色

5.调试时全部打印到标准的输出文件,上线时在输出在文件

6.创建一个output文件夹存放输出的日志.

7.日志分级,如一般信息,警告信息,错误信息.

8.把重要信息保存到log,比如你充值了10元.


验证数据的合法性

1.tcp socket收到的数据必须是Buffer类型

2.ws socket json数据协议下收到的类型必须是字符串

3.ws socket buf数据协议下收到的类型必须是buffer



三、协议管理模块

1.协议规定是: 服务号,命令号,数据部分

2.提供协议解码 cmd[0]服务号,cmd[1]命令号,cmd[2]body三个部分

3.提供协议编码函数转json字符 或者 buff二进制(2字节2字节+body);

4提供协议服务端buf解码器注册函数

5.同时支持json和二进制,通过客户端连接自己选择

6.协议加密和解密也可以加入到这个模块

先编码在加密 —— 先解密在解码

一般只需要支持一种协议即可.



四、netbus服务管理模块

1.当netbus收到数据包的时候,需要把包分发给对应的服务来进行处理

2.service_manager(mg管理)

3.所有服务的管理模块,所有的服务都注册到这里

4.netbus收到数据,玩家掉线等,都进入它,通知对应的服务

5.提供服务模块注册函数,编写模板服务编写

6.转发到对应的服务后,使用decode_cmd 加密命令

7.告诉所有的service链接丢失





五、creator支持websocket_http支持buf和json协议

1.creator使用websocket和服务器进行联机,因为本身creator

本身是h5的,所以

2.ArrayBuffer.DataView,utf8,string字节长度,DataView读/写字符串

ArrayBuffer没有Buffer模块这么多接口,比如readUInt16LE这些.

这个时候就需要借助DataView. 

图片.png


他有一个参数,这个参数是可选的。

如果为 false 或未定义,则写入big-endian(大尾) 值;

否则应写入 little-endia(小尾) 值。

1
2
3
4
5
6
7
         var  buf =  new  ArrayBuffer(10);
         //无法直接操作数据 借助DataView
         var  dataview =  new  DataView(buf);
         //在第0个字节写入100 8一个字节没有大小尾
         dataview.setUint8(0,100);
         var  value = dataview.getUint8(0);
         console.log(value);


图片.png


而DataView只能处理数,没办法处理字符串.需要扩展DataView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//写入utf8字符串
DataView.prototype.write_utf8 =  function (offset,str){
     var  now = offset;
     var  dataview =  this ;
 
     for  ( var  i = 0; i < str.length; i++) {
         var  charcode = str.charCodeAt(i);
         if  (charcode < 0x80) { 
             dataview.setUint8(now, charcode);
             now ++;
         }
         else  if  (charcode < 0x800) {
             dataview.setUint8(now, (0xc0 | (charcode >> 6)));
             now ++;
 
             dataview.setUint8(now, 0x80 | (charcode & 0x3f));
             now ++;
         }
         else  if  (charcode < 0xd800 || charcode >= 0xe000) {
 
             dataview.setUint8(now, 0xe0 | (charcode >> 12));
             now ++;
 
             dataview.setUint8(now, 0x80 | ((charcode>>6) & 0x3f));
             now ++;
 
             dataview.setUint8(now, 0x80 | (charcode & 0x3f));
             now ++;
         }
         // surrogate pair
         else  {
             i ++;
 
             charcode = 0x10000 + (((charcode & 0x3ff)<<10)
                       | (str.charCodeAt(i) & 0x3ff));
 
             dataview.setUint8(now, 0xf0 | (charcode >>18));
             now ++;
 
             dataview.setUint8(now, 0x80 | ((charcode>>12) & 0x3f));
             now ++;
 
             dataview.setUint8(now, 0x80 | ((charcode>>6) & 0x3f));
             now ++;
 
             dataview.setUint8(now, 0x80 | (charcode & 0x3f));
             now ++;
         }
     }
}
 
 
//读取utf8字符串
DataView.prototype.read_utf8 =  function (offset,byte_length){
     var  out,i,len,c;
     var  char2,char3;
     var  dataview =  this ;
     //输出
     out =  "" ;
     len = byte_length;
     i = offset;
 
     while (i < len){
         c = dataview.getUint8(i);
         i++;
         //这个字符串右移4位 判断这个位的值
         switch (c >> 4)
         {
         case  0:  case  1:  case  2:  case  3:  case  4:  case  5:  case  6:  case  7:
             //Unicode 编码转为一个字符
             //例如他是十进制65 则会转成ACSLL 'A'
             out += String.fromCharCode(i);
         break ;
 
         case  12:  case  13:
             char2 = array[i++];
             out += String.fromCharCode(((c & 0x1F)<<6) | (char2 & 0x3F));
         break ;
 
         case  14:
             char2 = dataview.getUint8(i);
             i++;
             char3 = dataview.getUint8(i);
             i++;
             out += String.fromCharCode(((c & 0x0F)<<12)|
                 ((char2 & 0x3F) << 6) |
                 ((char3 & 0x3F) << 0));
 
         break ;
 
         } //end switch
     }
}

封装websocket模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//websocket模块封装
 
var  proto = require( "proto_mgr" );
console.log( "proto:" ,proto);
 
var  websocket = {
     sock:  null ,
     serivces_handler:  null ,   //当有消息来的时候回调函数
     proto_type: 0,       //协议类型
     is_commected:  false , //是否连接
 
     _on_opened:  function (event){
         console.log( "ws connect server success!" );
         this .is_commected =  true ;
     },
 
     _on_recv_data:  function (strbufdata){
         if (! this .serivces_handler){
             console.log( "not find serivces_handler" );
             return ;
         }
 
         //获取命令
         var  cmd = proto.decode_cmd( this .proto_type,strbufdata);
         if (!cmd){
             console.log( "websocket.js(25) cmd invaild!" );
             return ;
         }
 
         var  stype = cmd[0];
         if ( this .serivces_handler[stype]){
             this .serivces_handler[stype](cmd[0],cmd[1],cmd[2]);
         }
     },
 
     _on_socket_close:  function (event){
         if ( this .sock){
             this .close();
         }
     },
 
     _on_socket_err:  function (event){
         this .close();
     },
 
     connect:  function (url,proto_type){
         this .sock =  new  WebSocket(url);
 
         this .sock.onopen =  this ._on_opened.bind( this );
         this .sock.onmessage =  this ._on_recv_data.bind( this );
         this .sock.onclose =  this ._on_socket_close.bind( this );
         this .sock.onerror =  this ._on_socket_err.bind( this );
         this .proto_type = proto_type;
     },
 
 
     send_cmd:  function (stype,ctype,body){
         if (! this .sock || ! this .is_commected){
             console.log( "send commind error!" );
             return ;
         }
         var  buf = proto.encode_cmd( this .proto_type,stype,ctype,body);
         this .sock.send(buf);
     },
 
     close:  function (){
         this .is_commected =  false ;
         if ( this .sock !==  null ){
             this .sock.close();
             this .sock =  null ;
         }
     },
 
     regist_services_handler:  function (serivces_handler){
         this .serivces_handler = serivces_handler;
     },
 
}
 
 
 
//选择启动协议
//使用json 连接到服务
//websocket.connect("ws://127.0.0.1:6081/ws",proto.PROTO_JSON);
//使用二进制连接到服务
//websocket.connect("ws://127.0.0.1:6083/ws",proto.PROTO_BUF);
 
 
module.exports = websocket;

封装http模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//http 模块
 
var  http = {
     //get请求 用于网页 文本
     get:  function (url, path, params,callback){
         //获取XMLHTpRequest实例
         var  xhr = cc.loader.getXMLHttpRequest();
         xhr.timeout = 5000;
         var  requestURL = url + path;
         //添加变量
         if (params){
             requestURL = requestURL +  "?"  + params;
         }
 
         //http或https请求必须通过open方法初始化
         //必须在发送请求前调用
         //1:请求方法,2url,3ture就是异步请求 false阻塞
         //4用户名 5密码
         xhr.open( "GET" ,requestURL, true );
         //true模拟器,手机  false web
         if (cc.sys.isNative){
             //设置请求头 信息
             xhr.setRequestHeader( "Accept-Encoding" , "gzip,deflate" , "text/html;charset=UTF-8" );
         }
 
 
         //readystate 是http的请求状态 当XMLHttpRequest
         //初次创建时,这个属性是0,直到接收到完整的HTTP响应
         //这个值增加到 4。
         //1是open方法被调用,send方法未调用
         //2是send方法被调用,HTTP请求到达web服务器
         //3是所有响应头部都已经接收到。响应体开始接收但未完成。
         //4是HTTP响应已经完成接收
         xhr.onreadystatechange =  function (){
             if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){
                 //输出响应长度 和 正文
                 console.log( "http res(" +xhr.responseText.length+ "):" +xhr.responseText);
                 try {
                     var  ret = xhr.responseText;
                     if (callback !==  null ){
                         //把响应信息 传给回调函数
                         callback( null ,ret);
                     }
                     return ;
                 } catch (e){
                     //错误处理 把错误信息传给回调
                     callback(e, null );
                 } //end catch
             } //end if
             else {
                 //这里就是请求错误 传给callback
                 //请求状态 和 状态码
                 callback(xhr.readyState+ ":" +xhr.status, null );
             } //end else
         };
 
         xhr.send();
         return  xhr;
     },
 
 
     //用于上传 body就是上传体
     post:  function (url,path,params,body,callback){
         var  xhr = cc.loader.getXMLHttpRequest();
         xhr.timeout = 5000;
         var  requestURL = url + path;
         if (params){
             requestURL = requestURL +  "?"  + params;
         }
         xhr.open( "POST" ,requestURL, true );
         if (cc.sys.isNative){
             xhr.setRequestHeader( "Accept-Encoding" , "gzip,deflate" , "text/html;charset=UTF-8" );
         }    
         
         //判断是否有body
         if (body){
             //在发送到服务器之前,所有字符都会进行编码
             xhr.setRequestHeader( "Content-Type" , "application/x-www-form=urlencoded" );
             //长度
             xhr.setRequestHeader( "Content-Length" ,body.length);
         }    
 
         xhr.onreadystatechange =  function (){
             if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){
                 try {
                     var  ret = xhr.responseText;
                     if (callback !==  null ){
                         //把响应信息 传给回调函数
                         callback( null ,ret);
                     }
                     return ;
                 } catch (e){
                     //错误处理 把错误信息传给回调
                     callback(e, null );
                 } //end catch
             } //end if
             else {
                 //这里就是请求错误 传给callback
                 //请求状态 和 状态码
                 callback(xhr.readyState+ ":" +xhr.status, null );
             } //end else
         };    
         if (body){
             xhr.sned(body);
         }
         return  xhr;
     },
 
 
     //用于下载 文件 二进制文件
     download:  function (url,path,params,callback){
         var  xhr = cc.loader.getXMLHttpRequest();
         xhr.timeout = 5000;
         var  requestURL = url + path;
         if (params){
             requestURL = requestURL +  "?"  + params;
         }
 
         //响应类型
         xhr.responseType =  "arraybuffer" ;
         xhr.open( "GET" ,requestURL, true );
         if (cc.sys.isNative){
             xhr.setRequestHeader( "Accept-Encoding" , "gzip,deflate" , "text/html;charset=UTF-8" );
         }
 
         xhr.onreadystatechange =  function (){
             if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){    
                 var  buffer = xhr.response;
                 var  dataview =  new  DataView(buffer);
                 //8 位无符号整数值的类型化数组
                 var  ints =  new  Uint8Array(buffer.byteLength);
                 for ( var  i = 0;i < ints.length; i++){
                     //获取response 二进制数据
                     ints[i] = dataview.getUint8(i);
                 }
                 callback( null ,ints);
             } else {
                 callback(xhr.readyState+ ":" +xhr.status, null );
             } //end else
         };
         xhr.send();
         return  xhr;
     },
 
};
 
 
 
module.exports = http;








 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/2071208,如需转载请自行联系原作者

相关实践学习
基于Hologres轻量实时的高性能OLAP分析
本教程基于GitHub Archive公开数据集,通过DataWorks将GitHub中的项⽬、行为等20多种事件类型数据实时采集至Hologres进行分析,同时使用DataV内置模板,快速搭建实时可视化数据大屏,从开发者、项⽬、编程语⾔等多个维度了解GitHub实时数据变化情况。
阿里云实时数仓实战 - 用户行为数仓搭建
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求:熟练掌握 SQL 语法熟悉 Linux 命令,对 Hadoop 大数据体系有一定的了解 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
存储 关系型数据库 数据库
|
安全 网络安全
棋牌游戏服务器该怎么选择,高防服务器的优势是什么呢?
棋牌游戏服务器 竞价服务器 医疗服务器 高防服务器
5153 0
|
数据库 数据安全/隐私保护 关系型数据库
棋牌游戏服务器如何动态创建房间?
如何动态创建房间? 客户需要这个功能,我们给设计成VIP会员可创建房间,那么涉及这几个功能, 一接受客户端请求,服务器端NEW一个ROOM类,并ADD到RoomList 二设定EMPTY TIME OUT,自动销毁空房间, 三向客户端通知事件,ROOM ADD,ROOM DEL   接下...
棋牌游戏服务器基本架构
1. 总体设计概述休闲游戏系统主要包括以下几个部分: l 服务器:CenterServer、GameDataServer、GameServer、LoginServer l 客户端 l 数据库 l 网管工具 l 网页服务器 用户使用客户端通过网络连接LoginServer进行身份验证,CenterServer和GameServer通过数据库存取用户数据,客户端和GameServer配
|
12月前
|
弹性计算 运维 安全
阿里云轻量应用服务器与云服务器ECS啥区别?新手帮助教程
阿里云轻量应用服务器适合个人开发者搭建博客、测试环境等低流量场景,操作简单、成本低;ECS适用于企业级高负载业务,功能强大、灵活可扩展。二者在性能、网络、镜像及运维管理上差异显著,用户应根据实际需求选择。
957 10
|
12月前
|
弹性计算 ice
阿里云4核8g服务器多少钱一年?1个月和1小时价格,省钱购买方法分享
阿里云4核8G服务器价格因实例类型而异,经济型e实例约159元/月,计算型c9i约371元/月,按小时计费最低0.45元。实际购买享折扣,1年最高可省至1578元,附主流ECS实例及CPU型号参考。
948 8
|
12月前
|
运维 安全 Ubuntu
阿里云渠道商:服务器操作系统怎么选?
阿里云提供丰富操作系统镜像,涵盖Windows与主流Linux发行版。选型需综合技术兼容性、运维成本、安全稳定等因素。推荐Alibaba Cloud Linux、Ubuntu等用于Web与容器场景,Windows Server支撑.NET应用。建议优先选用LTS版本并进行测试验证,通过标准化镜像管理提升部署效率与一致性。
|
12月前
|
存储 监控 安全
阿里云渠道商:云服务器价格有什么变动?
阿里云带宽与存储费用呈基础资源降价、增值服务差异化趋势。企业应结合业务特点,通过阶梯计价、智能分层、弹性带宽等策略优化成本,借助云监控与预算预警机制,实现高效、可控的云资源管理。

热门文章

最新文章