남기면 좋잖아
django-channels 라이브러리 consumer 이해 본문
django channels 라이브러리는 장고의 소켓통신을 위한 대표적인 라이브러리 입니다.
장고의 기본적인 데이터흐름과 굉장히 비슷해 학습하는데 큰 어려움은 없습니다. 하지만 저처럼 웹소켓을 처음 다뤄보는 초보자의 경우 몇몇 부분은 이해하기 힘든 부분이 있었습니다.
그 중 consumer 부분은 가장 핵심적이면서도 이해하기 힘든 부분중 하나인데, 크게 4가지 메소드를 사용합니다.
- connect()
- receive()
- disconnect()
- group_send()와 type 메소드
이 4가지 메소드에 통해 자바스크립트와 장고 사이의 소켓통신 데이터 흐름을 다뤄보려 합니다.
connect()
connect() 메소드는 자바스크립트에서 웹소켓 생성시 django에서 처음으로 응답받는 메소드입니다.
const ws = new WebSocket(SOCKET_HOST + `/ws/users/`);
위 처럼 웹소켓 생성시 장고에서는 Routing과 middleware stack 들을 거치고 connect() 메소드에 도달합니다.
보통 connect() 메소드에 도달하고 인증과정을 거쳐 해당 유저를 적절한 Group에 속하게 하는 작업들을 합니다.
receive()
ws.send(message);
receive() 메소드는 자바스크립트에서 웹소켓을 통해 메세지 전송시 django에서 응답을 받는 메소드입니다.
주의할 점은 해당 메세지를 JavaScript 값이나 객체를 JSON 문자열로 변환해야 합니다. 그래서 위 코드보다는
ws.send(
JSON.stringify({
message,
})
);
이 같은 형태로 자주 사용했습니다.
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
receive() 메소드는 text_data 인자를 받아 위 처럼 데이터를 받아볼 수 있게 됩니다.
반대로 django에서 프론트쪽으로 메세지를 보내고 싶다면?
# 현재 소켓으로 접속한 유저에게 전송
await self.send(text_data=json.dumps({
'message': message,
}))
# 현재 소켓으로 접속한 유저와 그가 속한 그룹원들에게 전송
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
}
)
위 처럼 send() 혹은 group_send() 메소드를 사용하면 됩니다.
다만 group_send()를 보낼땐 type 메소드를 지정해야하는데 뒤에서 알아보겠습니다.
disconnect()
ws.close()
disconnect() 메소드는 위 처럼 자바스크립트의 웹소켓의 종료 신호 전송시 응답받는 메소드입니다.
반대로 django에서 프론트쪽으로 종료신호를 보내고 싶다면 self.close()
메소드를 사용하면 됩니다.
더 나아가 내가 속한 그룹원들도 전부 종료시키고 싶다면 아래와 같이 사용할 수 있습니다.
await self.channel_layer.group_send(
GROUP_NAME,
{
'type': 'websocket.close',
}
)
자바스크립트에서도 close 신호를 받았을때 어떻게 처리해줄지 커스텀을 해줄 수 있습니다.
ws.onclose = function (e) {
console.log(e)
notification.open({
message: "채팅이 종료되었습니다.",
description: "새로운 채팅을 시작하세요",
icon: <CloseCircleOutlined style={{ color: "red" }} />,
});
};
group_send()와 type 메소드
만약 채널레이어 어느 한 그룹에 15명의 유저가 속해있다고 가정해봅시다. 저는 이 중에 12, 13번째 유저에게만 메세지를 보내고 싶습니다. 이런 상황에 group_send()
와 type 메소드를 이용해 볼 수있습니다.
group_send()
는send()
메소드와 다르게 현재 소켓통신으로 접속한 유저를 포함하여 모든 그룹원들에게 메세지를 전송할 수 있는 메소드입니다.
이렇게 보면 그렇게 헷갈려보이지 않은데 문제는 그 안에 반드시 지정해야하는 type 메소드 때문입니다.
type 메소드는 그룹원들 하나하나에 대응하여 메세지를 전송하는 역할의 메소드입니다. 글로는 어려우니 코드를 보겠습니다.
async def receive(self, text_data):
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'user_message',
'users': [12, 13]
}
)
async def user_message(self, event):
users = event['users']
message = ''
if self.scope['user'].pk in users:
message = "hello!"
await self.send(text_data=json.dumps({
'message': message
}))
위 코드에서 유심히 보셔야 할 곳은 user_message()
메소드의 self.scope['user'].pk
부분입니다. 지금까지의 해당 코드는 현재 소켓통신으로 접속한 유저의 pk값 이었습니다.
하지만 user_message()
안에서는 그룹원들 개개인의 pk 가 되어 각자가 차례차례 if 문을 거쳐 12, 13번째 유저를 찾아내 원하는 메세지를 보낼 수 있게됩니다.
과정 요약
전체 과정을 아주 심플하게 요약하자면 위 그림과 같습니다.
'Programming > Django' 카테고리의 다른 글
10. 장고 DRF를 활용한 웹 API 만들기(4) (0) | 2020.08.09 |
---|---|
10. 장고 DRF를 활용한 웹 API 만들기(3) (0) | 2020.08.05 |
10. 장고 DRF를 활용한 웹 API 만들기(2) (0) | 2020.08.04 |
10. 장고 DRF를 활용한 웹 API 만들기 (0) | 2020.07.28 |
9. 비SPA 방식으로 인스타그램 St 만들기 (0) | 2020.07.19 |