什么是netty?
Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。是对JDK的NIO的封装。
这里的异步不是指异步非阻塞IO,而是指使用多线程将方法的调用与结果的返回分离开来,一个线程进行方法的调用,另一个线程进行结果的返回。
小试牛刀,用netty编写hello world
编写服务端
public class HelloServer {
public static void main(String[] args) {
//ServerBootstrap 服务启动器,负责组装netty组件,协调工作
new ServerBootstrap()
// BossEventLoop(selector,tread) ,workerEventLoop(selector,tread):事件循环处理器,包含一个selector和一个线程
//通过selector去监听IO事件(连接、读取,写入等),同时采取多线程的方式,一个线程对应一个selector
// NioEventLoopGroup:NIO的事件循环处理组,可能包含多个BossEventLoop和workerEventLoop .group(new NioEventLoopGroup())
.group(new NioEventLoopGroup())
//选择ServerSocketChannel的实现
.channel(NioServerSocketChannel.class)//支持BIO、NIO
//boss负责处理连接 worker(child)负责处理读写
//childHandler决定了将来的worker(child)能执行哪些操作(handler)
.childHandler(
//Channel;代表和客户端进行数据读写的通道 Initializer:初始化,负责添加handler
new ChannelInitializer<NioSocketChannel>() { //在连接之后调用
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//添加handler
ch.pipeline().addLast(new StringDecoder());//对字节流ByteBuf转为字符串
//自定义handler
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override //处理读事件
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);;
}
});
}
//服务器绑定的启动端口
}).bind(8888);
}
}
编写客户端
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
//1.添加启动器
new Bootstrap()
// 2. 添加 EventLoop, 包含线程 和 选择器
.group(new NioEventLoopGroup())
// 3. 选择客户端 channel 实现
.channel(NioSocketChannel.class)
// 4. 添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//5.对ByteBuf进行编码
ch.pipeline().addLast(new StringEncoder());
}
})
// 6. 连接 服务器
.connect(new InetSocketAddress("192.168.176.1", 8888))
.sync()
.channel()
// 7.向服务器 发送信息
.writeAndFlush("hello,world");
}
}
代码执行流程
组件
1. NioEventLoop 事件循环对象
EventLoop 本质上是一个单线程的执行器(同时维护一个Selector来实现多路复用),当有事件发生时,通过调用内置的run方法来处理源源不断的IO事件
它的继承关系比较复杂
- 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法
- 另一条线是继承自 netty 自己的 OrderedEventExecutor,可以有序的处理事件
- 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop
- 提供了 parent 方法来看看自己属于哪个 EventLoopGroup
EventLoopGroup 事件循环组
EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)
- 继承自 netty 自己的 EventExecutorGroup
- 实现了 Iterable 接口提供遍历 EventLoop 的能力
- 另有 next 方法获取集合中下一个 EventLoop
EventLoopGroup 与EventLoop的使用
EventLoopGroup是一个接口,它的最强大的实现子类为NioEventLoopGroup,NioEventLoopGroup可以处理IO事件,普通事件、定时任务
NioEventLoopGroup的构造函数
EventLoopGroup eventLoopGroup1=new NioEventLoopGroup();
/**
* 对于无参构造函数new NioEventLoopGroup(),它会去调用this(0)方法,经过一系列的方法调用,最终会
* 调用MultithreadEventLoopGroup方法,由于它没有传递参数,所以它会通过DEFAULT_EVENT_LOOP_THREADS
* 来设置线程数,DEFAULT_EVENT_LOOP_THREADS会通过SystemPropertyUtil工具获取系统核心线程数,然后在NioEventLoopGroup
* 内创建出2 * NettyRuntime.availableProcessors()的 EventLoop循环事件
*public NioEventLoopGroup() {
* this(0);
* }
* protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
* super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
* }
* private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
*/
EventLoopGroup eventLoopGroup2=new NioEventLoopGroup(2);
/**
* 有参构造函数new NioEventLoopGroup(2);会创建2个EventLoop循环对象
*/
获取EventLoop对象的方法next
EventLoopGroup eventLoopGroup2=new NioEventLoopGroup(2);
System.out.println(eventLoopGroup2.next());
System.out.println(eventLoopGroup2.next());
System.out.println(eventLoopGroup2.next());
System.out.println(eventLoopGroup2.next());
只设置了2个EventLoop,经过4次next获取,他回去轮询获取EventLoop对象
EventLoop执行普通任务
EventLoopGroup eventLoopGroup2=new NioEventLoopGroup(2);
EventLoop eventLoop = eventLoopGroup2.next();
eventLoop.submit(()->{
log.debug(Thread.currentThread().getName()+"--->hello world");
});
log.debug("main");
EventLoop执行定时任务
EventLoopGroup group=new NioEventLoopGroup(2);
group.next().scheduleAtFixedRate(()->{
log.debug(Thread.currentThread().getName()+"-->Hello world");
},0,1,TimeUnit.SECONDS);
scheduleAtFixedRate(按固定的时间倍率刷新):0表示推迟0秒执行,1表示每隔一秒执行一次
结果
EventLoop执行IO任务
服务端
public class HelloServer {
public static void main(String[] args) {
//ServerBootstrap 服务启动器,负责组装netty组件,协调工作
new ServerBootstrap()
// BossEventLoop(selector,tread) ,workerEventLoop(selector,tread):事件循环处理器,包含一个selector和一个线程
//通过selector去监听IO事件(连接、读取,写入等),同时采取多线程的方式,一个线程对应一个selector
// NioEventLoopGroup:NIO的事件循环处理组,可能包含多个BossEventLoop和workerEventLoop .group(new NioEventLoopGroup())
.group(new NioEventLoopGroup())
//选择ServerSocketChannel的实现
.channel(NioServerSocketChannel.class)//支持BIO、NIO
//boss负责处理连接 worker(child)负责处理读写
//childHandler决定了将来的worker(child)能执行哪些操作(handler)
.childHandler(
//Channel;代表和客户端进行数据读写的通道 Initializer:初始化,负责添加handler
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//添加handler
ch.pipeline().addLast(new StringDecoder());//对字节流ByteBuf转为字符串
//自定义handler
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override //处理读事件
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);;
}
});
}
//服务器绑定的启动端口
}).bind(8888);
}
}
客户端
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
//1.添加启动器
new Bootstrap()
// 2. 添加 EventLoop, 包含线程 和 选择器
.group(new NioEventLoopGroup())
// 3. 选择客户端 channel 实现
.channel(NioSocketChannel.class)
// 4. 添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//5.对ByteBuf进行编码
ch.pipeline().addLast(new StringEncoder());
}
})
// 6. 连接 服务器
.connect(new InetSocketAddress("192.168.176.1", 8888))
.sync()
.channel()
// 7.向服务器 发送信息
.writeAndFlush("hello,world");
}
}
对EventLoop的分工细化
BOSS负责连接,Worker负责处理读写事件
new ServerBootstrap()
//第一个NioEventLoopGroup是BOSS负责连接,第二个是Worker负责处理读写
.group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
..........
当某个线程的 Channel事件处理时间过长,那么该线程关注的其他通道的事件就无法处理,可以将处理事件长的Channel事件交给其他的EventLoopGroup的EventLoop对象去处理。
DefaultEventLoopGroup defaultGroup = new DefaultEventLoopGroup();
//在addLast时指定处理数据的EventLoopGroup,"handler"是组的名字
ch.pipeline().addLast(defaultGroup, "handler", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug((String) msg);
}
});
完整代码
@Slf4j
public class HelloServer {
public static void main(String[] args) {
DefaultEventLoopGroup defaultGroup = new DefaultEventLoopGroup();
new ServerBootstrap()
//BOSS 负责连接 worker负责处理读写事件
.group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
//指定其他的EventLoopGroup来处理此次Read事件
ch.pipeline().addLast(defaultGroup, "handler", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug((String) msg);
}
});
}
})
.bind(8888);
}
}
2. Channel 通道
channel 的主要作用
- close() 可以用来关闭 channel
- closeFuture() 用来处理 channel 的关闭后的善后处理
- sync 方法作用是同步等待 channel 关闭
- 而 addListener 方法是异步等待 channel 关闭
- pipeline() 方法添加处理器
- write() 方法将数据写入
- writeAndFlush() 方法将数据写入并刷出
ChannelFuture的连接问题
我们来看代码
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8888));
channelFuture.sync();
Channel channel = channelFuture.channel();
channel .writeAndFlush("hello,world");
}
}
为什么要调用channelFuture.sync()方法?
原因是connect连接是异步非阻塞的,调用connect的是main线程,真正执行连接的是nio线程,调用sync()方法,在还没有完成connect前将main线程阻塞,只有当nio线程完成connect时才会将main线程放行,确保客户端与服务端之间的Cannel建立连接
ChannelFuture对结果的处理
- sync();阻塞main线程,把结果交给main来处理,属于同步阻塞处理
- addListener();放行main线程,把结果的处理交给nio线程,当nio线程connect完成后,nio线程会处理结果并返回
代码实例
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8888));
// 异步非阻塞调用
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Channel channel = future.channel();
log.debug("{}",channel);
channel .writeAndFlush("hello,world");
}
});
}
}
执行结果
ChannelFuture对关闭之后的善后处理
当我们的channle通道关闭之后,我们可能需要进行通道关闭之后的善后处理,包括但不限于对资源的释放等,这时哦我们就需要用到ChannelFuture对象
案例
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8888));
Channel channel = channelFuture.sync().channel();
log.debug("{}",channel);
new Thread(()->{
channel.writeAndFlush("hello world");
channel.close();//异步调用,由nio线程来执行关闭操作
log.debug("close之后善后处理");
},"MyThread").start();
}
}
我们想要在关闭channel之后进行善后操作,如果直接进行善后操作这是不行的,因为channel.close();的关闭操作不是由MyThread线程执行,它是由nio线程来异步执行,而且关闭channel耗费的时间比较久,在关闭channel成功之前Mythread线程可能会先执行==log.debug(“close之后善后处理”);==操作,所以我们需要通过ChannelFuture来实现close之后的操作
ChannelFuture的方法
1.sync();未完成channel关闭之前阻塞其他线程
2.addListener(处理对象);由nio来执行关闭之后的善后处理操作,属于异步调用
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException{
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8888));
Channel channel = channelFuture.sync().channel();
log.debug("{}",channel);
new Thread(()->{
channel.writeAndFlush("hello world");
channel.close();
},"MyThread").start();
ChannelFuture closeFuture = channel.closeFuture();
//方法一
closeFuture.sync();//在nio未完成关闭channel之前,阻塞main线程
log.debug("close之后的善后处理......");
//方法二
// closeFuture.addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) throws Exception {
// log.debug("close之后的善后处理......");
// }
// });
}
3. Future 与 Promise
当线程调用结束后会将结果返回给Future或Peomise,这两个类可以通过get方法获取返回的结果
EventLoop eventLoop = new NioEventLoopGroup().next();
Future<Integer> future = eventLoop.submit(() -> {
return 70;
});
System.out.println(future.get());
对于Future的创建和往Future里面注入返回结果都是不可见的,Future是被动的创建与结果注入,而 Promise可以主动创建对象和注入返回结果
public static void main(String[] args) throws Exception {
EventLoop eventLoop = new NioEventLoopGroup().next();
//主动创建对象
DefaultPromise<Integer> promise = new DefaultPromise<>(eventLoop);
new Thread(()->{
try {
log.debug("hello world");
//主动注入结果
promise.setSuccess(100);
} catch (Exception e) {
e.printStackTrace();
//主动注入结果
promise.setFailure(e);
}
}).start();
System.out.println(promise.get());
}
4 Handler & Pipeline
ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline
入站的意思是客户端往服务端发送数据,服务器对数据的接收处理就是入站
出站的意思是服务端往客户端发送数据,服务端对返回数据进行加工处理就是出战
- 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
- 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工
入站与出站的执行顺序
在往pipline加入handler的时候,会形成一条双向链表head<->h1<->h2<->tail,每个被添加的handler都会加入到链表的末尾,采取的是尾插法
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(....);
}
入战的执行顺序是从前往后,出战的执行顺序是从后往前
服务端
@Slf4j
public class HelloServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast("h1",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(1);
super.channelRead(ctx, msg);
}
});
ch.pipeline().addLast("h2",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(2);
super.channelRead(ctx, msg);
//写入数据
ch.writeAndFlush(ctx.alloc().buffer().writeBytes("".getBytes(StandardCharsets.UTF_8)));
}
});
ch.pipeline().addLast("h3",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(3);
super.write(ctx, msg, promise);
}
});
ch.pipeline().addLast("h4",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(4);
super.write(ctx, msg, promise);
}
});
}
})
.bind(8080);
}
}
1和2表示的是ChannelInboundHandlerAdapter的执行顺序,4和3表示的是ChannelOutboundHandlerAdapter 的执行顺序
5 .pipline是流水线,由多个handler组成,每个Handler将自己加工完的信息传递给下一个Handler,那产品是怎样传递的呢?
ChannelInboundHandlerAdapter的产品传递
ch.pipeline().addLast("h1",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取信息
ByteBuf byteBuf=(ByteBuf)msg;
String name = byteBuf.toString(Charset.defaultCharset());
log.debug(name);
//将信息传递给h2处理器
super.channelRead(ctx, name);
/**super.channelRead(ctx, name);的底层实现,调用fireChannelRead方法将信息传递给下一个Handler
* @Skip
* public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
* ctx.fireChannelRead(msg);
* }
*/
}
});
ch.pipeline().addLast("h2",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String name = msg.toString();
log.debug(name+" java");
}
});
关于ChannelOutboundHandlerAdapter处理器的触发顺序
当我们调用Channel对象的write()方法时会从双链表的tail从前往后去触发出站Handler,如果我们调用ChannelHandlerContext 的write()方法,会从当前的Handler从前往后去寻找出战Handler执行,因为Context对象属于当前Handler的上下文
ch.pipeline().addLast("h2",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(2);
super.channelRead(ctx, msg);
//写入数据
ctx.channel().write(msg);
ch.writeAndFlush(ctx.alloc().buffer().writeBytes("".getBytes(StandardCharsets.UTF_8)));
ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("".getBytes(StandardCharsets.UTF_8)));
}
});
ByteBuf 对字节数据的封装
在使用JDK的NIO时,我们创建了ByteBuffer来缓存数据,但是这个ByteBuffer是个定长数组,不可动态扩容,而netty提供的ByteBuf 是功能更为强大的缓冲区组件,可以实现数组的动态扩容
// ByteBuf的创建
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();//默认容量大小为256
ByteBuf的组成
通过读写指针,不用在通过compat或clear来切换读写模式,同时ByteBuf还支持动态扩容
ByteBuf之slice
【零拷贝】的体现之一,对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针,使用的还是统一个ByteBuf
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
buf.writeBytes(new byte[]{'a','b','c','d','e','f','g','h','i','j'});
log(buf);
// 切片,没有发生数据复制
ByteBuf f1 = buf.slice(0,5); //startIdx length
ByteBuf f2 = buf.slice(5,5);
log(f1);
log(f2);
//在f1切片对数据进行修改,原来的ByteBuf也发生了修改,所以他们是同一个ByteBuf
f1.setByte(0,'Z');
log(buf);
结果展示
ByteBuf之Composite
【零拷贝】的体现之一,可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免拷贝
ByteBuf f1 = ByteBufAllocator.DEFAULT.buffer();
f1.writeBytes(new byte[]{1,2,3,4,5});
ByteBuf f2 = ByteBufAllocator.DEFAULT.buffer();
f2.writeBytes(new byte[]{6,7,8,9,10});
//composite
CompositeByteBuf compositeBuffer = ByteBufAllocator.DEFAULT.compositeBuffer();
CompositeByteBuf buffer = compositeBuffer.addComponents(f1, f2);
log(buffer);
EmbeddedChannel 测试工具类
Netty 提供了它所谓的 Embedded 传输,用于测试 ChannelHandler。这个传输是一种特殊的 Channel 实现— EmbeddedChannel— 的功能,这个实现提供了通过 ChannelPipeline 传播事件的简便方法。
测试代码
@Test
public void test2()
{
//定义入站处理器
ChannelInboundHandlerAdapter in = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf=(ByteBuf)msg;
String s = byteBuf.toString(Charset.defaultCharset());
System.out.println(s);
}
};
//定义出站处理器
ChannelOutboundHandlerAdapter out = new ChannelOutboundHandlerAdapter() {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf byteBuf=(ByteBuf)msg;
String s = byteBuf.toString(Charset.defaultCharset());
System.out.println(s);
}
};
EmbeddedChannel embeddedChannel=new EmbeddedChannel(in,out);
//测试入站handler
embeddedChannel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("入站处理".getBytes()));
//测试出站handler
embeddedChannel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("出站处理".getBytes()));
}