zoukankan      html  css  js  c++  java
  • websocket

    一、介绍

    维基百科简绍(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>
    View Code

    github:https://github.com/baizhuang/springboot-websocket

  • 相关阅读:
    浅谈单调栈、单调队列
    关于博客迁移
    高二四班抽号
    数据结构
    图论
    洛谷 P3817 小A的糖果
    洛谷 P4016 负载平衡问题
    洛谷 P4306 [JSOI2010]连通数
    洛谷 P4822 [BJWC2012]冻结
    洛谷 P4568 [JLOI2011]飞行路线
  • 原文地址:https://www.cnblogs.com/bytecodebuffer/p/13687101.html
Copyright © 2011-2022 走看看