作为准应届生的小菜农早早的便找到了一份实习工作,初到公司一切都没那么适应,作为导师的程立这天给小菜农安排了一个需求,想要实现一个简单的《人工客服》需求,也就是即时通讯。小菜农尽管没啥经验,但为了给导师留下良好的印象便一口爽快的接下需求。
接下需求后小菜农便开始构思如何实现即时通讯,他开始在各大平台查看关于在线客服的案例~
他总结了下需求:在线客服,需求理解起来很简单,就相当于一个 web 的聊天页面,也就是客户端能够即时拉取到服务端的响应,尽管平时微信重度使用,但是到了自己实现的时候却满头雾水,眼看一上午的时间就要过去了,自己却没有任何进展,唯一的进展便是找到了以上那张图,却没有丝毫卵用~
中午吃完饭,其他人都已经息屏休息了,而小菜农还在电脑前为这个需求而烦恼,不由有些烦躁起来了,开始想念在学校的日子了。通过接口获取响应!前端整个定时任务去捞消息
这个奇妙的想法直接冲击菜农的大脑
"妙啊",小菜农为自己的好主意开始津津乐道起来,烦恼来得快去的也快~不由多想便开始吭哧吭哧的写起代码了,那一套可谓是行云流水
伪代码如下
:
服务端:
客户端:
setInterval(function () { $.ajax({ async: false, url: "localhost:8080/roll", type: "get", success(data){ console.log("success"); } }) },1000)
写完后,小菜农简单验证了下,发现都功能已经满足了便开始向导师程立showcase了,程立简单地过了遍页面效果,感觉效果在预期内便让小菜农提交代码准备合并发布了
小菜农提交完代码后心中不由欢喜起来,自我感觉十分良好,能在规定时间内完成这个不是那么简单的需求,想必离自己的转正又进一步了吧!但是没等小菜农高兴太久,电脑上便闪起了导师的叮叮提示,"小菜农,现在有空吗,过来下"。不好的念头浮上小菜农的心头,"这该不会出 bug 了吧"。小菜农颤颤巍巍的来到导师的工位,"我刚刚 review 了下你的代码",原来还没发布,那就不是bug的事情了,幸好幸好~
小菜农心中暗想。"我看了下你这个功能的实现方式,这种方式尽管能够实现需求,但是并不是很好的解决方案",导师接着说。“通过轮询的方法,尽管可以从服务端捞到聊天数据,但是接口的频繁请求缺陷也会很明显,十分浪费带宽流量,服务器的压力就会比较大,所以这种方式并不是很好的解决方法,你可以回去再想想看有没有什么其他比较好的解决方法!”
"嗯嗯,是我没考虑好,那我回去再改改!"小菜农涉世未深,导师都这样说了,那这个方案肯定得 pass,连忙接道。
小菜农回到工位后,难免有些沮丧,本来想好好表现表现,没想到自己想出的方案弊端这么多。一阵头大,现在也没时间想这件事,如何实现才是要紧之事!小菜农又陷入了沉思,这可该如何是好~
小菜农随后便打开了某度,看到了一个关键词 SSE
SSE 全称 Server-Sent Events,指的是网页自动获取来自服务器的更新,也就是自动化获取服务端推送至网页的数据,这是一个 H5 的属性,除了 IE,其他标准浏览器基本都兼容
小菜农认真研究了下,发现这种方式和自己之前的实现方式有些相似,但是就不需要客户端定时去获取,而是服务端向客户端声明要发送流信息,然后连续不断地发送过来。这时客户端是不会关闭连接的,会一直等这服务器发过来的新的数据流。"妙啊,这样子不就不会频繁建立连接,浪费带宽了",小菜农又兴奋了起来,这回肯定能够满足导师的需求了!小菜农又花费一个下午的时间将代码实现方式重构了一遍,便提交了~
伪代码
服务端:
客户端:
这回可别再出意外了!小菜农心中默念,但是好景不长,叮叮又开始闪烁了,这这这。。。小菜农的心态有些崩了,完了,这回试用期可能要提前结束了。
沉重都不足以形容小菜农现在的状态了,"我刚刚看了下你这种实现方式比之前改进了不少,但是我们应该还有更好的实现方式,不妨可以考虑下使用 websocket 来实现,没事不要急,咱们可以回去再好好看看"。小菜农并没有听到想象中的责怪,不由心中一暖,Websocket!这回我可要了解清楚再动手实现了,可不能想之前那样为了速度草草的实现了事
下定决心后,小菜农回到工位开始研究起了 Websocket
这次小菜农决定不再为了缩短工时而草草上线了,他打开了搜索引擎开始查找关于《Websocket》的有关资料。
什么是 websocket?
WebSocket 是一种基于 TCP 的网络协议,同时他也是一种 全双工通信的协议,既允许客户端向服务端发送消息,也允许服务器主动向客户端发送消息。在 WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,进行双向数据传输
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
"好家伙,这简介直接概括了我的需求!秒啊~",小菜农喜出望外,天是那么的蓝~ 他迫不及待的往下看
WebSocket 有哪些特点?
1、支持双向通信,实时性更强
2、协议标识符是 ws ,如果采用类似 Https 方式的加密就需要用 wss
3、轻量级,性能开销小,通信十分高效
4、建立在 TCP 协议智商,服务端的实现比较容易
原来是这么一回事,小菜农开始分析自己前两种实现方式的弊端
1、定时轮询的方式
优点就是实现简单,想到这个小菜农老脸一红。缺点也是导师所说的,有一定延迟性,而且服务器的压力较大,浪费带宽流量,因为绝大部分的请求是无效的
2、SSE 方式
这种方式和 websocket 有些类似,但是它只能单工通信,建立连接后,只能由服务端发往客户端,且占用一个连接,如果需要客户端向服务端通信,需要额外再打开一个连接
通过java编写的服务端自带websocket包,编写如下:
客户端实现 websocket 也十分简单,只需要以下API
var Socket = new WebSocket(url, [protocol] );
第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议
在websocket 存在 4 种事件如下:
事件 | 事件处理程序 | 描述 |
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
代码如下:
到这里,websocket 通信便已经实现了,当小菜农刚要准备提交的时候,一个念头兴起,websocket 是导师给我的建议,虽然我已经完成了,但是会不会有更好的方式,能让导师眼前一亮?
想到这里,小菜农不由磨手擦拳了。一番查找,没想到还真的让他找到了,STOMP 协议~
这应该就是我想要的了~
什么是 STOMP 协议?
STOMP (Simple Text-Orientated Messaging Protocaol) ,它是一种简单的面向文本的消息传递协议,提供了一个可互操作的连接格式,允许 STOMP 客户端向任意 STOMP 消息代理 Broker 进行交互,设计简单,易于开发
STOMP 的特点?
1、STOMP 是基于帧的协议,其 帧 是以 HTTP 为模型
2、STOMP 框架由命令,一组可选的标头和可选的主体构成
3、STOMP 基于文本,但也允许传输二进制消息
这有点牛啊,走心的感叹~
STOMP 帧是啥?
STOMP 的结构如下:
COMMAND header1:value1 header2:value2 Body^@
发送和接收分别使用命令
SEND
和SUBSCRIBE
,并且还可以使用destination
来描述消息的内容和接受者
STOMP 的常用帧有哪些?
- 连接相关
1、CONNECT (连接)
2、CONNECTED (成功连接)
- 客户端相关
1、SEND(发送)
2、SUBSRIBE(订阅)
3、UNSUBSCRIBE(取消订阅)
4、BEGIN(开始)
5、COMMIT(提交)
6、ABORT(中断)
7、ACK(确认)
8、NACK(否认)
9、DISCONNECT(断开连接)
- 服务端相关
1、MESSAGE(消息)
2、RECEIPT(接收)
3、ERROR(错误)
小菜农吭哧吭哧地整理了关于 STOMP 的笔记,那么为什么有 websocket,还需要有 stomp,stomp的出现带来了什么好处,或是解决了什么问题?
。小菜农逐渐开始学会思考了,他又开始查看 stomp 的相关资料,经过一番折腾,终于找到了些答案:
WebSocket 的创建,就很类似使用 TCP 套接字传输,传输的报文是无定义的,也就是自由度很高,没有明确的约定,那么这个时候可能就需要一种高层面的应用协议来定义这些报文的语义格式,也就是说 STOMP 也是一种协议,一种作为 WebSocket 的子协议,能够保证连接的两端都遵循这些语义。
那么使用 STOMP 的好处是什么呢
1、STOMP 已经定义好了语义格式,我们就可以无需自定义
2、现成的 stomp.js 客户端,开箱即用
3、可以使用配套的消息代理进行广播,适用于多集群的情况(RabbitMQ、ActiveMQ)
了解到这里再不动手写代码就真的是在划水了,小菜农打开项目开始撸代码了~
要使用 stomp,需要先定义 stomp 的配置类
上面的ws
就是前端的url,后端声明端点,前端进行连接。
stomp 拦截器:
接收客户端消息的地方:
发送消息:
到这里服务端部分的代码便已经实现了~客户端部分也很简单只需要引入两个 js 便可实现
这里为了在客户端接收到消息,必须要先订阅一个目的地 destination
,也就是使用 subscribe()
去订阅,这个方法有两个必需的参数:目的地,回调函数。还有一个可选的参数 headers。
当客户端与服务端连接成功后,可以调用 send()
来发送STOMP消息。这个方法必须有一个参数,用来描述对应的STOMP的目的地。另外可以有两个可选的参数:headers
,object
类型包含额外的信息头部
到这里就已经实现了 stomp 的功能,小菜农连忙打开页面验证下成果:
到这里,小菜农便已经实现了在线客服的功能~ 虽然小菜农实现了聊天室的功能,但实现的过程中也遇到不小的困难,得赶紧记录一下!
可以看到上面涉及到了一些关键词:
- Message:消息,携带 header 和 payload
- MessageHandler:处理 client 消息的实体
- MessageChannel:解耦消息发送者与消息接收者的实体
- clientInboudChannel:用于从 WebSocket 客户端接收消息
- clientOutboundChannel:用于将服务器消息发送给 WebSocket 客户端
- brokerChannel:用于从服务器,应用程序中向消息代理发送消息
- Broker:存放消息的中间件,client 可以订阅 broker 中的消息
可以看出stomp是一种类似订阅发布模式,我们可以动态灵活的声明主题
,前端可以订阅不同的主题,接收到不同主题下的消息,接触过消息队列的小伙伴肯定不会陌生~
小菜农到此便完成了《人工客服》 的需求,想到自己之前因为没有思绪而各种烦躁的行为不由尴尬一笑,所以说在遇到自己不会的时候切勿急躁,有问题还是要及时理清思路,可以多问,但不能不学~