โ ๋์ ์ฌ๋์๊ฒ ๋ฐ๋ชจ๋ฅผ ํ๋ฉด ํ ์๋ก ์ฑ๊ณต์จ์ด ๋ฎ์์ง๋ค. โ
- Dan
![Namespace-room](https://blog.kakaocdn.net/dn/elsHho/btrrDPFKa6q/rHjemgCFKLYy76bFKCGKk0/img.jpg)
Namespace ๊ฐ๋
๋ค์์คํ์ด์ค๋ Express์ ๋ผ์ฐํ ์ฒ๋ผ url์ ์ง์ ๋ ์์น์ ๋ฐ๋ผ ์ ํธ์ ์ฒ๋ฆฌ๋ฅผ ๋ค๋ฅด๊ฒ ํ๋ ๊ธฐ์ ์ด๋ค.
![Namespace-room](https://blog.kakaocdn.net/dn/bxMyiP/btrrDd7WJH3/DDNnc999APlTKtUIb04qQk/img.png)
์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ๋๋ฉด ์ค์๊ฐ ๋ฐ์ดํฐ ๊ณต์ ๊ฐ ๊ฐ๋ฅํ๋ฐ, socket์ ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ socket์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ค.
ํ์ง๋ง ํน์ ํ์ด์ง์์ ์์ผ์ด ๋ณด๋ด์ฃผ๋ ๋ชจ๋ ์ค์๊ฐ ๋ฉ์ธ์ง๋ฅผ ๋ชจ๋ ๋ฐ์ ํ์๋ ์๋ค. ๋ถํ์ํ๋ฉฐ ๋ญ๋น์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ํน์ ๋ ธ๋๋ผ๋ฆฌ๋ง ์ฐ๊ฒฐํด์ฃผ๋ ๊ฒ์ด namespace ์ด๋ค.
์ง๊ธ๊น์ง๋ ๊ธฐ๋ณธ ๋ค์์คํ์ด์ค์ธ / ์ ์ ํธ๋ฅผ ์ ์กํ๊ณ ์์ ํ์ง๋ง, ๋ค๋ฅธ ๋ค์์คํ์ด์ค๋ฅผ ๋ง๋ค์ด์ ์ ํธ๋ฅผ ๊ฐ๊ธฐ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌํ ์๋ ์๋ ๊ฒ์ด๋ค.
์ฆ, ์ง์ ํ Namespace์ ์๋ ์์ผ ๋ผ๋ฆฌ๋ง ํต์ ํ๋ค๋ ๊ฐ๋ ์ด๋ค.
Namespace ๋จ์ด ๋ง ๊ทธ๋๋ก ์ด๋ฆ์ด ๋ถ์ ๊ณต๊ฐ์ด๋ฉฐ, ์์ผ์ ๋ฌถ์ด์ฃผ๋ ๋จ์๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
Namespace ์์ ์ฝ๋
[์๋ฒ]
// ๋ค์์คํ์ด์ค ๋ฑ๋ก
const room = io.of('/room');
const chat = io.of('/chat');
// room ๋ค์์คํ์ด์ค ์ ์ฉ ์ด๋ฒคํธ
room.on('connection', (socket) => {
console.log('room ๋ค์์คํ์ด์ค์ ์ ์');
socket.on('disconnect', () => {
console.log('room ๋ค์์คํ์ด์ค ์ ์ ํด์ ');
});
socket.emit('newroom', '๋ฐฉ ๋ง๋ค์ด'); // ๊ฐ์ room ๋ค์์คํ์ด์ค ์์ผ์ผ๋ก๋ง ์ด๋ฒคํธ๊ฐ ๋ ๋ผ๊ฐ๋ค.
});
// chat ๋ค์์คํ์ด์ค ์ ์ฉ ์ด๋ฒคํธ
chat.on('connection', (socket) => {
console.log('chat ๋ค์์คํ์ด์ค์ ์ ์');
socket.on('disconnect', () => {
console.log('chat ๋ค์์คํ์ด์ค ์ ์ ํด์ ');
socket.leave(roomId);
});
socket.emit('join', '์ฐธ์ฌ') // ๊ฐ์ chat ๋ค์์คํ์ด์ค ์์ผ์ผ๋ก๋ง ์ด๋ฒคํธ๊ฐ ๋ ๋ผ๊ฐ๋ค.
});
> ๋ค์์คํ์ด์ค ์ค์ io.of('/something')
io.of ๋ฅผ ํตํด namespace๋ฅผ /room๊ณผ /chat ์ผ๋ก ๋๋์ด ์ง์ ํด์ฃผ์๋ค.
๋ค์์คํ์ด์ค์ ์ฐ๊ฒฐ ์ฒ๋ฆฌ๋ ์ ๊ฐ๊ฐ์ด๋ผ ์ฐ๊ฒฐ ์ฝ๋ฐฑ(connection)์ ๋ฐ๋ก๋ฐ๋ก ๋ฑ๋ก ํด์ค๋ค
์ด๋ ๊ฒ ๋๋ฉด namespace ๊ฐ์ฒด๋ ํด๋ผ์ด์ธํธ์์ /room ํน์ /chat ๋ค์์คํ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ์์ผ๊ณผ๋ง ํต์ ์ ํ๊ฒ ๋๋ค.
[ํด๋ผ์ด์ธํธ 1]
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io.connect('http://localhost:8005/room', { // room ๋ค์์คํ์ด์ค
path: '/socket.io'
});
//* newRoom ์ด๋ฒคํธ ์ room ๋ค์์คํ์ด์ค ์์๋ง ํต์ ํ๊ฒ ๋๋ค.
socket.on('newRoom', function (data) {
// ...
});
// removeRoom ์ด๋ฒคํธ ์ room ๋ค์์คํ์ด์ค ์์๋ง ํต์ ํ๊ฒ ๋๋ค.
socket.on('removeRoom', function (data) {
// ...
});
</script>
[ํด๋ผ์ด์ธํธ 2]
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io.connect('http://localhost:8005/chat', {path: '/socket.io'}); // chat ๋ค์์คํ์ด์ค
//* join ์ด๋ฒคํธ ์ chat ๋ค์์คํ์ด์ค ์์๋ง ํต์ ํ๊ฒ ๋๋ค.
socket.on('join', function (data) {
// ...
});
//* exit ์ด๋ฒคํธ ์ chat ๋ค์์คํ์ด์ค ์์๋ง ํต์ ํ๊ฒ ๋๋ค.
socket.on('exit', function (data) {
// ...
});
</script>
ํด๋ผ์ด์ธํธ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๋ค์์คํ์ด์ค๋ url์ฃผ์๋ก ์ฐ๊ฒฐ๋๋ ๊ฒ์ ์ ์ ์๋ค. ๊ทธ๋ ๊ฒ ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ง ์๋ฒ์ ํด๋ผ์ด์ธํธ ์์ผ๋ค์ด ์๋ก ์ก์์ ์ ํ๊ฒ ๋๋ ๊ฒ์ด๋ค.
์ธ๋ถ ํ์ผ์์ ํน์ Namespace ์ ์ด๋ฒคํธ ๋ณด๋ด๊ธฐ
ํต์ฌ์ io ๊ฐ์ฒด๋ฅผ ์ ์ญ ๋ณ์ ๋๋์๊ฐ ์ ํ์ผ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ ์ ์๋ public ๋ณ์๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฒ์ด๋ค.
public ๋ณ์๋ก ๋ง๋ค๋ฉด, ์ธ๋ถ ํ์ผ์์ ๋ง์ผ io๊ฐ์ฒด์ ์ ๊ทผํด ์ฌ์ฉํ ํ์๊ฐ ์๋ค๋ฉด ๊ฐ๋จํ๊ฒ req.app.get('io') ๋ก ๊ฐ์ ธ์ ์์ผIO ๊ธฐ๋ฅ์ ๋ง์๊ป ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
socket.js
const SocketIO = require('socket.io');
const io = SocketIO(server, { path: '/socket.io' });
// path๋ฅผ ์ง์ ํ ๊ณ ์ ํ io๊ฐ์ฒด๋ฅผ ์ ์ญ์ผ๋ก ๋ฑ๋ก.
// ์ ์ญ๋ณ์๋ก ๋ฑ๋กํจ์ผ๋ก์, ๋ค๋ฅธ ํ์ผ์์ ๋ฐ๋ก io๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ ์์ผ ์ค์ ์ ํ ์ ์๋ค.
app.set('io', io);
์ธ๋ถํ์ผ.js
//^ req.app.get('io') / res.app.get('io') ๋ฅผ ์ฌ์ฉํด์ ์ต์คํ๋ ์ค ๊ฐ์ฒด๋ก io๋ฅผ ๊ฐ์ ธ์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
const io = req.app.get('io'); // ์ ์ญ๋ณ์๋ก ๋ฑ๋กํด๋
ผ io๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ด
io.of('/room').emit('newRoom', "message"); // ํน์ room๋ค์์คํ์ด์ค์๊ฒ๋ง newRoom ์ด๋ฒคํธ์ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋
Namespace ์ข ๋ฅ
Default namespace
Default namespace๋ฅผ / ๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐ๊ฒฐ๋๋ Socket.IO ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์์ ํ๋ ํด๋ผ์ด์ธํธ๋ค.
์ด ๋ค์ ์คํ์ด์ค๋ io.sockets ๋๋ ๊ฐ๋จํ io๋ก ์๋ณ๋๋ค.
// ๊ธฐ๋ณธ ๋ค์์คํ์ด์ค ์ด๋ฒคํธ ์์
io.on('connection', socket => {
socket.on('disconnect', () => {});
});
// ๊ธฐ๋ณธ ๋ค์์คํ์ด์ค ์ด๋ฒคํธ ์ก์
io.sockets.emit('hi', 'everyone');
io.emit('hi', 'everyone'); // short form
Custom namespaces
์ฌ์ฉ์ ์ ์ ๋ค์ ์คํ์ด์ค๋ฅผ ์ค์ ํ๋ ค๋ฉด io.of ํจ์๋ฅผ ์ฌ์ฉํ๋ค.
const nsp = io.of('/my-namespace');
nsp.on('connection', socket => {
console.log('someone connected');
});
nsp.emit('hi', 'everyone!');
Namespace middleware
๋ชจ๋ Socket์ ๋ํด ์คํ๋๋ ํจ์๋ก, ์์ผ๊ณผ ๋ค์ ๋ฑ๋ก๋ ๋ฏธ๋ค์จ์ด๋ก ์คํ์ ์ ํ์ ์ผ๋ก ์กฐ์ ํ ์ ์๋ค.
์ฆ, express์ app.use ์ ์์ผIO ๋ฒ์ ผ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
์ฐ๋ฆฌ๊ฐ ์ด๋๊น์ง ์ด express์ ๋ฏธ๋ค์จ์ด๊ฐ ๊ทธ๋ฌํ๋ฏ์ด, next() ๋ฅผ ํตํด ๋ค์ ๋ฉ์๋๋ก ๋๊ธธ์ ์์ผ๋ฉฐ, ๋ํ ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด๋ฅผ ์์ผ ๋ฉ์๋ ๋ด์ ์ ์ธํด์ ์ฌ์ฉํ ์ ์๋ค.
const cookieParser = require('cookie-parser');
const io = SocketIO(server, { path: '/socket.io' });
// default namespace
io.use((socket, next) => {
if (isValid(socket.request)) {
// ์ธ๋ถ๋ชจ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์์๋ค ์ฐ์ผ์ ์๋ค. ๋ฏธ๋ค์จ์ด ํ์ฅ ์์น์ ๋ฐ๋ผ res, req์ธ์๋ฅผ ์ค๋ค (ํ์ )
cookieParser(process.env.COOKIE_SECRET)(socket.request, socket.request.res, next);
next();
} else {
// next(์ธ์)๋ฅผ ํ๋ฉด ๋ฐ๋ก ๋๊ฐ์ด Handling middleware error๋ก ๋์ด๊ฐ๊ฒ ๋๋ค. (ํ์ )
next(new Error('invalid'));
}
});
// custom namespace
io.of('/admin').use(async (socket, next) => {
const user = await fetchUser(socket.handshake.query);
if (user.isAdmin) {
socket.user = user;
next();
} else {
next(new Error('forbidden'));
}
});
Handling middleware error
next ๋ฉ์๋๊ฐ Error ๊ฐ์ฒด์ ํจ๊ป ํธ์ถ๋๋ฉด ํด๋ผ์ด์ธํธ๋ connect_error ์ด๋ฒคํธ๋ฅผ ์์ ํ๋ค.
express์ ์๋ฌ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด์ ์์ผIO ๋ฒ์ ผ ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
// ... next(new Error('inavlid'))
socket.on('connect_error', (err) => {
console.log(err.message); // prints the message associated with the error, e.g. "thou shall not pass" in the example above
});
Compatibility with Express middleware
๋ค์์คํ์ด์ค ๋ฏธ๋ค์จ์ด ์์์ ๊ธฐ์กด Express ๋ฏธ๋ค์จ์ด ๋ชจ๋๊ณผ ํจ๊ป ํธํ์์ผ ์ฌ์ฉํ๋ ค๋ฉด,
๋ฏธ๋ค์จ์ด ํ์ฅ ์ ์น์ ๋ฐ๋ผ (res, req, next) ์ธ์๋ฅผ ์ฃผ์ด์ผ ํ๋ค.
io.use((socket, next) => {
// ์ธ๋ถ๋ชจ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์์๋ค ์ฐ์ผ์ ์๋ค. ๋ฏธ๋ค์จ์ด ํ์ฅ ์์น์ ๋ฐ๋ผ res, req์ธ์๋ฅผ ์ค๋ค (ํ์ )
cookieParser(process.env.COOKIE_SECRET)(socket.request, socket.request.res, next);
});
๊ณต์๋ฌธ์์๋ wrap ์ด๋ผ๋ helperํจ์ ๊ธฐ๋ฒ์ ์ ๊ณตํด์ฃผ๋๋ฐ, ์ด ๊ธฐ๋ฒ์ ์ฌ์ฉํ๋ฉด app.use() ์ ๊ฐ์ด ์ข๋ ๊น๋ํ๊ฒ ๋ฏธ๋ค์จ์ด๋ค์ ๋ฐฐ์นํ ์ ์๊ฒ ๋๋ค.
๋ฌผ๋ก ์ด๋๊น์ง๋ ์ ํ์ ์ธ ๋ฐฉ๋ฒ์ผ ๋ฟ์ด๋ค.
// ์ธ๋ถ ๋ชจ๋ ๋ฏธ๋ค์จ์ด
const session = require('express-session');
const passport = require('passport');
// ๋ฏธ๋ค์จ์ด๋ฅผ ์ธ์๋ก ๋ฐ์ ์๋์ผ๋ก (req, res, next) ์ธ์๋ฅผ ๋ถ์ฌ์ค์ ํธ์ถํ๊ฒ ๋ง๋๋ helper ํจ์
const wrap = (middleware) => (socket, next) => middleware(socket.request, socket.request.res, next);
// app.use() ์ ๊ฐ์ด ๊น๋ํ๊ฒ ๋ฏธ๋ค์จ์ด๋ค์ ๋ฐฐ์นํ ์ ์๊ฒ ๋๋ค.
io.use(wrap(session({ secret: 'cats' })));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error('unauthorized'))
}
});
Room ๊ฐ๋
Room์ Namespace์ ํ์ ๊ฐ๋ ์ด๋ค. (IO -> NameSpace -> Room -> Socket )
namespace์์ ์๋ ์์ผ๋ค์ room์ผ๋ก ์ชผ๊ฐ์ด ๋๋ ๊ฒ์ด๋ค.
![Namespace-room](https://blog.kakaocdn.net/dn/bwy92j/btrrDdNJYzj/Wx9Ed85prGKzDyFGJHRt21/img.png)
์๋ฅผ ๋ค์ด ์นดํก ๋จํก๋ฐฉ ๋๊ฐ๊ฐ ์๋ค๊ณ ์น๋ฉด,
๋จํก๋ฐฉ1์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ฉด ๋จํก๋ฐฉ1์ ์ฌ์ฉ์๋ค์ ๋ฉ์์ง๋ฅผ ๋ฐ์ง๋ง / ๋ค๋ฅธ ๋จํก๋ฐฉ์ ์ฌ์ฉ์๋ค์ ๊ทธ ๋ฉ์์ง๋ฅผ ๋ฐ์ง ๋ชปํ๋ค.
์์ผ๋ค์ ๋ฃธ๋ ๋จํก๋ฐฉ๊ณผ ๋น์ทํ๋ค.
ํน์ ๋ฃธ์ ์ ํธ๋ฅผ ๋ณด๋ด๋ฉด ๋ฃธ ์์ ์์ผ๋ค์ ์ ํธ๋ฅผ ๋ฐ์ง๋ง, ๋ค๋ฅธ ๋ฃธ์ ์์๋ ์์ผ๋ค์ ์ ํธ๋ฅผ ๋ฐ์ง ๋ชปํ๋ค.
์ด๋ฅผ ํตํด room์ join๋์ด ์๋ ํด๋ผ์ด์ธํธ ๋ง์ ๋ฐ์ดํฐ ์ก์์ ์ด ๊ฐ๋ฅํ๊ฒ ๋๋ค.
์ฆ, ๊ฐ ํด๋ผ์ด์ธํธ๋ socket์ ๊ฐ์ง๊ฒ ๋๋ฉฐ, ์ด socket์ namespace๋ฅผ ๊ฐ์ง๊ณ , ๊ฐ namespace๋ room์ ๊ฐ์ง๋ค.
๋ค์์คํ์ด์ค๋ฅผ ํตํด ํฐ ์ค๊ธฐ์ ๋ฐ์ดํฐ ํต์ ์ ๋ง๋ค๊ณ ๋ฃธ์ ํตํด ๋ฏธ์ธํ๊ฒ ์์ผ์ ์ฐ๊ฒฐํ ์ ์๋ ๊ฒ์ด๋ค.
![Namespace-room](https://blog.kakaocdn.net/dn/bxDXAJ/btrroYiYUHb/pj7KU39QuOoK48zR1S0un0/img.png)
![Namespace-room](https://blog.kakaocdn.net/dn/spPB5/btrrOb9MkJm/Q9s3KlUJo2IsHL1YagOKok/img.png)
์ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ๋ ํนํ๊ฒ join, to, leave ๋ฑ ์น์ํ ๋์ฌ, ์ ์น์ฌ ๋ฉ์๋๋ค์ด ๋ณด์ด๋๋ฐ,
์ด๋ socket.io์์ ์ฑํ ๋ฐฉ์ ๋ ผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ๋ฉ์๋๋ก ๊ตฌํํด๋์ ๊ฒ์ด๋ค.
socket.join()๊ณผ leave(), to()๋ Socket.IO์์ ์ค๋นํด๋ ๋ฉ์๋์ด๋ค.
req.headers.referer ์๋ ์์ฒญ ์ฃผ์ uri๊ฐ ๋ค์ด ์๋ค. ์ด๋ฅผ ์ ๊ท์์ผ๋ก ํ์ฑํด์ roomID ๋ง ๋บด์จ ํ,
socket.join ์ ์ธ์๋ก ๋ฃ์ด ํน์ ๋ฐฉ์ ์
์ฅํ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ socket.leave ๋ก ๋ฐฉ์์ ๋๊ฐ ์ ์๋ค.
socket.to ๋ฅผ ํตํด ํน์ ๋ฃธ์๊ฒ๋ง ์ด๋ฒคํธ๋ฅผ emitํ ์ ์๋ค.
์ฆ, join, leave, to์ ๊ฐ๋ ์, socket.io์ Room(์ฑํ ๋ฐฉ)์ ์ฐธ๊ฐ/๋ ๋๋ค/ํํ ๊ฐ๋ ์ผ๋ก ๋ณด๋ฉด ๋๋ค.
![Namespace-room](https://blog.kakaocdn.net/dn/d7GJJa/btrrJUMuzgy/pjRuhaXj1HAw8hHTAjbxuk/img.png)
์ธ๋ถํ์ผ์์ ๋ค์์คํ์ด์ค ์์ ๋ฃธ์ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๊ธฐ
๋ค์์คํ์ด์ค ํฌ์คํ
์์ ํ ๊ฒ๊ณผ ๊ฐ์ด ๋๊ฐ์ด ์ ๊ทผํด์, to(rooId) ๋ฉ์๋๋ก ๋ฃธID๋ฅผ ํน์ ํด์ ๋ณด๋ด๋ฉด ๋๋ค.
req.app.get('io').of('/chat').to(roomId).emit('chat', chat);
Namespace & Room ์ด์ ๋ฆฌ
- IO : ์ ์ญ
- ๋ค์์คํ์ด์ค <path>: ์ ์ญ์์ ๋๋ ๊ณต๊ฐ
- ๋ฃธID : ๋ค์์คํ์ด์ค์์ ์ธ๋ถํ ๋๋ ๊ณต๊ฐ
- ์์ผID : ์ด๋ ํน์ ์ฌ์ฉ์์ ์ํต์ฉ
์ฆ, ๊ฐ์ ๋ค์์คํ์ด์ค ์์์ ์ํต ํ์ฌ, ์์ผ ์ก์์ ์ ๋ถ๋ฆฌ ํ ์์๊ณ
๋ ๋ค์์คํ์ด์ค ์์ ๊ฐ์ ๋ฃธ ์์์๋ง ์ํต ํ๊ฒ ํ์ฌ, ๋ ์ธ๋ถํ๊ฒ ์์ผ ์ก์์ ์ ๋ถ๋ฆฌ ํ ์ ์๋ค.
// ์ ์ฒด ๋ณด๋ด๊ธฐ
req.app.get('io').emit('์ด๋ฒคํธ', ๋ฉ์ธ์ง);
// ๋ค์์คํ์ด์ค์ ์๋ ์ ์ ํํ
๋ง ๋ณด๋ด๊ธฐ
req.app.get('io').of('๋ค์์คํ์ด์ค').emit('์ด๋ฒคํธ', ๋ฉ์ธ์ง);
// ๋ค์์คํ์ด์ค์ ์์ผ๋ฉด์, ๊ทธ์์ ๋ฃธ์ ์๋ ์ ์ ํํ
๋ง ๋ณด๋ด๊ธฐ
req.app.get('io').of('๋ค์์คํ์ด์ค').to(roomId).emit('์ด๋ฒคํธ', ๋ฉ์ธ์ง);
// ํน์ ์ ์ ํํ
๋ง ๋ณด๋ด๊ธฐ (1:1๋ํ, ๊ท์๋ง)
req.app.get('io').to(socket.id).emit('์ด๋ฒคํธ', ๋ฉ์ธ์ง);
// ๋๋ฅผ ์ ์ธํ ๋ชจ๋ ์ ์ ์๊ฒ ๋ณด๋ด๊ธฐ
req.app.get('io').broadcast.emit('event_name', msg);
# ์ฐธ๊ณ ์๋ฃ
https://berkbach.com/node-js%EC%99%80-socket-io%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85-%EA%B5%AC%ED%98%84-2-ce5ac35bb007
https://fred16157.github.io/node.js/nodejs-socketio-communication-room-and-namespace/
์ด ๊ธ์ด ์ข์ผ์ จ๋ค๋ฉด ๊ตฌ๋ & ์ข์์
์ฌ๋ฌ๋ถ์ ๊ตฌ๋
๊ณผ ์ข์์๋
์ ์์๊ฒ ํฐ ํ์ด ๋ฉ๋๋ค.