강동현

방 안에서 지속적으로 핑을 보내 유저의 연결 체크

......@@ -79,6 +79,9 @@ export class ServerInboundMessageRecordMap {
x: Number,
y: Number,
});
// 핑을 보냅니다.
ping = Record({});
}
type ServerInboundMessageMap = {
......@@ -164,6 +167,9 @@ interface ServerOutboundMessageMap {
x: number;
y: number;
};
// 핑을 보냅니다.
ping: {};
}
export interface RawMessage {
......
......@@ -26,6 +26,7 @@ export class Connection {
this.roomManager = roomManager;
socket.setHandler((raw) => this.handleRaw(raw));
socket.setDisconnectHandler(() => this.handleDisconnect());
socket.setPingHandler(() => this.handlePing());
}
public send<T extends ServerOutboundMessageKey>(
......@@ -38,6 +39,10 @@ export class Connection {
});
}
public sendPing() {
this.socket.sendPing();
}
public handleRaw(raw: RawMessage): ServerResponse<any> {
if (!raw || !raw.message || !raw.type) {
return { ok: false };
......@@ -93,4 +98,10 @@ export class Connection {
public handleDisconnect(): void {
this.user?.disconnected();
}
public handlePing(): void {
if (this.user) {
this.user?.room?.pong(this.user);
}
}
}
......
......@@ -4,7 +4,9 @@ import { RawMessage, ServerResponse } from "../../common";
export interface SocketWrapper {
setHandler: (listener: (raw: RawMessage) => ServerResponse<any>) => void;
setDisconnectHandler: (listener: () => void) => void;
setPingHandler: (listener: () => void) => void;
send: (raw: RawMessage) => void;
sendPing: () => void;
}
export class SocketIoWrapper implements SocketWrapper {
......@@ -26,7 +28,17 @@ export class SocketIoWrapper implements SocketWrapper {
});
}
public setPingHandler(listener: () => void) {
this.socketIo.on("ping", () => {
listener();
});
}
public send(raw: RawMessage) {
this.socketIo.emit("msg", raw);
}
public sendPing() {
this.socketIo.emit("ping", {});
}
}
......
......@@ -29,6 +29,8 @@ export class Room {
public handler: MessageHandler;
public pingTimeout: NodeJS.Timeout;
constructor(
roomManager: RoomManager,
name: string,
......@@ -95,6 +97,8 @@ export class Room {
if (this.admin) {
this.connect(this.admin);
}
this.pingTimeout = setInterval(() => this.ping(), 1000);
}
public connect(user: User): void {
......@@ -114,6 +118,8 @@ export class Room {
this.users.push(user);
user.room = this;
user.lastPong = Date.now();
}
public disconnect(user: User): void {
......@@ -143,6 +149,23 @@ export class Room {
}
}
public ping(): void {
this.users.forEach((u) => {
u.connection.sendPing();
if (Date.now() - u.lastPong > 2000) {
this.disconnect(u);
}
});
}
public pong(user: User) {
if (!this.users.includes(user)) {
return { ok: false };
}
user.lastPong = Date.now();
return { ok: true };
}
public setAdmin(user: User) {
if (this.users.includes(user)) {
const prevAdmin = this.admin;
......@@ -272,6 +295,7 @@ export class Room {
this.users.forEach((u) => this.disconnect(u));
this.closed = true;
this.roomManager.delete(this.uuid);
clearInterval(this.pingTimeout);
}
}
}
......
......@@ -15,6 +15,8 @@ export class User {
public handler: MessageHandler;
public lastPong: number = 0; // 방에서 마지막으로 핑을 받은 시각
constructor(nickname: string, connection: Connection) {
this.username = uuidv4();
this.nickname = nickname;
......