프로그래밍 기초

실시간 통신 - WebSocket이란

hs-archive 2023. 7. 18. 00:57

실시간 통신의 필요성

유저들은 더 빠르고 반응성 있는 웹 애플리케이션과 서비스를 기대합니다. 예를 들어 실시간 채팅 애플리케이션이나 주식 시장 모니터링, 실시간 위치 추적 및 멀티플레이어 게임과 실시간 협업 툴 등이 그러합니다. 우리는 HTTP 프로토콜을 사용하여 이러한 실시간 통신과 비슷한 것을 이룰 수 있습니다. 물론 완벽하지는 못하지만요.

 

이번 포스팅에서는 기존 HTTP 기술을 활용하여 이러한 실시간 통신과 비슷한 경험을 달성하는 기술과 해당 기술의 단점을 보완하는 WebSocket에 대해 배워보겠습니다.

 

HTTP를 사용한 실시간 통신의 구현

HTTP를 사용한 실시간 통신은 크게 Polling, Long Polling, Streaming 이 세 가지로 나눌 수 있습니다. 어떻게 HTTP를 사용하여 실시간 통신을 달성하는지 알아봅시다.

 

Polling

Polling

클라이언트가 http request를 계속 서버로 날려서 이벤트 내용을 전달받는 방식입니다. 변화가 생기면 서버가 알아서 응답을 하는 것이 아니라 클라이언트가 계속 http request를 보내서 변경 내역이 있는지를 확인하는 것입니다.

 

장점

그냥 http request를 계속 보내면 되니까 간단하고 구현이 쉽습니다.

 

단점

http request connection을 계속 맺고 끊고 해야하므로 클라이언트가 많아지면 서버의 부담이 급증하게 됩니다. 그리고 서버가 실시간으로 응답을 하는 것이 아니라 클라이언트의 요청에 따른 응답을 하는 것이므로 실시간정도의 빠른 응답을 기대하기 어렵습니다.

 

Long Polling

Long Polling

long polling은 클라이언트가 서버에 요청을 보내면 서버는 즉각적으로 어떠한 응답을 반환하는 대신 이벤트가 발생하면 그때 응답을 반환하는 방식입니다. 서버에 요청을 보내고 이벤트가 생겨 응답을 받을 때 까지 연결을 종료하지 않습니다. 그리고 응답을 받으면 http 연결을 끊고 이후에 다시 연결합니다.

 

장점

이벤트가 발생하면 그때 응답을 하므로 polling에 비해 실시간성이 개선됩니다. 

 

단점

어쨌든 요청을 보내야 응답을 해주는 것이므로, http 연결을 잇고 끊는 오버헤드가 발생합니다.

 

이벤트들의 발생 시간 간격이 좁다면 polling과 별 차이가 없게 됩니다.

 

Streaming

스트리밍

서버에 요청을 보내고 끊기지 않은 연결 상태에서 끊임없이 데이터를 받는 기법입니다. 이는 데이터가 조금씩 끊어져서 전송되는 식으로 이루어집니다. HTTP/1.1의 Chunked Transfer Encoding 또는 HTTP/2의 Server Push를 사용하여 구현될 수 있습니다.

 

HTTP 스트리밍은 데이터가 사용 가능해지면 서버에서 클라이언트로 데이터를 지속적으로 보낼 수 있도록 하는 기술입니다. 클라이언트는 연속 바이트 스트림으로 데이터를 수신하고 전체 응답을 수신하기 전에 처리를 시작할 수 있습니다. 이 기술은 대기 시간을 최소화하는 것이 중요한 비디오 스트리밍과 같은 실시간 응용 프로그램에 유용합니다.

 

아래와 같이 작성할 수 있습니다.

# ---------- client side -----------
const streamRequest = (url) => {
    fetch(url).then(function (response) {
        let reader = response.body.getReader();
        let decoder = new TextDecoder();
        return readData();
        function readData() {
            return reader.read().then(function ({value, done}) {
                console.log(value)
                if (value) {
                    let newData = decoder.decode(value, {stream: !done});
                    console.log(newData);    
                }
                if (done) {
                    console.log('end of stream');
                    return;
                }
                return readData();
            });
        }
    });
}

 

장점

데이터가 사용 가능해지면 서버에서 클라이언트 데이터를 계속 보낼 수 있습니다. 클라이언트는 연속 바이트 스트림으로 데이터를 수신하고 전체 응답을 수신하기 전에 처리를 시작할 수 있습니다. 대기 시간을 최소화하는 것이 중요한 경우 유용합니다.

 

단점

단방향 통신이므로, 클라이언트가 서버로 데이터를 보낼 수 없습니다.

 

코드 작성이 polling/long polling보다 더 복잡합니다. 

 

Server-Sent Events(SSE)

SSE

SSE를 사용하면 서버가 polling이나 long polling 없이 서버에서 클라이언트로 메시지를 보낼 수 있습니다.

 

SSE는 서버에 대한 클라이언트의 GET 요청으로 시작할 수 있습니다. 아래는 클라이언트가 서버로 보내는 HTTP 요청을 나타낸 것입니다.

GET /api/v1/live-scores 
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

 

Accept: text/event-stream은 클라이언트가 서버에게 요청하는 리소스에 대한 응답으로 SSE를 원한다는 것을 나타냅니다. Cache-Control: no-cache는 캐싱 비활성화를 나타내고 Connection: keep-alive는 지속적인 연결을 나타냅니다. 이 요청은 업데이트를 가져오는 데 사용할 열린 연결을 제공합니다. 연결 후 서버에서 이벤트를 보낼 준비가 되면 서버에서 메시지를 보낼 수 있습니다. 서버에서 보내는 이벤트는 UTF-8 인코딩의 텍스트 메시지입니다.

 

SSE 필드 이름 목록은 아래와 같습니다.

  - event: 애플리케이션에서 정의한 이벤트 유형입니다.

  - data: 이벤트 또는 메시지를 위한 필드입니다.

  - retry: 브라우저는 연결이 끊어지거나 closed되면 정의된 시간이 지난 후 리소스에 다시 연결을 시도합니다.

  - id: 이벤트/메시지의 각 ID를 나타냅니다.

id: 1
event: score
data: GOAL Liverpool 1 - 1 Arsenal
data: GOAL Manchester United 3 - 3 Manchester City

 

아래와 같이 작성할 수 있습니다.

# ---------- client side -----------

const eventSource = new EventSource("//your-api/workflow/state");

eventSource.addEventListener("queued", function(event) {
    ...
}
eventSource.addEventListener("started", function(event) {
    ...
}
eventSource.addEventListener("failed", function(event) {
    ...
}
eventSource.addEventListener("success", function(event) {
    ...
}

 

장점

실시간 업데이트에 효과적인 방법이며, 웹 브라우저에서 기본적으로 지원됩니다.

 

polling이나 long polling에 비해 더 적은 네트워크 부하가 발생합니다.

 

단점

SSE를 지원하는 기능을 구현해야 하므로 구현이 더 복잡할 수 있습니다. 

 

단방향 통신이므로, 클라이언트가 서버로 데이터를 보낼 수 없습니다.

 

HTTP2를 사용하지 않을 경우 브라우저에서 탭을 최대 6개 까지만 허용해주고 그것을 초과해서 연결할 수 없습니다. HTTP2 사용시 기본 100개 허용합니다.

더보기

When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be especially painful when opening multiple tabs, as the limit is per browser and is set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, which means that you can open 6 SSE connections across all of the tabs to www.example1.com and another 6 SSE connections to www.example2.com (per Stackoverflow). When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100). - 출처 모질라

 

HTTP streaming vs Server-Sent Events(SSE)

SSE는 실시간 업데이트를 위해 설계되었으며 서버에서 클라이언트로 이벤트를 보내기 위한 간단한 API를 제공합니다. HTTP 스트리밍은 더 유연하고 더 넓은 범위의 애플리케이션에 사용할 수 있지만 더 복잡한 클라이언트 측 코드가 필요합니다.

 

스택오버플로우 답변에 따르면, SSE는 HTTP 스트리밍의 한 형태라고 말하고 있습니다. 

 

WebSocket - 참고

WebSocket 동작 과정

WebSocket은 전이중 통신을 지원합니다. 또한, 기존 http 요청-응답 방식은 요청한 그 클라이언트에만 응답이 가능한 데 반해 ws 프로토콜을 통해 웹소켓 포트에 접속해 있는 모든 클라이언트에게 이벤트 방식으로 응답합니다. 

 

WebSocket은 TCP 접속에 전이중 통신 채널을 제공하는 프로토콜입니다. HTTP 위에서 동작하는 프로토콜로 최초 접속은 HTTP 핸드셰이킹을 하기 때문에 http header를 사용합니다. 따라서 기존 80혹은 443 포트로 접속을 하므로 추가로 방화벽을 열지 않고도 양방향 통신이 가능하고 http 규격인 CORS 적용이나 인증 등의 과정을 기존과 동일하게 가져갈 수 있는 것이 장점입니다. 연결이 된 후에는 ws 헤더를 사용합니다.

 

위 사진에서 빨간 색 부분이 Opening Handshake, 노란 부분이 Data transfer, 보라색 부분이 Closing Handshake입니다.

 

Opening Handshake

웹 소켓 클라이언트에서 핸드쉐이크 요청(HTTP Upgrade)을 전송하고 이에 대한 응답으로 응답을 받습니다. 이때의 응답 코드는 101입니다. 101은 '프로토콜 전환'을 서버가 승인했음을 알리는 코드입니다. (HTTP -> WebSocket으로의 프로토콜 전환) 이 과정에서 요청과 응답의 헤더를 살펴보겠습니다.

 

아래는 클라이언트가 ws://localhost:8080/chat으로 접속하려 할 때의 예시 코드입니다.

GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://localhost:9000

 

  - Upgrade: 프로토콜을 전환하기 위해 사용하는 헤더입니다. 웹소켓 요청 시 반드시 websocket이라는 값을 가지며, 이 값이 없거나 다른 값이면 cross-protocal attack이라고 간주하여 웹소켓 접속을 중지시킵니다.

  - connection: 현재의 전송이 완료된 후 네트워크 접속을 유지할 것인가에 대한 정보입니다. 웹소켓 요청 시 반드시 Upgrade라는 값을 가지며, Upgrade와 마찬가지로 이 값이 없거나 다른 값이면 웹소켓 접속을 중지시킵니다.

  - Sec-WebSocket-Key: 길이가 16바이트인 임의로 선택된 숫자를 base64 인코딩한 값입니다. 신원을 인증하는 데 사용됩니다.

  - Sec-WebSocket-Protocol: 클라이언트가 요청하는 여러 서브 프로토콜을 의미합니다. 공백 문자로 구분되며 순서에 따라 우선권이 부여됩니다. 서버에서 여러 프로토콜 혹은 프로토콜 버전을 나눠서 서비스할 경우 필요합니다. 따라서 필수는 아닙니다.

  - Sec-Websocket-Version: 클라이언트가 사용하고자 하는 웹소켓 프로토콜 버전을 의미합니다.

  - Origin: 모든 브라우저는 CORS를 위해 이 헤더를 보냅니다. 

 

아래는 서버의 응답 예시입니다.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

 

  - Sec-WebSocket-Accept: 요청 헤더의 Sec-WebSocket-Key에 유니크 아이디를 더해서 SHA-1로 해싱 후 base64로 인코딩한 값입니다.

 

Data Transfer

핸드쉐이크를 통해 웹소켓 연결이 수립되면, 데이터 전송을 시작합니다. 클라이언트와 서버는 '메시지'라는 단위로 데이터를 주고받고 이 메시지는 '프레임'으로 구성되어 있습니다. '프레임'은 작은 헤더와 payload로 구성되어 있습니다. 이 헤더가 작아서 HTTP에 비해 overhead가 적은 장점이 있습니다.

 

핸드 세이크가 끝난 시점부터 서버와 클라이언트는 서로 살아 있는지 확인하기 위해(자기 멋대로 말도 안 하고 연결을 끊었을 수도 있으니까) hearbeat 패킷을 보내 주기적으로 체크합니다. ping을 받으면 수신측은 pong을 보내 연결이 정상적으로 유지되고 있음을 응답합니다. 이는 서버와 클라이언트 양측에서 설정 가능합니다. 참고로 ping 프레임은 Opcode가 "9"인 컨트롤 프레임 pong 프레임은 Opcode가 "10"인 컨트롤 프레임입니다.

 

Close Handshake

클라이언트와 서버 모두 커넥션을 종료하기 위한 컨트롤 프레임(opcode가 "8"인 프레임)을 전송할 수 있습니다. 이때 현재 전송 중인 메시지가 있다면 Colse 프레임 응답을 늦출 순 있지만, 이미 Close 프레임을 보낸 측에서 해당 데이터를 처리할지는 알 수 없습니다. 최종적으로 웹소켓이 종료되면 곧이어 TCP 연결 또한 종료됩니다.

 

WebSocket의 한계 

WebSocket이 만능은 아니고 약간의 문제점이 있습니다. 이에 대해 알아봅시다.

 

HTML5 이전에서는?

웹소켓은 HTML5 사양에서 TCPConnection으로 처음 참조되었습니다. 따라서 그 이전 사양에서는 사용할 수 없으며, 적절한 방법 예를 들어 위에서 설명한 폴링이나 롱 폴링 등을 사용하여 실시간성을 챙겨야 합니다.

 

이럴 때 Socket.io를 사용하면 편리합니다. Socket.io는 WebSocket, FlashSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling을 하나의 API로 추상화한 것입니다. 즉 브라우저와 웹 서버의 종류와 버전을 파악하여 가장 적합한 기술을 선택하여 줍니다. 가령 브라우저에 Flash Plugin v10.0.0 이상(FlashSocket 지원 버전)이 설치되어 있으면 FlashSocket을 사용하고, Flash Plugin이 없으면 AJAX Long Polling 방식을 사용합니다.

 

데이터의 해독은?

WebSocket은 데이터를 주고 받을 수 있게 해줄 뿐입니다. 주고 받은 문자열의 해독은 온전히 애플리케이션에게 맡깁니다. 즉, WebSocket만 사용해서 데이터를 주고 받을 때에는 해당 메시지가 어떤 요청인지, 어떤 포맷으로 오는지, 메시지 통신 과정을 어떻게 처리해야 하는지 정해져 있지 않아서 일일이 구현해야 합니다.

 

보통 이 문제는 STOMP(Simple Text Oriented Message Protocol)라는 프로토콜을 서브 프로토콜로 사용하여 해결합니다. STOMP는 클라이언트와 서버가 서로 통신하는 데 있어 메시지의 형식, 유형, 내용 등을 정의해주는 프로토콜이라고 할 수 있습니다.

 

STOMP를 사용하면 단순한 Binary, Text가 아닌 규격을 갖춘 메시지를 보낼 수 있으며, STOMP는 아래와 같이 command와 header 그리고 body로 이루어져 있습니다. 헤더와 바디는 빈 라인으로 구분하며, 바디의 끝은 NULL 문자로 설정합니다.

COMMAND
header1:value1
header2:value2

Body^@

 

요약

실시간성을 위해 구현할 수 있는 방법이 여러가지가 있는데, 대표적으로 Polling, Long Polling, Streaming SSE, WebSocket이 있습니다. 이중 Polling, Long Polling, Streaming SSE는 HTTP를 활용하여 실시간성을 달성하는 데 Polling은 요청을 계속 보내서 변경을 확인하는 방법이고, Long Polling은 하나의 요청에 대한 응답을 이벤트가 발생했을 때로 늦추는 방법이며, Streaming과 SSE는 한 번 요청하면 연속하여 스트림/이벤트를 전달하는 방법입니다. WebSocket은 맨 처음 HTTP를 사용하여 연결을 수립하지만 그 이후에는 ws를 사용하여 통신을 하며 ws 헤더는 HTTP 헤더보다 작아서 오버헤드가 작다는 장점이 있으며, 전이중 통신을 지원합니다.

 

 

 

 

 


https://medium.com/deliveryherotechhub/what-is-server-sent-events-sse-and-how-to-implement-it-904938bffd73

 

What is Server-Sent Events (SSE) and how to implement it?

Server sent events(SSE) is a pushing technology that enables pushing notification/message/events from the server to the client(s) via HTTP…

medium.com

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Polling-Long-Polling-Server-Sent-Event-WebSocket-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC

 

🌐 Polling / Long Polling / Server Sent Event / WebSocket 정리

서버의 event를 클라이언트로 보내는 4가지 방법 polling 클라이언트가 평범한 http request를 서버로 계속 날려서 이벤트 내용을 전달받는 방식이다. 가장 쉬운방법이지만 클라이언트가 계속적으로 re

inpa.tistory.com

https://stackoverflow.com/questions/42559928/what-is-the-difference-between-http-streaming-and-server-sent-events

 

What is the difference between HTTP streaming and server sent events?

My understanding is that HTTP streaming involves the client sending an HTTP request and then response to the request being sent over time allowing the server to essentially push to the client. In w...

stackoverflow.com

https://surviveasdev.tistory.com/entry/%EC%9B%B9%EC%86%8C%EC%BC%93-%EA%B3%BC-SSEServer-Sent-Event-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0

 

웹소켓 과 SSE(Server-Sent-Event) 차이점 알아보고 사용해보기

최근에 어떤 이벤트가 생겼을 때 client side에 ui를 업데이트해야 되는 기능을 구현해야 됐었습니다. 처음에는 이런 경우에 사용할 수 있는 것이 socket 밖에 몰라서 socket.io를 사용해서 socket으로 만

surviveasdev.tistory.com

https://jaehyeon48.github.io/network/websocket-protocol/

 

웹 소켓 프로토콜

등장 배경 웹 소켓이 등장하기 전에 실시간 통신을 구현하기 위해 사용되던 방식에는 폴링, 롱폴링, HTTP 스트리밍 기법 등이 있습니다. 하지만 이러한 기법들은 근본적으로 HTTP 프로토콜을 사용

jaehyeon48.github.io

https://d2.naver.com/helloworld/1336