基本介紹
-
1)異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的組件完成後,通過狀態、通知和回調來通知調用者
-
2)netty中的I/O操作是異步的,包括Bind、Write、Connect等操作會簡單的返回一個ChannelFuture
-
3)調用者不能立刻獲得結果。而是通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得I/O操作
-
4)Netty的異步模型是建立在future和callback的之上的。Callback就是回調。重點說future,他的核心思想是:假設一個方法fun,計算過程可能非常耗時,等待fun返回明顯不太合適。那麼可以在調用fun方法的時候,立馬返回一個future,後續可以通過future去監控方法fun的處理過程(即Future
-Listener)。
Future說明
-
1)表示異步的執行結果,可以通過他提供的方法來檢測執行是否完成,比如檢索計算等等
-
2)ChannelFuture是一個接口:public interface ChannelFuture extends
Future 我們可以添加監聽器,當監聽的事件發生時,會通知。 -
3)工作原理示意圖
-
說明:
在使用Netty進行編程時,攔截操作和轉換出入棧數據只需要您提供,callback或利用future即可。這使得鏈式操作、簡單、高效,並有利於編碼可重用的、通用的代碼 -
4.Future -Listener機制
- 當Future對象剛剛創建時,處於非完成狀態,調用者可以通過返回channelFuture來獲取操作執行的狀態,註冊監聽函數來執行完成後的操作
常見的操作有:
通過isDone方法來判斷當前的操作是否完成
通過isSuccess方法來判斷當前的操作是否成功
通過getCause方法來判斷當前的操作失敗的原因
通過isCancelled方法來判斷已完成的當前操作是否被取消
通過addListener方法來註冊監聽器,當操作已完成(isDone方法返回完成),將會通知指定的監聽器;如果Future對象已完成,則通知指定的監聽器
- 當Future對象剛剛創建時,處於非完成狀態,調用者可以通過返回channelFuture來獲取操作執行的狀態,註冊監聽函數來執行完成後的操作
舉例說明
- 演示:綁定端口是異步操作,當綁定操作處理完成後,將會調用響應的監聽器處理邏輯
//給cf註冊監聽器,監控我們關心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (cf.isSuccess()){
System.out.println("監聽端口6668,成功");
}else{
System.out.println("監聽失敗");
}
}
});
快速入門實列-HTTP服務
- 1)Netty服務器在6668端口監聽,瀏覽器發送請求http://localhost:6668/
- 2)服務器可以回覆消息給客戶端“hello 我是服務器”,並對特定請求資源進行過濾
- 3)目的:Netty可以做HTTP服務開發,並理解handler實例和客戶端及其請求的關係
- 4)代碼如下
package com.dd.netty.http;
import com.dd.netty.simple.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TestServer {
public static void main(String[] args) {
//創建bossGroup 和 WorkerGroup
//bossGroup只處理連接請求,真正的客戶端業務處理,會交給workerGroup完成
//兩個都是無限循環
//bossgroup和workergroup含有的子線程(NIOeventloop)的個數 默認爲cpu核數乘以2
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup woekerGroup = new NioEventLoopGroup();
try {
//創建服務器端啓動的對象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, woekerGroup)//設置兩個線程組
.channel(NioServerSocketChannel.class)//使用NIOsocketChannel實現服務器通道
.option(ChannelOption.SO_BACKLOG, 128)//設置線程隊列得到連接的個數
.childOption(ChannelOption.SO_KEEPALIVE, true)//設置保持活動連接狀態
.childHandler(new TestServerInitializer());//給WorkerGoop的 EventLoop 對應的管道設置處理器
//綁定一個端口並且同步處理,生成一個ChannelFuture,並啓動服務器
ChannelFuture cf = bootstrap.bind(16669).sync();
//給cf註冊監聽器,監控我們關心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (cf.isSuccess()){
System.out.println("監聽端口16669,成功");
}else{
System.out.println("監聽失敗");
}
}
});
//對關閉通道進行監聽
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
woekerGroup.shutdownGracefully();
}
}
}
package com.dd.netty.http;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//向管道加入處理器
//得到管道
ChannelPipeline pipeline = socketChannel.pipeline();
//加入Netty提供的HttpServerCodec codec=>[code decoder]
pipeline.addLast("myHttpServerCodec",new HttpServerCodec());
//增加自定義的handler
pipeline.addLast("myTestHttpServerHandler",new TestHttpServerHandler());
}
}
package com.dd.netty.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
/*
說明:他是ChannelInboundHandlerAdapter的子類
HttpObject表示客戶端和服務器端相互通訊的數據被封裝成HttpObject
*/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/*
當有讀取事件會觸發這個方法 讀取客戶端數據
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判斷msg是不是http請求
if (msg instanceof HttpRequest){
System.out.println("pipleline hashcode"+ctx.pipeline().hashCode()+"testHttpServerHandler hashcode"+this.hashCode());
System.out.println("msg類型"+msg.getClass());
System.out.println("客戶端地址"+ctx.channel().remoteAddress());
//獲取請求,或濾掉網站圖標訪問
HttpRequest httpRequest = (HttpRequest)msg;
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("請求了 favicon.ico ,不做響應");
return;
}
//回覆信息給瀏覽器 [http協議]
ByteBuf content = Unpooled.copiedBuffer("hello,我是服務器",CharsetUtil.UTF_8);
//構造一個http協議,即httpResponse
FullHttpResponse reponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
reponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=UTF-8");
reponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
//將構建好的response返回
ctx.writeAndFlush(reponse);
}
}
}
- 5)發現訪問一次地址,會發送兩次請求
//獲取請求,或濾掉網站圖標訪問
HttpRequest httpRequest = (HttpRequest)msg;
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("請求了 favicon.ico ,不做響應");
return;
}
- 注意:每個瀏覽器請求該地址,都會分別生成自己對應的piple,handler。由於爲HTTP協議,它不是長連接,刷新瀏覽器會產生新的piple,handler。