WebRTC既可以收发音频和视频,还可以传输其它数据,如短消息、二进制数据、文本数据等。可以实现文本聊天、文件传输等场景。
八、数据通道
1、基本概念
WebRTC的数据通道好比高速公路、文本、文件、图像以及其它数据 好比货物,通道打通 ,即可源源不断地发送数据。数据通道的创建依赖于RTCPeerConnection连接建立,数据通道相关API:
RTCDataChannel通信与WebSocket通信之间区别:
(1)WebRTCDataChannel通信基于Peer与Peer之间直接连接,WebSocket通信需要服务器中转,但WebRTC依靠ICE Servers穿透NAT,有时可能多一层TURN服务器的转发。
(2)WebSocket协议是基于TCP传输,RTCDataChannel是基于SCTP(同TCP 、UDP同级的传输协议)
(3)构造WebSocket需要一个URL,与服务器建立连接,创建一个唯一的SocketSessionId。DataChannel的连接依赖RTCPeerConnection 对象,可以包含多个RTCDataChannel。
2、文本传输示例
数据通道最基本应用场景是发送文本消息,使RTCPeerConnection的createData-Channel方法创建一个可以发送任意数据的数据通道,创建时需要传递一个字符串作为通道id。通道建立后,发送端通过 send方法发送消息,接收端连接通过监听ondatachannel事件,可以获取远端数据通道,此时远端数据通道监听onmessage事件,就可以接收文本消息。以下是一个发送的文本的示例,主体步骤如下:
主体步骤:
(1)创建本地/远端连接,发送/接收数据通道。如下所示:
本地连接对象:localConnection
远端连接对象: remoteConnection
发送通道: sendChannel
接收通道: receiveChannel
(2) 建立本地及远端连接
(3)实例化本地发送数据通道,并指定通道id,然后添加onopen及onclose事件监听。大致代码如下:
javascript
sendChannel=localConnection.createDataChannel('webrtc-datachannel')
sendChannel.onpen={...}
sendChannel.onclose{...}
(4)在远端连接里添加ondatachannel事件,然后拉收端数据通道receiveChannel再添加onmessage事件,用于接收发送端发过来的文本消息.
javascript
remoteConnection.ondatachannel=(event){
receiveChannel=event.channel;
//接收事件监听
receiveChannel.onmessage=(event){
//消息event.data
}
receiveChannel.onopen={...}
receiveChannel.oclose={...}
}
(5) 在界面渲染部分添加两个文本框,一个用于输入发送的内容,一个用于接收文本消息,通过调用dataChannel的send方法将数据发送出去,
完整代码如下:
javascript
import React from "react";
import { Button } from "antd";
import '../public/dataChannel.css'
//本地连接对象
let localConnection;
//远端连接对象
let remoteConnection;
//发送通道
let sendChannel;
//接收通道
let receiveChannel;
/**
* 数据通道示例
*/
class DataChannel extends React.Component {
//呼叫
call = async () => {
console.log('开始呼叫...');
//设置ICE Server,使用Google服务器
let configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
//创建RTCPeerConnection对象
localConnection = new RTCPeerConnection(configuration);
console.log('创建本地PeerConnection成功:localConnection');
//监听返回的Candidate信息
localConnection.addEventListener('icecandidate', this.onLocalIceCandidate);
//实例化发送通道
sendChannel = localConnection.createDataChannel('webrtc-datachannel');
//onopen事件监听
sendChannel.onopen = this.onSendChannelStateChange;
//onclose事件监听
sendChannel.onclose = this.onSendChannelStateChange;
//创建RTCPeerConnection对象
remoteConnection = new RTCPeerConnection(configuration);
console.log('创建本地PeerConnection成功:remoteConnection');
//监听返回的Candidate信息
remoteConnection.addEventListener('icecandidate', this.onRemoteIceCandidate);
//远端连接数据到达事件监听
remoteConnection.ondatachannel = this.receiveChannelCallback;
//监听ICE状态变化
localConnection.addEventListener('iceconnectionstatechange', this.onLocalIceStateChange);
//监听ICE状态变化
remoteConnection.addEventListener('iceconnectionstatechange', this.onRemoteIceStateChange);
try {
console.log('localConnection创建提议Offer开始');
//创建提议Offer
const offer = await localConnection.createOffer();
//创建Offer成功
await this.onCreateOfferSuccess(offer);
} catch (e) {
//创建Offer失败
this.onCreateSessionDescriptionError(e);
}
}
//创建会话描述错误
onCreateSessionDescriptionError = (error) => {
console.log(`创建会话描述SD错误: ${error.toString()}`);
}
//创建提议Offer成功
onCreateOfferSuccess = async (desc) => {
//localConnection创建Offer返回的SDP信息
console.log(`localConnection创建Offer返回的SDP信息\n${desc.sdp}`);
console.log('设置localConnection的本地描述start');
try {
//设置localConnection的本地描述
await localConnection.setLocalDescription(desc);
this.onSetLocalSuccess(localConnection);
} catch (e) {
this.onSetSessionDescriptionError();
}
console.log('remoteConnection开始设置远端描述');
try {
//设置remoteConnection的远端描述
await remoteConnection.setRemoteDescription(desc);
this.onSetRemoteSuccess(remoteConnection);
} catch (e) {
//创建会话描述错误
this.onSetSessionDescriptionError();
}
console.log('remoteConnection开始创建应答Answer');
try {
//创建应答Answer
const answer = await remoteConnection.createAnswer();
//创建应答成功
await this.onCreateAnswerSuccess(answer);
} catch (e) {
//创建会话描述错误
this.onCreateSessionDescriptionError(e);
}
}
//设置本地描述完成
onSetLocalSuccess = (pc) => {
console.log(`${this.getName(pc)}设置本地描述完成:setLocalDescription`);
}
//设置远端描述完成
onSetRemoteSuccess = (pc) => {
console.log(`${this.getName(pc)}设置远端描述完成:setRemoteDescription`);
}
//设置描述SD错误
onSetSessionDescriptionError = (error) => {
console.log(`设置描述SD错误: ${error.toString()}`);
}
getName = (pc) => {
return (pc === localConnection) ? 'localConnection' : 'remoteConnection';
}
//创建应答成功
onCreateAnswerSuccess = async (desc) => {
//输出SDP信息
console.log(`remoteConnection的应答Answer数据:\n${desc.sdp}`);
console.log('remoteConnection设置本地描述开始:setLocalDescription');
try {
//设置remoteConnection的本地描述信息
await remoteConnection.setLocalDescription(desc);
this.onSetLocalSuccess(remoteConnection);
} catch (e) {
this.onSetSessionDescriptionError(e);
}
console.log('localConnection设置远端描述开始:setRemoteDescription');
try {
//设置localConnection的远端描述,即remoteConnection的应答信息
await localConnection.setRemoteDescription(desc);
this.onSetRemoteSuccess(localConnection);
} catch (e) {
this.onSetSessionDescriptionError(e);
}
}
//Candidate事件回调方法
onLocalIceCandidate = async (event) => {
try {
if (event.candidate) {
//将会localConnection的Candidate添加至remoteConnection里
await remoteConnection.addIceCandidate(event.candidate);
this.onAddIceCandidateSuccess(remoteConnection);
}
} catch (e) {
this.onAddIceCandidateError(remoteConnection, e);
}
console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
//Candidate事件回调方法
onRemoteIceCandidate = async (event) => {
try {
if (event.candidate) {
//将会remoteConnection的Candidate添加至localConnection里
await localConnection.addIceCandidate(event.candidate);
this.onAddIceCandidateSuccess(localConnection);
}
} catch (e) {
this.onAddIceCandidateError(localConnection, e);
}
console.log(`IceCandidate数据:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
//添加Candidate成功
onAddIceCandidateSuccess = (pc) => {
console.log(`${this.getName(pc)}添加IceCandidate成功`);
}
//添加Candidate失败
onAddIceCandidateError = (pc, error) => {
console.log(`${this.getName(pc)}添加IceCandidate失败: ${error.toString()}`);
}
//监听ICE状态变化事件回调方法
onLocalIceStateChange = (event) => {
console.log(`localConnection连接的ICE状态: ${localConnection.iceConnectionState}`);
console.log('ICE状态改变事件: ', event);
}
//监听ICE状态变化事件回调方法
onRemoteIceStateChange = (event) => {
console.log(`remoteConnection连接的ICE状态: ${remoteConnection.iceConnectionState}`);
console.log('ICE状态改变事件: ', event);
}
//断开连接
hangup = () => {
console.log('结束会话');
//关闭localConnection
localConnection.close();
//关闭remoteConnection
remoteConnection.close();
//localConnection置为空
localConnection = null;
//remoteConnection置为空
remoteConnection = null;
}
sendData = () => {
let dataChannelSend =document.getElementById('dataChannelSend');
const data = dataChannelSend.value;
sendChannel.send(data);
console.log('发送的数据:' + data);
}
//接收通道数据到达回调方法
receiveChannelCallback = (event) => {
console.log('Receive Channel Callback');
//实例化接收通道
receiveChannel = event.channel;
//接收消息事件监听
receiveChannel.onmessage = this.onReceiveMessageCallback;
//onopen事件监听
receiveChannel.onopen = this.onReceiveChannelStateChange;
//onclose事件监听
receiveChannel.onclose = this.onReceiveChannelStateChange;
}
//接收消息处理
onReceiveMessageCallback = (event) => {
console.log('接收的数据:' + event.data);
let dataChannelReceive =document.getElementById('dataChannelReceive');
dataChannelReceive.value = event.data;
}
//发送通道状态变化`
onSendChannelStateChange = () => {
const readyState = sendChannel.readyState;
console.log('发送通道状态: ' + readyState);
}
//接收通道状态变化
onReceiveChannelStateChange = () => {
const readyState = receiveChannel.readyState;
console.log('接收通道状态:' + readyState);
}
render() {
return (
<div className="container">
<div>
<div>
<h2>发送</h2>
<textarea id="dataChannelSend" disabled={false}
placeholder="请输入要发送的文本..." />
</div>
<div>
<h2>接收</h2>
<textarea id="dataChannelReceive" disabled={false} />
</div>
</div>
<div>
<Button onClick={this.call} style={{ marginRight: "10px" }}>呼叫</Button>
<Button onClick={this.sendData} style={{ marginRight: "10px" }}>发送</Button>
<Button onClick={this.hangup} style={{ marginRight: "10px" }}>挂断</Button>
</div>
</div>
);
}
}
//导出组件
export default DataChannel;
测试此例时,一定选单击"呼叫"按钮建立对等连接,然后才能发送消息.