270 lines
9.8 KiB
Python
270 lines
9.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
GodEye Signaling Server
|
||
Сервер для обмена SDP и ICE кандидатами между устройством и оператором
|
||
"""
|
||
|
||
import asyncio
|
||
import websockets
|
||
import json
|
||
import logging
|
||
from datetime import datetime
|
||
import uuid
|
||
|
||
# Настройка логирования
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class SignalingServer:
|
||
def __init__(self):
|
||
self.clients = {} # client_id -> websocket
|
||
self.sessions = {} # session_id -> {device: ws, operator: ws}
|
||
|
||
async def register_client(self, websocket, path):
|
||
"""Регистрация нового клиента"""
|
||
client_id = str(uuid.uuid4())
|
||
self.clients[client_id] = websocket
|
||
|
||
logger.info(f"🔗 Клиент подключен: {client_id}")
|
||
|
||
try:
|
||
# Отправляем ID клиенту
|
||
await websocket.send(json.dumps({
|
||
"type": "client_registered",
|
||
"client_id": client_id,
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
async for message in websocket:
|
||
await self.handle_message(client_id, message)
|
||
|
||
except websockets.exceptions.ConnectionClosed:
|
||
logger.info(f"🔌 Клиент отключен: {client_id}")
|
||
finally:
|
||
# Очистка при отключении
|
||
await self.cleanup_client(client_id)
|
||
|
||
async def handle_message(self, client_id, message):
|
||
"""Обработка сообщений от клиентов"""
|
||
try:
|
||
data = json.loads(message)
|
||
message_type = data.get("type")
|
||
|
||
logger.info(f"📨 Сообщение от {client_id}: {message_type}")
|
||
|
||
if message_type == "create_session":
|
||
await self.create_session(client_id, data)
|
||
elif message_type == "join_session":
|
||
await self.join_session(client_id, data)
|
||
elif message_type == "offer":
|
||
await self.relay_offer(client_id, data)
|
||
elif message_type == "answer":
|
||
await self.relay_answer(client_id, data)
|
||
elif message_type == "ice_candidate":
|
||
await self.relay_ice_candidate(client_id, data)
|
||
elif message_type == "hangup":
|
||
await self.handle_hangup(client_id, data)
|
||
else:
|
||
logger.warning(f"⚠️ Неизвестный тип сообщения: {message_type}")
|
||
|
||
except json.JSONDecodeError:
|
||
logger.error(f"❌ Ошибка парсинга JSON от {client_id}")
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка обработки сообщения: {e}")
|
||
|
||
async def create_session(self, client_id, data):
|
||
"""Создание новой сессии (устройство)"""
|
||
session_id = str(uuid.uuid4())
|
||
device_info = data.get("device_info", {})
|
||
|
||
self.sessions[session_id] = {
|
||
"device": self.clients[client_id],
|
||
"operator": None,
|
||
"device_info": device_info,
|
||
"created_at": datetime.now().isoformat()
|
||
}
|
||
|
||
# Отправляем подтверждение устройству
|
||
await self.clients[client_id].send(json.dumps({
|
||
"type": "session_created",
|
||
"session_id": session_id,
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
logger.info(f"📱 Сессия создана: {session_id} для устройства {device_info.get('model', 'Unknown')}")
|
||
|
||
async def join_session(self, client_id, data):
|
||
"""Подключение к сессии (оператор)"""
|
||
session_id = data.get("session_id")
|
||
|
||
if session_id not in self.sessions:
|
||
await self.clients[client_id].send(json.dumps({
|
||
"type": "error",
|
||
"message": "Session not found"
|
||
}))
|
||
return
|
||
|
||
session = self.sessions[session_id]
|
||
if session["operator"] is not None:
|
||
await self.clients[client_id].send(json.dumps({
|
||
"type": "error",
|
||
"message": "Session already has an operator"
|
||
}))
|
||
return
|
||
|
||
# Подключаем оператора
|
||
session["operator"] = self.clients[client_id]
|
||
|
||
# Уведомляем оператора
|
||
await self.clients[client_id].send(json.dumps({
|
||
"type": "session_joined",
|
||
"session_id": session_id,
|
||
"device_info": session["device_info"],
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
# Уведомляем устройство
|
||
await session["device"].send(json.dumps({
|
||
"type": "operator_joined",
|
||
"session_id": session_id,
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
logger.info(f"👤 Оператор подключен к сессии: {session_id}")
|
||
|
||
async def relay_offer(self, client_id, data):
|
||
"""Передача SDP offer"""
|
||
session_id = data.get("session_id")
|
||
session = self.sessions.get(session_id)
|
||
|
||
if not session or session["operator"] is None:
|
||
return
|
||
|
||
# Передаем offer оператору
|
||
await session["operator"].send(json.dumps({
|
||
"type": "offer",
|
||
"session_id": session_id,
|
||
"sdp": data.get("sdp"),
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
logger.info(f"📞 SDP Offer передан в сессии: {session_id}")
|
||
|
||
async def relay_answer(self, client_id, data):
|
||
"""Передача SDP answer"""
|
||
session_id = data.get("session_id")
|
||
session = self.sessions.get(session_id)
|
||
|
||
if not session:
|
||
return
|
||
|
||
# Передаем answer устройству
|
||
await session["device"].send(json.dumps({
|
||
"type": "answer",
|
||
"session_id": session_id,
|
||
"sdp": data.get("sdp"),
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
logger.info(f"📞 SDP Answer передан в сессии: {session_id}")
|
||
|
||
async def relay_ice_candidate(self, client_id, data):
|
||
"""Передача ICE кандидатов"""
|
||
session_id = data.get("session_id")
|
||
session = self.sessions.get(session_id)
|
||
|
||
if not session:
|
||
return
|
||
|
||
# Определяем кому передавать (устройству или оператору)
|
||
if session["device"] == self.clients[client_id]:
|
||
# От устройства к оператору
|
||
if session["operator"]:
|
||
await session["operator"].send(json.dumps({
|
||
"type": "ice_candidate",
|
||
"session_id": session_id,
|
||
"candidate": data.get("candidate"),
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
else:
|
||
# От оператора к устройству
|
||
await session["device"].send(json.dumps({
|
||
"type": "ice_candidate",
|
||
"session_id": session_id,
|
||
"candidate": data.get("candidate"),
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
logger.info(f"🧊 ICE candidate передан в сессии: {session_id}")
|
||
|
||
async def handle_hangup(self, client_id, data):
|
||
"""Завершение сессии"""
|
||
session_id = data.get("session_id")
|
||
session = self.sessions.get(session_id)
|
||
|
||
if not session:
|
||
return
|
||
|
||
# Уведомляем обе стороны
|
||
if session["device"]:
|
||
await session["device"].send(json.dumps({
|
||
"type": "hangup",
|
||
"session_id": session_id,
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
if session["operator"]:
|
||
await session["operator"].send(json.dumps({
|
||
"type": "hangup",
|
||
"session_id": session_id,
|
||
"timestamp": datetime.now().isoformat()
|
||
}))
|
||
|
||
# Удаляем сессию
|
||
del self.sessions[session_id]
|
||
logger.info(f"📴 Сессия завершена: {session_id}")
|
||
|
||
async def cleanup_client(self, client_id):
|
||
"""Очистка при отключении клиента"""
|
||
if client_id in self.clients:
|
||
del self.clients[client_id]
|
||
|
||
# Удаляем сессии с этим клиентом
|
||
sessions_to_remove = []
|
||
for session_id, session in self.sessions.items():
|
||
if (session["device"] == self.clients.get(client_id) or
|
||
session["operator"] == self.clients.get(client_id)):
|
||
sessions_to_remove.append(session_id)
|
||
|
||
for session_id in sessions_to_remove:
|
||
del self.sessions[session_id]
|
||
logger.info(f"🧹 Сессия {session_id} удалена из-за отключения клиента")
|
||
|
||
def main():
|
||
"""Запуск сигналинг сервера"""
|
||
server = SignalingServer()
|
||
|
||
print("🚀 GodEye Signaling Server")
|
||
print("==========================")
|
||
print("📡 Сервер запущен на ws://localhost:8765")
|
||
print("💡 Для остановки нажмите Ctrl+C")
|
||
print()
|
||
|
||
# Исправляем проблему с event loop
|
||
async def run_server():
|
||
start_server = websockets.serve(
|
||
server.register_client,
|
||
"0.0.0.0", # Слушаем на всех интерфейсах
|
||
8765
|
||
)
|
||
await start_server
|
||
await asyncio.Future() # Запускаем вечно
|
||
|
||
try:
|
||
asyncio.run(run_server())
|
||
except KeyboardInterrupt:
|
||
print("\n🛑 Сервер остановлен")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|