#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MRCP 1.0 서버 에뮬레이터 + 클라이언트 테스트
실제 MRCP 서버를 시뮬레이션하여 클라이언트 테스트 가능
"""
import socket
import time
import threading
import random
import re
from enum import Enum
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
class RequestState(Enum):
"""MRCP 요청 상태"""
IDLE = "IDLE"
IN_PROGRESS = "IN-PROGRESS"
COMPLETE = "COMPLETE"
FAILED = "FAILED"
class CompletionCause(Enum):
"""완료 원인 코드"""
NORMAL = "000 normal"
TTS_ERROR = "006 tts-error"
NO_INPUT_TIMEOUT = "001 no-input-timeout"
CONNECTION_ERROR = "999 connection-error"
@dataclass
class MRCPRequest:
"""MRCP 요청 구조"""
method: str
url: str
cseq: int
content_type: str
content_length: int
headers: Dict[str, str]
body: str
def __str__(self):
"""RTSP 형식의 요청 패킷 생성"""
lines = [f"{self.method} {self.url} RTSP/1.0"]
lines.append(f"CSeq: {self.cseq}")
lines.append(f"Content-Type: {self.content_type}")
lines.append(f"Content-Length: {self.content_length}")
for key, value in self.headers.items():
lines.append(f"{key}: {value}")
lines.append("") # 빈 줄
lines.append(self.body)
return "\r\n".join(lines)
@dataclass
class MRCPResponse:
"""MRCP 응답 구조"""
status_code: int
status_message: str
cseq: int
content_length: int
request_state: RequestState
completion_cause: Optional[CompletionCause] = None
headers: Optional[Dict[str, str]] = None
body: str = ""
def __str__(self):
"""RTSP 형식의 응답 패킷 생성"""
lines = [f"RTSP/1.0 {self.status_code} {self.status_message}"]
lines.append(f"CSeq: {self.cseq}")
lines.append(f"Content-Length: {self.content_length}")
lines.append(f"Request-State: {self.request_state.value}")
if self.completion_cause:
lines.append(f"Completion-Cause: {self.completion_cause.value}")
if self.headers:
for key, value in self.headers.items():
lines.append(f"{key}: {value}")
lines.append("") # 빈 줄
if self.body:
lines.append(self.body)
return "\r\n".join(lines)
@classmethod
def parse(cls, response_text: str) -> 'MRCPResponse':
"""RTSP 응답 텍스트를 파싱"""
lines = response_text.strip().split('\r\n')
if not lines:
raise ValueError("빈 응답")
# 첫 번째 줄: RTSP/1.0 200 OK
status_line = lines[0]
status_parts = status_line.split(' ', 2)
if len(status_parts) < 2:
raise ValueError(f"잘못된 상태 라인: {status_line}")
status_code = int(status_parts[1])
status_message = status_parts[2] if len(status_parts) > 2 else ""
# 헤더 파싱
headers = {}
body_start = 0
cseq = 0
content_length = 0
request_state = RequestState.IDLE
completion_cause = None
for i, line in enumerate(lines[1:], 1):
if line == '': # 빈 줄이면 헤더 끝
body_start = i + 1
break
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
headers[key] = value
# 중요 헤더들 추출
if key.lower() == 'cseq':
cseq = int(value)
elif key.lower() == 'content-length':
content_length = int(value)
elif key.lower() == 'request-state':
try:
request_state = RequestState(value)
except ValueError:
request_state = RequestState.IDLE
elif key.lower() == 'completion-cause':
try:
completion_cause = CompletionCause(value)
except ValueError:
completion_cause = None
# 바디 추출
body = '\r\n'.join(lines[body_start:]) if body_start < len(lines) else ""
return cls(
status_code=status_code,
status_message=status_message,
cseq=cseq,
content_length=content_length,
request_state=request_state,
completion_cause=completion_cause,
headers=headers,
body=body
)
class MRCPServerEmulator:
"""MRCP 1.0 서버 에뮬레이터"""
def __init__(self, host: str = "127.0.0.1", port: int = 8000):
self.host = host
self.port = port
self.server_socket: Optional[socket.socket] = None
self.is_running = False
self.clients = {} # 클라이언트 연결 관리
def start(self):
"""서버 시작"""
try:
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
self.is_running = True
print(f"[SERVER] MRCP 서버 시작: {self.host}:{self.port}")
# 클라이언트 연결 대기 스레드
accept_thread = threading.Thread(target=self._accept_connections)
accept_thread.daemon = True
accept_thread.start()
return True
except Exception as e:
print(f"[SERVER] 서버 시작 실패: {e}")
return False
def stop(self):
"""서버 중지"""
self.is_running = False
if self.server_socket:
try:
self.server_socket.close()
except:
pass
finally:
self.server_socket = None
print(f"[SERVER] MRCP 서버 중지")
def _accept_connections(self):
"""클라이언트 연결 수락"""
while self.is_running:
try:
if not self.server_socket:
break
client_socket, client_address = self.server_socket.accept()
print(f"[SERVER] 클라이언트 연결: {client_address}")
# 클라이언트 처리 스레드
client_thread = threading.Thread(
target=self._handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True
client_thread.start()
except Exception as e:
if self.is_running: # 서버가 실행 중일 때만 오류 출력
print(f"[SERVER] 클라이언트 수락 오류: {e}")
break
def _handle_client(self, client_socket: socket.socket, client_address: Tuple[str, int]):
"""개별 클라이언트 처리"""
client_id = f"{client_address[0]}:{client_address[1]}"
try:
client_socket.settimeout(30) # 30초 타임아웃
while self.is_running:
# 요청 수신
request_data = b""
while True:
try:
chunk = client_socket.recv(4096)
if not chunk:
break
request_data += chunk
# RTSP 요청 끝 확인
request_text = request_data.decode('utf-8', errors='ignore')
if '\r\n\r\n' in request_text:
# Content-Length 체크
if 'Content-Length:' in request_text:
try:
headers_end = request_text.find('\r\n\r\n')
headers_part = request_text[:headers_end]
content_length = 0
for line in headers_part.split('\r\n'):
if line.lower().startswith('content-length:'):
content_length = int(line.split(':', 1)[1].strip())
break
body_start = headers_end + 4
if len(request_text[body_start:]) >= content_length:
break
except (ValueError, IndexError):
break
else:
break
except socket.timeout:
print(f"[SERVER] 클라이언트 {client_id} 수신 타임아웃")
return
if not request_data:
break
request_text = request_data.decode('utf-8', errors='ignore')
print(f"\n[SERVER] 클라이언트 {client_id}로부터 요청 수신:")
print("-" * 40)
print(request_text)
print("-" * 40)
# 요청 처리 및 응답 생성
response = self._process_request(request_text, client_id)
if response:
response_text = str(response)
print(f"\n[SERVER] 클라이언트 {client_id}에게 응답 전송:")
print("-" * 40)
print(response_text)
print("-" * 40)
# 응답 전송
try:
client_socket.sendall(response_text.encode('utf-8'))
except Exception as send_error:
print(f"[SERVER] 응답 전송 실패: {send_error}")
break
except Exception as e:
print(f"[SERVER] 클라이언트 {client_id} 처리 오류: {e}")
finally:
try:
client_socket.close()
except:
pass
print(f"[SERVER] 클라이언트 {client_id} 연결 해제")
def _process_request(self, request_text: str, client_id: str) -> Optional[MRCPResponse]:
"""RTSP/MRCP 요청 처리"""
lines = request_text.strip().split('\r\n')
if not lines:
return None
# 첫 번째 줄 파싱: METHOD URL RTSP/1.0
first_line = lines[0]
parts = first_line.split(' ')
if len(parts) < 3:
return None
method = parts[0]
url = parts[1]
protocol = parts[2]
# 헤더 파싱
headers = {}
cseq = 0
body_start = 0
for i, line in enumerate(lines[1:], 1):
if line == '': # 빈 줄이면 헤더 끝
body_start = i + 1
break
if ':' in line:
key, value = line.split(':', 1)
headers[key.strip()] = value.strip()
if key.strip().lower() == 'cseq':
cseq = int(value.strip())
# 바디 추출
body = '\r\n'.join(lines[body_start:]) if body_start < len(lines) else ""
# 메소드별 처리
if method == "ANNOUNCE":
return self._handle_announce(cseq, body, client_id)
elif method == "SETUP":
return self._handle_setup(cseq, url, client_id)
elif method == "DESCRIBE":
return self._handle_describe(cseq, client_id)
else:
# 지원하지 않는 메소드
return MRCPResponse(
status_code=405,
status_message="Method Not Allowed",
cseq=cseq,
content_length=0,
request_state=RequestState.FAILED
)
def _handle_announce(self, cseq: int, body: str, client_id: str) -> MRCPResponse:
"""ANNOUNCE 요청 처리 (MRCP 메시지 포함)"""
print(f"[SERVER] ANNOUNCE 처리 - 클라이언트: {client_id}")
# MRCP 메시지 파싱
if "SPEAK" in body:
return self._handle_tts_speak(cseq, body, client_id)
else:
# 알 수 없는 MRCP 요청
return MRCPResponse(
status_code=400,
status_message="Bad Request",
cseq=cseq,
content_length=0,
request_state=RequestState.FAILED,
completion_cause=CompletionCause.TTS_ERROR
)
def _handle_tts_speak(self, cseq: int, mrcp_body: str, client_id: str) -> MRCPResponse:
"""TTS SPEAK 요청 처리"""
print(f"[SERVER] TTS SPEAK 처리 - 클라이언트: {client_id}")
# MRCP 바디에서 텍스트 추출
lines = mrcp_body.strip().split('\r\n')
text_content = ""
voice_info = {}
# MRCP 헤더와 바디 파싱
in_headers = True
for line in lines:
if in_headers:
if line == '':
in_headers = False
continue
elif ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
if key == "Voice-Gender":
voice_info['gender'] = value
elif key == "Speech-Language":
voice_info['language'] = value
elif key == "Voice-Age":
voice_info['age'] = value
else:
if line.strip():
text_content = line.strip()
break
print(f"[SERVER] TTS 요청 내용:")
print(f" - 텍스트: {text_content}")
print(f" - 음성 정보: {voice_info}")
# TTS 처리 시뮬레이션
processing_time = random.uniform(1, 3) # 1-3초 처리 시간
success_rate = 0.9 # 90% 성공률
# 처리 시간 시뮬레이션
time.sleep(processing_time)
# 성공/실패 결정
if random.random() < success_rate:
# 성공 시나리오
response = MRCPResponse(
status_code=200,
status_message="OK",
cseq=cseq,
content_length=0,
request_state=RequestState.COMPLETE,
completion_cause=CompletionCause.NORMAL,
headers={
"Speech-Marker": f"mark1={int(processing_time * 1000)}",
"Content-Duration": f"{processing_time:.1f}",
"Voice-Used": voice_info.get('gender', 'female')
}
)
else:
# 실패 시나리오
response = MRCPResponse(
status_code=500,
status_message="Internal Server Error",
cseq=cseq,
content_length=0,
request_state=RequestState.FAILED,
completion_cause=CompletionCause.TTS_ERROR
)
return response
def _handle_setup(self, cseq: int, url: str, client_id: str) -> MRCPResponse:
"""SETUP 요청 처리"""
print(f"[SERVER] SETUP 처리 - 클라이언트: {client_id}")
# 세션 ID 생성
session_id = f"session_{int(time.time())}_{random.randint(1000, 9999)}"
return MRCPResponse(
status_code=200,
status_message="OK",
cseq=cseq,
content_length=0,
request_state=RequestState.COMPLETE,
headers={
"Session": session_id,
"Transport": "RTP/AVP;unicast;server_port=8000-8001"
}
)
def _handle_describe(self, cseq: int, client_id: str) -> MRCPResponse:
"""DESCRIBE 요청 처리"""
print(f"[SERVER] DESCRIBE 처리 - 클라이언트: {client_id}")
# SDP 형태의 서버 능력 정보
sdp_body = """v=0
o=- 123456789 123456789 IN IP4 127.0.0.1
s=MRCP Server
c=IN IP4 127.0.0.1
t=0 0
m=application 9 TCP/MRCPv1 1
a=resource:speechsynth
a=cmid:1
"""
return MRCPResponse(
status_code=200,
status_message="OK",
cseq=cseq,
content_length=len(sdp_body.encode('utf-8')),
request_state=RequestState.COMPLETE,
headers={
"Content-Type": "application/sdp"
},
body=sdp_body
)
class MRCPTTSClient:
"""MRCP 1.0 TTS 전용 실제 클라이언트"""
def __init__(self, server_host: str = "127.0.0.1", server_port: int = 554, timeout: int = 30):
self.server_host = server_host
self.server_port = server_port
self.timeout = timeout
self.session_id = f"session_{int(time.time())}"
self.cseq = 0
self.socket: Optional[socket.socket] = None
self.is_connected = False
def connect(self) -> bool:
"""실제 MRCP 서버에 연결"""
try:
print(f"[CLIENT] MRCP 서버 연결 시도: {self.server_host}:{self.server_port}")
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(self.timeout)
self.socket.connect((self.server_host, self.server_port))
self.is_connected = True
print(f"[CLIENT] 서버 연결 성공! 세션 ID: {self.session_id}")
return True
except socket.timeout:
print(f"[CLIENT] 연결 타임아웃: {self.server_host}:{self.server_port}")
return False
except ConnectionRefusedError:
print(f"[CLIENT] 연결 거부됨: {self.server_host}:{self.server_port} (서버가 실행 중인지 확인)")
return False
except socket.gaierror:
print(f"[CLIENT] 호스트명 해석 실패: {self.server_host}")
return False
except Exception as e:
print(f"[CLIENT] 연결 실패: {e}")
return False
def disconnect(self):
"""서버 연결 해제"""
if self.socket:
try:
self.socket.close()
except:
pass
finally:
self.socket = None
self.is_connected = False
print(f"[CLIENT] 서버 연결 해제")
def _get_next_cseq(self) -> int:
"""다음 시퀀스 번호 생성"""
self.cseq += 1
return self.cseq
def _send_request(self, request: str) -> str:
"""RTSP 요청 전송 및 응답 수신"""
if not self.is_connected or not self.socket:
raise ConnectionError("서버에 연결되지 않았습니다.")
try:
# 요청 전송
request_bytes = request.encode('utf-8')
self.socket.sendall(request_bytes)
print(f"\n[CLIENT] TTS 요청 전송:")
print("=" * 50)
print(request)
print("=" * 50)
# 응답 수신
response_data = b""
while True:
try:
chunk = self.socket.recv(4096)
if not chunk:
break
response_data += chunk
# RTSP 응답 끝 확인
response_text = response_data.decode('utf-8', errors='ignore')
if '\r\n\r\n' in response_text:
# Content-Length 체크
if 'Content-Length:' in response_text:
try:
headers_end = response_text.find('\r\n\r\n')
headers_part = response_text[:headers_end]
content_length = 0
for line in headers_part.split('\r\n'):
if line.lower().startswith('content-length:'):
content_length = int(line.split(':', 1)[1].strip())
break
body_start = headers_end + 4
if len(response_text[body_start:]) >= content_length:
break
except (ValueError, IndexError):
# Content-Length 파싱 실패 시 응답 종료로 간주
break
else:
break
except socket.timeout:
print("[CLIENT] 응답 수신 타임아웃")
break
response_text = response_data.decode('utf-8', errors='ignore')
print(f"\n[SERVER] TTS 응답 수신:")
print("-" * 50)
print(response_text if response_text else "응답 없음")
print("-" * 50)
return response_text
except Exception as e:
raise ConnectionError(f"요청 전송 실패: {e}")
def speak(self, text: str, voice_gender: str = "female",
language: str = "ko-KR", voice_age: str = "25") -> List[MRCPResponse]:
"""TTS SPEAK 요청 - 실제 서버와 통신"""
if not self.is_connected:
raise ConnectionError("서버에 연결되지 않았습니다.")
responses = []
try:
# MRCP SPEAK 요청 생성
url = f"rtsp://{self.server_host}:{self.server_port}/{self.session_id}"
cseq = self._get_next_cseq()
# MRCP 메시지 바디 생성
mrcp_body = f"""SPEAK {cseq} SPEAK 100\r
Voice-Gender: {voice_gender}\r
Voice-Age: {voice_age}\r
Speech-Language: {language}\r
Content-Type: text/plain\r
Content-Length: {len(text.encode('utf-8'))}\r
\r
{text}"""
# RTSP ANNOUNCE 요청 생성
announce_request = f"""ANNOUNCE {url} RTSP/1.0\r
CSeq: {cseq}\r
Content-Type: application/mrcp\r
Content-Length: {len(mrcp_body.encode('utf-8'))}\r
\r
{mrcp_body}"""
# 요청 전송 및 응답 수신
response_text = self._send_request(announce_request)
if response_text:
# 응답 파싱
try:
response = MRCPResponse.parse(response_text)
responses.append(response)
print(f"\n[SERVER] TTS 응답 분석:")
print(f"상태 코드: {response.status_code}")
print(f"상태 메시지: {response.status_message}")
print(f"요청 상태: {response.request_state.value}")
if response.completion_cause:
print(f"완료 원인: {response.completion_cause.value}")
# 성공/실패 판정
if response.status_code == 200:
if response.request_state == RequestState.COMPLETE:
if response.completion_cause == CompletionCause.NORMAL:
print("✅ TTS 성공! 음성이 생성되었습니다.")
else:
print(f"❌ TTS 완료되었지만 오류: {response.completion_cause}")
elif response.request_state == RequestState.IN_PROGRESS:
print("🔄 TTS 처리 중...")
else:
print(f"⚠️ TTS 상태: {response.request_state.value}")
else:
print(f"❌ TTS 요청 실패: {response.status_code} {response.status_message}")
except Exception as parse_error:
print(f"❌ 응답 파싱 실패: {parse_error}")
error_response = MRCPResponse(
status_code=500,
status_message="Parse Error",
cseq=cseq,
content_length=0,
request_state=RequestState.FAILED,
completion_cause=CompletionCause.TTS_ERROR
)
responses.append(error_response)
else:
print("❌ 서버로부터 응답을 받지 못했습니다.")
no_response = MRCPResponse(
status_code=408,
status_message="No Response",
cseq=cseq,
content_length=0,
request_state=RequestState.FAILED,
completion_cause=CompletionCause.CONNECTION_ERROR
)
responses.append(no_response)
except Exception as e:
print(f"❌ TTS 요청 중 오류 발생: {e}")
error_response = MRCPResponse(
status_code=500,
status_message="Internal Error",
cseq=self.cseq,
content_length=0,
request_state=RequestState.FAILED,
completion_cause=CompletionCause.CONNECTION_ERROR
)
responses.append(error_response)
return responses
def demo_server_client_test():
"""서버와 클라이언트 통합 테스트"""
print("=" * 60)
print("MRCP 1.0 서버-클라이언트 통합 테스트")
print("=" * 60)
# 서버 시작
server = MRCPServerEmulator("127.0.0.1", 8000)
if not server.start():
print("❌ 서버 시작 실패")
return
try:
# 서버가 완전히 시작될 때까지 잠시 대기
time.sleep(1)
# 클라이언트 테스트
print("\n" + "=" * 40)
print("클라이언트 테스트 시작")
print("=" * 40)
client = MRCPTTSClient("127.0.0.1", 8000, timeout=10)
try:
# 1. 서버 연결
if not client.connect():
print("❌ 클라이언트 연결 실패")
return
# 2. 첫 번째 TTS 테스트
print("\n[테스트 1] 기본 TTS 테스트")
responses1 = client.speak(
text="안녕하세요, MRCP 서버 에뮬레이터 테스트입니다.",
voice_gender="female",
language="ko-KR"
)
if responses1 and responses1[-1].status_code == 200:
print("✅ 테스트 1 성공!")
else:
print("❌ 테스트 1 실패!")
time.sleep(1)
# 3. 두 번째 TTS 테스트 (짧은 텍스트)
print("\n[테스트 2] 짧은 텍스트 TTS 테스트")
responses2 = client.speak(
text="안녕",
voice_gender="male",
language="ko-KR"
)
if responses2 and responses2[-1].status_code == 200:
print("✅ 테스트 2 성공!")
else:
print("❌ 테스트 2 실패!")
time.sleep(1)
# 4. 긴 텍스트 TTS 테스트
print("\n[테스트 3] 긴 텍스트 TTS 테스트")
long_text = "이것은 긴 텍스트 테스트입니다. " * 10 # 긴 텍스트 생성
responses3 = client.speak(
text=long_text,
voice_gender="female",
language="ko-KR"
)
if responses3 and responses3[-1].status_code == 200:
print("✅ 테스트 3 성공!")
else:
print("❌ 테스트 3 실패!")
finally:
client.disconnect()
finally:
# 서버 중지
print("\n" + "=" * 40)
print("서버 중지 중...")
server.stop()
time.sleep(1)
print("\n" + "=" * 60)
print("통합 테스트 완료!")
print("=" * 60)
def demo_multiple_clients_test():
"""다중 클라이언트 동시 접속 테스트"""
print("\n" + "=" * 60)
print("MRCP 1.0 다중 클라이언트 테스트")
print("=" * 60)
# 서버 시작
server = MRCPServerEmulator("127.0.0.1", 8001) # 다른 포트 사용
if not server.start():
print("❌ 서버 시작 실패")
return
try:
time.sleep(1) # 서버 시작 대기
def client_worker(client_id: int):
"""개별 클라이언트 작업"""
client = MRCPTTSClient("127.0.0.1", 8001, timeout=15)
try:
if client.connect():
# 각 클라이언트마다 다른 텍스트로 TTS 요청
text = f"클라이언트 {client_id} TTS 테스트입니다."
responses = client.speak(
text=text,
voice_gender="female" if client_id % 2 == 0 else "male",
language="ko-KR"
)
if responses and responses[-1].status_code == 200:
print(f"✅ 클라이언트 {client_id} 성공!")
else:
print(f"❌ 클라이언트 {client_id} 실패!")
else:
print(f"❌ 클라이언트 {client_id} 연결 실패")
except Exception as e:
print(f"❌ 클라이언트 {client_id} 오류: {e}")
finally:
client.disconnect()
# 5개의 클라이언트 동시 실행
threads = []
for i in range(1, 6):
thread = threading.Thread(target=client_worker, args=(i,))
threads.append(thread)
thread.start()
# 모든 클라이언트 완료 대기
for thread in threads:
thread.join()
finally:
print("\n다중 클라이언트 테스트 완료, 서버 중지 중...")
server.stop()
time.sleep(1)
def demo_error_scenarios():
"""오류 시나리오 테스트"""
print("\n" + "=" * 60)
print("MRCP 1.0 오류 시나리오 테스트")
print("=" * 60)
server = MRCPServerEmulator("127.0.0.1", 8002) # 또 다른 포트
if not server.start():
print("❌ 서버 시작 실패")
return
try:
time.sleep(1)
client = MRCPTTSClient("127.0.0.1", 8002, timeout=5)
try:
if client.connect():
print("✅ 서버 연결 성공")
# 1. 빈 텍스트 테스트
print("\n[오류 테스트 1] 빈 텍스트")
responses1 = client.speak(text="")
print(f"결과: {responses1[-1].status_code if responses1 else 'No Response'}")
time.sleep(0.5)
# 2. 매우 긴 텍스트 테스트
print("\n[오류 테스트 2] 매우 긴 텍스트")
very_long_text = "매우 긴 텍스트입니다. " * 50
responses2 = client.speak(text=very_long_text)
print(f"결과: {responses2[-1].status_code if responses2 else 'No Response'}")
time.sleep(0.5)
# 3. 특수문자 테스트
print("\n[오류 테스트 3] 특수문자 포함 텍스트")
special_text = "특수문자: !@#$%^&*()_+[]{}|;':\",./<>?"
responses3 = client.speak(text=special_text)
print(f"결과: {responses3[-1].status_code if responses3 else 'No Response'}")
else:
print("❌ 서버 연결 실패")
finally:
client.disconnect()
time.sleep(1)
# 4. 서버 중지 후 연결 시도
print("\n[오류 테스트 4] 서버 중지 후 연결 시도")
server.stop()
time.sleep(1)
client2 = MRCPTTSClient("127.0.0.1", 8002, timeout=3)
if client2.connect():
print("❌ 예상과 다름: 서버가 중지되었는데 연결됨")
else:
print("✅ 예상대로: 서버 중지 후 연결 실패")
client2.disconnect()
except Exception as e:
print(f"❌ 오류 시나리오 테스트 중 예외: {e}")
server.stop()
def demo_performance_test():
"""성능 테스트"""
print("\n" + "=" * 60)
print("MRCP 1.0 성능 테스트")
print("=" * 60)
server = MRCPServerEmulator("127.0.0.1", 8003)
if not server.start():
print("❌ 서버 시작 실패")
return
try:
time.sleep(1)
client = MRCPTTSClient("127.0.0.1", 8003, timeout=30)
try:
if client.connect():
print("✅ 성능 테스트 서버 연결 성공")
# 연속 요청 성능 테스트
num_requests = 10
start_time = time.time()
success_count = 0
print(f"\n{num_requests}개의 연속 TTS 요청 테스트 시작...")
for i in range(1, num_requests + 1):
print(f" [{i}/{num_requests}] TTS 요청 전송...")
text = f"성능 테스트 {i}번째 요청입니다."
responses = client.speak(text=text)
if responses and responses[-1].status_code == 200:
success_count += 1
print(f" ✅ 요청 {i} 성공")
else:
print(f" ❌ 요청 {i} 실패")
# 요청 간 짧은 간격
time.sleep(0.2)
end_time = time.time()
total_time = end_time - start_time
print(f"\n📊 성능 테스트 결과:")
print(f" - 총 요청 수: {num_requests}")
print(f" - 성공 요청 수: {success_count}")
print(f" - 실패 요청 수: {num_requests - success_count}")
print(f" - 성공률: {(success_count / num_requests * 100):.1f}%")
print(f" - 총 소요 시간: {total_time:.2f}초")
print(f" - 평균 요청 시간: {(total_time / num_requests):.2f}초")
print(f" - 초당 처리량: {(num_requests / total_time):.2f} req/s")
else:
print("❌ 성능 테스트 서버 연결 실패")
finally:
client.disconnect()
finally:
print("\n성능 테스트 완료, 서버 중지 중...")
server.stop()
time.sleep(1)
def demo_connection_test():
"""기본 연결 테스트"""
print("\n" + "=" * 60)
print("MRCP 1.0 기본 연결 테스트")
print("=" * 60)
# 다양한 포트에서 서버 시작 테스트
test_ports = [8004, 8005, 8006]
for port in test_ports:
print(f"\n[테스트] 포트 {port}에서 서버 시작...")
server = MRCPServerEmulator("127.0.0.1", port)
if server.start():
print(f"✅ 포트 {port} 서버 시작 성공")
time.sleep(1)
# 클라이언트 연결 테스트
client = MRCPTTSClient("127.0.0.1", port, timeout=5)
if client.connect():
print(f"✅ 포트 {port} 클라이언트 연결 성공")
client.disconnect()
else:
print(f"❌ 포트 {port} 클라이언트 연결 실패")
server.stop()
time.sleep(1)
else:
print(f"❌ 포트 {port} 서버 시작 실패")
if __name__ == "__main__":
print("MRCP 1.0 서버 에뮬레이터 + 클라이언트 테스트")
print("실제 MRCP 서버를 시뮬레이션하여 클라이언트 코드를 테스트합니다.")
print()
try:
# 1. 기본 연결 테스트
demo_connection_test()
time.sleep(2)
# 2. 기본 서버-클라이언트 테스트
demo_server_client_test()
time.sleep(2)
# 3. 다중 클라이언트 테스트
demo_multiple_clients_test()
time.sleep(2)
# 4. 오류 시나리오 테스트
demo_error_scenarios()
time.sleep(2)
# 5. 성능 테스트
demo_performance_test()
print("\n" + "🎉" * 20)
print("모든 테스트 완료!")
print("🎉" * 20)
except KeyboardInterrupt:
print("\n\n⚠️ 사용자에 의해 테스트가 중단되었습니다.")
except Exception as e:
print(f"\n\n❌ 테스트 실행 중 오류 발생: {e}")
print("\n프로그램을 종료합니다.")