Ⅰ. Django Channels
1. 사용하는 이유
•
Django는 기본적으로 요청(request)-응답(response) 주기로 동작하는데, 이것은 실시간 애플리케이션에 적합하지 않다.
•
이러한 한계를 극복하고 실시간 통신 및 비동기 작업을 처리하기 위해 Django Channels 라이브러리를 사용할 수 있다.
2. 주요 기능
1.
WebSocket : 서버와 실시간으로 데이터 주고 받을 수 있다.
•
예를 들어 실시간 채팅, 게임, 실시간 업데이트 등을 할 수 있음
2.
비동기 작업 : 시간이 오래 걸리는 작업을 블로킹하지 않고 처리할 수 있다.
•
예를 들어 백그라운드 작업, 큐잉 시스템과의 통합, 실시간 이벤트 처리 등을 할 수 있음
3.
채널 시스템 : 다양한 프로세스 간 통신 지원
•
이를 통해 여러 서버 인스턴스 또는 다른 백엔드 시스템과 통합할 수 있음
Ⅱ. Django Channels 사용해보기
1. Channels 설치
•
pip install Django channels
2. settings.py 수정
•
INSTALLED_APPS에 channel 추가
•
ASGI_APPLICATION에 ASGI 애플리케이션 지정
◦
ASGI_APPLICATION = 'project_name.routing.application'
◦
ASGI(Asynchronous Server Gateway Interface)란?
▪
웹 서버와 웹 애플리케이션 사이에 비동기 통신을 위한 인터페이스
▪
기본적으로 Django는 WSGI(Web Server Gateway Interface)를 사용하여 동기적인 요청-응답 처리를 하는데, 이러한 동작 방식은 실시간 기능을 구현하는 데 적합하지 않음
•
CHANNEL_LAYERS에 실시간 통신을 처리할 레이어 지정
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
Python
복사
◦
channels.layers.InMemoryChannelLayer는 임시 메모리 기반 채널 레이어로, 개발 및 테스트 목적으로 사용해볼 수 있지만, 실제 프로덕션 환경에서는 Redis와 같은 외부 메시지 브로커(?)를 사용하는 것이 권장된다.
3. ChatConsumer 생성
•
ChatConsumer 클래스 생성하고, WebSocket 통신을 위한 메서드를 정의합니다.
# chat_app/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
# 클라이언트가 WebSocket 연결을 시도할 때 호출되며,
# 연결을 수락하고 그룹에 사용자를 추가합니다.
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{self.room_name}'
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
# 클라이언트가 WebSocket 연결을 해제할 떄 호출되며,
# 그룹에서 사용자를 제거합니다.
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# 클라이언트로부터 메시지를 받을 때 호출되며,
# 받은 메시지를 그룹 내의 모든 클라이언트에 전송합니다.
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# 그룹으로부터 메시지를 받을 때 호출되며,
# 받은 메시지를 현재 클라이언트에게 전송합니다.
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))
Python
복사
◦
클래스명이 Consumer인 이유는?
▪
클라이언트와 서버 간의 WebSocket 연결을 통한 실시간 통신을 “소비”한다는 의미이다.
▪
Django Channels에서 WebSocket 연결 및 비동기 작업을 처리하는 클래스에 “Consumer”라는 용어를 보편적으로 사용한다.
4. routing.py 생성
•
WebSocket 라우팅을 설정하기 위해 프로젝트 디렉토리 아래에 routing.py 파일 생성한다.
•
예시 코드
# chat_project/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
Python
복사
•
왜 urls.py를 사용하지 않고, routing.py를 따로 만들어주는지?
◦
urls.py : 단방향 통신인 HTTP 요청을 처리하기 위한 규칙을 정의합니다.
◦
routing.py : WebSocket과 같은 실시간 통신, 비동기 작업을 처리하기 위한 라우팅 규칙을 정의합니다.
5. WebSocket URL 설정
•
WebSocket을 연결할 주소를 설정합니다. (강사님이 제공한 예제 코드에는 빠져있음)
# chat_project/settings.py
WebSocket_URL = "ws://localhost:8000"
Python
복사
◦
WebSocket 주소는 아래와 같은 형식을 가질 수 있다.
▪
ws://example.com: 실제 도메인 또는 호스트 주소를 사용하는 경우.
▪
wss://example.com: 안전한 WebSocket 연결을 위해 SSL을 사용하는 경우.
▪
ws://localhost:8000: 로컬 개발 서버에서 WebSocket 연결을 테스트하려는 경우.
6. URL 패턴 설정
•
WebSocket URL 패턴을 설정합니다.
# chat_app/urls.py
from django.urls import path
from . import views
urlpatterns = [
# 기존 URL 패턴
# ...
# WebSocket URL 패턴
path('ws/chat/<str:room_name>/', views.chat_room, name='chat_room'),
]
Python
복사
◦
실시간 채팅은 WebSocket 연결로 실시간 통신을 하는데,
HTTP와 같이 단방향 통신을 처리하는 urls.py에도 URL 패턴을 설정해야 하는 이유는?
▪
실시간 채팅 외에도 채팅 목록 조회, 새로운 채팅 생성 등의 HTTP 요청도 처리해야 하기 때문이다.
7. Views 및 Templates 설정
•
뷰와 템플릿을 생성하고, 클라이언트 측에서 WebSocket을 사용하여 1:1 채팅을 구현한다.
•
예제 코드
# views.py
from django.shortcuts import render
def chat_room(request, room_name):
# WebSocket 연결을 위한 경로 설정
ws_path = f"/ws/chat/{room_name}/"
# 템플릿에 전달할 context 데이터 설정
context = {
'room_name': room_name,
'ws_path': ws_path,
}
# chat_room.html 템플릿을 렌더링하여 화면 반환
return render(request, 'chat_room.html', context)
Python
복사
// WebSocket 연결을 위한 JavaScript 코드
const socket = new WebSocket("ws://" + window.location.host + "{{ ws_path }}");
// WebSocket 연결 이벤트 리스너
socket.onopen = function () {
console.log("WebSocket 연결이 열렸습니다.");
};
// WebSocket 연결 종료 이벤트 리스너
socket.onclose = function () {
console.log("WebSocket 연결이 종료되었습니다.");
};
// WebSocket 메시지 수신 이벤트 리스너
socket.onmessage = function (event) {
const messageData = JSON.parse(event.data);
// 메시지 데이터를 DOM에 추가하여 채팅 내용을 렌더링
// 예: const chatContainer = document.getElementById('chat-container');
// chatContainer.innerHTML += `<div>${messageData.username}: ${messageData.message}</div>`;
};
// 채팅 메시지를 서버로 전송하는 함수
function sendMessage(message) {
const messageData = {
username: "사용자 이름", // 메시지를 보낸 사용자의 이름
message: message
};
socket.send(JSON.stringify(messageData));
}
JavaScript
복사
◦
socket.onmessage : 이 이벤트 리스터에서 서버로부터 수신된 메시지를 파싱하고, DOM에 추가하여 채팅 내용을 실시간으로 렌더링합니다.
◦
sendMessage : 이 함수를 사용하여 새로운 메시지를 서버로 전송합니다.