音視頻流媒體開發(fā)【七十八】- WebRTC5-Nodejs實(shí)戰(zhàn)

音視頻流媒體開發(fā)-目錄
iOS知識(shí)點(diǎn)-目錄
Android-目錄
Flutter-目錄
數(shù)據(jù)結(jié)構(gòu)與算法-目錄
uni-pp-目錄

5. Nodejs實(shí)戰(zhàn)

對(duì)于我們WebRTC項(xiàng)目而言放仗,nodejs主要是實(shí)現(xiàn)信令服務(wù)器的功能赃蛛,客戶端和服務(wù)器端的交互我們選擇websocket作為通信協(xié)議,所以該章節(jié)的實(shí)戰(zhàn)以websocket的使用為主载城。

web客戶端的websocket和nodejs服務(wù)器端的websocket有一定的差別,所以我們分開兩部分進(jìn)行講解权埠。

5.1 web客戶端 websocket

WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議沈贝。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單眶蕉,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)填帽。在 WebSocketAPI 中蛛淋,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接盲赊,并進(jìn)行雙向數(shù)據(jù)傳輸铣鹏。

在 WebSocket API 中,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作哀蘑,然后诚卸,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送绘迁。

現(xiàn)在合溺,很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是 Ajax 輪詢缀台。輪詢是在特定的的時(shí)間間隔(如每1秒)棠赛,由瀏覽器對(duì)服務(wù)器發(fā)出HTTP請(qǐng)求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器膛腐。這種傳統(tǒng)的模式帶來很明顯的缺點(diǎn)睛约,即瀏覽器需要不斷的向服務(wù)器發(fā)出請(qǐng)求,然而HTTP請(qǐng)求可能包含較長(zhǎng)的頭部哲身,其中真正有效的數(shù)據(jù)可能只是很小的一部分辩涝,顯然這樣會(huì)浪費(fèi)很多的帶寬等資源。

HTML5 定義的 WebSocket 協(xié)議勘天,能更好的節(jié)省服務(wù)器資源和帶寬怔揩,并且能夠更實(shí)時(shí)地進(jìn)行通訊。

瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請(qǐng)求脯丝,連接建立以后商膊,客戶端和服務(wù)器端就可以通過TCP 連接直接交換數(shù)據(jù)。

當(dāng)你獲取 Web Socket 連接后宠进,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù)晕拆,并通過 onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)。

以下 API 用于創(chuàng)建 WebSocket 對(duì)象砰苍。

var Socket = new WebSocket(url, [protocol] );

以上代碼中的第一個(gè)參數(shù) url, 指定連接的 URL潦匈。第二個(gè)參數(shù) protocol 是可選的,指定了可接受的子協(xié)議赚导。

WebSocket 屬性

以下是 WebSocket 對(duì)象的屬性。假定我們使用了以上代碼創(chuàng)建了 Socket 對(duì)象:

image.png
WebSocket 事件

以下是 WebSocket 對(duì)象的相關(guān)事件赤惊。假定我們使用了以上代碼創(chuàng)建了 Socket 對(duì)象:

image.png
WebSocket 方法

以下是 WebSocket 對(duì)象的相關(guān)方法吼旧。假定我們使用了以上代碼創(chuàng)建了 Socket 對(duì)象:

為了建立一個(gè) WebSocket 連接,客戶端瀏覽器首先要向服務(wù)器發(fā)起一個(gè) HTTP 請(qǐng)求未舟,這個(gè)請(qǐng)求和通常的 HTTP 請(qǐng)求不同圈暗,包含了一些附加頭信息掂为,其中附加頭信息"Upgrade: WebSocket"表明這是一個(gè)申請(qǐng)協(xié)議升級(jí)的 HTTP 請(qǐng)求,服務(wù)器端解析這些附加的頭信息然后產(chǎn)生應(yīng)答信息返回給客戶端员串,客戶端和服務(wù)器端的 WebSocket 連接就建立起來了勇哗,雙方就可以通過這個(gè)連接通道自由的傳遞信息,并且這個(gè)連接會(huì)持續(xù)存在直到客戶端或者服務(wù)器端的某一方主動(dòng)的關(guān)閉連接寸齐。

5.2 Nodejs服務(wù)器 websocket

Nodejs教程

簡(jiǎn)單的說 Node.js 就是運(yùn)行在服務(wù)端的 JavaScript欲诺。

服務(wù)器端使用websocket需要安裝nodejs-websocket

cd 工程目錄
# 此刻我們需要執(zhí)行命令:
sudo npm init
#創(chuàng)建package.json文件,系統(tǒng)會(huì)提示相關(guān)配置渺鹦,也可以使用命令:
sudo npm init ‐y
sudo npm install nodejs‐websocket

官方參考

我們只要關(guān)注:
(1)如何創(chuàng)建websocket服務(wù)器扰法,通過createServer和listen接口;
(2)如何判斷有新的連接進(jìn)來毅厚,createServer的回調(diào)函數(shù)判斷塞颁;
(3)如何判斷關(guān)閉事件,通過on("close", callback) 事件的回調(diào)函數(shù)吸耿;
(4)如何判斷接收到數(shù)據(jù)祠锣,通過on("text", callkback)事件的回調(diào)函數(shù);
(5)如何判斷接收異常咽安,通過on("error", callkback)事件的回調(diào)函數(shù)伴网;
(6)如何主動(dòng)發(fā)送數(shù)據(jù),調(diào)用sendText

參考代碼

var ws = require("nodejs‐websocket")

// Scream server example: "hi" ‐> "HI!!!"
var server = ws.createServer(function (conn) {
  console.log("New connection")
  conn.on("text", function (str) { // 收到數(shù)據(jù)的響應(yīng)
    console.log("Received "+str)
    conn.sendText(str.toUpperCase()+"!!!") // 發(fā)送
  })
  conn.on("close", function (code, reason) { // 關(guān)閉時(shí)的響應(yīng)
    console.log("Connection closed")
  })
  conn.on("error", function (err) { // 出錯(cuò)
    console.log("error:" + err);
  });
}).listen(8001)

5.3 websocket聊天室實(shí)戰(zhàn)

效果展示+框架分析
效果展示
客服端

框架分析

消息類型分為三種:

  1. enter:新人進(jìn)入 (藍(lán)色字體顯示)
  2. message:普通聊天信息 (黑色字體顯示)
  3. leave:有人離開 (紅色字體顯示)

服務(wù)器在收到某個(gè)客戶端的消息(message+enter+leave)板乙,然后將其廣播給所有的客戶端(包括發(fā)送者)是偷。

客戶端代碼

目錄和文件名:05/5.3/client.html

<html>

<body>
  <h1>Websocket簡(jiǎn)易聊天</h1>
  <div id="app">
    <input id="sendMsg" type="text" />
    <button id="submitBtn">發(fā)送</button>
  </div>
</body>

<script type="text/javascript">
  //在頁面顯示聊天內(nèi)容
  function showMessage(str, type) {
    var div = document.createElement("div");
    div.innerHTML = str;
    if (type == "enter") {
    div.style.color = "blue";
    } else if (type == "leave") {
    div.style.color = "red";
    }
    document.body.appendChild(div);
}

//新建一個(gè)websocket
var websocket = new WebSocket("[ws://192.168.221.132:8010");](ws://192.168.221.132:8010/)
//打開websocket連接
websocket.onopen = function () {
  console.log("已經(jīng)連上服務(wù)器‐‐‐‐");
  document.getElementById("submitBtn").onclick = function () {
     var txt = document.getElementById("sendMsg").value;
     if (txt) {
      //向服務(wù)器發(fā)送數(shù)據(jù)
      websocket.send(txt);
    }
  };
};

//關(guān)閉連接
websocket.onclose = function () {
  console.log("websocket close");
};

//接收服務(wù)器返回的數(shù)據(jù)
websocket.onmessage = function (e) {
  var mes = JSON.parse(e.data); // json格式
  showMessage(mes.data, mes.type);
};
</script>

</html>
服務(wù)器端代碼

目錄和文件名:05/5.3/server.js

var ws = require("nodejs‐websocket")
var port = 8010;
var user = 0;

// 創(chuàng)建一個(gè)連接
var server = ws.createServer(function (conn) {
  console.log("創(chuàng)建一個(gè)新的連接‐‐‐‐‐‐‐‐");
  user++;
  conn.nickname="user" + user;
  conn.fd="user" + user;
  var mes = {};
  mes.type = "enter";
  mes.data = conn.nickname + " 進(jìn)來啦"
  broadcast(JSON.stringify(mes)); // 廣播

  //向客戶端推送消息
  conn.on("text", function (str) {
    console.log("回復(fù) "+str)
    mes.type = "message";
    mes.data = conn.nickname + " 說: " + str;
    broadcast(JSON.stringify(mes));
  });

  //監(jiān)聽關(guān)閉連接操作
  conn.on("close", function (code, reason) {
    console.log("關(guān)閉連接");
    mes.type = "leave";
    mes.data = conn.nickname+" 離開了"
    broadcast(JSON.stringify(mes));
  });

  //錯(cuò)誤處理
  conn.on("error", function (err) {
    console.log("監(jiān)聽到錯(cuò)誤");
    console.log(err);
  });
}).listen(port);

function broadcast(str){
  server.connections.forEach(function(connection){
    connection.sendText(str);
  })
}

5.4 Map實(shí)戰(zhàn)

因?yàn)樾帕罘?wù)器使用map管理房間,所以我們先做個(gè)小練習(xí)募逞。

主要涉及put/get/remove/size等操作蛋铆。

目錄和文件名:05/5.4/map.js

/** ‐‐‐‐‐ ZeroRTCMap ‐‐‐‐‐ */
var ZeroRTCMap = function () {
  this._entrys = new Array();
  // 插入
  this.put = function (key, value) {
    if (key == null || key == undefined) {
      return;
    }
    var index = this._getIndex(key);
    if (index == ‐1) {
      var entry = new Object();
    entry.key = key;
    entry.value = value;
      this._entrys[this._entrys.length] = entry;
    } else {
      this._entrys[index].value = value;
    }
  };
  // 根據(jù)key獲取value
  this.get = function (key) {
    var index = this._getIndex(key);
    return (index != ‐1) ? this._entrys[index].value : null;
  };

  // 移除key‐value
  this.remove = function (key) {
    var index = this._getIndex(key);
     if (index != ‐1) {
       this._entrys.splice(index, 1);
     }
  };

  // 清空map
  this.clear = function () {
    this._entrys.length = 0;
  };

  // 判斷是否包含key
  this.contains = function (key) {
    var index = this._getIndex(key);
    return (index != ‐1) ? true : false;
  };

  // map內(nèi)key‐value的數(shù)量
  this.size = function () {
    return this._entrys.length;
  };

  // 獲取所有的key
  this.getEntrys = function () {
    return this._entrys;
  };

  // 內(nèi)部函數(shù)
  this._getIndex = function (key) {
    if (key == null || key == undefined) {
      return ‐1;
    }
    var _length = this._entrys.length;
    for (var i = 0; i < _length; i++) {
      var entry = this._entrys[i];
      if (entry == null || entry == undefined) {
        continue;
      }
      if (entry.key === key) {// equal
        return i;
      }
    }
    return ‐1;
   };
}

function Client(uid, conn, roomId) {
 this.uid = uid; // 用戶所屬的id
 this.conn = conn; // uid對(duì)應(yīng)的websocket連接
  this.roomId = roomId;
  console.log('uid:' + uid +', conn:' + conn + ', roomId: ' + roomId);
}

var roomMap = new ZeroRTCMap();

// Math.random() 返回介于 0(包含) ~ 1(不包含) 之間的一個(gè)隨機(jī)數(shù):
// toString(36)代表36進(jìn)制,其他一些也可以放接,比如toString(2)刺啦、toString(8),代表輸出為二進(jìn)制和八進(jìn)制纠脾。最高支持幾進(jìn)制
// substr(2) 舍去0/1位置的字符
console.log('\n\n‐‐‐‐‐‐‐‐‐‐Math.random() ‐‐‐‐‐‐‐‐‐‐');
var randmo = Math.random();
console.log('Math.random() = ' + randmo);
console.log('Math.random().toString(10) = ' + randmo.toString(10));
console.log('Math.random().toString(36) = ' + randmo.toString(36));
console.log('Math.random().toString(36).substr(0) = ' + randmo.toString(36).substr(0));
console.log('Math.random().toString(36).substr(1) = ' + randmo.toString(36).substr(1));
console.log('Math.random().toString(36).substr(2) = ' + randmo.toString(36).substr(2));

console.log('\n\n‐‐‐‐‐‐‐‐‐‐create client ‐‐‐‐‐‐‐‐‐‐');
var roomId = 100;
var uid1 = Math.random().toString(36).substr(2);
var conn1 = 100;
var client1 = new Client(uid1, conn1, roomId);
var uid2 = Math.random().toString(36).substr(2);
var conn2 = 101;
var client2 = new Client(uid2, conn2, roomId);

// 插入put
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐put‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap put client1');
roomMap.put(uid1, client1);
console.log('roomMap put client2');
roomMap.put(uid2, client2);
console.log('roomMap size:' + roomMap.size());

// 獲取get
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐get‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
var client = null;
var uid = uid1;
client = roomMap.get(uid);
if(client != null) {
  console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
} else {
  console.log("can't find the client of " + uid);
}

uid = '123345';
client = roomMap.get(uid);
if(client != null) {
  console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
} else {
  console.log("can't find the client of " + uid);
}

console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐traverse‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
// 遍歷map
var clients = roomMap.getEntrys();
for (var i in clients) {
  let uid = clients[i].key;
  let client = roomMap.get(uid);
  console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
}

console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐remove‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap remove uid1');
roomMap.remove(uid1);
console.log('roomMap size:' + roomMap.size());

console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐clear‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap clear all');
roomMap.clear();
console.log('roomMap size:' + roomMap.size());
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玛瘸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苟蹈,更是在濱河造成了極大的恐慌糊渊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慧脱,死亡現(xiàn)場(chǎng)離奇詭異渺绒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門宗兼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躏鱼,“玉大人,你說我怎么就攤上這事殷绍∪究粒” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵主到,是天一觀的道長(zhǎng)茶行。 經(jīng)常有香客問我,道長(zhǎng)镰烧,這世上最難降的妖魔是什么拢军? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮怔鳖,結(jié)果婚禮上茉唉,老公的妹妹穿的比我還像新娘。我一直安慰自己结执,他們只是感情好度陆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著献幔,像睡著了一般懂傀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜡感,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天蹬蚁,我揣著相機(jī)與錄音,去河邊找鬼郑兴。 笑死犀斋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的情连。 我是一名探鬼主播叽粹,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼却舀!你這毒婦竟也來了虫几?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤挽拔,失蹤者是張志新(化名)和其女友劉穎辆脸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體螃诅,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡每强,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年始腾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了州刽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空执。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖穗椅,靈堂內(nèi)的尸體忽然破棺而出辨绊,到底是詐尸還是另有隱情,我是刑警寧澤匹表,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布门坷,位于F島的核電站,受9級(jí)特大地震影響袍镀,放射性物質(zhì)發(fā)生泄漏默蚌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一苇羡、第九天 我趴在偏房一處隱蔽的房頂上張望绸吸。 院中可真熱鬧,春花似錦设江、人聲如沸锦茁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽码俩。三九已至,卻和暖如春歼捏,著一層夾襖步出監(jiān)牢的瞬間稿存,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工瞳秽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓣履,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓寂诱,卻偏偏與公主長(zhǎng)得像拂苹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痰洒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容