Python Flask - 실시간 채팅 기능 구현하기 (양방향 소켓 통신, FlaskSocketIO)

개요

<파이썬 플라스크를 이용하여 실시간 채팅 기능을 구현한 웹 페이지 예시 화면>

 

파이썬의 플라스크로 소켓을 이용하여 실시간 채팅 기능을 구현해 보자.

이 글은 소켓에 관련된 컴퓨터 공학 관련 내용을 설명하는 글이 아니다.

오로지 '실시간 채팅'이라는 기능만을 중점으로 두려 한다.

가능한 컴퓨터 전공 지식은 다 걷어내고,

최대한 쉽고 간단하게 실시간으로 메시지를 주고받고 하는 방법을 다루도록 한다.

(솔직히 기능 구현하려고 검색했지 진부한 CS 지식 보려고 온 건 아니잖는가)

클라이언트 플랫폼은 웹 페이지를 기준으로 설명한다.

(부연 설명이 오히려 길어질 것으로 판단되어 제일 구현이 간단한 웹 페이지로 진행하였다.)

  • 서버 : Python Flask
  • 클라이언트 : 웹 페이지 이용 (html / JavaScript)

 

당연한 이야기지만...

이 글은 플라스크를 기본적으로 다룰 줄 알아야 이해가 쉽다.

 

아래의 예시 이미지는 테스트용 채팅 서버를 열고,

친구들에게 접속시켜서 대화를 진행한 화면이다 :)

  • 웹 페이지로 구성된 실시간 채팅 예시

<외부에서 서버에 접속해서 실시간 채팅을 진행하는 예시 화면>

 

포스팅에서 설명하는 샘플 프로젝트는 깃허브에서 확인 및 다운로드 가능하다.

 

GitHub - luvris2/python-example

Contribute to luvris2/python-example development by creating an account on GitHub.

github.com


개념 간단히 알아가기

파이썬 플라스크란? (Python Flask)

파이썬(Python)은 프로그래밍 언어 중 하나이다. 파이썬을 이용하여 개발할 수 있다.

플라스크(Flask)는 파이썬에서 사용되는 하나의 웹 프레임워크로 쉽게 말해 웹 서버 및 웹 페이지를 만들 수 있게 해 준다.

 

소켓이란?

네트워크를 통해 데이터를 교환할 때 사용되는 것이 소켓이다.

이 소켓을 이용하여 프로그램 간 서로 데이터를 주고받는다.

 

왜 소켓으로 구현하여야 할까?

서로 데이터를 주고받고 '실시간'으로 상호작용하기 위해서이다.

통신 방식은 크게 HTTP 통신과 소켓 통신으로 나뉜다.

HTTP 통신은 요청-응답 형식의 단방향 통신이고,

소켓 통신은 서로 상호작용이 가능한 양방향 통신이다.

 

요약

즉, 실시간 채팅 기능을 구현하려면 우선 유저가 메시지를 주고받을 서버가 필요하다.

이 서버를 파이썬을 통해 개발하고, 플라스크를 통해 서버가 운영된다.

그리고 각각의 클라이언트(사용자)들은

서버(이해하기 쉽게 소켓 서버라 칭하겠다)를 통해 메시지를 주고받기 위해 소켓을 이용해야 한다.

 

소켓 라이브러리 메서드, emit vs send 차이는?

서버와 클라이언트가 서버 간 실시간 양방향 통신을 하기 위해 기능을 수행하는 메서드가 필요하다.

소켓 전송 메서드는 'emit' 과 'send' 가 있다.

포스팅에서는 소켓 관련 데이터 전송으로 emit 메서드를 사용한다.

emit 은 특정 이벤트가 발생하면 데이터를 전송하는 데 사용하고,

send 는 단순히 메시지를 전송하는 데 사용한다.

포스팅에서는 'message' 라는 이벤트를 이용하여 전송하였고,

이해를 돕기 위해, 구분을 하기 위해서 emit 메서드를 사용하였다.

메서드는 개인에 따라 편한 것을 사용해면 된다.


필수 환경 구성

플라스크를 사용하므로 당연히 파이썬이 설치되어 있어야 한다.

추가적으로 실시간 채팅을 구현하려면 소켓 관련 라이브러리(flask_socketio)도 필요하다.

 

Python 다운로드

 

[ 파이썬 관련 필요 라이브러리 설치 ] 

  • Flask
pip install flask

 

  • Flask SocketIO
pip install flask_socketio

Flask 소켓 서버 구성

소켓 서버가 될 메인 파일을 생성하고 서버를 구성해 보자.

 

초기화

우선 필요한 라이브러리를 호출한다.

from flask import Flask, render_template
from flask_socketio import SocketIO

 

플라스크 애플리케이션을 생성하고 소캣 IO를 연결한다.

app = Flask(__name__)
socketio = SocketIO(app)

초기 접속 페이지 설정

서버에 접속할 때 기본 구성을 설정한다.

서버 접속 시 'index.html' 이라는 HTML 페이지를 보여주도록 설정하였다.

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

이벤트 정의 (기능 정의)

클라이언트에서 발생하는 이벤트를 처리할 함수를 설정한다.

사용자가 메시지를 전송하면, 전송된 메시지를 처리하는 기능을 정의해야 한다.

  • @socketio.on('event') : 이벤트를 처리할 함수 선언
  • def functions(args) : 이벤트가 발생하면 호출되는 함수
  • socketio.emit('event', args) : 서버가 이벤트를 가진 클라이언트에게 데이터를 전송

간단히 설명하면 클라이언트에서 'message'의 이벤트를 발생시켰다면,

handle_message 라는 함수가 호출되어 클라이언트에서 전송한 메시지 내용이 data 라는 파라미터로 전달된다.

그 후 소켓 서버는 받은 메시지 내용(data)을 모든 클라이언트들에게 실시간으로 전송한다.

@socketio.on('message')
def handle_message(data):
    print('Received message:', data)
    socketio.emit('message', data)

플라스크 소켓 서버 실행

플라스크 소켓 서버를 실행한다.

  • app : 플라스 애플리케이션을 의미한다.
  • debug : 디버그 모드를 설정한다. 디버그 모드가 활성화되었을 경우, 코드가 수정되면 자동으로 서버가 재시작되고 콘솔에 관련 메시지가 출력된다.
  • host : 소켓 서버가 실행되는 호스트를 지정한다.
  • port : 소켓 서버가 실행되는 포트 번호를 지정한다.
if __name__ == '__main__':
    socketio.run(app, debug=True)

 

나는 외부에서 접속을 하기 위해 호스트와 포트를 지정하였다.

# socketio.run(app, host='192.168.0.78', port=4000, debug=True)

플라스크 서버 구성 전체 코드 보기

더보기
from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
socketio = SocketIO(app)

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

@socketio.on('message')
def handle_message(data):
    print('Received message:', data)
    socketio.emit('message', data)

if __name__ == '__main__':
    socketio.run(app, host='192.168.0.46', port=4000, debug=True)

반응형

클라이언트 웹 페이지 구성

사용자가 메시지를 작성하고 소켓 서버로 작성한 메시지를 보내는 클라이언트를 구성해 보자.

포스팅에서 설명하는 내용이 주객전도되지 않도록 HTML, 자바스크립트에 대한 설명은 생략한다.

 

HTML 파일 생성

Python Flask 프로젝트 디렉토리 내에 templates 폴더를 생성한다.

플라스크는 기본적으로 템플릿 폴더에서 파일을 찾는다.

즉, Flask 애플리케이션(app 객체)은 기본적으로 'templates' 폴더가 경로로 설정되어 있다는 의미이다.

 

다른 경로로 변경하는 방법은 flask app 객체를 생성할 때 경로를 지정하면 된다.

더보기

현재 경로 확인을 위해 os 내장 모듈을 호출해야 한다.

import os
# 프로젝트 실행 경로를 템플릿 기본 경로로 지정
app = Flask(__name__, template_folder=os.getcwd())

 

포스팅에서는 그냥 진행하도록 하겠다.

<플라스크 프로젝트 내 index.html 파일 생성 예시>


HTML 요소 구성 (태그)

<HTML 태그 구성 예시>

 

HTML 요소를 구성하는 것은 개개인마다 다르므로 이 부분은 자신이 보기 좋게 꾸며서 하면 된다.

이 글은 기능 구현과 설명을 중심으로 하기 때문에 최대한 간단히 하였다.

<!-- 메시지 내용이 추가 되는 영역 -->
<div id="message-container"></div>

<!-- 유저명과 메시지를 작성하고 데이터를 보내는 요소 -->
<form id="messageForm">
    <input id="formUser" type="text" style=width:50px; >
    <input id="formMessage">
    <button>전송</button>
</form>

소켓 서버 연결

소켓 서버를 이용하려면 우선 소켓 서버를 연결해야 한다.

(소켓을 사용하기 위해 소켓 IO 라이브러리를 이용한다.)

<!-- 소켓IO 라이브러리 추가 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.1.3/socket.io.js"></script>

<script>
    // 소켓 서버 연결
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    
    // 생략
</script>

이벤트 정의 (기능 정의) - 메시지 보내기

클라이언트에서 서버로 작성한 메시지를 보내야 한다.

위에서 연결한 소켓 서버 객체에 .emit 메서드를 호출하여 내용을 전달하면 된다.

<script>
    // 소켓 서버 연결
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    
    // 전송 버튼을 누를 경우 유저 이름과 메시지 내용 소켓 서버로 전달
    document.querySelector('#messageForm').onsubmit = function () {
        var userName = document.getElementById('formUser');
        var userMessage = document.getElementById('formMessage');

        let userInfo = userName.value;
        let messageInfo = userMessage.value;

        // 메시지 데이터 보내기
        socket.emit('message', {'user': userInfo, 'message' : messageInfo} );

        // 메시지 입력 내용 초기화
        userMessage.value = "";

        return false;
    };
</script>

이벤트 정의 (기능 정의) - 메시지 받기

이번엔 소켓 서버에서 이벤트를 발생시키면 처리할 기능을 정의해야 한다.

사용자가 메시지를 전송하면, 소켓 서버는 모든 클라이언트에게 메시지의 내용을 다시 전달한다.

모든 클라이언트에게 전달된 내용을 받아 웹 페이지에 내용을 표시해야 한다.

 

받은 데이터는 위에서 정의한 메시지 보내기에서 키:밸류 타입으로 값을 전달하였기 때문에

받을 때의 데이터는 전송한 키 값 밸류에 맞는 속성을 사용하여야 한다.

  • 보낼 때 >>> 'user' : 내용
  • 받을 때 >>> .user
<script>
    // 소켓 서버 연결
    var socket = io.connect('http://' + document.domain + ':' + location.port);

    // 소켓 서버 이벤트 처리
    socket.on('message', function (data) {
        var userName = document.getElementById('formUser');
        var contentSpan = document.getElementById('message-container');
        var content = document.createElement('div');
        content.appendChild(document.createTextNode(data.user + " : " + data.message));
        contentSpan.appendChild(content);
    });
</script>

클라이언트 페이지 전체 소스 코드 보기

더보기
<!-- 소켓 통신 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.1.3/socket.io.js"></script>
<script>
    // 소켓 서버 연결
    var socket = io.connect('http://' + document.domain + ':' + location.port);

    // 소켓 서버 이벤트 처리
    socket.on('message', function (data) {
        var userName = document.getElementById('formUser');
        var contentSpan = document.getElementById('message-container');
        var content = document.createElement('div');
        content.appendChild(document.createTextNode(data.user + " : " + data.message));
        contentSpan.appendChild(content);
    });

    // 전송 버튼을 누를 경우 유저 이름과 메시지 내용 소켓 서버로 전달
    document.querySelector('#messageForm').onsubmit = function () {
        var userName = document.getElementById('formUser');
        var userMessage = document.getElementById('formMessage');

        let userInfo = userName.value;
        let messageInfo = userMessage.value;

        // 메시지 데이터 보내기
        socket.send( {'user': userInfo, 'message' : messageInfo} );

        // 메시지 입력 내용 초기화
        userMessage.value = "";

        return false;
    };
</script>

기능 확인

플라스크 서버 실행 (서비스 시작)

파이썬 플라스크 프로젝트 경로 내에서 작성한 메인파일을 실행하면된다.

포스팅에서 작성한 메인 파일의 이름은 main.py 이므로 해당 파일 이름으로 예를 들어보겠다.

  • 터미널에서 프로젝트 경로의 메인 파일 실행
python main.py

웹 서버 접속 (클라이언트 페이지)

기본적으로 실행되는 로컬 주소는 http://127.0.0.1:5000 이다.

5000은 플라스크의 기본 포트 번호이다.

브라우저에서 해당 주소로 접속을 해보자.

<로컬 기본 주소로 접속한 예시 화면>

 

개요에서는 외부 접속을 허용하도록 공인 IP로 포트를 열어서 개방한 서버에 지인들을 접속시켜서 테스트를 진행하였다.

블로그에서는 아이피 노출로 인한 보안이 문제 될 수 있을 수도 있어서 해당 내용은 따로 기재하지 않았다.

외부 접속에 대한 내용은 아래의 포스팅을 참고하면 된다.

MySQL 외부 접속 방법을 다룬 것인데 결국 모든 외부 접속 방법은 같다.


참고