- REST API, SOAP, GraphQL, gRPC 등은 주로 HTTP 기반이었음
- HTTP에서는 클라이언트가 서버에게 요청을 보내고 서버가 그에 응하여 응답을 보내는 식으로 단방향 소통이 이루어짐
- 서버가 클라이언트에게 메시지를 보내는 것은 클라이언트의 요청에 응답을 할 때 뿐임
- 일반적인 기능들은 이러한 단방향 방식으로 구현이 가능함
- 하지만 채팅 앱 같은 서비스는 불가능함
- 내가 상대방에게 메시지를 보내는것은 클라이언트에서 서버로 통보하면 되는 것이기 때문에 가능하지만
상대방이 메시지를 보냈을 때, 이를 나의 앱에서 감지하는것이 불가능함
(HTTP/1.1 이하에서는 클라이언트의 요청 없이는 서버에서 메시지를 보내지 못하기 때문임)
- HTTP를 사용해서 이 문제를 해결하기 위해서는 Polling이라는 방법이 있음
- 클라이언트가 주기적으로 서버에 요청을 보내서 상대가 새로운 채팅을 보냈는지 확인하는 방법임
- 서버는 업데이트가 있으면 있다고, 없으면 없다고 바로 응답을 보냄
Polling의 단점
- 요청을 보내는 주기만큼의 지연이 발생할 수 있음
- 서버가 가지고 있는 상태의 변화에 즉각적으로 반응하지는 못함
- 계속해서 불필요한 요청들이 보내짐
- 요청의 주기가 짧다면 반응속도는 빨라지겠지만 트래픽의 낭비가 심해짐
-> 개선하기 위해 Long Polling이 사용됨
Long Polling
-
서버가 클라이언트의 요청에 바로 응답하지 않고 업데이트가 발생할 때까지 기다림
-
그러다 상대방이 채팅을 보내거나 타임아웃으로 설정된 시간이 지나면 응답을 보내고, 클라이언트는 다시 요청을 보내서 다음 응답을 기다림
-
이 방식을 사용하면 데이터의 업데이트에 반응하는 속도는 빨라지지만 서버의 부담이 커짐
-
서버가 클라이언트로부터 요청을 받을 때부터 응답을 보내기까지 클라이언트와 의 연결이 지속되는데
동시에 여러 클라이언트가 서비스를 사용한다면 그만큼의 연결을 유지해야 하므로 부하가 발생함 -> 업데이트에 대한 반응이 느려지는 결과로 나타나기도 함
-
또한, HTTP에서의 요청과 응답에 포함되는 헤더 정보의 양도 매번 부담으로 작용함
-
이러한 서비스를 제대로 구현하기 위해서는 클라이언트와 서버가 동등하게 메시지를 주고받을 수 있는 양방향 통신이 가능한 방식이 필요함! -> WebSoket
WebSocket 개념
-
HTTP/1.1이 클라이언트가 편지로 요청을 보내고 서버는 답장만 하는 방식이라면
WebSocket은 서로가 자유롭게 대화를 주고받는 전화통화라고 볼 수 있음
-
HTTP/1.1 -> 편지
-
HTTP/2.0 -> 공중전화를 이용한 제한된 통화
-
WebSocket -> 스마트폰을 통한 자유로운 통화
WebSocket 통신과정
-
클라이언트에서 서버에게 WebSocket을 연결하자는 요청을 HTTP를 통해 보냄
(전화통화를 하자는 내용의 편지를 보냄)
-
서버는 그것이 가능한 경우 이를 수락하는 응답을 HTTP로 보냄
(이 과정을 Handshake라고 함)
-
연결이 이루어지만 그 때부터 클라이언트와 서버는 HTTP가 아닌 WebSocket 프로토콜을 사용하여 소통을 함
(이 때부터 전화통화가 이루어짐)
-
클라이언트와 서버는 자유롭게 서로에게 메시지를 보낼 수 있음
- WebSocket에서의 통신은 헤더의 크기가 작고 Overhead가 적기 때문에 HTTP보다 효율적인 통신이 가능함
- 전화통화는 한 쪽이 다른 쪽에게 통화를 종료하자는 메시지를 보내기 전까지 지속됨
- 한 쪽에 close 프레임을 보내면, 다른 쪽에서 이를 확인하고 close 프레임을 응답으로 보내고 연결이 종료됨
Handshake
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dgdjflgsarjwlkjedf==
Sec-WebSocket-Version: 13
- Upgrade, Connection 부분은 현재의 HTTP 연결을 WebSocket 프로토콜로 업그레이드 하자는 내용임
(지금부터 전화로 대화하자는 의미)
- Sec-WebSocket-Key에는 클라이언트가 랜덤으로 생성한 값을 Base64로 인코딩한 문자열이 담김
- 서버는 이 요청을 받고나서 GUID라고 불리우는 정해진 문자열을 그 키에 이어붙인 후 SHA-1 해시로 계산하여 다시 base64로 인코딩함
(모두에게 알려진 어떤 공식을 통해 위의 Sec-WebSocket-Key의 문자열을 아래의 Sec-WebSocket-Accept 문자열로 바꿈)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s4fCDSJKREWFDSLKADXDLFKS==
Sec-WebSocket-Version: 13
- 서버는 이 값을 헤더에 담아 클라이언트에게 돌려보내고
클라이언트는 이것이 자신이 보낸 키로부터 생성된 값이 맞는지 확인함
(모두에게 알려진 공식이기 때문에 서버가 제대로 키를 처리했다는 것을 클라이언트도 확인 가능)
- 이것으로 클라이언트는 응답이 자신이 요청을 보낸 상대로부터 온 것임을 확인하고, 둘은 자유롭게 양방향으로 소통할 수 있게 됨
비정상적인 종료 감지하는 방법들
-
지정된 시간동안 메시지가 없을 시 확인 패킷을 보내는 방법
-
주기적으로 ping pong 프레임을 주고받아서 서로의 접속여부를 확인하는 방법
-
WebSocket은 하나의 연결을 끝까지 유지하고, 그 과정에서도 적은 자원만 소모하기 때문에 Long Polling만큼 서버에 부담을 주지도 않음
-
WebSocket을 사용해서 채팅 앱과 같은 실시간으로 서버로부터의 업데이트가 필요한 서비스들을 효율적으로 구축할 수 있음
-
WebSocket에서의 통신은 TCP를 사용하기 때문에 데이터의 순서와 신뢰성이 보장됨
-
TCP: Transmission Control Protocol
-
이와 같은 장점들 덕분에 WebSocket은 채팅 앱 뿐 아니라 온라인 게임, 주식 관련 앱, 협업 도구, 위치 추적 등 실시간 양방향 통신이 필요한 분야에 활용이 됨
WebSocket의 한계
- 서버의 설계에 따라 구현이 복잡해질 수 있음
-
특히 로드 밸런싱이 적용된 서버에서는 이를 고려하고 설정할 부분이 많아짐
-
NGINX, HAProxy, AWS ELB 등 WebSocket을 처리할 수 있는 로드 밸런서를 선택하여 구성하는 등 서비스에 적합한 방법을 찾아 해결하면 됨
로드 밸런싱
- 은행 상담창구처럼 서버 여러대가 클라이언트의 요청을 나눠서 받는 것
- WebSocket은 특정 서버와의 지속적인 연결 안에서만 이루어지기 때문에
한 서버와 WebSocket 통신을 시작하면 그 이후로도 계속 그 서버로만 데이터가 전송되도록 설정해야 함
- 메시지의 크기가 제한되어 있음
- 브라우저, 서버, 네트워크 환경마다 WebSocket에서의 메시지 크기에 제약을 둘 수 있음
- 대용량의 데이터의 경우, 분할해서 전송하거나 다른 프로토콜을 사용하는 등의 방법을 사용할 수 있음
- WebSocket의 기본 프로토콜인 WS는 통신이 암호화 되어있지 않음
- 보안이 중요한 서비스라면 SSL/TLS 인증서를 발급받은 후 이를 사용하여 WSS를 설정해야 함
- WebSocket도 서버에 부담을 줌
- Polling 등의 방식보다는 훨씬 덜하지만 그래도 서버에 부담을 줌
- 많은 사용잘의 동시에 접속해 있을 수록 유지해야 하는 TCP 연결이 많아지고,
메시지들이 오가는 빈도가 높다면 네트워크 대역폭과 CPU의 사용량도 증가함
- 구현하고자 하는 서비스에 이러한 문제가 발생할 경우 WebSocket이 가장 적절한 선택인지 고려해봐야함