TOP
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] FastAPI - 웹소켓

by 기록자_Recordian 2025. 4. 27.
728x90
반응형
이전 내용
 

[파이썬] FastAPI - 스트리밍 응답

이전 내용 [파이썬] FastAPI - 쿼리 매개변수, 경로 매개변수, 백그라운드 태스크이전 내용 [파이썬] FastAPI - 정적 파일, API Router이전 내용 [파이썬] FastAPI - FastAPI와 Jinja2 고급 문법시작에 앞서해당

puppy-foot-it.tistory.com


웹소켓

 

웹소켓(WebSocket)은 웹 브라우저와 서버 간의 양방향 통신을 가능하게 하는 프로토콜이다. 이는 HTTP 프로토콜을 사용하는 기존의 요청-응답 모델의 한계를 극복하고, 실시간 데이터 전송이 필요한 애플리케이션에서 주로 사용된다.

 

[주요 특징]

  • 양방향 통신: 클라이언트와 서버 간의 연결이 설정되면, 양쪽 모두 데이터를 자유롭게 주고받을 수 있다. 이는 서버가 클라이언트에게 데이터를 푸시할 수 있는 기능을 제공한다.
  • 지속적인 연결:웹소켓은 오랜 시간 동안 연결을 유지할 수 있어, 반복적인 HTTP 요청을 보내는 것보다 더 효율적이고 낮은 지연 시간을 제공한다.
  • 서버 푸시 기능(server push): 서버가 필요한 경우 클라이언트에게 자동으로 데이터를 전송할 수 있다.
  • 저렴한 오버헤드:웹소켓은 헤더 크기가 작고, 연결이 한번 설정되면 그 이후의 메시지는 오버헤드가 적기 때문에 대량의 데이터를 전송하는 데 유리하다.
  • 실시간 업데이트: 데이터를 실시간으로 전송하기 때문에, 소셜 미디어, 게임, 주식 거래 시스템 등 실시간 데이터 업데이트가 중요한 애플리케이션에서 널리 사용된다.

[웹소켓의 작업 흐름]

  • 연결 설정: 클라이언트가 서버에 웹소켓 연결을 요청한다. 이 요청은 일반적인 HTTP 요청을 통해 이루어지며, Upgrade 헤더에 의해 웹소켓 프로토콜로 전환된다.
  • 데이터 전송: 연결이 설정된 후, 클라이언트와 서버는 텍스트 또는 바이너리 데이터 형식으로 메시지를 지속적으로 주고받을 수 있다.
  • 연결 종료: 통신이 종료되면, 클라이언트 또는 서버가 연결 종료 요청을 보내고, 상대방이 이를 확인한 후 연결이 종료된다.

웹소켓 기본 사용법

 

FastAPI 프레임워크에서는 웹소켓을 다룰 때 주로 WebSocket 타입을 매개변수로 지정해 주면 된다.

웹소켓은 연결이 지속되는 한 계속해서 메시지를 교환할 수 있으므로 서버 리소스를 효율적으로 사용하기 위해 비동기 처리가 필수적이다.

FastAPI에서 비동기 처리는 async와 await라는 두 가지 주요 문법을 통해 구현된다.

  • async: 함수가 '비동기 함수' 임을 나타내는 키워드.
  • 비동기 함수: 내부에서 awiat 표현식을 사용할 수 있으며, 호출될 때 즉시 실행되는 것이 아니라 이벤트 루프에 의해 스케줄링된다.
  • await: 비동기 실행을 지원하는 함수(awaitable)가 완료될 때까지 기다린다. 그러나 이 대기 과정에서 프로그램의 다른 부분이 멈추지 않고, 이벤트 루트가 다른 비동기 작업을 계속 진행할 수 있도록 한다.
※ 비동기 처리
프로그램이 한 작업을 완료하기를 기다리는 동안 다른 작업을 동시에 수행할 수 있도록 하는 기술로, 특히 네트워크 요청이나 파일 I/O와 같이 대기 시간이 긴 작업에서 유용. 
▶ 프로그램은 대기 없이 여러 작업을 병렬로 처리할 수 있다.

 

[FastAPI에서 웹소켓을 사용하여 실시간 양방향 통신 기능 구현 예제]

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Returned Message: {data} from Server")
    except WebSocketDisconnect:
        print("Websocket Disconnected")
        await websocket.close(code=1000) # 명시적으로 웹소켓 연결 종료

▶ 코드 설명

@app.websocket("/ws")
  • @app.websocket("/ws") 데코레이터를 사용하여 웹소켓 라우트 생성.
  • /ws 경로에서 웹소켓 연결을 처리하는 엔드포인트 정의.
  • 클라이언트가 이 경로로 웹소켓 연결을 요청하면, 해당 함수 호출.
async def websocket_endpoint(websocket: WebSocket):
  • websocket_endpoint 함수는 async def로 비동기로 정의되어 있으며, WebSocket 객체를 매개변수로 받음.
  • websocket: Websocket 은 함수 매개변수로 웹소켓 연결 객체를 타입 힌트와 함께 선언한다.
  • 클라이언트와의 연결을 나타내는 websocket 객체를 통해 데이터 송수신 처리.
await websocket.accept()
  • 함수 내부의 비동기 작업들은 await 키워드를 사용하여 호출한다.
  • websocket.accept(): 클라이언트에서의 웹소켓 연결 요청을 수락하는 부분.
  • 클라이언트가 웹소켓에 연결하면 이 명령을 통해 연결 허용하고, 연결 수립 과정이 완료될 때까지 함수 실행을 일시 중지한다.
try:
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Returned Message: {data} from Server")
  • try 블록 내의 코드는 무한 루프를 통해 클라이언트로부터 데이터를 지속적으로 수신하고 처리.
  • await websocket.receive_text() 명령을 사용하여 클라이언트에서 전송한 텍스트 메시지 수신하며, 수신된 데이터는 data 변수에 저장.
  • await websocket.send_text(...)를 통해 클라이언트에게 응답 메시지 전송. 여기서는 수신된 메시지를 포함하여 "Returned Message:..."라는 형식의 메시지를 보냄.
except WebSocketDisconnect:
    print("Websocket Disconnected")
    await websocket.close(code=1000)  # 명시적으로 웹소켓 연결 종료
  • except WebSocketDisconnect 블록은 클라이언트가 웹소켓 연결을 종료할 때 발생하는 예외 처리.
  • 이 예외가 발생하면 "Websocket Disconnected"라는 메시지를 콘솔에 출력, await websocket.close(code=1000) 명령을 통해 명시적으로 웹소켓 연결 종료.
  • 코드 1000은 정상적인 연결 종료를 나타내는 코드.

[웹소켓 테스트]

웹소켓 테스트를 위해 웹페이지 코드를 index.html에 작성한다.

웹페이지상에서 실시간 전송과 응답을 위해서는 프론트엔드 기술이 필요하여 자바스크립트(JavaScript)를 사용한다. 자바스크립트는 HTML 요소에 동적 기능을 추가하여 사용자와의 상호작용을 가능하게 한다.

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Test</title>
</head>
<body>
    <h1>WebSocket Test Page!</h1>

    <!-- 연결 및 해제 버튼-->
    <button id="connect">Connect</button>
    <button id="disconnect" disabled>Disconnect</button>

    <!-- 사용자로부터 메시지를 입력받는 필드-->
    <p>Enter your message: <input type="text" id="userMessage"></p>
    <button id="sendMessage">Send Message</button>

    <!-- 현재 연결 상태와 메시지 로그 표시-->
    <p>Status: <span id="status">Disconnected</span></p>
    <div id="messages"></div>

    <script>
        // 웹소켓 객체를 저장할 변수 선언
        let websocket;
        // DOM 엘리먼트에 대한 참조 가져옴
        const statusElement = document.getElementById("status");
        const messagesElement = document.getElementById("messages");
        const userMessageElement = document.getElementById("userMessage");

        // 'Connect' 버튼에 클릭 이벤트 리스너 추가
        document.getElementById("connect").addEventListener("click", () => {
            // 웹소켓 인스턴스 생성하고, 서버의 웹소켓 끝점에 연결
            websocket = new WebSocket("ws://127.0.0.1:8000/ws");

            // 웹소켓 연결이 성공적으로 열렸을 때 호출
            websocket.onopen = (event) => {
                statusElement.innerText = "Connected"; // 상태를 Connected 로 업데이트
                document.getElementById("connect").disabled = true; // 연결 버튼 비활성화
                document.getElementById("disconnect").disabled = false; // 해제 버튼 활성화
            };

            // 서버로부터 메시지를 받았을 때 호출
            websocket.onmessage = (event) => {
                // 메시지 로그에 새로운 메시지 추가
                const newMessage = document.createElement("p");
                newMessage.innerText = `Received: ${event.data}`;
                messagesElement.appendChild(newMessage);
            };

            // 웹소켓 연결이 닫혔을 때 호출
            websocket.onclose = (event) => {
                statusElement.innerText = "Disconnected"; // 상태를 'Disconnected'로 업데이트
                document.getElementById("connect").disabled = false; // 연결 버튼 활성화
                document.getElementById("disconnect").disabled = true; // 해제 버튼 비활성화
            };
        });

        // 'Disconnect' 버튼에 클릭 이벤트 리스너 추가
        document.getElementById("disconnect").addEventListener("click", () => {
            // 웹소켓 연결 닫음
            websocket.close();
        });

        // 'Send Message' 버튼에 클릭 이벤트 리스너 추가
        document.getElementById("sendMessage").addEventListener("click", () => {
            // 입력 필드에서 메시지 가져옴
            const message = userMessageElement.value;
            // 웹소켓을 통해 메시지를 서버로 전송
            websocket.send(message);

            // 메시지 로그에 전송한 메시지 추가
            const sentMessage = document.createElement("p");
            sentMessage.innerText = `Sent: ${message}`;
            messagesElement.appendChild(sentMessage);
        });
    </script>

</body>
</html>

 

해당 코드를 작성한 후,  (FastAPI 실행말고) index.html을 켜서 테스트해 볼 수 있다.


웹소켓 주요 메소드

 

◆ 기본 주요 메소드

  • await websocket.accept(): 웹소켓 연결 수락
    서버와 클라이언트가 통신 방식을 명시하기 위해 사용되며, "wamp", "mqtt", "soap" 등 특정 subprotocol을 수락할 때 사용된다.
await websocket.accept(subprotocol="wamp")
  • await websocket.receive_text(): 텍스트 메시지 수신
    클라이언트로부터 텍스트 형식의 메시지를 수신. 이 메소드는 비동기적으로 실행되며, 메시지를 받을 때까지 대기한다.
  • await websocket.receive_bytes(): 바이트 메시지 수신
  • await websocket.send_text(data): 텍스트 메시지 전송
    서버에서 클라이언트로 텍스트 메시지를 전송. data에는 전송할 메시지 내용이 포함된다.
await websocket.send_text("안녕하세요!")
  • await websocket.send_bytes(data): 바이트 메시지 전송
  • await websocket.close(code, reason): 웹소켓 연결 종료
    웹소켓 연결 종료. code: 종료 코드(예: 1000은 정상 종료)를 의미하며, reason: 종료 사유를 설명하는 선택적 문자열이다.
  • onopen: 연결 성공 이벤트
    웹소켓 연결이 성공적으로 수립되었을 때 호출되는 이벤트 핸들러. 클라이언트와 서버 간의 연결 상태를 확인할 수 있다.
  • onmessage: 메시지 수신 이벤트
    서버로부터 메시지를 수신했을 때 호출되는 이벤트 핸들러. 수신된 메시지에 대한 처리를 수행할 수 있다.
  • onclose: 연결 종료 이벤트
    웹소켓 연결이 종료되었을 때 호출되는 이벤트 핸들러. 연결 상태의 변경에 대응하여 필요한 작업을 수행할 수 있다.
  • onerror: 오류 발생 이벤트
    웹소켓 통신 중 오류가 발생했을 때 호출되는 이벤트 핸들러. 문제가 발생했을 때 적절한 처리를 할 수 있다.
  • WebsocketDisconnect: 연결 끊김 예외 처리
from fastapi import WebSocketDisconnect

try:
	data = await websockt.receive_text()
except WebSocketDisconnect:
	print("연결 종료")

 

◆ 매개변수 처리 간략화

웹소켓 경로에서도 쿼리와 경로 매개변수를 사용할 수 있다.

@app.websocket("/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str):

 

websocket.client_state와 websocket.application_state는 웹소켓의 현재 상태를 확인할 때 유용하며, 이를 통해 연결 상태에 따른 로직을 구현할 수 있다.

  • websocket.client_state: 클라이언트의 웹소켓 연결 상태
    현재 웹소켓 클라이언트의 상태를 나타낸다. 이 속성은 웹소켓 연결이 열려 있는지, 닫혀 있는지 등의 정보를 포함한다.
    상태 값:
    - CONNECTING: 연결이 시도 중인 상태.
    - OPEN: 연결이 성공적으로 완료된 상태.
    - CLOSING: 연결이 종료되고 있는 상태.
    - CLOSED: 연결이 종료된 상태.
from fastapi import FastAPI, WebSocket, WebSocketDiscconect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
	await websocket.accept()
    if websocket.client_state == WebSocketState.CONNECTED:
    	await websocket.send_text("클라이언트 연결 중")
  • websocket.application_state: 서버의 웹소켓 연결 상태
    웹소켓 연결을 통해 애플리케이션의 상태를 나타낸다. 이 속성은 서버 측의 상태를 관리하는 데 사용된다.
    상태 값: 이 값은 사용자 정의 애플리케이션 상태를 반영할 수 있다.
    - IDLE: 애플리케이션이 현재 대기 중인 상태.
    - ACTIVE: 애플리케이션이 현재 활성 상태인 경우.
    - ERROR: 애플리케이션에서 오류가 발생한 상태.
from fastapi import FastAPI, WebSocket, WebSocketDiscconect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
	await websocket.accept()
    if websocket.application_state == WebSocketState.CONNECTED:
    	await websocket.send_text("애클리케이션 연결 중")

 

※ WebSocketState: websockets 라이브러리에 정의된 열거형 값으로, CONNECT, DISCONNECTED, CONNECTING 등이 있다. 이 값을 사용해서 특정 상태일 때의 처리를 한다. 이러한 상태 정보를 이용하면 FastAPI에서 웹소켓을 더 효율적으로 사용할 수 있다.
예. 연결이 끊긴 경우 재연결 시도 로직 구현 등

 


[참고]

가장 빠른 풀스택을 위한 플라스크 & FastAPI


다음 내용

 

[파이썬] FastAPI - ORM 연동하기(SQLAlchemy)

이전 내용 [파이썬] FastAPI - 웹소켓이전 내용 [파이썬] FastAPI - 스트리밍 응답이전 내용 [파이썬] FastAPI - 쿼리 매개변수, 경로 매개변수, 백그라운드 태스크이전 내용 [파이썬] FastAPI - 정적 파일, AP

puppy-foot-it.tistory.com

 

728x90
반응형