WebSocket

本文源代码下载:spring_websocket

手册文档下载:websocket.doc

WebSocket_百度百科

Spring WebSocket简单入门测试Demo(网页及时聊天)

  项目需要一个及时推送消息的功能,并且这个任务落在了我的头上,早有实现此功能的心思,这不机会就来了,网上看了下页面ajax定时请求这种方案的居多,通过同时介绍找到了websocket,百度百科了下非常满意,并轻松在网上找到了相关文章。下面简单介绍下实现,代码就不在这里贴了,Spring WebSocket简单入门测试Demo(网页及时聊天)这篇博文给的很全,下面只对实现过程中遇到的一些问题简单说明下

问题1:建立链接失败

  这个问题是由于页面链接ip与地址栏中不一致所致,正确方式如下图

  

  

  如果页面写成127.0.0.1,地址栏为localhost会链接报错,相反也会报错

问题2:多页面使用websocket如何区分?

  这个问题困扰了好久,试过通过访问链接、单独建立websocket等方式,均不能很好的解决问题,最后还是通过万能的“打debug”得到了解决思路,豁然开朗,原来如此简单。解决思路如下:

  1)在WebsocketConfigure中为每个页面添加单独的websocket管理 

@Configuration
@EnableWebSocket//开启websocket
public class WebSocketConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //页面1的websocket支持
        registry.addHandler(new WebSocketHander(), "/echo").addInterceptors(new HandshakeInterceptor()); //支持websocket 的访问链接
        registry.addHandler(new WebSocketHander(), "/sockjs/echo").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的访问链接

        //页面2的websocket支持
        registry.addHandler(new WebSocketHander(), "/echo1").addInterceptors(new HandshakeInterceptor()); //支持websocket 的访问链接
        registry.addHandler(new WebSocketHander(), "/sockjs/echo1").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的访问链接
    }
}

  有原来的一个websocket变为两个,只需与各自的页面对应即可

  2)拦截器标识修改(粗体字为修改之处)

public class HandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor {

    //进入hander之前的拦截
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse,
                                   WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        String[] uri = request.getURI().toString().split("/");
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(true);
            //保证地址栏的请求和websocket的请求地址统一就能获取到了
            User user = (User) session.getAttribute("now_user");
            if (session != null) {
                //使用userName区分WebSocketHandler,以便定向发送消息
                map.put("websocket_index", uri[uri.length - 1] + "_" + user.getUserName());
            }
        }
        return true;
    }

    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
                               WebSocketHandler webSocketHandler, Exception e) {

    }
}

  3)消息发送方法代码修改(黑体字为修改内容)

package com.controller.websocket;

import org.apache.log4j.Logger;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by root on 16-10-26.
 */
public class WebSocketHander implements WebSocketHandler {

    private static Logger log = Logger.getLogger(WebSocketHander.class);
    private static int count = 0;//统计建立管道数
    private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();
    private static final Map<String, String> map = new HashMap();

    //初次链接成功执行
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.debug("链接成功......");
        users.add(session);
        String key = (String) session.getAttributes().get("websocket_index");
        if (key != null) {
            //未读消息处理逻辑
            count++;
            map.put(key, session.getId());
            session.sendMessage(new TextMessage(count + ""));
        }
    }

    //接受消息处理消息
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        sendMessageToUsers(new TextMessage(webSocketMessage.getPayload() + ""));
        //sendMessageToUser("123", new TextMessage(webSocketMessage.getPayload() + ""));
    }

    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }
        count--;
        log.debug("链接出错,关闭链接......");
        users.remove(webSocketSession);
    }

    //关闭或离开此页面管道关闭
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        count--;
        log.debug("链接关闭......" + closeStatus.toString());
        users.remove(webSocketSession);
    }

    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 给所有在线用户发送消息
     *
     * @param message
     */public void sendMessageToUsers1(String index, TextMessage message) {
        ArrayList<WebSocketSession> u = users;
        Map<String, String> m = map;
        for (WebSocketSession user : users) {
            try {
                String[] str = user.getAttributes().get("websocket_index").toString().split("_");
                if (user.isOpen() && str[0].equals(index)) {
                    user.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给某个用户发送消息
     *
     * @param userName
     * @param message
     */public void sendMessageToUser1(String index, TextMessage message) {
        ArrayList<WebSocketSession> u = users;
        Map<String, String> m = map;
        for (WebSocketSession user : users) {
            if (user.getId().equals(map.get(index))) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    public static Map<String, String> getMap() {
        return map;
    }
}

  4)后端发送消息控制层代码修改

package com.controller.websocket;

import com.entity.User;

import org.apache.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.TextMessage;

import javax.servlet.http.HttpServletRequest;

/**
 * websocket数据推送测试 Created by root on 16-10-27.
 */
@Controller
@RequestMapping(value = "/websocket")
public class WebsocketController {

    private static Logger log = Logger.getLogger(WebsocketController.class);

    @Bean
    public WebSocketHander webSocketHandler() {
        return new WebSocketHander();
    }

    /**
     * 后台推送消息给指定用户
     *
     * @param request
     * @return
     */
    @RequestMapping("/auditing")
    @ResponseBody
    public String auditing(HttpServletRequest request, String index) {
        User user = (User) request.getSession().getAttribute("now_user");
        //webSocketHandler().sendMessageToUser1(index + "_" + user.getUserName(), new TextMessage(user.getUserName()));
        webSocketHandler().sendMessageToUsers1(index, new TextMessage(user.getUserName()));
        return "success";
    }

    /**
     * 打开此页面前端和后端正式建立管道,关闭或离开此页面管道关闭
     *
     * @return
     */
    @RequestMapping(value = "/websocket")
    public String websocket() {
        return "websocket";
    }

    @RequestMapping(value = "/websocket1")
    public String websocket1() {
        return "websocket1";
    }

}

 到此问题二得到解决。

问题3:同一页面,在同一浏览器的两个标签中打开,此时同一功能的管道打开了两个,如何在新的标签打开页面建立管道的时候关闭之前的?

  解决方案是在新标签打开该页面时,将之前页面的管道关闭,代码如下: 

//初次链接成功执行
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String key = (String) session.getAttributes().get("websocket_index");
        users.add(session);
        log.debug(key + " 链接成功......");
        if (key != null) {
            if (map.get(key) != null) {
                for (WebSocketSession sess : users) {
                    if (sess.getId().equals(map.get(key))) sess.close();
                    break;
                }
            }
            //未读消息处理逻辑
            count++;
            map.put(key, session.getId());
            session.sendMessage(new TextMessage(count + ""));
        }
    }

 问题4:刷新页面,之前管道未加载完的信息仍会在刷新后的页面加载(要求刷新之后之前的请求停止)?

  解决方案记录每个管道的WebSocketSession的id,保存到要发送消息的方法中,每次发送消息时检测用户的当前的管道id与记录的是否相等,相等则发送,否则停止该方法。

  具体通过在WebSocketHander中定义map记录每个用户的管道信息实现。

  private static final Map<String, String> map = new HashMap();

最后给出下所需依赖
<!--websocket-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.3.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.3.3</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-websocket</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>
原文地址:https://www.cnblogs.com/sunjf/p/spring_websocket.html