<Django>[instamg] Direct Message

jm_yoon·2021년 4월 19일
1

기업협업

목록 보기
6/6

기업협업에서 진행한 프로젝트 instamg 기능 중 DM기능 정리가 필요한 것 같아 포스팅하였다.

direct message

웹소켓, django channels

실시간 채팅을 구현하기 위해서 웹소켓을 사용해야했고 django에서 channels라는 라이브러리를 제공해준다.

websoket이란?
웹소켓은 프로토콜로서, 실시간으로 데이터를 양방향 통신을 할 수 있게 해주는 기술이다.
소켓이란 쉽게 생각해서 통신을 위한 통로라고 생각하면 된다. web환경은 필요한 정보를 http 기반으로 request를 보내면 response을 보내주는 단순한 프로토콜이다. http 프로토콜은 매 요청과 응답마자 연결을 수립하고 끊는 과정을 반복해야 했기 때문에 유사한 통신을 계속 주고받는 실시간 채팅 같은 기능을 하는데 있어 비효율적이다.
http를 이용하여 실시간 통신의 문제를 해결하기 위해 html5부터 웹소켓이 등장했다.

웹소켓은 실시간 양방향 통신을 지원하며 한번 연결이 수립되면 클라이언트와 서버 모두 자유롭게 데이터를 보낼 수 있다. 채팅과 같은 연속적인 통신에 대해 계속 유사한 통신을 반복하지 않게 해주어 통신의 효율성도 개선하였다.

웹소켓 핸드쉐이크

핸드쉐이크는 한번의 http요청과 http요청으로 이루어져 있다.
핸드쉐이크가 끝나면 http 프로토콜을 웹소켓 프로토콜로 변환하여 통신을 하는 구조이다.

channels

channels는 django에서 HTTP프로토콜이 아닌 다른 프로토콜을 사용할 수 있게 해준다.
웹소켓은 프로토콜의 일종으로서 이를 사용하기 위해서 channels을 이용하게 된다.
channels layer가 생겨 일반적인 HTTP메시지 외에도 웹소켓메시지도 처리할 수 있게 된다.

ASGI

ASGI(Asynchronous Server Gateway Interface)는WSGI(Web Server Gateway Interface)를 계승한 것으로 비동기 방식으로 실행된다.
웹 서버와 python 응용프로그램 간의 표준 인터페이스를 제공하기 위해 Django Channels와 배포에 사용되는 Daphne 서버에서 정의한 사양으로서 HTTP, HTTP/2 및 WebSocket와 같은 프로토콜을 지원한다.
ASGI는 비동기 요청인 웹 소켓을 처리하는 이벤트로서 connect, send, receive, disconnect가 있다.

전체적인 flow
장고 프로젝트 생성 -> 앱 생성(direct_message) -> channels 라이브러리 설치 -> settings.py의 installes_apps 맨 위에 추가하기 -> consumers.py 작성

import json

from channels.generic.websocket import AsyncWebsocketConsumer

from direct_messages.models     import DirectMessage, DirectMessageAttachFiles, Room
from users.models               import User
from decorators                 import login_check


class ChatConsumer(WebsocketConsumer):
    def connect(self):
    	self.accept()

    def disconnect(self, close_code):
    	pass
        

    # Receive message from WebSocket
    def receive(self, text_data):
        data         = json.loads(text_data)
        message      = data['message']
        user_account = data['user_account']
        room_name    = data['room_name']
        rooms        = Room.objects.filter(name__contains=user_account)
        
        self.send(data=json.dumps({'message':message}))

-> routing.py 추가 -> channel layer 구현하기

channel layer란?

의사소통 시스템이다. 즉 많은 소비자들 더 나아가서 django의 다른 부분과 의사소통을 할 수 있게 해준다. channel layer에는 두 가지 개념이 존재한다.

channel
channel은 메시지를 보낼 수 있는 우편함이라고 생각하면 된다. 각 채널은 고유한 이름이 있으며 누구든지 채널에 메시지를 보낼 수 있다.

group
group은 채널과 관련된 그룹이다. 그룹도 마찬가지로 이름을 가지며 그룹 이름을 가진 사용자는 누구나 그룹에 채널을 추가, 삭제 가능하고 그룹의 모든 채널에게 메세지를 보낼 수 있다. 그러나 그룹에 속한 채널을 나열 할 수는 없다.

소비자들은 채널 이름을 하나씩 가지고 있으며 channel layer를 통해 메시지를 주고 받을 수 있다.

channel layer 구현하기
$ brew install redis redis 설치하기
$ redis-server redis-server 돌려주기
$ pip install channels_redis channels 가 redis 인터페이스를 얻을 수 있도록 channels_redis 패키지를 설치

-> settings.py에서 channel layer 설정하기


# Channels
ASGI_APPLICATION = 'instamg.asgi.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },

-> consumers.py 추가하기 -> consumers.py 비동기식으로 작성하기

consumer를 비동기식으로 작성하게 되면 요청을 처리할 때, 추가적인 스레드를 생성하지 않는다. 즉 성능개선을 불러올 수 있다. 실시간 채팅같은 경우 성능이 굉장히 중요하기 때문에 이 과정이 필요하다.
sync_to_async를 통해서 django 코드를 비동기적으로 바꿀 수 있지만 async-native라이브러리보다 성능이 떨어진다.

ChatConsumer가 상속받는 것이 WebsocketConsumer 에서 AsyncWebsocketConsumer 으로 바뀌었다. 또한 메소드 선언 시, async 가 붙고, 메소드를 호출 하는 경우에도 await 를 붙여서 비동기 처리를 했다.

Database Access
Django ORM은 동기식 코드이기 때문에 위와 같이 consumer를 비동기식으로 작성한 상태에서 접근하기 위해서는 해줘야할 작업이 있다.

# database_sync_to_async import 해주기
from channels.db  import database_sync_to_async

    @database_sync_to_async
    def create_message(self, user_account, message, rooms, room_name):
        if len(rooms) > 0:
            talked_user = sorted(room_name.split(user_account))[-1]
            for room in rooms:
                if talked_user in room.name and user_account in room.name:
                    room_name = room.name

        if not Room.objects.filter(name=room_name).exists():
            Room.objects.create(
                name = room_name
            )

        return DirectMessage.objects.create(
            room_id = Room.objects.get(name=room_name),
            user_id = User.objects.get(account=user_account),
            message = message
            )

    @database_sync_to_async
    def get_created_at(self):
        return DirectMessage.objects.last().created_at
        
 # 데코레이터로 사용하기 @database_sync_to_async
profile
Hello!

0개의 댓글