一、前言
1.什麼是netty?
高性能,事件驅動,異步非堵塞 基於nio的客戶端,服務端編程框架(nio的框架) 穩定性和伸縮性
2.netty的使用場景。
高性能領域 多線程併發領域 異步通信領域
3.學習目錄
io通信 netty入門 websocket入門 netty實現websocket通信案例
二.java io通信
客戶端個數:bio(1:1) 僞異步io(m:n) nio(m:1) aio(m:0) io類型:bio(阻塞同步io) 僞異步io(阻塞同步io) nio(非阻塞同步io) aio(非阻塞異步io) api使用難度:bio(簡單) 僞異步io(簡單) nio(複雜) aio(複雜,但比nio簡單) 調試難度:同上 可靠性:bio(差) 僞異步io(差) nio(好) aio(好) 吞吐量:同上
三.netty入門
1.原生nio的缺陷。類庫和api複雜;入門門檻高;工作量和難度大;jdk nio存在bug 2.netty的優勢。api簡單;入門門檻低;性能高;成熟、穩定。
四.websocket入門
1.什麼是websocket? h5協議規範;握手機制;解決客戶端與服務端實時通信而產生的技術。 2.websocket的優點? 節省通信開銷;服務器主動傳送數據給客戶端;實時通信; 3.websocket建立連接。 客戶端發起握手請求;服務端響應請求;連接建立。 4.websocket生命週期。 打開事件;消息事件;錯誤事件;關閉事件。 5.websocket關閉連接。 服務端關閉底層tcp連接;客戶端發起tcp close。
五.netty實現websocket通信案例。
1.功能介紹
netty開發服務端;html實現客戶端;實現服務端和客戶端的實時交互。
2.代碼實現
2.1存儲工廠的全局配置
package com.websocket.netty; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; /** * @author lilinshen * @title 存儲工廠的全局配置 * @description 請填寫相關描述 * @date 2018/5/23 10:32 */ public class NettyConfig { /** * 儲存每一個客戶端進來時的channel對象 */ public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); }
2.2處理/接收/響應客戶端websocket請求的核心業務處理類
package com.websocket.netty; 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.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.websocketx.*; import io.netty.util.CharsetUtil; import java.util.Date; /** * @author lilinshen * @title 處理/接收/響應客戶端websocket請求的核心業務處理類 * @description 請填寫相關描述 * @date 2018/5/23 10:36 */ public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket"; /** * 客戶端與服務端連接的時候調用 * * @param ctx * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { NettyConfig.channelGroup.add(ctx.channel()); System.out.println("客戶端與服務端連接開啓..."); } /** * 客戶端與服務端斷開連接的時候調用 * * @param ctx * @throws Exception */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { NettyConfig.channelGroup.remove(ctx.channel()); System.out.println("客戶端與服務端連接關閉..."); } /** * 服務端接收客戶端發送過來的數據結束之後調用 * * @param ctx * @throws Exception */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } /** * 工程出現異常的時候調用 * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } /** * 服務端處理客戶端websocket請求的核心方法 * * @param channelHandlerContext * @param o * @throws Exception */ @Override protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object o) throws Exception { if (o instanceof FullHttpRequest) { // 處理客戶端向服務端發起http握手請求的業務 handHttpRequest(channelHandlerContext, (FullHttpRequest) o); } else if (o instanceof WebSocketFrame) { // 處理websocket連接業務 handWebsocketFrame(channelHandlerContext, (WebSocketFrame) o); } } /** * 處理客戶端向服務端發起http握手請求的業務 * * @param ctx * @param request */ private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { if (!request.getDecoderResult().isSuccess() || !("websocket").equals(request.headers().get("Upgrade"))) { 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 (null == handshaker) { WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); } else { handshaker.handshake(ctx.channel(), request); } } /** * 服務端向客戶端響應消息 * * @param ctx * @param request * @param response */ private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest 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 channelFuture = ctx.channel().writeAndFlush(response); if (response.getStatus().code() != 200) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } /** * 處理客戶端與服務端之間的websocket業務 * * @param ctx * @param frame */ private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // 判斷是否是關閉websocket的指令 if (frame instanceof CloseWebSocketFrame) { handshaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain()); } // 判斷是否是ping的消息 if (frame instanceof PingWebSocketFrame) { 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.channelGroup.writeAndFlush(tws); } }
3.初始化連接時的各個組件
package com.websocket.netty; 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; /** * @author lilinshen * @title 初始化連接時的各個組件 * @description 請填寫相關描述 * @date 2018/5/23 11:12 */ public class MyWebSocketChannelHander extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast("http-codec", new HttpServerCodec()); socketChannel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); socketChannel.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); socketChannel.pipeline().addLast("handler", new MyWebSocketHandler()); } }
4.程序的入口,負責啓動應用
package com.websocket.netty;
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;
/**
* @author lilinshen
* @title 程序的入口,負責啓動應用
* @description 請填寫相關描述
* @date 2018/5/23 11:17
*/
public class Main {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new MyWebSocketChannelHander());
System.out.println("服務端開啓等待客戶端連接...");
Channel channel = bootstrap.bind(8888).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 優雅的退出程序
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
5.websocket.html客戶端代碼。
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <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 (ev) { var ta = document.getElementById("responseContent"); ta.value += ev.data + "\r\n"; } socket.onopen = function (ev) { var ta = document.getElementById("responseContent"); ta.value += "您當前的瀏覽器支持websocket,請進行後續操作\r\n"; } socket.onclose = function (ev) { var ta = document.getElementById("responseContent"); ta.value = ""; ta.value = "websocket連接已經關閉\r\n"; } } else { alert("您的瀏覽器不支持websocket"); } function send(message) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("websocket連接沒有建立成功"); } } </script> </head> <body> <form onsubmit="return false;"> <input type="text" name="message" value=""/> <br/><br/> <input onclick="send(this.form.message.value)" type="button" value="發送websocket請求消息"/> <hr color="red"/> <h2>客戶端接收到服務端返回的應答消息</h2> <textarea id="responseContent" style="width:1024px;height:300px;"></textarea> </form> </body> </html>
6.啓動。
1.main.java類是程序的入口,負責啓動應用。
2.將websocket.html在瀏覽器中打開,就可以建立一個websocket連接。