main commit
This commit is contained in:
269
signaling_server.py
Normal file
269
signaling_server.py
Normal file
@@ -0,0 +1,269 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user