본문 바로가기
C.W.K.
Stream
Lesson 04 of 07 · published

심박과 연결 유지

~13 min · management, heartbeat, ping

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

TCP 연결 유지 기능만으로 부족한 이유

TCP 자체에도 연결 유지 기능이 있지만 운영체제 기본값은 분에서 시간 단위이고 프로세스별로 조정하기도 어려워. 애플리케이션 수준의 ping/pong 은 우리가 조건을 정할 수 있어. 예를 들어 30초마다 ping 을 보내고 5초 안에 pong 이 오지 않으면 닫는 식이야.

조용히 반쯤 열린 연결 찾아내기

세션 중 노트북 덮개를 닫거나 휴대폰이 와이파이에서 이동통신으로 바뀌거나 NAT 시간 제한이 지나면 서버 소켓은 반쯤 열린 채 남을 수 있어. 커널 버퍼가 바이트를 받아서 send 가 여전히 성공한 것처럼 보이기도 하지. 운영체제의 시간 제한을 기다리는 대신 심박은 이런 상태를 분이 아니라 초 안에 찾아내.

서버와 클라이언트 중 누가 심박을 이끌까

어느 쪽이 시작해도 괜찮아. 서버가 ping 하고 클라이언트가 pong 하는 방식은 서버에 타이머 하나만 두면 돼서 단순해. 클라이언트가 ping 하는 방식은 IoT 나 배터리가 빠듯한 모바일처럼 단순한 클라이언트가 다른 활동이 없을 때 ping 을 건너뛸 수 있어. 하나를 고르고 프로토콜에 문서화해.

Code

서버가 이끄는 심박·python
import asyncio, time

class HeartbeatManager:
    def __init__(self, *, interval=30, timeout=5):
        self.interval = interval
        self.timeout = timeout
        self.last_pong: Dict[WebSocket, float] = {}

    async def run(self, ws: WebSocket):
        self.last_pong[ws] = time.time()
        try:
            while True:
                await asyncio.sleep(self.interval)
                if ws.client_state.value != 1:  # 1 == CONNECTED
                    return
                await ws.send_json({'type': 'ping'})
                # Give the client `timeout` seconds to pong.
                await asyncio.sleep(self.timeout)
                age = time.time() - self.last_pong.get(ws, 0)
                if age > self.interval + self.timeout:
                    await ws.close(code=4000, reason='heartbeat timeout')
                    return
        finally:
            self.last_pong.pop(ws, None)

    def handle_pong(self, ws: WebSocket):
        self.last_pong[ws] = time.time()

heartbeat = HeartbeatManager()

@app.websocket('/ws')
async def with_heartbeat(websocket: WebSocket):
    await websocket.accept()
    asyncio.create_task(heartbeat.run(websocket))
    try:
        async for msg in websocket.iter_json():
            if msg.get('type') == 'pong':
                heartbeat.handle_pong(websocket)
                continue
            # ... handle other messages
    except WebSocketDisconnect:
        pass

External links

Exercise

에코 서버에 심박 관리자를 넣어. 클라이언트를 연결한 뒤 kill -STOP 으로 멈춘 클라이언트를 흉내 내. 서버는 35초, 즉 간격과 시간 제한을 더한 시간 안에 코드 4000 으로 연결을 닫아야 해. 심박이 없으면 같은 죽은 연결이 몇 분씩 남을 수 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.