1. 瞭解WebSocket知識
略
2. websocket實現系統簡單反饋時間javascript
WebSocketServerHandler.javahtml
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.javajava
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 (這個隨便放均可以,到時候雙擊打開這個便可,不禁服務器提供)web
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實現簡單聊天室bootstrap
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.htmlwebsocket
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/