• WebSocket的简单认识&SpringBoot整合websocket


    1. 什么是WebSocket?菜鸟对websocket的解释如下

      WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

      WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

      在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

      现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询(我目前接触的线上聊天也确实是基于这种方式实现的)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

      HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯

      浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

      当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

    以下 API 用于创建 WebSocket 对象。

    var Socket = new WebSocket(url, [protocol] );

    WebSocket 属性

    属性描述
    Socket.readyState

    只读属性 readyState 表示连接状态,可以是以下值:

    • 0 - 表示连接尚未建立。

    • 1 - 表示连接已建立,可以进行通信。

    • 2 - 表示连接正在进行关闭。

    • 3 - 表示连接已经关闭或者连接不能打开。

    Socket.bufferedAmount

    只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

    WebSocket 事件:

    事件事件处理程序描述
    open Socket.onopen 连接建立时触发
    message Socket.onmessage 客户端接收服务端数据时触发
    error Socket.onerror 通信发生错误时触发
    close Socket.onclose 连接关闭时触发

    WebSocket 方法:

    方法描述
    Socket.send()

    使用连接发送数据

    Socket.close()

    关闭连接

     

    2. Tomcat中开发简单的WebSocket实例

      在这里实现一个简易的聊天室。

    开发环境:JDK7+Tomcat7.0.91(如果连接URL报404建议切换tomcat为高版本,我在7.0.88测试就连接不到后端)

    最终效果如下:

    代码如下:

    后端:

    package chat;
    
    import java.io.IOException;
    import java.util.HashMap;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.ServerEndpoint;
    
    @ServerEndpoint("/chat")
    public class ChatServer {
    
        private boolean first = true;
    
        private String name;
    
        private Session session;
    
        private static final HashMap<String, ChatServer> clientSet = new HashMap<String, ChatServer>();
    
        public ChatServer() {
            System.out.println("========ChatServer创建============");
        }
    
        /*
         * 客户端连接时触发该方法
         */
        @OnOpen
        public void onOpen(Session session) throws IOException {
            this.session = session;
        }
    
        /*
         * 客户端断开时触发该方法
         */
        @OnClose
        public void onClose() {
            clientSet.remove(name);
    
            String message = String.format("【%s %s】", name, "离开了聊天室!");
            broadcast(message);
        }
    
        /*
         * 客户端收到消息时触发该方法
         */
        @OnMessage
        public void onMessage(String msg) {
            if (first) {
                name = msg;
                clientSet.put(name, this);
                String message = String.format("【%s %s】", name, "加入了聊天室!");
                // 发送消息
                broadcast(message);
                first = false;
            } else {
                broadcast("【" + name + "】" + ":" + msg);
            }
    
        }
    
        // 当客户端通信出现错误时,激发该方法
        @OnError
        public void onError(Throwable t) throws Throwable {
            System.out.println("WebSocket服务端错误 " + t);
        }
    
        public void broadcast(String msg) {
            // 遍历服务器关联的所有客户端
            ChatServer client = null;
            for (String nickname : clientSet.keySet()) {
    
                try {
                    client = (ChatServer) clientSet.get(nickname);
                    synchronized (client) {
                        // 发送消息
                        client.session.getBasicRemote().sendText(msg);
                    }
                } catch (IOException e) {
                    System.out.println("聊天错误,向客户端 " + client + " 发送消息出现错误。");
                    clientSet.remove(name);
                    try {
                        client.session.close();
                    } catch (IOException e1) {
                    }
                    String message = String.format("【%s %s】", client.name, "已经被断开了连接。");
                    broadcast(message);
                }
            }
        }
    
    }

     前端:

    <!DOCTYPE html>
    <html>
    
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>简单聊天室</title>
    <script type="text/javascript">
        var webSocket = null;
        if ("WebSocket" in window) {
            // 创建WebSocket对象
            webSocket = new WebSocket("ws://localhost:86/WebSocket/chat");
        } else {
            alert("您的浏览器不支持 WebSocket!");
        }
    
        
        var sendMsg = function() {
            var inputElement = document.getElementById('msg');
            if (inputElement.value == '')
                alert("输入内容不为空");
            else {
                if (inputElement.value == "quit" || inputElement.value == "exit") {
                    webSocket.close();
                    return;
                }
                
                // 发送消息
                webSocket.send(inputElement.value);
                // 清空单行文本框
                inputElement.value = "";
            }
        }
        var send = function(event) {
            if (event.keyCode == 13) {
                sendMsg();
            }
        };
        
        webSocket.onopen = function() {
            n = prompt('请给自己取一个昵称:');
            if (n != '' && n != null)
                webSocket.send(n);
            else
                //设置游客登录
                webSocket.send("游客" + Math.random() * 100000000000000000);
            
            // 此处可以加一个异步请求name是否使用的请求来判断name是否可用
            
            document.getElementById('msg').onkeydown = send;
            document.getElementById('sendBn').onclick = sendMsg;
        };
        
        // 为onmessage事件绑定监听器,接收消息
        webSocket.onmessage = function(event) {
            var show = document.getElementById('show')
            // 接收、并显示消息
            show.innerHTML += new Date() + "<br/>" + event.data + "<br/>";
            //让聊天框滚动条始终显示新消息
            show.scrollTop = show.scrollHeight;
        };
        
        webSocket.onclose = function() {
            document.getElementById('msg').onkeydown = null;
            document.getElementById('sendBn').onclick = null;
        };
    </script>
    </head>
    <body>
        <div
            style=" 600px; height: 240px; overflow-y: auto; border: 1px solid #333;"
            id="show"></div>
        <input type="text" size="80" id="msg" name="msg" placeholder="输入聊天内容" />
        <input type="button" value="发送" id="sendBn" name="sendBn" />
    </body>
    </html>

    注意:

      ws://localhost:86/WebSocket/chat 这个地址 是  协议://IP:Port/项目名称/websocket地址

    第一次建立Socket连接的时候请求头和响应头如下:

    •  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 中使用。

    补充:上面是每个连接都会创建1个ChatServer对象,多例模式的对象。Session与HttpSession不一样。

     3. SpringBoot中开启WebSocket

      springboor整合websocket也非常简单。如下:

    1.  pom.xml增加如下依赖

            <!-- websocket的包 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-websocket</artifactId>
            </dependency>

    2.代码如下

    配置类:

    package cn.qlq.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    @Configuration
    public class WebSocketConfig {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    
    }

    ChatServer.java

    package cn.qlq.websocket;
    
    import java.io.IOException;
    import java.util.Hashtable;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.ServerEndpoint;
    
    import org.springframework.stereotype.Component;
    
    @ServerEndpoint("/chat")
    @Component
    public class ChatServer {
    
        private boolean first = true;
    
        private String name;
    
        private Session session;
    
        private static final Hashtable<String, ChatServer> clientSet = new Hashtable<String, ChatServer>();
    
        public ChatServer() {
            System.out.println("========ChatServer创建============");
        }
    
        /*
         * 客户端连接时触发该方法
         */
        @OnOpen
        public void onOpen(Session session) throws IOException {
            this.session = session;
        }
    
        /*
         * 客户端断开时触发该方法
         */
        @OnClose
        public void onClose() {
            clientSet.remove(name);
    
            String message = String.format("【%s %s】", name, "离开了聊天室!");
            broadcast(message);
        }
    
        /*
         * 客户端收到消息时触发该方法
         */
        @OnMessage
        public void onMessage(String msg) {
            if (first) {
                name = msg;
                clientSet.put(name, this);
                String message = String.format("【%s %s】", name, "加入了聊天室!");
                // 发送消息
                broadcast(message);
                first = false;
            } else {
                broadcast("【" + name + "】" + ":" + msg);
            }
    
        }
    
        // 当客户端通信出现错误时,激发该方法
        @OnError
        public void onError(Throwable t) throws Throwable {
            System.out.println("WebSocket服务端错误 " + t);
        }
    
        /**
         * 将此方法设计为静态方法用于服务器主动调用该方法广播消息
         * 
         * @param msg
         */
        public static void broadcast(String msg) {
            // 遍历服务器关联的所有客户端
            ChatServer client = null;
            for (String nickname : clientSet.keySet()) {
    
                try {
                    client = (ChatServer) clientSet.get(nickname);
                    synchronized (client) {
                        // 发送消息
                        client.session.getBasicRemote().sendText(msg);
                    }
                } catch (IOException e) {
                    System.out.println("聊天错误,向客户端 " + client + " 发送消息出现错误。");
                    clientSet.remove(nickname);
                    try {
                        client.session.close();
                    } catch (IOException e1) {
                    }
                    String message = String.format("【%s %s】", client.name, "已经被断开了连接。");
                    broadcast(message);
                }
            }
        }
    
    }

    Controller层代码:

    package cn.qlq.controller.websocket;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import cn.qlq.websocket.ChatServer;
    
    @RequestMapping("/chat")
    @Controller
    public class ChatController {
    
        /**
         * 跳转到聊天页面
         * 
         * @return
         */
        @RequestMapping("/chatRoom")
        public String chatRoom() {
            return "websocket/chatRoom";
        }
    
        /**
         * http请求发送消息
         * 
         * @param msg
         * @return
         */
        @RequestMapping("/sendMsg")
        public @ResponseBody String sendMsg(String msg) {
            ChatServer.broadcast(msg);
            return "success";
        }
    
    }

    前端页面:

    <!DOCTYPE html>
    <html>
    
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>简单聊天室</title>
    <script type="text/javascript">
        var webSocket = null;
        if ("WebSocket" in window) {
            // 创建WebSocket对象
            webSocket = new WebSocket("ws://192.168.43.137:8088/chat");
        } else {
            alert("您的浏览器不支持 WebSocket!");
        }
    
        
        var sendMsg = function() {
            var inputElement = document.getElementById('msg');
            if (inputElement.value == '')
                alert("输入内容不为空");
            else {
                if (inputElement.value == "quit" || inputElement.value == "exit") {
                    webSocket.close();
                    return;
                }
                
                // 发送消息
                webSocket.send(inputElement.value);
                // 清空单行文本框
                inputElement.value = "";
            }
        }
        var send = function(event) {
            if (event.keyCode == 13) {
                sendMsg();
            }
        };
        
        webSocket.onopen = function() {
            n = prompt('请给自己取一个昵称:');
            if (n != '')
                webSocket.send(n);
            else
                //设置游客登录
                webSocket.send("游客" + Math.random() * 100000000000000000);
            
            // 此处可以加一个异步请求name是否使用的请求来判断name是否可用
            
            document.getElementById('msg').onkeydown = send;
            document.getElementById('sendBn').onclick = sendMsg;
        };
        
        // 为onmessage事件绑定监听器,接收消息
        webSocket.onmessage = function(event) {
            var show = document.getElementById('show')
            // 接收、并显示消息
            show.innerHTML += new Date() + "<br/>" + event.data + "<br/>";
            //让聊天框滚动条始终显示新消息
            show.scrollTop = show.scrollHeight;
        };
        
        webSocket.onclose = function() {
            document.getElementById('msg').onkeydown = null;
            document.getElementById('sendBn').onclick = null;
        };
    </script>
    </head>
    <body>
        <div style=" 600px; height: 240px; overflow-y: auto; border: 1px solid #333;" id="show"></div>
        <input type="text" size="80" id="msg" name="msg" placeholder="输入聊天内容" />
        <input type="button" value="发送" id="sendBn" name="sendBn" />
    </body>
    </html>

    2.测试

    http发送消息:

      经测试,上面ChatServer也是多例模式,每次请求都会创建ChatServer实例对象。

    Git地址:https://github.com/qiao-zhi/springboot-ssm

  • 相关阅读:
    Ubuntu开机等待5分钟的取消方法
    329. 矩阵中的最长递增路径
    关于c语言中NULL的数值是否可以被修改
    #pragam在c++(visual studio 2019)编译器中的使用
    当cpu占有率过高时-sleep(0)的妙用
    inline解析
    一、【pytest实战--Web测试】搭建环境
    用openssl aes256 api实现文件加解密-带例程,兼容openssl enc -aes-256-cbc命令
    kali openvas安装
    C++关于变量初始化的琐记
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/11837310.html
Copyright © 2020-2023  润新知