SpringBoot整合WebSocket
”人不是一座孤岛,所有人的不幸皆是我的不幸“——出处未知
人不是一个喜欢独立个体,在上个世纪有短信,这个世纪有微信,可见,沟通在人们中占据了多么重要的地位,而我们今天,就来使用WebSocket,来打造一个聊天室。
在此之前,我想先说说WebJars,你如果明白,也可以直接跳到WebSocket。
WebJars
是什么
什么是WebJars?WebJars是将客户端(浏览器)资源(JavaScript,Css等)打成jar包文件,以对资源进行统一依赖管理。WebJars的jar包部署在Maven中央仓库上。
为什么
我们在开发Java web项目的时候会使用像Maven,Gradle等构建工具以实现对jar包版本依赖管理,以及项目的自动化管理,但是对于JavaScript,Css等前端资源包,我们只能采用拷贝到webapp目录下的手工方式,这样做就无法对这些资源进行依赖管理。而且容易导致文件混乱、版本不一致等问题。那么WebJars就提供给我们这些前端资源的jar包形式,我们就可以进行依赖管理。
WebJars是将这些通用的Web前端资源打包成Java的Jar包,然后借助Maven工具对其管理,保证这些Web资源版本唯一性,升级也比较容易。关于webjars资源,有一个专门的网站http://www.webjars.org/,我们可以到这个网站上找到自己需要的资源,在自己的工程中添加入maven依赖,即可直接使用这些资源了。
怎么样
可以在pom文件中引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency>
<dependency> <groupId>org.webjars.npm</groupId> <artifactId>mdui</artifactId> <version>0.4.0</version> </dependency>
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency>
|
有了它们,就能够在Html页面上引入它们,就能够直接使用它们的样式和语法,节省了很多开发所需的依赖,使得我们前端引入更为方便。
1 2 3
| <link rel="stylesheet" th:href="@{/webjars/mdui/dist/css/mdui.css}"> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script th:src="@{/webjars/mdui/dist/js/mdui.js}"></script>
|
使用Thymeleaf语法进行引用,这里是世界引用 /webjars的目录。
WebSocket
现在,就来正式的讲一下,WebSocket的使用吧。
概念
简介
WebSocket是一种与HTTP不同的协议。两者都位于OSI模型的应用层,并且都依赖于传输层的TCP协议。 虽然它们不同,但RFC 6455规定:“WebSocket设计为通过80和443端口工作,以及支持HTTP代理和中介”,从而使其与HTTP协议兼容。 为了实现兼容性,WebSocket握手使用HTTP Upgrade头从HTTP协议更改为WebSocket协议。
WebSocket协议支持Web浏览器(或其他客户端应用程序)与Web服务器之间的交互,具有较低的开销,便于实现客户端与服务器的实时数据传输。 服务器可以通过标准化的方式来实现,而无需客户端首先请求内容,并允许消息在保持连接打开的同时来回传递。通过这种方式,可以在客户端和服务器之间进行双向持续对话。 通信通过TCP端口80或443完成,这在防火墙阻止非Web网络连接的环境下是有益的。另外,Comet之类的技术以非标准化的方式实现了类似的双向通信。
大多数浏览器都支持该协议,包括Google Chrome、Firefox、Safari、Microsoft Edge、Internet Explorer和Opera。
与HTTP不同,WebSocket提供全双工通信。此外,WebSocket还可以在TCP之上启用消息流。TCP单独处理字节流,没有固有的消息概念。 在WebSocket之前,使用Comet可以实现全双工通信。但是Comet存在TCP握手和HTTP头的开销,因此对于小消息来说效率很低。WebSocket协议旨在解决这些问题。
WebSocket协议规范将ws
(WebSocket)和wss
(WebSocket Secure)定义为两个新的统一资源标识符(URI)方案,分别对应明文和加密连接。除了方案名称和片段ID(不支持#
)之外,其余的URI组件都被定义为此URI的通用语法。
使用浏览器开发人员工具,开发人员可以检查WebSocket握手以及WebSocket框架
历史
WebSocket最初在HTML5规范中被引用为TCPConnection,作为基于TCP的套接字API的占位符。2008年6月,Michael Carter进行了一系列讨论,最终形成了称为WebSocket的协议。
“WebSocket”这个名字是Ian Hickson和Michael Carter之后在 #whatwg IRC聊天室创造的,随后由Ian Hickson撰写并列入HTML5规范,并在Michael Carter的Cometdaily博客上宣布。 2009年12月,Google Chrome 4是第一个提供标准支持的浏览器,默认情况下启用了WebSocket。协议的开发随后于2010年2月从W3C和WHATWG小组转移到IETF,并在Ian Hickson的指导下进行了两次修订。
该协议被多个浏览器默认支持并启用后,RFC于2011年12月在Ian Fette下完成。
背景
早期,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会消耗很多的带宽资源。
比较新的轮询技术是Comet。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在Comet中普遍采用的HTTP长连接也会消耗服务器资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
Websocket使用ws
或wss
的统一资源标志符,类似于HTTPS。其中wss
表示使用了TLS的Websocket。如:
1 2
| ws://example.com/wsapi wss://secure.example.com/wsapi
|
Websocket与HTTP和HTTPS使用相同的TCP端口,可以绕过大多数防火墙的限制。默认情况下,Websocket协议使用80端口;运行在TLS之上时,默认使用443端口。
优点
较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
握手协议
WebSocket 是独立的、创建在 TCP 上的协议。
Websocket 通过 HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
Spring WebSocket 解析
依赖引入
SpringBoot有自带的WebSocket API,我们在使用WebSocket的时候,可以直接引入这个依赖,也可以手动引入:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
|
创建服务器端点
开启WebSocket服务端的自动注册。
在对WebSocket的使用中,可以先通过Spring创建Java配置文件。在这个文件中,先新建ServerEndpointExporter
1 2 3 4 5 6 7 8
| @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); } }
|
有了这个Bean,就可以使用@ServerEndpoint定义一个端点服务类。在这个站点服务类中,还可以定义WebSocket的打开,关闭,错误和发送消息方法。
ServerEndpointExporter 是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig配置类和@ServerEndpoint注解实例。使用规则也很简单:1.如果使用默认的嵌入式容器 比如Tomcat 则必须手工在上下文提供ServerEndpointExporter。2. 如果使用外部容器部署war包,则不要提供提供ServerEndpointExporter,因为此时SpringBoot默认将扫描服务端的行为交给外部容器处理。
ServerEndpoint
如下:
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
| @ServerEndpoint("/ws") @Service public class WebSocketServiceImpl { private static int onlineCount = 0; private static CopyOnWriteArraySet<WebSocketServiceImpl> webSocketSet = new CopyOnWriteArraySet<>(); private Session session;
@OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); addOnlineCount(); System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); try { sendMessage("有新的连接加入了!!"); } catch (IOException e) { System.out.println("IO异常"); } }
@OnClose public void onClose() { webSocketSet.remove(this); subOnlineCount(); System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); }
@OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message);
for (WebSocketServiceImpl item : webSocketSet) { try {
item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } }
@OnError public void onError(Session session, Throwable error) { System.out.println("发生错误"); error.printStackTrace(); }
private void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } private static synchronized int getOnlineCount() { return onlineCount; }
private static synchronized void addOnlineCount() { WebSocketServiceImpl.onlineCount++; }
private static synchronized void subOnlineCount() { WebSocketServiceImpl.onlineCount--; } }
|
这里是通过注解@OnOpen、@OnMessage、@OnClose、@OnError 来声明回调函数。
回调函数将由JavaScript处理
回调函数将会被前端的JavaScript所使用,如下:
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
| var websocket = null;
if ('WebSocket' in window) { websocket = new WebSocket("ws://localhost:8888/ws"); } else { alert('Not support websocket') }
websocket.onerror = function() { appendMessage("error"); };
websocket.onopen = function(event) { appendMessage("open"); }
websocket.onmessage = function(event) { appendMessage(event.data); }
websocket.onclose = function() { appendMessage("close"); }
window.onbeforeunload = function() { websocket.close(); }
function appendMessage(message) { var context = $("#context").html() +"<br/>" + message; $("#context").html(context); }
function closeWebSocket() { websocket.close(); }
function sendMessage() { var message = $("#message").val(); websocket.send(message); }
|
注意看清楚大小写,这里的 webSocket是利用了在后端所声明的WebSocket对象。
1
| var webSocket = new WebSocket(url);
|
而 webSocket这个JS实例将能够调用回调函数,就如onmessage而言:
1 2 3
| websocket.onmessage = function(event) { appendMessage(event.data); }
|
能通过 (event),来获取到后端传进来的值。
那么值是从哪里被传进去的呢?
请看HTML页面:
1 2 3 4 5 6 7 8
| <body> 测试一下WebSocket站点吧 <br /> <input id="message" type="text" /> <button onclick="sendMessage()">发送消息</button> <button onclick="closeWebSocket()">关闭WebSocket连接</button> <div id="context"></div> </body>
|
这里所只使用的 onclick=”sendMessage()” 正是在调用JS函数,而这个JS函数就是上面所提及的:
1 2 3 4 5
| function sendMessage() { var message = $("#message").val(); websocket.send(message); }
|
它会将文本框中的message的值,通过 send(message),发送到后端处理,由后端标注的@OnMessage函数处理完后,再被JS函数 websocket.onmessage = function(event) 所接收。
至于如何展示在页面上,则是由:
1 2 3 4 5
| function appendMessage(message) { var context = $("#context").html() +"<br/>" + message; $("#context").html(context); }
|
所决定的了。
当然,根据不同的情况,JS的代码可以被自行设计,所以这些代码仅供参考。我们必须记牢的,就是注解的含义:
事件类型 |
WebSocket回调函数 |
事件描述 |
open |
webSocket.onopen |
当打开连接后触发 |
message |
webSocket.onmessage |
当客户端接收服务端数据时触发 |
error |
webSocket.onerror |
当通信异常时触发 |
close |
webSocket.onclose |
当连接关闭时触发 |
打造一个在线聊天室
明白了上述的用法之后,我们来做一个实战练习吧,那就是做一个聊天室。这么说可能有点复古,好像聊天室这个称呼,是上个世纪的产物,不过也无所谓,开始一段练习吧。
前面的实验中,最为关键的,想必就是回调函数的构成了,于是,我么就开始先写回调函数吧。不过在这之前,还要介绍一下一个JSON转换工具。
基本配置
先导入一些基本配置吧:
pom
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
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency>
<dependency> <groupId>org.webjars.npm</groupId> <artifactId>mdui</artifactId> <version>0.4.0</version> </dependency>
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.49</version> </dependency>
|
pom依赖前面都讲过,在此不多赘述
application.yml
yml文件不需要太多配置
1 2 3 4 5 6
| server: port: 8888
spring: thymeleaf: cache: false
|
configuration
1 2 3 4 5 6 7 8
| @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); } }
|
这是必要的配置,前面也讲过。
回调函数的编写
前面有引入JSON依赖,我们就使用JSON,来完善我们的信息传输,所以,先编写一个信息类吧。
Message
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
|
@Data public class Message {
public static final String ENTER = "ENTER"; public static final String SPEAK = "SPEAK"; public static final String QUIT = "QUIT";
private String type;
private String username;
private String msg;
private int onlineCount;
public static String jsonStr(String type, String username, String msg, int onlineTotal) { return JSON.toJSONString(new Message(type, username, msg, onlineTotal)); }
public Message(String type, String username, String msg, int onlineCount) { this.type = type; this.username = username; this.msg = msg; this.onlineCount = onlineCount; } }
|
注意,这里的jsonStr方法是一个静态方法,而且,这个静态方法返回的是一个自身类的一个构造方法。也就是说,将使用这个静态方法去代替构造方法。
这里的JSON是一个alibaba提供的类。在pom文件中导入过。
服务端
在服务端完成回调函数的编写,这里用到了Message类
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
| @Component @ServerEndpoint("/chat") public class WebSocketChatServer {
private static Map<String, Session> onlineSessions = new ConcurrentHashMap<>();
@OnOpen public void onOpen(Session session) { onlineSessions.put(session.getId(), session); sendMessageToAll(Message.jsonStr(Message.ENTER, "", "", onlineSessions.size())); }
@OnMessage public void onMessage(Session session, String jsonStr) { Message message = JSON.parseObject(jsonStr, Message.class); sendMessageToAll(Message.jsonStr(Message.SPEAK, message.getUsername(), message.getMsg(), onlineSessions.size())); }
@OnClose public void onClose(Session session) { onlineSessions.remove(session.getId()); sendMessageToAll(Message.jsonStr(Message.QUIT, "", "下线了!", onlineSessions.size())); }
@OnError public void onError(Session session, Throwable error) { error.printStackTrace(); }
private static void sendMessageToAll(String msg) { onlineSessions.forEach((id, session) -> { try { session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } }); }
}
|
这里,我们逐个逐个去分析各个函数的使用:
- public void onOpen(Session session):每当有客户端连接到这个地址时,就会自动往这个函数传入Session,而我们使用HashMap去存储这个Session实例,当然,使用HashSet也可以,在存储了对象的同时,也需要更新实时在线的信息。
- public void onMessage(Session session, String jsonStr):在前端的JS使用了Send(),发送了信息后,onMessage函数就会自动接收到了一个文本框的String字符串和一个当前Session对象。这里直接把String字符串转化了Message类,再将这个Message转化为JSON的形式,再发送至前端。可能大家不太明白的,就是为什么要加ENTER和SPEAK,这是为了让前端能够判断这个消息到底是什么类型的,以便用于不同的地方。
- public void onClose(Session session):自然是在退出的时候,更新在线人数
- public void onError(Session session, Throwable error):错误处理
- private static void sendMessageToAll(String msg):可以看到这是一个静态方法,也是信息传输的主体,这里使用了只有JDK8才有的Lambda表达式和foreach,去将这个Map里面的Session对象,由这个Session对象去发送信息到前端。
sendMessageToAll函数也有三点是需要注意的:
- Lambda表达式的用法
- Session是WebSocket自己的Session类
- session.getBasicRemote().sendText(msg);可以是一个固定表达,不用深究。
登录的编写
既然是聊天,当然得知道你是谁才能正常会话,于是也需要登录界面,但是这里就不引入Security了,太麻烦,这里直接使用HTML和JS完成登录页面
login页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <head> <title>登陆聊天</title> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <style>省略。。。</style> </head> <body> <div class="logo_box"> <h3>登录聊天</h3> <form> <div class="input_outer"> <span class="u_user"></span> <input id="username" name="username" class="text" type="text" placeholder="任意中文名"> </div> <div class="input_outer"> <span class="us_uer"></span> <input id="password" name="password" class="text" type="password" placeholder="任意密码"> </div> <div class="mb2"> <a class="act-but submit" onclick="login()">登录</a> </div> </form> </div> ......
|
这里引入webjar的JQ库,可以使用一些非常简便的函数和类。
login-JavaScript
这一段的JS较为容易
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script>
function login() { location.href = '/index?username='+$('#username').val(); }
document.onkeydown = function (event) { var e = event || window.event || arguments.callee.caller.arguments[0]; e.keyCode === 13 && login(); }; </script>
|
和HT页面相结合,完成用户名的输入。
控制层
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
| @RestController public class WebController {
@GetMapping("/") public ModelAndView login() { return new ModelAndView("/login"); }
@GetMapping("/index") public ModelAndView index(String username, String password, HttpServletRequest request) throws UnknownHostException { if (StringUtils.isEmpty(username)) { username = "匿名用户"; } ModelAndView mav = new ModelAndView("/chat"); mav.addObject("username", username); mav.addObject("webSocketUrl", "ws://"+ InetAddress.getLocalHost().getHostAddress()+":"+request.getServerPort()+request.getContextPath()+"/chat"); return mav; }
}
|
JS的登录函数,将会被传入到这里,完成页面的跳转,较为简单。
聊天室的编写
chat页面
我们也来编写聊天室的页面吧,这个页面开始引用webjar的资源。
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
| <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>WebSocket简单聊天室</title> <meta charset="utf-8" name="viewport" content="width=device-width"> <link rel="stylesheet" th:href="@{/webjars/mdui/dist/css/mdui.css}"> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script th:src="@{/webjars/mdui/dist/js/mdui.js}"></script> </head> <body class="mdui-theme-primary-indigo mdui-theme-accent-pink">
<div class="mdui-container"> <div class="mdui-toolbar mdui-color-theme"> <a class="mdui-btn mdui-btn-icon"><i class="mdui-icon material-icons">menu</i></a> <span class="mdui-typo-title">简单聊天室</span> <div class="mdui-toolbar-spacer"></div> <a class="mdui-btn mdui-btn-icon" href="https://www.baidu.com/" target="_blank"><i class="mdui-icon material-icons">search</i></a> <a class="mdui-btn mdui-btn-icon" th:href="@{/}"><i class="mdui-icon material-icons">exit_to_app</i></a> <a class="mdui-btn mdui-btn-icon"><i class="mdui-icon material-icons">more_vert</i></a> </div> </div>
<div> <div class="mdui-container container_text">
<div class="mdui-row"> <div class="mdui-col-xs-12 mdui-col-sm-6"> <div class="mdui-col-xs-12 mdui-col-sm-10"> <div class="mdui-textfield-floating-label" style="margin-top:15px"> <i class="mdui-icon material-icons">欢迎:</i> <i class="mdui-icon" id="username" th:text="${username}"></i> </div> </div> <div class="mdui-col-xs-12 mdui-col-sm-10"> <div class="mdui-textfield mdui-textfield-floating-label"> <i class="mdui-icon material-icons">textsms</i> <label class="mdui-textfield-label">发送内容</label> <input class="mdui-textfield-input" id="msg"/> </div> <div class="mdui-container" style="padding:20px 35px"> <button class="mdui-btn mdui-color-theme-accent mdui-ripple" onclick="sendMsgToServer()">发送 (enter) </button> <button class="mdui-btn mdui-color-theme mdui-ripple" onclick="clearMsg()">清屏 </button> </div> </div> </div>
<div class="mdui-col-xs-6 mdui-col-sm-5" style="padding:10px 0"> <div class="mdui-chip"> <span class="mdui-chip-icon mdui-color-blue"> <i class="mdui-icon material-icons"></i></span> <span class="mdui-chip-title">聊天内容</span> </div>
<div class="mdui-chip"> <span class="mdui-chip-icon mdui-color-blue"> <i class="mdui-icon material-icons">face</i></span> <span class="mdui-chip-title">在线人数</span> <span class="mdui-chip-title chat-num">0</span> </div> <div class="message-container">
</div> </div>
</div> </div> </div>
|
Html页面的编写,比较繁琐,但是没有什么特别复杂的问题,这里在开头引入了 webjar,同时也引入了各种各样的CSS,此时的我们只需要去编写文字和放置class就可以了,省去了CSS文件的编写过程。
chat-JavaScript
接下来到了最为重要的JS的编写了,我们来一步步完成吧。
首先是要完成发送按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var webSocket = new WebSocket( 'ws://localhost:8888/chat');
function sendMsgToServer() { var $message = $('#msg'); if ($message.val()) { webSocket.send(JSON.stringify({username: $('#username').text(), msg: $message.val()})); $message.val(null); }
}
|
首先从获得了id为msg的文本框,将其变成一个值,如果这个值不为空,则将其发送到后端,进行处理。
为了有更好的可视性,我们将WebSocket由函数获取,改为:
1
| var webSocket = getWebSocket();
|
然后在这个getWebSocket函数中完成编写:
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
| function getWebSocket() { var webSocket = new WebSocket( 'ws://localhost:8888/chat'); webSocket.onmessage = function (event) { console.log('WebSocket收到消息:%c' + event.data, 'color:green'); var message = JSON.parse(event.data) || {}; var $messageContainer = $('.message-container'); if (message.type === 'SPEAK') { $messageContainer.append( '<div class="mdui-card" style="margin: 10px 0;">' + '<div class="mdui-card-primary">' + '<div class="mdui-card-content message-content">' + message.username + ":" + message.msg + '</div>' + '</div></div>'); } $('.chat-num').text(message.onlineCount); }); } };
return webSocket; }
|
它看起来复杂,其实也就三步走:
- 首先,先通过var message = JSON.parse(event.data),将传入的数据变为JSON类型。
- 然后,再获取到:$(‘.message-container’)这个class标签。
- 最后,若这个标签不为空,则使用append(),将消息类型和样式加入到其中。
这样就完成了,简单明了。最后测试一下吧:
测试
打开浏览器,输入localhost:8888
并输入任意用户名,点击登录:
再另外打开一个网页,登录:
互相发送消息试试:happy:
源码地址:https://github.com/Antarctica000/SpringBoot/tree/master/websocket