日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

前言

在疫情期間,上班族開(kāi)啟了遠(yuǎn)程辦公,體驗(yàn)了各種遠(yuǎn)程辦公軟件。老師做起了主播,學(xué)生們感受到了被釘釘支配的恐懼,歌手們開(kāi)啟了在線(xiàn)演唱會(huì),許多綜藝節(jié)目也變成了在線(xiàn)直播。在這全民互動(dòng)直播的時(shí)期,我們來(lái)聊聊互動(dòng)直播中的即時(shí)通訊技術(shù)在前端中的使用。

即時(shí)通訊技術(shù)

即時(shí)通訊(Instant Messaging,簡(jiǎn)稱(chēng)IM)是一個(gè)實(shí)時(shí)通信系統(tǒng),允許兩人或多人使用網(wǎng)絡(luò)實(shí)時(shí)的傳遞文字消息、文件、語(yǔ)音與視頻交流。如何來(lái)實(shí)現(xiàn)呢,通常我們會(huì)使用服務(wù)器推送技術(shù)來(lái)實(shí)現(xiàn)。常見(jiàn)的有以下幾種實(shí)現(xiàn)方式。

輪詢(xún)(polling)

這是一種我們幾乎都用到過(guò)的的技術(shù)實(shí)現(xiàn)方案。客戶(hù)端和服務(wù)器之間會(huì)一直進(jìn)行連接,每隔一段時(shí)間就詢(xún)問(wèn)一次。前端通常采取setInterval或者setTimeout去不斷的請(qǐng)求服務(wù)器數(shù)據(jù)。

缺點(diǎn):輪詢(xún)時(shí)間通常是死的,太長(zhǎng)就不是很實(shí)時(shí),太短增加服務(wù)器端的負(fù)擔(dān)。不斷的去請(qǐng)求沒(méi)有意義的更新的數(shù)據(jù)也是一種浪費(fèi)服務(wù)器資源的做法。

長(zhǎng)輪詢(xún)(long-polling)

客戶(hù)端發(fā)送一個(gè)請(qǐng)求到服務(wù)端,如果服務(wù)端沒(méi)有新的數(shù)據(jù),就保持住這個(gè)連接直到有數(shù)據(jù)。一旦服務(wù)端有了數(shù)據(jù)(消息)給客戶(hù)端,它就使用這個(gè)連接發(fā)送數(shù)據(jù)給客戶(hù)端。接著連接關(guān)閉。

缺點(diǎn):占較多的內(nèi)存資源與請(qǐng)求數(shù)。

iframe流

iframe流就是在瀏覽器中動(dòng)態(tài)載入一個(gè)iframe, 讓它的地址指向請(qǐng)求的服務(wù)器的指定地址(就是向服務(wù)器發(fā)送了一個(gè)http請(qǐng)求),然后在瀏覽器端創(chuàng)建一個(gè)處理數(shù)據(jù)的函數(shù),在服務(wù)端通過(guò)iframe與瀏覽器的長(zhǎng)連接定時(shí)輸出數(shù)據(jù)給客戶(hù)端,iframe頁(yè)面接收到這個(gè)數(shù)據(jù)就會(huì)將它解析成代碼并傳數(shù)據(jù)給父頁(yè)面從而達(dá)到即時(shí)通訊的目的。

缺點(diǎn):兼容性與用戶(hù)體驗(yàn)不好。服務(wù)器維護(hù)一個(gè)長(zhǎng)連接會(huì)增加開(kāi)銷(xiāo)。一些瀏覽器的的地址欄圖標(biāo)會(huì)一直轉(zhuǎn)菊花。

Server-sent Events(sse)

sse與長(zhǎng)輪詢(xún)機(jī)制類(lèi)似,區(qū)別是每個(gè)連接不只發(fā)送一個(gè)消息。客戶(hù)端發(fā)送一個(gè)請(qǐng)求,服務(wù)端保持這個(gè)連接直到有新消息發(fā)送回客戶(hù)端,仍然保持著連接,這樣連接就可以消息的再次發(fā)送,由服務(wù)器單向發(fā)送給客戶(hù)端。

缺點(diǎn):兼容性不好(IE,Edge不支持);服務(wù)器只能單向推送數(shù)據(jù)到客戶(hù)端。

WebSocket

html5 WebSocket規(guī)范定義了一種API,使Web頁(yè)面能夠使用WebSocket協(xié)議與遠(yuǎn)程主機(jī)進(jìn)行雙向通信。與輪詢(xún)和長(zhǎng)輪詢(xún)相比,巨大減少了不必要的網(wǎng)絡(luò)流量和等待時(shí)間。

WebSocket屬于應(yīng)用層協(xié)議。它基于TCP傳輸協(xié)議,并復(fù)用HTTP的握手通道。但不是基于HTTP協(xié)議的,只是在建立連接之前要借助一下HTTP,然后在第一次握手是升級(jí)協(xié)議為ws或者wss。

互動(dòng)直播中的前端技術(shù)——即時(shí)通訊

 

缺點(diǎn):開(kāi)發(fā)成本高,需要額外做重連保活。

在互動(dòng)直播場(chǎng)景下,由于本身的實(shí)時(shí)性要求高,服務(wù)端與客戶(hù)端需要頻繁雙向通信,因此與它十分契合。

搭建自己的IM系統(tǒng)

上面簡(jiǎn)單的概述了下即時(shí)通訊的實(shí)現(xiàn)技術(shù),接下來(lái)我們就聊聊如何實(shí)現(xiàn)自己的IM系統(tǒng)。

從零開(kāi)始搭建IM系統(tǒng)還是一件比較復(fù)雜與繁瑣的事情。自己搭建推薦基于 socket.io 來(lái)實(shí)現(xiàn)。socket.io對(duì)即時(shí)通訊的封裝已經(jīng)很不錯(cuò)了,是一個(gè)比較成熟的庫(kù),對(duì)不同瀏覽器做了兼容,提供了各端的方案包括服務(wù)端,我們不用關(guān)心底層是用那種技術(shù)實(shí)現(xiàn)進(jìn)行數(shù)據(jù)的通信,當(dāng)然在現(xiàn)代瀏覽器種基本上是基于WebSocket來(lái)實(shí)現(xiàn)的。市面上也有不少I(mǎi)M云服務(wù)平臺(tái),比如 云信 ,借助第三方的服務(wù)也可以快速集成。下面就介紹下前端怎么基于socket.io集成開(kāi)發(fā)。

基礎(chǔ)的搭建

服務(wù)端集成socket.io(有JAVA版本的),服務(wù)端即成可以參考下 這里 ,客戶(hù)端使用socket.io-client集成。

參考socket.io官方api,訂閱生命周期與事件,通過(guò)訂閱的方式或來(lái)實(shí)現(xiàn)基礎(chǔ)功能。在回調(diào)函數(shù)執(zhí)行解析包裝等邏輯,最終拋給上層業(yè)務(wù)使用。

import io from 'socket.io-client';
import EventEmitter from 'EventEmitter';
class Ws extends EventEmitter {
    constructor (options) {
        super();
        //...
        this.init();
    }
    init () {
        const socket  = this.link = io('wss://x.x.x.x');
        socket.on('connect', this.onConnect.bind(this));
        socket.on('message', this.onMessage.bind(this));
        socket.on('disconnect', this.onDisconnect.bind.(this);
        socket.on('someEvent', this.onSomeEvent.bind(this));
    }
    onMessage(msg) {
        const data = this.parseData(msg);
        // ...
        this.$emit('message', data);
    }
}

消息收發(fā)

與服務(wù)器或者其他客戶(hù)端進(jìn)行消息通訊時(shí)通常會(huì)基于業(yè)務(wù)約定協(xié)議來(lái)封裝解析消息。由于都是異步行為,需要有唯一標(biāo)識(shí)來(lái)處理消息回調(diào)。這里用自增seq來(lái)標(biāo)記。

發(fā)送消息

class Ws extends EventEmitter {
    seq = 0;
    cmdTasksMap = {};
    // ...
    sendCmd(cmd, params) {
        return new Promise((resolve, reject) => {
            this.cmdTasksMap[this.seq] = {
                resolve,
                reject
            };
            const data = genPacket(cmd, params, this.seq++);
            this.link.send({ data });
        });
    }
}

接受消息

class Ws extends EventEmitter {
    // ...
    onMessage(packet) {
        const data = parsePacket(packet);
        if (data.seq) {
            const cmdTask = this.cmdTasksMap[data.seq];
            if (cmdTask) {
                if (data.body.code === 200) {
                    cmdTask.resolve(data.body);
                } else {
                    cmdTask.reject(data.body);
                }
                delete this.cmdTasksMap[data.seq];
            }
        }
    }
}

生產(chǎn)環(huán)境中優(yōu)化

上文只介紹了基礎(chǔ)功能的簡(jiǎn)單封裝,在生產(chǎn)環(huán)境中使用,還需要對(duì)考慮很多因素,尤其是在互動(dòng)直播場(chǎng)景中,禮物展示,麥序(進(jìn)行語(yǔ)音通話(huà)互動(dòng)的順序),聊天,群聊等都強(qiáng)依賴(lài)長(zhǎng)鏈接的穩(wěn)定性,下面就介紹一些兜底與優(yōu)化措施。

連接保持

為了穩(wěn)定建立長(zhǎng)鏈接與保持長(zhǎng)鏈接。采用了以下幾個(gè)手段:

  • 超時(shí)處理
  • 心跳包
  • 重連退避機(jī)制

超時(shí)處理

在實(shí)際使用中,并不一定每次發(fā)送消息都服務(wù)端都有響應(yīng),可能在客戶(hù)端已經(jīng)出現(xiàn)異常了,我們與服務(wù)端的通訊方式都是一問(wèn)一答。基于這一點(diǎn),我們可以增加超時(shí)邏輯來(lái)判斷是否是發(fā)送成功。然后基于回調(diào)上層進(jìn)行有友好提示,進(jìn)入異常處理。接下來(lái)就進(jìn)一步改造發(fā)送邏輯。

class Ws extends EventEmitter {
    // ...
    sendCmd(cmd, params) {
        return new Promise((resolve, reject) => {
            this.cmdTasksMap[this.seq] = {
                resolve,
                reject
            };
            // 加個(gè)定時(shí)器
            this.timeMap[this.seq] = setTimeout(() => {
                const err = new newTimeoutError(this.seq);
                reject({ ...err });
            }, CMDTIMEOUT);

            const data = genPacket(cmd, params, this.seq++);
            this.link.send({ data });
        });
    }
    onMessage(packet) {
        const data = parsePacket(packet);
        if (data.seq) {
            const cmdTask = this.cmdTasksMap[data.seq];
            if (cmdTask) {
                clearTimeout(this.timeMap[this.seq]);
                delete this.timeMap[this.seq];
                if (data.body.code === 200) {
                    cmdTask.resolve(data.body);
                } else {
                    cmdTask.reject(data.body);
                }
                delete this.cmdTasksMap[data.seq];
            }
        }
    }
}

心跳包

心跳包: 心跳包就是在客戶(hù)端和服務(wù)器間定時(shí)通知對(duì)方自己狀態(tài)的一個(gè)自己定義的命令字,按照一定的時(shí)間間隔發(fā)送,類(lèi)似于心跳,所以叫做心跳包。

心跳包是檢查長(zhǎng)鏈接存活的關(guān)鍵手段,在web端我們通過(guò)心跳包是否超時(shí)來(lái)判斷。TCP中已有 keepalive選項(xiàng) ,為什么要在應(yīng)用層加入心跳包機(jī)制?

  • tcp keepalive檢查連接是否存活
  • 應(yīng)用keepalive檢測(cè)應(yīng)用是否正常可響應(yīng)

舉個(gè)栗子: 服務(wù)端死鎖,無(wú)法處理任何業(yè)務(wù)請(qǐng)求。但是操作系統(tǒng)仍然可以響應(yīng)網(wǎng)絡(luò)層keepalive包。所以我們通常使用空內(nèi)容的心跳包并設(shè)定合適的發(fā)送頻率與超時(shí)時(shí)間來(lái)作為連接的保持的判斷。

如果服務(wù)端只認(rèn)心跳包作為連接存在判斷,那就在連接建立后定時(shí)發(fā)心跳就行。如果以收到包為判斷存活,那就在每次收到消息重置并起個(gè)定時(shí)器發(fā)送心跳包。

class Ws extends EventEmitter {
    // ...
     onMessage(packet) {
        const data = parsePacket(packet);
        if (data.seq) {
            const cmdTask = this.cmdTasksMap[data.seq];
            if (cmdTask) {
                clearTimeout(this.timeMap[this.seq]);
                if (data.body.code === 200) {
                    cmdTask.resolve(data.body);
                } else {
                    cmdTask.reject(data.body);
                }
                delete this.cmdTasksMap[data.seq];
            }
        }
        this.startHeartBeat();
    }
    startHeartBeat() {
        if (this.heartBeatTimer) {
            clearTimeout(this.heartBeatTimer);
            this.heartBeatTimer = null;
        }
        this.heartBeatTimer = setTimeout(() => {
            // 在sendCmd中指定heartbeat類(lèi)型seq為0,讓業(yè)務(wù)包連續(xù)編號(hào)
            this.sendCmd('heartbeat').then(() => {
                // 發(fā)送成功了就不管
            }).catch((e) => {
                this.heartBeatError(e);
            });
        }, HEARTBEATINTERVAL);
    }
}

重連退避機(jī)制

連不上了,重連,還連不上,重連,又連不上,重連。重連是一個(gè)保活的手段,但總不能一直重連吧,因此我們要用合理策去重連。

通常服務(wù)端會(huì)提供lbs(Location Based Services,LBS)接口,來(lái)提供最優(yōu)節(jié)點(diǎn),我們端上要做便是緩存這些地址并設(shè)定端上的重連退避機(jī)制。按級(jí)別次數(shù)通常會(huì)做以下處理。

  • 重連(超時(shí)<X次)
  • 換連接地址重連 (超時(shí)>=X次)
  • 重新獲取連接地址(X<MAX)
  • 上層處理(超過(guò)MAX)

在重連X次后選擇換地址,在一個(gè)地址失敗后,選擇重新去拿地址再去循環(huán)嘗試。具體的嘗試次數(shù)根據(jù)實(shí)際業(yè)務(wù)來(lái)定。當(dāng)然在一次又一次失敗中做好異常上報(bào),以便于分析解決問(wèn)題。

接受消息優(yōu)化

在高并發(fā)的場(chǎng)景下尤其是聊天室場(chǎng)景,我們要做一定的消息合并與緩沖,來(lái)避免過(guò)多的UI繪制與應(yīng)用阻塞。

因此要約定好解析協(xié)議,服務(wù)端與客戶(hù)端都做消息合并,并設(shè)置消息緩沖。示例如下:

Fn.startMsgFlushTimer = function () {
    this.msgFlushTimer = setTimeout(() => {
    const msgs = this.msgBuffer.splice(0, BUFFERSIZE);
    // 回調(diào)消息通知
    this.onmsgs(msgs);
    if (!this.msgBuffer.length) {
      this.msgFlushTimer = null;
    } else {
      this.startMsgFlushTimer();
    }
  }, MSGBUFFERINTERVAL);
};

流量?jī)?yōu)化

持久化存儲(chǔ)

在單聊場(chǎng)景中每次都同步全量的會(huì)話(huà),歷史消息等這是一個(gè)很大的代價(jià)。此外關(guān)閉web也是一種比較容易的操作(基本上就需要重新同步一次)。如果我們用增量的方式去同步就可以減少很多流量。實(shí)現(xiàn)增量同步自然想到了web存儲(chǔ)。

常用web存儲(chǔ)cookie,localStorage,sessionStorage不太能滿(mǎn)足我們持久化的場(chǎng)景,然而html5的indexedDB正常好滿(mǎn)足我們的需求。IndexedDB 內(nèi)部采用對(duì)象倉(cāng)庫(kù)(object store)存放數(shù)據(jù)。所有類(lèi)型的數(shù)據(jù)都可以直接存入,包括JavaScript對(duì)象。indexedDB的api直接用可能會(huì)比較難受,可以使用 Dexie.js , db.js 這些二次封裝的庫(kù)來(lái)實(shí)現(xiàn)業(yè)務(wù)的數(shù)據(jù)層。

在滿(mǎn)足持久化存儲(chǔ)后, 我們便可以用時(shí)間戳,來(lái)進(jìn)行增量同步,在收到消息通知時(shí),存儲(chǔ)到web數(shù)據(jù)庫(kù)。上層操作獲取數(shù)據(jù),優(yōu)先從數(shù)據(jù)庫(kù)獲取數(shù)據(jù),避免總是高頻率、高數(shù)據(jù)量的與服務(wù)器通訊。當(dāng)然敏感性信息不要存在數(shù)據(jù)庫(kù)或者增加點(diǎn)破解難度,畢竟所有web本地存儲(chǔ)都是能看到的。此外注意下存儲(chǔ)大小還是有限制的, 每種瀏覽器可能不一樣 ,但是遠(yuǎn)大于其他Web本地存儲(chǔ)了,只要該放云端的數(shù)據(jù)放云端(比如云消息),不會(huì)有太大問(wèn)題。

在編碼實(shí)現(xiàn)上,由于處理消息通知都是異步操作,要維護(hù)一個(gè)隊(duì)列保證 入庫(kù)時(shí)序 。此外要做好 降級(jí)方案 。

減少連接數(shù)

在Web桌面端的互動(dòng)直播場(chǎng)景,同一種頁(yè)面開(kāi)啟了多個(gè)tab訪(fǎng)問(wèn)應(yīng)該是很常見(jiàn)的。業(yè)務(wù)上也會(huì)有多端互踢操作,但是對(duì)Web場(chǎng)景如果只能一個(gè)頁(yè)面能進(jìn)行互動(dòng)那肯定是不行的,一不小心就不知道切到哪個(gè)tab上去了。所以通常會(huì)設(shè)置一個(gè)多端在線(xiàn)的最大數(shù),超過(guò)了就踢。因而一個(gè)瀏覽器建立7,8個(gè)長(zhǎng)鏈接是一件很尋常的事情,對(duì)于服務(wù)端資源也是一種極大的浪費(fèi)。

Web Worker可以為Web內(nèi)容在后臺(tái)線(xiàn)程中運(yùn)行腳本提供了一種簡(jiǎn)單的方法,線(xiàn)程可以執(zhí)行任務(wù)而不干擾用戶(hù)界面。并且可以將消息發(fā)送到創(chuàng)建它的JavaScript代碼, 通過(guò)將消息發(fā)布到該代碼指定的事件處理程序(反之亦然)。雖然Web Worker中不能使用DOM API,但是XHR,WebSocket這些通訊API并沒(méi)有限制(而且可以操作本地存儲(chǔ))。因此我們可以通過(guò)SharedWorker API創(chuàng)建一個(gè)執(zhí)行指定腳本來(lái)共享web worker來(lái)實(shí)現(xiàn)多個(gè)tab之前的通訊復(fù)用,來(lái)達(dá)到減少連接數(shù)的目的。在兼容性要求不那么高的場(chǎng)景可以嘗試一下。

小結(jié)

本文介紹了互動(dòng)直播中的即時(shí)通訊技術(shù)的在前端中應(yīng)用,并分享了自己在工作開(kāi)發(fā)中的一些經(jīng)驗(yàn),希望對(duì)您有所幫助,歡迎探討。

本文作者:吳杰

分享到:
標(biāo)簽:互動(dòng) 直播
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定