一、WebSocket概述:
WebSocket是基于TCP协议的一种网络协议,它实现了浏览器与服务器全双工通信,支持客户端和服务端之间相互发送信息。在有WebSocket之前,如果服务端数据发生了改变,客户端想知道的话,只能采用定时轮询的方式去服务端获取,这种方式很大程度上增大了服务器端的压力,有了WebSocket之后,如果服务端数据发生改变,可以立即通知客户端,客户端就不用轮询去换取,降低了服务器的压力。目前主流的浏览器都已经支持WebSocket协议了。
WebSocket使用ws和wss作资源标志符,它们两个类似于http和https,wss是使用TSL的ws。主要有4个事件:
- onopen 创建连接时触发
- onclose 连接断开时触发
- onmessage 接收到信息时触发
- onerror 通讯异常时触发
最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
1.使用WebSocket原因:
了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
2.WebSocket工作方式:
Web 浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的 HTTP 连接不同,对服务器有重要的影响。
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。
二、WebSocket使用:
1.WebSocket客户端:
创建一个WebSocket对象:
var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
a.WebSocket 属性:
以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:
属性 |
描述 |
Socket.readyState |
只读属性readyState表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 |
Socket.bufferedAmount |
只读属性bufferedAmount已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
b.WebSocket 事件:
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 |
事件处理程序 |
描述 |
open |
Socket.onopen |
连接建立时触发 |
message |
Socket.onmessage |
客户端接收服务端数据时触发 |
error |
Socket.onerror |
通信发生错误时触发 |
close |
Socket.onclose |
连接关闭时触发 |
c.WebSocket 方法:
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
方法 |
描述 |
Socket.send() |
使用连接发送数据 |
Socket.close() |
关闭连接 |
2.WebSocket 服务端
SpringBoot整合WebSocket:
SpringBoot对WebSocket也做了支持,需要使用的话引入依赖所需要的包spring-boot-starter-websocket就可以了。利用它可以双向通信的特性来实现一个简单的聊天室功能。
- 用户在浏览器端进入聊天室(创建WebSocket连接);
- 用户端发送消息到服务端(客户端像服务端发信息);
- 服务端将消息转发到客户端(服务端向客户端发信息);
- 用户退出聊天室(断开WebSocket连接)。
<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>
server.port=8090 server.servlet.context-path=/websocket
要使用WebSocket,我们需要在启动来开启对WebSocket的支持功能,使用@EnableWebSocket注解
@SpringBootApplication @EnableWebSocket public class WebSocketApplication { public static void main(String[] args) { SpringApplication.run(WebSocketApplication.class, args); } /** * 初始化Bean,它会自动注册使用了 @ServerEndpoint 注解声明的 WebSocket endpoint * @return */ @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }
聊天时,我们需要知道有哪些用户在线,所以我们创建一个工具类来记录在线用户和实现服务端向客户端发送消息
public class WebSocketUtil { /** * 模拟存储 在线用户 */ public static final Map<String, Session> USERS_ONLINE = new ConcurrentHashMap<>(); /** * 向所有在线用户发送消息(遍历 向每一个用户发送) * @param message */ public static void sendMessageToAllOnlineUser(String message){ USERS_ONLINE.forEach((username, Session) -> sendMessage(Session, message)); } /** * 向指定用户发送消息 * @param session 用户session * @param message 发送消息内容 */ private static void sendMessage(Session session, String message) { if (session == null) { return; } final RemoteEndpoint.Basic basic = session.getBasicRemote(); if (basic == null) { return; } try { basic.sendText(message); } catch (IOException e) { e.printStackTrace(); } } }
服务端类,使用@ServerEndpoint("")说明服务端监听此地址的消息:
@Controller @ServerEndpoint("/chat/{username}") //说明创建websocket的endpoint public class ChatServerEndpoint { /** * 访问聊天室页面 * @return */ @GetMapping("/chatPage") public String chatPage(){ return "chat.html"; } }
WebSocket有4个事件,我们对每个事件做监听,使用对应的注解即可实现监听
WebSocket四个事件:
onopen:
onopen 在连接创建(用户进入聊天室)时触发
@OnOpen public void openSession(@PathParam("username") String username, Session session){ //存储用户 WebSocketUtil.USERS_ONLINE.put(username, session); //向所有在线用户发送用户上线通知消息 String message = "["+username+"]进入聊天室"; System.out.println(message); WebSocketUtil.sendMessageToAllOnlineUser(message); }
onclose:
onclose 在连接断开(用户离开聊天室)时触发
@OnClose public void closeSession(@PathParam("username") String username, Session session){ //删除用户 WebSocketUtil.USERS_ONLINE.remove(username); //向所有在线用户发送用户下线通知消息 String message = "["+username+"]离开了聊天室"; System.out.println(message); WebSocketUtil.sendMessageToAllOnlineUser(message); //下线后关闭session try { session.close(); } catch (IOException e) { e.printStackTrace(); } }
onmessage:
onmessage 在接收到消息时触发
@OnMessage public void onMessage(@PathParam("username") String username, String message){ //向聊天室中的人发送消息 message = "["+username+"]:" + message; System.out.println(message); WebSocketUtil.sendMessageToAllOnlineUser(message); }
onerror:
onerror 在连接发生异常时触发
@OnError public void sessionError(Session session, Throwable throwable){ try { session.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("WebSocket连接发生异常,message:"+throwable.getMessage()); }
聊天室页面:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <script src="jquery-1.9.1.min.js"></script> </head> <body> <table> <tr><td> <label for="messageArea">聊天信息:</label><textarea id="messageArea" cols="50" rows="30"></textarea></br> </td></tr> <tr><td> <label for="username">用 户 名:</label><input type="text" id="username" ></br> </td></tr> <tr><td> <input type="button" id="joinRoomBtn" value="进入聊天室" /> <input type="button" id="leaveRoomBtn" value="离开聊天室" /></br> </td></tr> <tr><td> <label for="sendMessage">输入消息:</label><textarea id="sendMessage" cols="50" rows="3"></textarea></br> </td></tr> <tr><td> <input type="button" id="sendBtn" value="发送消息" /> </td></tr> </table> </body> </html>
<script> $(function(){ var webSocket; var url = 'ws://localhost:8090/websocket/chat/'; //进入聊天室 $('#joinRoomBtn').click(function(){ var username = $('#username').val(); webSocket = new WebSocket(url+username); webSocket.onopen = function (){ console.log('webSocket连接创建。。。'); } webSocket.onclose = function(){ $('#messageArea').append('['+username+']离开了聊天室\n'); } webSocket.onmessage = function(event){ $('#messageArea').append(event.data + '\n'); } webSocket.onerror = function (event) { console.log('webSocket连接异常。。。'); } }); //退出聊天室 $('#leaveRoomBtn').click(function(){ if(webSocket){ //关闭连接 webSocket.close(); } }); //发送消息 $('#sendBtn').click(function(){ var msg = $('#sendMessage').val(); webSocket.send(msg); $('#sendMessage').val(''); }); }); </script>