Netty5 + WebSocket 练习
1. 了解WebSocket知识
略
2. websocket实现系统简单反馈时间
WebSocketServerHandler.java
1 package com.jieli.nettytest.websocketserver; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.EventLoopGroup; 7 import io.netty.channel.nio.NioEventLoopGroup; 8 import io.netty.channel.socket.SocketChannel; 9 import io.netty.channel.socket.nio.NioServerSocketChannel; 10 import io.netty.handler.codec.http.HttpObjectAggregator; 11 import io.netty.handler.codec.http.HttpServerCodec; 12 import io.netty.handler.stream.ChunkedWriteHandler; 13 14 public class WebSocketServer { 15 16 public void run(int port){ 17 EventLoopGroup bossGroup = new NioEventLoopGroup(); 18 EventLoopGroup workerGroup = new NioEventLoopGroup(); 19 try { 20 ServerBootstrap b = new ServerBootstrap(); 21 b.group(bossGroup, workerGroup) 22 .channel(NioServerSocketChannel.class) 23 .childHandler(new ChannelInitializer<SocketChannel>() { 24 @Override 25 protected void initChannel(SocketChannel ch) throws Exception { 26 //HttpServerCodec将请求和应答消息编码或解码为HTTP消息 27 //通常接收到的http是一个片段,如果想要完整接受一次请求所有数据,我们需要绑定HttpObjectAggregator 28 //然后就可以收到一个FullHttpRequest完整的请求信息了 29 //ChunkedWriteHandler 向客户端发送HTML5文件,主要用于支持浏览器和服务器进行WebSocket通信 30 //WebSocketServerHandler自定义Handler 31 ch.pipeline().addLast("http-codec", new HttpServerCodec()) 32 .addLast("aggregator", new HttpObjectAggregator(65536)) //定义缓冲大小 33 .addLast("http-chunked", new ChunkedWriteHandler()) 34 .addLast("handler", new WebSocketServerHandler()); 35 } 36 }); 37 38 ChannelFuture f = b.bind(port).sync(); 39 System.out.println("start..."); 40 f.channel().closeFuture().sync(); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } finally { 44 workerGroup.shutdownGracefully(); 45 bossGroup.shutdownGracefully(); 46 } 47 } 48 49 public static void main(String[] args) { 50 new WebSocketServer().run(7777); 51 } 52 }
WebSocketServerHandler.java
1 package com.jieli.nettytest.websocketserver; 2 3 import java.util.logging.Level; 4 import java.util.logging.Logger; 5 6 import io.netty.buffer.ByteBuf; 7 import io.netty.buffer.Unpooled; 8 import io.netty.channel.ChannelFuture; 9 import io.netty.channel.ChannelFutureListener; 10 import io.netty.channel.ChannelHandlerContext; 11 import io.netty.channel.SimpleChannelInboundHandler; 12 import io.netty.handler.codec.http.DefaultFullHttpResponse; 13 import io.netty.handler.codec.http.FullHttpRequest; 14 import io.netty.handler.codec.http.FullHttpResponse; 15 import io.netty.handler.codec.http.HttpHeaderUtil; 16 import io.netty.handler.codec.http.HttpResponseStatus; 17 import io.netty.handler.codec.http.HttpVersion; 18 import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 19 import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; 20 import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; 21 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 22 import io.netty.handler.codec.http.websocketx.WebSocketFrame; 23 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 24 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 25 import io.netty.util.CharsetUtil; 26 27 public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{ 28 29 /** 30 * 日志 31 */ 32 private static final Logger logger = 33 Logger.getLogger(WebSocketServerHandler.class.getName()); 34 /** 35 * 全局websocket 36 */ 37 private WebSocketServerHandshaker handshaker; 38 39 @Override 40 protected void messageReceived(ChannelHandlerContext ctx, Object msg) 41 throws Exception { 42 //普通HTTP接入 43 if(msg instanceof FullHttpRequest){ 44 handleHttpRequest(ctx, (FullHttpRequest) msg); 45 }else if(msg instanceof WebSocketFrame){ //websocket帧类型 已连接 46 //BinaryWebSocketFrame CloseWebSocketFrame ContinuationWebSocketFrame 47 //PingWebSocketFrame PongWebSocketFrame TextWebScoketFrame 48 handleWebSocketFrame(ctx, (WebSocketFrame) msg); 49 } 50 } 51 52 @Override 53 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 54 ctx.flush(); 55 } 56 57 private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){ 58 //如果http解码失败 则返回http异常 并且判断消息头有没有包含Upgrade字段(协议升级) 59 if(!request.decoderResult().isSuccess() 60 || (!"websocket".equals( request.headers().get("Upgrade"))) ){ 61 sendHttpResponse(ctx, request, new DefaultFullHttpResponse( 62 HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); 63 return ; 64 } 65 //构造握手响应返回 66 WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false); 67 handshaker = ws.newHandshaker(request); 68 if(handshaker == null){ 69 //版本不支持 70 WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); 71 }else{ 72 handshaker.handshake(ctx.channel(), request); 73 } 74 } 75 /** 76 * websocket帧 77 * @param ctx 78 * @param frame 79 */ 80 private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){ 81 //判断是否关闭链路指令 82 if(frame instanceof CloseWebSocketFrame){ 83 handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); 84 return ; 85 } 86 //判断是否Ping消息 -- ping/pong心跳包 87 if(frame instanceof PingWebSocketFrame){ 88 ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); 89 return ; 90 } 91 //本程序仅支持文本消息, 不支持二进制消息 92 if(!(frame instanceof TextWebSocketFrame)){ 93 throw new UnsupportedOperationException( 94 String.format("%s frame types not supported", frame.getClass().getName())); 95 } 96 97 //返回应答消息 text文本帧 98 String request = ((TextWebSocketFrame) frame).text(); 99 //打印日志 100 if(logger.isLoggable(Level.FINE)){ 101 logger.fine(String.format("%s received %s", ctx.channel(), request)); 102 } 103 //发送到客户端websocket 104 ctx.channel().write(new TextWebSocketFrame(request 105 + ", 欢迎使用Netty WebSocket服务, 现在时刻:" 106 + new java.util.Date().toString())); 107 } 108 109 /** 110 * response 111 * @param ctx 112 * @param request 113 * @param response 114 */ 115 private static void sendHttpResponse(ChannelHandlerContext ctx, 116 FullHttpRequest request, FullHttpResponse response){ 117 //返回给客户端 118 if(response.status().code() != HttpResponseStatus.OK.code()){ 119 ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8); 120 response.content().writeBytes(buf); 121 buf.release(); 122 HttpHeaderUtil.setContentLength(response, response.content().readableBytes()); 123 } 124 //如果不是keepalive那么就关闭连接 125 ChannelFuture f = ctx.channel().writeAndFlush(response); 126 if(!HttpHeaderUtil.isKeepAlive(response) 127 || response.status().code() != HttpResponseStatus.OK.code()){ 128 f.addListener(ChannelFutureListener.CLOSE); 129 } 130 } 131 132 /** 133 * 异常 出错 134 */ 135 @Override 136 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 137 throws Exception { 138 cause.printStackTrace(); 139 ctx.close(); 140 } 141 }
WebSocketServer.html (这个随便放都可以,到时候双击打开这个即可,不由服务器提供)
1 <html> 2 <head> 3 <meta charset="utf-8"> 4 <title>Netty websocket 时间服务器</title> 5 </head> 6 <body> 7 <form action="" onsubmit="return false;"> 8 <input type="text" name="message" value="..."/> 9 <br> 10 <input type="button" value="send" value="发送websocket请求消息" onclick="send(this.form.message.value);" /> 11 <hr color="blue"> 12 <h3>服务器返回信息</h3> 13 <textarea id="responseText" rows="10" cols=""></textarea> 14 </form> 15 </body> 16 17 <script type="text/javascript"> 18 var socket; 19 if(!window.WebSocket){ 20 window.WebSocket = window.MozWebSocket; 21 } 22 if(window.WebSocket){ 23 socket = new WebSocket("ws://localhost:7777/websocket"); 24 socket.onmessage = function(event){ 25 var ta = document.getElementById('responseText'); 26 ta.value=""; 27 ta.value=event.data; 28 }; 29 socket.onopen = function(event){ 30 var ta = document.getElementById('responseText'); 31 ta.value = "打开websocket服务正常"; 32 } 33 socket.onclose = function(event){ 34 var ta = document.getElementById('responseText'); 35 ta.value=""; 36 ta.value="websocket关闭"; 37 } 38 }else{ 39 alert("对不起,您的浏览器不支持WebSocket."); 40 } 41 42 function send(message){ 43 if(!window.WebSocket){ 44 return ; 45 } 46 if(socket.readyState == WebSocket.OPEN){ 47 socket.send(message); 48 }else{ 49 alert("WebSocket 连接创建失败."); 50 } 51 } 52 </script> 53 </html>
运行结果
3. websocket实现简单聊天室
WebSocketChatServer.java
1 package com.jieli.nettytest.websocket; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelOption; 6 import io.netty.channel.EventLoopGroup; 7 import io.netty.channel.nio.NioEventLoopGroup; 8 import io.netty.channel.socket.nio.NioServerSocketChannel; 9 10 public class WebsocketChatServer { 11 12 private int port; 13 14 public WebsocketChatServer(int port){ 15 this.port = port; 16 } 17 18 public void run() throws Exception{ 19 EventLoopGroup bossGroup = new NioEventLoopGroup(); 20 EventLoopGroup workerGroup = new NioEventLoopGroup(); 21 try { 22 ServerBootstrap b = new ServerBootstrap(); 23 b.group(bossGroup, workerGroup) 24 .channel(NioServerSocketChannel.class) 25 .childHandler(new WebsocketChatServerInitializer()) 26 .option(ChannelOption.SO_BACKLOG, 128) 27 .childOption(ChannelOption.SO_KEEPALIVE, true); 28 29 System.out.println("websocket start.."); 30 31 ChannelFuture f = b.bind(port).sync(); 32 33 f.channel().closeFuture().sync(); 34 } catch (Exception e) { 35 e.printStackTrace(); 36 } finally { 37 workerGroup.shutdownGracefully(); 38 bossGroup.shutdownGracefully(); 39 System.out.println("websocket close."); 40 } 41 } 42 43 public static void main(String[] args) throws Exception{ 44 new WebsocketChatServer(8080).run(); 45 } 46 }
WebsocketChatServerInitializer.java
1 package com.jieli.nettytest.websocket; 2 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelPipeline; 5 import io.netty.channel.socket.SocketChannel; 6 import io.netty.handler.codec.http.HttpObjectAggregator; 7 import io.netty.handler.codec.http.HttpServerCodec; 8 import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 9 import io.netty.handler.stream.ChunkedWriteHandler; 10 11 public class WebsocketChatServerInitializer extends ChannelInitializer<SocketChannel>{ 12 13 @Override 14 protected void initChannel(SocketChannel ch) throws Exception { 15 ChannelPipeline pipeline = ch.pipeline(); 16 pipeline.addLast(new HttpServerCodec()) 17 .addLast(new HttpObjectAggregator(64*1024)) 18 .addLast(new ChunkedWriteHandler()) 19 .addLast(new HttpRequestHandler("/ws")) //如果访问的是RUI"/ws",处理WebSocket升级 20 .addLast(new WebSocketServerProtocolHandler("/ws")) 21 .addLast(new TextWebSocketFrameHandler()); 22 } 23 24 }
HttpRequestHandler.java
1 package com.jieli.nettytest.websocket; 2 3 import java.io.File; 4 import java.io.RandomAccessFile; 5 import java.net.URL; 6 7 import io.netty.channel.Channel; 8 import io.netty.channel.ChannelFuture; 9 import io.netty.channel.ChannelFutureListener; 10 import io.netty.channel.ChannelHandlerContext; 11 import io.netty.channel.DefaultFileRegion; 12 import io.netty.channel.SimpleChannelInboundHandler; 13 import io.netty.handler.codec.http.DefaultHttpResponse; 14 import io.netty.handler.codec.http.FullHttpRequest; 15 import io.netty.handler.codec.http.FullHttpResponse; 16 import io.netty.handler.codec.http.HttpHeaderNames; 17 import io.netty.handler.codec.http.HttpHeaderUtil; 18 import io.netty.handler.codec.http.HttpHeaderValues; 19 import io.netty.handler.codec.http.HttpResponse; 20 import io.netty.handler.codec.http.HttpResponseStatus; 21 import io.netty.handler.codec.http.HttpVersion; 22 import io.netty.handler.codec.http.LastHttpContent; 23 import io.netty.handler.ssl.SslHandler; 24 import io.netty.handler.stream.ChunkedNioFile; 25 26 public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest>{ 27 //扩展SimpleChannelInboundHandler用于处理FullHttpRequest信息 28 private final String wsuri; 29 // private static final File index; 30 // 31 // static { 32 // URL location = HttpRequestHandler.class 33 // .getProtectionDomain().getCodeSource().getLocation(); 34 // try { 35 // String path = location.toURI() + "html/index.html"; 36 // path = !path.contains("file:") ? path : path.substring(5); 37 // index = new File(path); 38 // } catch (Exception e) { 39 // e.printStackTrace(); 40 // throw new IllegalStateException("can't find index.html"); 41 // } 42 // } 43 44 public HttpRequestHandler(String wsuri){ 45 this.wsuri = wsuri; 46 } 47 48 @Override 49 protected void messageReceived(ChannelHandlerContext ctx, 50 FullHttpRequest request) throws Exception { 51 if(wsuri.equalsIgnoreCase(request.uri())){ 52 //如果请求的是WebSocket升级,将其传递给在ChannelPipeline中的下一个ChannelInboundHandler处理 53 //这里跟第一个例子的websocket协议升级判断方式是不同的 因为只只是判断uri路径而已 54 //对应的js请求路径 socket = new WebSocket("ws://localhost:8080/ws"); 55 ctx.fireChannelRead(request.retain()); 56 }else{ 57 //处理100continue 58 if(HttpHeaderUtil.is100ContinueExpected(request)){ 59 send100Continue(ctx); 60 } 61 //读取默认页 62 RandomAccessFile file = new RandomAccessFile("C:\\html\\index.html", "r"); 63 64 HttpResponse response = new DefaultHttpResponse( 65 request.protocolVersion(), HttpResponseStatus.OK); 66 response.headers().set(HttpHeaderNames.CONTENT_TYPE, 67 "text/html; charset=UTF-8"); 68 69 boolean keepAlive = HttpHeaderUtil.isKeepAlive(request); 70 if(keepAlive){ 71 response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, (int) file.length()); 72 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 73 } 74 ctx.write(response); 75 76 if(ctx.pipeline().get(SslHandler.class) == null){ 77 //如果不是https安全连接的话 要达到最大效率的话,可以通过把index.html直接存储在DefaultFileRegion中 78 //实现零拷贝传输 就是不拷贝到内存,直接读取文件通过文件输出流进行处理 79 ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length())); 80 }else{ 81 //否则要读取全部文件,然后处理加密,在发送,只不过这一切都有netty内部处理 82 //A ChunkedInput that fetches data from a file chunk by chunk using NIO FileChannel. 83 //If your operating system supports zero-copy file transfer such as sendfile(), 84 //you might want to use FileRegion instead. 85 ctx.write(new ChunkedNioFile(file.getChannel())); 86 } 87 ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); 88 89 if(!keepAlive){ 90 future.addListener(ChannelFutureListener.CLOSE); 91 } 92 file.close(); 93 } 94 } 95 96 private static void send100Continue(ChannelHandlerContext ctx){ 97 FullHttpResponse response = (FullHttpResponse) new DefaultHttpResponse( 98 HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); 99 ctx.writeAndFlush(response); 100 } 101 102 @Override 103 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 104 throws Exception { 105 Channel incoming = ctx.channel(); 106 System.out.println("Client:"+incoming.remoteAddress()+"exception."); 107 cause.printStackTrace(); 108 ctx.close(); 109 } 110 }
html/index.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>WebSocket Chat</title> 6 </head> 7 <body> 8 <script type="text/javascript"> 9 var socket; 10 if (!window.WebSocket) { 11 window.WebSocket = window.MozWebSocket; 12 } 13 if (window.WebSocket) { 14 socket = new WebSocket("ws://localhost:8080/ws"); 15 socket.onmessage = function(event) { 16 var ta = document.getElementById('responseText'); 17 ta.value = ta.value + '\n' + event.data 18 }; 19 socket.onopen = function(event) { 20 var ta = document.getElementById('responseText'); 21 ta.value = "连接开启!"; 22 }; 23 socket.onclose = function(event) { 24 var ta = document.getElementById('responseText'); 25 ta.value = ta.value + "连接被关闭"; 26 }; 27 } else { 28 alert("你的浏览器不支持 WebSocket!"); 29 } 30 31 function send(message) { 32 if (!window.WebSocket) { 33 return; 34 } 35 if (socket.readyState == WebSocket.OPEN) { 36 socket.send(message); 37 } else { 38 alert("连接没有开启."); 39 } 40 } 41 </script> 42 <form onsubmit="return false;"> 43 <h3>WebSocket 聊天室:</h3> 44 <textarea id="responseText" style="width: 500px; height: 300px;"></textarea> 45 <br> 46 <input type="text" name="message" style="width: 300px" value="Welcome to localhost"> 47 <input type="button" value="发送消息" onclick="send(this.form.message.value)"> 48 <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录"> 49 </form> 50 <br> 51 Netty SEO 优化 52 Netty 是什么 53 Netty 怎么样 54 Netty4 Netty5 区别 55 Netty 效率 56 Netty 版本区别 57 Netty 和 Mina 58 Netty 网络编程 59 Netty Java 网络编程 60 Netty Java Socket NIO 61 NIO 编程 62 Netty NIO 开发 63 Netty3 Netty4 Netty5 64 Netty 好处 65 Netty 一般注意什么 66 Netty 例子程序 67 Netty Hello World 68 Netty 聊天程序 69 Netty Web HTML HTTP FTP SSL 70 Netty UDP TCP WebSocket 练习 71 Netty 连接数 72 Netty 源码 73 <br> 74 </body> 75 </html>
运行结果,依次运行1,2,3
服务器打印结果
参考资料:
《Netty 权威指南》
https://zh.wikipedia.org/wiki/WebSocket
https://www.zhihu.com/question/20215561
http://waylau.com/netty-websocket-chat/
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |