바뀜

9,351 바이트 추가됨 ,  2022년 11월 12일 (토) 08:43
편집 요약 없음
1번째 줄: 1번째 줄:  
== 개요 ==
 
== 개요 ==
장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.
+
django concumer. 장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.
 +
 
 +
=== 유의 ===
 +
 
 +
* 동기 코드와 비동기 코드는 구현시간이 다름.
 +
** 장고의 내용을 반영하면 곧장 새로고침되어 브라우저에 반영되지만, 컨슈머의 반영은 실시간 쓰레드로 이루어지기 때문에 동기 코드와 반영시간이 다른데, 더 늦기도 하다.(서버가 켜져서 동기페이지는 잘 나오는데, 비동기는 반영이 안되어 에러가 난 것으로 오해하게 되는 경우가 잦다.)
 +
* 컨슈머는 한 번 실행 후 지속적으로 유지되는듯.
 +
** 모델 실행이 오래걸리는 챗봇 구현에서 한 번 컨슈머가 실행되고 나면 이후 응답은 굉장히 빠르다.
    
== 형태 ==
 
== 형태 ==
19번째 줄: 26번째 줄:  
|
 
|
 
|
 
|
 +
|비동기에서 동기코드를 쓰면 다른 이벤트를 못받는건가?????
 +
아마 쓰레드를 쓰지 않고 순차적으로 작동하는 듯한데, 이게 왜 문제가 되는지 모르겠다;;;
 +
|-
 +
|
 +
|
 +
|DB를 쓰기 위한 방법이 없진 않다.
 +
 +
굳이 비동기방식에서 DB를 사용하고자 한다면 database_sync_to_async를 사용하면 된다.
 +
|}
 +
 +
=== Generic consumer ===
 +
뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다.
 +
 +
= 컨슈머 각 항목에 대하여 =
 +
 +
==Consumer.py==
 +
컨슈머는 클래스로 구현되어 connect, disconnect 등으로 구성된다.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|컨슈머 정의
 +
|클래스로 구현한다.
 +
|<syntaxhighlight lang="python">
 +
from channels.generic.websocket import WebsocketConsumer
 +
from asgiref.sync import async_to_sync  # 필요에 따라.
 +
 +
class 컨슈머이름(WebsocketConsumer):
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
from channels.generic.websocket import AsyncWebsocketConsumer
 +
 +
class 컨슈머이름(AsyncWebsocketConsumer):
 +
</syntaxhighlight>
 +
|}
 +
===connect===
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|사전 설정
 +
|각종 변수들의 조작을 먼저 한다.
 +
스코프로 받아서 변수들을 조작하는데, 그 구조는 다음과 같다.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
|-
 +
|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"]
 +
|-
 +
|그룹 참여
 +
|그룹을 지정하여 참여시킨다.
 +
|<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>
 +
|-
 +
|서브프로토콜
 +
|서브프로토콜을 지정해 연결할 수도 있다.
 +
|self.accept('subprotocol")
 +
<nowiki>#</nowiki> self.scope['subprotocols'] 을 통해 관련 정보를 알 수 있다.
 +
|}
 +
===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에서 특정 과정을 거쳐 self.close(code=1234) 등으로 커스텀에러를 낼 수도 있다.
 +
|disconnect에서 코드를 담아 상황에 맞게 작동, 반환한다.
 +
|}
 +
===receive===
 +
타인으로부터 메시지를 받는 게 아니라, 자바스크립트로부터 받는 메시지와 관련된 함수이다.(웹소켓에서 메시지를 받을 때.)
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|본인의 자바스크립트에서 웹소켓으로 메시지를 보낸 경우.
 +
자바스크립트에서 데이터가 넘어올 땐 json으로 변환되어 오기 때문에 우측과 같이 구성해야 한다.
 +
 +
 +
메시지를 보낼 때 type는 받는 함수를 지정할 때 쓰인다.(없어도 되긴 함.)
 +
 +
type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.
 +
{| class="wikitable"
 +
!데이터 전송 함수
 +
!설명
 +
|-
 +
|self.send()
 +
|웹소켓(자바스크립트)으로 데이터를 보내 창에 띄운다.
 +
|-
 
|
 
|
 +
*async_to_sync(동기식)
 +
*await self.channel_layer.group_send(비동기식)
 +
|다른 유저들에게 보낼 때 사용한다.
 +
|}
 +
|<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):  # 변수명으로 event 대신 text를 사용하기도 한다.
 +
        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>
 +
|}
 +
= Scope =
 +
뷰에 request가 있다면 컨슈머엔 scope가 있다.
 +
 +
request와 동일하게 사용자가 입력한 값, 기타정보들을 사전형태로 담고 있다.(담은 키도 request와 거의 동일하다고 보면 된다.)
 +
 +
===속성===
 +
{| class="wikitable"
 +
!속성
 +
!설명
 +
!활용
 +
|-
 +
|method
 +
|GET인가 POST인가의 정보.
 +
|if self.scope['method'] == POST:
 +
|-
 +
|user
 +
|요청자의 user계정이 담긴다.
 +
|self.scope['user']
 
|}
 
|}
 
[[분류:장고 웹소켓]]
 
[[분류:장고 웹소켓]]