[Node.js] WebSocket 실시간 채팅 기능

fasongsong·2024년 6월 24일

실습

목록 보기
1/4

WebSocket 기본 [하단 링크 참고]
https://velog.io/@fasongsong/WebSocket-%EC%9D%B4%EB%9E%80



📌 초기 환경 구성


  • 정적 파일 위치 : "front" 디렉토리가 index.js와 같은 디렉토리에 위치
  • 포트 번호 확인: 포트 8000에서 실행. localhost:8080이 아니라 localhost:8000에서 확인!!
  • 디렉토리 구조:
project-root
│
├── front
│   └── index.html
└── index.js


📌 문제 이해


  • 간단한 채팅 기능을 구현하기 위해, 다음과 같은 단계로 코드 설계
  1. 브로드캐스팅 기능 구현: 모든 클라이언트에게 메시지를 전송하는 기능을 추가.

  2. 서버 접속 및 연결 해제 정보 전송: 클라이언트 접속 및 연결 해제 시 모든 클라이언트에게 정보를 전송.

  3. 사용자 이름과 메시지 전송: 클라이언트가 사용자 이름과 메시지를 전송하고, 이를 모든 클라이언트에게 전달.



📌 문제 설계


  • 서버 코드 (index.js)

    • clients 집합에 클라이언트를 추가 및 제거하여 현재 접속된 사용자 수를 유지한다.
    • 새로운 유저가 접속하면 새로운 유저 접속 [현재 : x명] 메시지를 모든 클라이언트에 전송한다.
    • 유저가 연결을 해제하면 유저 연결 해제 [현재 : x명] 메시지를 모든 클라이언트에 전송한다.
    • 클라이언트로부터 받은 메시지를 JSON 형식으로 파싱하여 모든 클라이언트에 브로드캐스트한다.
  • 클라이언트 코드 (index.html)

    • 서버에 연결되면 콘솔에 로그를 남긴다.
    • 서버로부터 메시지를 받으면, 채팅 창에 추가한다.
    • sendMsg 함수는 사용자가 입력한 이름과 메시지를 서버로 전송한다. 메시지 입력 필드를 초기화한다.
    • 채팅 창에 새로운 메시지나 알림이 추가될 때마다 자동으로 스크롤이 맨 아래로 이동한다.


📌 코드 상세 설명


index.js

  • express와 ws(WebSocket)를 사용하여 웹 서버와 WebSocket 서버를 구현

1. 모듈 가져오기

const express = require("express");
const { WebSocketServer } = require("ws");
  • express 모듈과 ws 모듈을 가져온다.
  • express는 웹 서버를 쉽게 만들 수 있게 해주고, ws는 WebSocket 서버를 만들 수 있게 해준다.

2. Express 애플리케이션 생성 및 정적 파일 서비스 설정

const app = express();
app.use(express.static("front"));
  • express 애플리케이션을 생성하고, front 폴더에 있는 파일들을 정적 파일로 서빙하도록 설정한다.
  • 이를 통해 front 폴더에 있는 HTML, CSS, JS 파일 등을 클라이언트에게 제공한다.

3. HTTP 서버 시작

app.listen(8000, () => {
    console.log(`Server listening on port 8000`);
});
  • 포트 8000번에서 HTTP 서버를 시작하고, 서버가 시작되면 콘솔에 메시지를 출력한다.

4. WebSocket 서버 생성

const wss = new WebSocketServer({ port: 8001 });
  • 포트 8001번에서 WebSocket 서버를 생성한다.

5. 클라이언트 연결 관리

const clients = new Set();
  • 현재 연결된 클라이언트를 관리하기 위해 Set을 사용한다.

6. WebSocket 연결 이벤트 처리

wss.on("connection", ws => {
    clients.add(ws);
    console.log("New client connected");

    // Notify all clients about the new connection
    const connectMsg = `새로운 유저 접속 [현재 : ${clients.size}명]`;
    for (let client of clients) {
        if (client.readyState === ws.OPEN) {
            client.send(JSON.stringify({
                type: "notification",
                msg: connectMsg
            }));
        }
    }
  • 새로운 클라이언트가 연결되면 clients Set에 추가하고, 모든 클라이언트에게 새로운 클라이언트가 접속했음을 알리는 메시지를 보낸다.

7. 메시지 수신 및 브로드캐스트

ws.on("message", data => {
    const message = JSON.parse(data);
    console.log(`Received from client: ${message.name}: ${message.msg}`);
    
    // Broadcast the message to all clients
    for (let client of clients) {
        if (client.readyState === ws.OPEN) {
            client.send(JSON.stringify({
                type: "message",
                name: message.name,
                msg: message.msg
            }));
        }
    }
});
  • 클라이언트로부터 메시지를 수신하면, 이를 파싱하여 콘솔에 출력하고, 모든 클라이언트에게 브로드캐스트한다.

8. 연결 종료 이벤트 처리

ws.on("close", () => {
    clients.delete(ws);
    console.log("Client disconnected");

    // Notify all clients about the disconnection
    const disconnectMsg = `유저 연결 해제 [현재 : ${clients.size}명]`;
    for (let client of clients) {
        if (client.readyState === ws.OPEN) {
            client.send(JSON.stringify({
                type: "notification",
                msg: disconnectMsg
            }));
        }
    }
});
  • 클라이언트가 연결을 종료하면 clients Set에서 제거하고, 모든 클라이언트에게 연결 종료 사실을 알린다.


📌 최종 코드 원본


index.js

const express = require("express");
const { WebSocketServer } = require("ws");
const app = express();

app.use(express.static("front"));

app.listen(8000, () => {
    console.log(`Server listening on port 8000`);
});

const wss = new WebSocketServer({ port: 8001 });

const clients = new Set();

wss.on("connection", ws => {
    clients.add(ws);
    console.log("New client connected");

    // Notify all clients about the new connection
    const connectMsg = `새로운 유저 접속 [현재 : ${clients.size}명]`;
    for (let client of clients) {
        if (client.readyState === ws.OPEN) {
            client.send(JSON.stringify({
                type: "notification",
                msg: connectMsg
            }));
        }
    }

    ws.on("message", data => {
        const message = JSON.parse(data);
        console.log(`Received from client: ${message.name}: ${message.msg}`);
        
        // Broadcast the message to all clients
        for (let client of clients) {
            if (client.readyState === ws.OPEN) {
                client.send(JSON.stringify({
                    type: "message",
                    name: message.name,
                    msg: message.msg
                }));
            }
        }
    });

    ws.on("close", () => {
        clients.delete(ws);
        console.log("Client disconnected");

        // Notify all clients about the disconnection
        const disconnectMsg = `유저 연결 해제 [현재 : ${clients.size}명]`;
        for (let client of clients) {
            if (client.readyState === ws.OPEN) {
                client.send(JSON.stringify({
                    type: "notification",
                    msg: disconnectMsg
                }));
            }
        }
    });
});

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Websocket 실습</title>
    <style>
        #chat {
            border: 1px solid #000;
            height: 300px;
            overflow-y: scroll;
            margin-bottom: 10px;
            padding: 10px;
        }
        .message {
            padding: 5px;
            border-bottom: 1px solid #ddd;
        }
    </style>
    <script>
        const ws = new WebSocket("ws://localhost:8001");

        ws.onopen = () => {
            console.log("Connected to server");
        };

        ws.onmessage = event => {
            const message = JSON.parse(event.data);
            const chat = document.getElementById("chat");

            if (message.type === "message") {
                chat.innerHTML += `<div class="message"><strong>${message.name}:</strong> ${message.msg}</div>`;
            } else if (message.type === "notification") {
                chat.innerHTML += `<div class="message"><em>${message.msg}</em></div>`;
            }

            chat.scrollTop = chat.scrollHeight; // Scroll to bottom
        };

        function sendMsg() {
            const name = document.getElementById("name").value;
            const msg = document.getElementById("msg").value;
            if (name && msg) {
                ws.send(JSON.stringify({ name, msg }));
                document.getElementById("msg").value = ""; // Clear input
            }
        }
    </script>
</head>
<body>
    <h1>Websocket 실습 과제</h1>
    <div id="chat"></div>
    <input type="text" id="name" placeholder="Your name">
    <input type="text" id="msg" placeholder="Your message">
    <button onclick="sendMsg()">전송</button>
</body>
</html>


🤔 추가 학습한 내용


💡 const { WebSocketServer } = require("ws"); ?

  • const { WebSocketServer } = require("ws");는 Node.js에서 ws 모듈을 가져와서 WebSocketServer 클래스를 사용하는 구문.
  • 이를 통해 WebSocket 서버를 생성할 수 있다.

1. 모듈 가져오기

const { WebSocketServer } = require("ws");
  • 이 코드는 ws 모듈에서 WebSocketServer 클래스를 디스트럭처링하여 가져오는 부분.
  • ws 모듈은 Node.js에서 WebSocket을 구현하고 사용할 수 있도록 도와주는 패키지.

2. WebSocketServer 클래스

  • WebSocketServer는 WebSocket 서버를 생성하고 관리할 수 있는 클래스를 제공.
  • 이 클래스를 사용하여 클라이언트와의 실시간 양방향 통신을 설정할 수 있음

💡 wss.on("connection", ws => { ... }) 부분에서 ws ?

  • ws는 새로운 클라이언트가 WebSocket 서버에 연결될 때마다 생성되는 WebSocket 객체를 나타냄
  • 이 객체를 사용하여 해당 클라이언트와의 통신을 관리할 수 있음

1. 연결 이벤트 처리

wss.on("connection", ws => {

wss.on("connection", ws => { ... })는 새로운 클라이언트가 연결될 때마다 호출되는 콜백 함수.

  • ws는 새로 연결된 클라이언트를 나타내는 WebSocket 객체임

2. 클라이언트 목록에 추가

wss.on("connection", ws => {
  • 새로 연결된 클라이언트를 clients라는 Set에 추가하여 관리
  • 이 Set은 현재 연결된 모든 클라이언트를 추적

3. 새로운 클라이언트 연결 로그

console.log("New client connected");
  • 새로운 클라이언트가 연결되었음을 콘솔에 출력

4. 모든 클라이언트에게 새로운 유저 접속 알림

const connectMsg = `새로운 유저 접속 [현재 : ${clients.size}명]`;
for (let client of clients) {
    if (client.readyState === ws.OPEN) {
        client.send(JSON.stringify({
            type: "notification",
            msg: connectMsg
        }));
    }
}
  • connectMsg: 새로운 유저가 접속했음을 알리는 메시지를 생성. 현재 연결된 클라이언트 수를 포함.
  • for (let client of clients) { ... }: 현재 연결된 모든 클라이언트에 대해 반복문을 실행.
  • if (client.readyState === ws.OPEN) { ... }: 각 클라이언트가 여전히 연결된 상태인지 확인.
  • client.send(...): 각 클라이언트에게 새로운 유저 접속 메시지를 JSON 형식으로 보내줌.

💡 wss.on

wss.on ?

  • wss.on은 WebSocketServer 객체(wss)가 특정 이벤트가 발생했을 때 실행할 콜백 함수를 설정하는 메서드.
  • 이 메서드는 이벤트 기반 프로그래밍의 핵심 요소로, 특정 이벤트가 발생할 때마다 지정된 함수를 호출함.

주요 이벤트

  1. connection: 새로운 클라이언트가 서버에 연결될 때 발생.
  2. close: WebSocket 서버가 종료될 때 발생.
  3. error: WebSocket 서버에서 오류가 발생할 때 발생.
  4. headers: 서버가 핸드셰이크에 사용될 헤더를 준비할 때 발생.
  5. listening: 서버가 시작되어 연결을 받을 준비가 되었을 때 발생.

💡 ws.on

ws.on ?

  • ws.on은 WebSocket 객체(ws)에서 특정 이벤트가 발생할 때 실행할 콜백 함수를 설정하는 메서드.
  • 이를 통해 클라이언트와의 WebSocket 통신에서 발생하는 다양한 이벤트를 처리할 수 있음.

주요 이벤트

  1. message: 클라이언트로부터 메시지를 받을 때 발생한다.
  2. close: 클라이언트와의 연결이 종료될 때 발생한다.
  3. error: WebSocket 연결에 오류가 발생할 때 발생한다.

예제 코드

wss.on("connection", (ws) => {
    console.log("New client connected");

    // 메시지 이벤트 처리
    ws.on("message", (data) => {
        const message = JSON.parse(data);
        console.log(`Received message: ${message}`);
        
        // 예: 메시지를 다시 클라이언트에게 보냄
        ws.send(JSON.stringify({
            type: "response",
            msg: `You said: ${message.msg}`
        }));
    });

    // 연결 종료 이벤트 처리
    ws.on("close", () => {
        console.log("Client disconnected");
    });

    // 오류 이벤트 처리
    ws.on("error", (error) => {
        console.error(`WebSocket error: ${error}`);
    });
});

💡 wss.on("listening", () => { console.log("WebSocket server is listening");}); app.listen의 차이

  • wss.on("listening", () => { ... })와 app.listen은 각각 WebSocket 서버와 HTTP 서버에서 사용하는 이벤트 리스너 및 메서드
  • 둘 다 서버가 시작되어 클라이언트의 연결을 받을 준비가 되었을 때 실행되지만 그 목적과 사용되는 컨텍스트가 다르다.

wss.on("listening", () => { ... })

  • 목적: WebSocket 서버가 클라이언트의 연결을 받을 준비가 되었을 때 실행
  • 사용 위치: WebSocketServer 객체에서 사용
  • 예제:
const { WebSocketServer } = require("ws");

const wss = new WebSocketServer({ port: 8001 });

wss.on("listening", () => {
    console.log("WebSocket server is listening on port 8001");
});

wss.on("connection", (ws) => {
    console.log("New client connected");
    // 클라이언트와의 통신을 여기서 설정합니다.
});
  • 설명: WebSocket 서버가 지정된 포트에서 연결을 받을 준비가 되었음을 알림

app.listen

  • 목적: HTTP 서버가 지정된 포트에서 클라이언트의 요청을 받을 준비가 되었을 때 실행
  • 사용 위치: express 애플리케이션 객체에서 사용
  • 예제:
const express = require("express");
const app = express();

app.use(express.static("front"));

app.listen(8000, () => {
    console.log(`HTTP server listening on port 8000`);
});
  • 설명: Express 애플리케이션이 지정된 포트에서 HTTP 요청을 받을 준비가 되었음을 알림

주요 차이점

  1. 서버 유형:
  • wss.on("listening", () => { ... }): WebSocket 서버 (실시간 양방향 통신)
  • app.listen(port, () => { ... }): HTTP 서버 (웹 애플리케이션 또는 API 서비스)
  1. 사용되는 라이브러리:
  • wss: ws 라이브러리
  • app: express 라이브러리
  1. 기능:
  • wss.on("listening", () => { ... }): WebSocket 서버가 시작될 때 실행되는 콜백을 설정
  • app.listen(port, () => { ... }): HTTP 서버가 지정된 포트에서 요청을 받을 준비가 되었을 때 실행되는 콜백을 설정

요약

  • wss.on("listening", () => { ... })는 WebSocket 서버가 클라이언트의 연결을 받을 준비가 되었을 때 사용
  • app.listen(port, () => { ... })는 HTTP 서버가 지정된 포트에서 클라이언트의 요청을 받을 준비가 되었을 때 사용
profile
파송송의 개발 기록

0개의 댓글