[关闭]
@MrXiao 2018-12-12T16:41:45.000000Z 字数 4772 阅读 1096

Tomcat8整合websocket

websocket


原文地址:Tomcat8整合websocket

使用 tomcat8 开发 WebSocket 服务端非常简单,大致有如下两种方式。

  1. 使用注解方式开发,被 @ServerEndpoint 修饰的 Java 类即可作为 WebSocket 服务端
  2. 继承 Endpoint 基类实现 WebSocket 服务端

开发被 @ServerEndpoint 修饰的类之后,该类中还可以定义如下方法。

下面将基于 WebSocket 开发一个多人实时聊天的程序,在这个程序中,每个客户所用的浏览器都与服务器建立一个 WebSocket,从而保持实时连接,这样客户端的浏览器可以随时把数据发送到服务器端;当服务器收到任何一个浏览器发送来的消息之后,将该消息依次向每个客户端浏览器发送一遍。

按如下步骤开发 WebSocket 服务端程序即可

  1. 定义 @OnOpen 修饰的方法,每当客户端连接进来时激发该方法,程序使用集合保存所有连接进来的客户端。
  2. 定义 @OnMessage 修饰的方法,每当该服务端收到客户端消息时激发该方法,服务端收到消息之后遍历保存客户端的集合,并将消息逐个发给所有客户端。
  3. 定义 @OnClose 修饰的方法,每当客户端断开与该服务端连接时激发该方法,程序将该客户端从集合中删除。

1、搭建web系统

首先搭建一个简单可访问的web系统,这里提供一个我自己搭建的SSM框架供使用。

使用Tomcat整合websocket,注入导入javaee-api-7.0.jar

  1. <dependency>
  2. <groupId>javax</groupId>
  3. <artifactId>javaee-api</artifactId>
  4. <version>7.0</version>
  5. </dependency>

2、创建Websocket服务端

ChatEndpoint.java

  1. package com.websocket;
  2. import java.io.IOException;
  3. import java.util.Set;
  4. import java.util.concurrent.CopyOnWriteArraySet;
  5. import java.util.concurrent.atomic.AtomicInteger;
  6. import javax.websocket.*;
  7. import javax.websocket.server.ServerEndpoint;
  8. /**
  9. * 〈Tomcat8整合WS〉
  10. *
  11. * @author xiaoyue
  12. * @create 2018/12/12 15:36
  13. * @since 1.0.0
  14. */
  15. @ServerEndpoint(value = "/ws/chat")
  16. public class ChatEndpoint {
  17. private static final String GUEST_PREFIX = "访客";
  18. private static final AtomicInteger connectionIds = new AtomicInteger(0);
  19. // 定义一个集合,用于保存所有接入的 WebSocket 客户端
  20. private static final Set<ChatEndpoint> clientSet = new CopyOnWriteArraySet<>();
  21. // 定义一个成员变量,记录 WebSocket 客户端的聊天昵称
  22. private final String nickname;
  23. // 定义一个成员变量,记录与 WebSocket 之间的会话
  24. private Session session;
  25. public ChatEndpoint() {
  26. nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
  27. }
  28. // 当客户端连接进来时自动激发该方法
  29. @OnOpen
  30. public void start(Session session) {
  31. this.session = session;
  32. // 将 WebSocket 客户端会话添加到集合中
  33. clientSet.add(this);
  34. String message = String.format("[%s %s]", nickname, "加入了聊天室");
  35. // 发送消息
  36. broadcast(message);
  37. }
  38. // 当客户端断开连接时自动激发该方法
  39. @OnClose
  40. public void end() {
  41. clientSet.remove(this);
  42. String message = String.format("[%s %s]", nickname, "离开了聊天室!");
  43. // 发送消息
  44. broadcast(message);
  45. }
  46. // 每当收到客户端消息时自动激发该方法
  47. @OnMessage
  48. public void incoming(String message) {
  49. String filteredMessage = String.format("%s: %s", nickname, filter(message));
  50. // 发送消息
  51. broadcast(filteredMessage);
  52. }
  53. // 当客户端通信出现错误时激发该方法
  54. @OnError
  55. public void onError(Throwable t) throws Throwable {
  56. System.out.println("WebSocket 服务端错误" + t);
  57. }
  58. // 实现广播消息的工具方法
  59. private static void broadcast(String msg) {
  60. // 遍历服务器关联的所有客户端
  61. for (ChatEndpoint client : clientSet) {
  62. try {
  63. synchronized (client) {
  64. // 发送消息
  65. client.session.getBasicRemote().sendText(msg);
  66. }
  67. } catch (IOException e) {
  68. System.out.println("聊天错误,向客户端" + client + "发送消息出现错误。");
  69. clientSet.remove(client);
  70. try {
  71. client.session.close();
  72. } catch (IOException el) {
  73. }
  74. String message = String.format("[%s %s]", client.nickname, "已经被断开了连接");
  75. broadcast(message);
  76. }
  77. }
  78. }
  79. // 定义一个工具方法,用于对字符串中的 HTML 字符标签进行转义
  80. private static String filter(String message) {
  81. if (message == null)
  82. return null;
  83. char content[] = new char[message.length()];
  84. message.getChars(0, message.length(), content, 0);
  85. StringBuilder result = new StringBuilder(content.length + 50);
  86. for (int i = 0; i < content.length; i++) {
  87. // 控制对尖括号等特殊字符转义
  88. switch (content[i]) {
  89. case '<':
  90. result.append("<");
  91. break;
  92. case '>':
  93. result.append(">");
  94. break;
  95. case '&':
  96. result.append("&");
  97. break;
  98. case '"':
  99. result.append("\"");
  100. break;
  101. default:
  102. result.append(content[i]);
  103. }
  104. }
  105. return (result.toString());
  106. }
  107. }

需要说明的是,该 CharEndpoint 类并不是真正的 WebSocket 服务端,它只实现了 WebSocket 服务端的核心功能,Tomcat 会调用它的方法作为 WebSocket 服务端。因此,Tomcat 会为每个 WebSocket 客户端创建一个 ChatEndpoint 对象,也就是说,有一个 WebSocket 服务端,程序就有一个 ChatEndpoint 对象。所以上面程序中的 clientSet 集合保存了多个 ChatEndpoint 对象,其中每个 ChatEndpoint 对象对应一个 WebSocket 客户端。

3、创建JS客户端

chat.jsp

  1. <%--
  2. Created by IntelliJ IDEA.
  3. User: Administrator
  4. Date: 2018/12/12
  5. Time: 15:47
  6. To change this template use File | Settings | File Templates.
  7. --%>
  8. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  9. <html>
  10. <head>
  11. <title>Title</title>
  12. </head>
  13. <body>
  14. <div style="width: 600px; height:240px;overflow-y: auto;border: 1px solid #333;" id="show">
  15. </div>
  16. <input type="text" size="80" id="msg" name="msg" placeholder="请输入聊天内容"/ >
  17. <input type="button" value="发送" id="sendBtn" name="sendBtn" />
  18. <br>
  19. <script>
  20. window.onload = function() {
  21. // 创建 WebSocket 对象
  22. var webSocket = new WebSocket("ws://127.0.0.1:8080/ws/chat");
  23. var sendMsg = function() {
  24. var inputElement = document.getElementById('msg');
  25. // 发送消息
  26. webSocket.send(inputElement.value);
  27. // 清空单行文本框
  28. inputElement.value = "";
  29. };
  30. var send = function(event) {
  31. if (event.keyCode == 13) {
  32. sendMsg();
  33. }
  34. };
  35. webSocket.onopen = function() {
  36. console.log('WebSocket已经打开');
  37. // 为 onmessage 事件绑定监听器,接收消息
  38. webSocket.onmessage = function(event) {
  39. var show = document.getElementById('show');
  40. // 接收并显示消息
  41. show.innerHTML += event.data + "<br/>";
  42. show.scrollTop = show.scrollHeight;
  43. };
  44. document.getElementById('msg').onkeydown = send;
  45. document.getElementById('sendBtn').onclick = sendMsg;
  46. };
  47. webSocket.onclose = function() {
  48. // document.getElementById('msg').onkeydown = null;
  49. // document.getElementById('sendBtn').onclick = null;
  50. console.log('WebSocket已经被关闭');
  51. };
  52. }
  53. </script>
  54. </body>
  55. </html>
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注