美文网首页JAVA服务器开发通信协议
基于spring实现websocket服务端的案例

基于spring实现websocket服务端的案例

作者: Felix_ | 来源:发表于2017-08-29 15:29 被阅读69次

    引言:

        websocket和socket的区别是什么?
    

    在开始之前,想起刚出校门的时候,曾经想进入软件开发行业,然而一直在学校里面成长的我们忽略了社会的包容。这件事情我记得很清楚:以实习生的身份去面试软件开发,面试官百般刁难,各种看不起没有经验的面试者,在各种嘲讽和蔑视中,他提问了一个问题——html和xml的区别是什么,我竟语塞。我确实说不清楚,但是我敢肯定你从来没有见过这么傲慢的面试官。

    再后来,随着工作时间的增长,随着知识的积累,也慢慢的进入了正轨,也面试过不少没有经验或者经验不足的人,但是我一直以包容和相互学习的态度认真和每个人交流,只为了给有想进入这个行业的人一个机会,一个鼓励,当他们不幸遇到上面的那种面试官的时候,可以理直气壮、胸有成竹的回答说:

    艹你吗的,html和xml的区别就和周杰和周杰伦的区别是一样的,然后踢门而去。
    所以,没错,websocket和socket的区别就像是周杰和周杰伦的区别一样,并没有什么关系。
    

    正文:

    本来就不是搞JAVA的,遇到这种问题肯定是去google,去stackoverflow等等,但是,当你发现在网上按照别人说的坐下来之后,就是不成功,要么就是版本问题,要么就是描述不全,要么就是。。。反正就是不能用。

    好吧,确实谁也不能说有多么的细心,所以万事还是要靠自己,搞起。(这篇文章针对有spring经验的同学阅读)
    首先创建一个maven项目,添加如下依赖,或者普通项目添加spring-websocket的jar包

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

    接下来配置web.xml,让spring拦截所有的请求

    <servlet>
            <display-name>minitoycloud</display-name>
            <servlet-name>mvc-dispather</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring/spring-*.xml</param-value>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>mvc-dispather</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    

    然后就是配置spring来扫描接下来我们需要的@Component包了
    首先拦截器的配置需要除去我们对于websocket地址的拦截:

    <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/**/**"/>
                <mvc:exclude-mapping path="/ws/**"/>
                <bean class="com.jasson.interceptors.BaseInterceptor"/>
            </mvc:interceptor>
        </mvc:interceptors>
    

    然后配置spring去扫描我们的@Component等组件

    <context:component-scan base-package="com.jasson">
            <!-- 指定扫包规则 ,只扫描使用@Controller注解的JAVA类 -->
            <!--<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>-->
            <!--<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
        </context:component-scan>
    

    有些文章可能会提到其他的配置,或者压根就没有这两次向配置,其实大多数项目一般就配置给spring来管理链接了,所以web.xml基本不用管就行了。
    做好了上面的这些,接下来就需要创建配置类了:
    创建一个配置类,这里我的是JassonWebSocketConfig:

    @Configuration
    @EnableWebSocket
    public class JassonWebSocketConfig implements WebSocketConfigurer {
        @Autowired
        JassonWebSocketHandler jassonWebSocketHandler;
    
        public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
            System.out.println("register add handler");
            webSocketHandlerRegistry.addHandler(jassonWebSocketHandler,"/ws").addInterceptors(new JassonWebSocketInterceptor()).setAllowedOrigins("*");
            webSocketHandlerRegistry.addHandler(jassonWebSocketHandler,"/ws/sockjs").addInterceptors(new JassonWebSocketInterceptor()).setAllowedOrigins("*").withSockJS();
        }
    }
    

    上面的JassonWebSocketHandler我们稍后创建。
    接下来我们创建JassonWebSocketInterceptor用来对远端的握手请求进行过滤和标记,这里强调下,本来之前按照网上的方法写好了,但是无论如何都连接不上,不是200就是500或者400,还有302,后来发现,其实就是因为在beforeHandshake这里进行了逻辑判断,并且无意中拒绝了连接,所以,在环境搭建过程中,不建议直接添加逻辑判断,只打印响应log然后返回true即可,逻辑判断在websocket调通之后再一点点添加,这样很容易发现问题所在。

    public class JassonWebSocketInterceptor implements HandshakeInterceptor {
    
        public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
            System.out.println("handshake interceptor");
            if (serverHttpRequest instanceof  ServletServerHttpRequest) {
                ServletServerHttpRequest servletHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
                HttpSession session = servletHttpRequest.getServletRequest().getSession();
                System.out.println("session:"+session);
                User user = (User) session.getAttribute("user");
                System.out.println("user:"+user.toString());
                if (user != null) {
                    if (user.getDevtype().equals("0"))
                    {
                        //标记打印机
                        map.put("user", user);
                    }else if (user.getDevtype().equals("1"))
                    {
                        //标记用户
                        map.put("user", user);
                    }else
                    {
                        System.out.println("未知的设备拒绝握手");
                        return false;
                    }
                } else{
                    System.out.println("未登录用户拒绝握手");
                    return false;
                }
            }
            return true;
        }
    
        public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    
        }
    }
    

    最后就是我们的JassonWebSocketHandler了,这里集成了我们所有的通讯逻辑,也是我们管理session的地方,建议测试阶段可以直接用map或者list用来存储用户的session,如果用户量大则需要考虑使用redis来管理用户session,代码如下:

    @Component
    public class JassonWebSocketHandler implements WebSocketHandler {
    
        public static final Map<String,WebSocketSession> userSocketSessionMap;
    
        static {
            System.out.println("creat socket handler");
            userSocketSessionMap = new HashMap<String, WebSocketSession>();
        }
    
        //握手实现连接后
        public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
            User user = (User) webSocketSession.getAttributes().get("user");
            System.out.println(user.toString());
            sendMessageToAllUser(new TextMessage(("welcome ["+ user.getUid()+"] to minitoycloud").getBytes()));
        }
    
        //发送信息前的处理
        public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
            System.out.println("收到消息:"+webSocketMessage.getPayload().getClass().getName());
    
    
    //这个地方之所以加上逻辑,是为了方便大家知道怎么处理过来的消息,环境搭建过程还是不建议做逻辑判断
            if (webSocketMessage.getPayloadLength() == 0)
            {
                return;
            }
            //获取socket通道中的数据并转化为Message对象
            WSMessage message = new Gson().fromJson(webSocketMessage.getPayload().toString(),WSMessage.class);
            System.out.println(message.toString());
            sendMessageToUser(message.getToId(),message);
        }
    
    
    
        public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
            System.out.println("transport error");
        }
    
        public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
                System.out.println("当前在线人数[" + userSocketSessionMap.size() + "]人");
        }
    
        public boolean supportsPartialMessages() {
            return false;
        }
    
        //发送信息
        public void sendMessageToUser(String uid, WSMessage message){
            WebSocketSession session = userSocketSessionMap.get(uid) == null ? printerSocketSessionMap.get(uid):userSocketSessionMap.get(uid);
            if (session != null && session.isOpen())
            {
                try {
                    session.sendMessage(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(message).getBytes()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void sendMessageToAllUser(TextMessage message) throws IOException{
            Set<String> targetIDs = userSocketSessionMap.keySet();
            WebSocketSession session = null;
            for (String targetID:targetIDs){
                session = userSocketSessionMap.get(targetID);
                if (session.isOpen()){
                    session.sendMessage(message);
                }
            }
        }
    }
    

    本来不想更的,项目写好了太长时间,放在那里再去找也麻烦,本着有始有终的原则,继续把流程跑通。

    其实上面的代码已经完成了websocket服务端的搭建了,剩下的就是测试了,客户端通过

    ws://服务端IP:服务端端口号/ws就可以成功进行连接了
    例如:ws://127.0.0.1:8888/ws

    网页端可采用上面的方式进行连接,但是对于IE10以下的浏览器,由于不兼容websocket,可使用下面的方式进行连接:

    http://服务端IP:服务端端口号/ws/sockjs就可以成功进行连接了
    例如:http://127.0.0.1:8888/ws/sockjs

    至于接下来的发送消息测试,客户端连接的维持,就请大家在实践中进行测试,毕竟实践才是掌握技术的最有效方式。

    相关文章

      网友评论

      • koalakfc:厉害了我的哥,但是续呢????
        koalakfc:@独箸 快更,我给你加鸡腿。
        Felix_:最近忙于项目,暂时没有更新,见谅,有空会更新,谢关注

      本文标题:基于spring实现websocket服务端的案例

      本文链接:https://www.haomeiwen.com/subject/lswirxtx.html