JAVA服务器开发通信协议

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

2017-08-29  本文已影响69人  Felix_

引言:

    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

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

上一篇下一篇

猜你喜欢

热点阅读