채팅을 구현해야하는 프로젝트의 요구사항이 존재해 채팅 기능을 개발해야 했습니다. 채팅이라고 하면 웹 소켓이 가장 먼저 떠올랐지만 다르게 구현할 수 있는 기술이 무엇이 있을까 하고 고민했습니다. 오늘은 그 과정을 포스팅 하고자 합니다.
기능 요구사항 중 채팅 기능에 대한 요구사항이 있었습니다. 그런데 단순히 "채팅 기능이 있어야 한다." 정도로 요구사항이 써져있었기 때문에 문제에 대한 상황은 저희가 정해야 했습니다.
채팅을 구현하는 방식에는 여러가지가 존재합니다. 그중에서 우리 팀이 생각한 방식은 아래 세 가지 입니다.
처음으로 폴링에 대해서 알아보겠습니다. 클라이언트가 일정 주기로 서버에게 필요한 데이터를 요청하는 방식입니다. 예를 들면, 이런 거죠!
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 없는데?
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 없다니까?
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: ...없어
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 오 이번에는 줄게 있어.
가장 구현하기 쉬운 방법이지만 다음과 같은 단점이 있습니다.
서버에 변경사항이 없어도 클라이언트는 계속 요청을 보내게 됩니다.
이로 인해 서버에 부담을 주게 됩니다. 또한 HTTP Connection을 맺기 위한 비용이 계속 발생합니다.
폴링 방식은 서비스가 매우 작고 구현이 굉장히 급하다면 선택할 수 있겠지만 일반적으로 더 나은 방법을 필요로 합니다.
롱 폴링은 이름답게 클라이언트의 요청에 대해서 서버가 일정시간동안 기다렸다가 클라이언트에게 응답을 주는 방식입니다.
이 방식도 한 번 예를 들어 보겠습니다.
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다렸다가)...없어
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다렸다가)...없는데
클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다리는 중에 채팅 메시지가 옴)여기있어!
즉 롱폴링의 흐름은 다음과 같습니다.
롱폴링 방식은 클라이언트와 연결을 유지하며 변경된 데이터가 발생하거나 정해진 타임아웃 시간이 지나면 데이터를 전송하게 됩니다. 하지만 다음과 같은 단점이 있습니다.
웹소켓은 채널을 이용해 양방향 통신을 가능하게 합니다. 기본적으로 HTTP는 단방향 통신이기 때문에 서버가 먼저 클라이언트에게 응답할 수 없습니다. 하지만 양방향 통신을 가능하게 하는게 바로 웹소켓이라는 기술입니다.
이때문에 웹소켓은 HTTP 프로토콜을 사용하지 않고 WS 프로토콜을 사용합니다. 웹소켓의 동작과정은 TCP에 의존하며 동작하게 되는데 과정을 간단하게 적어보겠습니다.
최초 연결 요청시 HTTP를 통해 웹 서버에게 나 너랑 웹소켓 프로토콜로 통신하고 싶어라고 요청합니다. RFC 6455문서를 보면 아래와 같은 요청을 먼저 보낸다고 합니다.
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
눈여겨 볼점은 Upgrade 헤더를 함께 전송하는 것이겠네요.
이를 받은 서버는 이에 대한 응답을 아래와 같이 보내게 됩니다.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
이렇게 핸드쉐이크가 성공하면 프로토콜이 Websokcet으로 전환됩니다.
앞서 설명했듯이 웹소켓은 양방향 통신을 가능하게 합니다. 따라서 서로는 메시지를 주고 받으며 통신하는데 이때 전송되는 메시지를 웹소켓 프레임이라고 합니다. (자세한 부분은 이 블로그에 나와 있습니다.)
또한 연결 수립 이후에 서버와 클라이언트는 언제든 상대방에게 ping 패킷을 보낼 수 있습니다. ping을 받은 수신측은 pong을 빠르게 응답해야 합니다. 이런 과정으로 상대방과의 연결이 지속되고 있는지 확인하는 과정을 Heartbeat라고 합니다.
클라이언트 혹은 서버측 어느 누구나 연결을 종료할 수 있습니다. 연결 종료를 원하는 쪽이 상대방에게 close frame을 전송합니다.
결론부터 말하자면 저희는 롱폴링 방식을 이용해 채팅을 구현하기로 했습니다. 이유는 다음과 같습니다.
이러한 이유들로 저희는 롱폴링 방식을 결정하게 되었습니다!
https://ko.javascript.info/long-polling
https://developer.mozilla.org/ko/docs/Web/API/WebSockets_API
https://datatracker.ietf.org/doc/html/rfc6455#section-1.2