WebRTC 入门与实战教程 | 掘金技术征文

本文深入探讨WebRTC技术,介绍其三大核心API:getUserMedia、RTCPeerConnection和RTCDataChannel,通过实战演示如何搭建视频聊天室,包括本地音视频流的捕获、实时传输及端对端连接建立。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前公司准备用 webRTC 来实现视频聊天,研究了几天,撸了个 demo 出来,(虽然最后并没有采用这项技术,囧),听说掘金最近在搞 webRTC 有奖征文活动,自不量力一下,硬着头皮写个教程吧,顺便也给自己整理下思路。

WebRTC简单介绍

WebRTC (Web Real-Time Communication) 是一个可以用在视频聊天,音频聊天或P2P文件分享等Web App中的 API。-MDN

目前这项Web技术支持的浏览器有chrome, firefox和safari。

WebRTC 有三个主要的API

  • getUserMedia - 采集本地音频和视频流
  • RTCPeerConnection - 用来创建对端连接并传输音视频的API
  • RTCDataChannel - 用于传输二进制数据。

这三个API里面涉及了很多知识,要想全面细致的了解这项技术,建议去看官网,这篇教程只会介绍重要的知识点并且以此为前提,写一个聊天室和多人视频聊天的demo

直播软件(比如斗鱼直播,熊猫直播等)是在客户端采集和编码主播的音频和视频,传输给流媒体服务器,流媒体服务器将媒体数据转发出去,客户端收到视频流进行解码和播放 架构简化图

WebRTC提供端对端的音视频通讯,不需要媒体服务器转发媒体数据,架构简化图如下,

其中每个相互连接的客户端叫做对等端

WebRTC采集和传输音视频数据的过程可以分为三步进行

  1. 实时捕获本地的音视频流
  2. 实时编码音视频并在网络中向对等端传输多媒体数据
  3. 对等端接受发送者的音视频,实时解码播放

捕获本地媒体流

这一步非常简单,调用navigator.getUserMedia这个api就可以,第一个参数是音视频的限制条件,第一个参数是捕获媒体流成功的回调,回调参数是stream, 第三个参数是捕获失败的回调,在成功的回调函数种将stream赋值给video的srcObject即可显示视频

示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div>
        <button id="start">开始录制</button>
        <button id="stop">停止录制</button>
    </div>
    <div>
        <video autoplay controls id="stream"></video>
    </div>
    <script>
        // 只获取视频
        let constraints = {audio: false, video: true}; 
        let startBtn = document.getElementById('start')
        let stopBtn = document.getElementById('stop')
        let video = document.getElementById('stream')
        startBtn.onclick = function() {
            navigator.getUserMedia(constraints, function(stream) {
                video.srcObject = stream;
                window.stream = stream;
            }, function(err) {
                console.log(err)
            })
        }
        stopBtn.onclick = function() {
            video.pause();
        }
    </script>
</body>
</html>
复制代码

本地视频已经以采集和播放了,接下来要解决如何和对方进行视频会议,也就是实时传输音视频,WebRTC是提供端对端的音视频传输,也就是封装好的RTCPeerConnection这个API,调用这个API可以创建对等端连接并传输音视频,但是光有这个api还不够,在传送音视频数据之前,我们需要先传输信令

什么是信令

信令是协调通信的过程。为了使WebRTC应用程序能够建立一个"通话",其客端户需要交换以下信息:

      会话控制消息用于打开或关闭通信

      错误消息

      媒体元数据,如编解码器和编解码器设置,带宽和媒体类型

      密钥数据,用于建立安全的连接

      网络数据,如主机IP地址和端口
复制代码

这个信令过程需要一个方式使客户端之间来回地进行消息传递, WebRTC标准并没有规定信令方法和协议,我们可以采用JavaScript会话建立协议JSEP来实现信令的交换,这里用一个例子来说明这个过程,假设A和B之间要建立RTCPeerConnection连接

     1. A创建一个RTCPeerConnection对象。

     2. A使用RTCPeerConnection .createOffer()方法产生一个offer(一个SDP会话描述)。

     3. A用生成的offer调用setLocalDescription(),设置成自己的本地会话描述。

     4. A将offer通过信令机制发送给B。

     5. B用A的offer调用setRemoteDescription(),设置成自己的远端会话描述,以便他的RTCPeerConnection知道A的设置。

     6. B调用createAnswer()生成answer

     7. B通过调用setLocalDescription()将其answer设置为本地会话描述。

     8. B然后使用信令机制将他的answer发回给A。

     9. A使用setRemoteDescription()将B的应答设置为远端会话描述。
复制代码

A和B除了交换会话信息,还需要交换网络信息。"查找候选项"是指使用ICE框架查找网络接口和端口的过程。

     1. A使用onicecandidate创建一个RTCPeerConnection对象,注册一个处理器函数。

     2. 处理器在网络候选变得可用时被调用。

     3. 在处理器中,A通过信令通道将候选数据发送给B。

     4. 当B从A那里获得候选消息时,调用addIceCandidate(),将候选项添加到远端对等描述中。
复制代码

稳住,别晕,我们用代码来说明这个过程吧,交换信令需要信令服务器,这个我们晚点讨论,我们现在考虑同一个页面上的两个RTCPeerConnection对象,一个代表本地,一个代表远端, 来说明信令交换和网络信息交换的过程和细节。 这个是官方文档的demo,第五个实验室教程, 能够科学上网的小伙伴建议直接去看教程,由浅入深地教你搭建WebRTC项目,在这里借用这个demo来解释建立RTCPeerConnection连接建立的过程

首先建立index.html

html结构如下:

 <!DOCTYPE html>
 <html>

     <head>
         <title>Realtime communication with WebRTC</title>
         <style>
             body {
                 font-family: sans-serif;
             }
     
             video {
                 max-width: 100%;
                 width: 320px;
             }
         </style>
     </head>

     <body>
         <h1>Realtime communication with WebRTC</h1>
     
         <video id="localVideo" autoplay playsinline></video>
         <video id="remoteVideo" autoplay playsinline></video>
     
         <div>
             <button id="startButton">开始</button>
             <button id="callButton">调用</button>
             <button id="hangupButton">挂断</button>
         </div>
         <script src="./main.js">
         </script>
 </body>

</html>
复制代码

一个video元素显示通过getUserMedia()方法获取的本地视频流,另一个显示通过 RTCPeerconnection传输的视频流,这里是同样的视频流,但是在真实的应用中,一个video显示本地视频流,另一个显示远端视频流

main.js代码如下

    'use strict';
    // 传输视频,不传输音频
    const mediaStreamConstraints = {
      video: true,
      audio: false
    };
    
    // 设置只交换视频
    const offerOptions = {
      offerToReceiveVideo: 1,
    };
    
    let startTime = null;
    
    // 设置两个video,分别显示本地视频流和远端视频流
    const localVideo = document.getElementById('localVideo');
    const remoteVideo = document.getElementById('remoteVideo');
    
    let localStream;
    let remoteStream;
    // 建立两个对等连接对象,分表代表本地和远端
    let localPeerConnection;
    let remotePeerConnection;



function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false; 
}

function handleLocalMediaStreamError(error) {
  trace(`navigator.getUserMedia error: ${error.toString()}.`);
}

function gotRemoteMediaStream(event) {
  const mediaStream = event.stream;
  remoteVideo.srcObject = mediaStream;
  remoteStream = mediaStream;
  trace('Remote peer connection received remote stream.');
}

function logVideoLoaded(event) {
  const video = event.target;
  trace(`${video.id} videoWidth: ${video.videoWidth}px, ` +
        `videoHeight: ${video.videoHeight}px.`);
}

function logResizedVideo(event) {
  logVideoLoaded(event);
  if (startTime) {
    const elapsedTime = window.performance.now() - startTime;
    startTime = null;
    trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`);
  }
}

localVideo.addEventListener('loadedmetadata', logVideoLoaded);
remoteVideo.addEventListener('loadedmetadata', logVideoLoaded);
remoteVideo.addEventListener('onresize', logResizedVideo);


function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

function handleConnectionSuccess(peerConnection) {
  trace(`${getPeerName(peerConnection)} addIceCandidate success.`);
};

function handleConnectionFailure(peerConnection, error) {
  trace(`${getPeerName(peerConnection)} failed to add ICE Candidate:\n`+
        `${error.toString()}.`);
}

function handleConnectionChange(event) {
  const peerConnection = event.target;
  console.log('ICE state change event: ', event);
  trace(`${getPeerName(peerConnection)} ICE state: ` +
        `${peerConnection.iceConnectionState}.`);
}

function setSessionDescriptionError(error) {
  trace(`Failed to create session description: ${error.toString()}.`);
}

function setDescriptionSuccess(peerConnection, functionName) {
  const peerName = getPeerName(peerConnection);
  trace(`${peerName} ${functionName} complete.`);
}

function setLocalDescriptionSuccess(peerConnection) {
  setDescriptionSuccess(peerConnection, 'setLocalDescription');
}

function setRemoteDescriptionSuccess(peerConnection) {
  setDescriptionSuccess(peerConnection, 'setRemoteDescription');
}

function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}

const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
callButton.disabled = true;
hangupButton.disabled = true;

function startAction() {
  startButton.disabled = true;
  navigator.getUserMedia(mediaStreamConstraints, gotLocalMediaStream, handleLocalMediaStreamError)
  trace('Requesting local stream.');
}
// 创建对等连接
function callAction() {
  callButton.disabled = true;
  hangupButton.disabled = false;

  trace('Starting call.');
  startTime = window.performance.now();

  const videoTracks = localStream.getVideoTracks();
  const audioTracks = localStream.getAudioTracks();
  if (videoTracks.length > 0) {
    trace(`Using video device: ${videoTracks[0].label}.`);
  }
  if (audioTracks.length > 0) {
    trace(`Using audio device: ${audioTracks[0].label}.`);
  }
  // 服务器配置
  const servers = null; 

  localPeerConnection = new RTCPeerConnection(servers);
  trace('Created local peer connection object localPeerConnection.');

  localPeerConnection.addEventListener('icecandidate', handleConnection);
  localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);

  remotePeerConnection = new RTCPeerConnection(servers);
  trace('Created remote peer connection object remotePeerConnection.');

  remotePeerConnection.addEventListener('icecandidate', handleConnection);
  remotePeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream);

  localPeerConnection.addStream(localStream);
  trace('Added local stream to localPeerConnection.');

  trace('localPeerConnection createOffer start.');
  localPeerConnection.createOffer(offerOptions)
    .then(createdOffer).catch(setSessionDescriptionError);
}
function hangupAction() {
  localPeerConnection.close();
  remotePeerConnection.close();
  localPeerConnection = null;
  remotePeerConnection = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
  trace('Ending call.');
}

startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);

function getOtherPeer(peerConnection) {
  return (peerConnection === localPeerConnection) ?
      remotePeerConnection : localPeerConnection;
}

function getPeerName(peerConnection) {
  return (peerConnection === localPeerConnection) ?
      'localPeerConnection' : 'remotePeerConnection';
}

function trace(text) {
  text = text.trim();
  const now = (window.performance.now() / 1000).toFixed(3);
  console.log(now, text);
}
复制代码

好吧,很长,我们来一起看看main.js到底做了什么

WebRTC使用RTCPeerConnection这个API来建立连接,在客户端(也就是我们所说的端对端中的端)之间传输媒体流,在这个例子中,两个RTCPeerConnection对象在同一个页面,这个例子没有什么实际的用处,但是却可以很好地说明这个API的工作流程

建议两个WebRTC用户之间的连接涉及三个任务:

每一个调用需要为每个端创建一个RTCPeerConnection对象
获取和分享网络信息:可能的连接端点,也就是ICE候选
获取和分享本地和远端的描述:SDP格式的本地媒体的元信息
复制代码

假设A和B想通过RTCPeerConnection建立视频一个聊天室

首先,A和B交换网络信息,查找候选项就是使用ICE框架查找网络接口和端口的过程

A创建一个RTCPeerConnection对象,绑定一个onicecandidate事件的处理器(addEventListener('icecandidate')),这个过程对应main.js下面的代码:

    let localPeerConnection;
    // servers在这个例子中并没有使用,servers是配置STUN and TURN s服务器的,后面我们会介绍这个两种服务器
    localPeerConnection = new RTCPeerConnection(servers);
    localPeerConnection.addEventListener('icecandidate', handleConnection);
    localPeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange);
复制代码

WebRTC是为了直接端对端而设计的,让用户可以使用最直接的路由进行连接,而不需要经过服务器,然而,WebRTC 需要处理真实世界中的网络:客户端应用需要穿透NAT和防火墙,端对端直接连接失败的时候需要回退处理,在这个过程,WebRTC使用STUN服务器来获取IP,TURN服务器来作为回退服务器以免端对端连接失败

A 调用getUserMedia()获取视频流,然后addStream(localPeerConnection.addStream):

navigator.getUserMedia(mediaStreamConstraints, gotLocalMediaStream, handleLocalMediaStreamError).
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // 采集到了视频流,可以开始调用了
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
复制代码

当网络候选项(candidate)变得可用的时候,onicecandidate的处理器会被调用 A发送序列化的数据给B, 在真实的应用中,这个过程(交换信令)需要一个信令服务器, 当然,在我们这个例子中, 两个RTCPeerConnection在同一个页面中, 可以直接沟通,不需要额外的信令传输 当B获取到A的candidate信息的时候, 他会调用addIceCandidate, 将candidate添加到候选中

function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}
复制代码

WebRTC的对等端也需要找出并交换本地和远程的音视频媒体信息, 比如编解码能力,使用会话描述协议也就是SDP,通过交换元数据,也就是offer和answer来达到交换媒体配置信息的目的

A运行RTCPeerConnection的createOffer()方法,返回一个promise,提供了一个RTCSessionDescription: A的本地会话描述

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
复制代码

如果Promise成功了,A通过setLocalDescription方法将promise成功返回的描述设置成自己的本地描述,并且将描述通过信令通道发送给B, B使用setRemoteDescription将A发送给他的描述设置成自己的远端描述,B运行RTCPeerConnection的createAnswer()方法,可以产生一个相匹配的描述,B将自己产生的描述设置为本地描述并且发送给A, A将其设置成自己的远端描述

function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}
复制代码

搞定!

最终demo

信令服务器

信令服务器也是web服务器,只是传输的不是普通的数据,而是信令,你可以根据自己的喜好选择服务器(如 Apache,Nginx 或 Nodejs), 这里使用node和搭配socket.io来搭建信令服务器

socket.io

官网,使用socekt.io可以非常容易的创建聊天室 用socket.io当信令服务器,后端是node, 前端是vue框架,搭建了一个聊天室,并且可以向聊天室的人发起视频聊天的demo,STUN是用的谷歌提供的免费服务器,TURN是我们公司的TURN服务器,这只是为了让demo可以运行,真实的项目需要自己搭建STUN和TRUN服务器,github项目地址

  • 克隆项目
  • npm install 安装依赖
  • node run start在本地的9000端口启动后端服务器
  • npm run dev在8080端口开启前端项目
  • 访问http://localhost:8080访问项目,输入用户名和密码,进入聊天室,在用户列表里面可以选择其他用户进行视频聊天(可以通过再开一个窗口,再输入用户名和密码来模拟多人)

图示说明:打开谷歌浏览器,开三个标签页,分别创建A,B, C三个用户,点击加入聊天室后点击开始采集本地视频, A, B, C的页面如下,

  • A

  • B
  • C

A页面切换到用户列表,可以看到有A,B和C三个用户,

点击B后面的互动按钮,给B发送视频互动请求

切换到B页面,发现B收到了A的互动邀请,点击接受

回到A页面,可以看到本人A和和A互动的用户B

再点击和C互动,C接受互动邀请,那么A现在就可以看到,A,B,C三路视频了,同理,B现在看到A和B两路视频,C看到C和A两路视频,你也可以让B用户和C用户进行互动,这样就是一个三人的视频会议了

此时, A页面如下

注意: 这个项目启动了两个本地node服务器,socket.io的访问地址是后端服务器也就是http://localhost:9000, 如果需要部署到线上,需要将src/APP.vue的joinRoom的方法中的socket的url替换成线上服务器的地址,

转载于:https://juejin.im/post/5cb81e7e6fb9a068985fb24e

WebRTC 简介 WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音通话或视频聊天的技术,是谷歌2010年以6820万美元收购Global IP Solutions公司而获得的一项技术WebRTC提供了实时音视频的核心技术,包括音视频的采集、编解码、网络传输、显示等功能,并且还支持跨平台:windows,linux,mac,android。 虽然WebRTC的目标是实现跨平台的Web端实时音视频通讯,但因为核心层代码的Native、高品质和内聚性,开发者很容易进行除Web平台外的移殖和应用。很长一段时间内WebRTC是业界能免费得到的唯一高品质实时音视频通讯技术。 为什么需要 WebRTC 开发者教程? 虽然WebRTC技术已经较为成熟,其集成了最佳的音/视频引擎,十分先进的codec,且包含了使用STUN、ICE、TURN、RTP-over-TCP的关键NAT和防火墙穿透等众多门槛并不低的技术。抛开音视频技术本身的复杂性外,要想找到合适的资料、完整的代码和库、配合合适的IDE和辅助工具能正常地实现编译和安装都非常的不容易,而这还只是个开始。没有靠谱的教程,你该怎么开始?那么地坑等在那,难道你打算一个一个趟过去? 本《WebRTC 零基础开发者教程》主要讲了什么 本文中提供下载的《WebRTC 零基础开发者教程》将以一个初学者的角度,从0开始逐步引导你掌握WebRTC开发的方方面面(当然,教程中更多的是操作性的内容,具体到技术原理和实现,显然不是本教程的讨论范畴)。 《WebRTC 零基础开发者教程》目录 1 工具 1.1 depot_tools 1.1.1 目标 1.1.2 Chromium 1.1.3 使用说明在这儿 1.1.4 下载 1.1.5 使用 1.1.6 具体使用例子 1.2 Gyp工具 1.3 Python工具 1.4 本地集成开发环境(IDE ) 1.4.1 Visual studio 1.4.2 Kdevelop 1.4.3 Eclipse 2 Webrtc 2.1 下载、编译 2.1.1 Windows下 2.1.2 ubuntu下编译 2.1.3 编译Android(只能在 linux 下) 3 webrtc开发 3.1 开发P2P视频软件需要处理的问题 3.1.1 用户列的获取、交换、信令的交换 3.1.2 P2P通信 3.1.3 多媒体处理 3.2 webrtc架构 3.2.1 WebRTC架构组件介绍 3.2.2 WebRTC核心模块API介绍 3.2.3 webRTC核心API详解 4 Libjingle详细介绍 4.1 重要组件 4.1.1 信号 4.1.2 线程和消息 4.1.3 名称转换 4.1.4 SSL支持 4.1.5 连接 4.1.6 传输,通道,连接 4.1.7 候选项 4.1.8 数据包 4.2 如何工作 4.2.1 Application模块 4.2.2 XMPP Messaging Component 模块 4.2.3 Session Logic and management commponent 模块 4.2.4 Peer to peer Component 模块 4.2.5 其他 4.3 建立libjingle应用程序 5 代码分析 5.1 音频通道建立过程 5.2 音频接收播放过程 5.3 视频接收播放过程 6 协议 6.1 XMPP协议 6.1.1 原理介绍 6.1.2 XMPP 协议网络架构 6.1.3 XMPP 协议的组成 6.1.4 Xmpp介绍 6.1.5 协议内容 6.2 Stun协议 6.2.1 P2P实现的原理 6.2.2 P2P的常用实现 6.2.3 Stun URI 6.2.4 内容 6.2.5 中文内容 6.2.6 开源服务器 6.2.7 公开的免费STUN服务器 6.3 Turn协议 6.3.1 概念 6.3.2 Turn uri 6.3.3 开源服务器工程 6.3.4 开源库 6.4 交互式连接建立(Interactive Connectivity Establishment) 6.4.1 IETF规格 6.4.2 开源工程 6.5 XEP-0166 Jingle 6.5.1 绪论 6.5.2 需求 6.6 Sctp协议 6.7 Rtp协议 7 附件 7.1 Gyp工具 7.2 Google test程序 7.3 Webrtc库介绍 7.4 webrtc代码相关基础知识 7.5 STUN和TURN技术浅析 7.6 基于ICE的VoIP穿越NAT改进方案 7.7 ubuntu安装使用stuntman 7.8 一个开源的ICE库——libnice介绍 7.9 4种利用TURN穿越对称型NAT方案的设计实现 7.10 基于ICE方式SIP信令穿透Symmetric_NAT技术研究
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值