SpringMVC使用websocket做消息推送

WebSocket

WebSocket協議支持(在受控環境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠程主機之間進行全雙工通信。用於此的安全模型是Web瀏覽器常用的基於原始的安全模式。 協議包括一個開放的握手以及隨後的TCP層上的消息幀。 該技術的目標是爲基於瀏覽器的、需要和服務器進行雙向通信的(服務器不能依賴於打開多個HTTP連接(例如,使用XMLHttpRequest或<iframe>和長輪詢))應用程序提供一種通信機制。

socket消息推送流程

  1. 後臺創建socket服務;
  2. 用戶登錄後與後臺建立socket連接,默認使用websocket,如果瀏覽器不支持則使用scokjs連接;
  3. 建立連接後,服務端可以向用戶推送信息;

javaweb中,socket的實現方式有多種,這裏使用Spring-webscoket的方式實現。

demo

搭建環境

在SpringMVC的項目基礎上,導入websocket的相關jar包。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.1.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>4.1.9.RELEASE</version>
</dependency>

websocket服務端實現類


@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{

    private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        System.out.println("==========================註冊socket");
        //註冊websocket server實現類,"/webSocketServer"訪問websocket的地址
        registry.addHandler(msgSocketHandle(),
                "/webSocketServer").
                addInterceptors(new WebSocketHandshakeInterceptor());
        //使用socketjs的註冊方法
        registry.addHandler(msgSocketHandle(),
                "/sockjs/webSocketServer").
                addInterceptors(new WebSocketHandshakeInterceptor())
                .withSockJS();
    }

     /**
     *
     * @return 消息發送的Bean
     */
    @Bean(name = "msgSocketHandle")
    public WebSocketHandler msgSocketHandle(){
        return new MsgScoketHandle();
    }
}

這裏使用的config配置的形式註冊bean和配置,所以需要在SpringMVC的配置文件中添加對類的自動掃描

    <mvc:annotation-driven />
    <context:component-scan base-package="com.wqh.websocket"/>

攔截器類

主要是獲取到當前連接的用戶,並把用戶保存到WebSocketSession中

public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
   private static final Logger logger = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);

    /**
     * 握手前
     * @param request
     * @param response
     * @param webSocketHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        logger.info("握手操作");
        if (request instanceof ServletServerHttpRequest){
           ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
           HttpSession session = servletServerHttpRequest.getServletRequest().getSession(false);
           if(session != null){
                //從session中獲取當前用戶
               User user = (User) session.getAttribute("user");
               attributes.put("user",user);
           }
       }

        return true;
    }

    /**
     * 握手後
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @param webSocketHandler
     * @param e
     */
    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

socket處理消息類

@Component
public class MsgScoketHandle implements WebSocketHandler {

    /**已經連接的用戶*/
    private static final ArrayList<WebSocketSession> users;

    static {
        //保存當前連接用戶
        users = Lists.newArrayList();
    }

    /**
     * 建立鏈接
     * @param webSocketSession
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        //將用戶信息添加到list中
        users.add(webSocketSession);
        System.out.println("=====================建立連接成功==========================");
        User user  = (User) webSocketSession.getAttributes().get("user");
        if(user != null){
            System.out.println("當前連接用戶======"+user.getName());
        }
        System.out.println("webSocket連接數量====="+users.size());
    }

    /**
     * 接收消息
     * @param webSocketSession
     * @param webSocketMessage
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        User user = (User) webSocketSession.getAttributes().get("user");
        System.out.println("收到用戶:"+user.getName()+"的消息");
        System.out.println(webSocketMessage.getPayload().toString());
        System.out.println("===========================================");

    }

    /**
     * 異常處理
     * @param webSocketSession
     * @param throwable
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable){
        if (webSocketSession.isOpen()){
            //關閉session
            try {
                webSocketSession.close();
            } catch (IOException e) {
            }
        }
        //移除用戶
        users.remove(webSocketSession);
    }

    /**
     * 斷開鏈接
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        users.remove(webSocketSession);
        User user = (User) webSocketSession.getAttributes().get("user");
        System.out.println(user.getName()+"斷開連接");
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 發送消息給指定的用戶
     * @param user
     * @param messageInfo
     */
    public void sendMessageToUser(User user, TextMessage messageInfo){
        for (WebSocketSession session : users) {
            User sessionUser = (User) session.getAttributes().get("user");
            //根據用戶名去判斷用戶接收消息的用戶
            if(user.getName().equals(sessionUser.getName())){
                try {
                    if (session.isOpen()){
                        session.sendMessage(messageInfo);
                        System.out.println("發送消息給:"+user.getName()+"內容:"+messageInfo);
                    }
                    break;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

controller及頁面

這裏簡單的模擬登錄,前臺傳入登錄參數,直接將參數保存到session中。

@RequestMapping("websocket")
@Controller
public class UserController {

    @Autowired
    private MsgScoketHandle msgScoketHandle;

    @RequestMapping("login")
    public String login(User user, HttpServletRequest request){
        user.setId(UUID.randomUUID().toString().replace("-",""));
        request.getSession().setAttribute("user",user);
        return "/index";
    }

    @ResponseBody
    @RequestMapping("sendMsg")
    public String sendMag(String content,String toUserName){
        User user = new User();
        user.setName(toUserName);
        TextMessage textMessage = new TextMessage(content);
        msgScoketHandle.sendMessageToUser(user,textMessage);
        return "200";
    }
}

登錄頁面省略,直接socket連接頁面,這裏使用sockjs來創建連接,所以需要先添加js文件
sockjs.min.js

<script>
    $(document).ready(function() {
        var ws;
        if ('WebSocket' in window) {
            ws = new WebSocket("ws://"+window.location.host+"/webSocketServer");
        } else if ('MozWebSocket' in window) {
            ws = new MozWebSocket("ws://"+window.location.host+"/webSocketServer");
        } else {
            //如果是低版本的瀏覽器,則用SockJS這個對象,對應了後臺“sockjs/webSocketServer”這個註冊器,
            //它就是用來兼容低版本瀏覽器的
            ws = new SockJS("http://"+window.location.host+"/sockjs/webSocketServer");
        }
        ws.onopen = function (evnt) {
        };
        //接收到消息
        ws.onmessage = function (evnt) {
            alert(evnt.data);
            $("#msg").html(evnt.data);
        };
        ws.onerror = function (evnt) {
            console.log(evnt)
        };
        ws.onclose = function (evnt) {
        }

        $("#btn1").click(function () {

            ws.send($("#text").val());
        });
        $("#btn2").bind("click",function () {
            var url = "${pageContext.request.contextPath}/websocket/sendMsg";
            var content =  $("#text").val();
            var toUserName = "admin"
            $.ajax({
                data: "content=" + content + "&toUserName=" + toUserName,
                type: "get",
                dataType: 'text',
                async: false,
                contentType: "application/x-www-form-urlencoded;charset=UTF-8",
                encoding: "UTF-8",
                url: url,
                success: function (data) {
                    alert(data.toString());
                },
                error: function (msg) {
                    alert(msg);
                },

            });

        })
    });

</script>
<body>
當前登錄用戶:${pageContext.session.getAttribute("user").name}<br>
    <input type="text" id="text">
    <button id="btn1" value="發送給後臺">發送給後臺</button>
    <button id="btn2" value="發送給其他用戶">發送給其他用戶</button>
    <div id="msg"></div>
</body>
</html>

啓動項目

在控制可以看到socket註冊成功

訪問頁面,第一個用戶使用admin登錄,第二個使用1234登錄

首先將消息發送給後臺,後臺打印消息

使用1234用戶發送消息給admin

爬坑

在Springmvc項目中都會指定連接訪問的後綴,比如.do、.action,但是這裏會導致按照以上配置會導致前端連接socket服務時404。我的解決辦法是修改web.xml,將DispatcherServlet<url-pattern>改爲/。。。但是新的問題又出現了,頁面無法加載資源文件,所以還需要在SpringMVC.xml中添加對靜態資源的配置,這裏具體的mappinglocation看自己的具體項目。

    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/images/**" location="/images/" />
    <mvc:resources mapping="/js/**" location="/js/" />
發佈了71 篇原創文章 · 獲贊 89 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章