남기면 좋잖아

django-channels 라이브러리 consumer 이해 본문

Programming/Django

django-channels 라이브러리 consumer 이해

Beautiful Hugo 2021. 1. 14. 00:21
반응형

django channels 라이브러리는 장고의 소켓통신을 위한 대표적인 라이브러리 입니다.

장고의 기본적인 데이터흐름과 굉장히 비슷해 학습하는데 큰 어려움은 없습니다. 하지만 저처럼 웹소켓을 처음 다뤄보는 초보자의 경우 몇몇 부분은 이해하기 힘든 부분이 있었습니다.

그 중 consumer 부분은 가장 핵심적이면서도 이해하기 힘든 부분중 하나인데, 크게 4가지 메소드를 사용합니다.

  1. connect()
  2. receive()
  3. disconnect()
  4. 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번째 유저를 찾아내 원하는 메세지를 보낼 수 있게됩니다.

과정 요약

image

전체 과정을 아주 심플하게 요약하자면 위 그림과 같습니다.

반응형
Comments