SpringMVC WebSocket Apache代理 相关技术点总结

SpringMVC + WebSocket这个技术的文章一大把,最后笔者发现对WebSocket一知半解的情况下使用这个技术,任然会存在问题,有时还会觉得莫名其妙,而且在直接使用别人代码的情况下更是如此。本文希望通过本篇文章介绍一下个人经验总结。
如果英语尚可,直接参考SpringMVC官方对WebSocket的支持文档,点击这里

WebSocket

这个技术不神秘,而且简单易用,只需要使用@ServerEndpoint注解一个类即可,再使用@OnMessage, @OnOpen, @OnClose注解一下方法,实现对WebSocket的管理和消息处理。需要Tomcat 7.0以上。
客户端,使用WebSocket实现访问。
参考资料:JSR-356标准, TOMCAT 7.0 以上是支持该标准的。
ServerEndPoint文档点击这里
下面就一步步的通过Eclipse+Tomcat实现WebSocket通讯,建议新手像我一样先试试。

自己动手创建第一个WebSocket

  • Eclipse建立Web 3.0的项目(Eclipse4.5.1)
    Eclipse的项目配置
  • 编写WebSocket服务端代码
@ServerEndpoint("/hello")
public class HelloServer {
    @OnMessage
    public String processGreeting(String message, Session session) {
        System.out.println("Greeting received:" + message);

        return "Server echo:" + message + " at " + Calendar.getInstance().getTime().toString();
    }
}

上述代码参考了前面JSR-356的标准文档,ServerEndPoint文档,稍做修改,使用了带返回值的OnMessage注解的方法,这样就可以实现和前端通讯。
- 编写HTML测试代码
test/jsr356.html

<!DOCTYPE html>
<html>
<head>
<title>Testing websockets using JSR-356</title>
<script type="text/javascript">
    var webSocket =  new WebSocket('ws://localhost:9090/TestWebSocket/hello');

    webSocket.onerror = function(e){onError(e)};
    webSocket.onopen = function(e) {onOpen(e)};
    webSocket.onmessage = function(e) {onMessage(e)};

    function onMessage(event) {
        document.getElementById('messages').innerHTML += '<br />' + event.data;
    }

    function onOpen(event) {
        document.getElementById('messages').innerHTML  = 'Connection established';
    }

    function onError(e) {alert(e.data);}

    function startWebSocket() { webSocket.send('hello'); return false; }
</script>
</head>
<body>
    <div>
        <input type="submit" value="Start" onclick="startWebSocket()" />
    </div>
    <div id="messages"></div>
</body>
</html>

以上代码需要修改的地方只有:WebSocket对象建立时指定的WEB服务器名称,端口,虚拟目录名称。笔者为本机9090, 项目名称为TestWebSocket, hello为websocket服务端口URL。
http://localhost:9090/TestWebSocket/test/jsr356.html

小结

以上代码就是简要使用TOMCAT直接支持的方法和浏览器一同实现了WebSocket通讯,客户端提交hello消息,会从服务端就收消息。好像和一般的请求没啥区别啊!是啊!没有区别!Web请求,实现一个数据请求,就可以返回数据啊!WebSocket的关键在于,他的通讯是在水下面发生的,通过浏览器的工具没有办法直接查看WebSocket的数据报文,这是第一点,另外一点就是可以实现相互通讯,即通过服务器就可以直接和在线的客户端通讯,解决了以前客户端只能通过轮询实现数据更新。在JS4-356标准种,@OnOpen可以获得新打开的会话,从而实现和浏览器的关联,并可以实现服务器向客户端直接通讯。
下面是WebSocket在浏览器工具中看到的通讯过程:
101状态码
注意:WebSocket对象 IE 9以下不支持……“WebSocket”未定义
MAVEN项目需要包含如下依赖,以便让TOMCAT使用新版本的javeee api.

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
</dependency>

SpringMVC + WebSocket

参考文献:WebSocket Support
通过SpringMVC实现WebSocket好处多多,具体请看英文文档,主要是解决了协议兼容的问题。
使用SpringMVC需要的库有好几个,本文示例,就直接通过普通J2EE项目搭建示例,建议项目中采用maven或其他管理依赖。
本示例需要用到jar包:
commons-logging-1.2.jar; spring-aop-4.2.5.RELEASE.jar; spring-beans-4.2.5.RELEASE.jar; spring-context-4.2.5.RELEASE.jar; spring-context-support-4.2.5.RELEASE.jar; spring-core-4.2.5.RELEASE.jar; spring-expression-4.2.5.RELEASE.jar; spring-web-4.2.5.RELEASE.jar; spring-webmvc-4.2.5.RELEASE.jar; spring-websocket-4.2.5.RELEASE.jar.

实现WebSocket + SpringMVC的基本处理类:
test.websocket.MyHandler.java

package test.websocket;

import java.io.IOException;
import java.util.Calendar;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyHandler extends TextWebSocketHandler{
    public MyHandler(){
        super();
        System.out.println("MyHandler constractor...");
    }
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        System.out.println("Client received:" + message.getPayload());
        try {
            session.sendMessage(new TextMessage("Server echo:" + message.getPayload() + " at " + Calendar.getInstance().getTime().toString()));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

通过注解实现WebSocket的配置类(直接用注解实现初始化):
test.websocket.WebSocketConfig

package test.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

有两种方式通过Spring初始化WebSocket.
- 通过注解的方式(@EnableWebSocket)
- 通过XML配置文件的方式(websocket:handlers)
无论是通过上述哪种方式,都需要配置Spring.
- 在web.xml 配置文件中添加SpringMVC监听器,和分发器配置
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>TestWebSocket</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

  <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/test/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>
  • 配置Spring配置文件applicationContext.xml
    这个文件有两种配置,分别对于注解的方式和XML配置的方式,两种方式的任务主要是配置Bean的初始化,还有websocket的配置初始化。
    笔者的applicationContext.xml如下:
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <context:component-scan base-package="test.websocket"/>
    <!-- 
    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="MyHandler"/>
 -->
</beans>

component-scan 实现的注解的扫描,从而实现注解功能的实现,该功能是Spring AOP功能,需要AOP库。参考文档

如果没有采用注解实现,也可以手动在applicationContext.xml种配置websocket处理器,参见上面的配置文件。

SpringMVC + WebSocket + SockJs

客户端使用SockJs可以实现更好的浏览器兼容性, 示例参考官方网站,很简单,sockjs-client github链接

SockJs是如何实现服务端/客户端兼容的?通过尝试服务器的支持能力,从而确认是否支持特定的协议,这些协议包括:websocket ,xhr-streaming,xdr-streaming,eventsource…这些在特定的环境下会依次采用,也可以由客户端指定。比如websocket需要ws协议,如果经过没有相关配置的apache/ngnix,协议就会退化到轮询的websocket实现。通过协议退化,就保重了功能的实现。
比如Aapache需要配置如下才可以访问后端的Tomcat的websocket:

RewriteEngine On
    RewriteCond %{REQUEST_URI}  ^/JFGJ/sockjs/webSocketServer/.*/websocket$            [NC]
    RewriteRule /(.*)           ws://localhost:9090/$1 [P,L]

    ProxyPass /JFGJ/sockjs/webSocketServer http://localhost:9090/JFGJ/sockjs/webSocketServer
    ProxyPassReverse /JFGJ/sockjs/webSocketServer http://localhost:9090/JFGJ/sockjs/webSocketServer

笔者项目的sockjs的端点为:/JFGJ/sockjs/webSocketServer,/JFGJ/sockjs/webSocketServer/.*/websocket表达式标识对websocket的请求需要映射为ws协议(这个协议保护特殊的http头)。

如果遇到问题欢迎在留言区交流。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值