# webrtc继续学习

先看socketio

# 发消息

  • socket.emit() 给本次连接发消息
  • io.in(room).emit() 给某个房间内所有人发消息
  • socket.to(room).emit() 除本连接外,给某个房间内所有人发消息
  • socket.broadcast.emit() 除本连接外,给所有人发消息

# 收到消息

  • S:socket.emit("action",data1,data2)//data可不需要,,也可以是个函数
  • C:socket.on("action",function(data1,data2){。。。})

# webrtc信令服务器

var https = require('https');
var express = require('express');
//https server
var https_server = https.createServer(options, app);
//bind socket.io with https_server
var io = socketIo.listen(https_server);

//connection
io.sockets.on('connection', (socket)=>{

	socket.on('message', (room, data)=>{
		socket.to(room).emit('message', room, data)//房间内所有人,除自己外
	});

	//该函数应该加锁
	socket.on('join', (room)=> {
		socket.join(room);//进入房间
		var myRoom = io.sockets.adapter.rooms[room];//获取房间
		var users = Object.keys(myRoom.sockets).length;//获取人数
		logger.log('the number of user in room is: ' + users);
		//在这里可以控制进入房间的人数,现在一个房间最多 3个人
		//为了便于客户端控制,如果是多人的话,应该将目前房间里
		//人的个数当做数据下发下去。
		if(users < 4) {
			socket.emit('joined', room, socket.id);	
			if (users > 1) {
				socket.to(room).emit('otherjoin', room);//除自己之外
			}
		}else {
			socket.leave(room);
			socket.emit('full', room, socket.id);	
		}
		io.in(room).emit('joined', room, socket.id)//房间内所有人
	 	//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点	
	});


});

注意:前端还需要引入socket.io.js

# webrtc传输基本知识

  • NAT (Network Address Translator)
    • 完全锥型(Full Cone NAT)
    • 地址限制锥型(Address Restricted Cone NAT)
    • 端口限制锥型(Port Restricted Cone NAT)
    • 对称型(Symmetric)
  • STUN (Simple Traversal of UDP Through NAT)
  • TURN (Traversal Using Relays around NAT)
  • ICE (Interactive Connectivity Establishment)

# NAT穿越原理

  • C1,C2向STUN发消息
  • 交换公网IP及端口
  • C1->C2,C2->C1,甚至端口猜测

# NAT类型检测

#### STUN协议 有两种

wireShark网络分析

# RTCPeerConnection

RTCPeerConnection 接口代表一个由本地计算机到远端的WebRTC连接。该接口提供了创建,保持,监控,关闭连接的方法的实现。

一个基本的RTCPeerConnection使用需要协调本地机器以及远端机器的连接,它可以通过在两台机器间生成Session Description的数据交换协议来实现。呼叫方发送一个offer(请求),被呼叫方发出一个answer(应答)来回答请求。双方-呼叫方以及被呼叫方,最开始的时候都要建立他们各自的RTCPeerConnection对象。

mdn文档 (opens new window)

pc = new RTCPeerConnection([configuration])
  • 媒体协商
  • Straem/Track
  • 传输相关方法
  • 统计相关方法

# 媒体协商过程

  • createOffer
  • createAnswer
  • setLocalDescription
  • setRemoteDescription
aPromise = myPeerConnection.createOffer([options])

aPromise = myPeerConnection.createAnswer([options])

aPromise = myPeerConnection.setLocalDescription(sessionDescription)

aPromise = myPeerConnection.setRemoteDescription(sessionDescription)
  • addTrack
  • removeTrack
rtpSender =myPc.addTrack(track,stream...)
//track 添加媒体鬼类型
myPc.remoteTrack(rtpSender)

# 协商事件

  • onnegotiationneeded 是收到negotiationneeded 事件时调用的事件处理器, 浏览器发送该事件以告知在将来某一时刻需要协商。
  • onicecandidate 是收到 icecandidate 事件时调用的事件处理器.。当一个 RTCICECandidate 对象被添加时,这个事件被触发
<html>
	<head>
		<title>RTCPeerConnection</title>
		<link rel="stylesheet" href="css/main.css"/>
	</head>
	<body>
		<div>
			<video id="localVideo" autoplay playsinline></video>
			<video id="remoteVideo" autoplay playsinline></video>
			<div>
				<button id="start">start</button>
				<button id="call">call</button>
				<button id="hangup">hang up</button>
			</div>
		</div>

		<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

	</body>
</html>
<script>
'use strict'

var localVideo = document.querySelector('video#localVideo');
var remoteVideo = document.querySelector('video#remoteVideo');
var btnStart = document.querySelector('button#start');
var btnCall = document.querySelector('button#call');
var btnHangUp= document.querySelector('button#hangup');


var localStream;
var pc1;
var pc2;

function gotMediaStream(stream){
	localVideo.srcObject = stream;
	localStream = stream;
}

function handleError(err){
	console.log("Failed to call getUserMedia", err);
}

function start(){
	var constraints = {
		video: true,
		audio: false 
	}

	if(!navigator.mediaDevices ||
		!navigator.mediaDevices.getUserMedia){
		return;
	}else {
		navigator.mediaDevices.getUserMedia(constraints)
					.then(gotMediaStream)
					.catch(handleError);
	}

}

function gotAnswerDescription(desc){
	pc2.setLocalDescription(desc);

	//send sdp to caller
	//recieve sdp from callee
	
	pc1.setRemoteDescription(desc);

}

function gotLocalDescription(desc){
	pc1.setLocalDescription(desc);

	//send sdp to callee
	
	//receive sdp from caller 
 	pc2.setRemoteDescription(desc);	
	pc2.createAnswer().then(gotAnswerDescription)
			 .catch(handleError);
}

function gotRemoteStream(e){

	if(remoteVideo.srcObject !== e.streams[0]){
		remoteVideo.srcObject = e.streams[0];
	}
}

function call(){
	var offerOptions = {
		offerToReceiveAudio: 0,
		offerToReceiveVideo: 1 
	}

	pc1 = new RTCPeerConnection();
	pc1.onicecandidate = (e) => {
	
		// send candidate to peer
		// receive candidate from peer
		/**
		 * 当本机当前页面的 RTCPeerConnection 接收到一个从远端页面通过信号通道发来的新的 ICE 候选地址信息,
		 * 本机可以通过调用RTCPeerConnection.addIceCandidate() 来添加一个 ICE 代理。*/
		pc2.addIceCandidate(e.candidate)
			.catch(handleError);
		console.log('pc1 ICE candidate:', e.candidate);
	}

	pc1.iceconnectionstatechange = (e) => {
		console.log(`pc1 ICE state: ${pc.iceConnectionState}`);
		console.log('ICE state change event: ', e);
	}


	pc2 = new RTCPeerConnection();
	pc2.onicecandidate = (e)=> {
	
		// send candidate to peer
		// receive candidate from peer

		pc1.addIceCandidate(e.candidate)
			.catch(handleError);
		console.log('pc2 ICE candidate:', e.candidate);
	}

	pc2.iceconnectionstatechange = (e) => {
		console.log(`pc2 ICE state: ${pc.iceConnectionState}`);
		console.log('ICE state change event: ', e);
	}
	/**
	 * 是一个 EventHandler 此属性指定了在RTCPeerConnection接口上触发 track 事件时调用的方法。
	 * 该方法接收一个RTCTrackEvent类型的event对象,该event对象将在MediaStreamTrack被创建时
	 * 或者是关联到已被添加到接收集合的RTCRtpReceiver对象中时被发送。*/
	pc2.ontrack = gotRemoteStream;

	//add Stream to caller
	localStream.getTracks().forEach((track)=>{
		pc1.addTrack(track, localStream);
	});

	pc1.createOffer(offerOptions)
		.then(gotLocalDescription)
		.catch(handleError);

}


function hangup(){
	pc1.close();
	pc2.close();
	pc1 = null;
	pc2 = null;

}

btnStart.onclick = start;
btnCall.onclick = call;
btnHangUp.onclick = hangup;


</script>

# SDP

session description protocol;获取sdp的时机就是createOffer、createAnswer的时候

pc1.createOffer(offerOptions)
		.then(getOffer)
		.catch(handleOfferError);

function getOffer(desc){
	pc1.setLocalDescription(desc);
	offerSdpTextarea.value = desc.sdp

	//send desc to signal
	//receive desc from signal
	
	pc2.setRemoteDescription(desc);

	pc2.createAnswer()
		.then(getAnswer)
		.catch(handleAnswerError);

}

function getAnswer(desc){
	pc2.setLocalDescription(desc);
	answerSdpTextarea.value = desc.sdp

	//send desc to signal
	//receive desc from signal
	
	pc1.setRemoteDescription(desc);
}

# SDP规范

  • 会话层
  • 媒体层
最后更新: 6/2/2020, 4:25:50 PM