Skip to content

⛏️ Node WebSocket 파고들기

baegyeong edited this page Nov 29, 2024 · 2 revisions
분야 작성자 작성일
BE 김민수 24년 11월 05일

Node Websocket 파고든 이유

이번 프로젝트에서 Websocket을 사용하게 되었다. Websocket을 사용하는 기능은 채팅, 실시간 주가, 알림이다. 프로젝트를 기획하면서 3가지 기능에서 성능 상의 병목이 발생할 가능성이 크다고 생각했다. 따라서 이를 효율적으로 줄일 수 있는 방법을 고민하였고, Node에서 Websocket이 어떻게 동작하는지 명확하게 알아야지 적합한 해결책을 찾는데 도움이 될 것이라는 생각이 들어 파고들었다.

WS 서버 초기화

WebSocketServerwebsocket을 관리하는 서버 객체이다. 초기화 과정은 다음과 같다.

  1. 매개변수로 전달받은 옵션을 체크
  2. 설정 port가 null이 아닐 때 http 라이브러리 서버를 생성한다.
    1. 만약 port가 null이면 option으로 전달받은 서버로 진행
  3. 서버가 등록되면 이벤트 등록 작업을 진행
    1. 이때 upgrade 이벤트를 등록하여 WS 작업을 진행할 수 있도록 한다.
  4. perMessageDeflate 옵션을 확인한다.
    1. 해당 옵션은 클라이언트와 서버간 메시지를 압축하여 전달하기 때문에 네트워크 부하가 줄어들 수 있다는 장점이 있다.
    2. 하지만 메모리 낭비나 높은 CPU 사용률이 나타날 수 있는 단점이 있다.
    3. 이번에 진행하는 프로젝트는 메시지가 크지 않으므로 필요없는 옵션
  5. clientTracking 옵션을 확인한다.
    1. clientTracking 은 웹소켓과 연결하는 클라이언트의 소켓을 추적하는지 설정하는 옵션으로 default는 true이다.
    2. 이때 클라이언트는 Set에 저장된다.
  6. 옵션과 상태값을 저장한다.

Websocket 연결

server에서 upgrade 이벤트가 발생한 후 reqsocket, head를 전달받는다.

handleUpgrade 작업 진행

handleUpgrade 는 클라이언트 요청을 확인하여 검증 진행 및 Websocket과 연관된 헤더를 추출하는 작업을 진행한다.

  1. 에러 이벤트가 발생하면 소켓의 연결을 끊도록 설정
  2. request로 부터 sec-websocket-key , upgrade, sec-websocket-version 을 받는다.
    1. sec-websocket-key - 핸드쉐이크 과정에서 연결하고자하는 클라이언트
    2. upgrade - HTTP/1.1 버전에 사용되는 헤더로 프로토콜을 변경하기 위한 헤더
    3. version - websocket 버전
  3. 요청 메서드를 확인한다. GET이 아니면 예외 메시지 전달
  4. upgrade 헤더 체크
  5. sec-websocket-key 체크
  6. version이 8 또는 13인지 확인
  7. 해당 요청의 경로가 설정된 경로가 맞는지 확인
  8. sec-websocket-protocol 체크
    1. 서브 프로토콜을 확인하여 저장한다.
  9. sec-websocket-extensions 체크
    1. 메시지 압축과 같은 여러가지 확장 옵션을 저장한다.
  10. 제한된 클라이언트 옵션을 확인
    1. 만약 검증 옵션이 있으면 주어진 조건으로 클라이언트를 필터링한다.
  11. 업그레이드 작업 완료

completeUpgrade 작업 진행

  1. 소켓의 연결이 끊겼는지 확인
  2. 소켓이 이미 업그레이드가 되었는지 서버 상태를 확인
  3. sec-websocket-key 으로 Sec-WebSocket-Accept 을 생성
    1. Sec-WebSocket-Accept 는 응답 서버가 websocket으로 업그레이드 할 의향이 있다는 의미를 나타낸다.
  4. 응답 값 초기 설정
    1. 101 상태 값을 설정
    2. upgrade, connection, Sec-WebSocket-Accept 헤더를 설정
  5. WebSocket 클래스 객체 생성
  6. WebSocket 객체에 요청으로부터 전달된 서브 프로토콜과 extentions 설정
  7. 소켓에 start line과 헤더를 write 이후 이전에 설정한 에러 리스너를 삭제
  8. WebSocket 에 전달 받은 소켓을 주입, 이때 3가지 옵션도 설정
    1. allowSynchronousEvents - message, ping, pong 이벤트를 한 틱에 여러 개를 emit 할 수 있는지에 대한 옵션, default는 false
    2. maxPayload - 메시지 최대 크기, default는 104857600
    3. skipUTF8Validation - UTF-8 형식으로 올바르게 전달되었는지 확인하는 옵션, default는 false
  9. 설정된 WebSocket 을 서버에 저장한다.
    1. 이때 WebSocket 의 연결이 끊기는 이벤트가 발생하면 자동으로 삭제하도록 설정

WebSocketServer에서 connection 이벤트가 발생하여 사용자가 설정한 connection 작업을 진행

해당 부분은 공식 문서에 설정하는 방법이 있으므로 생략한다.

클라이언트로부터 메시지를 받을 때

우선 클라이언트로부터 메시지를 받으면 WebSocket 내부의 Receiver에서 메시지 전처리 작업을 진행한다. 이때 이전에 WebSocket 에서 소켓을 설정할 때 전달 받은 옵션의 작업을 진행한다. ReceiverDuplex 라는 양방향 스트림이므로 tcp 소켓에 메시지를 전달하면 일정 길이만큼 버퍼를 전처리하는 작업을 계속 진행한 후 모든 메시지를 받게 된다면, WebSocketmessage라는 이벤트를 호출하여 프로그래머가 미리 설정한 리스너 이벤트를 진행하도록 한다.

클라이언트에게 메시지를 전달할 때

WebSocket 내부의 Sender를 사용한다. 먼저 WebSocket 이 메시지를 전달할 수 있는 상태인지 확인한 후 다음의 작업을 진행한다.

  1. 데이터가 숫자인지 확인 후 문자로 변환
  2. 웹 소켓이 열려있지 않으면 sendAfterClose진행
  3. 압축 옵션을 확인 후 압축이 설정되면 압축 진행
  4. sender에 전달

이후 sender 가 클라이언트 소켓에 메시지를 전달한다.

클라이언트가 연결이 끊길 때

close 이벤트가 발생하여 연결이 끊기고 클라이언트를 저장한 Set에 소켓을 삭제하도록한다.

결론

Websocket 라이브러리를 파고들면서 해당 라이브러리는 정말 기본적인 옵션을 제공한다는 것을 느꼈다. 이렇게 생각한 이유는 프로토콜에서 명시한 헤더 값으로 소켓을 설정하는 작업과 메시지 전달을 주고받는 작업 이외에 어떤 기능도 제공하지 않기 때문이다. 클라이언트로부터 연결이 끊기면 연결을 재시도 하지 않고, 클라이언트 소켓을 저장하지만 채팅 룸에 대한 기능이 없다. 따라서 WebSocket은 node net과 같이 웹 소켓에 대한 기본적인 기능만 제공하고, Socket.ionestjs와 같이 다양한 기능이 제공되는 라이브러리라고 생각하게 되었다.

🐜 팀 개미

🏛️ 팀 문화

개발 위키

FE

BE

Infra

🗣️ 발표

📚 회의록

🔴 인터미션
🟠 1주차
🟡 2주차
🟢 3주차
🔵 4주차
🟣 5주차
🟤 6주차

💭 회고

🧑‍🤝‍🧑 멘토링

Clone this wiki locally