Published on

[Django] 장고에서 웹소켓을 이용한 채팅 구현

Authors
  • avatar
    Name
    Almer Minified
    Twitter

[Django] 장고의 웹소켓과 채팅

장고의 웹소켓 방법

주로 웹소켓을 이용해서 많은 서비스들이 채팅을 구현한다. 장고에서는 주로 Django Channels를 이용해서 이걸 구현하는데 캐시에서 살펴봤듯 도움이 되는 라이브러리라고 이해하면 된다.

웹소켓이란

웹소켓은 클라이언트랑 서버 간 양방향 통신이라는 점에서 HTTP통신과 다르다. HTTP통신은 한번 전송하면 끝이지만 웹소켓은 소켓이 연결된 상태에서는 서로 데이터가 계속 이동하기 떄문에 오버헤드도 적고 속도도 빠르다.

설치방법

먼저 Django Channels를 설치하자.

  pip install channels

그 다음엔 ASGI를 설정하자.

  # settings.py에 작성한다
  ASGI_APPLICATION = 'projectname.routing.application'          

settings.py에 저 부분을 추가해줘야한다. 이렇게 추가했다면 routing.py를 프로젝트에 생성해야 한다. http통신에서의 urls.py라고 생각하면 된다. routing.py는 이렇게 설정하면 된다

  # routing.py를 만들자
  from channels.routing import ProtocolTypeRouter, URLRouter
  from django.urls import path
  from .consumers import MyConsumer

  websocket_urlpatterns = [
      path('ws/your_path/', MyConsumer.as_asgi()),
  ]

  application = ProtocolTypeRouter({
      'websocket': URLRouter(websocket_urlpatterns)
  })

이걸 보면 알수 있는데 Consumer라는 클래스를 만들어서 써야한다. 웹소켓을 연결해주는 역할인데 컨슈머는 이렇게 만들면 된다.

# 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, text_data):
        # 클라이언트로부터 메시지를 받았을 때 실행되는 코드
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        await self.send(text_data=json.dumps({
            'message': message
        }))

이렇게 하면 된다. 코드를 해석해보자. 이 컨슈머는 클라이언트와 웹소켓으로 연결될 때 connect함수가 실행된다. 그 다음에 disconnect함수는 웹소켓이 종료됐을 때 실행된다. 이제 연결 중간에 receive 함수가 작동하는데 클라이언트와 message를 주고 받는다. message는 통신간의 데이터라고 생각하면 된다. 여기서 이제 비동기 처리같은걸 해주면 매끄럽게 진행되게 된다. 위에서 빼먹었지만 클라이언트에 역으로 메시지를 보낼 수도 있다.

  async def send_message(self, message):
  await self.send(text_data=json.dumps({
      'message': message
  }))      

이렇게하면 클라이언트에 서버에서 직접 메시지를 보낼 수 있다. http에선 잘 안될 일이다. 여러가지 기법을 써야하는데 이렇게 웹소켓을 쓰면 편리하게 해결할 수 있다.

컨슈머 활용하기

Group기능

그룹기능을 이용해 소켓들을 한번에 모아서 처리할 수 있다. 단체 채팅방을 만들 수 있다는 뜻이다.

from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

class ChatConsumer(AsyncWebsocketConsumer):
    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()

    async def disconnect(self, close_code):
        # 채팅방 그룹에서 제거
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # 그룹에 메시지 보내기
    async def chat_message(self, event):
        message = event['message']

        # 웹소켓을 통해 메시지 보내기
        await self.send(text_data=json.dumps({
            'message': message
        }))

이렇게하면 여러명이 한 채팅방에 들어왔을때 그 채팅방에 어떤 클라이언트에서 보낸 메시지가 그 방에 있는 다른 클라이언트들에게 다 전달된다. 이걸 HTTP통신으로 구현하려면 정말 복잡한 코드가필요하지만 여기서는

  # 그룹에 메시지 보내기
  async def chat_message(self, event):
      message = event['message']

      # 웹소켓을 통해 메시지 보내기
      await self.send(text_data=json.dumps({
          'message': message
      }))

이 부분으로 끝난다. 이게 웹소켓의 정말 편리한 점이다. 이렇게하면 여러가지 코드 없이 단순하게 아까 그 send 함수를 이용해 처리할 수 있게 된다.

HTTP통신에 비한 단점

동시에 여러 연결이 많아지면 서버에 과한 부하를 줄 수 있다. 기본적으론 CORS를 적용할 수 없으므로 보안에서 조금 밀릴 수 있다.