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 = "";
}