NodeJS+MongoDB

NodeJS+MongoDB Part3 - 실시간 채팅 (Socket.io)

hminor 2023. 7. 3. 12:50

(Socket.io) 실시간 데이터 보내고 받는 법

이전에 실습한 SSE와 Socket.io의 차이

  • SSE
    • 서버 → 유저 (일방적 통신)
  • Socket.io
    • 서버 ↔ 유저 (양방향 통신)

보통 WebSocket을 사용할 때는 JavaScript로도 구현할 수 있지만

socket.io 라이브러리를 사용함.

우선 간단하게 생각하면 되는 건

  • emit: 데이터 보내기
  • on: 데이터 받기

설치

npm i socket.io

server.js에서 socket.io셋팅

// socket.io 셋팅
const http = require('http').createServer(app)
const {Server} = require('socket.io')
const io = new Server(http)

그리고 기존에 설정했던 app.listen을 http.listen으로 변경해주기

원래 NodeJS는 http 를 사용하지만 기존 우리는 express를 사용했어서 app을 사용했다고 함.

MongoClient.connect(process.env.DB_URL, (에러, client) => {
  if (에러) return console.log(에러);

  db = client.db("todoapp"); // todoapp이라는 database에 연결하는 코드
  http.listen(process.env.PORT, () => {
    console.log("listening on" + process.env.PORT);
  });
});

이후 server.js에서 /socket 로 이동시 socket.ejs 파일을 보여주도록 하기

app.get("/socket", (req, res) => {
  res.render("socket.ejs");
});

그리고 이제는 유저 또한 서버로 데이터를 보낼 수 있기에 유저가 보는 html 파일에도 socket.io 셋팅을 해줘야 한다.

그래서 https://cdnjs.com/ ←-해당 링크에서

위에서 설치한 npm i socket.io와 같은 버전의 cdn을 socket.ejs 파일에 추가해주고

const socket = io() 로 socket 연결해주기

<!doctype html>
<html>

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
    integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
  <link rel="stylesheet" href="/public/main.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.js"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
  <title>NodeJS+MongoDB</title>
</head>

<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>

  <script>
    var socket = io() // <------ 이렇게 해서 socket 연결을 해주기!
  </script>
</body>

</html>

그리고 server.js 에서 연결되었을때 접속이 되었는지 확인해보기

// 누가 웹소켓에 접속하면 내부 코드를 실행해달라는 코드
io.on("connection", () => {
  console.log("접속됨.");
});

이후 socket.js에서 버튼을 생성 후 버튼 클릭시 서버로 전송해서 데이터를 보내기

io()로 연결되어있는 socket을 emit으로 데이터 이름과 데이터를 함께 서버로 보내주기

<!doctype html>
<html>

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
    integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
  <link rel="stylesheet" href="/public/main.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.js"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
  <title>NodeJS+MongoDB</title>
</head>

<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>
  <button id="send">서버에 메시지 보내기</button>


  <script>
    var socket = io()
    $('#send').click((e) => {
      // socket.emit('보낼 데이터 이름','보낼 데이터')
      socket.emit('user-send', 'hi')
    })
  </script>
</body>

</html>

이후 유저가 보낸 데이터를 서버에서도 받을 때 그냥 받아지는 것이 아니기에

코드를 작성해주기

io.on() 으로 받아온 파라미터인 socket을 on 으로 연결해

유저가 보낸 데이터 명으로 연결을 해주고, 해당 데이터를 받을 수 있다.

// 누가 웹소켓에 접속하면 내부 코드를 실행해달라는 코드
io.on("connection", (socket) => {
  console.log("접속됨.");

  // 유저가 데이터를 보내면 서버가 수신할 수 있도록 코드 작성하기
  socket.on("user-send", (data) => {
    // <-- data 가 유저가 보낸 데이터
    console.log("유저가 보낸거: ", data);
  });
});

그래서 정해져 있는 데이터가 아닌 input 태그에 입력한 데이터를 보낼 경우는 아래와 같이 작성하면 됨

<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>
  <input id="input" type="text">
  <button id="send">서버에 메시지 보내기</button>


  <script>
    var socket = io()
    $('#send').click((e) => {
      // socket.emit('보낼 데이터 이름','보낼 데이터')
      socket.emit('user-send', $('#input').val())
    })
  </script>
</body>

이제 서버에서 유저에게 데이터를 보내는 방법

// 누가 웹소켓에 접속하면 내부 코드를 실행해달라는 코드
io.on("connection", (socket) => {
  console.log("접속됨.");

  // 유저가 데이터를 보내면 서버가 수신할 수 있도록 코드 작성하기
  socket.on("user-send", (data) => {
    // data 가 유저가 보낸 데이터
    // console.log("유저가 보낸거: ", data);

    // 유저에게 보낼 데이터
    io.emit("broadcast", data);
  });
});

서버에서 보낸 데이터 유저가 받기

<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>
  <input id="input" type="text">
  <button id="send">서버에 메시지 보내기</button>


  <script>
    var socket = io()
    $('#send').click((e) => {
      // 서버에 데이터를 보낼 때
      // socket.emit('보낼 데이터 이름','보낼 데이터')
      socket.emit('user-send', $('#input').val())
    })

    socket.on('broadcast', (data) => {
      console.log('서버가 보낸 데이터: ', data);
    })
  </script>
</body>

여기서 io.emit은 socket이 연결된 모든 유저에게 데이터를 보내는 것.

이유는 보통 채팅 시스템을 생각하면 이해가 편하다.

그래서 시크릿 창을 열어 하나의 페이지에서 입력 값을 전송하게 되면 똑같이 콘솔창에 서버에서 데이터를 동시에 받게 된다.

우선 단체 채팅을 만들고자 한다면 아래와 같이 할 수 있을 것.

<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>
  <input id="input" type="text">
  <div id="content">

  </div>
  <button id="send">서버에 메시지 보내기</button>



  <script>
    var socket = io()
    $('#send').click((e) => {
      // 서버에 데이터를 보낼 때
      // socket.emit('보낼 데이터 이름','보낼 데이터')
      socket.emit('user-send', $('#input').val())
    })

    socket.on('broadcast', (data) => {
      $('#content').append('<div><span>' + data + '</span></div>')
    })
  </script>
</body>

만약 서버와 유저 1명간

즉 특정 유저와의 소통을 원한다면

socket.to(socket.id).emit()

// 누가 웹소켓에 접속하면 내부 코드를 실행해달라는 코드
io.on("connection", (socket) => {
  console.log("접속됨.");

  // 유저가 데이터를 보내면 서버가 수신할 수 있도록 코드 작성하기
  socket.on("user-send", (data) => {
    // 특정 유저에게 보낼 데이터
    //  socket을 날린 사용자에게 데이터를 전송하게 됨
    // console.log(socket.id);  // <--- 요청을 한 사용자, 유저 구분이 가능
    io.to(socket.id).emit("broadcast", data);
  });
});

채팅방 여러개 만드는 방법

우선 서버에서 채팅방을 만드는 방법은 socket.join(’방 이름’) 을 하면 된다.

// 누가 웹소켓에 접속하면 내부 코드를 실행해달라는 코드
io.on("connection", (socket) => {
  console.log("접속됨.");

  // // 채팅방 개설하는 방법
  // socket.join("room1"); // <-- 개설을 하고 유저 또한 함께 참가할 수 있게 해줌

  socket.on("joinroom", (data) => {
    socket.join(data);
  });

  socket.on("room1_send", (data) => {
    io.to("room1").emit("broadcast", data);
  });  

});
<body>
  <%- include('nav.html') %>

  <h2>채팅방</h2>
  <input id="input" type="text">
  <div id="content">

  </div>
  <button id="send">서버에 메시지 보내기</button>

  <button id="room1">채팅방 1</button>
  <button id="room1_send">채팅방1 에서 메세지보내기</button>



  <script>
    var socket = io()

    $('#room1').click((e) => {
      // socket을 사용해서 GET, POST 요청을 서버에 할 경우엔
      //  그냥 socket으로 대체 할 수 있다.
      socket.emit('joinroom', 'room1')
    })

    $('#room1_send').click((e) => {
      socket.emit('room1_send', $('#input').val())
    })

    $('#send').click((e) => {
      // 서버에 데이터를 보낼 때
      // socket.emit('보낼 데이터 이름','보낼 데이터')
      socket.emit('user-send', $('#input').val())
    })

    socket.on('broadcast', (data) => {
      $('#content').append('<div><span>' + data + '</span></div>')
    })
  </script>
</body>