근채의 기술 블로그
[Django] Websocket으로 실시간 채팅 구현 본문
비동기적, 실시간 채팅을 구현하기 위하여 Websocket을 사용하게 된다. Django에서 Websocket을 통한 서버를 구현하기 위해 기본적으로 제공하는 라이브러리인 djsngo-channels 라이브러리를 사용하며, 전체적인 내용은 공식 문서를 참고하였다.
Websocket 이란?
Websocket은 양방향(Full-Duplex) 통신을 가능하게 하는 프로토콜로, 클라이언트(웹 브라우저)와 서버 간의 실시간 데이터 교환을 지원한다. Websocket을 사용하면, 일반적인 HTTP 요청-응답 방식과 달리, 한 번의 연결을 유지하면서 양방향으로 이터를 주고받을 수 있다.
Websocket 동작 방식
1. Handshake (HTTP Upgrade)
클라이언트는 서버에 웹소켓 연결을 요청하는 HTTP 업그레이드 요청을 보낸다. 이 요청은 표준 HTTP 요청과 유사하지만, "Upgrade: websocket" 헤더를 포함하여 HTTP 연결을 웹소켓 프로토콜로 전환하겠다는 의도를 나타낸다.
#클라이언트 요청 예시
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
서버가 이 요청을 수락하면, 101 Switching Protocols 응답을 보내고 웹소켓 연결이 시작된다.
#서버 응답 예시
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
연결이 성공적으로 열리면, HTTP 연결은 웹소켓 연결로 업그레이드 되며, 이후부터는 웹소켓 프로토콜이 사용된다.
2. Bi-directional Messages
웹소켓 연결이 열리고 나면, 클라이언트와 서버는 양방향으로 버는 JSON, XML, 텍스트 또는 바이너리 데이터 등의 메시지를 주고받을 수 있다. 이것이 바로 웹소킷의 핵심 기능이며, 지속적인 연결을 통해 실시간으로 데이터를 송수신한다.
이 통신은 네트워크 지연이 매우 적고, 연결이 열려 있는 동안에 클라이언트와 서버 어디서든지 데이터를 보낼 수 있다.
일반적인 TCP 소켓과 달리 메시지 프레임 단위로 데이터가 처리된다.
#Websocket 프레임 형식
0x00 <페이로드 데이터> 0xFF
3. One side closes channel
클라이언트와 서버 중 연결 종료를 원할 시에 연결을 닫는 신호를 보낸다. 웹소켓 연결 종료 프로세스가 시작이 되며, 종료 프레임을 송신하여 수행된다. 정상 종료 시 1000 상태 코드와 함께 연결이 종료된다. 결과적으로 연결이 닫히고, 메시지를 주고 받을 수 없게 되지만, 언제든지 새로운 연결을 다시 시작할 수 있다.
Channels 이란?
Django Channels는 Django에서 비동기 기능(WebSocket, HTTP/2, MQTT 등)을 지원하는 프레임워크이다. 기존 Django는 동기식(Synchronous) 방식으로 HTTP 요청-응답을 처리하지만, Django Channels를 사용하면 WebSocket, 실시간 알림, 채팅, 스트리밍 같은 비동기 기능을 구현할 수 있다.
Channels의 주요 특징
1. WebSocket 지원
Django 기본 요청-응답 모델과 달리 Websocket 연결을 유지하면서 실시간 데이터를 처리할 수 있다.
2. 비동기(Asynchronous) 지원
Django는 기본적으로 동기(Sync) 방식이지만, Django Channels는 async/await을 사용하여 비동기 처리가 가능하다.
3. 백그라운드 작업 (Background Tasks)
Django Channels는 Channel Layer를 사용하여 백그라운드에서 메시지를 처리할 수 있다.
4. MQTT, HTTP/2 등 다양한 프로토콜 지원
Websocket뿐만 아니라 MQTT, Redis Pub/Sub, Kafka 등 다양한 실시간 프로토콜을 지원한다.
5. Daphne 서버 사용
Django는 기본적으로 gunicorn 같은 WSGI(Web Server Gateway Interface)를 사용하지만, Django Channels는 ASGI(Asynchronous Server Gateway Interface) 기반의 Daphne 서버를 사용한다.
Channels 아키텍처
Django Channels는 3가지 주요 컴포넌트로 구성된다.
1. ASGI Server (Daphne)
Django의 비동기 기능을 지원하는 ASGI 서버로 Websocket을 포함한 비동기 처리가 가능하다.
2. Application Layer
Django와 같은 웹 애플리케이션이 실제로 동작하는 영역으로, Websocket 연결을 관리하고, 요청을 처리한다. consumersp.y에서 Websocket 메시지를 처리하는 Consumer를 정의한다.
3. Channel Layer (Redis)
Django Channels는 요청을 비동기적으로 처리하기 위해 Channel Layer를 사용한다. 일반적으로 Redis를 사용하여 여러 프로세스에서 메시지를 주고받을 수 있다.
![]() |
![]() |
Daphne란?
Django Channels를 실행하는 ASGI 서버로, Django의 기본 WSGI 서버(runserver) 대신 Websocket, HTTP/2, 비동기 쵸엉을 처리하는 서버 역할을 한다. Django 프로젝트에서 Websocket을 실행하려면 Daphne를 사용해야 하며, 기존 "python manage.py runserver" 대신 "daphne myproject.asgi:application" 명령어를 사용하여 실행하게 된다.
Daphne와 ASGI의 관계
전통적인 Django 응용 프로그램은 WSGI (Web Server Gateway Interface)를 사용하여 작동하지만, ASGI (Asynchronous Server Gateway Interface)는 Django의 비동기 기능을 확장하여 더 복잡하고 비동기적인 작업을 수행할 수 있도록 한다. Daphne는 이러한 ASGI 스펙을 구현하여, Channels와 함께 또는 독립적으로 비동기 웹 애플리케이션을 운영할 수 있는 서버로 사용된다.
Daphne의 주요 기능
1. 비동기 처리
Daphne는 동시에 여러 네트워크 연결을 비동기적으로 처리할 수 있다. 이는 채팅 서비스나 실시간 알림과 같은 기능을 구현할 때 중요한 요소이다.
2. 웹소켓 지원
Daphne는 HTTP와 웹소켓 프로토콜 모두를 지원한다. 이를 통해 클라이언트와 서버 간의 지속적이고 양방향 통신이 가능해진다.
3. 프로토콜 확장성
ASGI는 쉽게 확장할 수 있는 프로토콜을 지향하며, Daphne는 이를 구현한다. 따라서 HTTP/2 같은 새로운 인터넷 프로토콜을 지원할 수 있는 여지가 있다.
4. 스케일 아웃
Daphne는 다중 서버 인스턴스로 스케일 아웃이 가능하도록 설계되어 있다. 채널 레이어를 통해 메시지를 교환함으로써 여러 Daphne 인스턴스 간에 동기화할 수 있다.
5. 인터페이스 서버
Daphne는 인터페이스 서버로서 웹 애플리케이션과 네트워크 사이에 위치한다. 클라이언트의 비동기 요청을 받아서 ASGI 프로토콜을 통해 Django 애플리케이션에 전달한다.
WSGI와 ASGI란?
WSGI와 ASGI는 모두 Python 웹 애플리케이션과 웹 서버 사이의 인터페이스 표준이라는 공통점을 가지고 있다. 이 둘의 주요한 차이점은 WSGI는 동기식 통신을, ASGI는 비동기식 통신을 지원한다는 점이다.
WSGI (Web Server Gateway Interface)
1. 동기식 인터페이스
WSGI는 요청을 처리할 때 하나의 요청이 완전히 처리될 때까지 다른 요청을 기다리는 동기 방식을 사용하며, 이는 처리할 수 있는 요청의 수가 제한되어 동시성을 높이기 위해 여러 프로세스나 스레드를 사용해야 함을 의미한다.
2. Django와의 호환성
Django는 전통적으로 WSGI 표준을 따르는 웹 애플리케이션이며, 이 인터페이스를 통해 웹 서버와 통신을 한다.
3. 요청-응답 패턴 (Request-Reponse)
WSGI는 클라이언트에서 서버로 요청을 보내고 서버가 응답을 반환하는 전형적인 HTTP 요청-응답 패턴을 기반으로 한다. 이 방식은 웹 페이지를 요청하고 응답을 받는 전형적인 웹 애플리케이션에 적합하다.
ASGI (Asynchoronous Server Gateway Interface)
1. 비동기식 인터페이스
ASGI는 비동기식 프로그래밍을 지원하며, 여러 요청을 동시에 처리할 수 있고, 특히 I/O 작업이 많은 프로젝트에서 높은 효율을 보인다.
2. Websocket 지원
ASGI는 HTTP 요청뿐만 아니라 Websocket 같은 양방향 비동기 프로토콜도 지원을 한다. 따라서 실시간 통신이 필요한 채팅, 게임, 주식/코인 애플리케이션에 매우 적합하다.
3. Django와 Channels
Django는 기본적으로 ASGI를 지원하지 않기에, Channels 라이브러리를 사용하여 Django 애플리케이션에 ASGI 기능을 추가하고, 비동기 처리 및 Websocket 통신을 가능하게 한다.
4. 스케일러빌리티
ASGI는 하나의 이벤트 루프에서 여러 연결을 관리할 수 있기 때문에, 동일한 하드웨어 리소스에서 더 많은 연결을 처리할 수 있다. 이는 대규모 실시간 애플리케이션을 구축할 때 중요한 장점으로 이어진다.
실습 예제
Project 파일
먼저 터미널에 아래 명령어를 입력하여 Django Channles를 설치한다.
pip install channels
진행하는 Project 디렉토리의 settings.py에 아래 코드를 추가하여 ASGI 프로토콜을 설정한다.
#projectname/settings.py
ASGI_APPLICATION = 'projectname.routing.application'
'projectname'에는 진행하고자 하는 Project 파일 명을 입력하면 된다.
예를 들어 backend라는 디렉토리에 settings.py가 있다면, 아래와 같이 추가하면 된다.
ASGI_APPLICATION = 'backend.routing.application'
또한, Project 디렉토리 내에 asgi.py을 새로 생성하여 아래 코드를 입력한다.
#projectname/asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
import appname.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket":
AuthMiddlewareStack(
AllowedHostsOriginValidator(
URLRouter(
appname.routing.websocket_urlpatterns
)
),
),
})
위 코드에서 6번째 줄과 18번째 줄의 appname에는 웹소켓을 적용시키고 싶은 app 파일 명을 입력하면 된다.
예를 들어 채팅 기능 구현을 위해 chat이라는 app을 만들었다면, 아래와 같이 적용하면 된다.
#6번째 줄
import chat.routing
#18번째 줄
chat.routing.websocket_urlpatterns
App 파일
이제 적용시키고자 하는 app 파일을 만들고, routing.py와 consumers.py를 생성한다.
#yourapp/routing.py
from django.urls import path
from .consumers import Myconsumer
websocket_urlpatterns = [
path('ws/your_path/', MyConsumer.as_asgi()),
]
위 코드에서 your_path는 지정한 엔드포인트를 입력하면 되고, MyConsumer는 consumers.py에서 만든 class 명을 입력하면 된다.
#yourapp/consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class MyConsumer(AsyncWebsocketConsumer):
async def connect(self):
# 웹소켓 연결 시 실행될 로직
await self.accept()
async def disconnect(self, close_code):
# 웹소켓 종료 시 실행될 로직
pass
async def receive(self):
# 클라이언트로부터 입력을 받았을 때 실행될 로직
※ MyConsumer는 원하는 class 명으로 수정을 하도록 하자.
connect, disconnet, receive에는 각각 실행되고자 하는 코드를 구현하면 된다.
위와 같이 구현한다면 기초적인 websocket 연동은 완료이다.