WebRTC는 웹 브라우저 상에서 실시간 p2p 통신을 지원하는 표준 라이브러리입니다.
WebRTC의 동작 과정
피어 간 연결을 위해선 한 쌍의 RTCPeerConnection
객체들이 짝을 이뤄야 합니다. 짝을 이룬 두 객체 중 한 객체는 송신 역할만 가능하고, 다른 객체는 수신 역할만 가능합니다. 이에 대한 역할을 명확하게 구분하고자 새로운 클래스로 재정의하였습니다.
PeerConnection 클래스 계층도
PeerConnection
— RTCPeerConnection
에 대해 재정의(소스코드)
두 클라이언트들이 서로 양방향 통신(bidirectional communication)을 하기 위해선 두 쌍의 PeerConnection
이 필요합니다.
양방향 통신(bidirectional communication) 시 PeerConnection들
하지만, 클라이언트들의 수가 3개 이상일 때부터 문제가 발생합니다. 클라이언트가 관리하는 LocalPeerConnection
과 RemotePeerConnection
이 어떤 클라이언트와 연결되어 있는지 구분하기 어려워집니다.
세 클라이언트들의 연결
이를 위해 각 Connection에 대한 식별자를 도입하고, Map
구조를 이용해 관리하는 PeerConnectionManager
를 도입했습니다.
export default class PeerConnectionManager {
private _localId: string = "";
private _localPeerConnMap: Map<string, LocalPeerConnection>;
private _remotePeerConnMap: Map<string, RemotePeerConnection>;
// ...
}
Signaling 서버는 피어들 사이의 SDP(Session Description Protocol)를 교환해주는 별도의 서버입니다. 일반적으로 HTTP 기반 웹 API로 구현됩니다. 이번에는 FastAPI 기반의 WebSocket 서버를 구현했습니다.
앞서 클라이언트들의 Connection에 대한 식별자가 필요하다는 것을 언급했습니다. 이 식별자에 대한 관리는 Signaling 서버에서 담당합니다.
from typing import Any
from fastapi import WebSocket
from managers.exceptions import ConnIdAlreadyExists
from utils.generators import generate_random_digit_char_string
class ConnectionManager:
def __init__(self):
self.connections: dict[str, WebSocket] = {}
def generate_next_id(self):
ids: list[str] = self.get_conn_ids()
while True:
new_id: str = generate_random_digit_char_string()
if new_id not in ids:
return new_id
def get_conn_ids(self):
return list(self.connections.keys())
async def connect(self, conn_id: str, websocket: WebSocket):
if conn_id in self.connections.keys():
raise ConnIdAlreadyExists()
self.connections.update({conn_id: websocket})
await websocket.accept()
def disconnect(self, conn_id: str):
self.connections.pop(conn_id)
async def send_personal_data(self, data: Any, conn_id: str):
await self.connections.get(conn_id).send_json(data)
async def broadcast_except_sender(self, data: Any, sender_conn_id: str):
for [conn_id, conn] in self.connections.items():
if conn_id == sender_conn_id:
continue
await conn.send_json(data)
async def broadcast(self, data: Any):
for conn in self.connections.values():
await conn.send_json(data)