SLASH 기술 블로그

Socket.IO에 대해 알아야 하는 모든 것 본문

Socket.IO에 대해 알아야 하는 모든 것

SLASH 2022. 2. 15. 04:27
반응형

시작하기

최근에 브라우저상에서 실시간 통신이 필요한 웹 사이트를 만든 적이 있다.
관련해서 Socket.IO라는 라이브러리를 활용하게 되었는데, 이 과정에서 Socket.IO가 무엇인지 어떤 이유로 사용하게 되었는지 이 글을 통해 실시간 통신을 하기 위해 알아야 하는 것들을 정리해보려고 한다.

HTTP 통신의 특징

보통 우리가 웹 사이트를 접속할 때, URL을 통해 "http://domain.com", "https://domain.com"이러한 도메인 주소를 입력해서 접속한다. 이때 도메인 맨 앞에 HTTP라는 텍스트를 붙이는데, 이것이 바로 웹브라우저상에서 데이터를 주고받는 프로토콜이다. 프로토콜은 간단하게 말해서 하나의 규칙인데, 웹브라우저 상에서는 HTTP라는 규칙을 통해 통신한다고 생각하면 된다. HTTPHyper Text Transfer Protocol의 약자로 HTML 파일을 전송하는 프로토콜이라는 의미를 갖는다. 하지만 이것은 초기의 목적이며 요즘은 Json이나 이미지와 같은 것들도 전송한다.

이러한 HTTP통신은 아래의 그림과 같이 동작한다.

HTTP-Request-Response 사이클

위의 그림처럼 HTTP통신은 클라이언트에서 서버로 요청을 보내고 서버에서 클라이언트로 해당 요청에 대한 응답을 보내는 방식으로 통신한다. 이러한 통신 방법은 아래와 같은 특징을 갖게 된다.

클라이언트에서 요청을 했을 때, 서버가 요청에 대한 응답을 하는 방식. (단방향 통신)

이런 단방향 통신은 우리가 흔히 사용하는 웹 사이트에서는 웬만하면 문제가 되지 않는다.
(버튼을 눌렀을 때 결과, 메뉴를 눌렀을 때 결과 등)

하지만 만약 사이트에 접속한 다수의 유저들끼리 서로가 액션을 주고받는 사이트를 만든다고 했을 때 이러한 HTTP통신만으로 원하는 동작을 할 수 있을까?

나는 조금 어렵다고 생각한다. HTTP만으로 구현을 한다고 할지라도 불필요한 오버헤드가 발생하는 것을 피할 수는 없을 것이다.

그렇다면 웹 사이트에서 실시간 통신을 하는 서비스를 구현하려면 어떻게 해야 할까?

실시간 통신 구현 방법

웹 사이트에서 실시간 통신을 구현하려면 Polling, Long Polling, Streaming 그리고 WebSocket에 대해 알아야 한다. 우리가 만약 웹브라우저 상에서 실시간으로 상황을 안내해주는 사이트나 유저들끼리의 액션을 서로 공유해야 하는 사이트를 만들어야 한다면 리얼타임을 지원할 수 있는 여러 방법들을 사용해야 한다.
Polling, Long Polling, Steaming, WebSocket은 웹에서 리얼타임을 지원할 수 있는 4가지 방법이다.

Polling

폴링 방식은 위에서 얘기한 HTTP통신을 통해 일정한 주기로 계속 요청하는 방식이다.
아래의 그림과 같이 HTTP 요청을 특정 주기에 맞춰 서버에 계속 보낸다.

Polling 방식

이 방식의 단점은 특정 주기와 데이터가 변하지 않았을 때에 리소스 낭비이다.
특정 주기마다 요청을 보내는 형태로 리얼타임을 구현하려고 하다 보면 주기에 따라 실시간의 보장이 달라진다.
(ex. 주기를 1초마다 하는 것과 10초마다 하는 것의 데이터 업데이트 차이)
실시간을 위해 요청 주기를 줄이게 되면 의미 없는 요청들이 서버에 계속 가게 되고 자연스럽게 서버에는 부하가 생기게 된다는 것이다. 그렇다고 주기를 늘리게 되면 실시간 성능이 떨어진다. 또한 보내는 요청에 대한 데이터 업데이트가 없을 경우 리소스를 낭비하고 불필요한 오버헤드와 트래픽이 발생한다.

폴링 방식의 단점을 보완하는 방법으로 롱 폴링 방식이 있다.

Long Polling

롱 폴링 방식은 서버 측에서 전달받은 요청을 일정 시간 대기시키는 방법이다. 대기 시간 안에 데이터의 업데이트가 일어나면 그 즉시 클라이언트에게 응답을 보내고 응답받은 클라이언트는 다시 서버에 요청을 보내가 된다.

Long Polling 방식

이러한 방식 또한 데이터 업데이트가 빈번하게 일어났을 경우에는 폴링 방식과 다르지 않다.

Streaming

스트리밍 방식은 롱 폴링 방식과 비슷하지만 다르다.
롱 폴링 방식에서는 요청을 받고 나서 데이터가 업데이트될 때까지 유지하다가 응답을 보내고 요청을 닫는다. 하지만 스트리밍 방식에서는 데이터가 업데이트될 때마다 응답 후 요청을 닫지 않고 Chunk DataStream데이터 형태의 부분 데이터만을 전달하고 요청을 유지한다.

Streaming 방식

응답마다 다시 요청을 보내야 하는 롱 폴링 방식과 비교했을 때 스트리밍 방식은 효율적이다. 스트리밍 방식은 서버에서 데이터 업데이트가 자주 일어날 때 유리한 방법이다. 하지만 이 방법 역시 연결을 길게 맺고 있을 경우 해당 연결에 대한 유효성 관리가 어려워진다는 단점이 있다.

WebSocket

위에서 언급한 3가지 방법은 전부 단방향 통신을 기반으로 한 실시간 통신 방법이다.
단방향 통신을 기반으로 한 실시간 통신은 억지로 실시간 통신을 할 수 있게 구현하는 느낌이 강하게 든다.

웹 소켓은 HTML5 명세에 포함된 기술로서 클라이언트와 서버가 TCP 기반 연결을 통해 지속적으로 양방향 통신이 가능하도록 하는 방식이다. 간단하게 웹 소켓은 TCP/IP 스택 위에 구축된 얇은 전송 계층이라고 생각하면 된다. 웹 소켓의 목적은 TCP 통신 계층을 웹 애플리케이션 개발자들에게 제공하여 HTTP 통신에서 발생했던 문제점을 해결해주기 위함이다.

WebSocket 방식

웹 소켓은 최초의 연결은 HTTP로 하고 정상적으로 연결된 것을 확인하면 웹 소켓 프로토콜로 연결을 유지한다.
웹 소켓은 HTTP 프로토콜이 아닌 별도의 프로토콜을 사용하기 때문에 오버헤드가 굉장히 적다. 하지만 웹 소켓
API에서 지원해주는 기능들은 굉장히 적다. 그래서 별도의 라이브러리를 사용해야 한다. (SockJS, Socket.IO 등)

또한 웹 소켓을 지원하지 않는 브라우저 같은 경우는 사용할 수 없다. (2022년 기준 대부분의 브라우저에서 지원한다.)

들어가기에 앞서

우리는 웹 사이트에서 실시간 통신을 하기 위한 4가지 방법을 알아보았다.
마지막에 웹 소켓을 설명하면서 Socket.IO에 대한 이야기를 했는데, 이 글의 제목이기도 한 라이브러리이다.
실시간 통신을 웹에서 처리하는 방법들을 알아보았으니 Socket.IO가 무엇인지 어떤 식으로 구현하여 사용하는지 알아보자.

Socket.IO란

Socket.IO란 클라이언트와 서버를 이벤트 기반으로 실시간 양방향 통신을 가능하게 해주는 라이브러리이다.
WebSocket만 가지고 직접 개발하게 되면 많은 어려움이 있는데, WebSocket을 활용하여 좀 더 쉽게 개발할 수 있게 도와준다.

Socket.IO 동작 원리 및 구조

Socket.IO는 처음 연결 시 폴링 방식으로 연결을 만든다. 그다음 연결에 성공하면 더 나은 방법으로 업그레이드하는데, 이때 대부분 WebSocket 연결로 전환한다.
(만약 WebSocket을 사용할 수 없는 상황일 경우 롱 폴링을 유지한다.)

아래의 그림은 Socket.IO의 구조를 도식화한 것이다.

Socket.IO 구조

Socket.IO는 서버 쪽에서 사용하는 socket.io와 클라이언트 쪽에서 사용하는 socket.io-client가 있다.
socket.io-client에서 socket.io의 정보를 통해 두 Socket 간의 통신이 이루어진다.

Socket.IO는 클라이언트와 서버 간의 통신을 위해 내부적으로 engine.io를 사용하는데 이것은 Low-level에서의 처리를 위한 부분이고, Socket.IO는 쉽게 소켓 통신을 하기 위해 필요한 API들을 제공한다.

Socket.IO 사용 예시

웹브라우저상에서 실시간 처리를 하는 방법들과 Socket.IO라는 라이브러리에 대해 알아보았다.
이제 우리는 Socket.IO를 통해 실제로 구현을 하려면 어떤 식으로 해야 하는지 알기만 하면 된다.
(이 글은 개념 설명에 집중되어 있기 때문에 디테일한 구현은 하지 않을 예정이다.)

간편하게 사용하기 좋은 Node.js, Express, React를 써서 Socket.IO를 이용한 양방향 통신을 구현해보자.

서버 - Node.js - express 클라이언트 - react

먼저 필요한 npm모듈을 설치한다.

npm install sokcet.io // 서버 프로젝트에 npm install socket.io-client // 클라이언트 프로젝트에

소켓 통신은 서버와 클라이언트 간의 양방향 통신을 하기 위함이기 때문에 socket.io는 서버 프로젝트에 설치하고 socket.io-client는 클라이언트 프로젝트 쪽에 설치하면 된다.

가장 먼저 서버 쪽에 socket.io를 연결하는 부분을 만든다.

const express = require('express') const { server } = require('socket.io') const http = require('http') const app = express() const httpServer = http.createServer(app) const io = new Server(httpServer, {}) io.on('connection', (socket) ==> { socket.on('start', ({ status }) => { console.log(status) }) }) httpServer.listen(5000, () => console.log('server start')

위의 코드를 보면 생성한 express 서버에 socket.io를 연결한 후, connection을 맺은 모습을 볼 수 있다. 이렇게 띄운 서버에 Socket.IO를 연결하게 되면 생성한 이벤트가 발생했을 때, 그 이벤트에 정의된 동작을 하게 된다.

예시로 start라는 이벤트가 발생했을 때, 클라이언트에서 전달받은 status를 서버 쪽에서 확인하는 이벤트를 만들었다.
이번에는 클라이언트에서 start라는 이벤트를 어떻게 발생시키는지 알아보자.

import React, { useEffect } from 'react' import io from 'socket.io-client' let socket const Test = () => { const END_POINT = 'http://localhost:5000/' useEffect(() => { socket = io(END_POINT) socket.emit('start', { status: 'happy' }) }, []) return ( <div> <h4>소켓 테스트 컴포넌트</h4> </div> ) } export default Test

클라이언트 코드를 보면 socket.io-client를 import 해서 사용하는 것을 볼 수 있다. 연결하고자 하는 socket.io가 있는 END_POINT를 통해 소켓에 접근한 다음에 emit함수를 통해서 원하는 이벤트를 호출하면 된다.

Socket.IO에는 다양한 기능들이 존재하는데, 연결된 모든 브라우저에 데이터를 뿌리거나 연결이 끊겼을 때 처리하는 방법들은 공식 문서를 통해 확인해보면 좋을 것 같다. (공식 문서)

Socket.IO의 한계

Socket.IO를 통해 채팅 기능이 있는 사이트를 만들었다고 가정한다면 아래와 같은 문제가 발생한다.

채팅방에 들어온 사람들끼리 채팅을 통해 대화를 할 수 있는데, 이때 유저가 많이 몰리는 상황을 대비해서 서버가 자동으로 확장될 수 있게 구성했다고 생각해보자. 확장된 상태에서 같은 채팅방에 있는 유저들끼리 원활하게 대화를 진행할 수 있을까? 정답은 NO이다.

왜냐하면 우리는 같은 채팅방에 있는 유저가 확장된 A서버의 소켓에 연결되었는지 B서버의 소켓에 연결되었는지 모르기 때문이다.

Socket.IO는 실시간 서비스를 더 쉽게 만들 수 있게 강력한 기능들을 많이 제공해주지만
이처럼 확장성을 고려한 아키텍처를 구성할 때에는 고려해야 할 것들이 많이 늘어난다.

또한 Socket.IO가 WebSocket을 지원하지 않는 브라우저를 위해 하는 행동이 그럴 필요 없는 상황에서 불필요한 오버헤드를 일으킨다.

마무리

위에서 언급한 한계들은 redis를 사용하거나 Socket.IO에 있는 옵션을 통해 해결할 수 있는 부분이기는 하다.
하지만 확실한 것은 실제 유저들에게 서비스하기 위해서는 기본적인 API를 활용하는 것 이외의 많은 것들을 알아야 한다는 것이다.

이번에 개인적으로 만들고 있는 사이트에서 Socket.IO를 사용하면서 찾아봤던 문서나 정보들을 정리해봤다.
기회가 된다면 Socket.IO에 있는 기능들이나 규칙 같은 것들을 정리해볼까 한다.

@JJoGeon

반응형

'' 카테고리의 다른 글

nginx란 - 대체 nginx가 뭔데?  (0) 2021.05.04
Comments