在前面两篇文章中,咱们对原生websocket进行了了解,且用demo来简单的讲解了其用法。可是在实际项目中,那样的用法是不可取的,理由是tomcat对高并发的支持不怎么好,特别是tomcat9以前,能够测试发现websocket链接达到的数量很低,且容易断开。
因此有如今的第三篇,对websocket的一种进阶方法。java
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是数一数二的,它已经获得成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty做为底层通讯框架,其余还有业界主流的RPC框架,也使用Netty来构建高性能的异步通讯能力。
经过对Netty的分析,咱们将它的优势总结以下:nginx
点此进入git
1.导入netty包github
<!-- netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha1</version> </dependency>
2.server启动类
如下@Service,@PostConstruct注解是标注spring启动时启动的注解,新开一个线程去开启netty服务器端口。web
package com.nettywebsocket; import javax.annotation.PostConstruct; import org.springframework.stereotype.Service; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * ClassName:NettyServer 注解式随spring启动 * Function: TODO ADD FUNCTION. * @author hxy */ @Service public class NettyServer { public static void main(String[] args) { new NettyServer().run(); } @PostConstruct public void initNetty(){ new Thread(){ public void run() { new NettyServer().run(); } }.start(); } public void run(){ System.out.println("===========================Netty端口启动========"); // Boss线程:由这个线程池提供的线程是boss种类的,用于建立、链接、绑定socket, (有点像门卫)而后把这些socket传给worker线程池。 // 在服务器端每一个监听的socket都有一个boss线程来处理。在客户端,只有一个boss线程来处理全部的socket。 EventLoopGroup bossGroup = new NioEventLoopGroup(); // Worker线程:Worker线程执行全部的异步I/O,即处理操做 EventLoopGroup workGroup = new NioEventLoopGroup(); try { // ServerBootstrap 启动NIO服务的辅助启动类,负责初始话netty服务器,而且开始监听端口的socket请求 ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workGroup); // 设置非阻塞,用它来创建新accept的链接,用于构造serversocketchannel的工厂类 b.channel(NioServerSocketChannel.class); // ChildChannelHandler 对出入的数据进行的业务操做,其继承ChannelInitializer b.childHandler(new ChildChannelHandler()); System.out.println("服务端开启等待客户端链接 ... ..."); Channel ch = b.bind(7397).sync().channel(); ch.closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); }finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
3.channle注册类spring
package com.nettywebsocket; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; /** * ClassName:ChildChannelHandler * Function: TODO ADD FUNCTION. * @author hxy */ public class ChildChannelHandler extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel e) throws Exception { // 设置30秒没有读到数据,则触发一个READER_IDLE事件。 // pipeline.addLast(new IdleStateHandler(30, 0, 0)); // HttpServerCodec:将请求和应答消息解码为HTTP消息 e.pipeline().addLast("http-codec",new HttpServerCodec()); // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息 e.pipeline().addLast("aggregator",new HttpObjectAggregator(65536)); // ChunkedWriteHandler:向客户端发送HTML5文件 e.pipeline().addLast("http-chunked",new ChunkedWriteHandler()); // 在管道中添加咱们本身的接收数据实现方法 e.pipeline().addLast("handler",new MyWebSocketServerHandler()); } } 4.存储类 如下类是用来存储访问的channle,channelGroup的原型是set集合,保证channle的惟一,如需根据参数标注存储,可使用currentHashMap来存储。 package com.nettywebsocket; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; /** * ClassName:Global * Function: TODO ADD FUNCTION. * @author hxy */ public class Global { public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); }
5.实际处理类
如下处理类虽然作了注释,可是在这里仍是详细讲解下。bootstrap
package com.nettywebsocket; import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; 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.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringDecoder; 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.AttributeKey; import io.netty.util.CharsetUtil; /** * ClassName:MyWebSocketServerHandler Function: TODO ADD FUNCTION. * * @author hxy */ public class MyWebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private static final Logger logger = Logger.getLogger(WebSocketServerHandshaker.class.getName()); private WebSocketServerHandshaker handshaker; /** * channel 通道 action 活跃的 当客户端主动连接服务端的连接后,这个通道就是活跃的了。也就是客户端与服务端创建了通讯通道而且能够传输数据 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 添加 Global.group.add(ctx.channel()); System.out.println("客户端与服务端链接开启:" + ctx.channel().remoteAddress().toString()); } /** * channel 通道 Inactive 不活跃的 当客户端主动断开服务端的连接后,这个通道就是不活跃的。也就是说客户端与服务端关闭了通讯通道而且不能够传输数据 */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // 移除 Global.group.remove(ctx.channel()); System.out.println("客户端与服务端链接关闭:" + ctx.channel().remoteAddress().toString()); } /** * 接收客户端发送的消息 channel 通道 Read 读 简而言之就是从通道中读取数据,也就是服务端接收客户端发来的数据。可是这个数据在不进行解码时它是ByteBuf类型的 */ @Override protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { // 传统的HTTP接入 if (msg instanceof FullHttpRequest) { handleHttpRequest(ctx, ((FullHttpRequest) msg)); // WebSocket接入 } else if (msg instanceof WebSocketFrame) { System.out.println(handshaker.uri()); if("anzhuo".equals(ctx.attr(AttributeKey.valueOf("type")).get())){ handlerWebSocketFrame(ctx, (WebSocketFrame) msg); }else{ handlerWebSocketFrame2(ctx, (WebSocketFrame) msg); } } } /** * channel 通道 Read 读取 Complete 完成 在通道读取完成后会在这个方法里通知,对应能够作刷新操做 ctx.flush() */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // 判断是否关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { System.out.println(1); handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } // 判断是否ping消息 if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } // 本例程仅支持文本消息,不支持二进制消息 if (!(frame instanceof TextWebSocketFrame)) { System.out.println("本例程仅支持文本消息,不支持二进制消息"); throw new UnsupportedOperationException( String.format("%s frame types not supported", frame.getClass().getName())); } // 返回应答消息 String request = ((TextWebSocketFrame) frame).text(); System.out.println("服务端收到:" + request); if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("%s received %s", ctx.channel(), request)); } TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + ":" + request); // 群发 Global.group.writeAndFlush(tws); // 返回【谁发的发给谁】 // ctx.channel().writeAndFlush(tws); } private void handlerWebSocketFrame2(ChannelHandlerContext ctx, WebSocketFrame frame) { // 判断是否关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); return; } // 判断是否ping消息 if (frame instanceof PingWebSocketFrame) { ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } // 本例程仅支持文本消息,不支持二进制消息 if (!(frame instanceof TextWebSocketFrame)) { System.out.println("本例程仅支持文本消息,不支持二进制消息"); throw new UnsupportedOperationException( String.format("%s frame types not supported", frame.getClass().getName())); } // 返回应答消息 String request = ((TextWebSocketFrame) frame).text(); System.out.println("服务端2收到:" + request); if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("%s received %s", ctx.channel(), request)); } TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + ":" + request); // 群发 Global.group.writeAndFlush(tws); // 返回【谁发的发给谁】 // ctx.channel().writeAndFlush(tws); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) { // 若是HTTP解码失败,返回HHTP异常 if (!req.getDecoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) { sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); return; } //获取url后置参数 HttpMethod method=req.getMethod(); String uri=req.getUri(); QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri); Map<String, List<String>> parameters = queryStringDecoder.parameters(); System.out.println(parameters.get("request").get(0)); if(method==HttpMethod.GET&&"/webssss".equals(uri)){ //....处理 ctx.attr(AttributeKey.valueOf("type")).set("anzhuo"); }else if(method==HttpMethod.GET&&"/websocket".equals(uri)){ //...处理 ctx.attr(AttributeKey.valueOf("type")).set("live"); } // 构造握手响应返回,本机测试 WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory( "ws://"+req.headers().get(HttpHeaders.Names.HOST)+uri, null, false); handshaker = wsFactory.newHandshaker(req); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), req); } } private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) { // 返回应答给客户端 if (res.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8); res.content().writeBytes(buf); buf.release(); } // 若是是非Keep-Alive,关闭链接 ChannelFuture f = ctx.channel().writeAndFlush(res); if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) { f.addListener(ChannelFutureListener.CLOSE); } } /** * exception 异常 Caught 抓住 抓住异常,当发生异常的时候,能够作一些相应的处理,好比打印日志、关闭连接 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }