時間:2018年04月11日星期三
說明:本文部份內容均來自慕課網。@慕課網:https://www.imooc.com
教學源碼:https://github.com/zccodere/s...
學習源碼:https://github.com/zccodere/s...javascript
什麼是Nettyhtml
Netty使用場景java
課程提綱git
課程要求github
BIO通訊web
BIO通訊模型apache
僞異步IO通訊編程
僞異步IO通訊模型bootstrap
NIO通訊瀏覽器
AIO通訊
四種IO對比
原生NIO的缺陷
Netty的優點
什麼是WebSocket
WebSocket的優勢
WebSocket創建鏈接
WebSocket生命週期
WebSocket關閉鏈接
基於Netty實現WebSocket通訊案例
功能介紹
代碼編寫
1.建立名爲netty-websocket的maven工程pom以下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.myimooc</groupId> <artifactId>netty-websocket</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
2.編寫NettyConfig類
package com.myimooc.netty.websocket; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; /** * <br> * 標題: Netty 全局配置類<br> * 描述: 存儲整個工程的全局配置<br> * * @author zc * @date 2018/04/11 */ public class NettyConfig { /** * 存儲每個客戶端接入進來時的 Channel */ public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); }
3.編寫MyWebSocketHandler類
package com.myimooc.netty.websocket; 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.*; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import java.util.Date; /** * <br> * 標題: 處理客戶端WebSocket請求的核心業務處理類<br> * 描述: 接收/處理/響應 客戶端websocket請求的核心業務處理類<br> * * @author zc * @date 2018/04/11 */ public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket"; /** * 服務端處理客戶端websocket請求的核心方法 */ @Override protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { // 處理客戶端向服務端發起http握手請求的業務 FullHttpRequest request = (FullHttpRequest) msg; this.handHttpRequest(ctx, request); } else if (msg instanceof WebSocketFrame) { // 處理websocket鏈接的業務 WebSocketFrame frame = (WebSocketFrame) msg; this.handWebSocketFrame(ctx, frame); } } /** * 處理客戶端與服務端以前的websocket業務 */ private void handWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { if (frame instanceof CloseWebSocketFrame){ // 若是是關閉websocket的指令 handshaker.close(ctx.channel(),(CloseWebSocketFrame)frame.retain()); } if (frame instanceof PingWebSocketFrame){ // 若是是ping消息 ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return; } if (!(frame instanceof TextWebSocketFrame)){ // 若是不是文本消息,則拋出異常 System.out.println("目前暫不支持二進制消息"); throw new RuntimeException("【"+this.getClass().getName()+"】不支持二進制消息"); } // 獲取客戶端向服務端發送的文本消息 String request = ((TextWebSocketFrame) frame).text(); System.out.println("服務端收到客戶端的消息=====>>>" + request); // 將客戶端發給服務端的消息返回給客戶端 TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + "====>>>" + request); // 羣發,服務端向每一個鏈接上來的客戶端羣發消息 NettyConfig.group.writeAndFlush(tws); } /** * 處理客戶端向服務端發起http握手請求的業務 */ private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { if (!request.getDecoderResult().isSuccess() || !("websocket").equals(request.headers().get("Upgrade"))) { // 不是websocket握手請求時 this.sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); return; } WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false); handshaker = wsFactory.newHandshaker(request); if (handshaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), request); } } /** * 服務端向客戶端響應消息 */ private void sendHttpResponse(ChannelHandlerContext ctc, FullHttpMessage request, DefaultFullHttpResponse response) { if (response.getStatus().code() != 200) { ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8); response.content().writeBytes(buf); buf.release(); } // 服務端向客戶端發送數據 ChannelFuture future = ctc.channel().writeAndFlush(response); if (response.getStatus().code() != 200) { future.addListener(ChannelFutureListener.CLOSE); } } /** * 工程出現異常時調用 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } /** * 客戶端與服務端建立鏈接時調用 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { NettyConfig.group.add(ctx.channel()); System.out.println("客戶端與服務端鏈接開啓..."); } /** * 客戶端與服務端斷開鏈接時調用 */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { NettyConfig.group.remove(ctx.channel()); System.out.println("客戶端與服務端鏈接關閉..."); } /** * 服務端接收客戶端發送過來的數據結束以後調用 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } }
4.編寫MyWebSocketChannelHandler類
package com.myimooc.netty.websocket; import io.netty.channel.ChannelHandler; 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.stream.ChunkedWriteHandler; /** * <br> * 標題: 初始化鏈接時的各個組件<br> * 描述: 初始化鏈接時的各個組件<br> * * @author zc * @date 2018/04/11 */ public class MyWebSocketChannelHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { // 將請求和應答消息解碼爲HTTP消息 ch.pipeline().addLast("http-codec",new HttpServerCodec()); // 將HTTP消息的多個部分合成一條完整的HTTP消息 ch.pipeline().addLast("aggregator",new HttpObjectAggregator(65536)); // 向客戶端發送HTML5文件 ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("handler",new MyWebSocketHandler()); } }
5.編寫AppStart類
package com.myimooc.netty.websocket; 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; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; /** * <br> * 標題: 程序入口<br> * 描述: 啓動應用<br> * * @author zc * @date 2018/04/11 */ public class AppStart { public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.childHandler(new MyWebSocketChannelHandler()); System.out.println("服務端開啓等待客戶端鏈接..."); Channel channel = serverBootstrap.bind(8888).sync().channel(); channel.closeFuture().sync(); }catch (Exception e){ e.printStackTrace(); }finally { // 優雅的退出程序 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
6.編寫websocket.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"/> <title>WebSocket客戶端</title> <script type="text/javascript"> var socket; if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { socket = new WebSocket("ws://localhost:8888/websocket"); socket.onmessage = function (event) { var ta = document.getElementById("responseContent"); ta.value += event.data + "\r\n"; }; socket.onopen = function (event) { var ta = document.getElementById("responseContent"); ta.value = "您當前的瀏覽器支持 WebSocket,請進行後續操做\r\n"; }; socket.onclose = function (event) { var ta = document.getElementById("responseContent"); ta.value = ""; ta.value = "WebSocket 鏈接已近關閉\r\n"; }; } else { alert("您的瀏覽器不支持 WebSocket"); } function send(msg) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(msg); } else { alert("WebSocket鏈接沒有創建成功"); } } </script> </head> <body> <form onSubmit="return false;"> <input type="text" name="msg" value=""/> <br/> <br/> <input type="button" value="發送WebSocket請求消息" onclick="send(this.form.msg.value)"/> <hr color="red"/> <h2>客戶端接收到服務端返回的應答消息</h2> <textarea id="responseContent" style="width: 1024px;height: 300px"></textarea> </form> </body> </html>
課程總結