비동기적, 즉 실시간 채팅을 구현하기 위해서 WebSocket 을 사용하고자 하였고, django에서 이를 가능하게 해주는 Channels 라이브러리를 공부해봤습니다!
WebSocket
은 프로토콜로서, 실시간으로 데이터를 양방향 통신 할 수 있게 해주는 기술 입니다. Socket 이란 쉽게 생각해서 통신을 위한 통로라고 생각하면 됩니다. 이런 통로를 Web 에도 도입하게 되었는데 기존의 Web이 정보를 어떻게 주고 받는지 생각해보면 socket이 필요한 이유를 알 수 있습니다.
Web 환경은 필요한 정보를 HTTP 기반으로 Request/Response로 연결하여 데이터를 주고 받아 네트워크의 연결을 유지하지 않는 특징을 가지고 있습니다. 그렇기 때문에 연결을 유지하며 실시간으로 데이터를 주고 받기에는 새로운 개념, WebSocket 이 필요하게 된 것 입니다.
사실 HTTP 프로토콜에 대해 잘 파악하고 계신 분이라면 HTTP 방식으로도 실시간성을 보장하는 기법이 존재한다는 것을 알고 계실겁니다. HTTP 와 같은 단방향 프로토콜로, 제한된 웹 환경에서 웹 브라우저와 서버 사이의 양방향 실시간 통신을 위한 다양한 위회 기법들이 존재합니다. 그들은 비교.분석 해보겠습니다.
Polling 은 웹 브라우저가 주기적으로 서버에게 이벤트가 발생했는지 확인하는 방식입니다. 만일 이벤트가 발생하지 않았다면 Response 에는 이벤트 정보가 포함되지 않고, 반대로 이벤트가 발생했다면 포함되는 방식입니다.
다만 주기적으로 서버에게 이벤트 발생여부를 확인하는 방식이므로 실시간성이 떨어지는 단점이 있으며, 주기적인 요청/응답이 오가기 때문에 계속 트래픽이 발생한다는 문제도 있습니다.
Long Polling 은 기존 Polling 의 실시간성을 보완한 기법입니다. 웹 브라우저가 요청을 전송할때 이벤트가 발생하기 전까지는 Response 를 보내지 않고, 이벤트가 발생할때만 Response 를 이벤트 정보와 함께 담아서 보내는 방식입니다.
실시간성을 보완한 좋은 방식이 되었지만, 그러나 서버의 이벤트가 자주 발생하지 않는 환경에서 이용하는 것이 좋습니다. 서버의 이벤트가 자주 발생하면 비효율적인 트레픽이 발생할 수 있기 때문이죠.
마지막으로 Server-sent Events 기법은 유추할 수 있듯이, 클라이언트의 요청은 없이 서버에서 클라이언트에게 단방향으로 이벤트를 전송하는 기법입니다. 즉, 클라이언트에서 서버로는 이벤트를 전송할 수는 없는 방식입니다.
또 클라이언트는 서버에게 특정 이벤트를 구독하는(Subscribe) 요청을 전송합니다. 이후 서버는 해당 이벤트가 발생시 웹 브라우저에게 해당 이벤트를 전송합니다.
HTTP 방식과의 차이점을 다시 간단히 짚고 넘어갈 필요가 있습니다. 정리해보자자면 아래와 같습니다.
HTTP
- 비 연결성(Connection less)
- 매번 연결을 맺고 끊는 과정의 비용이 많이듭니다.
- Request - Response 의 구조를 지닙니다.
Web Socket
- 연결지향적인 특징을 지닙니다.
- 한번 연결을 맺은 뒤 계속 유지되는 실시간성을 보장합니다.
- 양방향 통신이라는 특징을 지닙니다.
1) HTTP는 클라이언트가 원하는 어떤 결과를 얻고자한다면 항상 서버에 요청을 보내야하지만, 웹소켓은 연결이 계속 유지되고 있는 상태이므로 클라이언트가 보낸 메시지를 서버는 그냥 듣고있기만 하면 됩니다.
2) HTTP 에 비해 웹소켓이 보내야하는 메시지, 데이터 양이 훨씬 적습니다.
HTTP 요청을 보낼시 Request URL, 상태코드, Request&Response 헤더와 바디, .. 등의 데이터 양이 매번 요청으로 만들어져서 서버로 보내진다면 실시간성을 요구하는 서비스에서는 꽤 큰 부담이 생길것입니다.
3) 웹 소켓을 사용할때 처음 HandShake를 하는 과정은 HTTP 프로토콜을 통해 진행하겠지만, 한번 연결이 수립된 후로는 간단한 메시지만 오가는 방식입니다.
따라서 앞서 살펴본 HTTP Polling 과 같은 방식으로 실시간성을 보장하는 서비스에 활용하는 것은 그리 좋은 방법은 아닐겁니다. 웹소켓이 아무리 보더라도 실시간 통신에 있어서 더 유리한 점이 많기 때문입니다.
Channels
은 django 에서 HTTP 프로토콜이 아닌 다른 프로토콜을 사용할 수 있게 해줍니다. 위에서 알아봤듯이 WebSocket 은 프로토콜의 일종으로서 이를 사용하기 위해서 Channels 을 이용하게 됩니다. 아래 그림을 통해 일반적으로 django 가 Request/Response 을 다루는 경우와, Channels 을 사용했을 때의 차이를 알아볼 수 있습니다.
Django에 위에서 알아봤던 개념들을 적용시켜보도록 하겠습니다. 우선 프로젝트 app 을 구성해줍시다.
$ django-admin startproject mysite
$ python manage.py startapp chat
우선 Django Channels 라이브러리를 설치해줍시다!
$ pip install -U channels
이제 settings 에 channels 앱을 등록하고, ASGI_APPLICATION 을 추가해줍시다.
INSTALLED_APPS = [
'channels',
'chat',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
이제 channels 은 runserver 명령을 제어하고 django 개발 서버를 Channel 개발 서버로 대처하게 됩니다. 서버를 돌리게 되면 기존과 다른 방식으로 서버가 돌아가는 걸 확인할 수 있습니다.
현재는 채팅방에 접속을 해도 채팅방이 존재하지 않으므로 에러가 발생하지만, 채팅방에 해당하는 room.html을 만들고, 아래처럼 room 에 해당하는 view 를 만들어줍시다.
# chat/views.py
from django.shortcuts import render
from django.utils.safestring import mark_safe
import json
def index(request):
return render(request, 'chat/index.html', {})
def room(request, room_name):
return render(request, 'chat/room.html', {
'room_name_json': mark_safe(json.dumps(room_name))
})
이를 매핑해주는 urls도 추가해줍시다.
# chat/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
]
이렇게까지 잘 따라했다면, 원하는 채팅방으로 접속이 가능하고 채팅방에서 메세지를 작성함으로 대화를 할 수 있게 됩니다. 하지만 아직 웹소캣 소비자가 만들어지지 않았기 때문에 에러가 발생하게 되는데, 이에 대한 내용은 추후 포스트에서 자세히 다루도록 하겠습니다!