[udemy] Django channels - create your own web chat application - 강의 후기
저장소 : https://github.com/m7md-almalki/Django-channels-course
학습 코드 저장소 : https://github.com/bluebamus/master-django-channels
1. 채널 구조
- 채널의 routing은 url과 같고 consumers는 views와 같다.
- WS(websockert)은 http로 접근하는게 아닌, "ws://127.0.0.1:8000/websocket/" 형식으로 접근한다.
- consumer는 동기식과 비동기식이 있다. 학습에서는 WebsocketConsumer, 동기식 웹소켓을 사용한다.
2. WebsocketConsumer 주요 함수 정리
2.1. connect(self)
2.1.1. WebSocket 연결 요청을 처리하는 함수
- 클라이언트가 WebSocket을 열면 자동으로 호출됨
- 기본적으로 .accept()를 호출해야 정상적으로 연결됨
2.1.2. 사용 예시
def connect(self):
self.accept() # 클라이언트의 WebSocket 연결을 승인
print(f"WebSocket 연결됨: {self.channel_name}")
2.2. disconnect(self, close_code)
2.2.1. WebSocket 연결이 끊어질 때 실행되는 함수
- 사용자가 페이지를 닫거나 인터넷이 끊길 때 호출됨
- close_code는 연결이 종료된 이유를 나타냄
2.2.2. 사용 예시
def disconnect(self, close_code):
print(f"WebSocket 연결 해제: {self.channel_name}, 코드: {close_code}")
2.3. receive(self, text_data)
2.3.1. 클라이언트로부터 메시지를 받을 때 실행되는 함수
- 클라이언트가 WebSocket을 통해 JSON 또는 일반 텍스트 데이터를 보내면 호출됨
- 데이터는 text_data로 전달됨
- json.loads(text_data)를 사용하여 JSON 데이터를 파싱할 수 있음
2.3.2. 사용 예시
import json
def receive(self, text_data):
data = json.loads(text_data)
print(f"메시지 수신: {data}")
2.4. send(self, text_data)
2.4.1. 클라이언트에게 메시지를 보낼 때 사용
- JSON 데이터를 보낼 경우 json.dumps(data)를 사용해야 함
2.4.2. 사용 예시
import json
def send_message(self, message):
data = {"type": "chat_message", "message": message}
self.send(json.dumps(data)) # JSON 형식으로 메시지 전송
2.5. channel_layer.send(channel_name, message)
2.5.1. 특정 채널로 메시지를 보낼 때 사용
- 다른 Consumer에게 메시지를 보내는 용도로 사용됨
- channel_name은 메시지를 받을 Consumer의 이름
- message는 JSON 형식의 데이터
2.5.2. 사용 예시 (다른 사용자에게 메시지 보내기)
from asgiref.sync import async_to_sync
def receive(self, text_data):
data = json.loads(text_data)
# 상대방의 채널 찾기
other_user = User.objects.get(id=data["to_user_id"])
user_channel = models.UserChannel.objects.get(user=other_user)
# 메시지 전송
async_to_sync(self.channel_layer.send)(
user_channel.channel_name,
{
"type": "chat_message",
"message": data["message"]
}
)
2.6. channel_layer.group_add(group_name, channel_name)
2.6.1. 특정 그룹에 WebSocket을 추가
- 여러 WebSocket을 그룹화할 때 사용
2.6.2. 사용 예시 (채팅방 그룹에 추가)
from asgiref.sync import async_to_sync
def connect(self):
self.accept()
group_name = "chat_room_1"
async_to_sync(self.channel_layer.group_add)(
group_name,
self.channel_name
)
2.7. channel_layer.group_discard(group_name, channel_name)
2.7.1. 그룹에서 WebSocket을 제거
- 연결이 끊어질 때 그룹에서 제거해야 함
2.7.2. 사용 예시 (채팅방 그룹에서 제거)
def disconnect(self, close_code):
group_name = "chat_room_1"
async_to_sync(self.channel_layer.group_discard)(
group_name,
self.channel_name
)
2.8. channel_layer.group_send(group_name, message)
2.8.1. 그룹에 속한 모든 WebSocket에 메시지 전송
- 같은 채팅방에 있는 모든 사용자에게 메시지를 보낼 때 사용
2.8.2. 사용 예시 (채팅방의 모든 사용자에게 메시지 보내기)
def receive(self, text_data):
data = json.loads(text_data)
group_name = "chat_room_1"
async_to_sync(self.channel_layer.group_send)(
group_name,
{
"type": "chat_message",
"message": data["message"]
}
)
2.9. 사용자 정의 핸들러 (chat_message(self, event))
2.9.1. group_send 또는 channel_layer.send를 통해 호출되는 함수
- event["type"]에 정의된 핸들러 이름과 일치해야 실행됨
2.9.2. 사용 예시 (그룹 메시지 처리) python 복사 편집
def chat_message(self, event):
message = event["message"]
self.send(text_data=json.dumps({"message": message}))
2.10. 정리
함수명 | 설명 |
connect(self) | WebSocket 연결 요청을 처리 |
disconnect(self, close_code) | WebSocket 연결 해제 시 실행 |
receive(self, text_data) | 클라이언트로부터 메시지 수신 |
send(self, text_data) | 클라이언트에게 메시지 전송 |
channel_layer.send(channel_name, message) | 특정 WebSocket에 메시지 전송 |
channel_layer.group_add(group_name, channel_name) | 그룹에 WebSocket 추가 |
channel_layer.group_discard(group_name, channel_name) | 그룹에서 WebSocket 제거 |
channel_layer.group_send(group_name, message) | 그룹의 모든 WebSocket에 메시지 전송 |
chat_message(self, event) | group_send에서 실행되는 핸들러 |
3. 데이터 형식
- 다양항 방식을 사용할 수 있다.
data = {
"type": "recevier_function",
"type_of_data": "new_message",
"data": text_data.get("message"),
}
print("user_channel_name.channel_name :",user_channel_name.channel_name)
async_to_sync(self.channel_layer.send)(
user_channel_name.channel_name, data
)
async_to_sync(self.channel_layer.send)(
user_channel.channel_name,
{
"type": "chat_message",
"message": "the_messages_have_been_seen_from_the_other"
}
)
- channel_name 뒤에 오는 파라미터의 내용은 정해진 형식이 없다. 하지만 type는 하나의 규칙이 있다.
- type의 이름이 클래스 내 함수 명일 경우, send를 하기 전 해당 함수를 먼저 호출한다
- type와 매칭되는 함수 이름이 없는 경우, client에게 전달한다.
3.1. 상세 동작 설명
3.1.1. type이 함수명과 동일한 경우
- Channels는 type의 값에 해당하는 메서드를 자동으로 호출합니다.
- 예를 들어 type이 "chat_message"일 때 chat_message라는 함수가 존재하면, 그 함수가 자동으로 실행됩니다.
3.1.2. type에 해당하는 함수가 없는 경우
- 만약 type에 해당하는 함수가 Consumer 클래스 안에 정의되지 않은 경우, 메시지는 클라이언트로 직접 전송됩니다.
- 메시지 처리 로직이 없기 때문에 메시지가 그대로 클라이언트로 전송되며, 클라이언트는 메시지를 받아 처리할 수 있습니다.
4. 강의 중 문제
- 윈도우에서 channels의 동작이 제대로 수행되지 않았다. 사용자의 세션이 제대로 이루어지지 않아 user의 접근시 정보가 공란으로 나왔다.
- 리눅스에서 작업 하는 것으로 해결함
- 테스트 중 연결이 계속 끊기는 현상이 발견 되었다.
- 일정 시간 후 발생되는 것으로 보아 세션의 타임아웃으로 보였다.
- 설정 파일의 파라미터를 변경하건, ping을 이용한 더미 데이터를 보내는 것으로 유지할 수 있다.
5. 고도화
- 강의는 1:1 학습만 제공한다. layer를 이용하여 그룹을 만들 수 있는 것으로 보아 그룹에 추가 및 삭제를 하고 그룹 전송을 할 수 있는것으로 해결할 수 있을것 같다.
- redis가 아닌, 인메모리를 사용했다. redis를 사용하면 많은 문제를 해결할 수 있을 것이다.
- 예외처리가 하나도 되지 않았지만, 대략적인 발생 위치들은 예상되기에 문제되지 않았다.
6. 강의 후기
- 1:1 채팅을 위한 최소한, 필수적인 내용들을 쉽게 설명했다.
- 영어를 천천히 구사하며 발음도 상당히 깔끔했다.
- 메시지의 저장, 채널의 저장을 위한 DB table을 만들고, 이를 이용하여 이전 데이터를 가져오고 읽은 메시지를 읽음 처리하는 기능까지 구현한다.
- 큰 줄기의 필요한 기능은 구현해보기 때문에, 생각을 조금만 한다면 추가로 고도화 할 내용들을 금방 찾고 이해할 수 있다.
- 초급 혹은 나처럼 예전 강의로 리마인드 하기에 좋은 강의인것 같다.