根据 chat 示例创建 game-server,支持分布式部署、域名访问、数据库连接和基础使用

This commit is contained in:
liangtongchuan
2020-08-15 20:34:31 +08:00
parent e52a829567
commit 8ce0dc040f
36 changed files with 3165 additions and 2 deletions

View File

@@ -1,3 +1,15 @@
# zyz_server
# 环境搭建
- 安装 ts 环境
`npm install -g tsc ts-node typescript`
- 安装 pinus
`npm install -g pinus`
赵云传服务器
# 运行
## 简介
服务器主要分为游戏服和账号服,游戏服以长连接为主,处理主要的游戏逻辑。账号服以短连接为主,处理账号等功能。
## 运行游戏服
`cd game-server && node tsrun.js`
## 运行 web-server
`cd web-server && node app`

9
game-server/Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM node:12
RUN mkdir /game-server
WORKDIR /game-server
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN cnpm install -g ts-node
RUN cnpm install -g typescript
RUN cnpm install -g pm2
RUN cnpm install -g pinus
CMD pinus start

172
game-server/app.ts Normal file
View File

@@ -0,0 +1,172 @@
import {
createTcpAcceptor,
createTcpMailBox,
FrontendOrBackendSession,
HandlerCallback,
pinus,
RESERVED,
RouteRecord
} from 'pinus';
import * as mongoose from 'mongoose';
import * as redis from 'redis';
import './app/servers/user.rpc.define'
import * as routeUtil from './app/util/routeUtil';
import { preload } from './preload';
// TODO 需要整理。
import _pinus = require('pinus');
const filePath = (_pinus as any).FILEPATH;
filePath.MASTER = '/config/master';
filePath.SERVER = '/config/servers';
filePath.CRON = '/config/crons';
filePath.LOG = '/config/log4js';
filePath.SERVER_PROTOS = '/config/serverProtos';
filePath.CLIENT_PROTOS = '/config/clientProtos';
filePath.MASTER_HA = '/config/masterha';
filePath.LIFECYCLE = '/lifecycle';
filePath.SERVER_DIR = '/app/servers/';
filePath.CONFIG_DIR = '/config';
const adminfilePath = _pinus.DEFAULT_ADMIN_PATH;
adminfilePath.ADMIN_FILENAME = 'adminUser';
adminfilePath.ADMIN_USER = 'config/adminUser';
/**
* 替换全局Promise
* 自动解析sourcemap
* 捕获全局错误
*/
preload();
// 创建 mongodb 连接
mongoose.connect('mongodb://root:zyz_2020@dds-8vbdb47c6fb58a541.mongodb.zhangbei.rds.aliyuncs.com:3717,dds-8vbdb47c6fb58a542.mongodb.zhangbei.rds.aliyuncs.com:3717/admin?replicaSet=mgset-500808098', (err) => {
if (err) {
console.log('mongodb connect err', err);
} else {
console.log('mongodb connect suc');
}
});
// 创建 redis 连接
const client = redis.createClient(6379, 'r-8vb4i2kgl91886fkxd.redis.zhangbei.rds.aliyuncs.com', {detect_buffers: true});
client.auth('zyz_2020', (err, reply) => {
if (err) {
console.log('redis err', err);
} else {
console.log('redis suc');
}
})
client.set('hello', 'redis', redis.print);
/**
* Init app for client.
*/
let app = pinus.createApp();
app.set('name', 'chatofpomelo-websocket');
// app configuration
app.configure('production|development', 'connector', function () {
app.set('connectorConfig',
{
connector: pinus.connectors.hybridconnector,
heartbeat: 60,
useDict: true,
useProtobuf: true
});
/**
// 缓存大小不够 日志示例
[2020-03-27T10:44:48.752] [ERROR] pinus - [chat-server-1 channelService.js] [pushMessage] fail to dispatch msg to serverId: connector-server-1, err:RangeError [ERR_OUT_OF_RANGE]: The value of "offset" is out of range. It must be >= 0 and <= 0. Received 1
at boundsError (internal/buffer.js:53:9)
at writeU_Int8 (internal/buffer.js:562:5)
at Buffer.writeUInt8 (internal/buffer.js:569:10)
at Encoder.writeBytes (F:\develop\gong4-server\logicServer\pinus\packages\pinus-protobuf\lib\encoder.ts:195:20)
*/
app.set('protobufConfig', {
// protobuf Encoder 使用 5m 的缓存 需要保证每个消息不会超过指定的缓存大小,超过了就会抛出异常
encoderCacheSize: 5 * 1024 * 1024,
// decode 对客户端请求消息做校验
decodeCheckMsg: true,
});
});
app.configure('production|development', 'gate', function () {
app.set('connectorConfig',
{
connector: pinus.connectors.hybridconnector,
useProtobuf: true
});
});
function errorHandler(err: Error, msg: any, resp: any,
session: FrontendOrBackendSession, cb: HandlerCallback) {
console.error(`${ pinus.app.serverId } error handler msg[${ JSON.stringify(msg) }] ,resp[${ JSON.stringify(resp) }] ,
to resolve unknown exception: sessionId:${ JSON.stringify(session.export()) } ,
error stack: ${ err.stack }`);
if (!resp) {
resp = { code: 1003 };
}
cb(err, resp);
}
export function globalErrorHandler(err: Error, msg: any, resp: any,
session: FrontendOrBackendSession, cb: HandlerCallback) {
console.error(`${ pinus.app.serverId } globalErrorHandler msg[${ JSON.stringify(msg) }] ,resp[${ JSON.stringify(resp) }] ,
to resolve unknown exception: sessionId:${ JSON.stringify(session.export()) } ,
error stack: ${ err.stack }`);
if (cb) {
cb(err, resp ? resp : { code: 503 });
}
}
// app configure
app.configure('production|development', function () {
app.set(RESERVED.ERROR_HANDLER, errorHandler);
app.set(RESERVED.GLOBAL_ERROR_HANDLER, globalErrorHandler);
app.globalAfter((err: Error, routeRecord: RouteRecord, msg: any, session: FrontendOrBackendSession, resp: any, cb: HandlerCallback) => {
console.log('global after ', err, routeRecord, msg)
})
app.globalBefore((routeRecord: RouteRecord, msg: any, session: FrontendOrBackendSession, cb: HandlerCallback) => {
if (msg.body === null) {
cb(new Error(`msg body ===null maybe protobuf check error uid:${ session.uid } ${ JSON.stringify(msg) }`), { code: 499 });
return;
}
cb(null);
})
// route configures
app.route('chat', routeUtil.chat);
// filter configures
app.filter(new pinus.filters.timeout());
// RPC 启用TCP协议
app.set('proxyConfig', {
mailboxFactory: createTcpMailBox,
// bufferMsg:true
// rpc 超时时间
// timeout: 20 * 1000,
// dynamicUserProxy: true,
});
app.set('remoteConfig', {
acceptorFactory: createTcpAcceptor,
// bufferMsg:true,
// interval:50,
});
});
app.configure('development', function () {
// enable the system monitor modules
app.enable('systemMonitor');
});
if (app.isMaster()) {
// app.use(createRobotPlugin({scriptFile: __dirname + '/robot/robot.js'}));
}
// start app
app.start();

View File

@@ -0,0 +1,24 @@
import { prop, pre } from '@typegoose/typegoose';
/**
* BaseModel
*/
@pre<BaseModel>('save', function (next) {
if (!this.createdAt || this.isNew) {
this.createdAt = this.updatedAt = new Date()
} else {
this.updatedAt = new Date()
}
next()
})
export default class BaseModel {
_id?: string
@prop()
createdAt: Date
@prop()
updatedAt: Date
}

View File

@@ -0,0 +1,45 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
/**
* 用户字段接口
*/
@index({ userNo: 1 })
export default class User extends BaseModel {
@prop({ required: true})
userNo: number;
@prop({ required: true})
userName: string;
@prop({ required: true})
token: string;
@prop({ required: true})
telHash: string;
//#region实例方法 和 实例方法)
public async userInstanceTestMethods() {
const user: User = new User();
user.userName = '我是实例化方法测试';
user.userNo = 9527;
return user;
}
public static async userStaticTestMethods() {
const user: User = new User();
user.userName = '我是静态方法测试';
user.userNo = 9527;
return user;
}
//#endregion
}
export const UserModel = getModelForClass(User);

View File

@@ -0,0 +1,76 @@
import { UserModel } from './../../../db/User';
import { ChatRemote } from '../remote/chatRemote';
import {Application, BackendSession} from 'pinus';
import { FrontendSession } from 'pinus';
export default function(app: Application) {
return new ChatHandler(app);
}
export class ChatHandler {
constructor(private app: Application) {
}
/**
* Send messages to users
*
* @param {Object} msg message from client
* @param {Object} session
*
*/
async send(msg: {content: string , target: string}, session: BackendSession) {
let rid = session.get('rid');
let username = session.uid.split('*')[0];
let channelService = this.app.get('channelService');
let param = {
msg: msg.content,
from: username,
target: msg.target
};
let channel = channelService.getChannel(rid, false);
// the target is all users
if (msg.target === '*') {
channel.pushMessage('onChat', param);
}
// the target is specific user
else {
let tuid = msg.target + '*' + rid;
let tsid = channel.getMember(tuid)['sid'];
channelService.pushMessageByUids('onChat', param, [{
uid: tuid,
sid: tsid
}]);
}
}
async send2(msg: {content: string , target: string}, session: BackendSession) {
let rid = session.get('rid');
let username = session.uid.split('*')[0];
let channelService = this.app.get('channelService');
let param = {
msg: msg.content,
from: username,
target: msg.target
};
let channel = channelService.getChannel(rid, false);
console.log(`got user in send2 :`,);
const user = await UserModel.findOneAndUpdate({userName: username}, {userName: username, userNo: 666}, {upsert: true, new: true}).lean();
console.log(`got user in send2 :`, user);
// the target is all users
if (msg.target === '*') {
channel.pushMessage('onChat', param);
}
// the target is specific user
else {
let tuid = msg.target + '*' + rid;
let tsid = channel.getMember(tuid)['sid'];
channelService.pushMessageByUids('onChat', param, [{
uid: tuid,
sid: tsid
}]);
}
}
}

View File

@@ -0,0 +1,92 @@
import { Application, ChannelService, FrontendSession, RemoterClass } from 'pinus';
export default function (app: Application) {
return new ChatRemote(app);
}
// rpc 定义挪到单独的定义文件(user.rpc.define.ts)。解决ts-node 有可能找不到定义的问题。
// 你也可以用其它方法解决,或者没有遇到过这个问题的话,定义还是可以放在这里。
// UserRpc的命名空间自动合并
// declare global {
// interface UserRpc {
// chat: {
// chatRemote: RemoterClass<FrontendSession, ChatRemote>;
// };
// }
// }
export class ChatRemote {
constructor(private app: Application) {
this.app = app;
this.channelService = app.get('channelService');
}
private channelService: ChannelService;
/**
* Add user into chat channel.
*
* @param {String} uid unique id for user
* @param {String} sid server id
* @param {String} name channel name
* @param {boolean} flag channel parameter
*
*/
public async add(uid: string, sid: string, name: string, flag: boolean) {
let channel = this.channelService.getChannel(name, flag);
let username = uid.split('*')[0];
let param = {
user: username
};
channel.pushMessage('onAdd', param);
if (!!channel) {
channel.add(uid, sid);
}
return this.get(name, flag);
}
/**
* Get user from chat channel.
*
* @param {Object} opts parameters for request
* @param {String} name channel name
* @param {boolean} flag channel parameter
* @return {Array} users uids in channel
*
*/
private get(name: string, flag: boolean) {
let users: string[] = [];
let channel = this.channelService.getChannel(name, flag);
if (!!channel) {
users = channel.getMembers();
}
for (let i = 0; i < users.length; i++) {
users[i] = users[i].split('*')[0];
}
return users;
}
/**
* Kick user out chat channel.
*
* @param {String} uid unique id for user
* @param {String} sid server id
* @param {String} name channel name
*
*/
public async kick(uid: string, sid: string, name: string) {
let channel = this.channelService.getChannel(name, false);
// leave channel
if (!!channel) {
channel.leave(uid, sid);
}
let username = uid.split('*')[0];
let param = {
user: username
};
channel.pushMessage('onLeave', param);
}
}

View File

@@ -0,0 +1,63 @@
import {Application} from 'pinus';
import {FrontendSession} from 'pinus';
export default function (app: Application) {
return new EntryHandler(app);
}
export class EntryHandler {
constructor(private app: Application) {
}
/**
* New client entry chat server.
*
* @param {Object} msg request message
* @param {Object} session current session object
*/
async enter(msg: { rid: string, username: string }, session: FrontendSession) {
let self = this;
let rid = msg.rid;
let uid = msg.username + '*' + rid;
let sessionService = self.app.get('sessionService');
// duplicate log in
if (!!sessionService.getByUid(uid)) {
return {
code: 500,
error: true
};
}
await session.abind(uid);
session.set('rid', rid);
session.push('rid', function (err) {
if (err) {
console.error('set rid for session service failed! error is : %j', err.stack);
}
});
session.on('closed', this.onUserLeave.bind(this));
// put user into channel
let users = await self.app.rpc.chat.chatRemote.add.route(session)(uid, self.app.get('serverId'), rid, true);
return {
users: users
};
}
/**
* User log out handler
*
* @param {Object} app current application
* @param {Object} session current session object
*
*/
onUserLeave(session: FrontendSession) {
if (!session || !session.uid) {
return;
}
this.app.rpc.chat.chatRemote.kick.route(session, true)(session.uid, this.app.get('serverId'), session.get('rid'));
}
}

View File

@@ -0,0 +1,42 @@
import { dispatch } from '../../../util/dispatcher';
import { Application , BackendSession} from 'pinus';
export default function (app: Application) {
return new GateHandler(app);
}
export class GateHandler {
constructor(private app: Application) {
}
/**
* Gate handler that dispatch user to connectors.
*
* @param {Object} msg message from client
* @param {Object} session
*
*/
async queryEntry(msg: {uid: string}, session: BackendSession) {
let uid = msg.uid;
if (!uid) {
return {
code: 500
};
}
// get all connectors
let connectors = this.app.getServersByType('connector');
console.log('ltc connectors', connectors);
if (!connectors || connectors.length === 0) {
return {
code: 500
};
}
// select connector
let res = dispatch(uid, connectors);
return {
code: 200,
host: res.host,
port: res.clientPort
};
}
}

View File

@@ -0,0 +1,14 @@
// 这种
// UserRpc的命名空间自动合并
import { FrontendSession, RemoterClass } from 'pinus';
import { ChatRemote } from './chat/remote/chatRemote';
declare global {
interface UserRpc {
chat: {
chatRemote: RemoterClass<FrontendSession, ChatRemote>;
};
}
}

View File

@@ -0,0 +1,7 @@
import * as crc from 'crc';
import { ServerInfo } from 'pinus';
export function dispatch(uid: string , connectors: ServerInfo[]) {
let index = Math.abs(crc.crc32(uid)) % connectors.length;
return connectors[index];
}

View File

@@ -0,0 +1,15 @@
import { dispatch} from './dispatcher';
import { Session, Application } from 'pinus';
export function chat(session: Session, msg: any, app: Application, cb: (err: Error , serverId ?: string) => void) {
let chatServers = app.getServersByType('chat');
if(!chatServers || chatServers.length === 0) {
cb(new Error('can not find chat servers.'));
return;
}
let res = dispatch(session.get('rid'), chatServers);
cb(null, res.id);
}

View File

@@ -0,0 +1,11 @@
module.exports = [{
'type': 'connector',
'token': 'agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn'
}, {
'type': 'chat',
'token': 'agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn'
}, {
'type': 'gate',
'token': 'agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn'
}
];

View File

@@ -0,0 +1,17 @@
module.exports = [{
'id': 'user-1',
'username': 'admin',
'password': 'admin',
'level': 1
}, {
'id': 'user-2',
'username': 'monitor',
'password': 'monitor',
'level': 2
}, {
'id': 'user-3',
'username': 'test',
'password': 'test',
'level': 2
}
];

View File

@@ -0,0 +1,14 @@
module.exports = {
"chat.chatHandler.send": {
"required string content": 1,
"required string target": 2,
"required string rid": 3,
"required string from": 4,
},
"chat.chatHandler.send2": {
"required string rid": 1,
"required string content": 2,
"required string from": 3,
"required string target": 4
},
};

View File

@@ -0,0 +1 @@
module.exports = [];

View File

@@ -0,0 +1,134 @@
module.exports = {
'appenders': {
'console': {
'type': 'console'
},
'con-log': {
'type': 'file',
'filename': '${opts:base}/logs/con-log-${opts:serverId}.log',
'pattern': 'connector',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'rpc-log': {
'type': 'file',
'filename': '${opts:base}/logs/rpc-log-${opts:serverId}.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'forward-log': {
'type': 'file',
'filename': '${opts:base}/logs/forward-log-${opts:serverId}.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'rpc-debug': {
'type': 'file',
'filename': '${opts:base}/logs/rpc-debug-${opts:serverId}.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'crash-log': {
'type': 'file',
'filename': '${opts:base}/logs/crash.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'admin-log': {
'type': 'file',
'filename': '${opts:base}/logs/admin.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'pinus': {
'type': 'file',
'filename': '${opts:base}/logs/pinus-${opts:serverId}.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'pinus-admin': {
'type': 'file',
'filename': '${opts:base}/logs/pinus-admin.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
},
'pinus-rpc': {
'type': 'file',
'filename': '${opts:base}/logs/pinus-rpc-${opts:serverId}.log',
'maxLogSize': 1048576,
'layout': {
'type': 'basic'
},
'backups': 5
}
},
'categories': {
'default': {
'appenders': ['console', 'pinus'],
'level': 'debug'
},
'con-log': {
'appenders': ['con-log'],
'level': 'debug'
},
'rpc-log': {
'appenders': ['rpc-log'],
'level': 'debug'
},
'forward-log': {
'appenders': ['forward-log'],
'level': 'debug'
},
'rpc-debug': {
'appenders': ['rpc-debug'],
'level': 'debug'
},
'crash-log': {
'appenders': ['crash-log'],
'level': 'debug'
},
'admin-log': {
'appenders': ['admin-log'],
'level': 'debug'
},
'pinus-admin': {
'appenders': ['pinus-admin'],
'level': 'debug'
},
'pinus-rpc': {
'appenders': ['pinus-rpc'],
'level': 'debug'
},
},
'prefix': '${opts:serverId} ',
'replaceConsole': true,
'lineDebug': false,
'errorStack': true
};

View File

@@ -0,0 +1,30 @@
// 不写日志文件 示例配置文件。
module.exports = {
'appenders': {
'displayConsole': {
'type': 'console'
},
// "other": {
// "type": "file",
// "filename": "${opts:base}/logs/${opts:serverId}.log",
// "maxLogSize": 1048576,
// "layout": {
// "type": "basic"
// },
// "backups": 5
// }
},
'categories': {
'default': {
'appenders': [
/*"other",*/'displayConsole'
],
'level': 'debug'
}
},
'replaceConsole': true,
'prefix': '${opts:serverId} ',
'lineDebug': false,
'errorStack': true
};

View File

@@ -0,0 +1,13 @@
module.exports = {
'development': {
'id': 'master-server-1',
'host': '127.0.0.1',
'port': 3005
},
'production': {
'id': 'master-server-1',
'host': '127.0.0.1',
'port': 3005
}
};

View File

@@ -0,0 +1,7 @@
module.exports = {
'onChat': {
'required string msg': 1,
'required string from': 2,
'required string target': 3
},
};

View File

@@ -0,0 +1,45 @@
module.exports = {
'development': {
'connector': [
{
'id': 'connector-server-1',
'host': '127.0.0.1',
'port': 4050,
'clientHost': 'pinus_test.trgame.cn',
'clientPort': 3050,
'frontend': true,
'args': '--inspect=10001'
}
],
'chat': [
{'id': 'chat-server-1', 'host': '127.0.0.1', 'port': 6050, 'args': '--inspect=10002'},
{'id': 'chat-server-2', 'host': '127.0.0.1', 'port': 6051, 'args': '--inspect=10004'},
{'id': 'chat-server-3', 'host': '127.0.0.1', 'port': 6052, 'args': '--inspect=10005'}
],
'gate': [
{
'id': 'gate-server-1',
'host': '127.0.0.1',
'clientHost': 'pinus_test.trgame.cn',
'clientPort': 3014,
'frontend': true,
'args': '--inspect=10003'
}
]
},
'production': {
'connector': [
{'id': 'connector-server-1', 'port': 4050, 'host': 'pinus_test.trgame.cn', 'clientPort': 3050, 'frontend': true},
{'id': 'connector-server-2', 'port': 4051, 'host': 'pinus_test.trgame.cn', 'clientPort': 3051, 'frontend': true},
{'id': 'connector-server-3', 'port': 4052, 'host': 'pinus_test.trgame.cn', 'clientPort': 3052, 'frontend': true}
],
'chat': [
{'id': 'chat-server-1', 'host': '127.0.0.1', 'port': 6050},
{'id': 'chat-server-2', 'host': '127.0.0.1', 'port': 6051},
{'id': 'chat-server-3', 'host': '127.0.0.1', 'port': 6052}
],
'gate': [
{'id': 'gate-server-1', 'host': '127.0.0.1', 'clientHost': 'pinus_test.trgame.cn', 'clientPort': 3014, 'frontend': true}
]
}
};

View File

@@ -0,0 +1,45 @@
module.exports = {
'development': {
'connector': [
{
'id': 'connector-server-1',
// 'host': '127.0.0.1',
'host': '39.101.222.20',
'port': 4050,
'clientHost': '39.101.222.20',
'clientPort': 3050,
'frontend': true,
'args': '--inspect=10001'
}
],
'chat': [
{'id': 'chat-server-1', 'host': '39.101.222.20', 'port': 6050, 'args': '--inspect=10002'},
{'id': 'chat-server-2', 'host': '39.100.68.60', 'port': 6051, 'args': '--inspect=10004'},
{'id': 'chat-server-3', 'host': '39.100.68.60', 'port': 6052, 'args': '--inspect=10005'}
],
'gate': [
{
'id': 'gate-server-1',
'host': '39.101.222.20',
'clientPort': 3014,
'frontend': true,
'args': '--inspect=10003'
}
]
},
'production': {
'connector': [
{'id': 'connector-server-1', 'host': '127.0.0.1', 'port': 4050, 'clientPort': 3050, 'frontend': true},
{'id': 'connector-server-2', 'host': '127.0.0.1', 'port': 4051, 'clientPort': 3051, 'frontend': true},
{'id': 'connector-server-3', 'host': '127.0.0.1', 'port': 4052, 'clientPort': 3052, 'frontend': true}
],
'chat': [
{'id': 'chat-server-1', 'host': '127.0.0.1', 'port': 6050},
{'id': 'chat-server-2', 'host': '127.0.0.1', 'port': 6051},
{'id': 'chat-server-3', 'host': '39.100.68.60', 'port': 6052}
],
'gate': [
{'id': 'gate-server-1', 'host': '127.0.0.1', 'clientPort': 3014, 'frontend': true}
]
}
};

View File

@@ -0,0 +1,20 @@
version: '3'
services:
game-server:
container_name: "zyz_game_server"
build: ../game-server
# depends_on:
# - "mongo"
# - "redis"
volumes:
- "$PWD/../game-server:/game-server"
ports:
- "3050:3050"
- "3051:3051"
- "3052:3052"
- "6050:6050"
- "6051:6051"
- "6052:6052"
- "3014:3014"
command: sh ./startGameServer.sh #使用 shell脚本启动游戏服务器

View File

@@ -0,0 +1,119 @@
/**
* Created by superzhan on 2018/3/19.
*
* 根据pomelo 的 servers.json 生成 pm2 启动文件
*/
//服务器端 工程代码的目录
var cwd='/game-server';
//项目的运行环境
var envType= 'production';
//配置文件的输出目录
var outputFilePath = './pomeloPm2Start.json';
var masterJsonFile = require('./config/master.ts');
var serversJosnFile = require('./config/servers.ts');
//模板数据
var processConfigType = {
"name" : "",
"script" : "./dist/app.js",
"args" : [] ,
"watch": false,
"out_file": "./logs/app.log",
"error_file": "./logs/err.log",
"cwd": "",
"merge_logs": true,
"exec_mode": "fork_mode",
};
//最后的结果数据
var resultJson={};
resultJson.apps=new Array();
var clone = function(origin) {
if(!origin) {
return;
}
var obj = {};
for(var f in origin) {
obj[f] = origin[f];
}
return obj;
};
//
var masterConfig = masterJsonFile[envType];
var serversConfig = serversJosnFile[envType];
//生成master 的配置
var pm2Master = clone( processConfigType );
pm2Master.name="master";
pm2Master.args = new Array();
pm2Master.args.push('serverType=master');
pm2Master.args.push('id='+masterConfig.id);
pm2Master.args.push('host='+masterConfig.host);
pm2Master.args.push('port='+masterConfig.port);
pm2Master.args.push('env='+envType);
pm2Master.args.push('mode=stand-alone');
pm2Master.cwd= cwd;
pm2Master.out_file = './logs/'+masterConfig.id+"_app.log";
pm2Master.error_file='./logs/'+masterConfig.id+'_error.log';
resultJson.apps.push(pm2Master);
//生成当个服务器的配置
for(serverType in serversConfig)
{
var servers = serversConfig[serverType];
for(var i=0;i<servers.length;++i)
{
var singleServer= servers[i];
var appPm2Config = clone(processConfigType);
appPm2Config.name=singleServer.id;
appPm2Config.args= new Array();
appPm2Config.args.push('env='+envType);
appPm2Config.args.push('id='+singleServer.id);
appPm2Config.args.push('port='+singleServer.port);
appPm2Config.args.push('host='+singleServer.host);
appPm2Config.args.push('serverType='+serverType);
if(singleServer.frontend !=null)
{
appPm2Config.args.push('frontend='+ singleServer.frontend);
appPm2Config.args.push('clientPort='+singleServer.clientPort);
}
appPm2Config.cwd= cwd;
appPm2Config.out_file = './logs/'+ singleServer.id+'_app.log';
appPm2Config.error_file = './logs/'+singleServer.id+'_error.log';
resultJson.apps.push(appPm2Config);
}
}
//生成结果数据
var resultFileStr = JSON.stringify(resultJson);
//console.log(resultFileStr);
var fs = require('fs');
fs.writeFile(outputFilePath, resultFileStr, function (err) {
if (err) {
console.log(err);
} else {
console.log('finish genereate Server pm2 config');
}
});

1726
game-server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
game-server/package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "pinus-sample-websocket-chat-game-server-ts-run",
"version": "1.4.9",
"private": false,
"main": "./dist/app",
"scripts": {
"build": "tsc",
"start": "yarn run build && cd dist && node app",
"dev": "node tsrun.js",
"test": "yarn run build",
"cov": "nyc mocha",
"ci": "yarn run test",
"gen-api-ref": "node ../../../node_modules/typedoc/bin/typedoc --mode file --hideGenerator --excludeExternals --ignoreCompilerErrors --out ../../../run/site/public/api-reference/pinus-loader lib/"
},
"dependencies": {
"@typegoose/typegoose": "^7.3.0",
"@types/bluebird": "^3.5.19",
"@types/crc": "^3.4.0",
"@types/node": "8.10.54",
"@types/redis": "^2.8.25",
"bluebird": "^3.5.1",
"crc": "^3.5.0",
"mongoose": "^5.9.27",
"pinus": "^1.4.9",
"pinus-robot": "^1.4.9",
"pinus-robot-plugin": "^1.4.9",
"redis": "^3.0.2",
"reflect-metadata": "^0.1.10",
"source-map-support": "^0.5.0",
"ts-node": "^8.2.0"
},
"devDependencies": {
"@types/mongoose": "^5.7.35",
"tslint": "^5.9.1",
"typescript": "^3.9.7"
}
}

View File

@@ -0,0 +1,149 @@
{
"apps": [
{
"name": "master",
"script": "app.ts",
"args": [
"serverType=master",
"id=master-server-1",
"host=127.0.0.1",
"port=3005",
"env=production",
"mode=stand-alone"
],
"watch": false,
"out_file": "./logs/master-server-1_app.log",
"error_file": "./logs/master-server-1_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "connector-server-1",
"script": "app.ts",
"args": [
"env=production",
"id=connector-server-1",
"port=4050",
"host=127.0.0.1",
"serverType=connector",
"frontend=true",
"clientPort=3050"
],
"watch": false,
"out_file": "./logs/connector-server-1_app.log",
"error_file": "./logs/connector-server-1_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "connector-server-2",
"script": "app.ts",
"args": [
"env=production",
"id=connector-server-2",
"port=4051",
"host=127.0.0.1",
"serverType=connector",
"frontend=true",
"clientPort=3051"
],
"watch": false,
"out_file": "./logs/connector-server-2_app.log",
"error_file": "./logs/connector-server-2_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "connector-server-3",
"script": "app.ts",
"args": [
"env=production",
"id=connector-server-3",
"port=4052",
"host=127.0.0.1",
"serverType=connector",
"frontend=true",
"clientPort=3052"
],
"watch": false,
"out_file": "./logs/connector-server-3_app.log",
"error_file": "./logs/connector-server-3_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "chat-server-1",
"script": "app.ts",
"args": [
"env=production",
"id=chat-server-1",
"port=6050",
"host=127.0.0.1",
"serverType=chat"
],
"watch": false,
"out_file": "./logs/chat-server-1_app.log",
"error_file": "./logs/chat-server-1_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "chat-server-2",
"script": "app.ts",
"args": [
"env=production",
"id=chat-server-2",
"port=6051",
"host=127.0.0.1",
"serverType=chat"
],
"watch": false,
"out_file": "./logs/chat-server-2_app.log",
"error_file": "./logs/chat-server-2_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "chat-server-3",
"script": "app.ts",
"args": [
"env=production",
"id=chat-server-3",
"port=6052",
"host=127.0.0.1",
"serverType=chat"
],
"watch": false,
"out_file": "./logs/chat-server-3_app.log",
"error_file": "./logs/chat-server-3_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
},
{
"name": "gate-server-1",
"script": "app.ts",
"args": [
"env=production",
"id=gate-server-1",
"port=undefined",
"host=127.0.0.1",
"serverType=gate",
"frontend=true",
"clientPort=3014"
],
"watch": false,
"out_file": "./logs/gate-server-1_app.log",
"error_file": "./logs/gate-server-1_error.log",
"cwd": "/game-server",
"merge_logs": true,
"exec_mode": "fork_mode"
}
]
}

40
game-server/preload.ts Normal file
View File

@@ -0,0 +1,40 @@
import { Promise } from 'bluebird';
// 支持注解
import 'reflect-metadata';
import { pinus } from 'pinus';
/**
* 替换全局Promise
* 自动解析sourcemap
* 捕获全局错误
*/
export function preload() {
// 使用bluebird输出完整的promise调用链
global.Promise = Promise;
// 开启长堆栈
Promise.config({
// Enable warnings
warnings: true,
// Enable long stack traces
longStackTraces: true,
// Enable cancellation
cancellation: true,
// Enable monitoring
monitoring: true
});
// 自动解析ts的sourcemap
require('source-map-support').install({
handleUncaughtExceptions: false
});
// 捕获普通异常
process.on('uncaughtException', function (err) {
console.error(pinus.app.getServerId(), 'uncaughtException Caught exception: ', err);
});
// 捕获async异常
process.on('unhandledRejection', (reason: any, p) => {
console.error(pinus.app.getServerId(), 'Caught Unhandled Rejection at:', p, 'reason:', reason);
});
}

2
game-server/pushdocker.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
rsync -av --include '.babelrc' --include '.eslintrc.js' --exclude '.*' --exclude 'node_modules' --exclude 'bower_components' --exclude 'dist' --progress --inplace --no-owner --no-group --rsh='ssh -p22' . root@ltctest:/root/zyz/game-server/

View File

@@ -0,0 +1,2 @@
#!/bin/sh
rsync -av --include '.babelrc' --include '.eslintrc.js' --exclude '.*' --exclude 'node_modules' --exclude 'bower_components' --exclude 'dist' --progress --inplace --no-owner --no-group --rsh='ssh -p22' . root@zyz_test:/root/zyz/game-server/

View File

@@ -0,0 +1,97 @@
import { Actor } from 'pinus-robot';
import { PinusWSClient, PinusWSClientEvent} from 'pinus-robot-plugin';
export class Robot {
constructor(private actor: Actor) {
}
openid = String(Math.round(Math.random() * 1000));
pinusClient = new PinusWSClient();
public connectGate(): void {
let host = '127.0.0.1';
let port = '3014';
this.pinusClient.on(PinusWSClientEvent.EVENT_IO_ERROR, (event) => {
// 错误处理
console.error('error', event);
});
this.pinusClient.on(PinusWSClientEvent.EVENT_CLOSE, function(event) {
// 关闭处理
console.error('close', event);
});
this.pinusClient.on(PinusWSClientEvent.EVENT_HEART_BEAT_TIMEOUT, function(event) {
// 心跳timeout
console.error('heart beat timeout', event);
});
this.pinusClient.on(PinusWSClientEvent.EVENT_KICK, function(event) {
// 踢出
console.error('kick', event);
});
// this.actor.emit("incr" , "gateConnReq");
this.actor.emit('start' , 'gateConn' , this.actor.id);
this.pinusClient.init({
host: host,
port: port
}, () => {
this.actor.emit('end' , 'gateConn' , this.actor.id);
// 连接成功执行函数
console.log('gate连接成功');
this.gateQuery();
});
}
gateQuery() {
// this.actor.emit("incr" , "gateQueryReq");
this.actor.emit('start' , 'gateQuery' , this.actor.id);
this.pinusClient.request('gate.gateHandler.queryEntry', {uid: this.openid} , (result: {code: number , host: string , port: number}) => {
// 消息回调
// console.log("gate返回",JSON.stringify(result));
this.actor.emit('end' , 'gateQuery' , this.actor.id);
this.pinusClient.disconnect();
this.connectToConnector(result);
});
}
connectToConnector(result: {host: string , port: number}) {
// this.actor.emit("incr" , "loginConnReq");
this.actor.emit('start' , 'loginConn' , this.actor.id);
this.pinusClient.init({
host: result.host,
port: result.port
}, () => {
this.actor.emit('end' , 'loginConn' , this.actor.id);
// 连接成功执行函数
console.log('connector连接成功');
this.loginQuery({rid: this.actor.id.toString() , username : this.actor.id.toString()});
});
}
loginQuery(result: {rid: string, username: string}) {
// this.actor.emit("incr" , "loginQueryReq");
this.actor.emit('start' , 'loginQuery' , this.actor.id);
this.pinusClient.request('connector.entryHandler.enter', result , (ret: any) => {
// 消息回调
this.actor.emit('end' , 'loginQuery' , this.actor.id);
console.log('connector返回', JSON.stringify(result));
setTimeout(() =>
this.loginQuery(result) , Math.random() * 5000 + 1000);
});
}
}
export default function(actor: Actor) {
let client = new Robot(actor);
client.connectGate();
return client;
}

View File

@@ -0,0 +1,5 @@
#/bin/bash
cnpm install -d #安装依赖库
npm run build
node generatePm2Config.js #使用pm2来做进程管理生成进程配置文件
pm2-runtime pomeloPm2Start.json #pm2 启动游戏服务器

35
game-server/tsconfig.json Normal file
View File

@@ -0,0 +1,35 @@
{
"compilerOptions": {
// types option has been previously configured
"types": [
// add node as an option
"node"
],
"module": "commonjs", //指定生成哪个模块系统代码
"target": "es2017",
"lib": [
"es2015",
"es2016",
"esnext.asynciterable"
],
"noImplicitAny": false, //暂时关闭在表达式和声明上有隐含的'any'类型时报错。
"noImplicitThis": true,
"inlineSourceMap": true, //用于debug
"rootDirs": ["."], //仅用来控制输出的目录结构--outDir。
"outDir":"./dist", //重定向输出目录。
"experimentalDecorators":true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"watch":false //在监视模式下运行编译器。会监视输出文件,在它们改变时重新编译。
},
"include":[
"./app/**/*.ts",
"./config/**/*.ts",
"./app.ts",
"./preload.ts"
],
"exclude": [
"./dist/**/*.*"
]
}

34
game-server/tsrun.js Normal file
View File

@@ -0,0 +1,34 @@
require('ts-node/register');
//
// 如果堆栈信息不准确.取消注释下面的代码应该可以解决.
// 参考 ts-node 下错误堆栈问题排查小记: https://zhuanlan.zhihu.com/p/43181384
//
/*
const sourceMapSupport = require('source-map-support');
const cacheMap = {};
const extensions = ['.ts', '.tsx'];
sourceMapSupport.install({
environment: 'node',
retrieveFile: function (path) {
// 根据路径找缓存的编译后的代码
return cacheMap[path];
}
});
extensions.forEach(ext => {
const originalExtension = require.extensions[ext];
require.extensions[ext] = (module, filePath) => {
const originalCompile = module._compile;
module._compile = function(code, filePath) {
// 缓存编译后的代码
cacheMap[filePath] = code;
return originalCompile.call(this, code, filePath);
};
return originalExtension(module, filePath);
};
})
*/
require('./app');

4
npm-install.bat Normal file
View File

@@ -0,0 +1,4 @@
::npm-install.bat
@echo off
::install web server dependencies && game server dependencies
cd web-server && npm install -d && cd .. && cd game-server && npm install -d

5
npm-install.sh Normal file
View File

@@ -0,0 +1,5 @@
cd ./game-server && npm install -d
echo '============ game-server npm installed ============'
cd ..
cd ./web-server && npm install -d
echo '============ web-server npm installed ============'