๋ฐฐ๊ฒฝ
์ค์๊ฐ ์๋ํ๋ ์๋ฆผ ์น ์๋น์ค์์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ธ ์ค์๊ฐ ์๋ฆผ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผํฉ๋๋ค
์ฐ์ ์์ ์๋๋ฆฌ์ค๋ฅผ ๋ณด๋ฉด
- ํญ๋ ฅํ์ ๋ฐ์ -> DB์ ํด๋น ๋ด์ฉ ์ ์ฅ -> ํด๋น DB ๋ด์ฉ์ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก
DB์ ๋ด์ฉ์ด ์ ์ฅ๋๋ฉด ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ํ๋ก ํธ๋ก ๋ ๋ ค์ค์ผํ๋ ์ค์๊ฐ ๊ธฐ๋ฅ์ด ํ์ํ์ต๋๋ค
์ด๋ฅผ ์ํด Websocket์ ์ฌ์ฉํ๊ธฐ๋ก ํ์ต๋๋ค!
๐คท ์ Websocket?
ํด๋ผ์ด์ธํธ๊ฐ DB์ ๋ด์ฉ์ด ์ ์ฅ๋์๋ค๋ ๊ฒ์ ์๋ ค๋ฉด ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ฐฉํฅ ํต์ ์ ํด์ผํ๋๊ฑด ์๊ฒ ๋๋ฐ ์ ํํ ์น์์ผ์ ์ฌ์ฉํ์๊น์?
์๋ฐฉํฅ ํต์ ์๋ ์ด 2๊ฐ์ง๊ฐ ์์ต๋๋ค.
1. polling
- ์๋ฐฉํฅ ํต์ ์ค ํ๋๋ก n์ด๋ง๋ค request๋ฅผ ๋ ๋ ค์ response๋ฅผ ์ ๋ฌ ๋ฐ๋ ๋ฐฉ์์ ๋๋ค.
- ์ด ๋ฐฉ์์ n์ด ์์์ง ์๋ก ์๋ฒ์ ๋ถ๋ด์ด ์ฆ๊ฐํ๊ฒ ๋ฉ๋๋ค.
- ์๋ฒ์ ๋ถ๋ด์ ์ค์ด๊ธฐ ์ํด n์ ๋๋ฆฌ๋ฉด ์ค์๊ฐ์ฑ์ด ๋จ์ด์ง๊ฒ ๋ฉ๋๋ค.
์ฌ์ค์ polling์ โ๏ธ์ค์๊ฐโ๏ธ ์ ์๋๋ผ๋ ๋ป์ ๋๋ค
์์ฒญ/์๋ต์ ๋น ๋ฅด๊ฒํด์ ์ค์๊ฐ์ฒ๋ผ ๋ณด์ด๋ ๋ฐฉ๋ฒ์ ๋๋ค
2. websocket
polling๋ณด๋ค ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ฐพ๊ฒ ๋์๊ณ ์ค์๊ฐ ํต์ ์ด ๊ฐ๋ฅํ ์น์์ผ ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ๋ก ํ์ต๋๋ค!
์น์์ผ์ ํธ๋์์ดํฌ ๋ฐฉ์์ผ๋ก ์ฒ์ ์ฐ๊ฒฐ์ ํ ๋ค ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค
Django์์๋ Websocket์ ์ฌ์ฉํ๊ธฐ ์ํ Channels ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ต๋๋ค.
https://channels.readthedocs.io/en/stable/
๐ ์ด์ ๊ตฌํํด๋ณด์
๊ฐ๋ฐํ๊ฒฝ ๊ตฌ์ฑ
- ubuntu
- docker
- redis
- channels
- channels-redis
์ด ํ๋ก์ ํธ๋ Djago์ Vue.js๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
channels ์ค์น
pip install channels
pip install channels-redis
docker image๋ก redis ์๋ฒ ๋์ฐ๊ธฐ
channel layer์ backing store๋ก redis๊ฐ ํ์ํฉ๋๋ค.
๋์ปค ์ด๋ฏธ์ง๋ฅผ ์ด์ฉํด์ redis ์๋ฒ๋ฅผ ๋์ฐ๊ณ django์์ ์ฐ๊ฒฐํฉ๋๋ค.
docker pull redis:alpine
docker network create redis-net
docker run --name djangoredisserver -p 6379:6379 --network redis-net -d redis:alpine redis-server --appendonly yes
docker run -it --network redis-net --rm redis:alpine redis-cli -h djangoredisserver
redis ๋์ปค ์ด๋ฏธ์ง๋ฅผ pull ๋ฐ์์ค๋๋ค.
ํฌํธ๋ฒํธ 6379๋ฒ์ redis ๋คํธ์ํฌ๋ฅผ ์ฐ๊ฒฐํด์ค๋๋ค.
settings.py ์ค์
# settings.py
...
ASGI_APPLICATION = 'backend.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('capstone_redis', 6379)]
}
}
}
...
python manage.py shell ๋ก shell์ ์คํ ์ํจ ๋ค ์๋ ๋ช ๋ น์ด๋ฅผ ์์ฑํ๋ฉด channels์ redis๊ฐ ์ ๋๋ก ์ฐ๊ฒฐ๋์๋์ง ํ์ธํ ์ ์๋ค
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
ํ๋ก์ ํธ ํด๋ ๊ตฌ์กฐ๋ ์ด๋ ์ต๋๋ค
backend
...
โโโ backend # ํ๊ฒฝ๋ณ์ ๋ฐ ์ค์
โโโ asgi.py
โโโ routing.py
โโโ settings.py
โโโ urlspy
โโโ wsgi.py
..
โโโ notification # ์๋ฆผ ๊ด๋ จ app
โโโ consumers.py
โโโ routing.py
โโโ models.py
โโโ serializers.py
โโโ urls.py
โโโ views.py
...
โโโ Dockerfile(FILE) # ๋์ปค ํ์ผ
โโโ manage.py(FILE) # command line ์ ํธ๋ฆฌํฐ
โโโ requriements.txt(FILE)# ์ค์น ๋ชฉ๋ก
asgi.py
"""
ASGI config for backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
import django
from .wsgi import *
from channels.auth import AuthMiddlewareStack
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter, URLRouter
import notification.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
django.setup()
application = ProtocolTypeRouter({
"http": AsgiHandler(),
"websocket": AuthMiddlewareStack(
URLRouter(
notification.routing.websocket_urlpatterns
)
),
})
- ์๋ฒ๋ก ์ฐ๊ฒฐ์ด ์์ฑ๋๋ฉด ๋จผ์
ProtocolTypeRouter
๊ฐ ์ฐ๊ฒฐ์ ์ข ๋ฅ๋ฅผ ํ์ํฉ๋๋ค - http ์ฐ๊ฒฐ์ผ ๊ฒฝ์ฐ
AsginHandler()
๊ฐ ์คํ๋๊ณ - websocket ์ฐ๊ฒฐ(ws:// ํน์ wss://)์ผ ๊ฒฝ์ฐ
AuthMiddlewareStack
์ผ๋กrouting.py
์ ์ ์ด๋์ ์ฝ๋๋ก ๋ผ์ฐํ ํฉ๋๋ค
routing.py
settings.py์ ๊ฐ์ ํด๋ ๋ด์ ์๋ routing.py ์ ๋๋ค
from channels.routing import ProtocolTypeRouter, URLRouter
import notification.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': URLRouter(
notification.routing.websocket_urlpatterns
)
})
์น์์ผ์ด ์ฐ๊ฒฐ๋๋ฉด ๋ผ์ฐํ
ํ notification.routing
์ ์ ์ด์ฃผ์์ต๋๋ค
routing.py
app ํด๋ ๋ด์ routing.py ์ ๋๋ค
from django.urls import re_path
from django.conf.urls import url
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/notification/(?P<center_name>\w+)/$', consumers.NotificationConsumer.as_asgi()),
]
TypeError: __call__() missing 1 required positional argument: 'send'
์ ์๋ฌ๊ฐ ๋จ๋ ๊ฒฝ์ฐ์๋ routing.py์ .as_asgi()
๋ฅผ ์จ์ฃผ์ธ์!
์ฐธ๊ณ https://github.com/django/channels/discussions/1736
๊ทธ๋ฆฌ๊ณ ์ฝ๋๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค python manage.py runserver
๋ฅผ ํด์ฃผ์
์ผ ์ฝ๋๊ฐ ๋ฐ์์ด ๋ฉ๋๋ค!!!!!
์ด๊ฑธ ๊น๋จน์ด์ ๊ณ ์น๊ณ ๋์๋ ๊ณ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ..
consumers.py
from channels.generic.websocket import WebsocketConsumer
from channels.db import database_sync_to_async
from channels.layers import get_channel_layer
import json
from asgiref.sync import async_to_sync
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Notification
from .serializers import NotificationSerializer
def get_notification():
notifications = Notification.objects.all()
serializer = NotificationSerializer(notifications, many=True)
return serializer.data
class NotificationConsumer(WebsocketConsumer):
def connect(self):
self.center_name = self.scope['url_route']['kwargs']['center_name'] # notification/routing.py์ ์๋ center_name
async_to_sync(self.channel_layer.group_add)( # group ์ฐธ์ฌ
self.group_name,
self.channel_name
)
self.accept() # websocket ์ฐ๊ฒฐ
# notification์ด ์์ผ๋ฉด ์๋ ์ ์ก
notifications = get_notification()
if notifications:
async_to_sync(self.channel_layer.group_send)(
"center_name", {
"type": "notify",
"data": notifications
}
)
def disconnect(self, close_code):
# Leave group
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
def notify(self, event):
# Send message to WebSocket
self.send(text_data=json.dumps(event))
- connect
group.add
๋ก group์ ์ฐธ์ฌํฉ๋๋คself.accept()
๋ก websocket์ ์ฐ๊ฒฐํฉ๋๋คget_notification
์ผ๋ก notification์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค- notification์ด ์๋ ๊ฒฝ์ฐ,
group_send
๋ก ํด๋น ๋ฐ์ดํฐ๋ค์ ์ ์กํฉ๋๋ค
- disconnect
- ์ฐ๊ฒฐ์ ๋์ ๋์๋
group.discard
๋ก ํด๋น group๊ณผ ์ฐ๊ฒฐ์ ๋์ต๋๋ค
- ์ฐ๊ฒฐ์ ๋์ ๋์๋
websocket ์ฐ๊ฒฐํ๊ธฐ
์น์์ผ ์ปค๋ฅ์
์ ๋ง๋ค๋ ค๋ฉด new WebSocket
์ ํธ์ถํ๋ฉด ๋ฉ๋๋ค
const socket = new WebSocket(url)
- ์ด ๋ url ์๋ ํน์ ํ๋กํ ์ฝ์ธ ws์ ์ฌ์ฉํด์ผํฉ๋๋ค.
new WebSocket(url)
์ ํธ์ถํด ์์ผ์ ์์ฑํ๋ฉด ์ฆ์ ์ฐ๊ฒฐ์ด ์์๋ฉ๋๋ค.
์์ผ์ด ์ ์์ ์ผ๋ก ๋ง๋ค์ด์ง๋ฉด ์๋ ๋ค ๊ฐ์ ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
- open – ์ปค๋ฅ์ ์ด ์ ๋๋ก ๋ง๋ค์ด์ก์ ๋ ๋ฐ์ํจ
- message – ๋ฐ์ดํฐ๋ฅผ ์์ ํ์์ ๋ ๋ฐ์ํจ
- error – ์๋ฌ๊ฐ ์๊ฒผ์ ๋ ๋ฐ์ํจ
- close – ์ปค๋ฅ์ ์ด ์ข ๋ฃ๋์์ ๋ ๋ฐ์
<script>
const socket = new WebSocket(
'ws://'
+ '127.0.0.1:8000'
+ '/ws/notification/'
+ 'center_name'
+ '/'
);
export default {
name: "DashBoardAlarmCard",
props: {
alarmList: Array
},
methods: {
createWebSocket () {
socket.onopen = function(e) {
console.log('์๋ฒ์ ์ฐ๊ฒฐ๋์์ต๋๋ค.')
};
},
},
created () {
this.createWebSocket()
}
}
</script>
DOM์ด ์์ฑ๋ ๋(created) websocket์ ์ฐ๊ฒฐ๋๋๋ก ๊ตฌํํ์์ต๋๋ค.
- url์๋ ๋ฐฑ์๋
routing.py
์์ ์ค์ ํด๋์ ์ฃผ์๋ก ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
'๐ ํ๋ก์ ํธ > ์บก์คํค' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Django Channels๋ก ์ค์๊ฐ ์๋ ๊ธฐ๋ฅ ๋ง๋ค๊ธฐ (2) (0) | 2022.07.04 |
---|