이제 채팅방에서 사용되는 기본 기능을 구현하는 마지막 포스팅입니다. 이번에는 공개 방과 비공개 방을 구분하고, 공개 방에 관한 정보를 업데이트 해보도록 하겠습니다.
공개 방과 비공개 방의 개념
사실 공개 방과 비공개 방의 개념이 낯선 것일 수도 있는데요. 왜냐하면 이제까지는 이를 구분하지 않고 방을 만들고 여기에 참여했기 때문입니다.
앞서 채팅 서비스를 구현하며 소켓의 id
로 사용자를 구분했던 것을 기억하시나요? 해당 id
는 소켓에 접속하는 사용자에게 부여되는 고유한 아이디로 접속과 실은 동시에 자신만의 프라이빗한 비공개 방에 접속하게 되는 것과 같은 이치입니다. 반면, 우리가 공개 방이라고 정의하는 것에는 이러한 소켓 아이디가 존재하지 않습니다.
공개 방과 비공개 방 구분하기
그렇다면 공개 방과 비공개 방을 어떻게 구분할 수 있을까요? 바로 아래와 같이 rooms
과 sids
속성을 통해 이를 구분할 수 있습니다. 앞서 파악한 것처럼 모든 소켓은 기본적으로 sid
를 갖지만, 공개 방은 비공개 방에 포함되어 있는 sid
가 없습니다. 이를 활용하여 다음과 같이 공개 방의 키를 추출할 수 있습니다.
// server.js
// 공개 방 찾기
function updatePublicRoom() {
const rooms = ioServer.sockets.adapter.rooms;
const sids = ioServer.sockets.adapter.sids;
let publicRooms = [];
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
}
위 코드는 아래와 같이 리팩토링할 수 있습니다. 동일하게 공개 방의 키를 추출하는 로직입니다.
// server.js
// 공개 채팅 방 찾기
function updatePublicRoom() {
const {
sockets: {
adapter: { rooms, sids },
},
} = ioServer;
let publicRooms = [];
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
}
공개 채팅 방 알림 메시지 보내기
이제 해당 공개 채팅 방에 관한 정보를 서버 전체에 알림 메시지로 전달해보겠습니다. home.pug에서 다음과 같이 화면을 업데이트 해줍니다.
// home.pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
link(rel="stylesheet", href="https://unpkg.com/mvp.css")
title Zoom
body
header
h1 Let's Chat Together!
main
div#openPublicRoom
h2 Available Rooms
ul
li 🚪
div#nickname
form
h2 Set a Nickname
input(type="text", placeholder="Please enter your nicknamee", required, autofocus)
button Save
div#room
form
h2 Enter a Room
input(type="text", placeholder="Please type Room name", required)
button Enter the Room
div#chat
h2 Room Name
ul
form
input(type="text", placeholder="You can type here", required)
button Send
script(src="/socket.io/socket.io.js")
script(src="/public/js/app.js")
방 변경 사항을 업데이트 할 프론트 로직을 추가해줍니다. 아래는 프론트 app.js의 현재까지의 최종 코드입니다.
// app.js
const socket = io();
const room = document.querySelector("#room");
const roomForm = room.querySelector("form");
const chat = document.querySelector("#chat");
const chatForm = chat.querySelector("form");
const nicknameSet = document.querySelector("#nickname");
const nicknameForm = nickname.querySelector("form");
const openPublicRoom = document.querySelector("#openPublicRoom");
const openPublicRoomList = openPublicRoom.querySelector("ul");
function sendMessage(message) {
const ul = chat.querySelector("ul");
const li = document.createElement("li");
li.innerText = message;
ul.appendChild(li);
}
function handleSendMessage(event) {
event.preventDefault();
const message = chatForm.querySelector("input");
socket.emit("message", message.value, sendMessage);
message.value = "";
}
room.hidden = true;
chat.hidden = true;
openPublicRoom.hidden = true;
function showRoom(roomName) {
room.hidden = true;
nicknameSet.hidden = true;
chat.hidden = false;
openPublicRoom.hidden = false;
const roomNameHeader = chat.querySelector("h2");
roomNameHeader.innerText = `Room: ${roomName}`;
chatForm.querySelector("input").focus();
chatForm.addEventListener("submit", handleSendMessage);
}
function handleRoomName(event) {
event.preventDefault();
const input = roomForm.querySelector("input");
const roomName = input.value;
socket.emit("room", roomName, showRoom);
}
function saveNickname(nickname) {
room.hidden = false;
nicknameSet.hidden = true;
chat.hidden = true;
roomForm.querySelector("input").focus();
roomForm.addEventListener("submit", handleRoomName);
}
function handleNickName(event) {
event.preventDefault();
const input = nicknameForm.querySelector("input");
const nickname = input.value;
socket.emit("nickname", nickname, saveNickname);
}
nicknameForm.addEventListener("submit", handleNickName);
socket.on("greeting", (nickname) => {
sendMessage(`${nickname} has joined!`);
});
socket.on("goodbye", (nickname) => {
sendMessage(`${nickname} has left!`);
});
socket.on("sendMessage", sendMessage);
// 방 변경 사항 받기
socket.on("roomUpdate", (rooms) => {
const li = openPublicRoomList.querySelector("li");
li.innerText = `🚪 ${rooms}`;
});
다음으로 server.js에서 방 업데이트 사항을 전송할 수 있도록 설정해줍니다. 아래는 백엔드 측의 현재까지의 최종 코드입니다.
// server.js
import http from "http";
import { Server } from "socket.io";
import express from "express";
const app = express();
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (_, res) => res.render("home"));
app.get("/*", (_, res) => res.redirect("/"));
const httpServer = http.createServer(app);
const ioServer = new Server(httpServer);
// 공개 방 업데이트 로직
function updatePublicRoom() {
const {
sockets: {
adapter: { rooms, sids },
},
} = ioServer;
let publicRooms = [];
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
return publicRooms;
}
ioServer.on("connection", (socket) => {
socket.onAny((event) => {
console.log(`Socket Event: ${event}`);
});
socket.on("room", (roomName, showRoom) => {
socket.join(roomName);
showRoom(roomName);
console.log(socket.rooms);
socket.to(roomName).emit("greeting", socket["nickname"]);
ioServer.sockets.emit("roomUpdate", updatePublicRoom()); // 입장하며 방에 업데이트하기
socket.on("message", (message, sendMessage) => {
message = `${socket["nickname"]}: ${message}`;
socket.to(roomName).emit("sendMessage", message, sendMessage(message));
});
});
socket.on("disconnecting", () => {
socket.rooms.forEach((room) =>
socket.to(room).emit("goodbye", socket["nickname"])
);
ioServer.sockets.emit("roomUpdate", updatePublicRoom()); // 퇴장하며 방에 업데이트하기
});
socket.on("nickname", (nickname, saveNickname) => {
socket["nickname"] = nickname;
console.log(`설정한 닉네임: ${socket["nickname"]}`);
saveNickname(nickname);
});
});
httpServer.listen(8000);
최종적으로 구현한 채팅 서비스의 모습은 아래와 같습니다!
'개발 > Projects' 카테고리의 다른 글
[WebSocket/Socket IO] 채팅 서비스 구현 10. 관리자 패널 추가하기 (0) | 2022.12.07 |
---|---|
[WebSocket/Socket IO] 채팅 서비스 구현 8. 닉네임 설정하기 (0) | 2022.12.07 |
[WebSocket/Socket IO] 채팅 서비스 구현 7. 메시지 전송 및 수신 (0) | 2022.12.06 |