您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
WebScoket简介与使用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
WebScoket简介与使用
自猿其说Tech
2021-10-15
IP归属:未知
53640浏览
计算机编程
WebScoket作为html5规范中的协议,可以看做是Http的升级,那么WebScoket能够给我们带来什么好处?本文简要介绍了WebScoket协议,WebScoket与Http的不同点以及如何搭建一个简单的WebScoket服务。 ### 1 WebSocket介绍 首先简单介绍一下WebSocket,WebSocket协议是html5规范中的一个部分,它借鉴了socket思想,为web应用程序客户端和服务端之间提供了一种全双工通信机制。同时,它又是一种新的应用层协议,websocket协议是为了提供web应用程序和服务端全双工通信而专门制定的一种应用层协议,通常它表示为:ws://localhost:20000/time,可以看到除了前面的协议名和http不同之外,它的表示地址就是传统的url地址。 可以通过以下这张图理解Http与WebScoket ![](//img1.jcloudcs.com/developer.jdcloud.com/004badf5-f134-41ea-95a6-8286e0cf78de20211015135434.png) ### 2 为什么需要WebScoket 简单了解了 WebSocket 后,同学们可能会有一个问题:我们已经有了 HTTP 协议,为什么还需要WebScoket协议呢?使用WebScoket协议又能给我们带来什么样的好处? 其实答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。 在讲Websocket之前,我们先顺带着讲下Http下的ajax和long poll轮询。 #### 2.1 Ajax Ajax轮询的原理非常简单,就是让浏览器隔几秒就发送一次请求,询问服务器是否有新信息。也是我们现在使用的最常使用的方式(例如我们618大屏的看板数据是每隔20s请求服务端接口一次)。 #### 2.2 LongPoll Long poll其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型。也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。 #### 2.3 问题所在 从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理。这就体现了HTTP协议的一个特点,被动性。其实就是,服务端不能主动联系客户端,只能有客户端发起请求,服务端对请求进行响应。 说完这个,我们再来说一说上面的缺陷。从上面很容易看出来,上面这两种方式都是非常消耗资源的。ajax轮询需要服务器有很快的处理速度和资源。long poll 需要有很高的并发,也就是说同时处理请求的能力。所以ajax轮询和long poll都有可能发生客户端还在一直发送请求,但是服务端资源已经不够用,然后给客户端返回错误信息等情况。 #### 2.4 WebScoket解决的问题 所以在这种情况下WebSocket出现了。他解决了HTTP的难题。 被动性,当**服务器完成协议升级后(HTTP->WebSocket),服务端就可以主动推送信息给客户端啦**。这样WebSocket解决了上面数据同步有延迟,而且还非常消耗资源的这种情况。那么为什么他会解决服务器上消耗资源的问题呢? 其实我们所用的程序简单的来看是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的MVC来处理。 简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(MVC)。Nginx基本上速度是足够的,但是每次都卡在MVC上,有的MVC处理请求的速度太慢,以至于在有大量请求的情况下,应用的线程就不够用了。Websocket就解决了这样一个难题,建立连接后,可以直接跟Nginx建立持久连接,有信息的时候MVC想办法通知Nginx,然后Nginx在统一转交给客户。这样就可以解决MVC处理速度过慢的问题了。同时,在传统的方式上,要不断的建立,关闭HTTP链接,由于HTTP是无状态性的,每次都要重新传输鉴权信息,来告诉服务端你是谁。 但是**Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的无状态性**,服务端会一直知道你的信息,直到你关闭请求,这样就解决了反复解析HTTP协议和鉴权的问题。 这也意味着服务端会一直持有WebScoket的session信息,占用服务端机器的内存资源,所以我们要对无效的session信息及时的进行清除。第一种情况就是客户端正常关闭了连接,这时要调用服务端的closed方法,调用这个方法后服务端就知道了这个会话已经断开,就可以清除掉相关的session信息了。第二种情况是由于网络或者服务端重启等问题,客户端可能没有调用closed,造成链接失效。这时就需要加入ping pong机制(也就是心跳机制),来保持链接的持续性和稳定性。具体的做法可以是客户端发送ping,服务端返回pong,当服务端断开时,客户端尝试重新连接。当客户端断开时,服务端尝试清除无效的session。 #### 3 Spring整合Webscoket 上面说了这么多WebScoket的好处,那么如何搭建起一个WebScoket的服务呢?其实我们最常用的Spring框架已经整合了Webscoket。下面就用一个简单的demo来演示一下如何用SpringBoot搭建一个简单的WebScoket工程。首先在idea中创建一个SpringBoot工程。 #### 3.1 POM 在pom.xml中加入以下引用 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` #### 3.2 服务配置 在application.yml文件中配置端口号如下 ```yaml server: port: 20000 ``` #### 3.3 WebScoket配置 WebScoketConfig ```java @Configuration @EnableWebSocket @Log4j2 public class WebSocketConfig implements WebSocketConfigurer { public static final String TIME = "/time"; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(defaultTextWebSocketHandler(), TIME) .setAllowedOrigins("*"); } @Bean DefaultTextWebSocketHandler defaultTextWebSocketHandler() { return new DefaultTextWebSocketHandler(); } ``` 这一步我们通过实现WebSocketConfigurer类并覆盖相应的方法进行WebSocket的配置。我们主要覆盖registerWebSocketHandlers这个方法。通过向WebSocketHandlerRegistry设置不同参数来进行配置。**其中addHandler方法添加我们的WebSocket的handler处理类,第二个参数是暴露出的WebSocket路径,就是类似Http中的请求链接。**setAllowedOrigins("*") 这个是关闭跨域校验,方便本地调试,线上推荐打开。 #### 3.4 Handler实现 ```java public class DefaultTextWebSocketHandler extends TextWebSocketHandler { Map<String,WebSocketSession> map = new HashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); map.put(session.getId(),session); session.sendMessage(new TextMessage("你好")); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); session.sendMessage(message); log.info("{}:{}", toStringSession(session), message); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); map.remove(session.getId()); } private String toStringSession(WebSocketSession session) { List ipHeader = session.getHandshakeHeaders().get("x-real-ip"); String ip = session.getRemoteAddress().toString(); if(!CollectionUtils.isEmpty(ipHeader)) { ip = ipHeader.toString(); } if(session.getPrincipal() == null) { return String.format("id=%s:uri=%s:ip=%s", session.getId(), session.getUri().getPath(),ip); } else { return String.format("user=%s:id=%s:uri=%s:ip=%s", session.getPrincipal().getName(),session.getId(), session.getUri().getPath(),ip); } ``` 这一段代码通过继承TextWebSocketHandler类并覆盖相应方法,我们就可以对WebSocket的事件进行处理,这里简单说明下这几个方法的作用 ``` afterConnectionEstablished方法是在socket连接成功后被触发 afterConnectionClosed方法是在socket连接关闭后被触发 handleTextMessage方法是在客户端发送信息时触发,session.sendMessage(message)就是我们 向客户端发送消息的方法 toStringSession打印一些session的信息,方便自己调试 ``` ``` @Scheduled(cron = "0/5 * * * * ?") public void test(){ map.forEach((k,v)->{ try { this.handleTextMessage(v,new TextMessage(LocalDateTime.now().toString())); } catch (Exception e) { e.printStackTrace(); } }); } ``` 这里我在本地启动了一个定时任务,每五秒钟向客户端推送当前的服务器时间。 这样的话,一个简单的WebScoket的demo就完成了,接下来就可以启动服务,然后用下面的链接测试自己的代码了 <a href="http://coolaf.com/tool/chattest" target="_blank">WebScoket在线测试工具</a> ![](//img1.jcloudcs.com/developer.jdcloud.com/dc55492f-857a-454e-b198-1084dc7fdf3220211015140050.png) ### 4 WebScoket与Session 从上面的demo中可以看到服务端发送消息给客户端必须要通过session,那么我们如何来维护这些session信息呢?很多人可能第一时间想到的是通过spring-session来解决这个问题。但是不幸的是由于ws的session无法序列化到redis,而实际的生产环境中我们基本上都是集群部署的模式,因此在集群中,我们无法将所有的WebSocketSession都缓存到redis进行session共享。 上面的demo是通过一个HashMap来实现了一个session池,用来保存已经登录的WebScoket的session。在生产环境的集群中。每台服务器都有各自的session。所以需要我们自己手动的解决session共享的问题。以下的结构图是一个通过redis的发布订阅机制来解决这个问题的简单方案。 ![](//img1.jcloudcs.com/developer.jdcloud.com/517f858d-6293-4fa5-9e6f-b54ea2c7d78820211015140110.png) ### 5 WebScoket协议简介 上面的demo启动后,我们就可以通过浏览器进行测试了,通过浏览器我们可以简单的了解下WebSocket协议。首先WebSocket是通过HTTP/1.1协议的101状态码进行握手。也就是说,WebSocket协议的建立需要先借助HTTP协议,在服务器返回101状态码之后,就可以进行WebSocket全双工双向通信了,就没有HTTP协议什么事情了。 ``` Request URL: ws://localhost:20000/time Request Method: GET Status Code: 101 ``` WebSocket协议下的客户端请求 ``` GET ws://localhost:20000/time HTTP/1.1 Connection: Upgrade Upgrade: websocket Origin: http://coolaf.comSec-WebSocket-Version: 13 Sec-WebSocket-Key: xLbjgeE/YDljYWSiA/1NFQ== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits ``` Connection:Connection必须设置为Upgrade,表示客户端希望连接升级 Upgrade:Upgrade必须设置为WebSocket,表示在取得服务器响应之后,使用HTTP升级将HTTP协议转换(升级)为WebSocket协议。这个就是Websocket的核心了,告诉Apache、Nginx等服务器:发起的是WebSocket协议。 Sec-WebSocket-Version:表示使用WebSocket的哪一个版本。 Sec-WebSocket-key:随机字符串,用于验证协议是否为WebSocket协议而非HTTP协议 ### 6 总结 最后总结下一下WebScoket的的特点 WebScoket的主要优点就是可以主动向客户端推送信息,相对于Ajax轮询,可以节省一部分资源,另一方面,WebScoket推送的时机是服务端控制,当服务端有数据变更的时候,就可以推送给客户端,消息的时效性更高。但WebScoket也有一定的缺点,例如集群session共享,需要自己提供解决方案。还要就是部分代理中间件对WebScoket的支持可能不够好。 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:京喜达技术部 陈旭
原创文章,需联系作者,授权转载
上一篇:京东快递APP对Flutter 2.0空安全的适配
下一篇:Kafka Broker通信模型与顺序处理Producer消息的原理
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号