文章目录
- 1. 基本介绍
- 2. Future 说明
- 3. 工作原理示意图
- 4. Future-Listener 机制
- 5. 小结
- 6. 快速入门实例-HTTP服务
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;
}
- 启动测试