使用webSocket实现及时通信

本方案是为了解决前后台及时通信设计的,例如后台代码触发一个事件,可以及时的传递给前台,也就是消息的推送功能.
关于消息的推送,方案1是使用定时任务,Cron表达式设置每分钟处理一下后台逻辑进行事件的判断.方案2是使用webSocket建立消息通信通道,挂起一个线程进行时间的判断和消息推送.虽然都能实现消息推送的功能,但是方案二明显效率更高,对服务器造成的压力相对于方案1来说也更小,这里就简单介绍下使用第二种方案进行消息的及时推送实现方法.
首先说下使用webSocket进行访问是路径格式:ws:// path + “wsMy?jspCode=” + jspCode
以WS开头,后面可以追加参数,上文路径是中的wsMy是为了接下来的过滤器过滤,防止恶意访问.
首先添加webSocket依赖:

<!--spring-websocket-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-websocket</artifactId>
          <version>4.0.1.RELEASE</version>
      </dependency>

接下来就是前台页面上的触发访问webSocket:

function getUserId() {
    $.ajax({
        type : "post",
        url : "${ctx}/memo/getCurrentUserId",
        beforeSend: function () {
            //加载中
            waitload();
        },
        success : function(data) {
            currentUserId = data;
            closewait();
            //动态获取当前系统服务器IP地址
            //若执行成功的话,则隐藏进度条提示
            //${ctx}为定义的项目名称,此处不展示,因地制宜
            var path = addrAndPort+"${ctx}/"
            console.log("当前系统的IP及端口号为:"+path);
            var userId = currentUserId;
            if(userId==-1){
                window.location.href="${ctx}/memo/testWebSocket";
            }
            var jspCode = userId;
            var websocket;
            if ('WebSocket' in window) {
                //alert("webSocket"+"ws://" + path + "wsMy?jspCode=" + jspCode);
                <shiro:hasPermission name="user_add_custommenu">
                websocket = new WebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
                </shiro:hasPermission>
            } else if ('MozWebSocket' in window) {
                websocket = new MozWebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
            } else {
                websocket = new SockJS("http://" + path + "wsMy/sockjs?jspCode=" + jspCode);
            }
            websocket.onopen = function(event) {
                console.log("WebSocket:已连接");
                console.log(event);
            };
            websocket.onmessage = function(event) {


                if (event.data =="木有消息啊"){
                    //alert("木有消息啊,测试webSocket是否死亡")
                }else {
                    alert("您有新的提醒,请进入系统首页进行查看")
                    window.flushParent();
                    var  aaa   = event.data.split("*");
                    var id = aaa[0];
                    var content = aaa[1];
                    window.layer.alert(content, {
                         skin: 'layui-layer-molv' //样式类名  自定义样式
                         ,closeBtn: 1    // 是否显示关闭按钮
                         ,anim: 1 //动画类型
                         ,btn: ['取消提醒','确定'] //按钮
                         ,icon: 6    // icon
                        ,yes:function(){
                            $.ajax({
                                type : "post",
                                data :{
                                    id :id
                                },
                                url : "${ctx}/memo/cancleNotepadByMap",
                                success : function(data) {
                                    window.location.reload();
                                    //若执行成功的话,则隐藏进度条提示
                                    if (data != null && data != 'undefined'
                                        && data == 1) {
                                        layer.msg('该提醒取消成功!', {icon: 6,time:1000});
                                        parent.flushParent();
                                        layer_close();
                                        window.location.reload();
                                    }else if (data == -1) {
                                        layer.msg('记事本名称已经存在!', {icon: 5,time:1000});
                                    }else if (data == 0) {
                                        layer.msg('很抱歉!添加失败!', {icon: 5,time:1000});
                                    }else{
                                        layer.msg('系统异常!请与系统管理员联系!', {icon: 5,time:1000});
                                    }
                                }
                            });
                        }
                        ,btn2:function(){
                                 layer.msg('按钮2')
                            }});

                }
            };
            websocket.onerror = function(event) {
                console.log("WebSocket:发生错误 ");
                console.log(event);
            };
            websocket.onclose = function(event) {
                console.log("WebSocket:已关闭");
                console.log(event);
            }
        }
    })
}

因为本案例中访问的是当前服务器的路径,所以要对当前服务器的IP以及端口号进行获取,然后赋值给webSocket的访问路径:

/*获取当前用户id*/
var addrAndPort = "";
function getAddrAndport() {
    $.ajax({
        type : "get",
        url : "${ctx}/getAddrAndport",
        beforeSend: function () {
            //加载中
            waitload();
        },
        success : function(data) {
            addrAndPort = data;
        }
    })
}

后台获取IP以及端口号返回到前台:
@RequestMapping(value = "/getAddrAndport", method = { RequestMethod.GET,
            RequestMethod.POST })
    @ResponseBody
    public Object getAddrAndport(HttpServletRequest request,
                                HttpServletResponse response) {
        String addrAndPort = "";
        addrAndPort = request.getLocalAddr()+":"+request.getLocalPort();
        return addrAndPort;
    }
之前查找资料,获取当前系统的IP以及端口号的方法,杂乱,还是直接从HttpServletRequest 中直接获取最为直接和准确.

当前台页面触发访问的时候,后台服务器接收到访问请求并且进行处理,这时候我把后台关于webSocket的请求分为三个文件:
1:HandShake.java

package com.yyx.webSocket;

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.HandshakeInterceptor;

import java.util.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
 * Created by zhangrui on 2018/1/22.
 */
public class HandShake implements HandshakeInterceptor{
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        // TODO Auto-generated method stub
        String jspCode = ((ServletServerHttpRequest) request).getServletRequest().getParameter("jspCode");
        // 标记用户
       // String userId = (String) ((ServletServerHttpRequest) request).getServletRequest().getSession().getAttribute("userId");
        if(jspCode!=null){
            attributes.put("jspCode", jspCode);
        }else{
            return false;
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        // TODO Auto-generated method stub
    }
}

2.MyWebSocketConfig.java

package com.yyx.webSocket;

import org.springframework.stereotype.Component;
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 javax.annotation.Resource;

/**
 * Created by zhangrui on 2018/1/22.
 */
@Component
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    @Resource
    MyWebSocketHandler handler;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // TODO Auto-generated method stub
        registry.addHandler(handler, "/wsMy");
        registry.addHandler(handler, "/wsMy/sockjs").withSockJS();
    }
}

3:MyWebSockethandler.java访问成功后的处理逻辑,本文中是使用线程写的,因人而异.

package com.yyx.webSocket;

import com.google.gson.GsonBuilder;
import com.yyx.zbhr.controller.MemoController;
import com.yyx.zbhr.entity.MemoEntity;
import com.yyx.zbhr.entity.NotepadEntity;
import com.yyx.zbhr.entity.User;
import com.yyx.zbhr.service.MMemoService;
import com.yyx.zbhr.service.MPersonInfoService;
import com.yyx.zbhr.utils.UserUtil;
import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.*;

/**
 * Created by zhangrui on 2018/1/22.
 */
@Component
public class MyWebSocketHandler implements WebSocketHandler {
    public static final Map<String, WebSocketSession> userSocketSessionMap;
    private  static Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);
    private static Calendar fromCal=Calendar.getInstance();
    @Autowired(required = false)
    private MPersonInfoService mPersonInfoService;

    @Autowired(required = false)
    private MMemoService mMemoService;
    static {
        userSocketSessionMap = new HashMap<String, WebSocketSession>();
    }
    public static WebSocketSession session1 ;
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // TODO Auto-generated method stub
        //HttpSession httpSession = (HttpSession) session;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        //调用接口获取ID
        //Long userId = mMemoService.getCurrentUserId();
        Map<String,Object> map = new HashedMap();
        /*//wsMy?jspCode=23*/
        String uri = session.getUri().toString();
        String[] split = uri.split("=");
        int currentUserId = Integer.parseInt(split[1]);
        // TODO 自动shiro获取id失败
        map.put("userId",currentUserId);
        String jspCode = (String) session.getHandshakeAttributes().get("jspCode");
        if (userSocketSessionMap.get(jspCode) == null) {
            userSocketSessionMap.put(jspCode, session);
        }
            new Thread(new Runnable() {
                public void run() {
                    while(true){
                        Date date = new Date();
                        String format = simpleDateFormat.format(date);
                        List<NotepadEntity> notepadEntities = mMemoService.selectNotepadForNotice(map);
                        for (int i = 0; i < notepadEntities.size(); i++) {
                            String format1 = simpleDateFormat.format(notepadEntities.get(i).getStarTime());
                            String format2 = simpleDateFormat.format(notepadEntities.get(i).getEndTime());
                            StringBuffer message = new StringBuffer();
                            int flag1 = date.compareTo(notepadEntities.get(i).getEndTime());
                            int flag2 = date.compareTo(notepadEntities.get(i).getStarTime());
                            if (flag1 < 0 && flag2 >= 0) {
                                try {
                                    String noteType = "";
                                    if (notepadEntities.get(i).getNoteType() == 1){
                                        noteType = "工作";
                                    }else if (notepadEntities.get(i).getNoteType() == 2){
                                        noteType = "生活";
                                    }else if (notepadEntities.get(i).getNoteType() == 3){
                                        noteType = "家庭";
                                    }else if (notepadEntities.get(i).getNoteType() == 4){
                                        noteType = "私密";
                                    }else {
                                        noteType = "其他";
                                    }
                                    message.append(notepadEntities.get(i).getId()+"*");
                                    message.append("<b>日程类型:</b></br>"+noteType+"</br>");
                                    message.append("<b>起始时间:</b></br>"+format1+"</br>");
                                    message.append("<b>终止时间:</b></br>"+format2+"</br>");
                                    message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
                                    message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
                                    session.sendMessage(new TextMessage(message,true));
                                    //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" +  notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
                                    //session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }else if(flag1>0){
                                try {
                                    String noteType = "";
                                    if (notepadEntities.get(i).getNoteType() == 1){
                                        noteType = "工作";
                                    }else if (notepadEntities.get(i).getNoteType() == 2){
                                        noteType = "生活";
                                    }else if (notepadEntities.get(i).getNoteType() == 3){
                                        noteType = "家庭";
                                    }else if (notepadEntities.get(i).getNoteType() == 4){
                                        noteType = "私密";
                                    }else {
                                        noteType = "其他";
                                    }
                                    message.append(notepadEntities.get(i).getId()+"*");
                                    message.append("<h3 style=\"color:red\">日程过期提醒</h3>");
                                    message.append("<b>日程类型:</b></br>"+noteType+"</br>");
                                    message.append("<b>起始时间:</b></br>"+format1+"</br>");
                                    message.append("<b>终止时间:</b></br>"+format2+"</br>");
                                    message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
                                    message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
                                    session.sendMessage(new TextMessage(message,true));
                                    //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" +  notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
                                    //session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }else {
                                String noteType = "";
                                if (notepadEntities.get(i).getNoteType() == 1){
                                    noteType = "工作";
                                }else if (notepadEntities.get(i).getNoteType() == 2){
                                    noteType = "生活";
                                }else if (notepadEntities.get(i).getNoteType() == 3){
                                    noteType = "家庭";
                                }else if (notepadEntities.get(i).getNoteType() == 4){
                                    noteType = "私密";
                                }else {
                                    noteType = "其他";
                                }
                                //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型 \":\" " + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题\":\"" +  notepadEntities.get(i).getTitle() + "\";\"内容 \":\"" + notepadEntities.get(i).getContent() + "\"")));
                                //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"number\":\"" + i + "\";\"deptId\":\"" + format + "\";\"事件:\":\"" + "这只是哥哥拿来测试的事件" + i + "\"")));
                                /* message.append("<b>日程类型:</b></br>"+noteType+"</br>");
                                 message.append("<b>起始时间:</b></br>"+format1+"</br>");
                                 message.append("<b>终止时间:</b></br>"+format2+"</br>");
                                 message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
                                 message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
                                 session.sendMessage(new TextMessage(message,true));*/
                                try {
                                    session.sendMessage(new TextMessage("木有消息啊",true));
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                        try {
                            Thread.sleep(1000*60);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }).start();
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        // TODO Auto-generated method stub
        //Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
        //msg.setDate(new Date());
//      sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));

        session.sendMessage(message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // TODO Auto-generated method stub
        if (session.isOpen()) {
            session.close();
        }
        Iterator<Map.Entry<String, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();
        // 移除Socket会话
        while (it.hasNext()) {
            Map.Entry<String, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
                break;
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("Websocket:" + session.getId() + "已经关闭");
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();
        // 移除Socket会话
        while (it.hasNext()) {
            Entry<String, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                //TODO 对webSocket资源进行回收
                System.gc();
                System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
                break;
            }
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
    public void broadcast(final TextMessage message) throws IOException {
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();

        // 多线程群发
        while (it.hasNext()) {

            final Entry<String, WebSocketSession> entry = it.next();

            if (entry.getValue().isOpen()) {
                new Thread(new Runnable() {

                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }).start();
            }

        }
    }
    public void sendMessageToJsp(final TextMessage message,String type) throws IOException {
        Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
                .entrySet().iterator();

        // 多线程群发
        while (it.hasNext()) {

            final Entry<String, WebSocketSession> entry = it.next();
            if (entry.getValue().isOpen() && entry.getKey().contains(type)) {
                new Thread(new Runnable() {

                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }).start();
            }

        }
    }

}

第三个文件是核心,具体的代码是实验代码,有一部分是冗余代码,粘贴出来仅供参考,值得一提的是,在webSocket进行sendMessage的时候,可选的message格式有多种,当时博主想传回类json,但是传到前台发现处理比较麻烦,所以就传回了StringBuffer类型的,至于StringBuffer和StringBuidder之间的选择,看情况决定,这里不多言.再展示下消息传递后的效果图:
这里写图片描述

对返回到前台的消息处理使用的layer进行弹出,比较好用,样式多.可以直接从layer官网查询样式进行使用,网址….百度.
最后一点,本文中使用的web容器及版本是Tomcat8.0.48,虽然有人说在Tomcat8.0之前,不支持webSocket消息通讯,说的是那时候webSocket协议未制定,但是….博主试了下使用Tomcat7.9可行,所以这里就不再多说废话啦,博主也只是个门外汉,更多更细的webSocket知识点还是留待大家自行发掘吧!
最后一点,请不要诟病博主的访问路径:getAddrAndport,虽然不规范,但是….这破习惯一下子难改啊…逐步改正.
完.

猜你喜欢

转载自blog.csdn.net/m0_37190495/article/details/79396781