热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

websocket深入,tomcat,jetty服务器使用对比

转载整理自https:www.zhihu.comquestion20215561http:www.open-open.comlibviewopen1435905714122.ht

转载整理自https://www.zhihu.com/question/20215561

http://www.open-open.com/lib/view/open1435905714122.html

http://www.ibm.com/developerworks/cn/java/j-lo-WebSocket/

http://www.open-open.com/lib/view/open1413011727155.html

一、WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)


首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解


<img src&#61;"https://pic1.zhimg.com/6651f2f811ec133b0e6d7e6d0e194b4c_b.jpg" data-rawwidth&#61;"374" data-rawheight&#61;"133" class&#61;"content_image" width&#61;"374">有交集&#xff0c;但是并不是全部。三、Websocket的作用
在讲Websocket之前&#xff0c;我就顺带着讲下 long poll 和 ajax轮询 的原理。
首先是 ajax轮询 &#xff0c;ajax轮询 的原理非常简单&#xff0c;让浏览器隔个几秒就发送一次请求&#xff0c;询问服务器是否有新信息。
场景再现&#xff1a;
客户端&#xff1a;啦啦啦&#xff0c;有没有新信息(Request)
服务端&#xff1a;没有&#xff08;Response&#xff09;
客户端&#xff1a;啦啦啦&#xff0c;有没有新信息(Request)
服务端&#xff1a;没有。。&#xff08;Response&#xff09;
客户端&#xff1a;啦啦啦&#xff0c;有没有新信息(Request)
服务端&#xff1a;你好烦啊&#xff0c;没有啊。。&#xff08;Response&#xff09;
客户端&#xff1a;啦啦啦&#xff0c;有没有新消息&#xff08;Request&#xff09;
服务端&#xff1a;好啦好啦&#xff0c;有啦给你。&#xff08;Response&#xff09;
客户端&#xff1a;啦啦啦&#xff0c;有没有新消息&#xff08;Request&#xff09;
服务端&#xff1a;。。。。。没。。。。没。。。没有&#xff08;Response&#xff09; ---- loop

long poll
long poll 其实原理跟 ajax轮询 差不多&#xff0c;都是采用轮询的方式&#xff0c;不过采取的是阻塞模型&#xff08;一直打电话&#xff0c;没收到就不挂电话&#xff09;&#xff0c;也就是说&#xff0c;客户端发起连接后&#xff0c;如果没消息&#xff0c;就一直不返回Response给客户端。直到有消息才返回&#xff0c;返回完之后&#xff0c;客户端再次建立连接&#xff0c;周而复始。
场景再现
客户端&#xff1a;啦啦啦&#xff0c;有没有新信息&#xff0c;没有的话就等有了才返回给我吧&#xff08;Request&#xff09;
服务端&#xff1a;额。。 等待到有消息的时候。。来 给你&#xff08;Response&#xff09;
客户端&#xff1a;啦啦啦&#xff0c;有没有新信息&#xff0c;没有的话就等有了才返回给我吧&#xff08;Request&#xff09; -loop

从上面可以看出其实这两种方式&#xff0c;都是在不断地建立HTTP连接&#xff0c;然后等待服务端处理&#xff0c;可以体现HTTP协议的另外一个特点&#xff0c;被动性
何为被动性呢&#xff0c;其实就是&#xff0c;服务端不能主动联系客户端&#xff0c;只能有客户端发起。
简单地说就是&#xff0c;服务器是一个很懒的冰箱&#xff08;这是个梗&#xff09;&#xff08;不会、不能主动发起连接&#xff09;&#xff0c;但是上司有命令&#xff0c;如果有客户来&#xff0c;不管多么累都要好好接待。

说完这个&#xff0c;我们再来说一说上面的缺陷&#xff08;原谅我废话这么多吧OAQ&#xff09;
从上面很容易看出来&#xff0c;不管怎么样&#xff0c;上面这两种都是非常消耗资源的。
ajax轮询 需要服务器有很快的处理速度和资源。&#xff08;速度&#xff09;
long poll 需要有很高的并发&#xff0c;也就是说同时接待客户的能力。&#xff08;场地大小&#xff09;
所以ajax轮询 和long poll 都有可能发生这种情况。

客户端&#xff1a;啦啦啦啦&#xff0c;有新信息么&#xff1f;
服务端&#xff1a;月线正忙&#xff0c;请稍后再试&#xff08;503 Server Unavailable&#xff09;
客户端&#xff1a;。。。。好吧&#xff0c;啦啦啦&#xff0c;有新信息么&#xff1f;
服务端&#xff1a;月线正忙&#xff0c;请稍后再试&#xff08;503 Server Unavailable&#xff09;


然后服务端在一旁忙的要死&#xff1a;冰箱&#xff0c;我要更多的冰箱&#xff01;更多。。更多。。&#xff08;我错了。。这又是梗。。&#xff09;


--------------------------
言归正传&#xff0c;我们来说Websocket吧
通过上面这个例子&#xff0c;我们可以看出&#xff0c;这两种方式都不是最好的方式&#xff0c;需要很多资源。
一种需要更快的速度&#xff0c;一种需要更多的&#39;电话&#39;。这两种都会导致&#39;电话&#39;的需求越来越高。
哦对了&#xff0c;忘记说了HTTP还是一个无状态协议。&#xff08;感谢评论区的各位指出OAQ&#xff09;
通俗的说就是&#xff0c;服务器因为每天要接待太多客户了&#xff0c;是个健忘鬼&#xff0c;你一挂电话&#xff0c;他就把你的东西全忘光了&#xff0c;把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

所以在这种情况下出现了&#xff0c;Websocket出现了。
他解决了HTTP的这几个难题。
首先&#xff0c;被动性&#xff0c;当服务器完成协议升级后&#xff08;HTTP->Websocket&#xff09;&#xff0c;服务端就可以主动推送信息给客户端啦。
所以上面的情景可以做如下修改。
客户端&#xff1a;啦啦啦&#xff0c;我要建立Websocket协议&#xff0c;需要的服务&#xff1a;chat&#xff0c;Websocket协议版本&#xff1a;17&#xff08;HTTP Request&#xff09;
服务端&#xff1a;ok&#xff0c;确认&#xff0c;已升级为Websocket协议&#xff08;HTTP Protocols Switched&#xff09;
客户端&#xff1a;麻烦你有信息的时候推送给我噢。。
服务端&#xff1a;ok&#xff0c;有的时候会告诉你的。
服务端&#xff1a;balabalabalabala
服务端&#xff1a;balabalabalabala
服务端&#xff1a;哈哈哈哈哈啊哈哈哈哈
服务端&#xff1a;笑死我了哈哈哈哈哈哈哈

就变成了这样&#xff0c;只需要经过一次HTTP请求&#xff0c;就可以做到源源不断的信息传送了。&#xff08;在程序设计中&#xff0c;这种设计叫做回调&#xff0c;即&#xff1a;你有信息了再来通知我&#xff0c;而不是我傻乎乎的每次跑来问你&#xff09;
这样的协议解决了上面同步有延迟&#xff0c;而且还非常消耗资源的这种情况。
那么为什么他会解决服务器上消耗资源的问题呢&#xff1f;
其实我们所用的程序是要经过两层代理的&#xff0c;即HTTP协议在Nginx等服务器的解析下&#xff0c;然后再传送给相应的Handler&#xff08;PHP等&#xff09;来处理。
简单地说&#xff0c;我们有一个非常快速的接线员&#xff08;Nginx&#xff09;&#xff0c;他负责把问题转交给相应的客服&#xff08;Handler&#xff09;
本身接线员基本上速度是足够的&#xff0c;但是每次都卡在客服&#xff08;Handler&#xff09;了&#xff0c;老有客服处理速度太慢。&#xff0c;导致客服不够。
Websocket就解决了这样一个难题&#xff0c;建立后&#xff0c;可以直接跟接线员建立持久连接&#xff0c;有信息的时候客服想办法通知接线员&#xff0c;然后接线员在统一转交给客户。
这样就可以解决客服处理速度过慢的问题了。

同时&#xff0c;在传统的方式上&#xff0c;要不断的建立&#xff0c;关闭HTTP协议&#xff0c;由于HTTP是非状态性的&#xff0c;每次都要重新传输identity info&#xff08;鉴别信息&#xff09;&#xff0c;来告诉服务端你是谁。
虽然接线员很快速&#xff0c;但是每次都要听这么一堆&#xff0c;效率也会有所下降的&#xff0c;同时还得不断把这些信息转交给客服&#xff0c;不但浪费客服的处理时间&#xff0c;而且还会在网路传输中消耗过多的流量/时间。
但是Websocket只需要一次HTTP握手&#xff0c;所以说整个通讯过程是建立在一次连接/状态中&#xff0c;也就避免了HTTP的非状态性&#xff0c;服务端会一直知道你的信息&#xff0c;直到你关闭请求&#xff0c;这样就解决了接线员要反复解析HTTP协议&#xff0c;还要查看identity info的信息。
同时由客户主动询问&#xff0c;转换为服务器&#xff08;推送&#xff09;有信息的时候就发送&#xff08;当然客户端还是等主动发送信息过来的。。&#xff09;&#xff0c;没有信息的时候就交给接线员&#xff08;Nginx&#xff09;&#xff0c;不需要占用本身速度就慢的客服&#xff08;Handler&#xff09;
--------------------
至于怎么在不支持Websocket的客户端上使用Websocket。。答案是&#xff1a;不能
但是可以通过上面说的 long poll 和 ajax 轮询来 模拟出类似的效果

作者&#xff1a;董可人
链接&#xff1a;https://www.zhihu.com/question/20215561/answer/40250050
来源&#xff1a;知乎
著作权归作者所有&#xff0c;转载请联系作者获得授权。

你可以把 WebSocket 看成是 HTTP 协议为了支持长连接所打的一个大补丁&#xff0c;它和 HTTP 有一些共性&#xff0c;是为了解决 HTTP 本身无法解决的某些问题而做出的一个改良设计。在以前 HTTP 协议中所谓的 keep-alive connection 是指在一次 TCP 连接中完成多个 HTTP 请求&#xff0c;但是对每个请求仍然要单独发 header&#xff1b;所谓的 polling 是指从客户端&#xff08;一般就是浏览器&#xff09;不断主动的向服务器发 HTTP 请求查询是否有新数据。这两种模式有一个共同的缺点&#xff0c;就是除了真正的数据部分外&#xff0c;服务器和客户端还要大量交换 HTTP header&#xff0c;信息交换效率很低。它们建立的“长连接”都是伪.长连接&#xff0c;只不过好处是不需要对现有的 HTTP server 和浏览器架构做修改就能实现。

WebSocket 解决的第一个问题是&#xff0c;通过第一个 HTTP request 建立了 TCP 连接之后&#xff0c;之后的交换数据都不需要再发 HTTP request了&#xff0c;使得这个长连接变成了一个真.长连接。但是不需要发送 HTTP header就能交换数据显然和原有的 HTTP 协议是有区别的&#xff0c;所以它需要对服务器和客户端都进行升级才能实现。在此基础上 WebSocket 还是一个双通道的连接&#xff0c;在同一个 TCP 连接上既可以发也可以收信息。此外还有 multiplexing 功能&#xff0c;几个不同的 URI 可以复用同一个 WebSocket 连接。这些都是原来的 HTTP 不能做到的。

另外说一点技术细节&#xff0c;因为看到有人提问 WebSocket 可能进入某种半死不活的状态。这实际上也是原有网络世界的一些缺陷性设计。上面所说的 WebSocket 真.长连接虽然解决了服务器和客户端两边的问题&#xff0c;但坑爹的是网络应用除了服务器和客户端之外&#xff0c;另一个巨大的存在是中间的网络链路。一个 HTTP/WebSocket 连接往往要经过无数的路由&#xff0c;防火墙。你以为你的数据是在一个“连接”中发送的&#xff0c;实际上它要跨越千山万水&#xff0c;经过无数次转发&#xff0c;过滤&#xff0c;才能最终抵达终点。在这过程中&#xff0c;中间节点的处理方法很可能会让你意想不到。

比如说&#xff0c;这些坑爹的中间节点可能会认为一份连接在一段时间内没有数据发送就等于失效&#xff0c;它们会自作主张的切断这些连接。在这种情况下&#xff0c;不论服务器还是客户端都不会收到任何提示&#xff0c;它们只会一厢情愿的以为彼此间的红线还在&#xff0c;徒劳地一边又一边地发送抵达不了彼岸的信息。而计算机网络协议栈的实现中又会有一层套一层的缓存&#xff0c;除非填满这些缓存&#xff0c;你的程序根本不会发现任何错误。这样&#xff0c;本来一个美好的 WebSocket 长连接&#xff0c;就可能在毫不知情的情况下进入了半死不活状态。

而解决方案&#xff0c;WebSocket 的设计者们也早已想过。就是让服务器和客户端能够发送 Ping/Pong Frame&#xff08;RFC 6455 - The WebSocket Protocol&#xff09;。这种 Frame 是一种特殊的数据包&#xff0c;它只包含一些元数据而不需要真正的 Data Payload&#xff0c;可以在不影响 Application 的情况下维持住中间网络的连接状态。


作者&#xff1a;劳永超
链接&#xff1a;https://www.zhihu.com/question/20215561/answer/17884059
来源&#xff1a;知乎
著作权归作者所有&#xff0c;转载请联系作者获得授权。

websocket的实现已经有朋友提到了基于HTTP来发起&#xff0c;我这里不讨论太多实现&#xff0c;而是主要回答一下题主补充的问题&#xff1a;“【【【【【【【【【【【【【【补充】】】】】】】】】】&#xff1a;&#xff1a;&#xff1a;&#xff1a;&#xff1a;
既然WebSocket和HTTP是两个协议 为什么要在HTML5才支持
又如果说HTML5 出来以后可以用WebSocket了 就说明WebSocket是本来就有点东西只是HTML4不支持而已 http4时代 如何使用WebSocket呢&#xff1f;&#xff1f; 谢谢”

websocket是随着WWW的发展&#xff0c;应用场景越来越复杂而提出的&#xff0c;针对浏览器和web服务器之间双向持续通信而设计&#xff0c;而且优雅地兼容HTTP&#xff08;我猜想&#xff1a;同时因为建立在HTTP上&#xff0c;也可以利用好HTTP原有的基础比如basic认证&#xff09;。它和HTML5不是非得扯到一起&#xff0c;但是刚好是同时期&#xff0c;而且HTML5也刚好需要“socket”功能&#xff0c;因此顺理成章成为一部分。

而 websocket 的使用&#xff0c;更多的是“脚本”层面的事情&#xff0c;比如在Javascript&#xff08;不是非得Javascript&#xff0c;但是Javascript已经成为和浏览器交互的事实标准&#xff09;中创建Websocket对象以便操作浏览器的websocket功能&#xff08;这个功能便是浏览器根据HTML5草案实现出来的&#xff0c;参考草案的interface WebSocket部分定义&#xff09;&#xff0c;然后注册onopen/onmessage等回调接口&#xff0c;有数据或者状态变更之类的时机你就可以做相应的处理了。而这部分刚好在旧的标准没定义&#xff0c;浏览器也没有这样的功能&#xff0c;因此遵循HTML4编写的页面不会用到这些功能。


作者&#xff1a;长天之云
链接&#xff1a;https://www.zhihu.com/question/20215561/answer/14365823
来源&#xff1a;知乎
著作权归作者所有&#xff0c;转载请联系作者获得授权。

Web 1 的时代人们访问 Web 页面是即停即走。Web 2 之后单个页面停留时间越来越长&#xff0c;页面功能越来越丰富——这时有了 RIA 的概念&#xff0c;改变了客户端的编程模型——更甚至许多实时应用根本不用离开页面&#xff0c;比如聊天、游戏应用。
客户端浏览器决定了客户端编程语言的能拥有的功能&#xff0c;以前如何做那些交互性很高的应用呢&#xff1f;
一些技术有 XHR&#xff0c;iframe, 实时性要求非常高的就只能用第三方插件&#xff0c;比如 Flash 或 Silverlight。
但 XHR 和 iframe 存在一些根本避免不了问题&#xff1a;1&#xff09;每次交互就需要两个 HTTP 请求 2&#xff09;即使单个 HTTP 请求也要传送很多字节&#xff08;header 笨重&#xff09;3&#xff09;客户端不知道消息何时能够到达&#xff0c;只能轮询。服务器肯定会表示压力很大&#xff01;
插件则需要额外安装&#xff0c;还有安全性问题和移动设备根本不能被支持的问题。
有了需要之后才有了解决方案—— WebSocket 就是这种灵丹妙药&#xff0c;看看主要特性&#xff1a;实时交互、服务器能够主动推送内容、只需要建立一次连接、快速&#xff08;延迟小&#xff0c;每条消息可以小到两个字节&#xff09;、开发者友好&#xff08;接口简单&#xff0c;并是熟悉的事件模型&#xff09;等等。
所以&#xff0c;HTML 4 选择权很小&#xff0c;是否要支持 WebSocket 依据需求和环境而定&#xff1b;而选择 HTML 5 的话&#xff0c;有了 socket 通信和图形编程的能力&#xff0c;能够开发出什么精彩应用只取决于你的想象力。






Tomcat&#xff1a;

       J2EE下面用的最多的容器应该就是tomcat了。说到tomcat对WebSocket的支持&#xff0c;不得不先提一下&#xff0c;目前的WebSocket协议已经经过了好几代的演变&#xff0c;不同浏览器对此协议的支持程度也不同&#xff0c;因此&#xff0c;如果作为服务器&#xff0c;最理想的是支持尽可能多的WebSocket协议版本。

tomcat8真正支持jsr-356&#xff08;包含对websocket的支持&#xff09;&#xff0c; tomcat7支持部分版本的websocket实现不兼容jsr-356。因此&#xff0c;能用tomcat8的话&#xff0c;还是尽量用。

 

代码实现相当简单&#xff0c;以下是一个列子&#xff0c;只需要tomcat8的基本库&#xff0c;不需要其他依赖。

import java.io.IOException; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; &#64;ServerEndpoint("/websocket") public class WebSocketTest { &#64;OnMessage public void onMessage(String message, Session session) throws IOException, InterruptedException { // Print the client message for testing purposes System.out.println("Received: " &#43; message); // Send the first message to the client session.getBasicRemote().sendText("This is the first server message"); // Send 3 messages to the client every 5 seconds int sentMessages &#61; 0; while (sentMessages <3) { Thread.sleep(5000); session.getBasicRemote().sendText("This is an intermediate server message. Count: " &#43; sentMessages); sentMessages&#43;&#43;; } // Send a final message to the client session.getBasicRemote().sendText("This is the last server message"); } &#64;OnOpen public void onOpen() { System.out.println("Client connected"); } &#64;OnClose public void onClose() { System.out.println("Connection closed"); } }



Jetty&#xff1a;

       Jetty和Tomcat一样&#xff0c;也是一个Servlet的容器。如果说不同之处&#xff0c;那么最大的不同应该是Tomcat采用的是BIO处理方式&#xff0c;也就是说一个request会用一个线程去处理&#xff0c;即使是WebSocket这种长连接&#xff0c;也是会独立开一个线程。作为一个普通的Web服务器&#xff0c;tomcat可以轻松应对耗时比较短的Request/Response。但是如果换成是长连接的WebSocket&#xff0c;那麻烦就来了&#xff0c;对于上万用户的聊天和推送&#xff0c;总不能开上万个线程去处理吧。此时&#xff0c;Jetty的性能就体现出来了&#xff0c;Jetty采用的是NIO&#xff0c;一个线程可以处理多个WebSocket的长链接&#xff0c;如果你的需求是大量耗时比较长的request或者大量长连接&#xff0c;那么建议采用Jetty。

 

        Jetty对WebSocket的实现有点绕&#xff0c;Servlet不再是继承原来的HttpServlet&#xff0c;而是继承WebSocketServlet。此处要注意导入jetty-util.jar和jetty-websocket.jar两个包&#xff0c;否则可能会有class not found错误。

ReverseAjaxServlet.java:

import java.io.IOException; import java.util.Date; import java.util.Random; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jettison.json.JSONArray; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketServlet; /** * &#64;author Mathieu Carbou (mathieu.carbou&#64;gmail.com) */ public final class ReverseAjaxServlet extends WebSocketServlet { private final Endpoints endpoints &#61; new Endpoints(); private final Random random &#61; new Random(); private final Thread generator &#61; new Thread("Event generator") { &#64;Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(random.nextInt(5000)); endpoints.broadcast(new JSONArray().put("At " &#43; new Date()).toString()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }; &#64;Override public void init() throws ServletException { super.init(); generator.start(); } &#64;Override public void destroy() { generator.interrupt(); super.destroy(); } &#64;Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return endpoints.newEndpoint(); } &#64;Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("11111"); } }


Endpoints.java:

package com.cn.test.chapter2.websocket; import org.eclipse.jetty.websocket.WebSocket; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; /** * &#64;author Mathieu Carbou (mathieu.carbou&#64;gmail.com) */ final class Endpoints { private final Queue endpoints &#61; new ConcurrentLinkedQueue(); void broadcast(String data) { // for (Endpoint endpoint : endpoints) { // endpoint.onMessage(data); // } } void offer(Endpoint endpoint) { endpoints.offer(endpoint); } void remove(Endpoint endpoint) { endpoints.remove(endpoint); } public WebSocket newEndpoint() { return new Endpoint(this); } }


Endpoint.java

import java.io.IOException; import java.util.concurrent.ConcurrentLinkedQueue; import org.codehaus.jettison.json.JSONArray; import org.eclipse.jetty.websocket.WebSocket; /** * &#64;author Mathieu Carbou (mathieu.carbou&#64;gmail.com) */ class Endpoint implements WebSocket.OnTextMessage { protected Connection _connection; private Endpoints endpoints; private static int clientCounter &#61; 0; private int clientId &#61; clientCounter&#43;&#43;; public Endpoint(Endpoints endpoints) { this.setEndpoints(endpoints); } &#64;Override public void onClose(int code, String message) { System.out.println("Client disconnected"); this.endpoints.remove(this); } &#64;Override public void onOpen(Connection connection) { System.out.println("Client connected"); _connection &#61; connection; try { this._connection.sendMessage(new JSONArray().put("ClientID &#61; " &#43; clientId).toString()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } endpoints.offer(this); } &#64;Override public void onMessage(final String data) { System.out.println("Received data: " &#43; data); this.endpoints.broadcast(data); } public Endpoints getEndpoints() { return endpoints; } public void setEndpoints(Endpoints endpoints) { this.endpoints &#61; endpoints; } }

 

 

辅助工具&#xff1a;

        在编写服务器最麻烦的是要写对应的客户端来测试&#xff0c;还好Chrome为我们解决了这个问题。下载Chrome插件WebSocket Clinet可以轻松地和服务器建立连接&#xff0c;发送消息到服务器。

来自&#xff1a;http://blog.csdn.net/lrenjun/article/details/39934823

WebSocket 前世今生

众所周知&#xff0c;Web 应用的交互过程通常是客户端通过浏览器发出一个请求&#xff0c;服务器端接收请求后进行处理并返回结果给客户端&#xff0c;客户端浏览器将信息呈现&#xff0c;这种机制对于信息变化不是特别频繁的应用尚可&#xff0c;但对于实时要求高、海量并发的应用来说显得捉襟见肘&#xff0c;尤其在当前业界移动互联网蓬勃发展的趋势下&#xff0c;高并发与用户实时响应是 Web 应用经常面临的问题&#xff0c;比如金融证券的实时信息&#xff0c;Web 导航应用中的地理位置获取&#xff0c;社交网络的实时消息推送等。

传统的请求-响应模式的 Web 开发在处理此类业务场景时&#xff0c;通常采用实时通讯方案&#xff0c;常见的是&#xff1a;

  • 轮询&#xff0c;原理简单易懂&#xff0c;就是客户端通过一定的时间间隔以频繁请求的方式向服务器发送请求&#xff0c;来保持客户端和服务器端的数据同步。问题很明显&#xff0c;当客户端以固定频率向服务器端发送请求时&#xff0c;服务器端的数据可能并没有更新&#xff0c;带来很多无谓请求&#xff0c;浪费带宽&#xff0c;效率低下。
  • 基于 Flash&#xff0c;AdobeFlash 通过自己的 Socket 实现完成数据交换&#xff0c;再利用 Flash 暴露出相应的接口为 Javascript 调用&#xff0c;从而达到实时传输目的。此方式比轮询要高效&#xff0c;且因为 Flash 安装率高&#xff0c;应用场景比较广泛&#xff0c;但在移动互联网终端上 Flash 的支持并不好。IOS 系统中没有 Flash 的存在&#xff0c;在 Android 中虽然有 Flash 的支持&#xff0c;但实际的使用效果差强人意&#xff0c;且对移动设备的硬件配置要求较高。2012 年 Adobe 官方宣布不再支持 Android4.1&#43;系统&#xff0c;宣告了 Flash 在移动终端上的死亡。

从上文可以看出&#xff0c;传统 Web 模式在处理高并发及实时性需求的时候&#xff0c;会遇到难以逾越的瓶颈&#xff0c;我们需要一种高效节能的双向通信机制来保证数据的实时传输。在此背景下&#xff0c;基于 HTML5 规范的、有 Web TCP 之称的 WebSocket 应运而生。

早期 HTML5 并没有形成业界统一的规范&#xff0c;各个浏览器和应用服务器厂商有着各异的类似实现&#xff0c;如 IBM 的 MQTT&#xff0c;Comet 开源框架等&#xff0c;直到 2014 年&#xff0c;HTML5 在 IBM、微软、Google 等巨头的推动和协作下终于尘埃落地&#xff0c;正式从草案落实为实际标准规范&#xff0c;各个应用服务器及浏览器厂商逐步开始统一&#xff0c;在 JavaEE7 中也实现了 WebSocket 协议&#xff0c;从而无论是客户端还是服务端的 WebSocket 都已完备&#xff0c;读者可以查阅HTML5 规范&#xff0c;熟悉新的 HTML 协议规范及 WebSocket 支持。

回页首

WebSocket 机制

以下简要介绍一下 WebSocket 的原理及运行机制。

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯&#xff0c;它建立在 TCP 之上&#xff0c;同 HTTP 一样通过 TCP 来传输数据&#xff0c;但是它和 HTTP 最大不同是&#xff1a;

  • WebSocket 是一种双向通信协议&#xff0c;在建立连接后&#xff0c;WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据&#xff0c;就像 Socket 一样&#xff1b;
  • WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接&#xff0c;连接成功后才能相互通信。

非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示&#xff1a;

图 1. 传统 HTTP 请求响应客户端服务器交互图
图 1. 传统 HTTP 请求响应客户端服务器交互图

使用 WebSocket 模式客户端与服务器的交互如下图&#xff1a;

图 2.WebSocket 请求响应客户端服务器交互图
图 2.WebSocket 请求响应客户端服务器交互图

上图对比可以看出&#xff0c;相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式&#xff0c;WebSocket 是类似 Socket 的 TCP 长连接的通讯模式&#xff0c;一旦 WebSocket 连接建立后&#xff0c;后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前&#xff0c;不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下&#xff0c;极大的节省了网络带宽资源的消耗&#xff0c;有明显的性能优势&#xff0c;且客户端发送和接受消息是在同一个持久连接上发起&#xff0c;实时性优势明显。

我们再通过客户端和服务端交互的报文看一下 WebSocket 通讯与传统 HTTP 的不同&#xff1a;

在客户端&#xff0c;new WebSocket 实例化一个新的 WebSocket 客户端对象&#xff0c;连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL&#xff0c;WebSocket 客户端对象会自动解析并识别为 WebSocket 请求&#xff0c;从而连接服务端端口&#xff0c;执行双方握手过程&#xff0c;客户端发送数据格式类似&#xff1a;

清单 1.WebSocket 客户端连接报文

GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg&#61;&#61;
Origin: http://localhost:8080
Sec-WebSocket-Version: 13

可以看到&#xff0c;客户端发起的 WebSocket 连接报文类似传统 HTTP 报文&#xff0c;”Upgrade&#xff1a;websocket”参数值表明这是 WebSocket 类型请求&#xff0c;“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文&#xff0c;要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答&#xff0c;否则客户端会抛出“Error during WebSocket handshake”错误&#xff0c;并关闭连接。

服务端收到报文后返回的数据格式类似&#xff1a;

清单 2.WebSocket 服务端响应报文

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8&#61;

“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接&#xff0c;经过这样的请求-响应处理后&#xff0c;客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。读者可以查阅WebSocket 协议栈了解 WebSocket 客户端和服务端更详细的交互数据格式。

在开发方面&#xff0c;WebSocket API 也十分简单&#xff0c;我们只需要实例化 WebSocket&#xff0c;创建连接&#xff0c;然后服务端和客户端就可以相互发送和响应消息&#xff0c;在下文 WebSocket 实现及案例分析部分&#xff0c;可以看到详细的 WebSocket API 及代码实现。

回页首

WebSocket 实现

如上文所述&#xff0c;WebSocket 的实现分为客户端和服务端两部分&#xff0c;客户端&#xff08;通常为浏览器&#xff09;发出 WebSocket 连接请求&#xff0c;服务端响应&#xff0c;实现类似 TCP 握手的动作&#xff0c;从而在浏览器客户端和 WebSocket 服务端之间形成一条 HTTP 长连接快速通道。两者之间后续进行直接的数据互相传送&#xff0c;不再需要发起连接和相应。

以下简要描述 WebSocket 服务端 API 及客户端 API。

WebSocket 服务端 API

WebSocket 服务端在各个主流应用服务器厂商中已基本获得符合 JEE JSR356 标准规范 API 的支持&#xff08;详见JSR356 WebSocket API 规范&#xff09;&#xff0c;以下列举了部分常见的商用及开源应用服务器对 WebSocket Server 端的支持情况&#xff1a;

表 1.WebSocket 服务端支持
厂商 应用服务器 备注
IBM WebSphere WebSphere 8.0 以上版本支持&#xff0c;7.X 之前版本结合 MQTT 支持类似的 HTTP 长连接
甲骨文 WebLogic WebLogic 12c 支持&#xff0c;11g 及 10g 版本通过 HTTP Publish 支持类似的 HTTP 长连接
微软 IIS IIS 7.0&#43;支持
Apache Tomcat Tomcat 7.0.5&#xff0b;支持&#xff0c;7.0.2X 及 7.0.3X 通过自定义 API 支持
  Jetty Jetty 7.0&#xff0b;支持

以下我们使用 Tomcat7.0.5 版本的服务端示例代码说明 WebSocket 服务端的实现&#xff1a;

JSR356 的 WebSocket 规范使用 javax.websocket.*的 API&#xff0c;可以将一个普通 Java 对象&#xff08;POJO&#xff09;使用 &#64;ServerEndpoint 注释作为 WebSocket 服务器的端点&#xff0c;代码示例如下&#xff1a;

清单 3.WebSocket 服务端 API 示例

&#64;ServerEndpoint("/echo")public class EchoEndpoint {&#64;OnOpenpublic void onOpen(Session session) throws IOException {//以下代码省略...}&#64;OnMessagepublic String onMessage(String message) {//以下代码省略...}&#64;Message(maxMessageSize&#61;6)public void receiveMessage(String s) {//以下代码省略...} &#64;OnErrorpublic void onError(Throwable t) {//以下代码省略...}&#64;OnClosepublic void onClose(Session session, CloseReason reason) {//以下代码省略...} }

代码解释&#xff1a;

上文的简洁代码即建立了一个 WebSocket 的服务端&#xff0c;&#64;ServerEndpoint("/echo") 的 annotation 注释端点表示将 WebSocket 服务端运行在 ws://[Server 端 IP 或域名]:[Server 端口]/websockets/echo 的访问端点&#xff0c;客户端浏览器已经可以对 WebSocket 客户端 API 发起 HTTP 长连接了。

使用 ServerEndpoint 注释的类必须有一个公共的无参数构造函数&#xff0c;&#64;onMessage 注解的 Java 方法用于接收传入的 WebSocket 信息&#xff0c;这个信息可以是文本格式&#xff0c;也可以是二进制格式。

OnOpen 在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session 表明两个 WebSocket 端点对话连接的另一端&#xff0c;可以理解为类似 HTTPSession 的概念。

OnClose 在连接被终止时调用。参数 closeReason 可封装更多细节&#xff0c;如为什么一个 WebSocket 连接关闭。

更高级的定制如 &#64;Message 注释&#xff0c;MaxMessageSize 属性可以被用来定义消息字节最大限制&#xff0c;在示例程序中&#xff0c;如果超过 6 个字节的信息被接收&#xff0c;就报告错误和连接关闭。

注意&#xff1a;早期不同应用服务器支持的 WebSocket 方式不尽相同&#xff0c;即使同一厂商&#xff0c;不同版本也有细微差别&#xff0c;如 Tomcat 服务器 7.0.5 以上的版本都是标准 JSR356 规范实现&#xff0c;而 7.0.2x/7.0.3X 的版本使用自定义 API &#xff08;WebSocketServlet 和 StreamInbound&#xff0c; 前者是一个容器&#xff0c;用来初始化 WebSocket 环境&#xff1b;后者是用来具体处理 WebSocket 请求和响应&#xff0c;详见案例分析部分&#xff09;&#xff0c;且 Tomcat7.0.3x 与 7.0.2x 的 createWebSocketInbound 方法的定义不同&#xff0c;增加了一个 HttpServletRequest 参数&#xff0c;使得可以从 request 参数中获取更多 WebSocket 客户端的信息&#xff0c;如下代码所示&#xff1a;

清单 4.Tomcat7.0.3X 版本 WebSocket API

public class EchoServlet extends WebSocketServlet {
&#64;Override
protected StreamInbound createWebSocketInbound(String subProtocol,
HttpServletRequest request) {//以下代码省略....
return new MessageInbound() {//以下代码省略....
}
protected void onBinaryMessage(ByteBuffer buffer)
throws IOException {//以下代码省略...
}
protected void onTextMessage(CharBuffer buffer) throws IOException {getWsOutbound().writeTextMessage(buffer);//以下代码省略...
}
};
}
}

因此选择 WebSocket 的 Server 端重点需要选择其版本&#xff0c;通常情况下&#xff0c;更新的版本对 WebSocket 的支持是标准 JSR 规范 API&#xff0c;但也要考虑开发易用性及老版本程序移植性等方面的问题&#xff0c;如下文所述的客户案例&#xff0c;就是因为客户要求统一应用服务器版本所以使用的 Tomcat 7.0.3X 版本的 WebSocketServlet 实现&#xff0c;而不是 JSR356 的 &#64;ServerEndpoint 注释端点。

WebSocket 客户端 API

对于 WebSocket 客户端&#xff0c;主流的浏览器&#xff08;包括 PC 和移动终端&#xff09;现已都支持标准的 HTML5 的 WebSocket API&#xff0c;这意味着客户端的 WebSocket JavaScirpt 脚本具备良好的一致性和跨平台特性&#xff0c;以下列举了常见的浏览器厂商对 WebSocket 的支持情况&#xff1a;

表 2.WebSocket 客户端支持
浏览器 支持情况
Chrome Chrome version 4&#43;支持
Firefox Firefox version 5&#43;支持
IE IE version 10&#43;支持
Safari IOS 5&#43;支持
Android Brower Android 4.5&#43;支持

客户端 WebSocket API 基本上已经在各个主流浏览器厂商中实现了统一&#xff0c;因此使用标准 HTML5 定义的 WebSocket 客户端的 Javascript API 即可&#xff0c;当然也可以使用业界满足 WebSocket 标准规范的开源框架&#xff0c;如 Socket.io。

以下以一段代码示例说明 WebSocket 的客户端实现&#xff1a;

清单 5.WebSocket 客户端 API 示例

var ws &#61; new WebSocket(“ws://echo.websocket.org”); ws.onopen &#61; function(){ws.send(“Test!”); }; ws.onmessage &#61; function(evt){console.log(evt.data);ws.close();}; ws.onclose &#61; function(evt){console.log(“WebSocketClosed!”);}; ws.onerror &#61; function(evt){console.log(“WebSocketError!”);};

第一行代码是在申请一个 WebSocket 对象&#xff0c;参数是需要连接的服务器端的地址&#xff0c;同 HTTP 协议开头一样&#xff0c;WebSocket 协议的 URL 使用 ws://开头&#xff0c;另外安全的 WebSocket 协议使用 wss://开头。

第二行到第五行为 WebSocket 对象注册消息的处理函数&#xff0c;WebSocket 对象一共支持四个消息 onopen, onmessage, onclose 和 onerror&#xff0c;有了这 4 个事件&#xff0c;我们就可以很容易很轻松的驾驭 WebSocket。

当 Browser 和 WebSocketServer 连接成功后&#xff0c;会触发 onopen 消息&#xff1b;如果连接失败&#xff0c;发送、接收数据失败或者处理数据出现错误&#xff0c;browser 会触发 onerror 消息&#xff1b;当 Browser 接收到 WebSocketServer 发送过来的数据时&#xff0c;就会触发 onmessage 消息&#xff0c;参数 evt 中包含 Server 传输过来的数据&#xff1b;当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时&#xff0c;就会触发 onclose 消息。我们可以看出所有的操作都是采用异步回调的方式触发&#xff0c;这样不会阻塞 UI&#xff0c;可以获得更快的响应时间&#xff0c;更好的用户体验。

回页首

WebSocket 案例分析

以下我们以一个真实的客户案例来分析说明 WebSocket 的优势及具体开发实现&#xff08;为保护客户隐私&#xff0c;以下描述省去客户名&#xff0c;具体涉及业务细节的代码在文中不再累述&#xff09;。

案例介绍

该客户为一个移动设备制造商&#xff0c;移动设备装载的是 Android/IOS 操作系统&#xff0c;设备分两类&#xff08;以下简称 A&#xff0c;B 两类&#xff09;&#xff0c;A 类设备随时处于移动状态中&#xff0c;B 类设备为 A 类设备的管理控制设备&#xff0c;客户需要随时在 B 类设备中看到所属 A 类设备的地理位置信息及状态信息。如 A 类设备上线&#xff0c;离线的时候&#xff0c;B 类设备需要立即获得消息通知&#xff0c;A 类设备上报时&#xff0c;B 类设备也需要实时获得该上报 A 类设备的地理位置信息。

为降低跨平台的难度及实施工作量&#xff0c;客户考虑轻量级的 Web App 的方式屏蔽 Android/IOS 平台的差异性&#xff0c;A 类设备数量众多&#xff0c;且在工作状态下 A 类设备处于不定时的移动状态&#xff0c;而 B 类设备对 A 类设备状态变化的感知实时性要求很高&#xff08;秒级&#xff09;。

根据以上需求&#xff0c;A/B 类设备信息存放在后台数据库中&#xff0c;A/B 类设备的交互涉及 Web 客户端/服务器频繁和高并发的请求-相应&#xff0c;如果使用传统的 HTTP 请求-响应模式&#xff0c;B 类设备的 Web App 上需要对服务进行轮询&#xff0c;势必会对服务器带来大的负载压力&#xff0c;且当 A 类设备没有上线或者上报等活动事件时&#xff0c;B 类设备的轮询严重浪费网络资源。

解决方案

综上所述&#xff0c;项目采用 WebSocket 技术实现实时消息的通知及推送&#xff0c;每当 A 类设备/B 类设备上线登录成功即打开 WebSocket 的 HTTP 长连接&#xff0c;新的 A 类设备上线&#xff0c;位置变化&#xff0c;离线等状态变化通过 WebSocket 发送实时消息&#xff0c;WebSocket Server 端处理 A 类设备的实时消息&#xff0c;并向所从属的 B 类设备实时推送。

WebSocket 客户端使用 jQuery Mobile&#xff08;jQuery Mobile 移动端开发在本文中不再详细描述&#xff0c;感兴趣的读者可以参考jQuery Mobile 简介)&#xff0c;使用原生 WebSocket API 实现与服务端交互。

服务端沿用客户已有的应用服务器 Tomcat 7.0.33 版本&#xff0c;使用 Apache 自定义 API 实现 WebSocket Server 端&#xff0c;为一个上线的 A 类设备生成一个 WebSocket 的 HTTP 长连接&#xff0c;每当 A 类设备有上线&#xff0c;位置更新&#xff0c;离线等事件的时候&#xff0c;客户端发送文本消息&#xff0c;服务端识别并处理后&#xff0c;向所属 B 类设备发送实时消息&#xff0c;B 类设备客户端接收消息后&#xff0c;识别到 A 类设备的相应事件&#xff0c;完成对应的 A 类设备位置刷新以及其他业务操作。

其涉及的 A 类设备&#xff0c;B 类设备及后台服务器交互时序图如下&#xff1a;

图 3&#xff1a;A/B 类设备 WebSocket 交互图
图 3&#xff1a;A/B 类设备 WebSocket 交互图

A/B 类设备的 WebSocket 客户端封装在 websocket.js 的 Javascript 代码中&#xff0c;与 jQuery MobileApp 一同打包为移动端 apk/ipa 安装包&#xff1b;WebSocket 服务端实现主要为 WebSocketDeviceServlet.java, WebSocketDeviceInbound.java&#xff0c;WebSocketDeviceInboundPool.java 几个类。下文我们一一介绍其具体代码实现。

代码实现

在下文中我们把本案例中的主要代码实现做解释说明&#xff0c;读者可以下载完整的代码清单做详细了解。

WebSocketDeviceServlet 类

A 类设备或者 B 类设备发起 WebSocket 长连接后&#xff0c;服务端接受请求的是 WebSocketDeviceServlet 类&#xff0c;跟传统 HttpServlet 不同的是&#xff0c;WebSocketDeviceServlet 类实现 createWebSocketInbound 方法&#xff0c;类似 SocketServer 的 accept 方法&#xff0c;新生产的 WebSocketInbound 实例对应客户端 HTTP 长连接&#xff0c;处理与客户端交互功能。

WebSocketDeviceServlet 服务端代码示例如下&#xff1a;

清单 6.WebSocketDeviceServlet.java 代码示例

public class WebSocketDeviceServlet extends org.apache.catalina.websocket.WebSocketServlet {private static final long serialVersionUID &#61; 1L;&#64;Overrideprotected StreamInbound createWebSocketInbound(String subProtocol,HttpServletRequest request) {WebSocketDeviceInbound newClientConn &#61; new WebSocketDeviceInbound(request);WebSocketDeviceInboundPool.addMessageInbound(newClientConn);return newClientConn;}}

代码解释&#xff1a;

WebSocketServlet 是 WebSocket 协议的后台监听进程&#xff0c;和传统 HTTP 请求一样&#xff0c;WebSocketServlet 类似 Spring/Struct 中的 Servlet 监听进程&#xff0c;只不过通过客户端 ws 的前缀指定了其监听的协议为 WebSocket。

WebSocketDeviceInboundPool 实现了类似 JDBC 数据库连接池的客户端 WebSocket 连接池功能&#xff0c;并统一处理 WebSocket 服务端对单个客户端/多个客户端&#xff08;同组 A 类设备&#xff09;的消息推送&#xff0c;详见 WebSocketDeviceInboundPool 代码类解释。

WebSocketDeviceInboundl 类

WebSocketDeviceInbound 类为每个 A 类和 B 类设备验证登录后&#xff0c;客户端建立的 HTTP 长连接的对应后台服务类&#xff0c;类似 Socket 编程中的 SocketServer accept 后的 Socket 进程&#xff0c;在 WebSocketInbound 中接收客户端发送的实时位置信息等消息&#xff0c;并向客户端&#xff08;B 类设备&#xff09;发送下属 A 类设备实时位置信息及位置分析结果数据&#xff0c;输入流和输出流都是 WebSocket 协议定制的。WsOutbound 负责输出结果&#xff0c;StreamInbound 和 WsInputStream 负责接收数据&#xff1a;

清单 7.WebSocketDeviceInbound.java 类代码示例

public class WebSocketDeviceInbound extends MessageInbound {
private final HttpServletRequest request;
private DeviceAccount connectedDevice;public DeviceAccount getConnectedDevice() {
return connectedDevice;
}public void setConnectedDevice(DeviceAccount connectedDevice) {
this.connectedDevice &#61; connectedDevice;
}public HttpServletRequest getRequest() {
return request;
}public WebSocketDeviceInbound(HttpServletRequest request) {
this.request &#61; request;
DeviceAccount connectedDa &#61; (DeviceAccount)request.getSession(true).getAttribute("connectedDevice");
if(connectedDa&#61;&#61;null)
{
String deviceId &#61; request.getParameter("id");
DeviceAccountDao deviceDao &#61; new DeviceAccountDao();
connectedDa &#61; deviceDao.getDaById(Integer.parseInt(deviceId));
}
this.setConnectedDevice(connectedDa);
}&#64;Override
protected void onOpen(WsOutbound outbound) {/}&#64;Override
protected void onClose(int status) {
WebSocketDeviceInboundPool.removeMessageInbound(this);}&#64;Override
protected void onBinaryMessage(ByteBuffer message) throws IOException {
throw new UnsupportedOperationException("Binary message not supported.");
}&#64;Override
protected void onTextMessage(CharBuffer message) throws IOException {
WebSocketDeviceInboundPool.processTextMessage(this, message.toString());}public void sendMessage(BaseEvent event)
{
String eventStr &#61; JSON.toJSONString(event);
try {
this.getWsOutbound().writeTextMessage(CharBuffer.wrap(eventStr));
//…以下代码省略
} catch (IOException e) {
e.printStackTrace();
}
}
}

代码解释&#xff1a;

connectedDevice 是当前连接的 A/B 类客户端设备类实例&#xff0c;在这里做为成员变量以便后续处理交互。

sendMessage 函数向客户端发送数据&#xff0c;使用 Websocket WsOutbound 输出流向客户端推送数据&#xff0c;数据格式统一为 JSON。

onTextMessage 函数为客户端发送消息到服务器时触发事件&#xff0c;调用 WebSocketDeviceInboundPool 的 processTextMessage 统一处理 A 类设备的登入&#xff0c;更新位置&#xff0c;离线等消息。

onClose 函数触发关闭事件&#xff0c;在连接池中移除连接。

WebSocketDeviceInbound 构造函数为客户端建立连接后&#xff0c;WebSocketServlet 的 createWebSocketInbound 函数触发&#xff0c;查询 A 类/B 类设备在后台数据库的详细数据并实例化 connectedDevice 做为 WebSocketDeviceInbound 的成员变量&#xff0c;WebSocketServlet 类此时将新的 WebSocketInbound 实例加入自定义的 WebSocketDeviceInboundPool 连接池中&#xff0c;以便统一处理 A/B 设备组员关系及位置分布信息计算等业务逻辑。

WebSocketDeviceInboundPool 类

WebSocketInboundPool 类: 由于需要处理大量 A 类 B 类设备的实时消息&#xff0c;服务端会同时存在大量 HTTP 长连接&#xff0c;为统一管理和有效利用 HTTP 长连接资源&#xff0c;项目中使用了简单的 HashMap 实现内存连接池机制&#xff0c;每次设备登入新建的 WebSocketInbound 都放入 WebSocketInbound 实例的连接池中&#xff0c;当设备登出时&#xff0c;从连接池中 remove 对应的 WebSocketInbound 实例。

此外&#xff0c;WebSocketInboundPool 类还承担 WebSocket 客户端处理 A 类和 B 类设备间消息传递的作用&#xff0c;在客户端发送 A 类设备登入、登出及位置更新消息的时候&#xff0c;服务端 WebSocketInboundPool 进行位置分布信息的计算&#xff0c;并将计算完的结果向同时在线的 B 类设备推送。

清单 8.WebSocketDeviceInboundPool.java 代码示例

public class WebSocketDeviceInboundPool {private static final ArrayList connections &#61;
new ArrayList();public static void addMessageInbound(WebSocketDeviceInbound inbound){
//添加连接
DeviceAccount da &#61; inbound.getConnectedDevice();
System.out.println("新上线设备 : " &#43; da.getDeviceNm());
connections.add(inbound);
}public static ArrayList getOnlineDevices(){
ArrayList onlineDevices &#61; new ArrayList();
for(WebSocketDeviceInbound webClient:connections)
{
onlineDevices.add(webClient.getConnectedDevice());
}
return onlineDevices;
}public static WebSocketDeviceInbound getGroupBDevices(String group){
WebSocketDeviceInbound retWebClient &#61;null;
for(WebSocketDeviceInbound webClient:connections)
{
if(webClient.getConnectedDevice().getDeviceGroup().equals(group)&&
webClient.getConnectedDevice().getType().equals("B")){
retWebClient &#61; webClient;
}
}
return retWebClient;
}
public static void removeMessageInbound(WebSocketDeviceInbound inbound){
//移除连接
System.out.println("设备离线 : " &#43; inbound.getConnectedDevice());
connections.remove(inbound);
}public static void processTextMessage(WebSocketDeviceInbound inbound,String message){BaseEvent receiveEvent &#61; (BaseEvent)JSON.parseObject(message.toString(),BaseEvent.class);
DBEventHandleImpl dbEventHandle &#61; new DBEventHandleImpl();
dbEventHandle.setReceiveEvent(receiveEvent);
dbEventHandle.HandleEvent();
if(receiveEvent.getEventType()&#61;&#61;EventConst.EVENT_MATCHMATIC_RESULT||
receiveEvent.getEventType()&#61;&#61;EventConst.EVENT_GROUP_DEVICES_RESULT||
receiveEvent.getEventType()&#61;&#61;EventConst.EVENT_A_REPAIRE){
String clientDeviceGroup &#61; ((ArrayList)
receiveEvent.getEventObjs()).get(0).getDeviceGroup();
WebSocketDeviceInbound bClient &#61; getGroupBDevices(clientDeviceGroup);
if(bClient!&#61;null){
sendMessageToSingleClient(bClient,dbEventHandle.getReceiveEvent());
}
}
}
}
public static void sendMessageToAllDevices(BaseEvent event){
try {
for (WebSocketDeviceInbound webClient : connections) {
webClient.sendMessage(event);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void sendMessageToSingleClient(WebSocketDeviceInbound webClient,BaseEvent event){try {
webClient.sendMessage(event);}
catch (Exception e) {
e.printStackTrace();
}
}
}

代码解释&#xff1a;

addMessageInbound 函数向连接池中添加客户端建立好的连接。

getOnlineDevices 函数获取所有的连线的 A/B 类设备。

removeMessageInbound 函数实现 A 类设备或者 B 类设备离线退出&#xff08;服务端收到客户端关闭 WebSocket 连接事件&#xff0c;触发 WebSocketInbound 中的 onClose 方法&#xff09;&#xff0c;从连接池中删除连接设备客户端的连接实例。

processTextMessage 完成处理客户端消息&#xff0c;这里使用了消息处理的机制&#xff0c;包括解码客户端消息&#xff0c;根据消息构造 Event 事件&#xff0c;通过 EventHandle 多线程处理&#xff0c;处理完后向客户端返回&#xff0c;可以向该组 B 设备推送消息&#xff0c;也可以向发送消息的客户端推送消息。

sendMessageToAllDevices 函数实现发送数据给所有在线 A/B 类设备客户端。sendMessageToSingleClient 函数实现向某一 A/B 类设备客户端发送数据。

websocket.js 客户端代码

客户端代码 websocket.js&#xff0c;客户端使用标准 HTML5 定义的 WebSocket API&#xff0c;从而保证支持 IE9&#43;&#xff0c;Chrome&#xff0c;FireFox 等多种浏览器&#xff0c;并结合 jQueryJS 库 API 处理 JSON 数据的处理及发送。

清单 9&#xff1a;客户端 WebSocket.js 脚本示例

var websocket&#61;window.WebSocket || window.MozWebSocket;
var isConnected &#61; false;function doOpen(){isConnected &#61; true;
if(deviceType&#61;&#61;&#39;B&#39;){mapArea&#61;&#39;mapB&#39;;doLoginB(mapArea);}else{mapArea&#61;&#39;mapA&#39;;doLoginA(mapArea);}}function doClose(){
showDiagMsg("infoField","已经断开连接", "infoDialog");
isConnected &#61; false;
}function doError() {
showDiagMsg("infoField","连接异常!", "infoDialog");
isConnected &#61; false;}function doMessage(message){
var event &#61; $.parseJSON(message.data);
doReciveEvent(event);
}function doSend(message) {
if (websocket !&#61; null) {
websocket.send(JSON.stringify(message));
} else {
showDiagMsg("infoField","您已经掉线&#xff0c;无法与服务器通信!", "infoDialog");
}
}//初始话 WebSocket
function initWebSocket(wcUrl) {
if (window.WebSocket) {
websocket &#61; new WebSocket(encodeURI(wcUrl));
websocket.onopen &#61; doOpen;
websocket.onerror &#61; doError;
websocket.onclose &#61; doClose;
websocket.onmessage &#61; doMessage;
}
else{
showDiagMsg("infoField","您的设备不支持 webSocket!", "infoDialog");}
};function doReciveEvent(event){
//设备不存在&#xff0c;客户端断开连接
if(event.eventType&#61;&#61;101){
showDiagMsg("infoField","设备不存在或设备号密码错!", "infoDialog");
websocket.close();
}
//返回组设备及计算目标位置信息&#xff0c;更新地图
else if(event.eventType&#61;&#61;104||event.eventType&#61;&#61;103){
clearGMapOverlays(mapB); $.each(event.eventObjs,function(idx,item){var deviceNm &#61; item.deviceNm;//google api
// var deviceLocale &#61; new google.maps.LatLng(item.lag,item.lat);
//baidu apivar deviceLocale &#61; new BMap.Point(item.lng,item.lat);var newMarker;if(item.status&#61;&#61;&#39;target&#39;){newMarker &#61; addMarkToMap(mapB,deviceLocale,deviceNm,true);//…以下代码省略}else{newMarker &#61; addMarkToMap(mapB,deviceLocale,deviceNm);} markArray.push(newMarker);});showDiagMsg("infoField","有新报修设备或设备离线, 地图已更新&#xff01;", "infoDialog");
}}

代码解释&#xff1a;

doOpen 回调函数处理打开 WebSocket&#xff0c;A 类设备或者 B 类设备连接上 WebSocket 服务端后&#xff0c;将初始化地图并显示默认位置&#xff0c;然后向服务端发送设备登入的消息。

doReciveEvent 函数处理关闭 WebSocket&#xff0c;A 类/B 类设备离线&#xff08;退出移动终端上的应用&#xff09;时&#xff0c;服务端关闭 HTTP 长连接&#xff0c;客户端 WebSocket 对象执行 onclose 回调句柄。

initWebSocket 初始化 WebSocket&#xff0c;连接 WebSocket 服务端&#xff0c;并设置处理回调句柄&#xff0c;如果浏览器版本过低而不支持 HTML5&#xff0c;提示客户设备不支持 WebSocket。

doSend 函数处理客户端向服务端发送消息&#xff0c;注意 message 是 JSON OBJ 对象&#xff0c;通过 JSON 标准 API 格式化字符串。

doMessage 函数处理 WebSocket 服务端返回的消息&#xff0c;后台返回的 message 为 JSON 字符串&#xff0c;通过 jQuery 的 parseJSON API 格式化为 JSON Object 以便客户端处理 doReciveEvent 函数时客户端收到服务端返回消息的具体处理&#xff0c;由于涉及大量业务逻辑在此不再赘述。

回页首

结束语

以上简要介绍了 WebSocket 的由来&#xff0c;原理机制以及服务端/客户端实现&#xff0c;并以实际客户案例指导并讲解了如何使用 WebSocket 解决实时响应及服务端消息推送方面的问题。本文适用于熟悉 HTML 协议规范和 J2EE Web 编程的读者&#xff0c;旨在帮助读者快速熟悉 HTML5 WebSocket 的原理和开发应用。文中的服务端及客户端项目代码可供下载&#xff0c;修改后可用于用户基于 WebSocket 的 HTTP 长连接的实际生产环境中。

使用四种框架分别实现1百万websocket常连接的服务器

参见&#xff1a;http://www.open-open.com/lib/view/open1435905714122.html







推荐阅读
  • 本文深入解析了通过JDBC实现ActiveMQ消息持久化的机制。JDBC能够将消息可靠地存储在多种关系型数据库中,如MySQL、SQL Server、Oracle和DB2等。采用JDBC持久化方式时,数据库会自动生成三个关键表:`activemq_msgs`、`activemq_lock`和`activemq_ACKS`,分别用于存储消息数据、锁定信息和确认状态。这种机制不仅提高了消息的可靠性,还增强了系统的可扩展性和容错能力。 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 使用jQuery与百度地图API实现地址转经纬度功能
    本文详细介绍了如何利用jQuery和百度地图API将地址转换为经纬度,包括申请API密钥、页面构建及核心代码实现。 ... [详细]
  • 本文探讨了 Java 中 HttpClient 和 HtmlUnit 的区别,重点介绍了它们的功能和应用场景。 ... [详细]
  • Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ... [详细]
  • 三角测量计算三维坐标的代码_双目三维重建——层次化重建思考
    双目三维重建——层次化重建思考FesianXu2020.7.22atANTFINANCIALintern前言本文是笔者阅读[1]第10章内容的笔记,本文从宏观的角度阐 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 探索OpenWrt中的LuCI框架
    本文深入探讨了OpenWrt系统中轻量级HTTP服务器uhttpd的工作原理及其配置,重点介绍了LuCI界面的实现机制。 ... [详细]
  • 本文详细介绍了在PHP中如何获取和处理HTTP头部信息,包括通过cURL获取请求头信息、使用header函数发送响应头以及获取客户端HTTP头部的方法。同时,还探讨了PHP中$_SERVER变量的使用,以获取客户端和服务器的相关信息。 ... [详细]
  • 本文由公众号【数智物语】(ID: decision_engine)发布,关注获取更多干货。文章探讨了从数据收集到清洗、建模及可视化的全过程,介绍了41款实用工具,旨在帮助数据科学家和分析师提升工作效率。 ... [详细]
  • 页面预渲染适用于主要包含静态内容的页面。对于依赖大量API调用的动态页面,建议采用SSR(服务器端渲染),如Nuxt等框架。更多优化策略可参见:https://github.com/HaoChuan9421/vue-cli3-optimization ... [详细]
  • 本文总结了一次针对大厂Java研发岗位的面试经历,探讨了面试中常见的问题及其背后的原因,并分享了一些实用的面试准备资料。 ... [详细]
  • oracle 对硬件环境要求,Oracle 10G数据库软硬件环境的要求 ... [详细]
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • 【线段树】  本质是二叉树,每个节点表示一个区间[L,R],设m(R-L+1)2(该处结果向下取整)左孩子区间为[L,m],右孩子区间为[m ... [详细]
author-avatar
道义信_686
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有