最近在做一个 WebSocket 通信服务的软件,所以必须跟着学一学。
一般情况下,我们的服务器和服务器之间可以发送请求,但是服务器是不能向浏览器去发送请求的。因为设计之初并没有想到以后会出现服务端频繁向客户端发送请求的情况。
全双工的通信协议(WebSocket 最大的特点是浏览器也可以往服务器发请求,服务器也可以向浏览器发请求)。
1. 浏览器发起http请求,请求建立 WebSocket 连接
这里的协议升级就是说,我想通过这个 http 连接去升级为 WebSocket 连接
2. 服务器响应统一协议更改
3. 相互发送数据
升级了协议之后浏览器就可以和服务器相互通信了:
- WebSocket 协议是建立在 tcp 协议基础上的,所以不同语言也都支持
- tcp 协议是全双工协议,http 协议基于它是单向的
- WebSocket 没有同源限制,所以前后端端口不一致也不影响信息的发送
服务终端类:
- @ServerEndpoint:监听连接(需要传递一个地址参数)
- @OnOpen:连接成功
- @OnClose:连接关闭
- @OnMessage:收到消息
配置类
- 把 Spring 中的 ServerEndpointExporter 对象注入进来
2.2.1、编写服务终端类
注意:这里监听的地址不可以是 "ws" 不然会报错,可能这是关键字吧,毕竟我们的协议就叫 ws 。
2.2.2、编写配置类
在 resources 目录下创建 static/ws-client.html
测试:启动 SpringBoot 并访问 localhost:8080/ws-client.html
执行结果:
Spring 提供的类和框架
- HttpSessionHandshakeInterceptor(抽象类):握手拦截器,在握手前后添加操作
- AbstractWebSocketHandler(抽象类):WebSocket 处理程序,监听连接前、中、后
- WebSocketConfigurer(接口):配置程序,比如配置监听哪个端口,配置自定义的握手拦截器,配置我们自定义的处理程序
2.3.1、编写握手拦截器类
2.3.2、编写 WebSocket 处理程序
2.3.3、编写配置类
修改 html 中的 websocket 地址为 /myWs1
测试:访问 localhost:8080/ws-client.html
服务器会和每个客户端维护一个连接 :
- 进入聊天室
- 群聊功能,任何人说话,所有人都能接受到消息
- 退出群聊
3.2.1、编写前端界面和通信逻辑
这里我们通过 js 实现了当用户点击发送按钮的时候,把文本框的内容通过 websocket 发送给指定的地址,我们的后台 websocket 程序收到后会全部群发给所有当前连接的客户端:
3.2.2、WebSocket 后台处理程序
这里我们编写了一个 sendMessage 的方法来给所有已连接的客户端进行消息的群发,而我们上面的前端代码中设置当 websocket 收到消息后,会把文本域(<p>标签)中的内容替换掉(innerHTML),因为我们的消息是不断累加到 StringBuffer 当中的,而文本域内容的替换开销并不会造成视觉上的卡顿:
3.2.3、测试
启动项目,并访问 http://localhost:8080/ws-client.html
可以看到,当我们启动多个客户端(开启多个网页),就会显示多人加入群聊,当任意客户端发送消息后,所有客户端都可以看到,当有客户端退出后,同样所有人都可以看到。
总的来讲,websocket 主要的特点就是全双工通信吗,它最大的优势就是实时性特别好,因为传统的单工通信我们客户端需要不断的向服务器发送请求,要追求实时性就必须不断频繁发送请求,效率肯定是低下的。而基于 WebSocket 协议的全双工通信的话就不需要了,因为服务端下你在可以主动给客户端发送消息了,就不需要客户端不断的去发送请求了。
比如股价分析,股票什么时候发生变动我们谁也说不清楚,但是难道让客户端每几百毫秒去访问一次服务器吗?那必不可能,最好的办法就是每当股价发生变动,让服务器主动给客户端发送消息,客户端收到消息以后再做更新。这样通信效率一下子刷就上来了。
HTTP 请求只能从浏览器(客户端)发送服务器,不能从服务器发往浏览器(客户端),这就导致一些需要从服务器发往浏览器(客户端)的场景满足不了。
WebSocket 是 HTML5 支持的,它是基于现有的 HTTP 请求进行了协议升级的一个全双工通信协议。一般用于实时性要求比较高的场景。