一. webSocket 简单介绍
随着互联网的发展, 传统的 HTTP 协议已经很难满足 Web 应用日益复杂的需求了. 近年来, 随着 html5 的诞生, WebSocket 协议被提出, 它实现了浏览器与服务器的全双工通信, 扩展了浏览器与服务端的通信功能, 使服务端也能主动向客户端发送数据.
我们知道, 传统的 HTTP 协议是无状态的, 每次请求 (request) 都要由客户端 (如 浏览器) 主动发起, 服务端进行处理后返回 response 结果, 而服务端很难主动向客户端发送数据; 这种客户端是主动方, 服务端是被动方的传统 Web 模式 对于信息变化不频繁的 Web 应用来说造成的麻烦较小, 而对于涉及实时信息的 Web 应用却带来了很大的不便, 如带有即时通信, 实时数据, 订阅推送等功能的应 用. 在 WebSocket 规范提出之前, 开发人员若要实现这些实时性较强的功能, 经常会使用折衷的解决方法: 轮询 (polling) 和 Comet 技术. 其实后者本质上也是一种轮询, 只不过有所改进.
轮询是最原始的实现实时 Web 应用的解决方案. 轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求, 频繁地查询是否有新的数据改动. 明显地, 这种方法会导致过多不必要的请求, 浪费流量和服务器资源.
Comet 技术又可以分为长轮询和流技术. 长轮询改进了上述的轮询技术, 减小了无用的请求. 它会为某些数据设定过期时间, 当数据过期后才会向服务端发送请求; 这种机制适合数据的改动不是特别频繁的情况. 流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个 HTTP 长连接, 服务端会不断更新连接状态以保持 HTTP 长连接存活; 这样的话, 服务端就可以通过这条长连接主动将数据发送给客户端; 流技术在大并发环境下, 可能会考验到服务端的性能.
这两种技术都是基于请求 - 应答模式, 都不算是真正意义上的实时技术; 它们的每一次请求, 应答, 都浪费了一定流量在相同的头部信息上, 并且开发复杂度也较大.
伴随着 HTML5 推出的 WebSocket, 真正实现了 Web 的实时通信, 使 B/S 模式具备了 C/S 模式的实时通信能力. WebSocket 的工作流程是这 样的: 浏览器通过 JavaScript 向服务端发出建立 WebSocket 连接的请求, 在 WebSocket 连接建立成功后, 客户端和服务端就可以通过 TCP 连接传输数据. 因为 WebSocket 连接本质上是 TCP 连接, 不需要每次传输都带上重复的头部数据, 所以它的数据传输量比轮询和 Comet 技术小 了很多. 本文不详细地介绍 WebSocket 规范, 主要介绍下 WebSocket 在 Java Web 中的实现.
JavaEE 7 中出了 JSR-356:Java API for WebSocket 规范. 不少 Web 容器, 如 Tomcat,Nginx,Jetty 等都支持 WebSocket.Tomcat 从 7.0.27 开始支持 WebSocket, 从 7.0.47 开始支持 JSR-356, 下面的 Demo 代码也是需要部署在 Tomcat7.0.47 以上的版本才能运行.
客户端 (Web 主页) 代码:
- <%@ page language="java" pageEncoding="UTF-8" %>
- <!DOCTYPE html>
- <html>
- <head>
- <title>Java 后端 WebSocket 的 Tomcat 实现</title>
- </head>
- <body>
- Welcome<br/><input id="text" type="text"/>
- <button onclick="send()">发送消息</button>
- <hr/>
- <button onclick="closeWebSocket()">关闭 WebSocket 连接</button>
- <hr/>
- <div id="message"></div>
- </body>
- <script type="text/javascript">
- var websocket = null;
- // 判断当前浏览器是否支持 WebSocket
- if ('WebSocket' in window) {
- websocket = new WebSocket("ws://172.16.98.31:8080/websocket/websocket");
- }
- else {
- alert('当前浏览器 Not support websocket');
- }
- // 连接发生错误的回调方法
- websocket.onerror = function () {
- setMessageInnerHTML("WebSocket 连接发生错误");
- };
- // 连接成功建立的回调方法
- websocket.onopen = function () {
- setMessageInnerHTML("WebSocket 连接成功");
- }
- // 接收到消息的回调方法
- websocket.onmessage = function (event) {
- setMessageInnerHTML(event.data);
- }
- // 连接关闭的回调方法
- websocket.onclose = function () {
- setMessageInnerHTML("WebSocket 连接关闭");
- }
- // 监听窗口关闭事件, 当窗口关闭时, 主动去关闭 websocket 连接, 防止连接还没断开就关闭窗口, server 端会抛异常.
- window.onbeforeunload = function () {
- closeWebSocket();
- }
- // 将消息显示在网页上
- function setMessageInnerHTML(innerHTML) {
- document.getElementById('message').innerHTML += innerHTML + '<br/>';
- }
- // 关闭 WebSocket 连接
- function closeWebSocket() {
- websocket.close();
- }
- // 发送消息
- function send() {
- var message = document.getElementById('text').value;
- websocket.send(message);
- }
- </script>
- </html>
Java Web 后端代码
- package cn.com;
- import java.io.IOException;
- import java.util.concurrent.CopyOnWriteArraySet;
- import javax.websocket.*;
- import javax.websocket.server.ServerEndpoint;
- /**
- * @ServerEndpoint 注解是一个类层次的注解, 它的功能主要是将目前的类定义成一个 websocket 服务器端,
- * 注解的值将被用于监听用户连接的终端访问 URL 地址, 客户端可以通过这个 URL 来连接到 WebSocket 服务器端
- * 每次请求, 都会创建一个实例
- */
- @ServerEndpoint("/websocket")
- public class WebSocketTest {
- // 静态变量, 用来记录当前在线连接数. 应该把它设计成线程安全的.
- private static int onlineCount = 0;
- //concurrent 包的线程安全 Set, 用来存放每个客户端对应的 MyWebSocket 对象. 若要实现服务端与单一客户端通信的话, 可以使用 Map 来存放, 其中 Key 可以为用户标识
- private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
- // 与某个客户端的连接会话, 需要通过它来给客户端发送数据
- private Session session;
- /**
- * 连接建立成功调用的方法
- * @param session 可选的参数. session 为与某个客户端的连接会话, 需要通过它来给客户端发送数据
- */
- @OnOpen
- public void onOpen(Session session){
- this.session = session;
- webSocketSet.add(this); // 加入 set 中
- addOnlineCount(); // 在线数加 1
- System.out.println("有新连接加入! 当前在线人数为" + getOnlineCount());
- }
- /**
- * 连接关闭调用的方法
- */
- @OnClose
- public void onClose(){
- webSocketSet.remove(this); // 从 set 中删除
- subOnlineCount(); // 在线数减 1
- System.out.println("有一连接关闭! 当前在线人数为" + getOnlineCount());
- }
- /**
- * 收到客户端消息后调用的方法
- * @param message 客户端发送过来的消息
- * @param session 可选的参数
- */
- @OnMessage
- public void onMessage(String message, Session session) {
- System.out.println("来自客户端的消息:" + message);
- // 群发消息
- for(WebSocketTest item: webSocketSet){
- try {
- item.sendMessage(message);
- } catch (IOException e) {
- e.printStackTrace();
- continue;
- }
- }
- }
- /**
- * 发生错误时调用
- * @param session
- * @param error
- */
- @OnError
- public void onError(Session session, Throwable error){
- System.out.println("发生错误");
- error.printStackTrace();
- }
- /**
- * 这个方法与上面几个方法不一样. 没有用注解, 是根据自己需要添加的方法.
- * @param message
- * @throws IOException
- */
- public void sendMessage(String message) throws IOException{
- this.session.getBasicRemote().sendText(message);
- //this.session.getAsyncRemote().sendText(message);
- }
- public static synchronized int getOnlineCount() {
- return onlineCount;
- }
- public static synchronized void addOnlineCount() {
- WebSocketTest.onlineCount++;
- }
- public static synchronized void subOnlineCount() {
- WebSocketTest.onlineCount--;
- }
- }
打开两个浏览器, 输入网址, 直接运行
来源: https://www.cnblogs.com/andy-alone/p/9154353.html