Django WebSocket AsyncConsumer [1] - group

Chung Hwan·2021년 8월 9일
0

Django

목록 보기
2/2
post-thumbnail

HTTP 놔두고 WebSocket을 쓰는 이유가 실시간 송수신 때문이다. HTTP는 클라이언트의 요청 없이 서버가 응답을 보내지 못하기 때문에 실시간이 될 수 없다. 서버가 클라이언트의 요청없이 그 클라이언트에게 응답을 보내야 하는 상황이 왜 생길까? 많은 경우는 한 클라이언트의 요청이 다른 클라이언트에게 영향을 주어야 할 때 생긴다. 가장 대표적인 게 채팅이다. A, B, C 클라이언트가 채팅을 하는 중일 때, A가 서버에 메세지를 보내면, 서버는 B와 C의 요청 없이 B와 C에게 A의 메세지를 응답으로 보내야 한다. 그렇다 보니 WebSocket을 다룰 때 group으로 다뤄야 하는 경우가 많다. 이 글은 django에서 channels의 AsyncConsumer를 이용해 WebSocket으로 group을 관리하는 방법을 다룬다.

group_add

클라이언트를 그룹에 추가 시키는 건 AsyncConsumer 클래스에 내장된 channel_layer.group_add 함수를 이용하면 된다. argument로 그룹을 식별할 수 있는 id (group_id) 와 해당 세션의 channel_name을 받는다. group_id를 가진 그룹이 이미 존재하면 그 그룹에 세션을 추가하고, 존재하지 않는다면 그룹을 새로 만든다.

async def websocket_connect(self, event):
    group_id = "a"

    await self.channel_layer.group_add(
        group_id,
        self.channel_name
    )

    await self.send({
        "type": "websocket.accept"
    })

group_send

그룹 전체의 클라이언트에 메세지를 보내려면 channel_layer.group_send를 이용한다. argument로 group_id와 dictionary를 받는데 이는 지난번 다룬 send와 똑같다. "type"으로 콜백을, 나머지 인자로 그 콜백에 들어갈 argument를 받는다.

async def websocket_receive(self, event):
    group_id = event["group_id"]
    data = event["data"]

    await self.channel_layer.group_send(
        group_id, {
            "type": "write_message",
            "data": data
            "status": "start"
        })

위 예시의 경우 클라이언트로 부터 group_id와 data를 받는다. 그리고 그 group_id를 가진 그룹에 메세지를 보낸다. 이때 type으로 websocket.send가 아니라 write_message가 들어왔으니 클라이언트에 메세지가 바로 전달되지는 않는다. 대신 그룹에 포함된 클라이언트의 채널 마다 callback으로 write_message(data=data, status="start")가 호출될 것이다.

자연스럽게 data와 status를 argument로 받는 write_message라는 함수가 정의되어야 하고, 그 함수 안에서는 websocket.send가 콜백으로 호출되어야 함을 추론해 볼 수 있다.

callback

sendgroup_send에서 dictionary로 주는 argument에 대해서, "type"은 callback이고 나머지는 argument라고 했다. 이때 callback에서 argument를 받을때는 event라는 dictionary로 받는다.

그러니까, 사실 앞서 말한 것 처럼
write_message(data=data, status="start") 가 호출되는게 아니라 사실
write_message(event={"data": data, "status": "start"})가 호출되는 게 맞다.
앞서 얘기한 것은 이해를 돕기 위해 저렇게 표기한 것이니 이해해주시면 좋겠다.

async def write_message(self, event):
    data = json.dumps({
           "data": event["data"],
        "status": event["status"]
    })

    await self.send({
        "type": "websocket.send",
        "text": data,
    })

write_message 는 data와 status를 argument로 받아 self.send({'type': 'websocket.send','text': data}) 로 넘긴다. 즉 실제 클라이언트에게로 전달한다.

이렇게 하면 그룹의 모든 클라이언트에게 메세지를 보낼 수 있다. callback 작동 방식을 이해했다면 그룹의 특정 클라이언트에게만 메세지를 보내는 방법도 쉽게 구현할 수 있다. group_send에서 callback에 원하는 클라이언트의 id를 argument로 넘겨주면, 각 클라이언트의 채널마다 callback이 호출되므로, callback에서 해당 채널의 클라이언트 id가 argument로 넘어온 id와 일치하는지 체크하면 된다.

async def websocket_receive(self, event):
    group_id = event["group_id"]
    data = event["data"]
    target = event["target"] # target's channel_name

    await self.channel_layer.group_send(
        group_id, {
            "type": "target_message",
            "data": data
            "target": "target"
        })

async def target_message(self, event):
    if event["target"] == self.channel_name:
        await self.send({
            "type": "websocket.send",
            "text": event["data"],
        })

callback 을 잘 다루면 AsyncConsumer로 생각보다 더 다양한 것들을 구현할 수 있다.

0개의 댓글