【流媒体服务器Mediasoup】 源码中使用到的 protoo.websocket 基本API讲解与使用(六)

目录

 

前言

介绍protoo.WebSocket

Messages protoo消息体介绍

    Request (请求格式)

 Response(响应体)

Notification(通知)

protoo-server 介绍

 WebSocketServer

WebSocketTransport

Room(房间)

Peer(房间成员)

protoo-client 介绍

 WebSocketTransport 

Peer(房间的参与者,客户端用户) 

MediaSoup中的 protoo.websocket 建立连接流程

WebSocket的连接

room的创建

Peer(成员)的创建

信令的接收与处理

小结


前言


上篇文章对【流媒体服务器Mediasoup】 NodeJs与C++信令通信详解及Linux下管道通信的详解(五),本章节主要对MediaSoup的客户端与服务端 源码中源码中 信令通讯使用的protoo.WebSocket 详解,以及整个信令的处理过程

      在下一篇文章中将继续对MediaSoup的源码进行分析和架构的讲解。

     protoo官网

介绍protoo.WebSocket

        protoo是一个面向多方实时通信应用的最小可扩展Node.js信令框架。
它提供了一个服务器端Node.js模块和一个客户端JavaScript库。其主要目的是为应用程序提供轻松添加群聊、状态和多方多媒体功能的能力。

        与protoo.WebSocket 一样有着房间管理的有 Socket.IO  

Messages protoo消息体介绍

(消息体)
protoo定义了一个基于JSON请求、响应和通知的信令协议。由应用程序定义和扩展信令协议以及这些消息的内容,以实现所需的特性集。

    Request (请求格式)

{
  request : true,
  id      : 12345678,
  method  : 'chatmessage',
  data    :
  {
    type  : 'text',
    value : 'Hi there!'
  }
}

 Response(响应体)

   Success response 成功响应体

{
  response : true,
  id       : 12345678,
  ok       : true,
  data     :
  {
    foo : 'lalala'
  }
}

  Error response  失败响应体

Notification(通知)

   需要进行响应,主动发数据给服务端

{
  notification : true,
  method       : 'chatmessage',
  data         :
  {
    foo : 'bar'
  }
}

protoo-server 介绍

//nodeJs安装服务端库
npm install --save protoo-server  
//代码中引用
const protooServer = require('protoo-server');

 WebSocketServer

const options =
{
  //允许的最大接收帧大小(以字节为单位)。单帧消息也将限于此最大值。
  maxReceivedFrameSize     : 960000, // 960 KBytes. 
  //允许的最大消息大小(对于分段消息),以字节为单位。
  maxReceivedMessageSize   : 960000,
  //是否对传出消息进行分段。如果为true,则邮件将自动分成最大为fragmentationThreshold字节的块
  fragmentOutgoingMessages : true,
  //在自动分段之前,帧的最大大小(以字节为单位)。
  fragmentationThreshold   : 960000
};

const server = new protooServer.WebSocketServer.Room(httpServer, options);

 具体options: https://github.com/theturtle32/WebSocket-Node/blob/master/docs/WebSocketServer.md#server-config-options

  当WebSocket客户端尝试连接到WebSocket服务器时触发事件。

server.on('connectionrequest', (info, accept, reject) =>
{
  //  info 是属于一个连链接的所有信息,可以根据info获取如一些url或者其他信息来判断处理相对应的业务逻辑
  if (something in info)
  {
    const transport = accept();
    // 创建一个房间和房间用户
    const peer = async room.createPeer('bob', transport);
  }
  else
  {
    reject(403, 'Not Allowed');
  }
});

 函数接收的参数:

参数 描述
info 具有有关连接尝试信息的对象。
accept 如果接受连接将调用的函数。
reject 如果拒绝连接,则调用该函数。

  info 是属于一个连链接的所有信息,可以根据info获取如一些url或者其他信息来判断处理相对应的业务逻辑

  info 对象象具有以下字段:

领域 描述
request 代表在Websocket握手期间收到的HTTP请求的Node.js 连接对象的信息。
origin HTTP请求中基础机地址的值 
socket Node.js  net.Socket 对象。

WebSocketTransport

 accept()在的connectionrequest事件内调用时创建WebSocketServer。它代表与客户端建立的WebSocket连接。如:

server.on('connectionrequest', (info, accept, reject) =>
{
    //建立和远程客户端的连接
    const protooWebSocketTransport = accept();
});

Room(房间)

   创建一个新的房间

const room = new protooServer.Room();

   返回房间里 多有的Peer(成员)

for (let peer of room.peers)
{
  console.log('peer id: %s', peer.id);
}
//------------------------
if(room.closed){
    //房间关闭了
}else{
    //房间没有关闭了
}

在此房间内创建用户。它解析为用户实例。如果给出了错误的参数,或者房间中已经有一个具有相同ID的用户,则它会拒绝。

close关闭房间并发出关闭事件。这个房间内的所有人也将被关闭,他们的关闭事件将被触发。

const peer = await room.createPeer('alice', transport);

//hasPeer如果存在会返回PeerId
if(room.hasPeer(peerId)){
   //房间内有这个人
}else{
   // 房间内没有这个人
}

//返回房间的为pereID的用户对象
const user= room.getPeer(peerId);

//关闭房间时触发的回调函数
room.on('close', () =>    
  {
    //DO SOMETINGS
  }
);

//关闭房间
room.close();

Peer(房间成员)

   代表远端的一个连接实例,其实也可以理解成一个成员用户。

            perr对象中主要有2个字段

            id       唯一标识

            data   可自定义数据

   发送信令数据给 指定的peer客户端

//请求信令
try
{
  const data = await peer.request('chicken', { foo: 'bar' });

  console.log('got response data:', data);
}
catch (error)
{
  console.error('request failed:', error);
}

//服务端主动下发 通知的信令
peer.notify('lalala', { foo: 'bar' });

 接收来自peer客户端的信令消息

peer.on('request', (request, accept, reject) =>
{
  if (根据 request 的一些信息做一些判断)
    accept({ foo: 'bar' });
  else
    reject(400, 'Not Here');
});

//关闭对等方及其底层传输,并发出关闭事件。
peer.close();
参数 描述
请求 一个protoo请求。
接受 如果接受请求,则调用该函数。
拒绝 如果拒绝请求,则调用该函数。

 accept函数具有以下参数:  

Parameter Default Description
[data] {} 响应的数据体

 rejuct函数具有以下参数:

Parameter Default Description
errorCode   错误码
[errorReason]   错误原因

or:

Parameter Default Description
error   错误的对象实例

 收到通知时 以及 关闭时

通过对对等方调用close()关闭peer、远程关闭基础传输或关闭时时激发的事件。

//接收到服务端通知
peer.on('notification', (notification) =>
{
  // Do something.
});

//客户端关闭时触发
peer.on('close', fn())

protoo-client 介绍

//安装依赖库
npm install --save protoo-client
//nodejs使用库
const protooServer = require('protoo-server');

 WebSocketTransport 

    WebSocketTransport创建WebSocket连接。

const transport = new protooClient.WebSocketTransport('wss://example.org');
Parameter Description
url WebSocket 连接的地址
[options] 包括websocket.W3CWebSocket的选项(除了requestUrl之外的所有选项)和一个retry参数

retry参数与给retry.operation()options对象匹配,并控制连接和重新连接尝试,如果options.retry未给出,则默认为以下值

{
  forever    : true //是否一直重连,默认为false。
  retries    : 10, //重试连接该操作的最大时间。默认值为10
  factor     : 2,  //重练的的次数
  minTimeout : 1 * 1000, //开始第一次连接之前的毫秒数。默认值为1000
  maxTimeout : 8 * 1000  //两次重连之间的最大毫秒数。默认值为Infinity。
};

Peer(房间的参与者,客户端用户) 

  创建本地peer

const peer = new protooClient.Peer(transport);
参数 描述
transport 一个WebSocketTransport实例。

 Peer对象中有data参数 ,可写的自定义对象,直到应用程序为止。

peer.data.bar = 1234;
console.log(peer.data.bar);

客户端中 Peer的 close、notify、request   函数等 和服务端的一样,这里不做过多讲解

  • peer.connected 字段标识对 客户端是否已连接到服务端。

连接传输时激发的事件

on('open', fn())

与服务器的连接失败时激发的事件(由于网络错误、未运行服务器、无法访问服务器地址等)。

客户端将尝试连接其重试选项中定义的次数。重试之后,关闭事件将触发。

on('failed', fn(currentAttempt))
参数 描述
currentAttempt 重新连接尝试(从1开始)。

 当已建立的连接突然关闭时激发的事件。peer将启动在其重试选项中定义的重新连接过程。
 peer将尝试重新连接其重试选项中定义的次数。重试之后,关闭事件将触发

on('disconnected', fn())

  如果close()是在服务器端中触发,服务器端peer或此客户端中的peer,则不会进行任何重新连接尝试。

 

MediaSoup中的 protoo.websocket 建立连接流程

下面将简单介绍源码中使用到的创建连接、创建房间。创建peer的地方

WebSocket的连接

    文件定位: mediasoup-demo/server/server.js

     runProtooWebSocketServer() 方法中主要对protoo socket的初始化,具体看源码

     方法中体现了创建socket server的实例,并监听客户端来的连接

/** 创建ProtooWebSocket地服务*/
async function runProtooWebSocketServer()
{
	//  创建实例  option参数参见上面的讲解
	protooWebSocketServer = new protoo.WebSocketServer(httpsServer,
		{
			maxReceivedFrameSize     : 960000, // 960 KBytes.
			maxReceivedMessageSize   : 960000,
			fragmentOutgoingMessages : true,
			fragmentationThreshold   : 960000
		});
	// 监听客户端连接
	protooWebSocketServer.on('connectionrequest', (info, accept, reject) =>
	{
	    ...省略部分代码
        //这里使用了一个同步队列,为了防止同一时刻创建相同的房间
		queue.push(async () =>
		{
			const room = await getOrCreateRoom({ roomId, forceH264, forceVP9 });
			// 确定客户端的请求,并生成一个连接Transport
			const protooWebSocketTransport = accept();
			room.handleProtooConnection({ peerId, protooWebSocketTransport });
		})
			.catch((error) =>
			{
				logger.error('room creation or room joining failed:%o', error);
				reject(error);
			});
	});
}

room的创建

   文件定位: mediasoup-demo/server/lib/Room.js

   在上述的 runProtooWebSocketServer() 方法中,调用        const room = await getOrCreateRoom({ roomId, forceH264, forceVP9 });

  最后再getOrCreateRoom中调用了Room类中的create 方法具体看下部分源码,

  当一个客户端连接首先会查看房间是否已经创建,如果没有创建那么就调用create 方法最后实例化了一个房间。

static async create({ mediasoupWorker, roomId, forceH264 = false, forceVP9 = false })
	{	
		// 创建 protoo room 实例 
		const protooRoom = new protoo.Room();
		 ....省略部分代码
        //并实例化一个封装的Room
		return new Room(
			{
				roomId,
				protooRoom,
				mediasoupRouter,
				audioLevelObserver,
				bot
			});
	}

Peer(成员)的创建

     文件定位: mediasoup-demo/server/lib/Room.js

    在上述的 runProtooWebSocketServer() 方法中,监听事件         

protooWebSocketServer.on('connectionrequest', (info, accept, reject) =>  {}

  方法中,当有一个客户端连接,先去判断房间是否存在,如果不存在则创建,走上面讲述的创建房间流程,

  这时候客户端连接相当于一个成员加入,当房间创建好的时候,需要在房间中添加这个连接进来的成员

 最后调到  room.handleProtooConnection({ peerId, protooWebSocketTransport }); 具体源码:

 从源码以及源码注释中可以看出整个清晰的逻辑

handleProtooConnection({ peerId, consume, protooWebSocketTransport })
	{
        //查看房间是否存在这个用户
		const existingPeer = this._protooRoom.getPeer(peerId);
        //如果存在则关闭此用户
		if (existingPeer)
		{
			existingPeer.close();
		}
		let peer;
		// 在此房间创建一个peer 成员
		try
		{
			peer = this._protooRoom.createPeer(peerId, protooWebSocketTransport);
		}
		catch (error)
		{
		 ....省略部分代码

		peer.on('request', (request, accept, reject) =>
		{
			//接收信令数据
			this._handleProtooRequest(peer, request, accept, reject)
				.catch((error) =>
				{
					logger.error('request failed:%o', error);

					reject(error);
				});
		});
		peer.on('close', () =>
		{
			// 通知其他用户 此用户已经退出
			if (peer.data.joined)
			{
				for (const otherPeer of this._getJoinedPeers({ excludePeer: peer }))
				{
					otherPeer.notify('peerClosed', { peerId: peer.id })
						.catch(() => {});
				}
			}
			// 遍历关闭用户所有的数据连接通道
			for (const transport of peer.data.transports.values())
			{
				transport.close();
			}
			// 退出去为房间最后一个人,则关闭此房间
			if (this._protooRoom.peers.length === 0)
			{
				this.close();
			}
		});
	}

 

信令的接收与处理

 文件定位: mediasoup-demo/server/lib/Room.js 

  async _handleProtooRequest(peer, request, accept, reject)

  源码中的信令不多,也可以自己添加自定义的信令

  针对目前demo,一个用户进入房间,信令的执行顺序为:

      /* 信令执行的顺序

        getRouterRtpCapabilities

        createWebRtcTransport   (收 传输通道)

        createWebRtcTransport    (发 传输通道)

        join

        connectWebRtcTransport

        connectWebRtcTransport

        produce

        produceData

        produceData

        */

async _handleProtooRequest(peer, request, accept, reject)
{
	    switch (request.method)
		{
            ...省略处理信令过程
        }
}

小结

      上述主要对protoo socket的一些Api进行简单的讲解,熟悉Api带入源码去分析显得更容易一些,protoo对node的支持是相当的好,在源码中的整个执行顺序也是比较明朗,对于定制源码也起到了关键的作用。

       在往后的博文中,将对信令的处理从上层到C++ 进行一些系统的分析,包括整个流程的走向,调试等。

 

你可能感兴趣的:(MediaSoup,音视频,后端开发,protoo,websocket,mediasoup)