棋牌游戏服务器设计(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轻松玩转一站式实时仓库
本场景介绍如何利用阿里云MaxCompute、实时计算Flink和交互式分析服务Hologres开发离线、实时数据融合分析的数据大屏应用。
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
棋牌游戏软件
本文研究全球及中国市场棋牌游戏软件现状及未来发展趋势,侧重分析全球及中国市场的主要企业,同时对比北美、欧洲、中国、日本、东南亚和印度等地区的现状及未来发展趋势
|
负载均衡 安全 网络安全
经济危机下,三招实现企业网络应用最大化
本文讲的是经济危机下,三招实现企业网络应用最大化,2009年是充满机遇和挑战的一年,个人、企业、政府、都将面临日益复杂化的挑战。企业面临的变化复杂多样:如客户的选择、竞争动向、技术发展、经济环境、外包与开发战略、分销与服务模式、商务价格、市场覆盖范围等都在时刻发生变化。
1415 0
|
安全 网络安全
曾优雅击退史上最凶狠的DDoS攻击,AliGuard的高性能从何而来?
2016年10月21日,美国发生了一次震惊全球互联网的安全事件,大半个美国的互联网因为DDoS攻击发生瘫痪,攻击从清晨开始一直持续到傍晚,黑客发起了长时间多批次攻击,直接导致twitter、Spotify、Netflix、Github、Airbnb、Visa等出现无法登陆的情况,而且twitter甚至都出现了长时间瘫痪的情况,因此造成的直接和间接经济损失无法估量。
10508 0
棋牌游戏服务器基本架构
1. 总体设计概述休闲游戏系统主要包括以下几个部分: l 服务器:CenterServer、GameDataServer、GameServer、LoginServer l 客户端 l 数据库 l 网管工具 l 网页服务器 用户使用客户端通过网络连接LoginServer进行身份验证,CenterServer和GameServer通过数据库存取用户数据,客户端和GameServer配
|
算法 数据处理 数据库
棋牌游戏架构
一、物理架构说明 游戏系统组件包括: 服务器系统(中心服务器控制系统,服务器登陆控制系统,游戏登陆服务器,游戏房间控制系统,游戏组件系统),游戏客户端(游戏大厅,游戏组件)。 数据库系统:用于保存用户信息数据、游戏积分数据、游戏系统运行状态数据,系统日志数据等数据。 中心服务器控制系统:用于向客户端提供全局配置,初始化数据使用。 服务器登陆控制系统:用于房间服务控制系统登陆效验使用
推荐文章
更多