Java tutorial
/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package cn.npt.net.websocket; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.apache.log4j.Logger; import cn.npt.fs.CachePoolFactory; import cn.npt.fs.cache.BaseMemoryCache; import cn.npt.fs.cache.CachePool; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.util.CharsetUtil; import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpMethod.*; import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpVersion.*; /** * <br>?,??????,???? * <br>??websocket??{cmd:getSensorValue,depth:0,timeInterval:1000,sensorIds:[...]} * <br>cmd:?getSensorValue,updateSensorValue,getStartTime,getSensorCount * <br>depth:0--?;1--BS;2--BS;3--BS * <br>timeInterval:,??ms * <br>sensorIds:?ID * @author Leonardo * */ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private String webSocketPath; private WebSocketServerHandshaker handshaker; private Timer timer; /** * */ private WebSocketTimerTask timerTask; /** * ?? */ private WebSocketTimerTask1 timerTask1; private boolean ssl; private static Logger log = Logger.getLogger(WebSocketServerHandler.class); public WebSocketServerHandler(String webSocketPath, boolean ssl) { this.webSocketPath = webSocketPath; this.ssl = ssl; } @Override public void channelRead0(ChannelHandlerContext ctx, Object msg) { if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, (FullHttpRequest) msg); } else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void channelInactive(ChannelHandlerContext ctx) { this.timer.cancel(); this.timer = null; log.info(ctx.channel() + " websocket disconnected!"); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) { this.timer = new Timer(); log.info(ctx.channel() + " websocket connected!"); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) { // Handle a bad request. if (!req.getDecoderResult().isSuccess()) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); return; } // Allow only GET methods. if (req.getMethod() != GET) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); return; } // Send the demo page and favicon.ico if ("/".equals(req.getUri())) { ByteBuf content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req)); FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); HttpHeaders.setContentLength(res, content.readableBytes()); sendHttpResponse(ctx, req, res); return; } if ("/favicon.ico".equals(req.getUri())) { FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); sendHttpResponse(ctx, req, res); return; } // Handshake WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), null, true); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req); } } private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // Check for closing frame if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException( String.format("%s frame types not supported", frame.getClass().getName())); } //??{cmd:getSensorValue,depth:0,timeInterval:1000,sensorIds:[...]},????? String request = ((TextWebSocketFrame) frame).text(); JSONObject reqObj = JSON.parseObject(request); if (reqObj.containsKey("cmd")) { String cmd = reqObj.getString("cmd"); switch (cmd) { case "getStartTime": getStartTime(ctx); break; case "getSensorCount": getSensorCount(ctx); break; case "getSensorValue"://? getSensorValue(reqObj, ctx); break; case "updateSensorValue"://? updateSensorValue(reqObj, ctx); break; case "getCachePoolDepth": getCachePoolDepth(reqObj, ctx); break; case "getSensorHandlers": getSensorHandlers(reqObj, ctx); break; default: ctx.writeAndFlush(new TextWebSocketFrame("")); break; } } else { log.warn("unknown command"); ctx.writeAndFlush(new TextWebSocketFrame("?")); } //ctx.executor().scheduleAtFixedRate(paramRunnable, paramLong1, paramLong2, paramTimeUnit) } /** * * @param ctx */ private void getStartTime(ChannelHandlerContext ctx) { JSONObject rs = new JSONObject(); rs.put("firstCreatedTime", CachePoolFactory.firstCreatedTime.toString()); ctx.writeAndFlush(new TextWebSocketFrame(rs.toString())); } /** * ??sensor? * @param ctx */ private void getSensorCount(ChannelHandlerContext ctx) { int count = CachePoolFactory.getSensorCount(); JSONObject rs = new JSONObject(); rs.put("sensorCount", count); ctx.writeAndFlush(new TextWebSocketFrame(rs.toString())); } /** * <br>??websocket??{depth:0,timeInterval:1000,sensorIds:[...]} * <br>depth:0--?;1--BS;2--BS;3--BS * <br>timeInterval:,??ms * <br>sensorIds:?ID * @param reqObj * @param ctx * @see #updateSensorValue(JSONObject, ChannelHandlerContext) */ private void getSensorValue(JSONObject reqObj, ChannelHandlerContext ctx) { if (reqObj.containsKey("timeInterval") && reqObj.containsKey("depth") && reqObj.containsKey("sensorIds")) { int timeInterval = reqObj.getIntValue("timeInterval"); if (timeInterval < 50) { ctx.writeAndFlush( new TextWebSocketFrame(",?timeInterval50")); } else { if (this.timerTask != null) { this.timerTask.cancel(); } if (this.timerTask1 != null) {// this.timerTask1.cancel(); } this.timerTask = new WebSocketTimerTask(ctx, reqObj); this.timer.schedule(this.timerTask, 0, timeInterval); } } else { //log.warn(""); ctx.writeAndFlush(new TextWebSocketFrame( "?,???{cmd:'getSensorValue',depth:0,timeInterval:1000,sensorIds:[...]}")); } } /** * ??? * @param reqObj * @param ctx * @see #getSensorValue(JSONObject, ChannelHandlerContext) */ private void updateSensorValue(JSONObject reqObj, ChannelHandlerContext ctx) { if (reqObj.containsKey("timeInterval") && reqObj.containsKey("depth") && reqObj.containsKey("sensorIds")) { int timeInterval = reqObj.getIntValue("timeInterval"); if (timeInterval < 50) { ctx.writeAndFlush( new TextWebSocketFrame(",?timeInterval50")); } else { if (this.timerTask1 != null) { this.timerTask1.cancel(); } if (this.timerTask != null) { this.timerTask.cancel(); } this.timerTask1 = new WebSocketTimerTask1(ctx, reqObj); this.timer.schedule(this.timerTask1, 0, timeInterval); } } else { //log.warn(""); ctx.writeAndFlush(new TextWebSocketFrame( "?,???{cmd:'getSensorValue',depth:0,timeInterval:1000,sensorIds:[...]}")); } } /** * ??value * @author Leonardo * */ private class WebSocketTimerTask extends TimerTask { private ChannelHandlerContext ctx; private JSONObject reqObj; private JSONArray sensorIds; private int depth; private final List<CachePool<?>> pools;//?? private StringBuilder errorInfo; public WebSocketTimerTask(ChannelHandlerContext ctx, JSONObject reqObj) { this.ctx = ctx; this.reqObj = reqObj; this.sensorIds = this.reqObj.getJSONArray("sensorIds"); this.depth = this.reqObj.getIntValue("depth"); this.pools = new ArrayList<CachePool<?>>(); this.errorInfo = new StringBuilder(); for (int i = 0; i < sensorIds.size(); i++) { boolean sensorExist = false; long sensorId = sensorIds.getLongValue(i); for (BaseMemoryCache cachePool : CachePoolFactory.getCachePools().values()) { CachePool<?> cp = cachePool.getCachePool(sensorId, depth); if (cp != null) { this.pools.add(cp); sensorExist = true; break; } } if (!sensorExist) { if (depth == 0) { this.errorInfo.append("sensorId:").append(sensorId).append(" ?\n"); } else {//?(?sensorId) this.errorInfo.append("sensorId:").append(sensorId) .append(" ??").append(depth) .append("\n"); } } } } @Override public void run() { if (this.errorInfo.length() == 0) {//? JSONObject rs = new JSONObject(); for (CachePool<?> pool : this.pools) { rs.putAll(pool.currentV2JSON()); } ctx.channel().writeAndFlush(new TextWebSocketFrame(rs.toJSONString())); } else { ctx.channel().writeAndFlush(new TextWebSocketFrame(this.errorInfo.toString())); this.cancel(); } } } /** * ?value??? * @author Leonardo * */ private class WebSocketTimerTask1 extends TimerTask { private ChannelHandlerContext ctx; private JSONObject reqObj; private JSONArray sensorIds; private int depth; private final List<CachePool<?>> pools;//?? /** * ????? */ private List<Integer> sensorPreviousIndexs; private StringBuilder errorInfo; private SimpleDateFormat sdf; public WebSocketTimerTask1(ChannelHandlerContext ctx, JSONObject reqObj) { this.ctx = ctx; this.reqObj = reqObj; this.sensorIds = this.reqObj.getJSONArray("sensorIds"); this.depth = this.reqObj.getIntValue("depth"); this.pools = new ArrayList<CachePool<?>>(); this.sensorPreviousIndexs = new ArrayList<Integer>(); this.errorInfo = new StringBuilder(); this.sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (int i = 0; i < sensorIds.size(); i++) { boolean sensorExist = false; long sensorId = sensorIds.getLongValue(i); for (BaseMemoryCache cachePool : CachePoolFactory.getCachePools().values()) { CachePool<?> cp = cachePool.getCachePool(sensorId, depth); if (cp != null) { this.pools.add(cp); this.sensorPreviousIndexs.add(cp.getIndex()); sensorExist = true; break; } } if (!sensorExist) { if (depth == 0) { this.errorInfo.append("sensorId:").append(sensorId).append(" ?\n"); } else {//?(?sensorId) this.errorInfo.append("sensorId:").append(sensorId) .append(" ??").append(depth) .append("\n"); } } } } @Override public void run() { if (this.errorInfo.length() == 0) {//? JSONObject rs = new JSONObject(); for (int i = 0; i < this.pools.size(); i++) { CachePool<?> pool = this.pools.get(i); if (!this.sensorPreviousIndexs.get(i).equals(pool.getIndex())) { this.sensorPreviousIndexs.set(i, pool.getIndex()); rs.putAll(pool.currentV2JSON()); rs.put("time", this.sdf.format(new Date(pool.getCurrentTime()))); } } if (!rs.isEmpty()) { ctx.channel().writeAndFlush(new TextWebSocketFrame(rs.toJSONString())); } } else { ctx.channel().writeAndFlush(new TextWebSocketFrame(this.errorInfo.toString())); this.cancel(); } } } private void getCachePoolDepth(JSONObject reqObj, ChannelHandlerContext ctx) { Long sensorId = reqObj.getLong("sensorId"); int depth = CachePoolFactory.getCachePool(sensorId).getDepth(sensorId); ctx.writeAndFlush(new TextWebSocketFrame("depth:" + depth)); } private void getSensorHandlers(JSONObject reqObj, ChannelHandlerContext ctx) { ctx.writeAndFlush(new TextWebSocketFrame("?")); } private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { // Generate an error page if response getStatus code is not OK (200). if (res.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); HttpHeaders.setContentLength(res, res.content().readableBytes()); } // Send the response and close the connection if necessary. ChannelFuture f = ctx.channel().writeAndFlush(res); if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error(cause.getMessage()); cause.printStackTrace(); ctx.close(); } private String getWebSocketLocation(FullHttpRequest req) { String location = req.headers().get(HOST) + this.webSocketPath; if (this.ssl) { return "wss://" + location; } else { return "ws://" + location; } } }