下一篇 :
1. 基本介绍
- 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者。
- Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
- 调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获 取或者通过通知机制获得 IO 操作结果
- Netty 的异步模型是建立在 future 和 callback 的之上的。callback 就是回调。重点说 Future,它的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun返回 显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future去监控方法 fun 的处理过程(即 : Future-Listener 机制)
2. Future 说明
- 表示异步的执行结果, 可以通过它提供的方法来检测执行是否完成,比如检索计算等等.
- ChannelFuture 是一个接口 :
我们可以添加监听器,当监听的事件发生时,就会通知到监听器.
3. 工作原理示意图
- 说明:
- 在使用 Netty 进行编程时,拦截操作和转换出入站数据只需要您提供 callback 或利用 future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。
- Netty 框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来
4. Future-Listener 机制
- 当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。
- 常见有如下操作
• 通过 isDone 方法来判断当前操作是否完成;
• 通过 isSuccess 方法来判断已完成的当前操作是否成功;
• 通过 getCause 方法来获取已完成的当前操作失败的原因;
• 通过 isCancelled 方法来判断已完成的当前操作是否被取消;
• 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知 指定的监听器;如果 Future 对象已完成,则通知指定的监听器
-
代码示例
给一个 ChannelFuture 注册监听器,来监控我们关系的事件
channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()){ System.out.println("监听端口 6668 成功"); }else { System.out.println("监听端口 6668 失败"); } } });
5. 小结
相比传统阻塞 I/O,执行 I/O 操作后线程会被阻塞住, 直到操作完成;
异步处理的好处是不会造成线程阻塞,线程在 I/O 操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量
6. 快速入门实例-HTTP服务
- 要求:
- Netty 服务器在 6668 端口监听,浏览器发出请求 "http://localhost:6668/ "
- 服务器可以回复消息给客户端 "Hello! 我是服务器 5 " , 并 对特定请求资源进行过滤.
- 目的:
Netty 可以做Http服务开发,并且理解Handler实例 和客户端及其请求的关系
- 编写代码 —— 服务端代码
-
编写 服务端 :HttpServer
public class HttpServer { public static void main(String[] args) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(8); try { // 创建 服务端 启动对象,并配置参数 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用自己写的 ServerInitializer 完成初始化 .childHandler(new HttpServerInitializer()); System.out.println("服务器准备好了……"); ChannelFuture channelFuture = serverBootstrap.bind(6660).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
-
编写 服务初始化器 :HttpServerInitializer
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { // 向管道加入处理器 // 得到管道 ChannelPipeline pipeline = socketChannel.pipeline(); // 加入一个 Netty 提供的 httpServerCodec (CoDec => Coder + Decoder => 编解码器) pipeline.addLast("MyHttpServerCodec",new HttpServerCodec()); // 增加一个自己的 Handler pipeline.addLast("MyServerHandler", new HttpServerHandler()); } }
-
编写 服务处理器 :HttpServerHandler
/* 1. SimpleChannelInboundHandler 是之前使用的 ChannelInboundHandlerAdapter 的子类 2. HttpObject 这个类型表示, 客户端、服务端 相互通信的数据需要被封装成什么类型 */ public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> { /** * 读取客户端数据 * @param channelHandlerContext 上下文 * @param httpObject 传递过来的消息 * @throws Exception */ @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception { // 判断 httpObject 是不是一个 HttpRequest 请求 if (httpObject instanceof HttpRequest){ System.out.println("httpObject 的类型 :"+ httpObject.getClass()); System.out.println("客户端的地址 : "+ channelHandlerContext.channel().remoteAddress()); // 回复信息给浏览器,需要把数据封装成 HttpObject 类型 // 创建一个 ButeBuf ByteBuf byteBuf = Unpooled.copiedBuffer("Hello,我是服务器", CharsetUtil.UTF_8); // 构建一个 Http 的响应,即 httpResponse ; 后面的三个参数 :(Http 协议的版本, Http 的状态码, 需要传输的内容) FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); // 设置文本的类型,及字符编码 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8"); // 文本的长度 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); // 将构建好的 response 返回 channelHandlerContext.writeAndFlush(response); } } }
-
启动测试
-
编写代码 —— 对特定资源的过滤
上面的服务端启动后,在页面上不止接收到了文本,还接收到了一个网页的图标
现在把它过滤掉修改 HttpServerHandler
// 获取请求的 URI HttpRequest httpRequest = (HttpRequest) httpObject; URI uri = new URI(httpRequest.uri()); // 判断请求路径为 /favicon.ico,就不做处理 if ("/favicon.ico".equals(uri.getPath())){ System.out.println("请求了 图标 资源,不做响应"); return; }
-
启动测试