WebSocket 프로토콜과 실시간 통신 구현

WebSocket 기초와 실시간 네트워크 통신

WebSocket은 TCP 기반의 양방향(full-duplex) 실시간 통신을 가능하게 하는 프로토콜입니다. HTML5 표준에 포함되며, 브라우저와 서버 간 지속적인 연결을 유지하면서 데이터를 주고받을 수 있습니다.

1. 서버 시작


import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 8002))
server_socket.listen(5)

# 클라이언트 연결 대기
client_socket, addr = server_socket.accept()

소켓 서버가 시작되면 클라이언트의 연결을 기다립니다. 연결이 수립되면 데이터 송수신이 가능해집니다.

2. 클라이언트 연결


var socket = new WebSocket("ws://127.0.0.1:8002/chat");

JavaScript를 사용한 클라이언트는 WebSocket 객체를 생성하여 서버에 연결합니다. 연결 시 '핸드셰이크' 과정을 거쳐 정상적인 WebSocket 연결을 확인합니다.

3. 핸드셰이크 과정


import socket
import hashlib
import base64

def generate_accept_key(key):
    magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    sha1_hash = hashlib.sha1((key + magic).encode()).digest()
    return base64.b64encode(sha1_hash).decode()

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 8002))
server_socket.listen(5)

client, addr = server_socket.accept()
request = client.recv(1024).decode()
key = extract_key_from_request(request)  # 요청에서 Sec-WebSocket-Key 추출

accept_key = generate_accept_key(key)
response = (
    "HTTP/1.1 101 Switching Protocols\r\n"
    "Upgrade: websocket\r\n"
    "Connection: Upgrade\r\n"
    f"Sec-WebSocket-Accept: {accept_key}\r\n"
    "\r\n"
)
client.send(response.encode())

클라이언트는 HTTP 요청을 보내 연결을 시작하고, 서버는 이 요청에 대해 핸드셰이크 응답을 반환합니다. 이 과정에서 Sec-WebSocket-Key를 추출하고 암호화하여 유효성을 검증합니다.

4. 데이터 송수신

연결이 완료된 후 클라이언트와 서버는 데이터를 주고받을 수 있습니다. WebSocket은 프레임 기반 통신을 사용하므로 수신된 바이트를 파싱하여 실제 데이터를 추출해야 합니다.

데이터 수신 (서버 측)


def parse_websocket_frame(data):
    second_byte = data[1]
    payload_length = second_byte & 127

    if payload_length == 126:
        mask = data[4:8]
        payload = data[8:]
    elif payload_length == 127:
        mask = data[10:14]
        payload = data[14:]
    else:
        mask = data[2:6]
        payload = data[6:]

    decoded = bytearray()
    for i in range(len(payload)):
        decoded.append(payload[i] ^ mask[i % 4])
    return decoded.decode('utf-8')

received = client.recv(8096)
message = parse_websocket_frame(received)
print("받은 메시지:", message)

데이터 송신 (서버 측)


def send_websocket_message(conn, text):
    header = b"\x81"
    length = len(text)
    
    if length < 126:
        header += bytes([length])
    elif length <= 0xFFFF:
        header += struct.pack("!BH", 126, length)
    else:
        header += struct.pack("!BQ", 127, length)
    
    conn.send(header + text.encode())

5. 전체 예제

간단한 채팅 서버 및 클라이언트 예제입니다. 서버는 소켓 기반으로 구현되며 클라이언트는 HTML과 JavaScript를 사용합니다.

Python 서버 코드


def run_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 8003))
    sock.listen(5)
    
    conn, addr = sock.accept()
    # 핸드셰이크 과정 생략
    
    while True:
        data = conn.recv(8096)
        message = parse_websocket_frame(data)
        if message:
            print("받은 메시지:", message)
            send_websocket_message(conn, f"서버 응답: {message}")

HTML 클라이언트 코드


<script>
const socket = new WebSocket("ws://127.0.0.1:8003/chat");

socket.onopen = () => console.log("연결됨");
socket.onmessage = event => console.log("받은 메시지:", event.data);
socket.send("안녕하세요");
</script>

6. Tornado 프레임워크를 활용한 채팅 애플리케이션

Tornado는 WebSocket을 내장 지원하는 Python 웹 프레임워크입니다. 복잡한 네트워킹 로직을 숨기고 애플리케이션 로직에 집중할 수 있게 해줍니다.

서버 코드 (Tornado)


import tornado.ioloop
import tornado.web
import tornado.websocket

class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    clients = set()

    def open(self):
        self.clients.add(self)
        print("클라이언트 연결됨")

    def on_message(self, message):
        for client in self.clients:
            if client is not self:
                client.write_message(message)

    def on_close(self):
        self.clients.remove(self)
        print("클라이언트 연결 종료")

def make_app():
    return tornado.web.Application([
        (r"/chat", ChatSocketHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

클라이언트 코드 (JavaScript)


const chatSocket = new WebSocket("ws://localhost:8888/chat");

chatSocket.onmessage = function(event) {
    console.log("메시지 수신:", event.data);
};

function sendMessage() {
    const input = document.getElementById("txt");
    chatSocket.send(input.value);
    input.value = "";
}

태그: websocket python socket Tornado JavaScript

9월 3일 03:26에 게시됨