"장고 실시간 채팅(Channels와 redis server 이용)"의 두 판 사이의 차이

Pywiki
둘러보기로 가기 검색하러 가기
410번째 줄: 410번째 줄:
 
=== routing.py ===
 
=== routing.py ===
 
들어온 경로와 컨슈머를 연결해준다. urls.py와 view.py의 관계.
 
들어온 경로와 컨슈머를 연결해준다. urls.py와 view.py의 관계.
 +
 +
다음과 같은 형태로 컨슈머를 연결한다.<syntaxhighlight lang="python">
 +
websocket_urlpatterns = [
 +
    re_path(r'ws/notification/(?P<center_name>\w+)/$', consumers.NotificationConsumer.as_asgi()),
 +
]
 +
</syntaxhighlight>
  
 
=== Consumer.py ===
 
=== Consumer.py ===
 +
컨슈머는 클래스로 구현되어 connect, disconnect 등으로 구성된다.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
| 컨슈머 정의
 +
|클래스로 구현한다.
 +
|<syntaxhighlight lang="python">
 +
from channels.generic.websocket import WebsocketConsumer
 +
 +
class 컨슈머이름(WebsocketConsumer):
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
from channels.generic.websocket import AsyncWebsocketConsumer
 +
 +
class 컨슈머이름(AsyncWebsocketConsumer):
 +
</syntaxhighlight>
 +
|}
 +
 +
==== connect ====
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
| 사전 설정
 +
|각종 변수들의 조작을 먼저 한다.
 +
스코프로 받아서 변수들을 조작하는데, 그 구조는 다음과 같다.
 
{| class="wikitable"
 
{| class="wikitable"
 
!항목
 
!항목
424번째 줄: 458번째 줄:
 
|self.scope["url_route"]["kargs"]["변수키"]
 
|self.scope["url_route"]["kargs"]["변수키"]
 
|변수의 값을 얻어온다.
 
|변수의 값을 얻어온다.
 +
|}
 +
|self.group_name = self.scope["url_route"]["kwargs"]["room_name"]
 +
|-
 +
|그룹 참여
 +
|그룹을 지정하여 참여시킨다.
 +
|<syntaxhighlight lang="python">
 +
async_to_sync(self.channel_layer.group_add)(
 +
            self.group_name,  # 위에서 설정하여 넣어준다.
 +
            self.channel_name  # 자동으로 고유하게 지정된다.
 +
        )
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
await self.channel_layer.group_add(self.group_name, self.channel_name)
 +
</syntaxhighlight>
 +
|-
 +
|연결
 +
|위 설정을 토대로 연결한다.
 +
연결 이후 필요한 명령을 넣기도 한다.
 +
 +
 +
연결과 동시에 다음과 같이 신호를 보내기도 한다.<syntaxhighlight lang="python">
 +
notifications = get_notification()
 +
        if notifications:
 +
            async_to_sync(self.channel_layer.group_send)(
 +
                "center_name", {
 +
                    "type": "notify",
 +
                    "data": notifications
 +
                }
 +
            )
 +
</syntaxhighlight>
 +
|<syntaxhighlight lang="python">
 +
self.accept()
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
await self.accept()
 +
</syntaxhighlight>
 +
|}
 +
 +
==== disconnect ====
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|연결을 끊기만 할 테니, 기본 설정은 간단하다.
 +
적당히 끊어질 때의 명령을 넣기도 한다.
 +
|<syntaxhighlight lang="python">
 +
def disconnect(self, close_code):
 +
    async_to_sync(self.channel_layer.group_discard)(
 +
        self.group_name,
 +
        self.channel_name
 +
    )
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
async def disconnect(self, close_code):
 +
    await self.channel_layer.group_discard(self.group_name, self.channel_name)
 +
</syntaxhighlight>
 +
|}
 +
 +
==== receive ====
 +
타인으로부터 메시지를 받는 게 아니라, 자바스크립트로부터 받는 메시지와 관련된 함수이다.(웹소켓에서 메시지를 받을 때.)
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|본인의 자바스크립트에서 웹소켓으로 메시지를 보낸 경우.
 +
자바스크립트에서 데이터가 넘어올 땐 json으로 변환되어 오기 때문에 우측과 같이 구성해야 한다.
 +
 +
 +
 +
메시지를 보낼 때 type는 받는 함수를 지정할 때 쓰인다.(없어도 되긴 함.)
 +
 +
type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.
 +
|<syntaxhighlight lang="python">
 +
def receive(self, text_data):
 +
        text_data_json = json.loads(text_data)
 +
        message = text_data_json['message']
 +
       
 +
        # 나 뿐 아니라 다른 사람들도 보게끔.
 +
        self.send(text_data=json.dumps({
 +
            'message': message
 +
        }))
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
    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}
 +
        )
 +
</syntaxhighlight>
 +
|}
 +
 +
==== 이벤트 ====
 +
다른 사용자들로부터 메시지를 받는 등의 이벤트가 나타났을 때.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|함수의 이름은 메시지를 보낼 때의 type와 동일하게 짓는다.
 +
type에 따라 다른 함수가 실행된다.
 +
 +
(event가 들어오면 자동으로 실행되는 듯하다.)
 +
 +
 +
웹소켓(자바스크립트)으로 보낼 땐 json으로 변환하여 보내주어야 한다.
 +
|<syntaxhighlight lang="python">
 +
def chat_message(self, event):
 +
        message = event['message']
 +
 +
        # Send message to WebSocket
 +
        self.send(text_data=json.dumps({
 +
            'message': message
 +
        }))
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
async def chat_message(self, event):
 +
    message = event["message"]
 +
 +
    # Send message to WebSocket
 +
    await self.send(text_data=json.dumps({"message": message}))
 +
</syntaxhighlight>
 +
|-
 +
|
 +
|보내는 함수의 차이.
 +
|
 +
|}
 +
 +
==== 자바스크립트에서 ====
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|웹소켓 생성
 +
|스크립트의 초반에 우측과 같이 주소를 지정하여 소켓을 생성한다.
 +
roomname과 같은 변수는 위에서 받아 넘겨주어야 한다.
 +
 +
 +
웹소켓이 만들어지면 아래 4개의 이벤트 사용이 가능해진다.
 +
 +
* open – 커넥션이 제대로 만들어졌을 때 발생함
 +
* message – 데이터를 수신하였을 때 발생함
 +
* error – 에러가 생겼을 때 발생함
 +
* close – 커넥션이 종료되었을 때 발생
 +
 +
이벤트의 활용은 아래 예시를 참고하자.
 +
|<syntaxhighlight lang="python">
 +
const chatSocket = new WebSocket(
 +
    'ws://' + window.location.host
 +
    + '/ws/chat/'
 +
    + roomName
 +
    + '/'
 +
);
 +
</syntaxhighlight>
 +
|-
 +
|message
 +
|웹소켓이 데이터를 수신할 때 발생
 +
|<syntaxhighlight lang="python">
 +
chatSocket.onmessage = function(e) {
 +
            const data = JSON.parse(e.data);
 +
            document.querySelector('#chat-log').value += (data.message + '\n');
 +
        };
 +
</syntaxhighlight>
 +
|-
 +
|close
 +
|
 +
|<syntaxhighlight lang="python">
 +
chatSocket.onclose = function(e) {
 +
            console.error('Chat socket closed unexpectedly');
 +
        };
 +
</syntaxhighlight>
 +
|-
 +
|메시지 보내기
 +
|만든 소켓으로 메시지 보낼 때.
 +
JavaScript 값이나 객체를 JSON 문자열로 변환해야만 한다.
 +
|<syntaxhighlight lang="python">
 +
chatSocket.send(JSON.stringify({
 +
                'message': message
 +
            }));
 +
</syntaxhighlight>
 
|}
 
|}
 
[[분류:장고 웹소켓]]
 
[[분류:장고 웹소켓]]

2022년 11월 5일 (토) 07:55 판

1 개요

장고 채널 기능의 활용을 위해 간단한 채팅 앱 만들어보기.

1.1 사전 준비

과정 설명 방법
장고 장고가 설치되어 있다고 가정한다. 1. 장고 개요
채널 설치 채널이 설치되어 있다고 가정한다. 장고 channels

2 기초준비

2.1 채팅어플 만들기

패키지설치부터 진행하기엔 중간점검이 어려워 틀 만들기를 먼저 수행한다.

과정 설명 방법
어플리케이션생성 채팅을 위한 앱을 생성한다. django-admin startapp chat
앱 등록 settings.py에 추가.
INSTALLED_APPS = [
    ...
    'chat',
폴더 정리 - 앱을 생성하고 생성된 __init__.py와 views.py를 제외한 모든 것들을 지운다.

(채팅기능만을 위해선 나머지는 필요 없어, 지워도 된다. 근데 그냥 두자.)

- 탬플릿을 담기 위해 templates/chat 디렉토리를 생성해준다.

urls.py 생성 및 매핑 앱 내에서 사용되는 url을 다루기 위해 urls.py를 생성한다. chat/urls.py의 내용.
from django.urls import path
from . import views
app_name = 'chat'  # 보통 앱이름을 써서 url을 구분하지만, 

urlpatterns = [
    path('', views.index, name='index'),
]
기초 urls.py 수정
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('chat/', include('chat.urls')),
    path('admin/', admin.site.urls),
]

여기까지 하고 runserver 후 제대로 작동하는지 확인하자.

2.2 채팅룸 구현

2.2.1 채팅 인덱스 구현

들어갈 채팅룸을 입력하는 공간.

과정 설명 방법
채팅 인덱스 탬플릿 작성 채팅룸을 입력하기 위한 인덱스 탬플릿.

templates/chat/index.html로 작성하자.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br>
    <input id="room-name-input" type="text" size="100"><br>
    <input id="room-name-submit" type="button" value="Enter">

    <script>
        document.querySelector('#room-name-input').focus();
        document.querySelector('#room-name-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#room-name-submit').click();
            }
        };

        document.querySelector('#room-name-submit').onclick = function(e) {
            var roomName = document.querySelector('#room-name-input').value;
            window.location.pathname = '/chat/' + roomName + '/';
        };
    </script>
</body>
</html>
뷰 작성
from django.shortcuts import render

def index(request):
    return render(request, 'chat/index.html')

2.2.2 채팅룸 구현

과정 설명 방법
탬플릿 작성 templates/chat/room.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br>
    <input id="chat-message-input" type="text" size="100"><br>
    <input id="chat-message-submit" type="button" value="Send">
    {{ room_name|json_script:"room-name" }}
    <script>
        const roomName = JSON.parse(document.getElementById('room-name').textContent);

        const chatSocket = new WebSocket(
            'ws://'
            + window.location.host
            + '/ws/chat/'
            + roomName
            + '/'
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (data.message + '\n');
        };

        chatSocket.onclose = function(e) {
            console.error('Chat socket closed unexpectedly');
        };

        document.querySelector('#chat-message-input').focus();
        document.querySelector('#chat-message-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#chat-message-submit').click();
            }
        };

        document.querySelector('#chat-message-submit').onclick = function(e) {
            const messageInputDom = document.querySelector('#chat-message-input');
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                'message': message
            }));
            messageInputDom.value = '';
        };
    </script>
</body>
</html>
뷰 작성 뷰에 다음 함수를 추가한다.
def room(request, room_name):
    return render(request, 'chat/room.html', {
        'room_name': room_name
    })
URL 다음의 내용을 urls.py에 추가한다.
path('<str:room_name>/', views.room, name='room'),

2.2.3 참가자(컨슈머) 구현

과정 설명 방법
참가자 파일 작성 /chat/consumers.py 작성.

모든 요청을 받아들이는 비동기적인 웹소켓.

클라이언트로부터 메시지를 받아서 그대로 전달.

import json
from channels.generic.websocket import WebsocketConsumer

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

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        self.send(text_data=json.dumps({
            'message': message
        }))
라우팅 작성 /chat/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()),
]
라우팅 등록 channels를 설치하며 작성한 agsi.py 안에 내용을 채워준다.

chat 안의 라우팅을 등록해준다.

그리고 이를 사용하기 위한 라이브러리를 불러온다.

......

from channels.routing import URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from channels.auth import AuthMiddlewareStack
import chat.routing

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns))
        ),
    }
)

여기까지 하고 점검해보자. /chat/lobby/ 에 접속한 후 채팅을 치면 떠야 한다.

같은 브라우저를 띄워두고 한쪽에서 채팅을 치면 다른 쪽에서 안뜨는데, 다른 브라우저에선 안뜬다. 접속한 모두에게 반영될 수 있도록 채널레이어를 구현한다.

3 채널 레이어 구현

consumer 인스턴스는 자동으로 유일한 channel name을 구성하기 때문에 서로 소통하려면 layer가 필요하다.

3.1 레디스

레디스 서버를 이용한 실시간 채팅 구현.

꽤 오래된 버전인 5를 사용하는데, 아마도 이는.. 5에서 처음으로 consumer groups이 구현된 버전인데, 이걸로 구현했기 때문이 아닐까 싶다.(최신버전에서도 될진.. 검증이 필요하다.)

3.1.1 사전설정

과정 설명 방법
서버 설치 설치 후 딱히 옵션을 변경할 내용은 없다.

어차피 로컬에서 모든 작동이 수행되기 때문에.

네이티브로 설치하는 게 아니라 도커를 사용할 거라면 아래로 넘어가자.

단순 apt-get install redis로 설치해 5 이상의 버전이 되어도 작동한다.

Redis 문서 참조.
도커 도커가 설치되어 있다면 아래 명령으로 바로 설치, 서비스가 시작된다.

docker run -p 6379:6379 -d redis:5

아래 문서를 통해 도커 설치를 참고하자.

http://id8436.iptime.org:8080/mediawiki/index.php/Docker

연동 라이브러리 설치 서버와 채널을 연동할 라이브러리를 설치한다. pip install channels_redis
세팅 settings.py 안에 추가.
ASGI_APPLICATION = "mysite.asgi.application"  # 이부분은 채널을 설치할 때 작성한 부분.
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}
을 추가해준다.

3.2 기존 컨슈머 변형

위에서 작성한 컨슈머는 중간 확인을 위한 동기식 함수인데, 비동기식으로 다시 쓰이면 성능이 좋아진다. 아래와 같이 변형하자.

import json

from channels.generic.websocket import AsyncWebsocketConsumer


class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = "chat_%s" % self.room_name

        # Join room group
        await self.channel_layer.group_add(self.room_group_name, self.channel_name)

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name, {"type": "chat_message", "message": message}
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event["message"]

        # Send message to WebSocket
        await self.send(text_data=json.dumps({"message": message}))

4 서버에 반영

튜토리얼엔 나오지 않지만(공식문서 한참 뒤에 나온다..), 서버에 반영하게 되면 ws/ 경로에 대하여 새로운 설정을 해주어야 한다.(필자는 이걸 몰라서 이것저것 건드리다가 sql 날려먹었다.. 젠장)

일반적으로 WSGI 통신을 gunicorn으로 처리한다면, ASGI통신은 daphne으로 처리한다.(daphne에서도 wsgi통신이 가능하지만, 보안상 권장되진 않는다.)

과정 설명 방법
설치 daphne은 channels를 설치하면 자동으로 설치가 된다.
확인 실행 명령은 우측과 같다.(asgi의 경로를 지정해주면 된다.)

포트번호는 필요에 따라 지정하면 된다.

해당 포트로 접속해보면 사이트와 채팅이 잘 작동한다.

daphne -b 0.0.0.0 -p 8001 config.asgi:application
서버에서 실행 서비스를 위해선 백그라운드로 진행해주어야 한다. nohup daphne -b 0.0.0.0 -p 8001 config.asgi:application &
웹서버에 반영 /static을 반영하듯, /ws로 시작되는 주소는 다핀으로 넘겨준다.

이후 관련 서비스들을 재시작하면 설정들이 반영된다.

채팅도 물론 성공적...!

location /ws {
                proxy_pass http://localhost:8001;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }

,

웹소켓에서도 https 인증을 적용할 수 있다.(추후에 반영해보자.)

5 마무리. 각 요소들이 어떤 역할을 하는지.

순서대로 어떤 경로를 거쳐 작동하는지 살펴보자.

5.1 asgi.py

항목 설명
ProtocolTypeRouter 연결의 종류를 파악하여 처리. http로 넘길지, 웹소켓 처리로 넘길지.
  • http 연결일 경우 보통 django_asgi_app(get_asgi_application()) 가 실행된다.
  • websocket 연결(ws:// 혹은 wss://)일 경우 AuthMiddlewareStack으로 앱별로 routing.py에 적어놓은 코드로 라우팅 한다.

5.2 routing.py

들어온 경로와 컨슈머를 연결해준다. urls.py와 view.py의 관계.

다음과 같은 형태로 컨슈머를 연결한다.

websocket_urlpatterns = [
    re_path(r'ws/notification/(?P<center_name>\w+)/$', consumers.NotificationConsumer.as_asgi()),
]

5.3 Consumer.py

컨슈머는 클래스로 구현되어 connect, disconnect 등으로 구성된다.

항목 설명 예시
컨슈머 정의 클래스로 구현한다.
from channels.generic.websocket import WebsocketConsumer

class 컨슈머이름(WebsocketConsumer):
비동기식으로 구성한 경우
from channels.generic.websocket import AsyncWebsocketConsumer

class 컨슈머이름(AsyncWebsocketConsumer):

5.3.1 connect

항목 설명 예시
사전 설정 각종 변수들의 조작을 먼저 한다.

스코프로 받아서 변수들을 조작하는데, 그 구조는 다음과 같다.

항목 설명
self.scope["url_route"] args와 kargs를 키로 갖는 사전.
self.scope["url_route"]["kargs"] routing에서 정규표현식으로 사용한 변수.
self.scope["url_route"]["kargs"]["변수키"] 변수의 값을 얻어온다.
self.group_name = self.scope["url_route"]["kwargs"]["room_name"]
그룹 참여 그룹을 지정하여 참여시킨다.
async_to_sync(self.channel_layer.group_add)(
            self.group_name,  # 위에서 설정하여 넣어준다.
            self.channel_name  # 자동으로 고유하게 지정된다.
        )
비동기식으로 구성한 경우
await self.channel_layer.group_add(self.group_name, self.channel_name)
연결 위 설정을 토대로 연결한다.

연결 이후 필요한 명령을 넣기도 한다.


연결과 동시에 다음과 같이 신호를 보내기도 한다.
notifications = get_notification()
        if notifications:
            async_to_sync(self.channel_layer.group_send)(
                "center_name", {
                    "type": "notify",
                    "data": notifications
                }
            )
self.accept()
비동기식으로 구성한 경우
await self.accept()

5.3.2 disconnect

항목 설명 예시
정의 연결을 끊기만 할 테니, 기본 설정은 간단하다.

적당히 끊어질 때의 명령을 넣기도 한다.

def disconnect(self, close_code):
    async_to_sync(self.channel_layer.group_discard)(
        self.group_name,
        self.channel_name
    )
비동기식으로 구성한 경우
async def disconnect(self, close_code):
    await self.channel_layer.group_discard(self.group_name, self.channel_name)

5.3.3 receive

타인으로부터 메시지를 받는 게 아니라, 자바스크립트로부터 받는 메시지와 관련된 함수이다.(웹소켓에서 메시지를 받을 때.)

항목 설명 예시
정의 본인의 자바스크립트에서 웹소켓으로 메시지를 보낸 경우.

자바스크립트에서 데이터가 넘어올 땐 json으로 변환되어 오기 때문에 우측과 같이 구성해야 한다.


메시지를 보낼 때 type는 받는 함수를 지정할 때 쓰인다.(없어도 되긴 함.)

type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.

def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        
        # 나 뿐 아니라 다른 사람들도 보게끔.
        self.send(text_data=json.dumps({
            'message': message
        }))
비동기식으로 구성한 경우
    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}
        )

5.3.4 이벤트

다른 사용자들로부터 메시지를 받는 등의 이벤트가 나타났을 때.

항목 설명 예시
정의 함수의 이름은 메시지를 보낼 때의 type와 동일하게 짓는다.

type에 따라 다른 함수가 실행된다.

(event가 들어오면 자동으로 실행되는 듯하다.)


웹소켓(자바스크립트)으로 보낼 땐 json으로 변환하여 보내주어야 한다.

def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))
비동기식으로 구성한 경우
async def chat_message(self, event):
    message = event["message"]

    # Send message to WebSocket
    await self.send(text_data=json.dumps({"message": message}))
보내는 함수의 차이.

5.3.5 자바스크립트에서

항목 설명 예시
웹소켓 생성 스크립트의 초반에 우측과 같이 주소를 지정하여 소켓을 생성한다.

roomname과 같은 변수는 위에서 받아 넘겨주어야 한다.


웹소켓이 만들어지면 아래 4개의 이벤트 사용이 가능해진다.

  • open – 커넥션이 제대로 만들어졌을 때 발생함
  • message – 데이터를 수신하였을 때 발생함
  • error – 에러가 생겼을 때 발생함
  • close – 커넥션이 종료되었을 때 발생

이벤트의 활용은 아래 예시를 참고하자.

const chatSocket = new WebSocket(
    'ws://' + window.location.host
    + '/ws/chat/'
    + roomName
    + '/'
);
message 웹소켓이 데이터를 수신할 때 발생
chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (data.message + '\n');
        };
close
chatSocket.onclose = function(e) {
            console.error('Chat socket closed unexpectedly');
        };
메시지 보내기 만든 소켓으로 메시지 보낼 때.

JavaScript 값이나 객체를 JSON 문자열로 변환해야만 한다.

chatSocket.send(JSON.stringify({
                'message': message
            }));