本文将介绍如何使用 JavaScript 创建 WebRTC UDP 连接的步骤。
第 1 步:要求
我们将使用 Node.js 服务器和普通浏览器 JavaScript 客户端。因此,需要:
- Node.js
websocket
和wrtc
节点包,npm i -g node-pre-gyp
安装wrtc
第 2 步:WebSocket 连接
在创建 WebRTC 连接之前,需要一种不同的数据交换方法。可以使用 WebSockets 或普通 HTTP 请求。在这里我们将使用 WebSockets。
先从一个简单的 WebSocket 服务器开始:
// server.js
const http = require( "http" );
const WebSocketServer = require( "websocket" ).server;
const wrtc = require( "wrtc" );
// 创建一个 http 服务器
const server = http.createServer();
server.listen( 12345 );
// 创建一个 websocket 服务器
const wsServer = new WebSocketServer( { httpServer: server } );
let peerConnection; // 稍后用于 wrtc
// 等待 websocket 连接
wsServer.on( "request" , ( request ) =>
{
const connection = request.accept( null , request.origin );
connection.on( "message" , async ( message ) =>
{
var msg = JSON.parse( message.utf8Data );
// [...]
} );
} );
// 发送 websocket 消息的辅助函数
function sendWebSocketMessage( connection , type , data )
{
var json = {};
json.type = type;
json.data = data;
connection.sendUTF( JSON.stringify( json ) );
}
在客户端,还需要一个函数来连接 WebSocket 服务器:
// client.html
let webSocket;
function connectWebSocket()
{
webSocket = new WebSocket( "ws://localhost:12345/" );
webSocket.onmessage = async ( msg ) =>
{
msg = JSON.parse( msg.data );
// [...]
};
}
// 用于发送 WebSocket 消息的辅助函数
function sendWebSocketMessage( type , data )
{
var json = {};
json.type = type;
json.data = data;
webSocket.send( JSON.stringify( json ) );
}
第 3 步:WebRTC 连接
成功建立 WebSocket 连接后,就可以开始创建 WebRTC 连接这一稍显复杂的过程了。具体做法是初始化一个新的 RTCPeerConnection
对象,然后通过现有的 WebSocket 连接创建并向服务器发送要约。我们还必须实现一个 onicecandidate
处理程序,它负责向服务器发送可用的 ICE 候选。
// client.html
let peerConnection;
async function connectWebRTC()
{
// 一个用于 WebRTC 协商的 stun 服务器
var config = {
iceServers: [ {
urls: [ "stun:stun.l.google.com:19302" ]
} ]
};
// 创建 WebRTC 对等连接
peerConnection = new RTCPeerConnection( config );
// WebRTC negotiations
peerConnection.onicecandidate = ( event ) =>
{
if ( event.candidate )
{
sendWebSocketMessage( "icecandidate" , event.candidate );
}
};
// [...] 稍后会在这里创建一个数据通道
// 创建一个 offer
const offer = await peerConnection.createOffer();
// 本地描述 offer
await peerConnection.setLocalDescription( offer );
// 将 offer 发送到服务器
sendWebSocketMessage( "call" , offer );
}
在服务器端,我们必须对客户端的 offer 做出反应,这看起来与客户端版本非常相似(事实上,我们也可以从服务器调用,从客户端 answer,因为它是点对点的)。唯一不同的是,使用客户端的 offer 并创建一个answer,然后将其发送回客户端。
// server.js
connection.on( "message" , async ( message ) =>
{
var msg = JSON.parse( message.utf8Data );
if ( msg.type === "call" )
{
// 创建 WebRTC 对等连接
peerConnection = new wrtc.RTCPeerConnection();
// WebRTC negotiations
peerConnection.onicecandidate = ( event ) =>
{
if ( event.candidate )
{
sendWebSocketMessage( connection , "icecandidate" , event.candidate );
}
};
// [...] 稍后会在这里处理数据通道
// 从客户端获得的 offer 是远程描述
await peerConnection.setRemoteDescription( new wrtc.RTCSessionDescription( msg.data ) );
// 为 offer 创建一个 answer
const answer = await peerConnection.createAnswer();
// answer 是一个本地描述
await peerConnection.setLocalDescription( answer );
// 将 answer 发送给客户端
sendWebSocketMessage( connection , "answer" , answer );
}
// 处理来自客户端的 ice candidates
else if ( msg.type === "icecandidate" )
{
var candidate = new wrtc.RTCIceCandidate( {
sdpMLineIndex: msg.data.sdpMLineIndex,
candidate: msg.data.candidate
} );
try
{
await peerConnection.addIceCandidate( candidate );
}
catch ( exception )
{
console.log( "exception: " , exception );
}
}
} );
由于服务器对客户端的 offer 发送了 answer ,因此我们还必须在客户端对 answer 做出反应:
// client.html
webSocket.onmessage = async ( msg ) =>
{
msg = JSON.parse( msg.data );
if ( msg.type === "answer" )
{
// 从服务器得到的answer是远程描述
await peerConnection.setRemoteDescription( new RTCSessionDescription( msg.data ) );
}
// 处理来自服务器的 ice candidates
else if ( msg.type === "icecandidate" )
{
var candidate = new RTCIceCandidate( {
sdpMLineIndex: msg.data.sdpMLineIndex,
candidate: msg.data.candidate
} );
await peerConnection.addIceCandidate( candidate );
}
};
完成所有这些后,我们应该能够在客户端和服务器之间成功建立 WebRTC 连接。不过,WebRTC 只有在有内容要传输时才会开始协商连接细节。通常,这将是来自网络摄像头或麦克风的媒体流。但在本例中,我们想传输其他任意数据,这可以通过数据通道来实现。
第 4 步:创建数据通道
在任何一方都可以创建数据通道。不过,Node.js 的 wrtc 软件包似有问题,我只能在客户端创建数据通道时才能让它工作。
// client.html
// 创建数据通道
// 参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel
dataChannel = peerConnection.createDataChannel( "dataChannel" , {} );
dataChannel.onopen = () => { console.log( "data channel onopen" ); };
dataChannel.onclose = () => { console.log( "data channel onclose" ); };
dataChannel.onmessage = ( event ) =>
{
console.log( "data channel onmessage: "+event.data );
};
在服务器上,必须实现 ondatachannel
回调。在本例中,服务器只需向客户端回复同样的内容:
// server.js
peerConnection.ondatachannel = ( event ) =>
{
dataChannel = event.channel;
dataChannel.onopen = () => { console.log( "data channel onopen" ); };
dataChannel.onclose = () => { console.log( "data channel onclose" ); };
dataChannel.onmessage = ( event ) =>
{
// 对于这个简单的例子,只需回复相同的内容
dataChannel.send( "server reply: "+event.data );
};
};
最后,使用以下命令在客户端发送数据:
// client.html
dataChannel.send( message );
完整实例
这是一个非常简单的示例,只有一个客户端。对于真正的游戏,服务器应该能够处理多个客户端。不过,添加这个功能也并不复杂,只需为每个客户端存储 WebSocket 和 WebRTC 连接,并向所有客户端而非仅向一个客户端发送广播即可。
另一种方法是根本不在服务器端添加 WebRTC。您也可以在客户端之间创建点对点连接,直接进行通信。不过,你仍然需要 WebSocket 服务器来转发 WebRTC offers 和 ice candidates。
虽然处理 WebSocket 连接看似是一个恼人且不必要的步骤,但它也有一个很好的作用:在游戏开发中,发送的大部分数据包都是实体位置和状态更新,你可以忍受少量的数据包丢失。但是,某些信息必须到达,比如重要的游戏状态更新。你可以在 UDP 上建立一个简单的 ACK 系统。但在这种情况下,我们已经有了 TCP WebSocket 连接,它可以保证数据包的到达。因此,我们可以使用 WebSockets 发送重要信息,而使用 WebRTC 发送其他信息。
作者:Christian Behler
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/57225.html