2번째 줄: |
2번째 줄: |
| 장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서. | | 장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서. |
| | | |
− | [실시간 채팅 말미의 내용을 이곳으로 가져와 정리하면 좋겠다.]
| + | === 유의 === |
| + | |
| + | * 에러가 난 것으로 오해 : 장고의 내용을 반영하면 곧장 새로고침되어 브라우저에 반영되지만, 컨슈머의 반영은 실시간 쓰레드로 이루어지기 때문에 더 늦기도 하다.(서버가 켜져서 동기페이지는 잘 나오는데, 비동기는 구현이 안되어 에러가 난 것으로 오해하게 되는 경우가 잦다. 서버에 반영되는 속도는 더 느린 느낌.) |
| | | |
| == 형태 == | | == 형태 == |
34번째 줄: |
36번째 줄: |
| 뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다. | | 뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다. |
| | | |
| + | = 컨슈머 각 항목에 대하여 = |
| + | |
| + | ==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 = | | = Scope = |
| 뷰에 request가 있다면 컨슈머엔 scope가 있다. | | 뷰에 request가 있다면 컨슈머엔 scope가 있다. |