同事说他一直用轮询解决数据实时性!来springboot整合websocket原生版|Java 开发实战

这是我参与更文挑战的第2天,活动详情查看: 更文挑战前端

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动连接java

[TOC]nginx

HTTP请求用于咱们开发以及用户之间最为普遍的一种协议,在HTTP中咱们能够简单的经过浏览器获取到咱们须要的内容(页面)。可是他也有他的局限性。今天咱们的主角websocket将为展示他的功能web

HTTP缺点

  • HTTP只能有client发起请求服务端作出响应返回结果。服务端是不能主动向客户端发送信息的。因此有的网站在解决实时性上采用的是页面的定时器功能。简而言之是客户端定时的向服务端发送请求

这样多多少少的形成资源的浪费。spring

  • HTTP是无记忆的。每次请求服务端是没法了解到客户端以前的行为的,可是咱们平时浏览器网站的时候感受浏览器是知道咱们以前作的事情的。这是网站在请求是添加的cookie这些服务端提供的数据。对咱们而言咱们感受是有记忆的。实则否则后端

  • HTTP1.1以后采用了短链接、长链接两种方式。HTTP请求的发送每次也须要三次握手机制。因此每次的链接耗费资源。1.1后才必定时间内HTTP其实采用的是长链接,这样能够减小资源的开销浏览器

  • 上述说道的长链接有人可能有疑问,其实HTTP协议是基于TCP协议开发的。因此天然有长链接的特性。缓存

HTTP websocket区别

  • HTTP由于短链接的特性因此是无记忆的。为了解决这个问题每一个请求都是由General+Request Head+Request Paylaod+Response Headers组成的。其中Heads就是浏览器须要记住的东西,每次传递来传递去的非常耗费性能。安全

  • websocket因为是长链接特性,一次链接就能够一直的双向通讯。从载体来讲websocket关注的更少,只须要通讯当前须要的信息。历史信息双方都是有的。springboot

websocket原理

使用场景

springboot整合websocket

环境准备

  • 在springboot基础上引入websocket的jar。由于咱们已经继承了springboot因此咱们不须要添加版本号
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

复制代码
  • 而后在项目中加入以下的配置,配置websocket须要的bean,交由spring管理
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

复制代码
  • 而后咱们就能够编写websocket的接受和发送等事件了。 我在次基础上封装了一下,先抽象除一个websocket
public abstract class BaseWebSocket extends BaseController{
    /** * 静态变量,用来记录当前在线链接数。应该把它设计成线程安全的。 */
    private int onlineCount = 0;

    /** * concurrent包的线程安全Set,用来存放每一个客户端对应的MyWebSocket对象。 */
    public CopyOnWriteArraySet<BaseWebSocket> webSocketSet = new CopyOnWriteArraySet<BaseWebSocket>();

    /** * 与某个客户端的链接会话,须要经过它来给客户端发送数据 */
    public Session session;

    private Logger log = LoggerFactory.getLogger("BaseWebSocket");


    /** * 链接创建成功调用的方法 * @param session */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) throws IOException {
        this.session = session;
        //加入set中
        webSocketSet.add(this);
        //在线数加1
        addOnlineCount();
        log.debug("有新链接加入!当前在线人数为" + getOnlineCount());
        //发送信息
        MultiMap multiMap = new MultiMap();
        if (null!=session.getQueryString()&&!"".equals(session.getQueryString())) {
            UrlEncoded.decodeTo(session.getQueryString(), multiMap, "UTF-8");
        }
        sendInfo(defaultMessage(multiMap));
    }

    /** * 链接关闭调用的方法 */
    @OnClose
    public void onClose() {
        //从set中删除
        webSocketSet.remove(this);
        //在线数减1
        subOnlineCount();
        log.info("有一链接关闭!当前在线人数为" + getOnlineCount());
    }

    /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 缓存 * @throws IOException */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        this.session = session;
        try {
            Map paraMap = (Map) JSONObject.parse(message);
            handlerMessage(paraMap);
        } catch (JSONException e) {
            MultiMap multiMap = new MultiMap();
            UrlEncoded.decodeTo(message, multiMap, "UTF-8");
            handlerMessage(multiMap);
            //throw new BusinessException("传递消息格式错误(Json)");
        }
    }

    /** * 处理消息接受 * @param paraMap 接受到map类型的参数 */
    public void handlerMessage(Map paraMap) {
        try {
            sendInfo(defaultMessage(paraMap));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Object defaultMessage(Map<String, Object> paraMap) {
        Object obj = new Object();
        try {
            obj = defauleMessage(paraMap);
        } catch (BusinessException e) {
            return formatReturnAppData(e.getMessage());
        }
        return obj;
    }
    /** * 默认的发送数据 * @param paraMap 链接时传递的参数 * @return */
    public abstract Object defauleMessage(Map<String, Object> paraMap);

    public static boolean isJson(String content) {
        try {
            JSONObject.parse(content);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    /** * 发生错误时调用 @OnError **/
    public void onError(Session session, Throwable error) {
        log.error("onMessage方法异常"+error.toString());
        error.printStackTrace();
    }


    /** * 发送消息需注意方法加锁synchronized,避免阻塞报错 * 注意session.getBasicRemote()与session.getAsyncRemote()的区别 * @param message * @throws IOException */
    public synchronized void sendMessage(Object message) throws IOException {
// this.session.getBasicRemote().sendText(message);
        this.session.getAsyncRemote().sendText(JSONObject.toJSONString(message));
    }


    /** * 群发自定义消息 * */
    public void sendInfo(Object message) throws IOException {
        for (BaseWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

    public synchronized int getOnlineCount() {
        return onlineCount;
    }

    public synchronized void addOnlineCount() {
        onlineCount++;
    }

    public synchronized void subOnlineCount() {
        onlineCount--;
    }
}

复制代码
  • 而后咱们新增一个websocket的时候咱们只须要继承一下这个抽象的websocket的类而后重现里面的defauleMessage方法就好了。在类上须要加上以下注解
@ServerEndpoint(value = "/accident/getAccident")
@Component
public class AccidentGetAccident extends BaseWebSocket {

    AccidentController accidentController;


    @Override
    public Object defauleMessage(Map<String, Object> paraMap) {
        accidentController = ContextUtil.getApplicationContext().getBean(AccidentController.class);
        return accidentController.getAccident(paraMap);
    }
}

复制代码

客户端链接

  • 在前端只须要构造WebSocket这个对象,这个对象须要传递一个参数-链接地址
ws = new WebSocket(wsUrl);

复制代码
  • 而后咱们就能够重写ws里面的一些事件了。前端的事件咱们后端也是基本对应相同的事件的。这样先后端就完成了事件的交互!
ws.onclose = function () {
    console.log('连接关闭');
};
ws.onerror = function() {
    console.log('发生异常了');
};
ws.onopen = function () {
    console.log('新建链接');
};
ws.onmessage = function (event) {
    console.log("接收到服务端反馈的信息了");
}

复制代码
  • 可是咱们得考虑一种状况就是,咱们的服务端由于某种因素形成服务宕机,这个时候客户端捕获到onclose事件,此次的链接就会结束,可是服务端可能在短期内抢修好了。这个时候咱们要客户端进行宠幸刷新才会进行重连。websocket正常都是用在大屏的时候,有时认为刷新并非很方便,因此这个时候须要咱们的客户端有重连机制。
var lockReconnect = false;//避免重复链接
    var wsUrl = "ws://127.0.0.1:8088/accident/getAccident?entId=zhonghuaxingzhong";
    var ws;
    var tt;
    function createWebSocket() {
        try {
            ws = new WebSocket(wsUrl);
            init();
        } catch(e) {
            console.log(e+'catch');
            reconnect(wsUrl);
        }
    }
    function init() {
        ws.onclose = function () {
            console.log('连接关闭');
            reconnect(wsUrl);
        };
        ws.onerror = function() {
            console.log('发生异常了');
            reconnect(wsUrl);
        };
        ws.onopen = function () {
            //心跳检测重置
            heartCheck.start();
        };
        ws.onmessage = function (event) {
            setMessageInnerHTML(event.data);
            //拿到任何消息都说明当前链接是正常的
            console.log('接收到消息');
            heartCheck.start();
        }
    }
    function reconnect(url) {
        if(lockReconnect) {
            return;
        };
        lockReconnect = true;
        //没链接上会一直重连,设置延迟避免请求过多
        tt && clearTimeout(tt);
        tt = setTimeout(function () {
            createWebSocket(url);
            lockReconnect = false;
        }, 4000);
    }
    //心跳检测
    var heartCheck = {
        timeout: 3000,
        timeoutObj: null,
        serverTimeoutObj: null,
        start: function(){

        }
    }
    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
    createWebSocket(wsUrl);

复制代码

总结

  • springboot将websocket作了层封装。咱们只须要配置咱们websocket服务端点后就能够与前端进行交互了。可是在nginx进行代理的时候咱们须要特殊注意下。由于websocket和http创建是有区别的。http是无状态的请求响应以后就没有后续了。可是websocket须要一段时间保持链接。还有一点是websocket也不会一直保持链接。在必定时间没有进行交互就会自动断开。这样保证资源不被一直占用

你们好!原创不易!springcloud系列+jvm系列正在创做中。。。。。

相关文章
相关标签/搜索