目录
前言
介绍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是一个面向多方实时通信应用的最小可扩展Node.js信令框架。
它提供了一个服务器端Node.js模块和一个客户端JavaScript库。其主要目的是为应用程序提供轻松添加群聊、状态和多方多媒体功能的能力。与protoo.WebSocket 一样有着房间管理的有 Socket.IO
(消息体)
protoo定义了一个基于JSON请求、响应和通知的信令协议。由应用程序定义和扩展信令协议以及这些消息的内容,以实现所需的特性集。
{
request : true,
id : 12345678,
method : 'chatmessage',
data :
{
type : 'text',
value : 'Hi there!'
}
}
Success response 成功响应体
{
response : true,
id : 12345678,
ok : true,
data :
{
foo : 'lalala'
}
}
Error response 失败响应体
需要进行响应,主动发数据给服务端
{
notification : true,
method : 'chatmessage',
data :
{
foo : 'bar'
}
}
//nodeJs安装服务端库
npm install --save protoo-server
//代码中引用
const protooServer = require('protoo-server');
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 对象。 |
accept()在的connectionrequest事件内调用时创建WebSocketServer。它代表与客户端建立的WebSocket连接。如:
server.on('connectionrequest', (info, accept, reject) =>
{
//建立和远程客户端的连接
const protooWebSocketTransport = accept();
});
创建一个新的房间
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();
代表远端的一个连接实例,其实也可以理解成一个成员用户。
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())
//安装依赖库
npm install --save protoo-client
//nodejs使用库
const protooServer = require('protoo-server');
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
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
,则不会进行任何重新连接尝试。
下面将简单介绍源码中使用到的创建连接、创建房间。创建peer的地方
文件定位: 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);
});
});
}
文件定位: 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
});
}
文件定位: 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++ 进行一些系统的分析,包括整个流程的走向,调试等。