HTTP 기반 통신에서는 클라이언트가 서버에게 요청을 보내고, 서버가 그에 응하여 응답을 보내는 식으로 단방향 소통이 이루어진다.
일반적인 기능들은 이러한 단방향 방식으로 구현이 가능하지만, 다른 방식이 필요할 때가 있다.
채팅 앱의 경우, HTTP 기반 통신을 했을 때 상대방에게 메시지를 보내는 것은 클라이언트에서 서버로 요청하면 되기 때문에 문제가 없지만, 상대방이 메시지를 보냈을 때 이를 내 앱에서 확인하는 것은 불가능하다. HTTP/1.1 이하에서는 클라이언트의 요청 없이는 서버에서 메시지를 보내지 못하기 때문이다.
Polling이라는 방식으로 이를 해결할 수 있는데, 클라이언트가 주기적으로 서버에 요청을 보내서 상대가 새 채팅을 보냈는지 확인하는 방식이다. 이에 서버는 업데이트 유무를 응답한다.
그러나 Polling 방식에는 2가지 큰 문제가 있다.
- 첫 번째로, 요청을 보내는 주기만큼의 지연이 발생할 수 있다는 점이다. 이렇게 되면 서버가 갖고 있는 상태의 변화에 즉각적으로 반응하지 못하게 된다. 요청의 주기가 짧다면 반응속도는 빨라지겠지만 트래픽의 낭비는 더 심해질 것이다.
- 두 번째로, 불필요한 요청이 반복적으로 이루어진다는 점이다. 업데이트가 없을 때도 확인을 위해 요청이 필요하기 때문이다.
이를 개선한 방식으로는 Long Polling이 존재한다. 서버가 클라이언트의 요청에 바로 응답하지 않고 업데이트가 발생할 때까지 기다린다. 업데이트가 발생하거나 타임아웃으로 설정된 시간이 지나면 응답을 보내고 클라이언트는 다시 요청을 보내서 다음 응답을 기다린다.
이 방식을 사용하면 데이터의 업데이트에 반응하는 속도는 빨라지지만 서버의 부담이 커지게 된다.
- 서버가 클라이언트로부터 요청을 받을 때부터 응답을 보내기까지 클라이언트와의 연결이 지속되는데, 동시에 여러 클라이언트가 서비스를 사용하면 그만큼의 연결을 유지해야 하므로 부하가 발생하기 때문이다. 이는 결국 업데이트에 대한 반응이 느려지는 결과로 나타나기도 한다.
- 또한 HTTP에서의 요청과 응답에 포함되는 헤더 정보의 양도 부담으로 작용한다.
때문에 채팅 등과 같은 서비스를 제대로 구현하기 위해서는 클라이언트와 서버가 동등하게 메시지를 주고받을 수 있는 양방향 통신이 가능한 방식이 필요하다.
클라이언트에서 서버에게 WebSocket을 연결하자는 요청을 HTTP를 통해 보낸다. 서버는 가능한 경우 수락하는 응답을 HTTP로 보낸다. 이렇게 HandShake가 이루어진다.
연결이 이루어지고 나면 그 때부터 클라이언트와 서버는 HTTP가 아닌 WebSocket 프로토콜을 사용하여 소통한다.
이때부터 클라이언트와 서버는 자유롭게 서로에게 메시지를 보낼 수 있다.
WebSocket에서의 통신은 헤더의 크기가 작고 오버헤드가 작기 때문에 HTTP보다 효율적인 통신이 가능하다.
연결은 종료한다는 명시적인 요청이 있기 전까지 유지된다. 또한, 비정상적인 종료를 감지하는 방법들도 존재한다.
지정된 시간동안 메시지가 없을 시 확인 패킷을 보내는 방법도 있고, 주기적으로 ping pong 프레임을 주고받아서 서로의 접속 여부를 확인하는 방법도 있다.
WebSocket은 하나의 연결을 끝까지 유지하고, 그 과정에서도 적은 자원만 소모하기 때문에 Long Polling만큼 서버에 부담을 주지 않는다.
Websocket 연결을 위한 HTTP request header에는 연결을 WebSocket으로 업그레이드하자는 내용이 담긴다. 그리고 클라이언트가 랜덤으로 생성한 값을 Base64로 인코딩한 문자열이 Sec-WebSocket-Key로 담긴다.
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
서버는 이 요청을 받고 나서, GUID라 불리는 정해진 문자열을 그 키에 이어붙인 뒤 SHA-1 해시로 계산하여 다시 Base64로 인코딩한다.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
응답을 받은 클라이언트는 해당 키가 본인이 보낸 키로부터 생성된 값이 맞는지 확인한다. 확인 후 맞다면, 연결이 완료된다.
먼저 속한 OSI 7계층이 다르다.
TCP와 UDP 소켓은 전송(Transport) 계층인 Layer 4에서, WebSocket은 응용(Application) 계층인 Layer 7에서 동작한다.
WebSocket은 TCP 소켓을 기반으로 작동한다. 때문에 데이터의 순서와 신뢰성이 보장된다.
WebSocket은 특정 서버와의 지속적인 연결 안에서만 이루어지기 때문에 한 서버와 WebSocket 통신을 시작하면 이후로도 계속 그 서버로만 데이터가 전송되도록 설정해야 한다. 때문에 만약 로드 밸러서 등을 활용할 경우 이러한 특징을 고려해야한다.
또한 메시지의 크기가 제한되어 있다는 점도 고려할 부분이다.
브라우저, 서버, 네트워크 환경마다 WebSocket에서의 메시지 크기에 제약을 둘 수 있다. 이때 대용량 데이터의 경우, 분할해서 전송하거나 다른 프로토콜을 사용하는 등의 방법을 사용할 수 있다.
그리고 WebSocket의 기본 프로토콜인 WS는 통신이 암호화되어 있지 않다. 때문에 보안이 중요한 서비스라면 SSL/TLS 인증서를 발급받은 뒤 이를 사용하여 WSS를 설정해야 한다.
마지막으로, Polling 등의 방식보다는 훨씬 덜하지만 WebSocket도 서버에 부담을 주는 건 마찬가지이다.
많은 사용자들이 동시에 접속해 있을수록 유지해야 하는 TCP 연결이 많아지고, 메시지들이 오가는 빈도가 높다면 네트워크 대역폭과 CPU의 사용량도 증가할 것이다.
때문에 구현하고자 하는 서비스에 이러한 문제가 발생할 경우 적절 여부를 고려할 필요가 있다.