中山公众号开发,中山小程序开发,中山企业官网开发,中山软件开发,中山APP开发
公司动态
COMPANY DYNAMIC
行业资讯
COMPANY DYNAMIC
扫一扫出方案
Serverless 实战:利用云函数 + API 网关实现 Websocket 聊天工具
点击数:
2020-06-17 10:38:57

如果是传统技术栈想要实现 Websocket 会比较容易,但是函数计算由于不支持长连接操作,由事件驱动,所以实现起来会有难度。本文将结合函数计算与 API 网关,尝试由 Websocket 实现一个聊天工具。

API 网关触发器实现 Websocket

WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。WebSocket 在服务端有数据推送需求时,可以主动发送数据至客户端。而原有 HTTP 协议的服务端对于需推送的数据,仅能通过轮询或 long poll 的方式来让客户端获得。

由于云函数是无状态且以触发式运行,即在有事件到来时才会被触发,因此,为了实现 WebSocket,需要云函数与 API 网关相结合,通过 API 网关承接、保持与客户端的连接,可以认为 API 网关与 SCF 一起实现了服务端。当客户端有消息发出时,会先传递给 API 网关,再由 API 网关触发云函数执行。当服务端云函数要向客户端发送消息时,会先由云函数将消息 POST 到 API 网关的反向推送链接,再由 API 网关向客户端完成消息的推送。具体的实现架构如下:

Serverless实战:利用云函数 + API网关实现Websocket聊天工具

对于 WebSocket 的整个生命周期,主要由以下几个事件组成:

  • 连接建立:客户端向服务端请求建立连接并完成连接建立。

  • 数据上行:客户端通过已经建立的连接向服务端发送数据。

  • 数据下行:服务端通过已经建立的连接向客户端发送数据。

  • 客户端断开:客户端要求断开已经建立的连接。

  • 服务端断开:服务端要求断开已经建立的连接。

对于 WebSocket 整个生命周期的事件,云函数和 API 网关的处理过程如下:

  • 连接建立:客户端与 API 网关建立 WebSocket 连接,API 网关将连接建立事件发送给 SCF。

  • 数据上行:客户端通过 WebSocket 发送数据,API 网关将数据转发送给 SCF。

  • 数据下行:SCF 通过向 API 网关指定的推送地址发送请求,API 网关收到后会将数据通过 WebSocket 发送给客户端。

  • 客户端断开:客户端请求断开连接,API 网关将连接断开事件发送给 SCF。

  • 服务端断开:SCF 通过向 API 网关指定的推送地址发送断开请求,API 网关收到后断开 WebSocket 连接。

API 网关与 SCF 之间的交互需要由 3 类云函数来承载:

  • 注册函数:在客户端发起和 API 网关之间建立 WebSocket 连接时触发该函数,通知 SCF WebSocket 连接的 secConnectionID。通常会在该函数记录 secConnectionID 到持久存储中,用于后续数据的反向推送。

  • 清理函数:在客户端主动发起 WebSocket 连接中断请求时触发该函数,通知 SCF 准备断开连接的 secConnectionID。通常会在该函数清理持久存储中记录的该 secConnectionID。

  • 传输函数:在客户端通过 WebSocket 连接发送数据时触发该函数,告知 SCF 连接的 secConnectionID 以及发送的数据。通常会在该函数处理业务数据。例如,是否将数据推送给持久存储中的其他 secConnectionID。

Websocket 功能实现

下图是腾讯云官网提供的整体架构图:

Serverless实战:利用云函数 + API网关实现Websocket聊天工具

我们可以使用 COS(对象存储)作为持久化的方案,当用户建立链接存储 ConnectionId 到 COS 中,用户断开连接时删除该链接 Id。

注册函数:

复制代码
# -*- coding: utf8 -*-import osfrom qcloud_cos_v5 import CosConfigfrom qcloud_cos_v5 import CosS3Clientbucket = 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

传输函数:

复制代码
# -*- coding: utf8 -*-import osimport jsonimport requestsfrom qcloud_cos_v5 import CosConfigfrom qcloud_cos_v5 import CosS3Clientbucket = 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"

清理函数:

复制代码
# -*- coding: utf8 -*-import osimport requestsfrom qcloud_cos_v5 import CosConfigfrom qcloud_cos_v5 import CosS3Clientbucket = 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: ChatCleanChatReg:  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 网关,完成之后获得回推地址,将回推地址以 url 的形式写入到对应函数的环境变量中:

Serverless实战:利用云函数 + API网关实现Websocket聊天工具

从理论上来讲,这里的设计不是很合理,按道理我们是可以通过${restApi.url[0].internalDomain}自动获得 url,但是我并没有成功获得到 url,所以只能先部署 API 网关,获得地址之后,再重新部署。

部署完成之后,我们可以编写 HTML 代码实现可视化的 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 = " 发送↑:";		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实战:利用云函数 + API网关实现Websocket聊天工具

总结

通过云函数 + API 网关进行 Websocket 的实践,绝对不仅仅只是一个聊天工具,它可以实现很多功能,例如通过 Websocket 进行实时日志系统的制作等。

单独的函数计算仅仅是一个计算平台,只有和周边的 BaaS 结合才能展示出 Serverless 架构的价值和真正的能力、意义。这也是为什么很多人说 Serverless=FaaS+BaaS 的一个原因。

文章版权归极客邦科技 InfoQ 所有

节点互动(广东)科技有限公司, 一家专注于 APP开发 + 小程序开发 + 微信开发 + 系统开发 + 网站开发 的专业互联网应用服务提供商。5年实战开发经验,高校合作基地,多年行业深耕经验,节点互动助力传统行业快速转型,为众多企业提供创新性互联网应用产品。


推荐文章
教育小程序开发“狂魔”实录:用户一下涨了十几倍,如何扛?
人在家中,课在网上。特殊时期,“停课不停学”,上网课已经成为小学生和家长都“逃不过”的现实。 在学生和老师花样百出的上网课实录背后,有一群看不见的开发“狂魔”,他们也创造了“中国速度”。 ——回不去办公室就线上集合,复工时间紧就不
热门品牌实战数据总结:小红书&B站投放策略
B站和小红书历来是品牌们的“必争之地”,巨大的用户量引得品牌经常在上面投放广告。本文作者以B站和小红书两个网站为例,观察热门品牌的投放策略,希望对你有帮助。接上文“刚上市就月销过百万,细数5个新起国货品牌的崛起之道”,好多人在看完文章后问我这几个的国
多案例解析小程序的全场景跨端运营
今年的疫情让整个Q1变得很魔幻,大家各自的工作业务上可能也面临了不同的机遇和挑战。这期间,我们也见证了小程序爆发式的增长。从去年12月到今年2月,小程序的活跃用户数增长了37%,小程序的数量也增长了19%。 最新一份来自阿拉丁的微信小程序公布榜单中,月度top100小程序有53%的替换率,说明上榜的名单里有一半都...
微信的火热发展在于机遇和平台
【摘要】在互联网时代,新浪微博一推出就受到众多网民的欢迎,开始在网络世界上爆红起来。在微博发展期间,蹿红了许多微博明星以及草根达人,明显开微博不仅满足了粉丝想要了解明星更多信息的需求,同时也拉近了明显与粉丝的距离。但是腾讯显然不满新浪抢走了它的目标用
基于动态知识图谱的大规模数据集成技术
编者按数据烟囱、信息孤岛已成为政府、企业在数据应用中不可回避的问题,都在寻求各种方案打破现状,实现数据融合已成当务之急。百分点在经历多个大型数据集成项目洗礼后,已经达到了业界领先水平,通过利用动态知识谱图技术,将模型与数据进行解耦,在业务处于探索期或
从“三个方面”着手,搞定产品优化迭代
一个产品从被创造出来就要经历不断完善的过程,从1-N,不断完善细节。本文从三方面,讲解产品如何迭代。如果说互联网产品从0-1是开疆拓土,那么从1-N就应该是守土创富。其实产品从0-1不难,只要前期多花点时间把业务需求梳理清楚,做出来v1.0还是比较容
互联网运营之“种子用户”
在做一个新产品时,我们往往会特别关注种子用户。什么是种子用户?有什么作用?本文将从五个方面展开分析,对种子用户感兴趣的童鞋不要错过。从产品开始运营的第一天,我们就心心念念的用户—种子用户。我们举个例子,假设你的产品上线之后,第一次圈到了大概有一千个用
2021年中国电商市场的9大趋势预测
2020年受新冠疫情影响,线上电子商务行业飞速发展。社区团购,C2M趋势越来越明显,因此预计2021年将是中国电子商务市场充满创新和扩展的一年。各商家将采用新的举措和技术,并希望将业务扩展到低端城市。以下是2021年中国电子商务市场的10大趋势预测。
搜狗AI技术再迎突破,全球首个3D AI合成主播发布
5月21日,在全国瞩目的两会召开之际,搜狗联合新华社推出的全球首个3D AI合成主播“新小微”正式亮相,为全国观众带来最新的两会新闻资讯报道。基于搜狗人工智能核心技术“搜狗分身”打造的“新小微”,将拉开中国传媒行业全面进入智慧时代的序幕。  搜狗分身
那些在越南淘金的中国人
货船在海上漂了7天后,一批从深圳工厂拆卸下来的机械制造设备,终于到达越南胡志明市。这批设备的主人老蔡,在深圳开了12年工厂。在今年,很多类似老蔡这样的工厂主,像候鸟一样,和工厂设备一起南漂。 这种南迁现象,从2010年就逐渐开始逐年增多。老
在线客服系统