用python实现一个简单的信令服务器
# signaling_server.py
import asyncio
import json
import websockets
from aiohttp import web
from pprint import pprint
# 存储所有连接的客户端
clients = {}
async def register_client(websocket):
# 将新连接的客户端存储在字典中
pprint(websocket)
clients[websocket] = {'peer_id': None, 'other_peer': None, 'event': asyncio.Event()}
async def unregister_client(websocket):
# 从字典中移除断开连接的客户端
if websocket in clients:
other_peer = clients[websocket]['other_peer']
if other_peer:
await other_peer.send_str(json.dumps({'type': 'peer_disconnected'}))
clients[other_peer]['other_peer'] = None
del clients[websocket]
async def handle_client_message(websocket, message):
# 解析客户端发送的消息
data = json.loads(message)
#pprint(data)
peer_id = clients[websocket]['peer_id']
if data['type'] == 'offer':
# 存储offer和发送给另一个客户端
clients[websocket]['offer'] = data['offer']
await clients[websocket]['event'].wait()
print("send to")
pprint(clients[websocket]['other_peer'])
await clients[websocket]['other_peer'].send_str(json.dumps({
'type': 'offer',
'offer': data['offer']
}))
elif data['type'] == 'answer':
# 发送answer给另一个客户端
await clients[websocket]['other_peer'].send_str(json.dumps({
'type': 'answer',
'answer': data['answer']
}))
elif data['type'] == 'candidate':
# 发送ICE candidate给另一个客户端
print("send candidate")
pprint(data['candidate'])
await clients[websocket]['other_peer'].send_str(json.dumps({
'type': 'candidate',
'candidate': data['candidate']
}))
elif data['type'] == 'register':
# 注册客户端ID并尝试匹配两个客户端
print("register........", data['peer_id'])
clients[websocket]['peer_id'] = data['peer_id']
if int(clients[websocket]['peer_id']) > 0:
for client, info in clients.items():
if client != websocket and int(info['peer_id']) == 0 and not info['other_peer']:
clients[client]['other_peer'] = websocket
clients[websocket]['other_peer'] = client
print("set peer")
pprint(client)
clients[client]['event'].set()
clients[websocket]['event'].set()
#await client.send(json.dumps({'type': 'other_peer_connected'}))
#await websocket.send(json.dumps({'type': 'other_peer_connected'}))
break
async def websocket_handler(request):
# 使用aiohttp创建WebSocket连接
ws = web.WebSocketResponse()
await ws.prepare(request)
await register_client(ws)
try:
async for msg in ws:
if msg.type == web.WSMsgType.TEXT:
await handle_client_message(ws, msg.data)
else:
break
except websockets.exceptions.ConnectionClosed:
await unregister_client(ws)
async def static_handler(request):
# 提供静态文件服务
return web.FileResponse(request.match_info['filename'])
app = web.Application()
# 添加WebSocket路由
app.add_routes([web.get('/ws', websocket_handler)])
# 添加静态文件服务路由
app.add_routes([web.get('/offer.html', static_handler)])
# 运行服务器
if __name__ == '__main__':
# 静态文件目录
app.router.add_static('/static/', path='./', name='static')
web.run_app(app, host='0.0.0.0', port=8080)
offer.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Data Channel Offer</title>
</head>
<body>
<h1>WebRTC Data Channel Offer</h1>
<button id="connectButton">Connect to WebSocket Server</button>
<textarea id="message" rows="4" cols="50"></textarea>
<button id="sendButton">Send</button>
<script>
const connectButton = document.getElementById('connectButton');
const sendButton = document.getElementById('sendButton');
const messageInput = document.getElementById('message');
let peerConnection;
let dataChannel;
let websocket;
function createOffer() {
console.log('reg : ');
sendMessage({'type': 'register', 'peer_id': "0"});
peerConnection = new RTCPeerConnection();
dataChannel = peerConnection.createDataChannel('myDataChannel');
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('send candidate: ');
sendMessage({'type': 'candidate', 'candidate': event.candidate});
}
};
dataChannel.onmessage = (event) => {
console.log('Received message:', event.data);
};
peerConnection.createOffer().then((offer) => {
return peerConnection.setLocalDescription(offer);
}).then(() => {
console.log('send offer: ');
sendMessage({'type': 'offer', 'offer': peerConnection.localDescription});
}).catch((error) => {
console.error('Error creating offer:', error);
});
}
function sendMessage(message) {
websocket.send(JSON.stringify(message));
}
sendButton.addEventListener('click', () => {
const message = messageInput.value;
dataChannel.send(message);
});
connectButton.addEventListener('click', () => {
// 连接到WebSocket服务器
websocket = new WebSocket('ws://10.3.10.192:8080/ws');
websocket.onopen = (event) => {
console.log('Connected to WebSocket server:', event);
createOffer();
};
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'answer') {
console.log('recv answer:', message);
peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer));
} else if (message.type === 'candidate') {
console.log('recv candidate:', message);
peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
};
});
</script>
</body>
</html>
answer.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Data Channel Answer</title>
</head>
<body>
<h1>WebRTC Data Channel Answer</h1>
<button id="connectButton">Connect to WebSocket Server</button>
<textarea id="message" rows="4" cols="50"></textarea>
<button id="sendButton">Send</button>
<script>
const connectButton = document.getElementById('connectButton');
const sendButton = document.getElementById('sendButton');
const messageInput = document.getElementById('message');
let peerConnection;
let dataChannel;
let websocket;
function createAnswer(offer) {
peerConnection = new RTCPeerConnection();
peerConnection.ondatachannel = (event) => {
dataChannel = event.channel;
dataChannel.onmessage = (event) => {
console.log('Received message:', event.data);
};
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('send candidate:');
sendMessage({'type': 'candidate', 'candidate': event.candidate});
}
};
peerConnection.setRemoteDescription(offer).then(() => {
console.log('setRemoteDescription:');
return peerConnection.createAnswer();
}).then((answer) => {
return peerConnection.setLocalDescription(answer);
}).then(() => {
console.log('send answer:');
sendMessage({'type': 'answer', 'answer': peerConnection.localDescription});
}).catch((error) => {
console.error('Error creating answer:', error);
});
}
function sendMessage(message) {
websocket.send(JSON.stringify(message));
}
sendButton.addEventListener('click', () => {
const message = messageInput.value;
console.log('send text :', message);
dataChannel.send(message);
});
connectButton.addEventListener('click', () => {
// 连接到WebSocket服务器
websocket = new WebSocket('ws://10.3.10.192:8080/ws');
websocket.onopen = (event) => {
console.log('Connected to WebSocket server:', event);
sendMessage({'type': 'register', 'peer_id': "1"});
};
websocket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'offer') {
console.log('recv offer:');
createAnswer(new RTCSessionDescription(message.offer));
} else if (message.type === 'candidate') {
console.log('recv candidate:');
peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
}
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
};
});
</script>
</body>
</html>
python 的客户端
import asyncio
from pprint import pprint
import json
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCIceCandidate
from aiortc.contrib.media import MediaBlackhole
import websockets
# 配置信令服务器的URL
SIGNALLING_SERVER = 'ws://10.3.10.192:8080/ws'
# 创建一个PeerConnection实例
pc = RTCPeerConnection()
# 创建一个数据通道
data_channel = pc.createDataChannel('DataChannel')
@data_channel.on('message')
def on_message(message):
print('收到消息:', message)
@data_channel.on("open")
def on_open():
data_channel.send("1231231231223")
print("on datachannel")
async def connect_signalling_server():
# 连接到信令服务器
async with websockets.connect(SIGNALLING_SERVER) as ws:
# 发送candidate
@pc.on('icecandidate')
def on_icecandidate(candidate):
if candidate:
print('发送candidate:', candidate)
ws.send(json.dumps({
'type': 'candidate',
'candidate':{
'type': 'candidate',
'candidate': candidate.to_dict()
}
}))
await ws.send(json.dumps({'type': 'register', 'peer_id': "0"}))
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
out = pc.localDescription
# 发送offer到信令服务器
print('发送offer')
await ws.send(json.dumps({
'type': 'offer',
'offer':{
'type': out.type,
'sdp': out.sdp
}
}))
# 接收信令服务器发送的消息
while True:
message = await ws.recv()
data = json.loads(message)
if data['type'] == 'answer':
# 接收到answer,设置远程描述
print('接收到answer')
sdp_data = data['answer']
pprint(data)
await pc.setRemoteDescription(RTCSessionDescription(sdp=sdp_data['sdp'], type=sdp_data['type']))
elif data['type'] == 'offer':
# 接收到offer,设置远程描述并创建answer
print('接收到offer')
pprint(data)
await pc.setRemoteDescription(RTCSessionDescription(sdp=data['sdp'], type=data['type']))
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
# 发送answer到信令服务器
print('发送answer')
await ws.send(json.dumps({
'type': answer.type,
'sdp': answer.sdp
}))
elif data['type'] == 'candidate':
# 接收到candidate,将其添加到PeerConnection
print('接收到candidate')
pprint(data)
sdp_data = data['candidate']
#candidate = RTCIceCandidate(**sdp_data['candidate'])
#await pc.addIceCandidate(candidate)
# 运行客户端
asyncio.get_event_loop().run_until_complete(connect_signalling_server())