본문 바로가기
SeSAC

[새싹X코딩온] 풀스택 34주차 회고록 WebSocket / Socket.IO

by 리잼 2023. 11. 21.
반응형

2차 프로젝트에서 사용했던 Socket 기술을 정리해본다

 

TCP / IP

  • 컴퓨터 네트워크에서 데이터 통신을 위한 프로토콜 스택

TCP ( Transmission Control Protocol )

TCP 는 데이터를 신뢰성 있게 전송하기 위한 프로토콜

TCP / IP 4 계층은 OSI 7계층을 간소화한 것이다

네트워크 인터페이스 계층

  • 물리 계층과 데이터 링크 계층에 해당
  • 데이터를 전기 신호로 변환하거나 광 신호로 변환하여 전송
  • MAC ( Media Access Control ) 주소를 관리

인터넷 계층

  • 네트워크 계층에 해당
  • 데이터 패킷의 라우팅과 논리적인 주소 지정을 담당
  • IP 프로토콜이 이 계층에서 작동하며, 패킷의 출발지와 목적지 IP 주소를 사용하여 라우팅 수행

전송 계층

  • 신뢰성과 흐름 제어를 관리
  • TCP와 UDP가 이 계층에서 작동

응용 계층

  • 최종 사용자에게 서비스를 제공하기 위한 응용프로그램과 사용자 인터페이스가 이 계층에 위치
  • 다양한 프로토콜을 포함하며, HTTP, FTP, SMTP, POP3, IMAP, DNS 등의 프로토콜이 이 계층에서 동작

Socket

  • 서버와 클라이언트를 연결해주는 도구로써 인터페이스 역할
    • 서버 : 클라이언트 소켓의 연결 요청을 대기하고, 연결 요청이 오면 클라이언트 소켓을 생성해 통신을 가능하게 하는 것
    • 클라이언트 : 실제 데이터 송수신이 일어나는 곳
  • 소켓은 프로토콜, IP 주소, 포트 넘버로 정의된다.
  • TCP 와 UDP 프로토콜을 사용하여 데이터를 전송

소켓 프로그래밍 흐름

  • 서버(Server)
    • socket() : Socket 생성 함수
    • bind() : ip와 port 번호 설정 함수
    • listen() : 클라이언트의 요청에 수신 대기열을 만드는 함수
    • accept() : 클라이언트와의 연결을 기다리는 함수
  • 클라이언트(client)
    • socket() : 소켓을 여는 함수
    • connect() : 통신할 서버의 설정된 ip와 port 번호에 통신 을 시도하는 함수
      통신 시도 시, 서버가 accept()함수를 이용해 클라이언트의 socket descriptor를 반환
      이를 통해 클라이언트와 서버가 서로 read() write()를 반 복하며 통신

사용자가 화면을 새로고침 하지 않아도 데이터를 갱신해야 할 때 소켓을 사용하게된다

일반적인 웹 데이터 송수신

  • HTTP 프로토콜 : 요청을 보내야지만 서버가 응답 ( 단방향 통신 )

웹 소켓

  • 화면 새로고침 없이 실시간성을 필요로 하는 앱에서 사용 ( 양방향 통신 )
  • 실시간 네트워킹 유리
  • 브로드캐스팅 ( 여러 컴퓨터에 데이터를 일괄 전송 ) 지원

WebSocket 이란

  • 양방향 소통을 위한 프로토콜 
    • HTML5 웹 표준 기술
    • 빠르게 작동하며 통신할 때 아주 적은 데이터를 이용
    • 이벤트를 단순히 듣고, 보내는 것만 가능
    • Handshake : 클라이언트가 서버로 웹소켓 연결을 요청할 때, 서버와 클라이언트 측에서는
      브라우저의 WebSocket 객체를 사용하여 웹소켓 연결을 생성하고 관리

WebSocket 이벤트

  • open: 웹소켓 연결이 성공적으로 열렸을 때 발생
  • message: 웹소켓을 통해 데이터를 주고받을 때 발생
  • error: 웹소켓 연결 중 오류가 발생했을 때 발생. 연결 실패, 통신 오류 등이 해당
  • close: 웹소켓 연결이 종료되었을 때 발생

ws 모듈 이벤트

  • connection: 클라이언트가 웹소켓 서버에 연결되었을 때 발생. 이 이벤트 의 콜백 함수는 새로운 클라이언트 연결마다 실행 
  • message: 클라이언트로부터 메시지를 받았을 때 발생
  • error: 웹소켓 연결 중 오류가 발생했을 때 발생
  • close: 클라이언트와의 연결이 종료되었을 때 발생

server.js

const ws = require('ws');
const express = require('express');
const app = express();
const PORT = 8000;

app.set('view engine', 'ejs');

app.get('/', (req, res) => {
    res.render('client');
});

const server = app.listen(PORT, () => {
    console.log(`http://localhost:${PORT}`);
});

const wsServer = new ws.Server({ server }); // 웹 소켓 서버 접속

const sockets = []; // 클라이언트들을 저장할 배열

wsServer.on('connection', (socket) => {
    console.log('클라이언트가 연결되었습니다.');

    sockets.push(socket);

    // 클라이언트의 메세지 수신
    socket.on('message', (msg) => {
        console.log(`클라이언트로부터 받은 메세지 : ${msg}`);

        // 웹소켓 서버에 접속한 모든 클라이언트에게 메세지 전송
        // 브로드캐스팅(여러 컴퓨터에게 데이터를 전송)
        sockets.forEach((socket) => {
            socket.send(`${msg}`);
        });
    });

    socket.on('error', (err) => {
        console.log('오류가 발생했습니다.', err);
    });

    socket.on('close', () => {
        console.log('클라이언트와 연결이 종료되었습니다.');
    });
});

 

client.js

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Web Socket 채팅</title>
    </head>
    <body>
        <h1>채팅</h1>

        <!-- 채팅 내역 -->
        <ul></ul>

        <!-- 채팅 인풋 -->
        <form id="chat">
            이름<input type="text" id="name" class="name" /> <br />
            내용<input type="text" id="message" class="message" /> <br />
            <button>전송</button>
        </form>
        <script>
            const socket = new WebSocket('ws://localhost:8000');

            const chatForm = document.querySelector('#chat'); // 폼 가져오기
            const ul = document.querySelector('ul'); // 채팅 내역을 보여주기 위한 ul 요소

            console.log(socket);
            socket.addEventListener('open', (event) => {
                console.log('서버 연결 완료');
            });
            socket.addEventListener('close', (event) => {
                console.log('서버 연결 종료');
            });

            socket.addEventListener('message', (event) => {
                const data = JSON.parse(event.data);
                console.log(data);
                const { msg, name } = data;

                const li = document.createElement('li');
                li.textContent = `${name}님 - ${msg}`;
                ul.appendChild(li);
            });

            socket.addEventListener('error', (event) => {
                console.log('오류 발생 : ', event.console.error);
            });

            // 인풋에 입력한 정보를 바탕으로 메세지 데이터 생성
            chatForm.addEventListener('submit', (event) => {
                event.preventDefault();
                const msg = chatForm.querySelector('#message');
                const name = chatForm.querySelector('#name');
                data = { msg: msg.value, name: name.value };
                console.log('보내려는 메세지 데이터: ', data);

                // 웹 소켓 서버가 데이터를 쉽게 처리할 수 있도록
                // JSON 형식의 텍스트 데이터로 변환하여 웹서버로 전송
                socket.send(JSON.stringify(data));

                msg.value = '';
                name.value = '';
            });
        </script>
    </body>
</html>

 


Socket.IO

  • 클라이언트와 서버 간의 짧은 지연 시간, 양방향 및 이벤트 기반 통신을 실시간 으로 가능하게 하는 라이브러리 
  • WebSocket 프로토콜 위에서 구축되었으며 통신 과정을 단순화하고 개선하 기 위한 추가 기능을 제공
  • 특징
    1. 이벤트 기반 ( 사용자가 임의로 설정이 가능하다 )
    2. 자동 재연결

socket.io 를 사용하기위한 기본 코드 구성

 

server.js 에서의 ' connection ' 이벤트 추가

// io.on(): socket 관련 통신 작업을 처리
io.on('connection', (socket) => {
    // connection 이벤트는 클라이언트가 접속했을 때 발생
    // 콜백 함수 인자로 socket 객체를 제공
    console.log('서버 연결', socket);
});

 

chat.ejs  에서의 ' connect ' 이벤트 추가

let socket = io.connect(); // 소켓 사용을 위한 객체 생성

            socket.on('connect', () => {
                console.log('클라이언트 연결 완료 : ', socket);
            });

소켓 서버에 연결 성공한 모습

 

SocketID ?

  • 소켓의 고유 ID ( 브라우저 탭 단위 )

소켓은 클라이언트가 연결될 때마다 각 클라이언트에 다른 ID를 부여한다
좌 : 클라이언트            우 : 서버

'hello' 라는 이벤트를 만들어 위의 코드를 작성하면 아래와 같은 결과를 가질 수 있다.

한마디로 소켓의 동작과정은

클라이언트의 요청 > 소켓서버가 요청을 받고 > 다시 클라이언트로 해당 메세지를 보냄 > 서버에서 보내온 메세지 출력 순이다

반응형