1 什么是netty?
是一个提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
1.1 netty的特点
- 提供易于使用的API,且可实现多种协议方式;
- 并发高:netty是基于NIO(同步非阻塞)的编程框架;
- 传输快,零拷贝:传统从I/O中读取数据到堆中,需要先从I/O流中读取放到缓冲区,再从缓冲区读取到堆内存,需要两次拷贝,若数据量大则造成资源浪费,netty会开辟一个新的堆内存,直接从I/O读取到堆中,实现零拷贝。
1.2 BIO/NIO/AIO
阻塞与非阻塞:是线程访问资源,该资源是否准备就绪的一种方式。
同步和异步:访问数据的一种机制,异步表示未返回处理结果也可以访问一下个内容,同步没有被提醒功能,异步有被提醒功能。
BIO:同步阻塞,会主动观察数据是否准备就绪,一旦准备好则进行处理。传统的BIO是一应一答模式,会频繁创建和销毁线程,造成资源浪费。
NIO:同步非阻塞(Non-Block-IO或者New IO),同样会主动观察数据是否准备就绪,但不会一直原地等待,会同时处理其他数据,并轮询查看该阻塞数据是否准备就绪,准备好了再进行处理。
一个selector是单线程的,会轮询查看客户端是否有请求,若有请求,则创建一个channel,channel直接从buffer读取数据,读完之后还有,不想stream读完就没了。
AIO:异步非阻塞。异步阻塞IO是指,数据阻塞后,不用主动查看数据是否准备完毕,不轮询数据状态,原地等待,不处理其他数据,当前线程挂起,等待该数据准备完毕后通知,再处理。而异步非阻塞IO,不用主动查看数据是否准备完毕,当前线程不挂起,直接处理其他数据,当该阻塞数据准备完毕后主动通知,再进行处理。
- 同步,就是我调用一个功能,该功能没有结束前,我死等结果,无回调,轮询等结果
- 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知),不轮询,等回调
- 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
- 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
2 为什么要用netty?
原生NIO的实现非常复杂,且存在不少Bug,而netty在修复了这些Bug的同时,封装了很多API,易于使用,提高了开发效率,几行代码便可以实现一个通信服务器。
3 netty的线程模型(Reactor线程模型)
- 单线程模型:所有的IO都由同一个NIO线程处理。但在高负载,大并发场景下,一个单线程在性能上无法满足,可能会导致客户端请求超时
- 多线程模型:由一组NIO线程处理IO操作,但当并发非常高时,请求处理能力仍然有限。
- 主从线程模型:一组线程池接受请求(客户端登陆,握手,安全认证等),当整体链路建立完毕后,交付从线程池处理,一组线程池处理IO(编解码,读写等)
3 HelloNetty服务器
- 构建一对主从线程
- 定义服务器启动类
- 为服务器添加channel(设置NIO类型)
- 设置处理从线程池的助手类初始化器:可理解为拦截器,内含很多助手类,即多个拦截器
- 监听启动和关闭服务器
添加pom.xml依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
构建主从线程组、服务器启动类、通道服务器Socket类型和初始化器,绑定端口,并同步等待服务器启动,同时同步监听关闭的channel。
public class HelloNetty {
public static void main(String[] args) throws Exception {
// 定义一个线程组
// 主线程组,用于接受客户端连接,不做处理
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 从线程组,从主线程组接受任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// netty服务器创建,ServerBootstrap 是一个服务器启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup) // 设置主从线程组
.channel(NioServerSocketChannel.class) // 设置nio双向通道
.childHandler(new HelloNettyInitializer()); // 子处理器
// 启动server,设置端口为8088,且为同步方式
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
// 监听关闭的channel,设置同步方式
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
每一个channel由多个handler(助手类,即处理器)共同组成管道(pipeline),可理解成拦截器
创建初始化器,继承ChannelInitializer类,通过SocketChannel获取对应管道,然后向管道中添加助手类(handler),即处理函数。
/**
* @@description 初始化器,channel注册后,会执行里面响应的初始化方法
*/
public class HelloNettyInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 通过socketChannel获取对应的管道
ChannelPipeline pipeline = socketChannel.pipeline();
// 通过管道添加Handler
// HttpServerCodec是由Netty提供的助手类
// 当请求服务器,需要解码,响应到客户端做编码
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 添加自定义助手类, 返回hello netty字符串
pipeline.addLast("customHandler", new CustomHandler());
}
}
创建自定义助手类,继承SimpleChannelInboundHandler类,该类主要用来处理接收数据的一些事件,如解析等,数据类型自定义,本案例处理http请求则选择为HttpObject,ChannelHandlerContext是管道中的channel的上下文对象,每个在pipeline中的ChannelHandler (入栈 或者 出栈),都对应一个ChannelHandlerContext与其绑定,channelRead0用于在缓冲区读取数据。
// SimpleChannelInboundHandler:对于请求来说,相当于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject)
throws Exception {
// 获取channel
Channel channel = channelHandlerContext.channel();
if (httpObject instanceof HttpRequest) {
// 显示客户端远程地址
System.out.println(channel.remoteAddress());
// 定义发送消息
ByteBuf content = Unpooled.copiedBuffer("hello netty~", CharsetUtil.UTF_8);
// 构建http response
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
content);
// 为响应增加数据类型和长度
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
// 把响应刷到客户端
channelHandlerContext.writeAndFlush(response);
}
}
}