ssocket
Version:
仿 Koa 中间件控制的 WebSocket 服务
513 lines (512 loc) • 20.5 kB
JavaScript
"use strict";
/*
* @Author: Summer
* @LastEditors: Summer
* @Description:
* @Date: 2021-04-26 16:51:46 +0800
* @LastEditTime: 2021-08-02 18:22:38 +0800
* @FilePath: /ssocket/src/adapter.ts
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Adapter = void 0;
const amqplib_1 = require("amqplib");
const code_1 = __importDefault(require("./code"));
const ioredis_1 = __importDefault(require("ioredis"));
const events_1 = require("events");
const logger_1 = __importDefault(require("./logger"));
const os_1 = __importDefault(require("os"));
const utils_1 = require("./utils");
const msgpack = require("notepack.io");
const logger = logger_1.default("adapter");
const REDIS_SURVIVAL_KEY = `ssocket-survival:${os_1.default.hostname()}:${process.pid}`;
let __mqconnect;
let __mqsub;
let __mqpub;
let __redisdata;
ioredis_1.default.prototype.keys = function (pattern) {
return __awaiter(this, void 0, void 0, function* () {
let cursor = 0;
let list = [];
do {
let res = yield this.scan(cursor, "match", pattern, "count", 2000);
cursor = +res[0];
list = list.concat(res[1]);
} while (cursor != 0);
return list;
});
};
var RequestMethod;
(function (RequestMethod) {
RequestMethod[RequestMethod["join"] = 0] = "join";
RequestMethod[RequestMethod["leave"] = 1] = "leave";
RequestMethod[RequestMethod["broadcast"] = 2] = "broadcast";
RequestMethod[RequestMethod["getRoomall"] = 3] = "getRoomall";
RequestMethod[RequestMethod["getClientidByroom"] = 4] = "getClientidByroom";
RequestMethod[RequestMethod["getRoomidByid"] = 5] = "getRoomidByid";
////////////////////
RequestMethod[RequestMethod["response"] = 6] = "response";
RequestMethod[RequestMethod["checkChannel"] = 7] = "checkChannel";
})(RequestMethod || (RequestMethod = {}));
var BroadcastType;
(function (BroadcastType) {
BroadcastType[BroadcastType["room"] = 0] = "room";
BroadcastType[BroadcastType["all"] = 1] = "all";
BroadcastType[BroadcastType["socket"] = 2] = "socket";
})(BroadcastType || (BroadcastType = {}));
class Adapter extends events_1.EventEmitter {
constructor(opt) {
var _a;
super();
this.opt = opt;
this.clients = new Map();
this.rooms = new Map();
this.client2rooms = new Map();
this.requests = new Map();
this.msgbuffers = [];
this.survivalid = 0;
/**检查通道可用性 */
this.checkchannelid = 0;
this.ispublish = false;
this.uid = utils_1.id24();
this.requestsTimeout = ((_a = this.opt) === null || _a === void 0 ? void 0 : _a.requestsTimeout) || 5000;
this.channel = `${(this.opt.key || "key")}-ssocket-adapter-message`;
this.cluster = Boolean(this.opt.redis && this.opt.mqurl);
this.init();
}
init() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this.cluster) {
this.ispublish = true;
clearInterval(this.survivalid);
clearTimeout(this.checkchannelid);
try {
if (__redisdata)
__redisdata.disconnect();
}
catch (error) {
console.log(REDIS_SURVIVAL_KEY, error);
}
try {
if (__mqsub)
__mqsub.close();
}
catch (error) {
console.log(REDIS_SURVIVAL_KEY, error);
}
try {
if (__mqpub)
__mqpub.close();
}
catch (error) {
console.log(REDIS_SURVIVAL_KEY, error);
}
try {
if (__mqconnect) {
if (__mqconnect.connection.heartbeater)
__mqconnect.connection.heartbeater.clear();
__mqconnect.close();
}
}
catch (error) {
console.log(REDIS_SURVIVAL_KEY, error);
}
__redisdata = __mqsub = __mqpub = __mqconnect = null;
__redisdata = new ioredis_1.default(this.opt.redis);
if ((_a = this.opt.redis) === null || _a === void 0 ? void 0 : _a.password)
__redisdata.auth(this.opt.redis.password).then(_ => logger("redis", "Password verification succeeded"));
__mqconnect = yield amqplib_1.connect(this.opt.mqurl + "");
__mqsub = yield __mqconnect.createChannel();
yield __mqsub.assertExchange(this.channel, "fanout", { durable: false });
let qok = yield __mqsub.assertQueue("", { exclusive: false, autoDelete: true, durable: false });
logger("QOK", qok);
yield __mqsub.bindQueue(qok.queue, this.channel, "");
yield __mqsub.consume(qok.queue, this.onmessage.bind(this), { noAck: true });
__mqpub = yield __mqconnect.createChannel();
yield __mqpub.assertExchange(this.channel, "fanout", { durable: false });
this.survivalid = setInterval(this.survivalHeartbeat.bind(this), 1000);
this.ispublish = false;
this.sendCheckChannel();
console.log(`[${REDIS_SURVIVAL_KEY}]["建立 MQ 消息通道完成", ${JSON.stringify(qok)}]`);
}
});
}
checkChannel() {
console.log(`[${REDIS_SURVIVAL_KEY}]["MQ 消息通道超时响应,开始重新建立连接"]`);
this.init();
}
sendCheckChannel() {
this.msgbuffers.unshift(msgpack.encode([RequestMethod.checkChannel, this.uid]));
this.checkchannelid = setTimeout(this.checkChannel.bind(this), this.requestsTimeout);
this.startPublish();
}
survivalHeartbeat() {
if (__redisdata) {
__redisdata.set(REDIS_SURVIVAL_KEY, 1, "ex", 2);
}
}
/**获取所有存活主机的数量 */
allSurvivalCount() {
return __awaiter(this, void 0, void 0, function* () {
let keys = yield __redisdata.keys(`ssocket-survival:*`);
return keys.length;
});
}
startPublish() {
if (this.ispublish === false && __mqpub) {
let msg = null;
try {
this.ispublish = true;
while (msg = this.msgbuffers.pop()) {
__mqpub.publish(this.channel, "", msg);
}
this.ispublish = false;
}
catch (error) {
msg && this.msgbuffers.unshift(msg);
this.init();
console.log(REDIS_SURVIVAL_KEY, error);
}
}
}
publish(msg) {
return __awaiter(this, void 0, void 0, function* () {
this.msgbuffers.push(msg);
this.startPublish();
});
}
onmessage(msg) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (msg && msg.content) {
try {
const args = msgpack.decode(msg.content);
const type = args.shift();
const uid = args.shift();
const requestid = args.shift();
switch (type) {
case RequestMethod.response: {
if (this.uid === uid) {
clearTimeout(this.checkchannelid);
setTimeout(this.sendCheckChannel.bind(this), 1000);
}
break;
}
case RequestMethod.response: {
if (this.uid === uid) {
(_a = this.requests.get(requestid)) === null || _a === void 0 ? void 0 : _a.call(this, args.shift());
}
break;
}
case RequestMethod.getRoomall: {
this.publish(msgpack.encode([RequestMethod.response, uid, requestid, [...this.rooms.keys()]]));
break;
}
case RequestMethod.getClientidByroom: {
this.publish(msgpack.encode([RequestMethod.response, uid, requestid, [...(this.rooms.get(args.shift()) || [])]]));
break;
}
case RequestMethod.getRoomidByid: {
this.publish(msgpack.encode([RequestMethod.response, uid, requestid, [...(this.client2rooms.get(args.shift()) || [])]]));
break;
}
case RequestMethod.broadcast: {
switch (args.shift()) {
case BroadcastType.room: {
let room = args.shift();
let [event, data, status, msg] = args.shift();
for (let id of this.rooms.get(room) || []) {
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
}
break;
}
case BroadcastType.socket: {
let id = args.shift();
let [event, data, status, msg] = args.shift();
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
break;
}
case BroadcastType.all: {
let [event, data, status, msg] = args.shift();
for (let id of this.clients.keys() || []) {
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
}
break;
}
default:
break;
}
break;
}
default:
}
}
catch (error) {
this.emit("error", error);
}
}
});
}
emitSocketMessage(id, event, data, status, msg) {
let client = this.clients.get(id);
if (client) {
client.response(event, status, msg, 0, data);
}
else {
this.delete(id);
}
}
/**
* 获取一个 Socket 客户端对象
* @param id
*/
get(id) {
return this.clients.get(id);
}
/**
* 增加一个 Socket 连接
* @param {*} id
* @param {*} socket
*/
set(socket) {
logger("set", socket.getid());
this.clients.set(socket.getid(), socket);
return socket;
}
/**
* 删除一个 Socket 连接
* @param {*} id
*/
delete(id) {
var _a;
logger("delete", id);
this.clients.delete(id);
for (let roomid of this.client2rooms.get(id) || []) {
(_a = this.rooms.get(roomid)) === null || _a === void 0 ? void 0 : _a.delete(id);
}
this.client2rooms.delete(id);
}
/**
* 加入房间
* @param id
* @param room
*/
join(id, room) {
var _a, _b;
logger("join", id, room);
room = String(room);
id = String(id);
if (!this.rooms.has(room))
this.rooms.set(room, new Set());
if (!this.client2rooms.has(id))
this.client2rooms.set(id, new Set());
(_a = this.client2rooms.get(id)) === null || _a === void 0 ? void 0 : _a.add(room);
(_b = this.rooms.get(room)) === null || _b === void 0 ? void 0 : _b.add(id);
}
/**
* 离开房间
* @param id
* @param room
*/
leave(id, room) {
var _a, _b;
logger("leave", id, room);
room = String(room);
id = String(id);
(_a = this.client2rooms.get(id)) === null || _a === void 0 ? void 0 : _a.delete(room);
(_b = this.rooms.get(room)) === null || _b === void 0 ? void 0 : _b.delete(id);
}
/**
* 获取所有的房间号
*/
getRoomall() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (!this.cluster) {
return resolve([...this.rooms.keys()]);
}
let requestoutid = setTimeout(_ => reject("Waiting for MQ to return [getRoomall] message timed out"), this.requestsTimeout);
let requestid = utils_1.id24();
let servercount = yield this.allSurvivalCount();
let result = [];
let callback = function (rooms) {
if (--servercount > 0) {
result = result.concat(rooms);
}
else {
this.requests.delete(requestid);
clearInterval(requestoutid);
result = result.concat(rooms);
resolve(result);
}
};
let msg = msgpack.encode([RequestMethod.getRoomall, this.uid, requestid]);
this.publish(msg);
this.requests.set(requestid, callback);
}));
});
}
/**
* 根据房间号获取所有的客户端ID
* @param room
*/
getClientidByroom(room) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
room = String(room);
if (!this.cluster) {
return resolve([...this.rooms.get(room) || []]);
}
let requestoutid = setTimeout(_ => reject("Waiting for MQ to return [getClientidByroom] message timed out"), this.requestsTimeout);
let requestid = utils_1.id24();
let servercount = yield this.allSurvivalCount();
let result = [];
let callback = function (sockets) {
if (--servercount > 0) {
result = result.concat(sockets);
}
else {
this.requests.delete(requestid);
clearInterval(requestoutid);
result = result.concat(sockets);
resolve(result);
}
};
let msg = msgpack.encode([RequestMethod.getClientidByroom, this.uid, requestid, room]);
this.publish(msg);
this.requests.set(requestid, callback);
}));
});
}
/**
* 根据 客户端ID 获取所在的所有房间ID
* @param id
*/
getRoomidByid(id) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
id = String(id);
if (!this.cluster) {
return resolve([...this.client2rooms.get(id) || []]);
}
let requestoutid = setTimeout(_ => reject("Waiting for MQ to return [getRoomidByid] message timed out"), this.requestsTimeout);
let requestid = utils_1.id24();
let servercount = yield this.allSurvivalCount();
let result = [];
let callback = function (rooms) {
if (--servercount > 0) {
result = result.concat(rooms);
}
else {
this.requests.delete(requestid);
clearInterval(requestoutid);
result = result.concat(rooms);
resolve(result);
}
};
let msg = msgpack.encode([RequestMethod.getRoomidByid, this.uid, requestid, id]);
this.publish(msg);
this.requests.set(requestid, callback);
}));
});
}
/**
* 判断客户端是否存在啊某个房间
* @param id
* @param room
*/
hasRoom(id, room) {
return __awaiter(this, void 0, void 0, function* () {
id = String(id);
room = String(room);
let rooms = yield this.getRoomidByid(id);
return rooms.includes(room);
});
}
/**
* 获取所有的房间总数
*/
getAllRoomcount() {
return __awaiter(this, void 0, void 0, function* () {
let rooms = yield this.getRoomall();
return rooms.length;
});
}
/**
* 获取房间内人员数量
* @param room
*/
getRoomsize(room) {
return __awaiter(this, void 0, void 0, function* () {
let clients = yield this.getClientidByroom(room);
return clients.length;
});
}
/**
* 发送房间消息
* @param room
* @param event
* @param data
* @param status
* @param msg
*/
sendRoomMessage(room, event, data, status = code_1.default[200][0], msg = code_1.default[200][1]) {
return __awaiter(this, void 0, void 0, function* () {
room = String(room);
if (!this.cluster) {
for (let id of this.rooms.get(room) || []) {
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
}
return;
}
this.publish(msgpack.encode([RequestMethod.broadcast, this.uid, 0, BroadcastType.room, room, [event, data, status, msg]]));
});
}
/**
* 发送广播消息
* @param event
* @param data
* @param status
* @param msg
*/
sendBroadcast(event, data, status = code_1.default[200][0], msg = code_1.default[200][1]) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.cluster) {
for (let id of this.clients.keys() || []) {
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
}
return;
}
this.publish(msgpack.encode([RequestMethod.broadcast, this.uid, 0, BroadcastType.all, [event, data, status, msg]]));
});
}
/**
* 发送终端消息
* @param {*} id Socket sid
* @param {*} type 消息类型
* @param {*} data
*/
sendSocketMessage(id, event, data, status = code_1.default[200][0], msg = code_1.default[200][1]) {
return __awaiter(this, void 0, void 0, function* () {
id = String(id);
if (!this.cluster) {
this.emitSocketMessage.apply(this, [id, event, data, status, msg]);
return;
}
this.publish(msgpack.encode([RequestMethod.broadcast, this.uid, 0, BroadcastType.socket, id, [event, data, status, msg]]));
});
}
}
exports.Adapter = Adapter;