๐Ÿฌ ๊ธดํ˜ธํก/๊ณ ์Šค๋ฝ ํ‹ฐ์ผ“

[ํ‹ฐ์ผ“ ์˜ˆ๋งค ํ”„๋กœ์ ํŠธ] 6. ์‹ค์‹œ๊ฐ„ ์ž…์žฅ ํ™•์ธ (Socket.IO)

ํ•œ๊ทœ์ง„ 2022. 2. 16. 21:16

๊ณต์—ฐ์žฅ ์ž…์žฅ ์‹œ QR์ฝ”๋“œ ํ‹ฐ์ผ“์„ ์ฐ๊ณ  ์ž…์žฅ ํ™•์ธ์„ ๋ฐ›์€ ํ›„์— ๋“ค์–ด๊ฐ„๋‹ค. ์ฝ”๋“œ๋ฅผ ์ฐ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์—์„œ ์ž…๊ธˆ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ ํ›„์— ์ง์ ‘ ์ž…์žฅํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ ์•„๋ฌด๋ž˜๋„ ๋ˆˆ์— ๋ณด์ด๋Š” ๋ณ€ํ™”๊ฐ€ ์žˆ์œผ๋ฉด ์ข‹์„๊ฑฐ๋ž€ ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

 

์†Œ์ผ“์„ ์ด์šฉํ•œ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์„ ๋„ฃ์–ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์ž…์žฅ ํ™•์ธ์ด ๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ(ํ•ด๋‹น ๊ด€๊ฐ ๊ธฐ๊ธฐ)๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

๋ฐฑ์—”๋“œ

๋Œ€์žฅ๋‹˜์ด ์ž‘์—…ํ•œ ๋ฐฑ์—”๋“œ ์ฝ”๋“œ์ด๋‹ค. ์—ด์‹ฌํžˆ ๋ฐฐ์›Œ์™”๋‹ค.

 

const { Server } = require('socket.io');
const { httpServer } = require('../index');

class SocketSingleton {
  constructor(httpServer) {
    // try catch  ์—†์ด ์ดˆ๊ธฐ ์„ค์ •์ด fail ํ•˜๋ฉด ์„œ๋ฒ„ ๋ฐ”๋กœ ์ข…๋ฃŒ
    this.io = new Server(httpServer, {
      /* options */
      cors: {
        origin: ['https://admin.gosrock.link','https://gosrock.link', 'http://localhost:3000']
      }
    });
    this.adminSocket = this.io.of('/socket/admin');
    this.ticketsSocket = this.io.of('/socket/tickets');
    this.startCheck = false; // ์†Œ์ผ“ ์„œ๋ฒ„ ์‹คํ–‰ ์ฒดํฌ ๋ณ€์ˆ˜
  }

  // ์†Œ์ผ“ ์„œ๋ฒ„ ์‹คํ–‰
  startSocketServer = () => {
    /**
    *
    **/
  };
}

module.exports = new SocketSingleton(httpServer);

์ƒ์„ฑ์ž ์•ˆ์—์„œ ์†Œ์ผ“์„ ์ดˆ๊ธฐํ™”ํ•ด์ฃผ๊ณ  ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค. ๊ณต์‹๋ฌธ์„œ๊ฐ€ ๋งค์šฐ ์ž˜ ๋˜์–ด ์žˆ๋‹ค.

adminSocket ์€ ์–ด๋“œ๋ฏผํŽ˜์ด์ง€์—์„œ ์ž…์žฅํ™•์ธํ•  ๋•Œ, ticketsSocket์€ ํ”„๋ก ํŠธ ํŽ˜์ด์ง€์—์„œ ์ž…์žฅํ™•์ธํ•  ๋•Œ ์‚ฌ์šฉํ•  ๋„ค์ž„์ŠคํŽ˜์ด์Šค์ด๋‹ค.

 

์‹ฑ๊ธ€ํ†ค

์†Œ์ผ“ ๊ฐ์ฒด๋ฅผ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋งŒ๋“ค์–ด์„œ ์™ธ๋ถ€์—์„œ๋„ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค. new SocketSingleton(httpServer); ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ชจ๋“ˆ๋กœ exports ํ•ด์ฃผ์—ˆ๋‹ค. nodejs์—์„œ ํ•œ๋ฒˆ load(require)๋œ ๋ชจ๋“ˆ์€ require.cache ๋ผ๋Š” ๊ฐ์ฒด์— ์บ์‹ฑ๋˜๋Š”๋ฐ, ์ฆ‰ ๋ชจ๋“ˆ์„ require ํ• ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์บ์‹ฑ๋œ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ์žฌ์‚ฌ์šฉํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฒƒ!!

 

const express = require('express');
const app = express();
const { createServer } = require('http');
const httpServer = createServer(app);
module.exports = { httpServer };
//...
const SocketSingleton = require('./sockets');


const server = () => {
    //...
    SocketSingleton.startSocketServer();
    //...
    httpServer.listen(5000);
}

server();

index.js์—์„œ๋Š” ์†Œ์ผ“ ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด๋ฅผ ์œ„์ฒ˜๋Ÿผ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•œ๋‹ค.

 

์†Œ์ผ“ ์—ฐ๊ฒฐํ•˜๊ธฐ

  startSocketServer = () => {
    if (this.startCheck) throw Error('์†Œ์ผ“์„œ๋ฒ„๋ฅผ ์ด์ค‘์œผ๋กœ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.');
    const connectionAuthMiddleware = require('./connectionAuthMiddleware');
    const ticketsUrlMiddleware = require('./ticketsUrlMiddleware');

    connectionAuthMiddleware();
    ticketsUrlMiddleware();
    this.io.on('connection', socket => {
      socket.disconnect();
    });
    this.adminSocket.on('connection', socket => {
      console.log('admin is enter', socket.data);
    });

    this.ticketsSocket.on('connection', socket => {
      console.log('tickets is enter', socket.data.ticketId);
      //๋ฃธ์— ๊ฐ•์ œ์‚ฝ์ž…

      socket.join(socket.data.ticketId);
    });

    this.startCheck = true;
  };

startSocketServer ๋ฉ”์†Œ๋“œ์ด๋‹ค. ์•„๊นŒ ์ƒ์„ฑ์ž์—์„œ ์ดˆ๊ธฐํ™”๋œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ connection ์ •๋ณด๋ฅผ ๋”ฐ๋กœ ๋ฐ›๊ณ  ์žˆ๋‹ค. ๊ธฐ๋ณธ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ์—ฐ๊ฒฐ๋œ ์†Œ์ผ“์€ ํ•„์š”์—†์œผ๋‹ˆ ๋ฐ”๋กœ ๋Š์–ด์ค€๋‹ค๊ณ  ํ–ˆ๋‹ค.

 

ticketsSocket์ด ์—ฐ๊ฒฐ๋˜๋ฉด socket.join(socket.data.ticketId); ๋กœ room์„ ๋งŒ๋“ค๊ณ  joinํ•œ๋‹ค. ์ž…์žฅํ™•์ธ ์ •๋ณด๋ฅผ ํ์•Œ์ฝ”๋“œ๋ฅผ ์ฐ์€ ๊ฐ๊ฐ์˜ ๊ธฐ๊ธฐ์— ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ณด๋‚ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— room์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ฑ„ํŒ…์œผ๋กœ ์น˜๋ฉด ๊ฐ ํ†ก๋ฐฉ. room์€ socket.data.ticketId์˜ ๊ฐ’์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๋Š”๋ฐ, ๊ทธ ๊ฐ’์€ ๋ฉ”์†Œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ ํ›„์— ๋ฏธ๋“ค์›จ์–ด์—์„œ ๋ฐ›์•„์„œ ๋„˜๊ฒจ์ค€ ๊ฐ’์ด๋‹ค.

 

๋ฏธ๋“ค์›จ์–ด

๋ฏธ๋“ค์›จ์–ด์—์„œ๋Š” ์†Œ์ผ“ ํ†ต์‹ ์— ๋‹ด๊ธด ํ—ค๋” ํ† ํฐ ๊ฒ€์ฆ๊ณผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ๋„˜๊ฒจ์ค€๋‹ค. ํ† ํฐ์ด ๊ฒ€์ฆ๋˜์—ˆ๋‹ค๋ฉด socket.data.ticketId = ticketId; ๋กœ ์ง์ ‘ socket ๊ฐ์ฒด์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š”๋‹ค.

 

https://socket.io/docs/v3/middlewares/#sending-credentials

ํด๋ผ์ด์–ธํŠธ์—์„œ auth ์˜ต์…˜์œผ๋กœ ๋ณด๋‚ธ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์—์„  handshake.auth๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 

์ž…์žฅ ํ™•์ธ

SocketSingleton.ticketsSocket
.to(ticketId)
.emit('enter', { enterState: true, ticketInfo: ticketUpdated });

์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ๊ธฐ์—์„œ ๋ณด๋‚ธ ์ž…์žฅํ™•์ธ api ์š”์ฒญ์„ ๋ฐ›๊ฒŒ ๋˜๋ฉด ํ‹ฐ์ผ“์˜ ์ž…๊ธˆ์ƒํƒœ๋ฅผ ๊ฒ€์ฆํ•œ ํ›„์— ์œ„์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. .to(ticketId).emit() ์œผ๋กœ ํŠน์ •ํ•œ ticketId์˜ room์—๋งŒ emit์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

 

์‹ฑ๊ธ€ํ†ค์„ ์“ฐ๋Š” ์ด์œ ๋Š” ์—ฌ๊ธฐ์— ์žˆ๋‹ค. SocketSingleton ์ธ์Šคํ„ด์Šค๊ฐ€ ๋”ฑ ํ•œ๊ฐœ๋ฐ–์— ์—†๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋””์„œ ์ ‘๊ทผํ•˜๋“  ๋˜‘๊ฐ™์€ ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 

 

ํ”„๋ก ํŠธ

๋‚ด๊ฐ€ ๋‹ด๋‹นํ•œ ํ”„๋ก ํŠธ์—”๋“œ ๋ถ€๋ถ„ ์ฝ”๋“œ๋Š” ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๋‹ค. ์„œ๋ฒ„์—์„œ '์ž…์žฅ ํ™•์ธ'์ด๋ผ๋Š” ์ •๋ณด๊ฐ€ ์˜ค๋ฉด ๋ฆฌ๋•์Šค์— ์ €์žฅ๋œ ํ‹ฐ์ผ“ ์ƒํƒœ๋ฅผ ๋ฐ”๊ฟ”์ฃผ๊ณ  ํ‹ฐ์ผ“์˜ ์ƒ‰๊น”์„ ๋ฐ”๊ฟ”์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

 

npm install socket.io-client

socket.io-client ๋ฅผ ์„ค์น˜ํ•˜๊ณ  import io from 'socket.io-client' ๋กœ ์ž„ํฌํŠธํ•ด์ค€๋‹ค.

 

const [socket, setSocket] = useState();

useEffect(() => {
  if (!socket) {
    setSocket(
      io('https://api.gosrock.link/socket/tickets', {
        auth: {
          ticketId: ticketId
        }
      })
    );
  } else {
    socket.on('enter', data => {
      if (data.enterState) {
        dispatch({ type: 'TICKET_ENTER_SUCCESS', payload: data.ticketInfo });
        toast('์ž…์žฅ ์™„๋ฃŒ', 5000);
      }
    });
  }
}, [socket]);

socket state๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค. useEffect ํ›…์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ œ์ผ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ์†Œ์ผ“๊ณผ ์—ฐ๊ฒฐ์„ ์ฒซ ๋ Œ๋”๋ง ์‹œ์—๋งŒ ํ•˜๊ธฐ ์œ„ํ•ด ํ›… ์•ˆ์— ๋„ฃ์—ˆ๋‹ค.

 

io()์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ๋„๋ฉ”์ธ์„ ๋„ฃ๊ณ  ์ดˆ๊ธฐํ™” ํ•ด์ค€๋‹ค. /socket/tickets ๋„ค์ž„ ์ŠคํŽ˜์ด์Šค๋กœ ์—ฐ๊ฒฐ์ด ๋œ๋‹ค. ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” ์˜ต์…˜์ด ๋“ค์–ด๊ฐ€๋Š”๋ฐ, auth ์˜ต์…˜์œผ๋กœ ๋„ค์ž„ ์ŠคํŽ˜์ด์Šค๋กœ ์—ฐ๊ฒฐํ•  ๋•Œ ์ธ์ฆ์„ ์œ„ํ•œ ํ† ํฐ์„ ๋‹ด์•„์„œ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ๋ฌธ์„œ

 

socket.on()์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. 'enter'์ด๋ฒคํŠธ๊ฐ€ ์™”์„ ๋•Œ data๋ฅผ ์—ด์–ด์„œ ์ž…์žฅ ํ™•์ธ์ด๋ฉด ๋ฆฌ๋•์Šค์— ๋””์ŠคํŒจ์น˜ํ•œ๋‹ค.

 

๊ฒฐ๊ณผ

ํฌ์ŠคํŠธ๋งจ์œผ๋กœ ๋ณด๋‚ด๋ณด์•˜๋‹ค. ์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ๊ธฐ์—์„œ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” ์ž…์žฅํ™•์ธ api์ด๋‹ค. ์„œ๋ฒ„๋Š” ํ‹ฐ์ผ“์˜ ์ž…๊ธˆ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ  emit์œผ๋กœ ๋ณด๋‚ธ๋‹ค.

 

๋ฆฌ๋•์Šค์— ์ €์žฅ๋œ ํ‹ฐ์ผ“์˜ ์ƒํƒœ๊ฐ€ 'enter'์ด๋ฉด QR์ฝ”๋“œ ์ฃผ๋ณ€์˜ ํ…Œ๋‘๋ฆฌ๊ฐ€ ๊ธˆ์ƒ‰์ด ๋˜๋„๋ก ํ–ˆ๋‹ค. ์ด์˜๋„ค. ํ† ์ŠคํŠธ ๋ฌธ๊ตฌ๋„ 5์ดˆ๋™์•ˆ ์˜ฌ๋ผ์™”๋‹ค๊ฐ€ ์‚ฌ๋ผ์ง€๋„๋ก. ์•„๋ฌด๋„ ์‹ ๊ฒฝ ์•ˆ์“ธ ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋ง˜์— ๋“ ๋‹ค!! ์ด์ „๋ถ€ํ„ฐ ์†Œ์ผ“ํ†ต์‹ ์„ ์จ๋ณด๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ์ด๋ฒˆ ๊ธฐํšŒ์— ์ ์ ˆํ•˜๊ฒŒ ๋„ฃ์–ด๋ณผ ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค. ๋‹ค์Œ ๋‹ฌ์—๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์จ๋ด์•ผ๊ฒŸ๋‹ค.