前言
我们在使用 Spring 做网页开发的时间,我们后端服务器的紧张作用就是处理惩罚前端、客户端发送来的哀求,也就是说我们的服务器是被动继承哀求的一方,而在某些时间,必要我们的服务器自动向客户端发送网络数据包,比方购物软件的贬价关照,谈天软件别人发送消息时间的关照,这些都必要服务器自动向客户端发送网络数据包。
欣赏器像服务器发送网络哀求,应用层使用的协议每每是 HTTP 大概 HTTPS 协议,而 HTTP 协议在发展的时间却没有涉及到服务器自动向客户端发送网络数据包的功能,由于 HTTP 的发展初志就是为了人们通过网络可以或许看报纸、消息的,也就没想到服务器可以或许通过 HTTP 协议自动向客户端发送网络数据包,以是通过 HTTP 协议是无法实现服务器自动向客户端发送网络数据包的功能的。
那么怎样实现服务器自动向客户端发送哀求的功能呢?这就必要使用到别的一种应用层协议——WebSocket 了。
什么是 WebSocket
来看看百度的表明:
WebSocket是一种在单个TCP毗连上举行全双工通讯的协议。它允许服务端自动向客户端推送数据,同时也支持客户端向服务端发送数据,实现了真正的双向通讯。WebSocket协议于2011年被IETF定为标准RFC 6455,并被RFC7936所增补规范。WebSocket API也被W3C定为标准,使得欣赏器和服务器之间的数据交换变得更加简朴和高效。
传统的应用层使用其他协议的 web 步伐,都是属于”一问一答“情势,客户端给服务器发送 HTTP 哀求之后,服务器给客户端返回一个 HTTP 相应,在这种情况下,服务器是属于被动的一方,如果客户端不自动发起哀求,服务器无法自动给客户端相应。而 WebSocket 则是更靠近于 TCP 这种级别的通讯方式,一旦创建毗连,客户端和服务端都可以自动向对方发送数据。
WebSocket 协媾和 HTTP 协议的区别
- WebSocket:是一种在单个TCP毗连上举行全双工通讯的协议。它允许服务端自动向客户端推送数据,使得客户端和服务器之间的数据交换变得更加简朴和高效。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,并由RFC7936增补规范。WebSocket API也被W3C定为标准。(泉源:百度百科)
- HTTP:全称超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式、超媒体信息体系的应用层协议。HTTP是一个基于TCP/IP通讯协议来转达数据的协议,客户端发送哀求,服务器返回相应。HTTP是无毗连的,即每次毗连只处理惩罚一个哀求,服务器处理惩罚完客户哀求,并收到客户的应答后,就断开毗连。(泉源:知乎专栏)
- WebSocket:支持双向通讯,即客户端和服务器可以同时向对方发送数据。这种全双工的通讯方式使得WebSocket特别实用于必要实时数据交换的场景,如在线谈天、实时数据更新等。
- HTTP:是单向的、哀求-相应模式的协议。客户端发起哀求,服务器返回相应,然后毗连断开。如果必要再次交换数据,必须重新创建毗连。
- WebSocket:是有状态的协议。一旦创建了WebSocket毗连,客户端和服务器之间的通讯就可以连续举行,直到毗连被关闭。这种长期毗连镌汰了因频仍创建毗连而产生的开销。
- HTTP:是无状态的协议。HTTP协议对事故处理惩罚没有影象本领,即服务器不会记着任何关于客户端哀求的信息。如果必要处理惩罚多个哀求之间的关联,必须在每个哀求中携带须要的状态信息。
- WebSocket:由于使用了长毗连和较少的控制开销(如头部信息较小),WebSocket在数据传输服从上优于HTTP。特别是在必要频仍交换小量数据的场景中,WebSocket可以或许明显镌汰网络带宽的斲丧。
- HTTP:每次哀求都必要携带完备的头部信息,这大概导致在传输小量数据时头部信息的开销占比力大。别的,HTTP哀求大概包罗较长的头部,此中真正有用的数据大概只是很小的一部门,从而浪费了网络带宽。
WebSocket 原理分析
WebSocket 本质上是一个基于 TCP 的协议。为了创建一个 WebSocket 毗连,客户端欣赏器起主要向服务器发起一个 HTTP 哀求,这个哀求和通常的 HTTP 哀求差别,包罗了一些附加的头信息,通过这些附加的头信息来完成握手过程。
那么这些附加的头信息是哪些呢?
对于欣赏器发送的哀求数据包的头信息中,附加的信息有:
- Connection: upgrade。表现我必要升级协议
- Sec-WebSocket-Accept: xxxxxx。服务端与该客户端通讯的钥匙
- Sec-WebSocket-Version: 13。升级的协议的版本
- Upgrade: websocket。升级的协议格式
WebSocket 报文格式
WebSocket 协议的相干信息各人可以去官方文档中检察:https://www.rfc-editor.org/rfc/rfc6455
- FIN 表现是否要关闭 WebSocket,为 1 表现断开 WebSocket 毗连,这里的 FIN 和 TCP 报文中的 FIN 不是一个概念。
- RSV1/RSV2/RSV3:保存位,如今先不消,但是不包管背面大概会用到,值一样平常为 0。
- opcode:操纵代码,决定了怎样明白背面的数据载荷。
- %x0:表现这是一个连续帧。当 opcode 为0,表现本次数据传输接纳了数据分片,当前收到的帧为此中一个分片
- %x1:表现这是文本帧,也就是载荷中的数据是文本范例
- %x2:表现这是二进制帧,也就是载荷中的数据是二进制范例
- mask:表现是否要对数据载荷举行掩码操纵。从客户端向服务端发送数据时,必要对数据举行掩码操纵;从服务端向客户端发送数据时,不必要对数据举行掩码操纵
- Payload length:数据载荷的长度,单位是字节,能表现的范围是0-127
- Extended Payload length:扩展的载荷长度,127字节的巨细肯定是不敷用的,以是就出现了扩展载荷,当Payload length的值0-125的时间表现扩展载荷的长度为0,Payload length的值为126时,表现扩展载荷的长度为16位,值为127时,表现扩展载荷的长度为64位
- Masking-key:0大概4字节(32位)全部从客户端传送到服务端的数据帧,数据载荷都举行了掩码操纵,mask 为 1,且携带了4字节的Masking-key。如果 mask 为0.则没有 Masking-key
- Payload Data:报文携带的载荷数据
websocket 载荷的长度可以是 6比特位,单位是字节能表现的巨细,也可以是 16比特位、大概64比特位能表现的范围巨细。
使用掩码算法的目标紧张是从安全角度思量,制止一些缓冲区溢出攻击。
我们可以直接使用 tomcat 提供的 WebSocket 的原生 API,也可以使用 Spring 内置的 WebSocket,着实这两者区别不大。
Spring 中 WebSocket 的使用
起首我们在创建 Spring 项目标时间必要添加进去 WebSocket 依靠。
也可以自己手动添加 websockt 依靠。
添加完依靠之后,我们创建一个类,继续TextWebSocketHandler 类,如果你的 websocket 中的载荷的数据范例是二进制范例的话,就继续 BinaryWebSocketHandler 类:
- package com.example.websocket.component;
- import org.springframework.stereotype.Component;
- import org.springframework.web.socket.handler.TextWebSocketHandler;
- @Component
- public class TestWebSocketComponent extends TextWebSocketHandler {
- }
复制代码 TextWebSocketHandler 父类中的方法有许多:
我们必要重写的方法紧张就是:afterConnectionEstablished、handleTextMessage、handleTransportError、afterConnectionClosed方法。
- afterConnectionEstablished: 方法表现客户端和服务端创建 websocket 毗连之后实验的方法
- handleTextMessage:方法表现对方传来 websocket 数据帧的时间实验的方法
- handleTransportError:方法表现 websocket 毗连出现非常的时间实验的方法
- afterConnectionClosed: 方法表现关闭 websocket 毗连后实验的方法
重写完成 TextWebSocketHandler 类之后,就必要将这个实例给注册到 spring 中,举行路由的设置:
创建一个类,实现 WebSocketConfigurer 接口,而且实现接口中的 registerWebSocketHandlers 方法:
- package com.example.websocket.config;
- import com.example.websocket.component.TestWebSocketComponent;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.socket.config.annotation.EnableWebSocket;
- import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
- import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
- @Configuration
- @EnableWebSocket
- public class WebSocketConfig implements WebSocketConfigurer {
- @Autowired
- private TestWebSocketComponent testWebSocketComponent;
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(testWebSocketComponent,"/test");
- }
- }
复制代码 @EnableWebSocket 注解用于开启Spring应用步伐对WebSocket协议的支持。
registry.addHandler() 方法中的参数分别上我们上面继续并重写了 TextWebSocketHandler 类中的方法的实例,而背面的字符串则是路由设置,当客户端发送的哀求的路由定位到这个字符串的时间,就会实验 TextWebSocketHandler 中的对应方法。
当设置完成服务端的代码之后,我们来实现一个简朴的页面:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>websocket测试</title>
- </head>
- <body>
- <input type="text" id="message">
- <button id="sendBtn">发送</button>
- </body>
- </html>
复制代码
然后编写 js 这边的 websocket 代码:
- <script>
- // 创建一个 websocket 实例
- // ws 是 websocket 的缩写,然后后面就是我们的服务器的ip地址以及前面服务端配置的路由地址
- let websocket = new WebSocket("ws://127.0.0.1/test");
- // 为 websocket 注册一些回调函数
- websocket.onopen = function() {
- // websocket 连接建立完成之后,自动执行到
- console.log('websocket 连接成功');
- }
- websocket.onclose = function() {
- // websocket 连接断开后,自动执行到
- console.log('websocket 连接断开');
- }
- websocket.onerror = function() {
- // websocket 连接异常时,自动执行到
- console.log('websocket 连接异常');
- }
- websocket.onmessage = function(e) {
- // 收到对端消息时,自动执行到
- console.log('websocket 收到消息' + e.data);
- }
- </script>
复制代码 这是 websocket 相干的 js 代码完成了,让后我们为在这个 button 创建一个事故:
- let sendBtn = document.querySelector('#sendBtn');
- sendBtn.onclick = function() {
- let input = document.querySelector('#message');
- websocket.send(input.value)
- }
复制代码 js 通过 WenSocket 的实例中的 send 方法向对端发送消息,而 spring 则通过类 WebSocketSession 实例中的 sendMessage 方法来向对端发送消息。
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- log.info("websocket 接收到消息:" + message.toString());
- //sendMessage方法中的参数是类WebSocketMessage及其子类
- session.sendMessage(message);
- }
复制代码 前后端发送的数据的数据范例是对象该怎样做
在网络传输中,不存在什么对象如许的概念,如果传输的数据范例是对象的话,起首必要将对象转换为 json 字符串,然后再传输。
前端发送的消息的数据范例是对象的话,就必要将对象转换为 JSON 字符串,而我们前面使用 Ajax 的时间是由于 Ajax 资助我们完成了 JSON 的转换,以是才不必要手动转换:
- let req = {
- type: 'message',
- data: input.value
- }
- websocket.send(JSON.stringify(req));
复制代码 然后后端通过 User user = objectMapper.readValue(message.asBytes(),User.class); 来将 json 字符串转换为 Java 对象。
如果后端发送的消息的数据范例是 Java 对象的话,就必要将 Java 对象转换为 json 字符串然后再发送:
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- log.info("websocket 接收到消息:" + message.toString());
- //向对端发送消息
- User user = new User();
- user.setUserId(1);
- user.setUserName("zhangsan");
- String respJson = objectMapper.writeValueAsString(user);
- session.sendMessage(new TextMessage(respJson));
- }
复制代码 然后前端通过 e = JSON.parse(e) 来将 json 字符串转换为对象。
使用websocket协议怎样获取到HTTP协议中的HttpSession
在许多时间,当我们登录的时间,如果登录乐成,每每会将用户信息存放在 session 会话中,而背面升级了 websocket 协议之后,如果我们必要使用到 session 中的信息该怎么办呢?这里 websocket 的开发者也想到了这里。当我们在注册 TextWebSocketHandler 的时间,再注册一个 HttpSession 拦截器就可以了,如许就可以把用户给 HttpSession 中添加的 Attributes 键值对往我们的 WebSocketSession 中也添加一份。
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(testWebSocketComponent,"/test")
- .addInterceptors(new HttpSessionHandshakeInterceptor());
- }
复制代码 在 spring 中,通过 WebSocketSession 的实例中的 getAttributes() 方法得到一个类似哈希表的结构,内里存放 HttpSession 中的设置的全部属性的键值对,然后再从这个哈希表结构中通过 get(key) 方法获取指定 key 的value。
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- log.info("websocket 接收到消息:" + message.toString());
- session.sendMessage(message);
- session.getAttributes().get("user");
- }
复制代码 WebSocket使用的完备代码
WebSocketComponent.java 中的代码
- package com.example.websocket.component;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import org.springframework.web.socket.CloseStatus;
- import org.springframework.web.socket.TextMessage;
- import org.springframework.web.socket.WebSocketSession;
- import org.springframework.web.socket.handler.TextWebSocketHandler;
- @Slf4j
- @Component
- public class TestWebSocketComponent extends TextWebSocketHandler {
- @Override
- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
- log.info("websocket 连接成功");
- }
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- log.info("websocket 接收到消息:" + message.toString());
- //向对端发送消息
- session.sendMessage(message);
- //获取HttpSession中的属性
- session.getAttributes().get("user");
- }
- @Override
- public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
- log.info("websocket 连接异常:" + exception.toString());
- }
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
- log.info("websocket 断开连接");
- }
- }
复制代码 WebSocketConfig.java 中的代码:
- package com.example.websocket.config;
- import com.example.websocket.component.TestWebSocketComponent;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.socket.config.annotation.EnableWebSocket;
- import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
- import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
- import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
- @Configuration
- @EnableWebSocket
- public class WebSocketConfig implements WebSocketConfigurer {
- @Autowired
- private TestWebSocketComponent testWebSocketComponent;
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(testWebSocketComponent,"/test")
- .addInterceptors(new HttpSessionHandshakeInterceptor());
- }
- }
复制代码 js 中的代码:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>websocket测试</title>
- </head>
- <body>
- <input type="text" id="message">
- <button id="sendBtn">发送</button>
- <script>
- // 创建一个 websocket 实例
- // ws 是 websocket 的缩写,然后后面就是我们的服务器的ip地址以及前面服务端配置的路由地址
- let websocket = new WebSocket("ws://127.0.0.1:8080/test");
- // 为 websocket 注册一些回调函数
- websocket.onopen = function() {
- // websocket 连接建立完成之后,自动执行到
- console.log('websocket 连接成功');
- }
- websocket.onclose = function() {
- // websocket 连接断开后,自动执行到
- console.log('websocket 连接断开');
- }
- websocket.onerror = function() {
- // websocket 连接异常时,自动执行到
- console.log('websocket 连接异常');
- }
- websocket.onmessage = function(e) {
- // 收到对端消息时,自动执行到
- console.log('websocket 收到消息' + e.data);
- }
- let sendBtn = document.querySelector('#sendBtn');
- sendBtn.onclick = function() {
- let input = document.querySelector('#message');
- websocket.send(input.value)
- }
- </script>
- </body>
- </html>
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |