[Django] Django rest framework로 웹소켓 채팅 서버 구현하기(1)

이민재·2023년 11월 6일
3
post-thumbnail

웹 개발의 세계는 항상 새로운 기술과 패러다임의 변화를 맞이하고 있습니다. 오늘날 실시간 통신은 주식,코인 사이트, 채팅 등 웹과 모바일 애플리케이션에서 필수적인 요소가 되었습니다.

이 블로그 에서는 Python의 강력한 웹 프레임워크인 Django와 그 REST framework를 사용하여 실시간 채팅 서버를 구현하는 방법에 대해 알아보겠습니다.

코드를 작성하기전에 웹소켓이 뭐고 어떻게 동작하는지를 알아야지 구조를 이해하기 쉽기때문에 먼저 웹소켓에 대해 알아봅시다.

📌웹소켓이란?

웹소켓은 웹 상에서 양방향 통신을 가능하게 하는 기술입니다. 전통적인 HTTP 통신과 달리, 웹소켓은 ws(또는 보안 연결에 wss) 프로토콜을 사용하여 구현되고 서버와 클라이언트 간에 지속적인 연결을 유지하며, 한번의 핸드셰이크 이후 양쪽 모두에서 자유롭게 데이터를 전송할 수 있게 하여데이터를 실시간으로 교환할 수 있습니다. 이는 채팅 애플리케이션과 같이 빠른 상호작용이 필요한 서비스에 매우 적합합니다.

📌웹소켓의 핵심 요소

영속 연결(Persistent Connection): 웹소켓은 TCP/IP 프로토콜을 기반으로, 클라이언트와 서버 간에 지속적인 연결을 유지합니다. 이를 통해 연결이 한 번 수립되면, 서버나 클라이언트가 연결을 끊을 때까지 데이터를 지속적으로 교환할 수 있습니다.

양방향 통신(Bi-directional Communication): 데이터 흐름이 양방향으로 이루어져 클라이언트와 서버가 동시에 메시지를 보낼 수 있습니다. 이는 실시간 데이터 전송에 적합합니다.

전이중 통신(Full-Duplex Communication): 웹소켓은 전이중 통신을 지원하여, 클라이언트와 서버가 동시에 서로에게 데이터를 보낼 수 있습니다. 이는 클라이언트가 데이터를 보내는 동안 서버도 독립적으로 데이터를 보낼 수 있음을 의미합니다.

저오버헤드(Low Overhead): 한 번의 연결 설정 이후에는 추가적인 HTTP 헤더를 교환할 필요 없이 순수한 데이터만 전송됩니다. 이는 효율적인 네트워크 자원 사용을 가능하게 합니다.

📌웹소켓의 작동 방식

📌1. Handshake (HTTP Upgrade)

  • 역할: 이 단계에서 클라이언트는 서버에 웹소켓 연결을 요청하는 HTTP 업그레이드 요청을 보냅니다. 이 요청은 표준 HTTP 요청과 유사하지만, "Upgrade: websocket" 헤더를 포함하여 HTTP 연결을 웹소켓 프로토콜로 전환하겠다는 의도를 나타냅니다.
  • 과정: 서버가 이 요청을 수락하면, 101 Switching Protocols 응답을 보내고 웹소켓 연결이 시작됩니다.
  • 결과: 연결이 성공적으로 열리면, HTTP 연결은 웹소켓 연결로 업그레이드되며, 이 후부터는 웹소켓 프로토콜이 사용됩니다.

📌2. Bi-directional Messages

  • 역할: 웹소켓 연결이 열리고 나면, 클라이언트와 서버는 양방향으로 버는 JSON, XML, 텍스트 또는 바이너리 데이터 등의 메시지를 주고받을 수 있습니다. 이것이 바로 웹소켓의 핵심 기능으로, 지속적인 연결을 통해 실시간으로 데이터를 송수신합니다.
  • 과정: 클라이언트는 서버로 메시지를 보낼 수 있고, 서버도 클라이언트로 메시지를 보낼 수 있습니다. 이 통신은 네트워크 지연이 매우 적고, 연결이 열려 있는 동안에는 어느 한쪽에서든지 데이터를 보낼 수 있습니다.
  • 결과: 빠르고 효율적인 실시간 통신이 가능하며, 이는 채팅 애플리케이션, 게임, 실시간 트레이딩 시스템 등에 이상적입니다.

📌3. One side closes channel

  • 역할: 어느 한쪽이 연결을 종료하기를 원할 때, 해당 측은 연결을 닫는 신호를 보냅니다.
  • 과정: 클라이언트나 서버 중 하나가 연결을 닫기로 결정하면, 웹소켓 연결 종료 프로세스가 시작됩니다. 이는 특별한 종료 프레임을 송신하여 수행됩니다.
  • 결과: 연결이 닫히고, 더 이상 메시지를 주고받을 수 없습니다. 하지만 필요하다면, 새로운 연결을 다시 시작할 수 있습니다.

이 과정을 통해 웹소켓은 전통적인 HTTP 기반 통신보다 훨씬 효율적인 방법으로 실시간 양방향 통신을 제공하며, 채팅 애플리케이션의 중요 요구 사항을 만족시킬 수 있습니다.

이제 Django에서 채팅서비스를 어떻게 구현하는지 알아봅시다.

Django는 Django Channels 라이브러리를 이용하여 채팅을 구현하는데 Django의 전통적인 동기식, 요청-응답 기반 통신을 비동기식 웹소켓 통신으로 확장하는 라이브러리입니다. 이를 통해 개발자는 Django의 기존 구조와 비슷한 패턴으로 실시간 기능을 구현할 수 있습니다. Channels는 비동기 웹 프로토콜을 처리하기 위해 Django의 기본적인 WSGI 인터페이스를 ASGI(Asynchronous Server Gateway Interface)로 대체합니다.

📌WSGI(Web Server Gateway Interface)와 ASGI(Asynchronous Server Gateway Interface)란?

WSGI(Web Server Gateway Interface)와 ASGI(Asynchronous Server Gateway Interface)는 둘 다 Python 웹 애플리케이션과 웹 서버 사이의 인터페이스 표준입니다. 그러나 주요 차이점은 WSGI가 동기식 통신을, ASGI가 비동기식 통신을 지원한다는 점에 있습니다.

📌WSGI (Web Server Gateway Interface)

  • 동기식 인터페이스: WSGI는 요청을 처리할 때 하나의 요청이 완전히 처리될 때까지 다른 요청을 기다리게 하는 동기 방식을 사용합니다. 이는 처리할 수 있는 요청의 수가 제한되며, 동시성을 높이기 위해 여러 프로세스나 스레드를 사용해야 함을 의미합니다.
  • 장고와의 호환성: Django는 전통적으로 WSGI 표준을 따르는 웹 애플리케이션이며, 이 인터페이스를 통해 웹 서버와 통신합니다.
  • 요청-응답 패턴: WSGI는 클라이언트에서 서버로 요청을 보내고 서버가 응답을 반환하는 전형적인 HTTP 요청-응답 패턴을 기반으로 합니다. 이 방식은 웹 페이지를 요청하고 응답을 받는 전형적인 웹 애플리케이션에 적합합니다.

📌ASGI (Asynchronous Server Gateway Interface)

  • 비동기식 인터페이스: ASGI는 비동기식 프로그래밍을 지원합니다. 이는 여러 요청을 동시에 처리할 수 있으며, 특히 I/O 작업이 많은 작업에서 높은 효율을 보입니다.
  • 웹소켓 지원: ASGI는 HTTP 요청뿐만 아니라 웹소켓 같은 양방향 비동기 프로토콜도 지원합니다. 따라서 실시간 통신이 필요한 채팅 애플리케이션에 매우 적합합니다.
  • Django와 Channels: Django는 기본적으로 ASGI를 지원하지 않습니다. Channels 라이브러리를 사용하여 Django 애플리케이션에 ASGI 기능을 추가하고, 비동기 처리 및 웹소켓 통신을 할 수 있습니다.
  • 스케일러빌리티: ASGI는 하나의 이벤트 루프에서 여러 연결을 관리할 수 있기 때문에, 동일한 하드웨어 리소스에서 더 많은 연결을 처리할 수 있습니다. 이는 대규모 실시간 애플리케이션을 구축할 때 중요한 장점입니다.

📌Daphne

Daphne는 Django 프로젝트의 ASGI 인터페이스를 통해 비동기 네트워크 프로토콜을 처리하는 ASGI 서버입니다. Daphne는 원래 Django Channels의 일부로 개발되었지만 독립적인 ASGI 서버로서 WebSocket, HTTP/2 및 기타 프로토콜을 지원합니다. 이러한 기능은 실시간 양방향 통신이 중요한 웹 애플리케이션에 매우 중요합니다.

📌ASGI와의 관계

전통적인 Django 응용 프로그램은 WSGI (Web Server Gateway Interface)를 사용하여 작동하지만, ASGI (Asynchronous Server Gateway Interface)는 Django의 비동기 기능을 확장하여 더 복잡하고 비동기적인 작업을 수행할 수 있도록 합니다. Daphne는 이러한 ASGI 스펙을 구현하여, Channels와 함께 또는 독립적으로 비동기 웹 애플리케이션을 운영할 수 있는 서버로 사용됩니다.

📌Daphne의 주요 기능

비동기 처리 : Daphne는 동시에 여러 네트워크 연결을 비동기적으로 처리할 수 있습니다. 이는 채팅 서비스나 실시간 알림과 같은 기능을 구현할 때 중요한 요소입니다.

웹소켓 지원 : Daphne는 HTTP와 웹소켓 프로토콜 모두를 지원합니다. 이를 통해 클라이언트와 서버 간의 지속적이고 양방향 통신이 가능해집니다.

프로토콜 확장성 : ASGI는 쉽게 확장할 수 있는 프로토콜을 지향하며, Daphne는 이를 구현합니다. 따라서 HTTP/2 같은 새로운 인터넷 프로토콜을 지원할 수 있는 여지가 있습니다.

스케일 아웃 : Daphne는 다중 서버 인스턴스로 스케일 아웃이 가능하도록 설계되어 있습니다. 채널 레이어를 통해 메시지를 교환함으로써 여러 Daphne 인스턴스 간에 동기화할 수 있습니다.

인터페이스 서버 : Daphne는 인터페이스 서버로서 웹 애플리케이션(예: Django)과 네트워크 사이에 위치합니다. 클라이언트의 비동기 요청을 받아서 ASGI 프로토콜을 통해 Django 애플리케이션에 전달합니다.

📌Daphne의 작동 방식

Daphne는 클라이언트와 서버 간의 초기 연결을 처리한 후, 각 요청을 ASGI 애플리케이션으로 라우팅합니다. WebSocket 연결의 경우, Daphne는 초기 Handshake를 처리하고 연결을 열린 상태로 유지합니다. 클라이언트와 서버는 이 연결을 통해 메시지를 자유롭게 주고받을 수 있습니다.

📌채팅 애플리케이션에서의 WSGI 대 ASGI

채팅 애플리케이션과 같이 실시간으로 많은 양의 데이터를 교환해야 하는 경우, ASGI가 훨씬 적합합니다. WSGI의 동기적 처리 방식은 요청마다 연결을 열고 닫는 과정이 필요하며, 이는 실시간 통신에 큰 지연을 발생시킬 수 있습니다. 반면에, ASGI는 비동기적이고 지속적인 연결을 통해 데이터를 교환할 수 있으므로, 사용자 간의 상호작용이 끊임없이 발생하는 채팅 애플리케이션에 더욱 적합합니다. ASGI를 사용하면 각 사용자의 연결이 하나의 이벤트 루프 내에서 비동기적으로 유지되므로, 더 많은 사용자를 더 낮은 지연 시간으로 처리할 수 있습니다.

이제 아래의 Django Channels를 사용하여 구축된 웹 애플리케이션의 아키텍처를 보며 구조를 더 심오하게 알아봅시다.

📌Django Channels 아키텍처

📌1. 브라우저

사용자의 웹 브라우저로부터 시작되며, HTTP 또는 WebSocket 연결을 통해 서버와 통신합니다.

📌2. 웹/웹소켓 서버

실제로 웹 브라우저와 인터페이스 서버 사이에서 HTTP 요청과 WebSocket 연결을 처리합니다. Daphne는 Channels에서 자주 사용되는 ASGI 웹서버입니다.

📌3. 인터페이스 서버

ASGI 인터페이스 서버로, Channels에서는 이 서버를 통해 비동기 요청을 관리합니다.

📌4. 채널 레이어

채널 레이어는 비동기 메시지 중개자로 작동하여, 다른 서버 인스턴스에 있는 Consumer 간에 통신을 가능하게 합니다.
주로 Redis와 같은 외부 데이터 스토어를 사용합니다. Redis는 메모리 내 데이터 구조 저장소로서, 채널 레이어의 백엔드로 사용될 때 고성능 통신을 위해 메시지를 저장하고 전달하는 데 매우 적합합니다.

📌5. Worker Processes

비동기 작업을 처리하는 워커 프로세스들입니다. 이러한 프로세스들은 통신을 비동기적으로 처리하고, 필요에 따라 백그라운드 작업을 실행할 수 있습니다.

📌6. Consumer (HTTP/WebSocket)

Consumer는 Django의 View와 유사한 개념으로, HTTP 또는 WebSocket 연결을 처리하는 코드 블럭입니다.
WebSocket Consumer는 웹소켓 연결을 통해 들어오는 메시지를 수신하고, 클라이언트로 메시지를 보내는 역할을 합니다.

📌채널 레이어에 왜 Redis를 사용하나요?

Redis는 고성능의 인-메모리 데이터 저장소로서, 키-값 쌍을 사용해 데이터를 저장합니다. 이러한 특성 때문에 데이터베이스, 캐시 및 메시지 브로커로 널리 사용됩니다. Channels에서는 주로 메시지 브로커로서의 기능을 수행합니다. 이는 채널 레이어에서의 데이터 전달을 위한 메커니즘을 제공하며, 이를 통해 다수의 컨슈머가 서로 통신할 수 있도록 해줍니다.

📌Redis의 구조 및 특징

  1. 인-메모리 저장소: Redis의 데이터는 메모리에 저장되기 때문에 디스크 기반의 데이터베이스보다 빠른 읽기/쓰기가 가능합니다. 이는 실시간 어플리케이션에서 중요한 요소입니다.
  2. 데이터 유형: Redis는 문자열, 해시, 리스트, 셋, 정렬된 셋 등 다양한 데이터 유형을 지원합니다. Channels에서는 주로 리스트 또는 스트림을 사용하여 메시지를 관리합니다.
  3. 발행/구독 모델: Redis는 발행/구독(publish/subscribe) 패턴을 지원하여, 채널을 통해 메시지를 발행하고 구독할 수 있습니다. 이는 여러 컨슈머 간 메시지를 전파하는 데 유용합니다.
  4. 지속성: Redis는 메모리에 저장된 데이터를 디스크에도 저장할 수 있으며, 이를 통해 데이터의 영속성을 제공합니다. 서버가 다시 시작되더라도 메시지가 유실되지 않습니다.
  5. 원자성: Redis 연산은 원자적으로 수행되므로, 여러 연산이 동시에 수행되더라도 데이터의 무결성을 보장합니다.
  6. 확장성: Redis는 데이터를 여러 서버에 분산하여 저장할 수 있는 클러스터 모드를 지원합니다. 이를 통해 수평적으로 확장이 가능하며, 더 많은 클라이언트를 처리할 수 있습니다.

📌Channels에서 Redis 사용 이유

  1. 성능: Redis의 빠른 읽기/쓰기 능력은 실시간 메시지 전달에 있어 지연 시간을 최소화합니다.
  2. 채널 레이어를 통한 커뮤니케이션: 여러 인스턴스에서 동작하는 애플리케이션에서는 컨슈머들이 같은 메시지 채널을 통해 통신할 필요가 있습니다. Redis는 이러한 메시지 전달에 있어서 효과적인 메커니즘을 제공합니다.
  3. 확장성 및 유연성: Redis는 대규모 트래픽을 처리하고 여러 서버 인스턴스에 걸쳐 쉽게 확장할 수 있습니다.
  4. 안정성: Redis는 데이터를 지속적으로 저장할 수 있으며, 클러스터를 통해 높은 가용성을 제공합니다.

이러한 이유로 Django Channels는 Redis를 채널 레이어의 백엔드로서 권장합니다. 그리고 실시간 메시지 전달을 위한 웹소켓 통신에 Redis를 활용함으로써, 안정적이고 빠른 응답성을 가진 채팅 애플리케이션을 구현할 수 있습니다.

📌Django Channels와 Redis를 함께 사용할 때의 일반적인 흐름

  1. 클라이언트가 웹소켓을 통해 서버에 연결을 시도합니다.

  2. 연결이 성립되면, 서버 측 Channels는 해당 클라이언트를 위한 새로운 채널을 생성하고, 이를 특정 그룹에 할당합니다. 그룹은 관련 있는 여러 채널들의 집합으로 볼 수 있습니다. 예를 들어, 같은 채팅방의 참여자들을 같은 그룹에 할당할 수 있습니다.

  3. 클라이언트는 할당된 채널을 통해 메시지를 보내고, 서버는 이 메시지를 받아 처리합니다. 이때, 메시지가 그룹에 속한 모든 채널에게 전달됩니다.

  4. Redis 채널 레이어는 이러한 그룹 내의 메시지 전달을 도와주는 중계자의 역할을 합니다. 클라이언트에서 서버로 메시지가 전달되면, 서버는 해당 메시지를 Redis를 통해 해당 그룹의 모든 채널에게 브로드캐스트합니다.

  5. 그룹에 구독된 모든 클라이언트는 이 메시지를 거의 실시간으로 수신하게 됩니다.

이 과정에서, Redis의 발행/구독 모델이 중요한 역할을 합니다. 서버가 메시지를 Redis에 발행하면, 구독하고 있는 모든 클라이언트의 채널을 통해 메시지가 전달됩니다. 이를 통해 실시간으로 데이터를 교환할 수 있으며, 특히 대규모의 연결과 통신이 필요한 채팅 어플리케이션에서 유용합니다.

이제 개발을 시작하기에 앞서 장고로 개발하기위한 개활환경을 구축하고 필요한 라이브러리들을 정리하겠습니다. IDE는 vscode를 사용하였습니다.

터미널창을 열어 아래의 명령어들을 입력합니다.

  • 가상환경 만들기
python -m venv venv
  • 가상환경
venv\Scripts\activate
  • 장고 설치
pip install django
  • 장고 프로젝트 생성
django-admin startproject config .
  • 장고 앱 생성
django-admin startapp chat

아래는 제가 사용한 주요 라이브러리 입니다

Daphne은 Cahnnel를 설치하면 자동으로 설치되며, Channels에서 지원하는 서버이므로 WebServer가 따로 필요 없습니다.

아래의 라이브러리를 설치합니다

channels==4.0.0
channels-redis==4.0.0
redis==5.0.1
daphne==4.0.0
Django==4.1.12
djangorestframework==3.14.0
django-cors-headers==4.3.0

config폴더 안의 settings.py에 설치한 라이브러리들과 생성한 앱을 등록시켜줍니다.

INSTALLED_APPS = [
    'corsheaders',
    'channels',
    'daphne',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'chat',
    'rest_framework',
]

ASGI_APPLICATION 도 적어줍니다.

ASGI_APPLICATION = 'config.asgi.application'

settings.py에 채널 레이어도 설정해줍니다

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [(os.environ.get('REDIS_HOST', 'localhost'), 6379)],
        },
    },
}

로컬 개발환경에서 redis를 사용하려면 여러방법이 있는데 여기서는 도커를 통해 띄우는 방식을 선택하겠습니다.

로컬에 도커가 설치되어있어야 하고 설치가 되어있는 상태라면 아래의 명령어를 터미널에 입력하면 됩니다.

docker run -d --restart always --name redis7 --publish 6379:6379 redis:7

장고로 채팅서비스를 구현하기위한 settings.py 의 설정을 마쳤습니다.

다음 포스팅에서 본격적인 코드를 작성해보도록 하겠습니다

0개의 댓글