【流媒體服務器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++ 進行一些系統的分析,包括整個流程的走向,調試等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章