前端使用WebRTC———DataChannel

上一篇中介绍了如何在局域网内使用浏览器进行1v1视频通话。现在我们介绍一下如何在前端使用WebRTC进行消息通信。

简单的Demo

通过PeerConnection让两个浏览器建立连接,并进行相互发送消息

效果

在这里插入图片描述
在控制台中,通过使用datachannel.send进行发送,可以看到两个页面之间可以实现相互发送消息。

流程

建立连接的流程可以参考局域网内单向通信的流程,只是在createOffer之前先添加了DataChannel而已。服务端的代码是完全一致的。

API

创建DataChannel

通过RTCPeerConnection.createDataChannel方法来创建一个DataChannel,需要在createOffer之前创建。

function createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel;

● label
其中labtel是必须要设置的,是data channel的名字,长度与能超过65535个字节。
● dataChannelDict
dataChannelDict是可选字段,下面会详细解释dataChannelDict的用法

interface RTCDataChannelInit {
    id?: number;
    maxRetransmits?: number;
    negotiated?: boolean;
    ordered?: boolean;
}

● id
id是通道的id,可以取值范围是0-65534。如果不设置id,会自动生成一个id。
● negotiated
默认是false,如果是true,在PeerConnection的另一端不会收到"datachannel",如果想要使用这个datachannel进行发送消息,需要在createAnswer之前,也创建一个相同id和label的datachannel。
● maxRetransmits
消息的最大重穿次数
● ordered
默认是true。表示是否是可靠通道。如果是可靠通道,则消息是有序的。

DataChannel的事件

DataChannel常用的事件和WebSocket是一样的。
● open
表示DataChannel建立连接成功了,收到open消息后,就可以开始接收和发送消息了。
● message
收到消息,同时消息的类型有string和binary两种。
● close
DataChannel被关闭
● error
DataChannel发生错误

DataChannel的方法

● send
发送数据,支持string和binary类型
● close
断开DataChannel连接

DataChannel VS WebSocket

通过上面的介绍可以看到,DataChannel和WebSocket在建立连接的部分是不同的,在建立连接之后的用法是基本一致的。他们之间还是有一些不同之处的。

  1. 传输协议
    WebSocket是基于TCP协议的,传输数据都是有序的。而DataChannel是基于UDP的,并且可以在创建的时候通过ordered字段来控制是否是可靠传输。如果选择无序模式,在弱网条件下延迟相对更低,但是不会保证消息的可靠性和有序。
  2. 架构不同
    WebSocket是客户端和服务端建立连接的,如果想要两个客户端相互发送消息,还需要有服务端的支持。消息是通过服务端进行转发的。但是DataChannel是P2P的模式,建立连接成功后,就不再需要服务端。
  3. 隐蔽性
    WebSocket的连接在浏览器的调试工具的network中是可见的,包括发送和接收的数据。但是DataChannel在network中是不可见的。

代码

可以直接从gittee获取源代码,也可以直接使用下面的代码

服务端
var ws = require("nodejs-websocket");


var pub_ws = null;
var sub_ws = null;


function start() {
  var msg = JSON.stringify({ type: "start" });
  pub_ws.send(msg);
}

var server = ws.createServer(function (conn) {
  // 收到websocket连接
  conn.on("text", function (str) {
    if (pub_ws === conn) {
      if (sub_ws) {
        sub_ws.send(str);
      }
    } else if (sub_ws === conn) {
      if (pub_ws) {
        pub_ws.send(str);
      }
    } else {
      let obj = JSON.parse(str);
      if (obj.type === 'publish') {
        pub_ws = conn;
        if (sub_ws) {
          start();
        }
      } else if (obj.type === 'subscribe') {
        sub_ws = conn;
        if (pub_ws) {
          start();
        }
      }
    }
  })

  conn.on("error", function (event) {

  });

  conn.on("close", function (code, reason) {
    if (conn === pub_ws) {
      console.log("remove pub")
      pub_ws = null;
    } else if (conn === sub_ws) {
      console.log("remove sub")
      sub_ws = null;
    }
  })
}).listen(9000);

发起端页面

<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>推流页面</title>
</head>

<body>
  <div>

  </div>
  <script>
    // datachannel对象
    let datachannel = null;

    // 推流用的MediaStream
    let pc_pub = null;

    let ws = new WebSocket('ws://127.0.0.1:9000');
    ws.addEventListener('open', () => {
      // 通知server pub已经上线
      ws.send(JSON.stringify({
        type: "publish"
      }))
    })

    ws.addEventListener('message', (event) => {
      let msg = JSON.parse(event.data);
      switch (msg.type) {
        case "start":
          start();
          break;

        case "answer":
          pc_pub.setRemoteDescription(msg).then(() => {

          }).catch((err) => {

          })
          break;

        default:
          pc_pub.addIceCandidate(msg);
          break;
      }
    })



    function start() {
      pc_pub = new RTCPeerConnection();
      pc_pub.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
          ws.send(JSON.stringify(event.candidate));
        }
      })
      addDataChannel();
      pc_pub.createOffer().then((offer) => {
        pc_pub.setLocalDescription(offer).then(() => {
          ws.send(JSON.stringify(offer));
        }).catch((err) => {
          console.error('setLocalDescription error', err);
        })
      }).catch((err) => {
        console.error("create offer error", err);
      })
    }


    function addDataChannel() {
      datachannel = pc_pub.createDataChannel('dc', {
        ordered: true,
        id: 1,
      });
      datachannel.addEventListener('open', onDataChannelOpen);
      datachannel.addEventListener('close', onDataChannelClose);
      datachannel.addEventListener('error', onDataChannelError);
      datachannel.addEventListener('message', onDataChannelMessage);
    }


    function onDataChannelOpen() {
      console.log("pub datachannel open")
    }

    function onDataChannelClose() {
      console.log("pub datachannel close")
    }

    function onDataChannelError() {
      console.log("pub datachannel error")
    }

    function onDataChannelMessage(event) {
      console.log("pub datachannel messae:", event.data)
    }



  </script>
</body>

</html>
接收端页面
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>推流页面</title>
</head>

<body>
  <div>

  </div>
  <script>
    // datachannel对象
    let datachannel = null;

    // 订阅流用的Peerconnection
    let pc_sub = new RTCPeerConnection();

    let ws = new WebSocket('ws://127.0.0.1:9000');
    ws.addEventListener('open', () => {
      // 通知server pub已经上线
      ws.send(JSON.stringify({
        type: "subscribe"
      }))
    })

    ws.addEventListener('message', (event) => {
      let msg = JSON.parse(event.data);
      switch (msg.type) {
        case "start":
          break;

        case "offer":
          pc_sub.setRemoteDescription(msg).then(() => {
            pc_sub.createAnswer().then((answer) => {
              pc_sub.setLocalDescription(answer).then(() => {
                ws.send(JSON.stringify(answer));
              }).catch((err) => {

              })
            }).catch((err) => {
              console.error('create answer error', err);
            })
          }).catch((err) => {
            console.error('setRemoteDescription error', err);
          })
          break;

        default:
          pc_sub.addIceCandidate(msg);
          break;
      }
    })

    pc_sub.addEventListener('icecandidate', (event) => {
      if (event.candidate) {
        ws.send(JSON.stringify(event.candidate));
      }
    })

    pc_sub.addEventListener('datachannel', (event) => {
      console.log('recv datachannel', event.channel);
      datachannel = event.channel;
      datachannel.addEventListener('open', onDataChannelOpen);
      datachannel.addEventListener('close', onDataChannelClose);
      datachannel.addEventListener('error', onDataChannelError);
      datachannel.addEventListener('message', onDataChannelMessage);
    })

    function onDataChannelOpen() {
      console.log("sub datachannel open")
    }

    function onDataChannelClose() {
      console.log("sub datachannel close")
    }

    function onDataChannelError() {
      console.log("sub datachannel error")
    }

    function onDataChannelMessage(event) {
      console.log("sub datachannel messae:", event.data)
    }


  </script>
</body>

</html>

其他

如果你也是专注前端多媒体或者对前端多媒体感兴趣,可以搜索微信公众号"前端多媒体"

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豆包啊啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值