目前关于WebSocket写了两篇
WebSocket详解(一)基础概念
WebSocket详解(二)应用实例
上一节中我们介绍了HTTP协议的缺点以及WebSocket做了哪些改进,知道了相关的基本概念。这一节通过一个简单的例子来展示如何使用WebSocket。
这个例子的场景是这样的:
年会上,展示给观众的是一个页面,后台人员控制的是另一个页面。在后台控制页面上点击停止摇一摇,然后展示给观众的页面弹出最后的获奖者。页面控制的例子还是很常见的,也不一定用WebSocket,这里只是用WebSocket举个例子。
本例中的实现方式是针对Tomcat7的,即利用Tomcat对WebSocket的支持。为什么不利用Spring呢?
这是因为Spring4.x才添加对于WebSocket的支持。我们的项目中用的是基于Spring3.x的dubbox,改成基于Spring4.x时间来不及,因此直接使用Tomcat提供的WebSocket支持。
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>tomcat-catalinaartifactId>
<version>7.0.70version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>tomcat-coyoteartifactId>
<version>7.0.70version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.java-websocketgroupId>
<artifactId>Java-WebSocketartifactId>
<version>1.3.0version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
dependency>
这里用的是dubbox框架,在web层新建websocket.properties,用于存储WebSocket的对应通道。
websocket.properties内容如下:
shake.websocket.address = ws://localhost/shakeChannel
在web.xml中注册通道
<servlet>
<servlet-name>shakeChannelservlet-name>
<servlet-class>com.sf.lottery.web.websocket.WebsocketChannelservlet-class>
servlet>
<servlet-mapping>
<servlet-name>shakeChannelservlet-name>
<url-pattern>/shakeChannelurl-pattern>
servlet-mapping>
shakeController.java
@Controller
public class ShakeController {
private final static Logger log = LoggerFactory.getLogger(ShakeController.class);
@Value("${shake.websocket.address}")
private String shakeAddress;
@ResponseBody
@RequestMapping(value = "/shake/closeShake", method = RequestMethod.POST)
public JsonResult closeShake(){
//得到获奖人的代码省略,只看下面WebSocket这一段
if(topUser != null){
UserShakeVo luckUser = (UserShakeVo)topUser;
result.setData(luckUser);
try {
//将获奖人持久化到数据库
userService.setUserShakeAward(luckUser.getUserId());
//发送到前台显示
WebSocketClient webSocketClient = WebsocketClientFactory.getWebsocketClient("shake", shakeAddress);
webSocketClient.connectBlocking();
webSocketClient.send(JSON.toJSONString(luckUser));
webSocketClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
WebsocketClientFactory.java。由于Dubbox依赖于spring3.x,但是spring对于websocket支持是从4.x开始的,所以通过本地建立client访问websocket。
package com.sf.lottery.web.websocket;
import com.sf.lottery.common.utils.StrUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @author Hash Zhang
* @version 1.0.0
* @date 2016/11/30.
*/
public class WebsocketClientFactory {
public static WebSocketClient getWebsocketClient(final String name, final String uri) throws URISyntaxException {
return new WebSocketClient(new URI(uri), new Draft_17()) {
private final Logger log = LoggerFactory.getLogger(WebSocketClient.class);
@Override
public void onMessage(String message) {
}
@Override
public void onOpen(ServerHandshake handshake) {
log.info(StrUtils.makeString("websocket client [",name,"] is established!"));
}
@Override
public void onClose(int code, String reason, boolean remote) {
log.info(StrUtils.makeString("websocket client [",name,"] is closed!"));
}
@Override
public void onError(Exception ex) {
log.warn(ExceptionUtils.getFullStackTrace(ex));
}
};
}
}
WebsocketChannel.java
package com.sf.lottery.web.websocket;
import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Hash Zhang
* @version 1.0.0
* @date 2016/11/30.
*/
public class WebsocketChannel extends WebSocketServlet {
private ConcurrentHashSet mmiList = new ConcurrentHashSet<>();
@Override
protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
return new MyMessageInbound();
}
private class MyMessageInbound extends MessageInbound {
WsOutbound myoutbound;
//维护一个WebSocket连接池,新建通道的时候,就向ConcurrentHashSet添加一个WebSocket连接
@Override
public void onOpen(WsOutbound outbound) {
this.myoutbound = outbound;
mmiList.add(this);
}
//关闭一个通道,本质上就是ConcurrentHashSet中删除一个元素
@Override
public void onClose(int status) {
mmiList.remove(this);
}
@Override
public void onTextMessage(CharBuffer cb) throws IOException {
for (MyMessageInbound mmib : mmiList) {
CharBuffer buffer = CharBuffer.wrap(cb);
try {
mmib.myoutbound.writeTextMessage(buffer);
mmib.myoutbound.flush();
}catch (Exception e){
}
}
}
@Override
public void onBinaryMessage(ByteBuffer bb) throws IOException {
}
@Override
public int getReadTimeout() {
return 0;
}
}
}
在variable.js中,记录WebSocket通道地址:
var shakeChannelAddress="ws://XX.XX.XX.XXX/shakeChannel";
这里为什么不能继续使用localhost了?原因在于,后端的代码是部署在服务器上的,所以用localhost;但是年会现场的时候,你是用本地的浏览器去解析variable.js,想要连接通道,当然要用服务器的具体IP地址。
在摇一摇界面的js(shake.js)中,
$(document).ready(function () {
time();
refreshShakePage();
//连接WebSocket通道
var ws = new WebSocket(shakeChannelAddress);
//打开通道时不作任何处理
ws.onopen = function(){
};
//一旦收到后端从WebSocket通道发来的消息,执行该方法
ws.onmessage = function(message){
var shakeWinnerMessage = JSON.parse(message.data);
var shakeWinnerHtml = "";
};
});
说明
如有转载,请务必注明出处
http://blog.csdn.net/antony9118/article/details/54381114