Web Socket & Socket.IO (23/01/26)

nazzzo·2023년 1월 29일
0

1. 웹소켓



HTTP 통신에서 클라이언트가 서버에 요청을 하면 서버는 응답을 보내줍니다
하지만 요청과 응답 과정이 끝나는 즉시 둘 사이의 연결은 끊기게 되죠

웹소켓은 이러한 HTTP의 비연결성으로 인한 불편함을 극복하고
실시간 통신을 구현하기 위해 고안된 통신 프로토콜입니다

웹소켓 (Web Socket)이란?
웹소켓은 OSI 7계층 모델에서 트랜스포트 계층(4계층)과 어플리케이션 계층(7계층)을 연결하는 기술입니다
HTTP 프로토콜에서는 4계층의 TCP 커넥션을 열고 닫으면서 데이터를 주고 받습니다
하지만 웹소켓은 '양방향 통신'을 지원하기 위해 커넥션을 연결 상태(논리적 연결)로 유지하며,
7계층에서 데이터를 주고 받는다는 차이가 있습니다

  • ws:// 프로토콜을 사용합니다 (http 레이어 위에서 작동합니다)
  • 웹소켓 통신은 양방향 통신을 지원하며 서버와 클라이언트,
    그리고 클라이언트끼리도 실시간으로 데이터를 주고받을 수 있습니다

2. 웹소켓 사용


아래 예제 코드는 클라이언트에서 사용할 경우입니다

  <script>
    const socket = new WebSocket("ws://localhost:8545");

    socket.onopen = (event) => {
      socket.send("Hello Server");
    };

    socket.onmessage = (event) => {
      console.log("서버로부터 온 메세지: ", event.data);
    };

    socket.onclose = (event) => {
      console.log("연결 종료");
    };
  </script>
  • WebSocket은 자바스크립트 내장 객체입니다
  • ws:// 프로토콜을 사용합니다
  • 웹소켓 통신은 이벤트 기반으로 구현됩니다 (onopen, onmessage, onclose)

예제2

<body>
    <form action="" id="frm">
        <input type="text" name="message">
        <button type="submit">전송</button>
    </form>

    <ul id="chat"></ul>
    <script>
        const socket = new WebSocket("ws://localhost:3000/namespace/room1")
        const ul = document.querySelector("#chat")
        
        socket.addEventListener("message", (event)=>{
            // console.log(event)
            // console.log(`서버로부터 온 메세지 : ${event.data}`)

            const li = document.createElement("li")
            li.innerHTML = event.data
            chat.append(li)
        })

        const frm = document.querySelector("#frm")
        frm.addEventListener("submit", (e)=>{
            e.preventDefault()

            const { message } = e.target
            socket.send(message.value)

            const li = document.createElement("li")
            li.classList.add('right')
            li.innerHTML = message.value
            chat.append(li)

            e.target.reset()
            message.focus()
        })
    </script>



다음은 Node 환경에서의 웹소켓 세팅 예제입니다


const WebSocket = require("ws");
// npm install ws

module.exports = (http) => {
  let sockets = [];
  const server = new WebSocket.Server({
    server: http, // server ~ WebSocket 서버에 연결할 HTTP를 지정합니다
    // port: 8545, // port ~ WebSocket 서버에서 사용할 포트를 지정합니다
  });

  // 웹소켓(양방향) 통신은 이벤트 기반으로 구현됩니다 ~ connection, message, error , close
  // 콜백의 매개변수는 socket 객체와 request
  server.on("connection", (socket, request) => {
    console.log(request.url)

    // === const { connection: {remoteAddress: ip }} = request
    const ip = request.connection.remoteAddress;

    console.log(`클라이언트 접속 ${ip}`);
    // console.log(socket.readyState); // 웹소켓의 연결상태 1~4 (1=OPEN)
    if (socket.readyState === socket.OPEN) {
      sockets.push(socket);
      console.log(` 접속자수:`, sockets.length);
      socket.send(`${ip}님 환영합니다 - back`);
    }

    socket.on("message", (msg) => {
      console.log(`${ip}로부터 받은 메세지 ${msg}`);
      for (const client of sockets) {
        if (client === socket) continue // *broadcast : 나를 제외한 모든 접속자에게 실행

        client.send(`${msg}`);
      }

      // 주고받은 메세지 내역(채팅 로그)을 DB에 저장할 수 있습니다 (insert문)
    });

    socket.on("close", () => {
      sockets = sockets.filter((v) => v !== socket); // 새로고침시 중복카운팅을 막기 위해서 sockets 배열 재할당
    //   console.log(`${ip}님의 접속이 종료되었습니다`)
    });
  });
};



요청과 응답 과정도 HTTP와는 다소 차이가 있습니다

요청

GET / HTTP/1.1
Upgrade websocket
Connection: Upgrade
Sec-WebSoket-key: adasdad....

응답

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSoket-key: zxczxc...
  • 웹소켓 통신의 성공 응답은 101번입니다
  • Sec-WebSoket-key : 보안을 위해 브라우저에서 생성한 키이며 고유 식별자 역할을 합니다
  • 하나의 프로세스에서 HTTP 서버와 웹소켓용 서버를 분리하더라도
    서로 같은 포트 넘버를 사용할 수 있습니다
    (서로 프로토콜이 다르기 때문에 가능합니다.
    http://localhost:3000 & ws://localhost:3000)

네임 스페이스? 룸? 소켓?
네임스페이스, 룸, 소켓은 웹소켓 프로그래밍에서 하나의 웹소켓 연결을
여러 개의 분리된 공간으로 나누는 기술을 말합니다

ex) url/namespace2/room1

라우터 경로 설정과도 비슷한 개념으로, 네임스페이스가 채널이라면 룸은 채팅방 쯤으로 볼 수도 있겠습니다
(네임스페이스 > 룸 > 소켓 순)



3. Socket.IO


Socket.IO는 웹소켓 기술을 기반으로
실시간 네트워크 어플리케이션을 개발하기 위해 탄생한 자바스크립트 라이브러리입니다

WebSocket? Socket.IO?
Socket.IO는 웹소켓을 기반으로 개발되었기 때문에 좀 더 사용자 친화적인 API를 제공합니다
(AJAX와 Axios의 관계와 비슷하다고 봐야 할까요...)

둘의 가장 큰 차이는 Socket.IO는 웹소켓을 지원하지 않는 구형 브라우저에서도
실시간 데이터 전송이 가능하도록 대안 제공을 해준다는 것입니다



4. Socket.IO 사용


<!--back-->
npm install socket.io

<!--front (cdn도 필요)-->
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>

*Socket.IO 방식을 사용할 때는 CDN도 필요합니다


백엔드

const SocketIO = require("socket.io");

// server === app.listen
module.exports = (server, app) => {
  const io = SocketIO(server);

  io.on('connection', (socket)=>{
    // 임의로 이벤트를 생성해서 사용합니다 ~ DB에 내역을 저장하려면 INSERT문 필요
    socket.on('data', (data)=>{
        const json = { userid: 'web7722', data}
        socket.broadcast.emit('reply', JSON.stringify(json))
    })
    // socket.on('hello', (data)=>{
    //     console.log("hello :", data);
    // })
  })
};

프론트엔드

<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>Document</title>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
    <style>
        .right {
            text-align: right;
        }
    </style>
</head>

<body>
    <form action="" id="frm">
        <input type="text" name="message">
        <button type="submit">전송</button>
    </form>

    <ul id="chat"></ul>
    <script>
        // ws://이 아닌 http://를 써야 합니다 (polling 방식의 특징)
        const socket = io.connect('http://localhost:3000', {
            path: '/socket.io',
            transports: ['websocket'],
        })

        socket.on('reply', (data) => {
            // console.log('data :',data)
            const json = JSON.parse(data)
            console.log(json)
            const li = document.createElement("li")
            li.innerHTML = json.userid + ' : ' + json.data
            chat.append(li)
        })

        const frm = document.querySelector("#frm")
        frm.addEventListener("submit", (e) => {
            e.preventDefault()

            const { message } = e.target
            socket.emit('data', message.value)
            // === socket.send(message.value)

            const li = document.createElement("li")
            li.classList.add('right')
            li.innerHTML = message.value
            chat.append(li)

            e.target.reset()
            message.focus()
        })
    </script>
</body>

0개의 댓글