본문 바로가기
Today I Learned/SeSAC 웹 2기

Node.js Web Socket 💬 | 15주차(1)

by suyeonnie 2025. 2. 5.

📂 오늘 배운 내용

Node.js와 WebSocket(ws) 라이브러리를 이용한 간단한 실시간 채팅 서버

  • 서버 : 클라이언트 연결 시마다 고유한 ID를 생성하여 관리
  • 클라이언트 : 채팅 메시지를 입력하면 서버로 전송되고, 서버는 이걸 모든 클라이언트에게 보냄

그 외

  • 랜덤 문자열 생성에서 사용한 함수들 random, toString, substinrg

TCP/IP

TCP (Transmission Control Protocol)

  • 네트워트 간 데이터 교환 기술
  • 데이터 신뢰성 있게 전송하기 위한 프로토콜(안녕하세요를 보냈을 때 순서대로 깨지지 않고 전달되는 것)
  • 정확한 전송 보장

IP (Internet Protocol)

  • 데이터 주고받기 위한 통신규약(약속)
  • 패킷 기반 : 데이터를 가장 작게 쪼갠 단위

 

TCP/IP 4계층 = OSI 7계층

 

같은 건데 흐름을 어떤 기준으로 나누냐에 따라 달라짐

둘 다 네트워크 통신 계층화하여 구조적으로 설명하는 모델

 

TCP/IP 4계층

  • 네트워크 인터페이스 : 이더넷, 와이파이 기술
  • 인터넷 계층 : IP 프로토콜 작동, 패킷의 출발지와 목적지
  • 전송: TCP(신뢰성) UDP(신뢰성은 떨어지지만 속도가 더 빠름)가 여기서 작동!!
    • 소켓은 TCP/UDP 프로토콜 사용해서 작성 -> 실습에서는 TCP 기반 소켓 사용할 예정 👀
    • TCP(Transmission Control Protocol) : 연결형 프로토콜 / 이메일, 웹사이트접속(http), 파일다운로드(FTP) 
    • UDP(User Datagram Protocol) : 비연결형 / 스트리밍, 온라인 게임 등에서 사용
  • 응용: 사용자와 가장 가깝다. HTTP/ FTP 등 다양한 프로토콜이 여기서 동작함

 


Socket

 

 

  • 프로세스가 네트워크로 데이터를 내보내거나 받기 위한 실제적인 창구 역할
    • 2가지 종류 (TCP, UDP) 중 하나 사용해서 네트워크 통신을 위한 엔드포인트 생성하는 개념!
  • 서버와 클라이언트 연결해주는 도구(인터페이스 역할)
    • 서버 : 클라이언트 소켓의 연결 요청 대기-> 연결 요청 오면 클라이언트 소켓 생성해 통신을 가능하게 함
    • 클라이언트 : 실제로 데이터 송수신이 일어나는 곳
  • 소켓 정의 : 프로토콜, IP 주소, 포트 넘버

 

HTTP vs Socket.io

HTTP

  • http는 새로고침해야 데이터가 오는 걸 확인할 수 있음
  • 클라이언트가 요청하면 서버가 응답하는 방식
  • 예 : 웹 페이지, API 호출

 

Socket.io

  • 실시간 양방향 통신 가능
  • 새로고침 없이도 서버에서 데이터가 오면 즉시 반영됨
  • 예 : 카카오톡, 실시간 주식 가격, 채팅!

 

소켓 프로그래밍의 흐름

이 메서드들은 다른 언어에서 사용하는 거 소켓에서는 사용 안 함

메서드 참고만 하기

 


Web Socket

💡💡 자바스크립트에서는 일반 소켓 사용 불가 웹 소켓 써야함

  • 한번 연결만 되면 요청과 응답의 순서가 상관없어짐
  • HTML5 웹 표준 기술이라 cdn 추가할 필요 없음

  • 웹 소켓은 양방향 소통을 위한 프로토콜(약속)
    • 이벤트 단순히 듣고 보내는 것만 가능
    • handshake: 서버와 클라이언트가 정상 연결됐는 지 확인하는 작업-> 확인 되면 웹소켓을 통해 실시간 통신 가능

Web Socket 이벤트

클라이언트

 

  • 클라이언트(html) 단에서 이루어지는 일
  • 소켓 객체를 만들어주고 addEventLister로 처리

💡 클라이언트 websocket 이벤트
      - open : 웹 소켓이 성공적으로 열렸을 때
      - message : 웹 소켓으로 데이터를 주고 받을 때
      - error : 웹 소켓 연결 중 오류가 발생했을 때
      - close : 웹 소켓 연결이 종료됐을 때

 

서버

백엔드에서는 설치 필요

npm i ws

 

 

  • connection, message, error, close
  • connection만 프론트와 다름 -> 이 이벤트의 콜백함수는 새로운 클라이언트 연결마다 실행
  • addevnetlistner 없어서 On이라는 함수를 씀

💡 ws 모듈 이벤트 (이벤트 핸들러)
connection : 클라이언트와 웹 소켓 서버가 연결되었을 때
- message : 클라이언트에게 메시지를 받았을 때
- error : 연결 중 오류
- close: 클라이언트와 연결 종료

 

 

소켓은 api만드는 건 아니고

 


간단 채팅방

 

🐶

 

클라이언트

: 웹 소켓을 이용해 채팅 메시지를 서버에 보내고, 서버에서 받은 메시지를 화면에 표시

 

[client.ejs]

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>간단 채팅 만들기(Web Socket)</title>
  </head>
  <body>
    <h4>간단 채팅방</h4>
    <h5>채팅 내용</h5>
    <ul></ul>

    <h5>채팅 입력</h5>
    <form id="chat">
      <label>이름: <input type="text" id="name" /></label>
      <label>내용: <input type="text" id="msg" /></label>
      <button>채팅 보내기</button>
    </form>

    <script>
      // ✅ 웹소켓 서버와 연결 ('ws://통신 주소');
      const socket = new WebSocket('ws://localhost:8080');
      const msg = document.querySelector('#msg');
      const name = document.querySelector('#name');
      const chatForm = document.querySelector('#chat');
      const ul = document.querySelector('ul');

      // ✅ WebSocket 이벤트 핸들러
      // [1] 서버와 연결이 완료됐을 때
      socket.addEventListener('open', (e) => {
        console.log('서버에 연결되었습니다!');
        // console.log('e', e);
        // socket.send('채팅방 입장~!'); // 서버에게 메시지를 보냄
      });

      //  [2] 서버로부터 메시지를 받았을 때
      socket.addEventListener('message', (e) => {
        console.log('서버로부터 받은 메세지', e.data); //// {'msg': 'ddd', 'name': 'ddd'}
        const data = JSON.parse(e.data); // json >> object
        console.log('객체로 변환!', data);

        // 채팅 내용 화면에 추가
        const li = document.createElement('li');
        li.textContent = `${data.name}: ${data.msg}`;
        ul.append(li);
      });

      // [3] 사용자가 메시지 입력 후 전송 버튼 눌렀을 때
      chatForm.addEventListener('submit', (e) => {
        e.preventDefault();

        // 사용자가 입력한 데이터 객체 생성
        const chatData = { msg: msg.value, name: name.value };
        console.log('chatData', chatData); // {msg: 'ddd', name: 'ddd'}

        // ❓ chatData를 JSON으로 바꿔서 보내는 이유
        // socket.send(chatData); // '클라이언트: 객체 -> 서버: 객체' 로 전달됨

        // 💡서버에 데이터 전송 (string으로 변경해서 보내야함)
        // 왜냐면 우리가 원하는 형식으로 보내기 위해서! (string -> string)
        socket.send(JSON.stringify(chatData));

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

 

서버

: 웹 소켓 서버를 만들고 연결된 클라이언트와 메시지 주고 받음

 

[server.js]

// npm i ejs express ws

// [1] 웹 서버 및 웹 소켓 서버 생성
const express = require('express'); //웹 서버 프레임워크
const ws = require('ws'); //ws 라이브러리
const app = express();
const PORT = 8080;

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

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

// [2] 서버 실행
const server = app.listen(PORT, () => {
  console.log(`http://localhost:${PORT}`);
});

// console.log(server); // 서버 객체

// [4] 연결될 클라이언트마다 고유한 ID 값 설정 (랜덤 문자열 생성)
function generateUniqueId() {
  const timestamp = Date.now().toString(36); //현재 시간을 36진수로 변환
  // console.log('timestamp', timestamp); //m6ooly28

  // - 좀 더 복잡한 id
  const randomString = Math.random().toString(36).substring(2); //랜덤 문자열 추가
  // console.log('randomString', randomString); //4dlmth06aqv

  return timestamp + randomString;
}

// console.log('id', generateUniqueId()); //m6ooly284dlmth06aqv

// ---------- [5] 클라이언트가 웹 소켓 서버에 연결될 때 실행되는 코드 --------
const sockets = []; // 클라이언트 소켓들을 저장하는 배열

// [3] 웹 소켓 서버 생성
// - 기존 Express 서버를 웹 소켓 서버와 연결 -> 웹 소켓을 통한 실시간 통신 가능해짐
const wsServer = new ws.Server({ server: server });

// - 클라이언트가 웹 소켓 서버에 연결될 때 실행
wsServer.on('connection', (socket) => {
  console.log(socket); // 연결될 하나의 클라이언트(브라우저)에 대한 정보
  const clientId = generateUniqueId();
  console.log(`클라이언트 id: [${clientId}] 연결됨`);

  sockets.push(socket); // 연결된 새로운 클라이언트 socket을 배열에 추가

  // 메시지 수신 이벤트 (클라이언트 -> 서버)
  socket.on('message', (message) => {
    // 메시지는 버퍼 객체 <Buffer ec b1 84 ed 8c 85 eb b0 a9 20 ec 9e 85 ec 9e a5 7e 21>

    // console.log('=------------');
    // console.log(message); // 버퍼객체
    // console.log(message.toString()); // 문자열 '채팅방 입장~!'
    console.log(`${clientId} 에게 받은 메시지 : ${message}`); 
    // [object object] -> 클라이언트 JSON처리 후 {"msg" : "ddd", "name":"ddd"}
    // console.log('=------------');

    // ✅ 현재 연결된 소켓에게만 메시지 전송
    // - socket: 요청하고 있는 클라이언트 1개
    // socket.send('안녕하세요?');

    // ✅ 연결된 모든 클라이언트에게 메시지 전송
    sockets.forEach((client) => {
      // client.send(`${client}`); // 👾 에러 발생한 이유: 문자열[object object]을 객체로 바꿔주라고 했기때문!!!
      client.send(`${message}`); //버퍼 객체 암시적 호출
    });
  });
});
// ---------------------------------------------------------------

 

 

랜덤 문자열 생성

🔍 substring 함수 알아보기

console.log('abcdef'.substring(2)); //cdef
console.log('abcdefgh'.substring(2, 5)); //cde

 

 


🔖  관련 글

Node.js WebSocket 메시지 처리와 Buffer 객체 변환 이해하기 🚀

 

🌐  참고

서울시 청년취업사관학교(SeSAC) x 코딩온 웹 개발자 풀스택 과정

31.web-socket

 

MDN : Number.toString()