为什么要使用Netty?
(NIO的框架,用于解决高并发出现的问题)
*BIO:同步且阻塞的IO
NIO:同步且非阻塞的IO(不是说线程)
AIO:异步且非阻塞的IO
还没有实现业务,光写整个流程就非常繁琐。NIO除了实现起来复杂之外,还存在一些需要解决的棘手问题,比如客户端断线重连如何实现,心跳处理(客户端在一定的时间内,不断的向服务器发送信息告诉服务器还在)、半包读写处理等等一些列问题,此时需要有这么一个框架,用于解决和优化NIO存在的问题,它就是Netty。
目的:客户端越来越多,随着客户端的增多,代码的复杂程度就变高,netty帮我们降低了编写nio的代码复杂程度*
Netty初体验
第一步:引入依赖
<dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.35.Final</version> </dependency> </dependencies>
服务器端
public class NettyServer { public static void main(String[] args) throws Exception { //创建只处理连接请求的线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(10); //创建只处理客户端读写业务的线程组 EventLoopGroup workGroup = new NioEventLoopGroup(10); //创建服务端启动对象 ServerBootstrap bootstrap = new ServerBootstrap(); //配置参数 bootstrap.group(bossGroup,workGroup) //使用NioServerSocketChannel作为服务器的通道实现 .channel(NioServerSocketChannel.class) //配置用于存放因没有空闲线程导致连接请求被暂存放到队列中的队列长度 .option(ChannelOption.SO_BACKLOG,1024) //创建通道初始化的对象并配置该对象,向该对象中添加处理器来实现具体的业务 .childHandler(new ChannelInitializer<SocketChannel>() { //初始化通道 @Override protected void initChannel(SocketChannel ch) throws Exception { //添加处理器,处理器里面是真正处理业务的 ch.pipeline().addLast(new NettyServerHandler()); } });//配置group System.out.println("Netty服务器启动了"); //同步阻塞地启动服务器 ChannelFuture channelFuture = bootstrap.bind(9090).sync(); //只要服务没关闭,该方法会一直阻塞 channelFuture.channel().closeFuture().sync(); System.out.println("================a"); bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }
public class NettyServerHandler extends ChannelInboundHandlerAdapter { //当有客户端发送数据来的时候该方法就会被调用 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; System.out.println("客户端发送的数据:"+buf.toString(StandardCharsets.UTF_8)); } //读完数据之后调用的方法:发送数据给客户端 @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //创建携带的ByteBuf对象 ByteBuf buf = Unpooled.copiedBuffer("hello client".getBytes(StandardCharsets.UTF_8)); ctx.writeAndFlush(buf); } //异常捕获 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println(cause.getMessage()); ctx.close(); } }
客户端
public class NettyClient { public static void main(String[] args) throws Exception { //创建一个线程组用于事件循环 EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); //创建客户端启动对象 Bootstrap bootstrap = new Bootstrap(); //设置相关参数 bootstrap.group(eventLoopGroup) //使用NioSocketChannel作为客户端的通道实现 .channel(NioSocketChannel.class) //创建通道初始化对象并设置handler业务处理器 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //添加处理器,处理器里面是实现具体业务的 ch.pipeline().addLast(new NettyClientHandler()); } }); System.out.println("Netty客户端启动了"); //告知客户端的服务器的地址,并启动客户端 ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",9090).sync(); channelFuture.channel().closeFuture().sync(); //阻塞等待完成操作后关闭通道 eventLoopGroup.shutdownGracefully(); } }
public class NettyClientHandler extends ChannelInboundHandlerAdapter { //当客户端完成连接服务器后调用该方法 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf buf = Unpooled.copiedBuffer("hello server".getBytes(StandardCharsets.UTF_8)); ctx.writeAndFlush(buf); } //当通道有读事件发生时调用的方法:读取服务器返回的数据 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; System.out.println("来自服务器"+ctx.channel().remoteAddress()+"的消息"+buf.toString(StandardCharsets.UTF_8)); } }
基础概念
NIO中实现多路复用的核心类是Selector,当多路复用器Selector调用select方法时,将会查找发生事件的
channel,问题是,该如何在多个注册到selector上的channel中找到哪些channel发生了事件,此时NIO不同的版本有不同的做法。
epoll函数
poll函数
select函数
Reactor模型
不同的线程决定了程序的性能,多线程是为了充分利用CPU
传统的阻塞IO模型
客户端连接服务端之后,会等客户端输入完之后,最后响应给客户端,才能紧接着为其它的客户端执行任务,因为等待会耗费大量时间,所以并没有把CPU用到极致
基础Reactor模型
可以执行完连接之后,让别的客户端来拿来处理自己的任务,各个操作之间是独立的,充分利用服务端之间的性能(可能是连接的,也可以是读的,也可以是写的)
多线程Reactor模型