最近做个东西,要用到服务器主动推送的功能,于是查了相关的资料,现就对查到的东西做下总结。
大致有如下几种实现策略:
1,轮训 ajax:缺陷:由浏览器发起,不实时,大多数时候服务器端并没有数据,还得建立一次连接处理,白浪费表情。舍弃。
2,HTML5 SEE:缺陷:代码写完后才发现的,内部实际上还是 1 的实现策略,无语删掉代码。舍弃。
3,Comet :缺陷:本人较笨,看了半天没理解它是要干嘛,感觉代码很复杂,服务器端又是BEGIN,又是END,又是READ的,前端代码也复杂,看几下就觉得头昏脑胀。放弃治疗。
4,websocket:似乎有几种实现方式,有纯servlet的。也有依赖spring的,作为懒惰份子,自然是选择拿来就用的spring方式。spring里面呢,又分为实现纯servlet方式的和sockjs方式的。能出现编外东西的肯定还是纯的用起来太麻烦嘛,不用想,直接使用 spring sockjs 方式的实现。OK,就是它了。
以下为最简略实现方式,连接的开关之类的,咱根本不关心,只要能拿来通信传数据就OK,基于此,快速实现如下:
1,得先去 www.sockjs.org 下载一个 js 文件,名字叫:sockjs.min.js
这个 js 文件,用于 web 客户端这边。整个代码如下:
<script src="sockjs.min.js"></script>
<script type="text/javascript">
var sock = new SockJS('${path}/pushEPC');
sock.onmessage = function(e) {
alert(e.data);
};
</script>
2,后台新建一个java类,大概代码如下:
import java.util.LinkedList;
import javax.annotation.PostConstruct;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class PushEPCHandler extends TextWebSocketHandler {
private static WebSocketSession session;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
this.session = session;
}
@PostConstruct
public void startReader1() {
final LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("2017-W12-001");
linkedList.add("QQQQ-13097");
linkedList.add("QQQQ-13098");
linkedList.add("QQQQ-13099");
linkedList.add("QQQQ-131");
linkedList.add("QQQQ-1310");
linkedList.add("QQQQ-13100");
linkedList.add("QQQQ-13101");
linkedList.add("QQQQ-13102");
linkedList.add("QQQQ-13103");
linkedList.add("QQQQ-13104");
linkedList.add("QQQQ-13105");
new Thread(() -> {
try {
while (true) {
Thread.sleep(3000);
try {
if (session != null && linkedList.size() > 0) {
session.sendMessage(new TextMessage(linkedList.poll() + ""));
}
} catch (Exception e) {
}
}
} catch (Exception e) {
}
}).start();
}
}
- 这个演示类有如下问题:
a,单 WebSocketSession 了,难道一个服务器只供给一个用户使用?作为 web 服务器显然是多用户的,那么第一步的前台 js 中 new SockJS 的时候,一起是3个参数项的,另外2个参数项可以传递目前是哪个用户的标识,这样就可以在后台区分出来连接是哪个用户建立的,然后再改改如上代码,即可实现多用户通信。
b, @PostConstruct 注解的方法,实际中是不许需要的,这边只是为了演示后台把数据推送到前台去了
- 这个类编写的注意点:
a,在连接建立起来之后,往前台推送数据,就是使用方法:
session.sendMessage(new TextMessage(linkedList.poll() + ""));
b,继承实现的 afterConnectionEstablished 方法,一定要快速失败(?这个词可能用的不准确)。啥意思呢?
就是这个方法,必须秒秒钟就执行完毕。切忌不要在这个方法中写耗时较长的操作,因为一旦写了,前台建立连接没有很快速的建立起来时,前台的 js 工具就会尝试其它协议或者底层方式建立连接,具体都尝试了哪些方式,我也懒得去搞清楚,总之,反而更容易连接失败导致通信不了。
那么,就更不能写一个 while(true) 循环在这个方法里面,以达到其它模块有了信息持续往前台推送的目的啦。我们能推送,肯定是要持续推送的,这个目的没错儿,但不能在这个 afterConnectionEstablished 方法里面去实现,得像我样例代码这样,把 WebSocketSession 先拿到内存里持有住就行了,然后在另外的方法里通过它往前台推送信息。
总而言之,你得让 afterConnectionEstablished 是死是活,快速给个话儿(结束掉)。
c,继承 TextWebSocketHandler 类还有 另外2个比较常用的方法,一个是连接关闭时,一个是发送信息时。关闭时,在多用户环境下,当然的把我们持有的对象删除掉,不然内存泄漏了对吧?发送信息时可能要给加点料再处理一下统一格式之类的,这个看具体情况。
3,spring.xml 配置,大概代码如下:
头配置:
xmlns:websocket="http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd
具体类配置:
<bean id="pushEPCHandler" class="com.xuanen.hardware.PushEPCHandler" />
<websocket:handlers>
<websocket:mapping path="/pushEPC" handler="pushEPCHandler" />
<websocket:sockjs />
</websocket:handlers>
注意点,一定要写 <websocket:sockjs /> 这个配置项,这个和前台的 sokejs 工具包是对应的,不写这个连接是建立不成功的。
4,可能的web.xml 配置如下:
给所有要通过的的 filter 和 servlet 加如下属性项:
<async-supported>true</async-supported>
这个主要用于建立通信较慢时,可能切换连接方式时,这个前台 js 工具会自动生成一大堆动态地址来调用。
5,需要的spring jar 包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
怎么样,嘿简单吧?至于到处能查到的这种方式,还要写拦截器之类的,完全不在我考虑范围,哥们我用不到啊。一个简单的 websocket 通信样例,就是这么简单。