🔔 #2.8~2.11
1. 방 이름 띄우기
띄우기 전 socket.io 의 adapter 기능에 대해 알고 넘어가야함
규모가 커짐에 따라 하나의 서버로 돌리지 못하는 경우가 생기는데,
여러 서버가 생기게 된다면, 여러 서버가 하나의 DB 를 보게 되고 이런 경우를 대처하기 위해 만들어진 것이 adapter(두 서버를 연결해서 데이터를 전송하는 기능을 맡음)
https://socket.io/docs/v4/adapter/
Adapter | Socket.IO
An Adapter is a server-side component which is responsible for broadcasting events to all or a subset of clients.
socket.io
rooms
- private room : 서버와 브라우저간의 연결을 뜻함, personal ID가 들어감
- public room : 내가 생성한 이름이 앞에 들어가있음, set(중복없는 리스트) 안에는 personal ID 가 들어가게 되는데
이 set 에는 해당 public room에 연결된 personal ID들이 들어가게 됨
sids
personal ID가 key 값으로 들어가 있음
value 에는 해당 personal ID가 접속해 있는 방이 들어가는데, 이 말은 즉 rooms 에는 방과 personal ID가 모두 들어가있고,
sids에는 personal ID만 들어가있다는 뜻
둘을 비교하면 내가 만든 방만 추출해낼 수 있다.
쉽게 이야기하자면
sids에는 개인방, rooms에는 개인방,공개방 다있음 rooms가 sids를 포함한다 보면됨.
그래서 공개방만 얻고 싶을때는 rooms에서 sids를 빼면 됨
>> app.js
publicRooms라는 함수를 만들고, adapter에서 sids와 rooms를 가져와서 저장 후
forEach 함수를 이용해 rooms와 sids를 비교하여 rooms에 있는 요소 중에서 sids에 없는 것이 바로 public room의 이름이 된다
function getPublicRooms() {
// const sids = io.sockets.adapter.sids;
// const rooms = io.sockets.adapter.rooms;
const {
sockets: {
adapter: { sids, rooms },
},
} = wsServer; # wsServer 에서 sids와 rooms 가져오기
const publicRooms = []; # public room list 만들기
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
return publicRooms;
}
>> home.pug
h4 와 ul 를 만들어 Open Rooms 를 출력할 수 있는 공간을 만들어준다.
div#welcome
form
input(placeholder='room name', required, type='text')
button Enter Room
h4 Open Rooms :
ul
>> app.js
rooms 라는 데이터를 받으면 방금 추가한 ul 을 querySelector를 통해 불러오고 forEach 를 이용해 모든 room의 이름을 li 태그에 넣는다.
매번 모든 room 이 추가되기 전에 ul 을 초기화시켜주기 위해 roomlist.innerText = ''; (공백) 상태로 만들어줌
socket.on("room_change", (rooms) => {
const roomlist = welcome.querySelector('ul');
roomList.innerText = ''; # roomList의 html 초기화
if (rooms.length === 0) {
return;
}
rooms.forEach((room) => {
const li = document.createElement('li');
li.innerText = room;
roomlist.append(li);
});
});
2. 방에 있는 사람 수
아까 rooms 에 있던 key 값들의 value 갯수가 해당 room 에 있는 사람 수가 됨
>> server.js
여기서 ?가 붙는 이유는 roomName을 찾지 못하는 경우
방에 사람이 몇 명 있는지 계산하는 함수 = set의 size 함수
function countRoom(roomName) {
return wsServer.sockets.adapter.rooms.get(roomName)?.size;
}
welcome 이벤트를 roomName에 있는 모든 사람들에게 emit (하나의 socket 전달), 들어오면 사람 수가 바뀌므로 사람 수 count하기
room_change 이벤트의 payload 는 publicRooms 함수의 결과 (우리 서버 안에 있는 모든 방의 array = 서버의 모든 socket)
방 안에 있는 사람들에게 보내기 위해ㅔ forEach 사용하고, 나가면 사람 수가 바뀌므로 count 하기
wsServer.on("connection", (socket) => {
socket['nickname'] = 'Anon';
socket.onAny((event) => {
console.log(`Socket Event: ${event}`);
});
socket.on('enter_room', (roomName, done) => {
socket.join(roomName);
done();
socket.to(roomName).emit('welcome', socket.nickname, countRoom(roomName));
wsServer.sockets.emit('room_change', publicRooms());
});
socket.on('disconnecting', () => {
socket.rooms.forEach((room) => socket.to(room).emit('bye', socket.nickname, countRoom(room) -1));
});
>> app.js
사람이 들어왔을 때 (welcome)와 나갔을 때 (bye) newCount 라는 데이터를 받아와서 출력하기 위해 기존 h3 수정
하지만 방에 막 들어간 경우 인원수를 파악할 수 없다 그 이유는 맨 처음 done 함수를 통해 newCount 없이 그려줬기 때문
socket.on("welcome", (user, newCount) => {
const h3 = room.querySelector('h3');
h3.innerText = `Room ${roomName} (${newCount})`;
addMessage(`${user} arrived!`);
});
socket.on("bye", (left, newCount) => {
const h3 = room.querySelector('h3');
h3.innerText = `Room ${roomName} (${newCount})`;
addMessage(`${left} left ㅠㅠ`);
});
3. Admin Panel
socket.io 에서는 admin 용 ui 존재
현재 서버가 어떻게 돌아가고, 어떤 사용자가 얼마나 사용 중인지 쉽게 파악 가능
설치
>> npm i @socket.io/admin-ui
>> server.js
사용하기 위해 코드 좀 수정해줘야함
import { Server } from 'socket.io';
import { instrument } from "@socket.io/admin-ui";
socket.io에서 import한 것의 이름을 Server로 바꿨기 때문에 맞춰서 바꾸고, cors에다가 해당 주소를 넣기
auth 는 인증 관련 부분으로 username 이나 password 부여하고 싶을 때 여기다 부여하면 됨
const wsServer = new Server(httpServer, {
cors: {
origin: ["https://admin.socket.io"],
credentials: true
}
});
instrument(wsServer, {
auth: false,
});
https://socket.io/docs/v4/admin-ui/
Admin UI | Socket.IO
The Socket.IO admin UI can be used to have an overview of the state of your Socket.IO deployment.
socket.io
원래 http://localhost:3000/admin 까지 ... 적어야되는데 http://localhost:3000 하니가 접속이된다 .. . 쩝
Socket.IO Admin UI
admin.socket.io
여기까지 전체 코드
>> home.pug
doctype html
html(lang="ko")
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")
title Zoom
link(rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css")
body
header
h1 Zoom!
main
div#welcome
form
input(placeholder='room name', required, type='text')
button Enter Room
h4 Open Rooms :
ul
div#room
h3
ul
form#name
input(placeholder='nickname', required, type='text')
button save
form#msg
input(placeholder='message', required, type='text')
button Send
script(src="/socket.io/socket.io.js")
script(src="/public/js/app.js")
>> app.js
const socket = io();
const welcome = document.getElementById('welcome');
const form = welcome.querySelector('form');
const room = document.getElementById('room');
room.hidden = true;
let roomName;
function addMessage(message){
const ul = room.querySelector('ul');
const li = document.createElement('li');
li.innerText = message;
ul.appendChild(li);
}
function handleMessageSubmit(event){
event.preventDefault();
const input = room.querySelector("#msg input");
const value = input.value;
socket.emit('new_message', input.value, roomName, () =>{
addMessage(`You: ${value}`);
});
input.value = '';
}
function handleNicknameSubmit(event){
event.preventDefault();
const input = room.querySelector("#name input");
socket.emit('nickname', input.value);
}
function showRoom() {
welcome.hidden = true;
room.hidden = false;
const h3 = room.querySelector('h3');
h3.innerText = `Room ${roomName}`;
const msgForm = room.querySelector("#msg");
const nameForm = room.querySelector("#name");
msgForm.addEventListener("submit", handleMessageSubmit);
nameForm.addEventListener("submit", handleNicknameSubmit);
}
function handleRoomSubmit(event) {
event.preventDefault();
const input = form.querySelector("input");
socket.emit("enter_room", input.value, showRoom);
roomName = input.value;
input.value = "";
}
form.addEventListener('submit', handleRoomSubmit);
socket.on("welcome", (user, newCount) => {
const h3 = room.querySelector('h3');
h3.innerText = `Room ${roomName} (${newCount})`;
addMessage(`${user} arrived!`);
});
socket.on("bye", (left, newCount) => {
const h3 = room.querySelector('h3');
h3.innerText = `Room ${roomName} (${newCount})`;
addMessage(`${left} left ㅠㅠ`);
});
socket.on("new_message", addMessage);
socket.on("room_change", (rooms) => {
const roomList = welcome.querySelector('ul');
roomList.innerText = '';
if (rooms.length === 0) {
return;
}
rooms.forEach((room) => {
const li = document.createElement('li');
li.innerText = room;
roomlist.append(li);
});
});
>> server.js
import http from "http";
import express from 'express';
import { Server } from 'socket.io';
import { instrument } from "@socket.io/admin-ui";
const app = express();
app.set("view engine", "pug"); // Pug로 view engine 설정
app.set("views", __dirname + "/views"); // express에 templatedl 어디 있는지 지정
app.use("/public", express.static(__dirname + "/public")); // public url 생성해 유저에게 파일 공유
app.get("/", (req, res) => res.render("home")); // home.pug를 render 해주는 route handler 만들기
app.get("/*", (req, res) => res.redirect("/"));
const handleListten = () => console.log(`Listening on http://localhost:3000`);
const httpServer = http.createServer(app);
const wsServer = new Server(httpServer, {
cors: {
origin: ["https://admin.socket.io"],
credentials: true
}
});
instrument(wsServer, {
auth: false,
});
function publicRooms() {
const {
sockets: {
adapter: {sids, rooms},
},
} = wsServer;
const publicRooms = [];
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
return publicRooms;
}
function countRoom(roomName) {
return wsServer.sockets.adapter.rooms.get(roomName)?.size;
}
wsServer.on("connection", (socket) => {
socket['nickname'] = 'Anon';
socket.onAny((event) => {
console.log(`Socket Event: ${event}`);
});
socket.on('enter_room', (roomName, done) => {
socket.join(roomName);
done();
socket.to(roomName).emit('welcome', socket.nickname, countRoom(roomName));
wsServer.sockets.emit('room_change', publicRooms());
});
socket.on('disconnecting', () => {
socket.rooms.forEach((room) => socket.to(room).emit('bye', socket.nickname, countRoom(room) -1));
});
socket.on('disconnect', () => {
wsServer.sockets.emit('room_change', publicRooms());
});
socket.on('new_message', (msg,room, done) => {
socket.to(room).emit('new_message', `${socket.nickname}: ${msg}`);
done();
});
socket.on('nickname', nickname => socket['nickname'] = nickname);
});
httpServer.listen(3000, handleListten);
// const sockets = [];
// function onSocketClose() {
// console.log("Disconnected from the Browser");
// }
// wss.on("connection", (socket) => {
// sockets.push(socket);
// socket['nickname'] = '';
// console.log("Connected to Browser ✅");
// socket.on("close", onSocketClose);
// socket.on("message", (msg) => {
// const message = JSON.parse(msg.toString("utf8"));
// switch(message.type) {
// case 'new_message' :
// sockets.forEach((aSocket) =>
// aSocket.send(`${socket.nickname}: ${message.payload}`));
// case 'nickname' :
// socket['nickname'] = message.payload;
// }
// });
// });
// socket.on('nickname', fn)
// socket.on('notification', fn)
>> 그 외
# babel.config.json
{
"presets": ["@babel/preset-env"]
}
# nodemon.json
{
"ignore" : ["src/pubilc/*"],
"exec" : "babel-node src/server.js"
}
'FrameWork > Express.js' 카테고리의 다른 글
4 . 노마드코더 zoom 클론 - 방만들기/알림/메세지/닉네임 (0) | 2023.02.05 |
---|---|
3 . 노마드코더 zoom 클론 - socket IO (0) | 2023.01.15 |
2 . 노마드코드 zoom클론코딩 - 채팅기능 구현하기 (0) | 2023.01.15 |
1 . 노마드코드 zoom클론코딩 - 환경설정/webSocket으로 클라이언트-서버 연결 (0) | 2023.01.15 |