溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行基于Serverless與Websocket的聊天工具實現(xiàn)

發(fā)布時間:2021-10-12 14:23:27 來源:億速云 閱讀:128 作者:柒染 欄目:云計算

如何進行基于Serverless與Websocket的聊天工具實現(xiàn),很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

傳統(tǒng)業(yè)務實現(xiàn) Websocket 并不難,然而函數(shù)計算基本上都是事件驅(qū)動,不支持長鏈接操作。如果將函數(shù)計算與 API 網(wǎng)關結(jié)合,是否可以有 Websocket 的實現(xiàn)方案呢?

API 網(wǎng)關觸發(fā)器實現(xiàn) Websocket

WebSocket 協(xié)議是基于 TCP 的一種新的網(wǎng)絡協(xié)議。它實現(xiàn)了瀏覽器與服務器全雙工 (full-duplex) 通信,即允許服務器主動發(fā)送信息給客戶端。WebSocket 在服務端有數(shù)據(jù)推送需求時,可以主動發(fā)送數(shù)據(jù)至客戶端。而原有 HTTP 協(xié)議的服務端對于需推送的數(shù)據(jù),僅能通過輪詢或 long poll 的方式來讓客戶端獲得。

由于云函數(shù)是無狀態(tài)且以觸發(fā)式運行,即在有事件到來時才會被觸發(fā)。因此,為了實現(xiàn) WebSocket,云函數(shù) SCF 與 API 網(wǎng)關相結(jié)合,通過 API 網(wǎng)關承接及保持與客戶端的連接。您可以認為云函數(shù)與 API 網(wǎng)關一起實現(xiàn)了服務端。當客戶端有消息發(fā)出時,會先傳遞給 API 網(wǎng)關,再由 API 網(wǎng)關觸發(fā)云函數(shù)執(zhí)行。當服務端云函數(shù)要向客戶端發(fā)送消息時,會先由云函數(shù)將消息 POST 到 API 網(wǎng)關的反向推送鏈接,再由 API 網(wǎng)關向客戶端完成消息的推送。

具體的實現(xiàn)架構(gòu)如下:

如何進行基于Serverless與Websocket的聊天工具實現(xiàn)

對于 WebSocket 的整個生命周期,主要由以下幾個事件組成:

  • 連接建立:客戶端向服務端請求建立連接并完成連接建立;

  • 數(shù)據(jù)上行:客戶端通過已經(jīng)建立的連接向服務端發(fā)送數(shù)據(jù);

  • 數(shù)據(jù)下行:服務端通過已經(jīng)建立的連接向客戶端發(fā)送數(shù)據(jù);

  • 客戶端斷開:客戶端要求斷開已經(jīng)建立的連接;

  • 服務端斷開:服務端要求斷開已經(jīng)建立的連接。

對于 WebSocket 整個生命周期的事件,云函數(shù)和 API 網(wǎng)關的處理過程如下:

  • 連接建立:客戶端與 API 網(wǎng)關建立 WebSocket 連接,API 網(wǎng)關將連接建立事件發(fā)送給 SCF;

  • 數(shù)據(jù)上行:客戶端通過 WebSocket 發(fā)送數(shù)據(jù),API 網(wǎng)關將數(shù)據(jù)轉(zhuǎn)發(fā)送給 SCF;

  • 數(shù)據(jù)下行:SCF 通過向 API 網(wǎng)關指定的推送地址發(fā)送請求,API 網(wǎng)關收到后會將數(shù)據(jù)通過 WebSocket 發(fā)送給客戶端;

  • 客戶端斷開:客戶端請求斷開連接,API 網(wǎng)關將連接斷開事件發(fā)送給 SCF;

  • 服務端斷開:SCF 通過向 API 網(wǎng)關指定的推送地址發(fā)送斷開請求,API 網(wǎng)關收到后斷開 WebSocket 連接。

因此,云函數(shù)與 API 網(wǎng)關之間的交互,需要由 3 類云函數(shù)來承載:

  • 注冊函數(shù):在客戶端發(fā)起和 API 網(wǎng)關之間建立 WebSocket 連接時觸發(fā)該函數(shù),通知 SCF WebSocket 連接的 secConnectionID。通常會在該函數(shù)記錄 secConnectionID 到持久存儲中,用于后續(xù)數(shù)據(jù)的反向推送;

  • 清理函數(shù):在客戶端主動發(fā)起 WebSocket 連接中斷請求時觸發(fā)該函數(shù),通知 SCF 準備斷開連接的 secConnectionID。通常會在該函數(shù)清理持久存儲中記錄的該 secConnectionID;

  • 傳輸函數(shù):在客戶端通過 WebSocket 連接發(fā)送數(shù)據(jù)時觸發(fā)該函數(shù),告知 SCF 連接的 secConnectionID 以及發(fā)送的數(shù)據(jù)。通常會在該函數(shù)處理業(yè)務數(shù)據(jù)。例如,是否將數(shù)據(jù)推送給持久存儲中的其他 secConnectionID。

Websocket 功能實現(xiàn)

根據(jù)騰訊云官網(wǎng)提供的該功能的整體架構(gòu)圖:

如何進行基于Serverless與Websocket的聊天工具實現(xiàn)

這里我們可以使用對象存儲 COS 作為持久化的方案,當用戶建立鏈接存儲 ConnectionId 到 COS 中,當用戶斷開連接刪除該鏈接 ID。

其中注冊函數(shù):

# -*- coding: utf8 -*-
import os
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))


def main_handler(event, context):
    print("event is %s" % event)

    connectionID = event['websocket']['secConnectionID']

    retmsg = {}
    retmsg['errNo'] = 0
    retmsg['errMsg'] = "ok"
    retmsg['websocket'] = {
        "action": "connecting",
        "secConnectionID": connectionID
    }

    cosClient.put_object(
        Bucket=bucket,
        Body='websocket'.encode("utf-8"),
        Key=str(connectionID),
        EnableMD5=False
    )

    return retmsg

傳輸函數(shù):

# -*- coding: utf8 -*-
import os
import json
import requests
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

sendbackHost = os.environ.get("url")


def Get_ConnectionID_List():
    response = cosClient.list_objects(
        Bucket=bucket,
    )
    return [eve['Key'] for eve in response['Contents']]


def send(connectionID, data):
    retmsg = {}
    retmsg['websocket'] = {}
    retmsg['websocket']['action'] = "data send"
    retmsg['websocket']['secConnectionID'] = connectionID
    retmsg['websocket']['dataType'] = 'text'
    retmsg['websocket']['data'] = data
    requests.post(sendbackHost, json=retmsg)


def main_handler(event, context):
    print("event is %s" % event)

    connectionID_List = Get_ConnectionID_List()
    connectionID = event['websocket']['secConnectionID']
    count = len(connectionID_List)
    data = event['websocket']['data'] + "(===Online people:" + str(count) + "===)"
    for ID in connectionID_List:
        if ID != connectionID:
            send(ID, data)

    return "send success"

清理函數(shù):

# -*- coding: utf8 -*-
import os
import requests
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

sendbackHost = os.environ.get("url")


def main_handler(event, context):
    print("event is %s" % event)

    connectionID = event['websocket']['secConnectionID']

    retmsg = {}
    retmsg['websocket'] = {}
    retmsg['websocket']['action'] = "closing"
    retmsg['websocket']['secConnectionID'] = connectionID
    requests.post(sendbackHost, json=retmsg)

    cosClient.delete_object(
        Bucket=bucket,
        Key=str(connectionID),
    )

    return event

Yaml 文件如下:

Conf:
  component: "serverless-global"
  inputs:
    region: ap-guangzhou
    bucket: chat-cos-1256773370
    secret_id: 
    secret_key: 

myBucket:
  component: '@serverless/tencent-cos'
  inputs:
    bucket: ${Conf.bucket}
    region: ${Conf.region}

restApi:
  component: '@serverless/tencent-apigateway'
  inputs:
    region: ${Conf.region}
    protocols:
      - http
      - https
    serviceName: ChatDemo
    environment: release
    endpoints:
      - path: /
        method: GET
        protocol: WEBSOCKET
        serviceTimeout: 800
        function:
          transportFunctionName: ChatTrans
          registerFunctionName: ChatReg
          cleanupFunctionName: ChatClean


ChatReg:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatReg
    codeUri: ./code
    handler: reg.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

ChatTrans:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatTrans
    codeUri: ./code
    handler: trans.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

ChatClean:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatClean
    codeUri: ./code
    handler: clean.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

注意,這里需要先部署 API 網(wǎng)關。當部署完成,獲得回推地址,將回推地址以 url 的形式寫入到對應函數(shù)的環(huán)境變量中:

如何進行基于Serverless與Websocket的聊天工具實現(xiàn)

理論上應該是可以通過 ${restApi.url[0].internalDomain} 自動獲得到 url 的,但是我并沒有成功獲得到這個 url,只能先部署 API 網(wǎng)關,獲得到這個地址之后,再重新部署。

部署完成之后,我們可以編寫 HTML 代碼,實現(xiàn)可視化的 Websocket Client,其核心的 JavaScript 代碼為:

window.onload = function () {
    var conn;
    var msg = document.getElementById("msg");
    var log = document.getElementById("log");

    function appendLog(item) {
        var doScroll = log.scrollTop === log.scrollHeight - log.clientHeight;
        log.appendChild(item);
        if (doScroll) {
            log.scrollTop = log.scrollHeight - log.clientHeight;
        }
    }

    document.getElementById("form").onsubmit = function () {
        if (!conn) {
            return false;
        }
        if (!msg.value) {
            return false;
        }
        conn.send(msg.value);
        //msg.value = "";
		
		var item = document.createElement("div");
		item.innerText = "發(fā)送↑:";
		appendLog(item);
		
		var item = document.createElement("div");
		item.innerText = msg.value;
		appendLog(item);
		
        return false;
    };

    if (window["WebSocket"]) {
        //替換為websocket連接地址
        conn = new WebSocket("ws://service-01era6ni-1256773370.gz.apigw.tencentcs.com/release/");
        conn.onclose = function (evt) {
            var item = document.createElement("div");
            item.innerHTML = "<b>Connection closed.</b>";
            appendLog(item);
        };
        conn.onmessage = function (evt) {
			var item = document.createElement("div");
			item.innerText = "接收↓:";
			appendLog(item);
		
            var messages = evt.data.split('n');
            for (var i = 0; i < messages.length; i++) {
                var item = document.createElement("div");
                item.innerText = messages[i];
                appendLog(item);
            }
        };
    } else {
        var item = document.createElement("div");
        item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
        appendLog(item);
    }
};

完成之后,我們打開兩個頁面,進行測試:

如何進行基于Serverless與Websocket的聊天工具實現(xiàn)

通過云函數(shù) + API 網(wǎng)關進行 Websocket 的實踐,絕對不僅僅是一個聊天工具這么簡單,它可以用在很多方面,例如通過 Websocket 進行實時日志系統(tǒng)的制作等。

單獨的函數(shù)計算,僅僅是一個計算平臺,只有和周邊的 BaaS 結(jié)合,才能展示出 Serverless 架構(gòu)的價值和真正的能力。這也是為什么很多人說 Serverless=FaaS+BaaS 的一個原因。

期待更多小伙伴,可以通過 Serverless 架構(gòu),創(chuàng)造出更多有趣的應用。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI