客户端分为以下5步(如无销毁操作,第5步可省略)
1、检测当前浏览器是否支持WebSocket
2、创建WebSocket对象(new WebSocket)
3、与服务端创建链接(onopen)
4、接收服务端返回的数据(onmessage)
5、当客户端或者服务端断开链接时,执行需要的操作(onclose)
function WebSocketTest() { if ("WebSocket" in window) { console.log("您的浏览器支持 WebSocket!"); // 打开一个 web socket var ws = new WebSocket("ws://192.168.2.196:8083/webSocket/webSocketTest/2/测试2"); ws.onopen = function() { // Web Socket 已连接上,使用 send() 方法发送数据 ws.send("测试WebSocket"); console.log("数据发送中..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; console.log("数据已接收..."); }; ws.onclose = function() { // 关闭 websocket console.log("连接已关闭..."); }; } else { // 浏览器不支持 WebSocket console.log("您的浏览器不支持 WebSocket!"); } }
服务端首先需要引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.0.6.RELEASE</version> </dependency>
写WebSocketConfig注入ServerEndpointExporter如下
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { /** * 使用spring boot时,使用的是spring-boot的内置容器, * 如果要使用WebSocket,需要注入ServerEndpointExporter */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
再写webSocket入口如下
import java.io.IOException; import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import org.springframework.stereotype.Component; @ServerEndpoint(value="/webSocketTest/{userId}/{message}") @Component public class WebSocket { private static int onlineCount=0; private static CopyOnWriteArraySet<WebSocket> webSocketSet=new CopyOnWriteArraySet<WebSocket>(); private static HashMap<String,Session> sessionHashMap = new HashMap<>(); private Session session; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(@PathParam("userId") String userId,@PathParam("message") String message, Session session) { if(userId != null){ //从url中获取userId sessionHashMap.put(userId,session); } this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); try { sendMessage(message,session); } catch (IOException e) { System.out.println("IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 sessionHashMap.remove(this.session.getPathParameters().get("userId")); System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message,Session session) { String[] split = message.split("-"); System.out.println("来自客户端的消息:" + split[0]); try{ Session session1 = sessionHashMap.get(split[1]); //如果获取不到session表明此客户端已下线,则获取到session为null,则群发 if(session1 == null){ //点对面 for (WebSocket item : webSocketSet) { try { item.sendMessage(message,item.session); } catch (IOException e) { e.printStackTrace(); } } }else{ //点对点 sendMessage(split[0],session1); } }catch(IOException e){ } } //消息推送 public void sendMessage(String message,Session session) throws IOException { session.getBasicRemote().sendText(message); } /** * 发生错误时调用 */ @OnError public void onError(Session session, Throwable error) { System.out.println("发生错误"); error.printStackTrace(); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocket.onlineCount++; } public static synchronized void subOnlineCount() { WebSocket.onlineCount--; } }
实现后端给前端群推消息
import java.io.IOException; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DemoTest { @RequestMapping("/test/{str}") public String back(@PathVariable("str") String str) throws IOException { new WebSocket().onMessage(str,null);//str=测试1-1 return null; } }
WebSocket对象属性
Socket.readyState
只读属性 readyState 表示连接状态,可以是以下值:
-
0 - 表示连接尚未建立。
-
1 - 表示连接已建立,可以进行通信。
-
2 - 表示连接正在进行关闭。
-
3 - 表示连接已经关闭或者连接不能打开
WebSocket 协议本质上是一个基于 TCP 的协议。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
Websocket 使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,Websocket 协议使用 80 端口;运行在 TLS 之上时,默认使用 443 端口。
一个典型的Websocket握手请求如下:
客户端请求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
服务器回应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
- Connection 必须设置 Upgrade,表示客户端希望连接升级。
- Upgrade 字段必须设置 Websocket,表示希望升级到 Websocket 协议。
- Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 BASE-64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为 Websocket 协议。
- Sec-WebSocket-Version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均应当弃用。
- Origin 字段是可选的,通常用来表示在浏览器中发起此 Websocket 连接所在的页面,类似于 Referer。但是,与 Referer 不同的是,Origin 只包含了协议和主机名称。
- 其他一些定义在 HTTP 协议中的字段,如 Cookie 等,也可以在 Websocket 中使用。