Flask와 웹소켓(SocketIO)으로 실시간 서비스 구현하기

  • 카카오톡 공유하기
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 링크 복사하기

웹 애플리케이션에서 실시간 기능은 사용자 경험을 크게 향상시킵니다. 채팅, 실시간 알림, 라이브 대시보드 등 다양한 기능을 구현하기 위해서는 웹소켓이 필수적입니다. 이 글에서는 Flask와 Flask-SocketIO를 사용하여 실시간 기능을 구현하는 방법을 알아보겠습니다.

웹소켓이란?

웹소켓은 클라이언트와 서버 간에 지속적인 양방향 연결을 제공하는 통신 프로토콜입니다. 기존 HTTP 통신과 달리 한 번 연결이 수립되면 양쪽에서 자유롭게 데이터를 주고받을 수 있어 실시간 애플리케이션에 이상적입니다.

기존 HTTP 통신과 웹소켓의 차이점:

  • HTTP: 요청-응답 모델, 클라이언트가 요청해야만 서버가 응답
  • 웹소켓: 양방향 통신, 서버가 클라이언트에게 능동적으로 데이터 전송 가능
http vs websocket 비교,
출처: https://www.scaleway.com/en/blog/iot-hub-what-use-case-for-websockets/

Flask-SocketIO 소개

Flask-SocketIO는 Flask 애플리케이션에 웹소켓 기능을 쉽게 통합할 수 있게 해주는 확장 라이브러리입니다. 이 라이브러리는 Socket.IO 프로토콜을 구현하여 모든 브라우저에서 실시간 기능을 지원합니다.

환경 설정하기

먼저 필요한 패키지를 설치합니다:

eventlet은 비동기 네트워킹 라이브러리로, Flask-SocketIO의 성능을 향상시키는 데 사용됩니다.

기본 Flask-SocketIO 애플리케이션 구조

간단한 실시간 채팅 애플리케이션을 만들어 보겠습니다. 먼저 기본 애플리케이션 구조를 설정합니다:

from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    socketio.run(app, debug=True)

실시간 채팅 기능 구현하기

이제 웹소켓 이벤트 핸들러를 추가하여 채팅 기능을 구현해 보겠습니다:

@socketio.on('connect')
def handle_connect():
    print('클라이언트가 연결되었습니다.')

@socketio.on('disconnect')
def handle_disconnect():
    print('클라이언트가 연결을 종료했습니다.')

@socketio.on('message')
def handle_message(data):
    print('받은 메시지:', data)
    # 모든 클라이언트에게 메시지 브로드캐스트
    emit('message', data, broadcast=True)

프론트엔드 구현

이제 클라이언트 측 HTML과 JavaScript를 작성해 보겠습니다. ‘templates’ 폴더에 ‘index.html’ 파일을 생성합니다:

<!DOCTYPE html>
<html>
<head>
    <title>Flask-SocketIO 채팅</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        #messages {
            height: 300px;
            overflow-y: scroll;
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <h1>Flask-SocketIO 실시간 채팅</h1>
    <div id="messages"></div>
    <input type="text" id="username" placeholder="사용자 이름" />
    <input type="text" id="message" placeholder="메시지 입력" />
    <button id="send">전송</button>

    <script>
        $(document).ready(function() {
            // 소켓 연결
            var socket = io();
            
            // 연결 이벤트
            socket.on('connect', function() {
                $('#messages').append('<p><i>서버에 연결되었습니다.</i></p>');
            });
            
            // 메시지 수신 이벤트
            socket.on('message', function(data) {
                $('#messages').append('<p><strong>' + data.username + '</strong>: ' + data.message + '</p>');
                // 스크롤을 항상 아래로 유지
                $('#messages').scrollTop($('#messages')[0].scrollHeight);
            });
            
            // 메시지 전송
            $('#send').click(function() {
                var username = $('#username').val() || '익명';
                var message = $('#message').val();
                
                if (message) {
                    socket.emit('message', {
                        username: username,
                        message: message
                    });
                    $('#message').val('');
                }
            });
            
            // Enter 키로 메시지 전송
            $('#message').keypress(function(e) {
                if(e.which == 13) {
                    $('#send').click();
                }
            });
        });
    </script>
</body>
</html>

실시간 알림 시스템 구현하기

채팅 외에도 실시간 알림 시스템을 구현해 보겠습니다. 이는 새로운 이벤트가 발생했을 때 사용자에게 즉시 알림을 보내는 기능입니다.

서버 측 코드에 알림 이벤트 핸들러를 추가합니다:

@socketio.on('notification')
def handle_notification(data):
    # 특정 사용자에게만 알림 전송
    emit('notification', data, to=data['user_id'])
    
# 서버에서 알림을 보내는 함수 (다른 라우트에서 호출 가능)
def send_notification(user_id, message):
    socketio.emit('notification', {
        'user_id': user_id,
        'message': message,
        'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    }, to=user_id)

룸(Room) 기능 활용하기

Socket.IO의 룸 기능을 사용하면 특정 그룹의 사용자에게만 메시지를 전송할 수 있습니다. 이는 그룹 채팅이나 특정 주제별 채팅방을 구현할 때 유용합니다.

from flask_socketio import join_room, leave_room

@socketio.on('join')
def on_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    emit('message', {'username': 'System', 'message': username + '님이 ' + room + '방에 입장했습니다.'}, to=room)

@socketio.on('leave')
def on_leave(data):
    username = data['username']
    room = data['room']
    leave_room(room)
    emit('message', {'username': 'System', 'message': username + '님이 ' + room + '방을 나갔습니다.'}, to=room)

@socketio.on('room_message')
def handle_room_message(data):
    emit('message', data, to=data['room'])

대규모 애플리케이션을 위한 확장

실제 프로덕션 환경에서는 더 많은 사용자와 메시지를 처리해야 할 수 있습니다. 이를 위한 몇 가지 팁을 소개합니다:

  • Redis를 메시지 브로커로 사용: 여러 서버 인스턴스 간 메시지 동기화에 유용
  • 비동기 모드 사용: eventlet이나 gevent를 사용하여 비동기 처리
  • 로드 밸런싱: 여러 서버에 부하 분산
# Redis를 메시지 브로커로 사용하는 예
from flask import Flask
from flask_socketio import SocketIO

app = Flask(__name__)
socketio = SocketIO(app, message_queue='redis://')

보안 고려사항

실시간 애플리케이션을 구현할 때 보안은 매우 중요합니다:

  • 사용자 인증 및 권한 확인
  • 입력 데이터 검증
  • CORS(Cross-Origin Resource Sharing) 설정
  • Rate limiting 구현
# CORS 설정 예시
socketio = SocketIO(app, cors_allowed_origins="*")

# 더 엄격한 CORS 설정
socketio = SocketIO(app, cors_allowed_origins=["https://example.com", "https://subdomain.example.com"])

실제 애플리케이션 예시: 실시간 협업 도구 , 구글 Doc, 웨어라유

이러한 기술을 활용하여 실시간 협업 도구를 만들 수 있습니다. 예를 들어, 여러 사용자가 동시에 문서를 편집하고 변경 사항을 실시간으로 볼 수 있는 기능을 구현할 수 있습니다. 대표적인 케이스가 구글 Doc입니다.

채팅 로직을 이용해서 GPS 정보를 실시간 주고 받으면서 위치정보 공유하는 위치공유채팅을 할 수도 있습니다. (웨어라유: https://choonzang.com/geo )

결론

Flask와 Flask-SocketIO를 사용하면 실시간 웹 애플리케이션을 비교적 쉽게 구현할 수 있습니다. 채팅, 알림, 라이브 업데이트 등 다양한 실시간 기능을 통해 사용자 경험을 크게 향상시킬 수 있습니다.

웹소켓 기술은 현대 웹 애플리케이션에서 점점 더 중요해지고 있으며, Flask와 같은 가벼운 프레임워크에서도 이러한 기능을 쉽게 구현할 수 있다는 것은 큰 장점입니다. 이 글에서 소개한 기본 개념과 예제를 바탕으로 여러분만의 실시간 애플리케이션을 개발해 보시기 바랍니다.


게시됨

카테고리

작성자

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다