zoukankan      html  css  js  c++  java
  • WebSocket

    1、WebSocket的使用场景
    社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等需要高实时的场景
     
    2、为什么要用WebSocket
    WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。
    特点:事件驱动、异步,使用ws或者wss协议的客户端socket,能够实现真正意义上的推送功能
    缺点:少部分浏览器不支持,浏览器支持的程度与方式有区别。
     
    判断浏览器是否支持webSocket:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>FORM Text</title>
            <script src="jquery-3.3.1.min.js"></script>
    </head>
    <body>
        <div id="test"></div>
        <script>
            $(function(){
                if(window.WebSocket){
                    alert("Support");
                }else{
                    alert("Unsupport");
                }
            });
    
        </script>
    </body>
    </html>
    3、websocket与http

    1、WebSocket是HTML5出的东西(协议),与Http协议没关系

    2、WebSocket 支持全双工长连接,Http不支持长连接,一个request只能有一个response。而且这个response也是被动的,不能主动发起。

    3、WebSocket 建立连接进行了一次Http握手。

    Http响应模式:                                                                                  WebSocket响应模式:

       

    4、轮询:

    客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。客户端会轮询,有没有新消息。这种方式连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。

    HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,而且后台无法主动向前端发送数据,只能被动等前端http请求。

    eg:放羊人,男孩,狼

    放羊人:狼来了吗?

    男孩:没有

    放羊人:狼来了没?

    男孩:没有

    .....(此过程,狼已经来了)

    放羊人:狼来了没?

    男孩:来了

    5、长轮询

    长轮询是轮询的改进,也是采取轮询模式,不同的是采取阻塞模式(类似一直打电话,没收到就不挂电话) 也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

     放羊人1:狼来了吗?

    男孩:....(等待,一直等狼来)

    .....(此过程,狼已经来了)

    放羊人2:狼来了吗?

    男孩:....(等待,一直等狼来)

    .....(此过程,狼已经来了)

    男孩放羊1:来了

    男孩放羊2:来了

    6、WebSocket

    放羊人1:狼来了吗?

    男孩->放羊人1:没有,狼还在十里外

    放羊人2:狼来了吗?

    男孩->放羊人2:没有,狼还在八里外

    男孩->放羊1:狼在五里外

    男孩->放羊2:狼在五里外

    .....(此过程,狼已经来了)

    男孩->放羊1:来了

    男孩->放羊2:来了

    7、使用Spring实现webSocket

    工程如下:

    package com.example.demo.controller;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.util.Map;
    
    @Controller
    @RequestMapping(value = "/webSocket")
    public class WebSocketController {
        private final Logger log = LoggerFactory.getLogger(getClass());
    
    
        @RequestMapping("/toLogin")
        private String toLoginPage(HttpServletRequest request, Map<String, Object> map) {
            return "login";
        }
        @RequestMapping("/login")
        private String loginPage(HttpServletRequest request, Map<String, Object> map) {
            String userName =  request.getParameter("userName");
    
            HttpSession session = null;
            if(!request.isRequestedSessionIdValid()){
    
                //会话失效重新登录
                session = request.getSession(true);
                //用户名放入Session
                session.setAttribute("SESSION_USERNAME",userName);
            }
    
            return "test_webSocket";
        }
    }
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>WebSocket演示</title>
        <!-- js 引用 -->
        <script th:src="@{~/static/js/jquery_3.1.0_jquery.min.js}"></script>
        <script th:src="@{~/static/js/webSocket-1.0.js}"></script>
    
    </head>
    <body>
    
    <form action="login" method="post">
        请输入:<input type="text" value="" name="userName" placeholder="请输入用户名"/>
                <input type="submit" value="登录">
    </form>
    
    </body>
    
    <script>
    
    </script>
    </html>
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>WebSocket演示</title>
        <!-- js 引用 -->
        <script th:src="@{~/static/js/jquery_3.1.0_jquery.min.js}"></script>
        <script th:src="@{~/static/js/webSocket-1.0.js}"></script>
    
    </head>
    <body>
    请输入:<textarea rows="5" cols="10" id="inputMsg" name="inputMsg"></textarea>
    <input type="button" id="send" value="发送"/>
    <input type="button" id="close" value="关闭"/>
    <!--<button id="s-close">关闭</button>-->
    
    </body>
    
    <script>
    
        var websocket = WebSkt.createNew("localhost:8080/websocket/socketServer.do");
    
        $("#send").on("click",function(){
             if (websocket.readyState == websocket.OPEN) {
                 var msg = document.getElementById("inputMsg").value;
                 websocket.send(msg);
                 alert("发送成功!");
             } else {
                 alert("连接失败!");
             }
        });
    
        $("#close").on('click',function(){
            websocket.close();
        });
    
    </script>
    </html>

    ------

    package com.example.demo.interceptor;
    
    import java.util.Map;
    
    import javax.servlet.http.HttpSession;
    
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    /**
     * 继承HttpSessionHandshakeInterceptor对象。该对象作为页面连接websocket服务的拦截器
     *
     * @author Administrator
     **/
    public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
    
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                       Map<String, Object> attributes) throws Exception {
    
            System.out.println("Before Handshake");
            if (request instanceof ServletServerHttpRequest) {
                ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                HttpSession session = servletRequest.getServletRequest().getSession(false);
                if (session != null) {
                    // 使用userName区分WebSocketHandler,以便定向发送消息
                    String userName = (String) session.getAttribute("SESSION_USERNAME");
                    if (userName == null) {
                        userName = "default-system";
                    }
                    attributes.put("WEBSOCKET_USERNAME", userName);
                }
            }
            return super.beforeHandshake(request, response, wsHandler, attributes);
    
        }
    
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Exception ex) {
            super.afterHandshake(request, response, wsHandler, ex);
        }
    }
    package com.example.demo.handle;
    
    import java.io.IOException;
    import java.util.ArrayList;
    
    import com.example.demo.config.SpringWebSocketConfig;
    import com.example.demo.util.BloomFilter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import javax.annotation.Resource;
    
    /*
     * 继承WebSocketHandler对象 该对象提供了客户端连接,关闭,错误,发送等方法,重写这几个方法即可实现自定义业务逻辑
     *
     * @author Administrator
     */
    public class SpringWebSocketHandler extends TextWebSocketHandler {
    
        //这个会出现性能问题,最好用Map来存储,key用userid
        private static final ArrayList<WebSocketSession> users;                                                        //
    
        private Logger logger = LoggerFactory.getLogger(SpringWebSocketConfig.class);
    
        /**
         * 利用布隆算法,判断Session用户是否在线
         */
        @Resource
        private BloomFilter bloomFilter;
    
        static {
            users = new ArrayList<WebSocketSession>();
        }
    
        public SpringWebSocketHandler() {
        }
    
        /**
         * 连接成功时候,会触发页面上onopen方法
         */
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    
            String sessionId = (String) session.getAttributes().get("HTTP.SESSION.ID");
    
            //这边也可以用Map<String,WebSocketSession> usersMap 的方式判断Session是否存在
            if(!bloomFilter.contains(sessionId)){
                //不在
                bloomFilter.add(sessionId);
                users.add(session);
            }
    
            System.out.println("connect to the websocket success......当前数量:" + users.size());
            // 这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
            // TextMessage returnMessage = new TextMessage("你将收到的离线");
            // session.sendMessage(returnMessage);
        }
    
        /**
         * 关闭连接时触发
         */
        public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
            logger.debug("websocket connection closed......");
            String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
            System.out.println("用户" + username + "已退出!");
            users.remove(session);
            System.out.println("剩余在线用户" + users.size());
        }
    
        /**
         * js调用websocket.send时候,会调用该方法
         */
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            super.handleTextMessage(session, message);
            super.handleTextMessage(session, message);
            System.out.println("收到消息:" + message.getPayload());
    
            //交互
            TextMessage returnMessage = new TextMessage("Server has recieved Your message and will be processed later... ");
            session.sendMessage(returnMessage);
        }
    
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            if (session.isOpen()) {
                session.close();
            }
            logger.debug("websocket connection closed......");
            users.remove(session);
        }
    
        public boolean supportsPartialMessages() {
            return false;
        }
    
        /**
         * 给某个用户发送消息
         *
         * @param userName
         * @param message
         */
        public void sendMessageToUser(String userName, TextMessage message) {
            for (WebSocketSession user : users) {
                if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
                    try {
                        if (user.isOpen()) {
                            user.sendMessage(message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
    
        /*
         *
         * 给所有在线用户发送消息
         *
         * @param message
         */
        public void sendMessageToUsers(TextMessage message) {
            for (WebSocketSession user : users) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    package com.example.demo.config;
    
    import com.example.demo.handle.SpringWebSocketHandler;
    import com.example.demo.interceptor.SpringWebSocketHandlerInterceptor;
    import com.example.demo.util.BloomFilter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    
    /**
     * 建立一个类实现WebSocketConfigurer接口
     *
     * @author Administrator
     *
     */
    
    @Configuration
    //@EnableWebMvc
    @EnableWebSocket
    public class SpringWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    
        private Logger logger = LoggerFactory.getLogger(SpringWebSocketConfig.class);
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    
            logger.info("SpringWebSocketConfig registerWebSocketHandlers ................");
    
            registry.addHandler(webSocketHandler(), "/websocket/socketServer.do").addInterceptors(
                    new SpringWebSocketHandlerInterceptor());
    
            //根据用途,添加其他WebSocket路径监听
            /*registry.addHandler(webSocketHandler(), "/sockjs/socketServer.do")
                    .addInterceptors(new SpringWebSocketHandlerInterceptor()).withSockJS();*/
        }
    
        @Bean
        public TextWebSocketHandler webSocketHandler() {
            return new SpringWebSocketHandler();
        }
    
        @Bean
        public BloomFilter getBloomFilter(){
            return new BloomFilter();
        }
    }

    运行测试:---------------

    相对于Http协议,多了Upgrade: websocket Connection: Upgrade,告诉Apache、Nginx等服务器:注意啦,窝发起的是Websocket协议,快点帮我找到对应的助理处理。

    Sec-WebSocket-Key: FrZGy1EVeL//7yZDEYrM4g==
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,告诉服务器:我要验证尼是不是真的是Websocket助理。
    Sec-WebSocket-Version 是告诉服务器所使用的Websocket Draft(协议版本)

     Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。服务器:知道啦,给你看我的ID CARD来证明行了吧。。

    参考:

    https://www.zhihu.com/question/20215561

    https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

  • 相关阅读:
    【转载】CentOS 6.3(x86_64)下安装Oracle 10g R2 天高地厚
    Oracle查看表空间和删除表空间 天高地厚
    获取android手机的定位信息(转)
    android里pull解析xml文件
    google map 开发去掉图片阴影
    ubuntu12.04配置android开发环境遇到的问题
    google code中下载你的项目源码
    android无法自动生成R文件
    android中ocr解决方案(tesseract)
    自定义Android标题栏TitleBar布局(转)
  • 原文地址:https://www.cnblogs.com/xiaozhuanfeng/p/11059398.html
Copyright © 2011-2022 走看看