웹소켓 4장에서는 websocket만을 사용해 여러 채팅방을 구현하였다. 이번에는 socket.io를 이용하여 쉽게 구현해보자.
이번에는 서버를 모듈화하여 기능을 분리하고, 알아보기 좋게 하였다.
1. 클라이언트 페이지
페이지 이름 | 설명 |
index | 들어갈 수 있는 채팅방 리스트를 보여준다. |
makeRoom | 이름을 정해 새로운 채팅방을 만들 수 있다. |
chat | 채팅방에서 채팅을 할 수 있다. 없는 채팅방은 들어갈 수 없다. |
<!--index.ejs-->
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
#roomList { list-style-type: none; margin: 0; padding: 0; }
#roomList > li { padding: 0.5rem 1rem; }
#roomList > li:nth-child(odd) { background: #efefef; }
</style>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
</head>
<body>
<br>
<button onclick="location.href='/makeRoom'">새로운 방 만들기</button>
<h2>방 리스트</h2>
<ul id="roomList">
<li>방이 없습니다</li>
</ul>
<br>
<script>
let socket = io();
socket.emit("indexPage");
socket.emit("request room List");
socket.on('response room List', function(list) {
console.log(list);
let temp = '';
list.forEach((val)=>{
temp += `<li onclick = "location.href='/chat/${val[0]}'">인원:${val[2]} 이름:${val[1]} </li>`;
})
if(list.length === 0) temp = `<li>방이 없습니다</li>`;
$('#roomList').html(temp);
window.scrollTo(0, document.body.scrollHeight);
})
</script>
</body>
</html>
<!--makeRoom.ejs-->
<!DOCTYPE html>
<html>
<head>
<title>방 만들기</title>
</head>
<body>
<h2>방 만들기</h2>
<form method="post">
채팅방이름:<input type="text" id="roomName" name="roomName" placeholder="채팅방이름">
<button type="submit">만들기</button>
</form>
</body>
</html>
<!--chat.ejs-->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<style>
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #efefef; }
</style>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>
<body>
<button onclick="location.href='/'">home</button>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" />
<input type="submit">
</form>
<script>
let socket = io();
let key = window.location.pathname.split('/')[2];
socket.emit("whatIsMyRoom", key);
let messages = document.getElementById('messages');
let form = document.getElementById('form');
let input = document.getElementById('input');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', key, input.value);
input.value = '';
}
});
socket.on('chat message', function(msg) {
let item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
})
</script>
</body>
</html>
2. 서버
모듈 이름 | 설명 |
index.js | express라이브러리를 사용해 페이지를 클라리언트에 전송하는 역할을 한다. |
room.js | 채팅방 클래스. 클래스에서 채팅방 정보와 함수를 관리한다. |
socket.js | socket.io를 사용해 클라이언트와 웹소켓 통신하는 모듈. |
//index.js
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
//room은 새로운 파일로 만들어 따로 관리하자.
const roomModule = require('./room');
const room = new roomModule.Room;
app.use(express.json());
app.use(express.urlencoded());
//위 2개를 추가해야 post body가 제대로 읽힌다.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.render('index');
});
app.get('/makeRoom', (req, res) => {
res.render('makeRoom');
});
app.post('/makeRoom', (req, res) => {
const roomName = req.body.roomName;
const key = room.makeNewRoom(roomName);
res.redirect(`/chat/${key}`);
});
app.get('/chat/:key', (req, res) => {
const key = req.params.key;
//없는 채팅방에 들어간다면 경고 표시후 /로 리다이렉트.
if(room.keys.has(key) === false)
res.send(`<script>alert('없는 채팅방입니다'); location.href='/';</script>`);
else
res.render('chat', {title:room.getRoomName(key)});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
require('./socket')(server, room);
//room.js
const crypto = require('crypto');
class Room {
constructor() {
//key는 방 접속id로, 방을 구분짓기 위한 것으로만 사용한다.
this.keys = new Set();
this.keyValuePopulation = new Array();
}
makeDistinctKey() {
let randomString = crypto.randomBytes(5).toString("hex");
//id가 겹친다면 다시 id를 만든다.
if (this.keys.has(randomString))
randomString = this.makeDistinctKey();
return randomString;
}
makeNewRoom(name) {
let key = this.makeDistinctKey();
this.keys.add(key);
this.keyValuePopulation.push([key, name, 0]);
return key;
}
addPopulation(key) {
for(let i=0; i<this.keyValuePopulation.length; i++) {
if(this.keyValuePopulation[i][0] == key) {
this.keyValuePopulation[i][2]++; break;
}
}
}
subPopulation(key) {
for(let i=0; i<this.keyValuePopulation.length; i++) {
if(this.keyValuePopulation[i][0] == key) {
this.keyValuePopulation[i][2]--;
if(this.keyValuePopulation[i][2] <= 0)
this.deleteRoom(key);
break;
}
}
}
deleteRoom(key) {
this.keys.delete(key);
this.keyValuePopulation = this.keyValuePopulation.filter((v)=> v[0] != key);
}
getRoomList() {
return this.keyValuePopulation;
}
getRoomName(key) {
let keyValuePopulation = this.keyValuePopulation.find((val)=>val[0]===key);
return keyValuePopulation[1];
}
}
module.exports = {Room};
//socket.js
const {Server} = require("socket.io");
module.exports = (server, room) => {
const io = new Server(server);
io.on('connection', (socket) => {
socket.on('chat message', (key, msg) => {
io.to(key).emit('chat message', msg);
});
socket.on('request room List', () => {
socket.emit('response room List', room.getRoomList());
});
socket.on("indexPage", () => {
socket.join('index');
});
socket.on("whatIsMyRoom", (key) => {
socket.join(key);
room.addPopulation(key);
io.to('index').emit('response room List', room.getRoomList());
});
socket.on('disconnecting', () => {
for (const r of socket.rooms) {
if(r != socket.id)
room.subPopulation(r);
}
});
socket.on('disconnect', () => {
io.to('index').emit('response room List', room.getRoomList());
});
});
};
결과:
'Node.js' 카테고리의 다른 글
nodejs의 file system (node:fs) (0) | 2023.07.29 |
---|---|
nodejs에서 session사용하기 - express-session 모듈 (0) | 2023.07.27 |
웹소켓(WebSocket) 5장 - socket.io 라이브러리 (0) | 2023.07.21 |
웹소켓(WebSocket) 4장 - ws 라이브러리를 이용한 채팅방 만들기 & 참여하기 (0) | 2023.06.08 |
웹소켓(WebSocket) 3장 - ws 라이브러리를 이용한 다중 클라이언트 채팅방 (0) | 2023.06.04 |