2.14 Tomcat8.5的WebSocket支持
严格来说,WebSocket
并不属于JavaWeb
相关规范,WebSocket
属于HTML5
规范的一部分,WebSocket
允许通过JavaScript
建立与远程服务器的连接,从而允许远程服务器将数据推送给浏览器。
通过使用WebSocket
,可以构建出实时性要求比较高的应用,比如在线游戏、在线证券、设备监控、新闻在线播报等,只要服务器端有了新数据,服务端就可以直接将数据推送给浏览器,让浏览器显示最新的状态。
WebSocket
规范已经相当成熟,而且各种主流浏览器(如Firefox
、Chrome
、Safari
、Opera
等)都已经支持WebSocket
技术,Java EE
规范则提供了WebSocket
服务端规范,而Tomcat8.5
则对该规范提供了优秀的实现。
开发WebSocket服务端的方式
使用Tomcat8.5
开发WebSocket
服务端非常简单,大致有如下两种方式:
- 使用注解方式开发,被
@ServerEndpoint
修饰的Java
类即可作为WebSocket
服务端。
- 继承
Endpoint
基类实现WebSocket
服务端
由于使用注解方式开发不仅开发简单,而且是目前的主流方式,因此本书将介绍使用注解方式进行开发。
与开发Servlet
一样,Servlet
并不需要处理底层并发、网络通信等通用的底层细节——因为Servlet
处于Web
服务器中运行,Web
服务器为Servlet
处理了底层的并发、网络通信等;开发WebSocket
服务端同样需要位于web
服务器中运行,因此此处开发的WebSocket
同样无须处理并发、网络通信等细节。
开发被@ServerEndpoint
修饰的Java
类之后,该类中还可以定义如下方法:
- 被
@OnOpen
修饰的方法:当客户端与该WebSocket
服务端建立连接时激发该方法。
- 被
@OnClose
修饰的方法:当客户端与该WebSocket
服务端断开连接时激发该方法
- 被
@OnMessage
修饰的方法:当WebSocket
服务端收到客户端消息时激发该方法。
- 被
@OnError
修饰的方法:当客户端与该WebSocket
服务端连接出现错误时激发该方法。
多人实时聊天程序
思路
下面将基于WebSocket
开发一个多人实时聊天的程序,该程序的思路很简单:
- 在这个程序中,每个客户所用的浏览器都与服务器建立一个
WebSocket
,从而保持实时连接,这样客户端的浏览器可以随时把数据发送到服务器端;
- 当服务器收到任何一个浏览器发送来的消息之后,将该消息依次向每个客户端浏览器发送一遍。
图2.54显示了基于WebSocket
的多人实时聊天示意图
步骤
为了实现图2.54所示的示意图,按如下步骤开发WebSocket
服务端程序即可:
- 定义
@OnOpen
修饰的方法,每当客户端连接进来时激发该方法,程序使用集合保存所有连接进来的客户端。
- 定义
@OnMessage
修饰的方法,每当该服务端收到客户端消息时激发该方法,服务端收到消息之后遍历保存客户端的集合,并将消息逐个发给所有客户端。
- 定义
@OnClose
修饰的方法,每当客户端断开与该服务端连接时激发该方法,程序将该客户端从集合中删除。
程序示例
项目结构
E:\workspacne_JDK8Tomcat8.5\WebSocket
├─src\
│ └─lee\
│ └─ChatEntpoint.java
└─WebContent\
├─chat.html
├─META-INF\
│ └─MANIFEST.MF
└─WEB-INF\
├─lib\
└─web.xml
ChatEntpoint.java
下面程序就是基于WebSocket
实现多人实时聊天的服务器程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| package lee;
import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import javax.websocket.*; import javax.websocket.server.*;
@ServerEndpoint(value = "/websocket/chat") public class ChatEntpoint { private static final String GUEST_PREFIX = "访客"; private static final AtomicInteger connectionIds = new AtomicInteger(0); private static final Set<ChatEntpoint> clientSet = new CopyOnWriteArraySet<>(); private final String nickname; private Session session;
public ChatEntpoint() { nickname = GUEST_PREFIX + connectionIds.getAndIncrement(); }
@OnOpen public void start(Session session) { this.session = session; clientSet.add(this); String message = String.format("【%s %s】", nickname, "加入了聊天室!"); broadcast(message); }
@OnClose public void end() { clientSet.remove(this); String message = String.format("【%s %s】", nickname, "离开了聊天室!"); broadcast(message); }
@OnMessage public void incoming(String message) { String filteredMessage = String.format("%s: %s", nickname, filter(message)); broadcast(filteredMessage); }
@OnError public void onError(Throwable t) throws Throwable { System.out.println("WebSocket服务端错误 " + t); }
private static void broadcast(String msg) { for (ChatEntpoint client : clientSet) { try { synchronized (client) { client.session.getBasicRemote().sendText(msg); } } catch (IOException e) { System.out.println("聊天错误,向客户端 " + client + " 发送消息出现错误。"); clientSet.remove(client); try { client.session.close(); } catch (IOException e1) { } String message = String.format("【%s %s】", client.nickname, "已经被断开了连接。"); broadcast(message); } } }
private static String filter(String message) { if (message == null) return null; char content[] = new char[message.length()]; message.getChars(0, message.length(), content, 0); StringBuilder result = new StringBuilder(content.length + 50); for (int i = 0; i < content.length; i++) { switch (content[i]) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; case '"': result.append("""); break; default: result.append(content[i]); } } return (result.toString()); } }
|
上面的ChatEntpoint
主要就是实现了@OnOpen
、@OnClose
、@OnMessage
和@OnError
这4个注解修饰的方法。
需要说明的是,该ChatEntpoint
类并不是真正的WebSocket
服务端,它只实现了WebSocket
服务端的核心功能,Tomcat
会调用它的方法作为WebSocket
服务端。因此,Tomcat
会为每个WebSocket
客户端创建一个ChatEntpoint
对象,也就是说,有一个WebSocket
客户端,程序就有一个ChatEntpoint
对象。所以上面程序中clientSet
集合保存了多个ChatEntpoint
对象,其中每个ChatEndpoint
对象对应一个WebSocket
客户端。
编译ChatEntpoint
类,并将生成的class
文件放在Web
应用的WEB-NF/Classes
目录下,该ChatEntpoint
即可作为WebSocket
服务端使用。
上面ChatEntpoint
类用到了WebSocket API
规范的相关注解,因此读者需要将Tomcat
的lib
目录下的websocket-api.jar
添加到CLASSPATH
环境变量下。
chat.html
接下来使用JavaScript
开发WebSocket
客户端。此处WebSocket
客户端使用一个简单的HTML
页面即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <!DOCTYPE html> <html> <head> <meta name="author" content="Yeeku.H.Lee(CrazyIt.org)" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title> 使用WebSocket通信 </title> <script type="text/javascript"> var webSocket = new WebSocket("ws://127.0.0.1:8888/WebSocket/websocket/chat"); var sendMsg = function() { var inputElement = document.getElementById('msg'); webSocket.send(inputElement.value); inputElement.value = ""; } var send = function(event) { if (event.keyCode == 13) { sendMsg(); } }; webSocket.onopen = function() { webSocket.onmessage= function(event) { var show = document.getElementById('show') show.innerHTML += event.data + "<br/>"; show.scrollTop = show.scrollHeight; } document.getElementById('msg').onkeydown = send; document.getElementById('sendBn').onclick = sendMsg; }; webSocket.onclose = function () { document.getElementById('msg').onkeydown = null; document.getElementById('sendBn').onclick = null; Console.log('WebSocket已经被关闭。'); }; </script> </head> <body> <div style="width:600px;height:240px; overflow-y:auto;border:1px solid #333;" id="show"></div> <input type="text" size="80" id="msg" name="msg" placeholder="输入聊天内容"/> <input type="button" value="发送" id="sendBn" name="sendBn"/> </body> </html>
|
上面程序中代码:
1
| var webSocket = new WebSocket("ws://127.0.0.1:8888/WebSocket/websocket/chat");
|
创建了一个WebSocket
对象(WebSocket
是HTML5
规范新增的类)创建对象时指定WebSocket
服务端的地址。一旦程序得到了WebSocket
对象,接下来程序即可调用WebSocket
的send()
方法向服务器发送消息。
除此之外,还可以为WebSocket
绑定如下三个事件处理函数:
onopen
:当WebSocket
客户端与服务端建立连接时自动激发该事件处理函数。
onclose
:当WebSocket
客户端与服务端关闭连接时自动激发该事件处理函数。
onmessage
:当WebSocket
客户端收到服务端消息时自动激发该事件处理函数。
WebSocket
肯定会成为Web
应用开发的主流技术,这种技术颠覆了传统Web
应用请求响应架构模型,它可以让服务端与浏览器建立实时通信的Socket
,因此具有广泛的用途。为了使用WebSocket
,还需要JavaScript
编程知识,关于JavaScript
和WebSocket
相关知识,请参考《疯狂HTML5/CSS3/JavaScript讲义》
测试
首先启动ChatEntpoint
所在的web
应用,使用多个浏览器登录chat.html
页面聊天即可看到如图2.55所示的聊天效果。
1
| http://localhost:8080/WebSocket/chat.html
|