Netty编程
NIO编程
关于NIO相关的文章网上也有很多,这里不打算详细深入分析,下面简单描述一下NIO是如何解决BIO的线程资源受限,线程切换效率低下,以字节为单位三个问题的。
1. 解决线程资源受限
NIO编程模型中,新来一个连接不再创建一个新的线程,而是可以把这条连接直接绑定到某个固定的线程,然后这条连接所有的读写都由这个线程来负责,那么他是怎么做到的?我们用一幅图来对比一下IO与NIO
如上图所示,IO模型中,一个连接来了,会创建一个线程,对应一个while死循环,死循环的目的就是不断监测这条连接上是否有数据可以读,大多数情况下,1w个连接里面同一时刻只有少量的连接有数据可读,因此,很多个while死循环都白白浪费掉了,因为读不出啥数据。
而在NIO模型中,他把这么多while死循环变成一个死循环,这个死循环由一个线程控制,那么他又是如何做到一个线程,一个while死循环就能监测1w个连接是否有数据可读的呢? 这就是NIO模型中selector的作用,一条连接来了之后,现在不创建一个while死循环去监听是否有数据可读了,而是直接把这条连接注册到selector上,然后,通过检查这个selector,就可以批量监测出有数据可读的连接,进而读取数据,下面我再举个非常简单的生活中的例子说明IO与NIO的区别。
在一家幼儿园里,小朋友有上厕所的需求,小朋友都太小以至于你要问他要不要上厕所,他才会告诉你。幼儿园一共有100个小朋友,有两种方案可以解决小朋友上厕所的问题:
- 每个小朋友配一个老师。每个老师隔段时间询问小朋友是否要上厕所,如果要上,就领他去厕所,100个小朋友就需要100个老师来询问,并且每个小朋友上厕所的时候都需要一个老师领着他去上,这就是IO模型,一个连接对应一个线程。
- 所有的小朋友都配同一个老师。这个老师隔段时间询问所有的小朋友是否有人要上厕所,然后每一时刻把所有要上厕所的小朋友批量领到厕所,这就是NIO模型,所有小朋友都注册到同一个老师,对应的就是所有的连接都注册到一个线程,然后批量轮询。
2. 解决线程切换效率低下
由于NIO模型中线程数量大大降低,线程切换效率因此也大幅度提高
3. 解决IO读写以字节为单位
NIO解决这个问题的方式是数据读写不再以字节为单位,而是以字节块为单位。IO模型中,每次都是从操作系统底层一个字节一个字节地读取数据,而NIO维护一个缓冲区,每次可以从这个缓冲区里面读取一块的数据, 这就好比一盘美味的豆子放在你面前,你用筷子一个个夹(每次一个),肯定不如要勺子挖着吃(每次一批)效率来得高。
简单讲完了JDK NIO的解决方案之后,我们接下来使用NIO的方案替换掉IO的方案,我们先来看看,如果用JDK原生的NIO来实现服务端,该怎么做。
Netty编程
1.netty简介
用一句简单的话来说就是:Netty封装了JDK的NIO,让你用得更爽,你不用再写一大堆复杂的代码了。 用官方正式的话来说就是:Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
下面是我总结的使用Netty不使用JDK原生NIO的原因
- 使用JDK自带的NIO需要了解太多的概念,编程复杂,一不小心bug横飞
- Netty底层IO模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从NIO模型变身为IO模型
- Netty自带的拆包解包,异常检测等机制让你从NIO的繁重细节中脱离出来,让你只需要关心业务逻辑
- Netty解决了JDK的很多包括空轮询在内的bug
- Netty底层对线程,selector做了很多细小的优化,精心设计的reactor线程模型做到非常高效的并发处理
- 自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
- Netty社区活跃,遇到问题随时邮件列表或者issue
- Netty已经历各大rpc框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大
2.netty的使用
- 首先引入maven依赖
也可以 fail ->project Structure -> Modules ->dependencies 右侧的+号
Library ->new Library -> from maven 输入 io.netty:netty-all
-
服务端的实现:
NettyServer.java
package java_netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建BossGroup 和WokerGroup
//说明:
//1.创建两个线程组 bossgroup和workergroup
//2.BossGroup 只是处理连接请求
//3.wokergroup 真正和客户端业务处理
//4.两个都是无限循环
//5. bossGroup, 和 workerGroup 含有的子线程(NioEventLoop)的个数
// 默认实际( cpu核数 * 2)
EventLoopGroup bossGroup=new NioEventLoopGroup(1);
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程进行设置
bootstrap.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//使用NioSocketChannel 作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
//创建一个通道测试对象(匿名类)
//给pipeline 设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());//给管道的最后添加一个处理器(即写的NettyServerHandler)
}
});// 给我们的workergroup 的EventLoopGroup 对应的管道设置处理器
System.out.println("服务器 is ready");
//绑定一个端口,并且同步处理,生成了一个ChannelFuture对象。
//相当于启动服务器并把端口端口
ChannelFuture channelFuture = bootstrap.bind(6668).sync();
//对关闭通道进行监听(当有关闭操作的时候会进行监听
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();//优雅的关闭
workerGroup.shutdownGracefully();//优雅的关闭
}
}
}
-
boos
对应了IOServer.java
中的接收新连接线程,主要负责创建新连接 -
worker
对应IOClient.java
中的负责读取数据的线程,主要用于读取数据以及业务逻辑处理<!--说明:--> <!--1.创建两个线程组 bossgroup和workergroup--> <!--2.`BossGroup` 只是处理连接请求--> <!--3.`wokergroup` 真正和客户端业务处理--> <!--4.两个都是无限循环--> <!--5. bossGroup`, 和 workerGroup 含有的子线程(NioEventLoop)的个数--> <!--( cpu核数 * 2)-->
-
服务端的handler处理器
NettyServerHandler.java
package java_netty.simple;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/*
说明:
1.自定义一个Handler 需要继续netty, 规定好某个HandlerAdapter
2.这时的Handler 才能算一个Hander
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据实际,&#x