转:棋牌游戏服务器设计(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.

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

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

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

        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);

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

//写入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模块

//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模块

//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;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • 各种自我宣传,类“我选我”。 背景 近日观察到微信群、朋友圈推荐自己写的文,不定期地分享想到的。
    许瑞光阅读 164评论 0 0
  • 一 叶华心自杀了。 从实验室窗户跳楼。窗前桌上留着她的手机,解锁是记事本,英文写着:“We shall meet ...
    拓皮阅读 564评论 0 1
  • 以前总是对别人说的“生了二胎之后,要随时关注大宝的情绪”这句话嗤之以鼻,因为我很自信我家孩子是一个充满爱的孩子,事...
    水草凤阅读 199评论 0 0
  • 我不敢面对,快三十岁的年龄。 我不敢面对,象征着中年的脱发和啤酒肚。 我不敢面对,渐渐稀疏的头顶,那永远凌乱而干枯...
    西南伪文青阅读 351评论 0 0