本文共 9603 字,大约阅读时间需要 32 分钟。
一、项目划分
框架依赖的模块
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(小尾) 值。
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); |
而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,如需转载请自行联系原作者