websocket是html5的特性之一,现有主流的浏览器都已经支持websocket,websocket也成了为Java EE7的规范之一。目前jetty、tomcat等主流的应用服务器都支持websocket协议。tomcat自7以上支持websocket协议,tomcat7和tomcat8还是有些不同。tomcat8对websocket有了更好的支持和样例,在webapp\examples下面有websocket的样例,可以直接运行,java文件在example的WEB-INF目录下,和class放在一起,非常方便。
spring4也增加了对websocket的API支持,目前有些容器对JSR-356规范还未实现,且不同版本的容器实现方式也有不同,使用spring提供的API可以做到对websocket的开发统一。
一. JAVA API for websocket JSR-356
1. 非常简单,服务器段只需要使用@ServerEndpoint标识就可以,具体使用方法:
在需要设置为websocket服务的类上面添加@ServerEndpoint(value = 'abcd'),实现对应的方法即可。
import javax.websocket.OnClose;
import javax.websocket.OnOpen;
import javax.websocket.server.ServerEndpoint;
import org.springframework.context.annotation.Configuration;
@Configuration
@ServerEndpoint(value = "/abcd")
public class RealEndpoint {
@OnOpen
public void onOpen() {
System.out.println("Client connected");
}
@OnClose
public void onClose() {
System.out.println("Connection closed");
}
}
2. 在客户端界面使用
var host = 'ws://' + window.location.host + '/THSCADAWEB/collectionList';
var webSocket;
if ('WebSocket' in window) {
webSocket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
webSocket = new MozWebSocket(host);
} else {
Ext.Msg.alert('提示', '当前浏览器不支持websocket,请更换浏览器');
return;
}
webSocket.onerror = function(event) {
console.log('error');
};
webSocket.onopen = function(event) {
console.log('open');
};
webSocket.onmessage = function(event) {
var data = event.data;
console.log('onmessage');
var address = data.split(';')[0];
var status = data.split(';')[1];
var collectionStore = Ext.getCmp('deployCollectionGrid').getStore();
var rec = collectionStore.findExact('remote_address', address);
if(rec > -1){
unit = collectionStore.getAt(rec).set('collection_connStatus',status);
}else{
collectionStore.add({
'remote_address' : address,
'collection_connStatus' : status
});
}
};
当然复杂点也可以判断浏览器是否支持websocket之类的,可以借鉴tomcat8里面的样例。
3. 所用到的包
如果只使用JAVA API,还需要导入tomcat下面的websocket-api.jar,这比较以来与tomcat的实现,而且直接导入还不行,直接导入tomcat下面的websocket-api.jar,部署运行的话,一直报错。Error during WebSocket handshake : Unexpected response code : 404。据说这是因为jar包冲突的原因导致。
解决方法:删除原有项目中的java ee7.jar包,将tomcat里面的websocket-api.jar包单独复制到一个目录里面,起名tomcat-websocket,然后在项目上右键->properties->Java Build Path -> Libraries -> Add Library -> User Library -> User Libraries -> New -> User library name 输入tomcat-websocket,打勾下面的system library -> ok -> 选中刚才加入的tomcat-websocket,点击Add External JARs,加入刚才复制websocket-api.jar的目录tomcat-websocket -> 勾选建好的User library -> Finish。
建好后再导入就出现了javax-websocket的包,并且可以部署到tomcat8中进行运行。
二、Spring API for WebSocket
用java的api是不是比较麻烦,但是用Spring就不用处理上述过程了。Spring从4起开始推出WebSocket API ,现在使用的是Spring4.2.3,算是较新的版本。当然上述JAVA API for websocket‘也可以在Spring工程中使用,但是还是需要注意和应用服务器jar包冲突的问题。
1. WebSocketConfig
Spring通过一个配置类来注册并且管理websoceket服务
package com.th.scada.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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;
import com.th.scada.real.controller.RealSocketHandler;
/**
* Spring框架WebSocket配置管理类
* @author guoyankun
*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements
WebSocketConfigurer {
/**
* 注册websocket
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册多个websocket服务,addHandler第一个参数是websocket的具体业务处理类,第二个参数collectionList相当于endpoint
registry.addHandler(RealSocketHandler(), "/collectionList").addInterceptors(new WebSocketHandshakeInterceptor());
// 可以注册多个websocket的endpoint
}
@Bean
public WebSocketHandler RealSocketHandler() {
return new RealSocketHandler();
}
}
2. Interceptor,顾名思义,拦截器,实现HandshakeInterceptor接口,处理websocket握手开始和握手结束的事件。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import com.th.scada.util.Constants;
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
private static Logger logger = LoggerFactory.getLogger(HandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
//使用userName区分WebSocketHandler,以便定向发送消息
String userName = (String) session.getAttribute(Constants.SESSION_USERNAME);
if(userName != null){
attributes.put(Constants.WEBSOCKET_USERNAME,userName);
}
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}
import java.io.IOException;
import java.util.ArrayList;
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;
public class RealSocketHandler implements WebSocketHandler {
private static final ArrayList users = new ArrayList();
private static Logger logger = Logger.getLogger(RealSocketHandler.class);
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus arg1) throws Exception {
logger.debug("websocket connection closed");
System.out.println("afterConnectionClosed");
users.remove(session);
}
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
logger.debug("connect to the websocket succcess ... ...");
System.out.println("afterConnectionEstablished");
users.add(session);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage> arg1) throws Exception {
System.out.println("handleMessage");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable arg1) throws Exception {
System.out.println("handleTransportError");
logger.debug("websocket connection closed");
users.remove(session);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* @param message
*
*/
public void sendMessageToUsers(TextMessage message) {
for (WebSocketSession user : users) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
*/
public void sendMessageToUser(String userName, TextMessage message) {
for (WebSocketSession user : users) {
if (user.getAttributes().get("websocket_username").equals(userName)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
4. 使用Spring API for WebSocket,就不需要tomcat里面的websocket-api.jar包了,取而代之的是spring-websocket-4.2.3.RELEASE.jar。