一、介绍
维基百科简绍(https://zh.wikipedia.org/wiki/WebSocket)
w3c简绍 https://www.runoob.com/html/html5-websocket.html
WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。WebSocket协议在2011年由IETF标准化为RFC 6455,后由RFC 7936补充规范。Web IDL中的WebSocket API由W3C标准化。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
WebSocket 通过 HTTP 端口80 和 443 进行工作,与HTTP 不同的是WebSocket 提供全双工通信,WebSocket 将 ws (WebSocket) 和 wss (WebSocket Secure) 定义为两个新的统一资源标识符,分别对应明文和加密连接。
早期,很多网站实现推送技术所用的都是轮询。即浏览器需要不断的向服务器发出请求,然而Http请求与恢复包含较长的头部,而真正有效的数据只有很小的部分,会消耗很多带宽资源。
websocket 在默认情况下使用80端口,运行在TLS 上时,默认使用443 端口。
二、握手协议
WebSocket 是独立的,创建在 TCP 上的协议
WebSocket 通过 HTTP/1.1 协议的 101 状态进行握手
请求
Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cache-Control: no-cache Connection: Upgrade Host: 127.0.0.1:8080 Origin: http://localhost:8080 Pragma: no-cache Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Key: hI8iVpJEo27FDI+3i+q+Ow== Sec-WebSocket-Version: 13 Upgrade: websocket User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
回复
Connection: upgrade Date: Thu, 17 Sep 2020 11:28:59 GMT Sec-WebSocket-Accept: dm7en9BqxezYWTsXqbCpNi/gTAE= Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15 Upgrade: websocket
Connection 必须设置Upgrade ,标识客户端希望连接升级
Upgrade 字段必须设置WebSocket ,表示希望升级到WebSocket协议
WebSocket 属性:
Sokcet .readyState
- 0 连接尚未建立
- 1 连接已经建立,可以进行通信
- 2 连接正在进行关闭
- 3 连接已经关闭或者不能打开
事件
- open 连接建立时触发
- message 客户端收到服务端数据时触发
- error 通信错误触发
- close 连接关闭时触发
三、spring boot 整合 websocket
添加maven 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
添加配置
@Configuration @EnableWebSocket public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpoint(){ return new ServerEndpointExporter(); } }
服务端监听
@ServerEndpoint(value = "/socketServer/{userId}",encoders = {ServerEncoder.class}) @Component @Slf4j @Data public class WebServerEndpoint { private static ConcurrentHashMap<String, Session> map = new ConcurrentHashMap<>(); private static CopyOnWriteArraySet<WebServerEndpoint> sessionSet = new CopyOnWriteArraySet<>(); private Session session; @OnOpen public void onOpen(@PathParam("userId")String userId, Session session){ if(userId!=null){ map.put(userId,session); } this.session = session; sessionSet.add(this); try { this.sendMessage("连接成功!", session); }catch (Exception e){ System.out.println("ws IO异常"); } } @OnClose public void onClose(Session session){ sessionSet.remove(this); Collection<Session> values = map.values(); values.remove(this); this.sendMessage("连接关闭:",session); } @OnMessage public void OnMessage(String message,Session session){ sendBatch(message); } @OnError public void onError(Throwable error,Session session){ log.error("服务端错误"); this.sendMessage("服务端错误:"+error.getMessage(),session); } public void sendMessage(String msg,Session session){ log.info("服务端发送消息:"+ msg); try { session.getBasicRemote().sendText(msg); }catch (Exception e){ e.printStackTrace(); } } /** * 单独发送消息 */ public String sendOne(String userId,String message){ if(map.containsKey(userId) && map.get(userId)!=null){ sendMessage(message,map.get(userId)); return "success"; }else{ log.info("用户不存在"); return "用户不存在"; } } /** * 群发自定义消息 */ public void sendBatch(String message){ log.info("【sendBatch】:"+message); sessionSet.stream().forEach( item->item.sendMessage(message,item.getSession()) ); } /** * 发送对象 * @param messageVo * @param session */ public void sendObject(MessageVO messageVo,Session session){ try { session.getBasicRemote().sendObject(messageVo); }catch (Exception e){ e.printStackTrace(); } } /** * 发送对象信息 * @param message */ public void sendBatchObject(MessageVO message){ sessionSet.stream().forEach( item->item.sendObject(message,item.getSession()) ); } }
客户端H5
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div style="line-height: 50px;"> <h1 style="color: red;display: inline;">websocket 测试 </h1> <h4 id="userId" style="color: darkviolet;display: inline;"></h4> </div> <div> <button onclick="connectWs()">建立连接</button> <button onclick="closeWs()">断开连接</button> <input id="text" type="text"/> <button onclick="send()">客户端发送</button> <!--<button onclick="location.href='localhost:8080/sendBatch5'">服务端广播:5条</button>--> </div> <h4 style="color: cornflowerblue;">操作信息:</h4> <div style=" 100%;"> <div id= "timeDiv" style=" 10%;float: left;"></div> <div id="msgDiv" style="color:green; 40%;float: left;"></div> </div> <script> var ws = null; /*打开即建立连接*/ if ('WebSocket' in window) { document.getElementById("userId").innerHTML +=getUuid(); var userId = document.getElementById("userId").innerHTML; ws = new WebSocket("ws://127.0.0.1:8080/socketServer/"+userId); } else { alert('Not support websocket') } //格式化时间 function dateFormat(fmt, date) { let ret; const opt = { "Y+": date.getFullYear().toString(), // 年 "m+": (date.getMonth() + 1).toString(), // 月 "d+": date.getDate().toString(), // 日 "H+": date.getHours().toString(), // 时 "M+": date.getMinutes().toString(), // 分 "S+": date.getSeconds().toString() // 秒 }; for (let k in opt) { ret = new RegExp("(" + k + ")").exec(fmt); if (ret) { fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) }; }; return fmt; } //连接发送错误回调方法 ws.onerror = function () { setMessageInnerHTML("error"); }; //收到消息调用 ws.onmessage = function getMsg(event){ setMessageInnerHTML(event.data); } //建立连接调用 ws.onopen = function(event){ setMessageInnerHTML("open"); } //关闭连接调用 ws.onclose = function(){ setMessageInnerHTML("close"); } //监听窗口,窗口关闭,主动关闭连接 window.onbeforeunload = function(){ ws.close(); } //将消息显示在页面 function setMessageInnerHTML(msg) { document.getElementById("timeDiv").innerHTML+=dateFormat("YYYY-mm-dd HH:MM:SS", new Date())+"<br>"; document.getElementById('msgDiv').innerHTML +=msg + '<br/>'; } //关闭连接 function closeWs(){ if(ws==null || ws.readyState!=1){ alert("连接未打开"); return; } setMessageInnerHTML("客户端关闭连接"); document.getElementById("userId").innerHTML =""; ws.close(); } //建立连接 function connectWs(){ if(ws!=null && ws.readyState ==1){ alert("连接已经建立"); return; } document.getElementById("userId").innerHTML =getUuid(); var userId = document.getElementById("userId").innerHTML; ws = new WebSocket("ws://127.0.0.1:8080/socketServer/"+userId); setMessageInnerHTML("客户端建立连接"); ws.onclose = function(){ setMessageInnerHTML("close"); } ws.onopen = function(event){ setMessageInnerHTML("open"); } ws.onmessage = function getMsg(event){ setMessageInnerHTML(event.data); } } //发送消息 function send(){ if(ws==null || ws.readyState!=1){ alert("连接未打开!"); return; } var message = document.getElementById("text").value; var message = document.getElementById("userId").innerHTML+"-"+message; ws.send(message); } //生成唯一随机数 function getUuid() { var s = []; var hexDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" for (var i = 0; i < 10; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1) } s[14] = "4" s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) //s[8] = s[13] = s[18] = s[23] = "-" let uuid = s.join("") return uuid } </script> </body> </html>