← Log

Socket.IO 웹소켓 세팅

Cola Chat 익명 채팅 개발기· 3 / 7
  1. 1 — Cola Chat — 익명 채팅 프로젝트 소개
  2. 2 — 웹소켓의 기본 개념과 통신 과정
  3. 3 — Socket.IO 웹소켓 세팅
  4. 4 — 채팅방 입장/퇴장 처리 로직
  5. 5 — 실시간 메시지 전송과 수신
  6. 6 — 실시간 사용자 온라인 상태 관리
  7. 7 — 프로젝트 배포와 점검

Node.js 환경에서는 웹소켓을 직접 구현할 수 있지만, 간편성, 성능, 보안 등의 이점으로 전용 라이브러리를 주로 사용한다. 그중 가장 보편적인 Socket.IO 라이브러리를 이용해 구현할 예정이다.

Socket.IO

Socket.IO는 WebSocket 프로토콜 기반의 애플리케이션 개발을 위한 JavaScript 라이브러리로, 클라이언트와 서버 간의 양방향 통신을 간편하게 설정할 수 있도록 지원한다.

특징

  • 이벤트 기반 모델
    서버와 클라이언트가 다양한 이벤트로 수신한다. 전송 시에는 emit, 수신 시에는 on 메서드를 사용한다.

  • 그룹화 방식

    • namespace: 소켓 서버 내에 여러개의 네임스페이스를 서로 다른 기능으로서 독립적으로 관리할 수 있다.(ex. 채팅 서비스의 채널 단위)
    • room: 네임스페이스 내에서 클라이언트를 그룹화해 관리할 수 있다.(ex. 채널 내의 채팅방 단위)
  • 통신 안정성
    연결이 끊어졌을 때 자동으로 재연결을 시도하며, WebSocket이 지원되지 않는 경우, XHR Polling, Long Polling 등 대체 방법으로 자동으로 전환하여 통신을 유지한다.

  • 보안 설정
    특정 도메인만을 허용하도록 CORS 적용을 지원하며, JWT 인증을 통해 사용자를 식별하고 무분별한 소켓 생성을 방지할 수 있다.

Socket.IO를 이용한 웹소켓 세팅

서버와 클라이언트가 웹소켓 프로토콜로 통신하기 위해선 두 사이드 모두 웹소켓 세팅을 해주어야 한다. 각각의 웹 프레임워크는 다음과 같다.

  • 서버: Nest.js
  • 클라이언트: Next.js

서버 웹소켓 세팅: Nest.js

  • 모듈 및 라이브러리 설치

    npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
    
    # @nestjs/websockets: Nest.js 환경에서 사용할 수 있는 웹소켓 관련 기능(핸들러, 데코레이터 등)이 담긴 모듈
    # @nestjs/platform-socket.io: Socket.IO를 Nest.js에서 사용할 수 있게 설정하는 모듈
    # socket.io: 웹소켓 실행 라이브러리

    Nest.js에서는 Socket.IO를 기반으로 만들어진 @nestjs/websockets 모듈을 사용할 수 있다. 이 모듈을 사용하면 Nest.js의 모듈 기반 아키텍처와 의존성 주입, 데코레이터 등 주요 특성을 활용하면서 Socket.IO를 적용할 수 있다.

  • 모듈 생성

    # Module(chat.module.ts)
    nest g mo chat
    
    # Gateway(chat.gateway.ts)
    nest g ga chat
    
    # Service(chat.service.ts)
    nest g s chat

    WebSocket을 활용한 메세징 기능은 chat이라는 모듈로 구성해 관리할 것이다. 이 모듈 내에서 gateway 파일이 WebSocket 통신의 진입점을 담당한다.

  • Module 세팅

    // src/app/chat/chat.module.ts
    import { Module } from '@nestjs/common';
    import { ChatGateway } from './chat.gateway';
    import { ChatService } from './chat.service';
    
    @Module({
      imports: [],
      providers: [ChatGateway, ChatService],
    })
    export class ChatModule {}

    생성한 기능 파일들을 모듈 파일에 등록한다. 이때 GatewayProvider로서 작동한다.

  • Gateway 구성

    // src/app/chat/chat.gateway.ts
    import {
      ConnectedSocket,
      OnGatewayConnection,
      OnGatewayDisconnect,
      OnGatewayInit,
      WebSocketGateway,
      WebSocketServer,
    } from '@nestjs/websockets';
    import { Server, Socket } from 'socket.io';
    
    // 웹소켓 게이트웨이 정의(namespace 지정)
    @WebSocketGateway({ namespace: /\/*.+/ })
    export class ChatGateway
      implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
    {
      // 웹소켓 서버 인스턴스 주입
      @WebSocketServer() public server: Server;
    
      // 웹소켓 서버 초기화
      afterInit(server: Server): void {
        console.log('WebSocket Server Init');
      }
    
      // 웹소켓 연결
      handleConnection(@ConnectedSocket() socket: Socket): void {
        console.log('WebSocket Connected to', socket.nsp.name);
      }
    
      // 웹소켓 연결 해제
      handleDisconnect(@ConnectedSocket() socket: Socket): void {
        console.log('WebSocket Disconnected from', socket.nsp.name);
      }
    }

    위와 같이 구성하면 서버 실행 시, 웹소켓 서버를 생성하고 외부 소켓 인스턴스를 연결할 준비가 완료된다.

    implements에 인터페이스를 지정해 필요한 메서드 구현을 강제하여 일관된 구조를 유지한다.

    • OnGatewayInitafterInit
    • OnGatewayConnectionhandleConnection
    • OnGatewayDisconnecthandleDisconnect

    소켓 연결(handleConnection)와 해제(handleDisconnect) 메서드는 각각 'connection''disconnect' 라는 소켓 이벤트가 암묵적으로 지정되어 있어 특정 소켓의 연결과 해제를 자동으로 처리한다.

클라이언트 웹소켓 세팅: Next.js

  • 모듈 및 라이브러리 설치

    npm install socket.io-client
    
    # socket.io-client: Socket.IO의 클라이언트 측 라이브러리
  • Hook 세팅

    // src/app/hooks/useSocket.ts
    import { useEffect } from 'react';
    import { io } from 'socket.io-client';
    import useSocketStore from '../stores/socketStore';
    import { Room } from '@/shared/types/type';
    import { SocketEvent } from '@/shared/types/enum';
    
    const SOCKET_BASE_URL = process.env.NEXT_PUBLIC_SOCKET_BASE_URL as string;
    
    type Props = {
      room: Room;
    };
    
    export default function useSocket(props: Props) {
      const { id: roomId, namespace } = props.room;
    
      const { socket, setSocket } = useSocketStore();
    
      useEffect(() => {
        if (!namespace) return;
    
        // 소켓 생성
        const socketInstance = io(`${SOCKET_BASE_URL}/${namespace}`, {
          transports: ['websocket'],
          autoConnect: false,
        });
    
        // 소켓 연결
        socketInstance.connect();
    
        // 소켓 연결 후
        socketInstance.on(SocketEvent.CONNECT, () => {
          console.log('Connected to WebSocket server');
          setSocket(socketInstance);
        });
    
        // 소켓 연결 해제 후
        socketInstance.on(SocketEvent.DISCONNECT, () => {
          console.log('Disconnected from WebSocket server');
          setSocket(null);
        });
    
        return () => {
          // 소켓 연결 해제
          if (socket) socket.disconnect();
        };
      }, [namespace, roomId]);
    }

    socket.io-client 라이브러리의 io 함수를 이용해 서버와 웹소켓 통신을 할 수 있는 ‘소켓’ 인스턴스를 생성한다.

    생성한 인스턴스로 connect 함수를 호출하면 핸드셰이크소켓 연결 작업이 순차적으로 이루어지며, 해당 Hook이 언마운트되면, disconnect 함수가 호출되어 소켓 연결이 해제된다.

    인스턴스의 on 함수는 특정 이벤트를 수신하는 역할을 하며, 서버에서는 소켓 연결 및 해제 후 자동으로 해당 이벤트를 전송한다. 각 이벤트 수신 장치에 대해 콜백 함수로 후속 작업을 설정해두었다.

    이제 웹소켓 연결이 필요한 컴포넌트에서 해당 Hook을 마운트하면 웹소켓 연결이 완료되며, 이벤트를 주고받을 수 있는 준비가 완료된다.

이렇게 서버와 클라이언트 간의 웹소켓 통신을 위한 기본적인 설정을 완료했다.

목차