Netty
Netty是一个异步的、甚于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端
1.使用
- 服务端
public static void main(String[] args) {
// 服务启动器
new ServerBootstrap()
.group(new NioEventLoopGroup())
// TCP SOCKET通道为NioServerSocketChannel
// UDP DatagramChannel
.channel(NioServerSocketChannel.class)
// 当客户端注册读写事件时 初始化Handler
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
})
.bind(8080);
}
- 客户端
public static void main(String[] args) throws Exception {
// 启动类
new Bootstrap()
// 添加EventLoop
.group(new NioEventLoopGroup())
// 客户端channel实现
.channel(NioSocketChannel.class)
// 处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
/**
* 连接后被调用
* @param ch
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8080))
.sync()
.channel()
.writeAndFlush("Netty");
}
2.EventLoop
EventLoop本质是一个单线程执行器 (同时维护了一个Selector)。里面有run方法处理Channel上源源不断的io事件
它的继承关系比较复杂
- 一条线是继承自j.u.c.ScheduledExecutorService因此包含了线程池中所有的方法
- 另一条线是继承自netty自己的OrderedEventExecutor
-- 提供了boolean InEventloop(Thread thread)方法判断一个钱程是否属于此EventLoop
-- 提供了parent方法来看看自己属于哪个EventoopGroup
事件循环组
EventoopGroup是一组EventLoop, Channel 一般会调用EventLoopGroup的register方法来绑定其中一个EventLoop
后续这个Channel上的io事件都由此EventLoop来处理(保证了io事件处理时的线程安全)
- 继承自netty自己的EventExecutorGroup
-- 实现了Iterable接口提供遍历EventLoop的能力
-- 有next方法获取集合中下一个EventLoop
EventoopGroup里线程循环处理channel
2.1 基本使用
// 可以处理io 普通 定时任务
// 可以指定线程数 默认cpu * 2
EventLoopGroup event = new NioEventLoopGroup();
// 可以处理 普通 定时任务
EventLoopGroup eventLoopGroup1 = new DefaultEventLoop();
// 获取下一个事件
event.next();
// 执行普通任务
event.next().submit(() -> {
System.out.println("123");
});
// 执行定时任务 初始执行时间0s 间隔1s 单位秒
event.next().scheduleAtFixedRate(() -> {
System.out.println("321");
}, 0, 1, TimeUnit.SECONDS);
2.2 分工
new ServerBootstrap()
/**
* 可以划分为boss 和 worker
* boss负责ServerSocketChannel上的accept worker
* worker负责SocketChannel上的读写
*/
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
})
.bind(8080);
2.3 处理长handler
EventLoopGroup group = new DefaultEventLoopGroup();
new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
/**
* 处理handler时间比较长
* @param ch
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(group, "name", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
// 传递给下一个handler
ctx.fireChannelRead(msg);
}
}).addLast(group, "name1", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
})
.bind(8080);
3.channel
channel的主要作用
- close 可以用来关闭channel
- closeFuture 用来处理channel的关闭
-- sync方法作用是同步等待channel关闭
-- 而addListener方法是异步等待channel关闭 - pipeline() 方法添加处理器
- write 方法将数据写入channel缓冲区,但不发送
- flush 立刻发送
- writeAndFlush() 方法将数据写入并刷出
3.1 channelFuture
- 异步问题
public static void main(String[] args) throws Exception {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
/**
* 连接后被调用
* @param ch
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 异步非阻塞 main调用 nio线程连接
.connect(new InetSocketAddress("localhost", 8080));
// 1.阻塞一下 以获取连接到的channel
// channelFuture.sync();
// Channel channel = channelFuture.channel();
// channel.writeAndFlush("123");
// 2.异步处理结果
channelFuture.addListener(new ChannelFutureListener() {
/**
* 在nio线程里连接成功后调用
* @param future
* @throws Exception
*/
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Channel channel = future.channel();
channel.writeAndFlush("123");
}
});
}
- 异步问题
NioEventLoopGroup group = new NioEventLoopGroup();
new Thread( () -> {
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if ("q".equals(line)) {
// 关闭也是异步的 在这里不能处理关闭操作
channel.close();
break;
}
channel.writeAndFlush(line);
}
}, "name").start();
/**
* 处理关闭操作
* 1.同步
* 2.异步
*/
ChannelFuture closeFuture = channel.closeFuture();
closeFuture.sync();
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
group.shutdownGracefully();
}
});
4.Future Promise
在异步处理时,经常用到这两个接口
首先要说明netty中的Future与jdk中的Future同名,但是是两个接口,netty 的Future继承自jdk的Future
而Promise又对netty Future进行了扩展
- jdk Future只能同步等待任务结束(或成功、或失败)才能得到结果
- netty Future可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束
- netty Promise不仅有netty Future的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
4.1 Future
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
EventLoop eventLoop = group.next();
Future<Integer> future = eventLoop.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null;
}
});
// 同步获取结果
future.get();
// 异步
future.addListener(new GenericFutureListener<Future<? super Integer>>() {
@Override
public void operationComplete(Future<? super Integer> future) throws Exception {
future.getNow();
}
});
}
4.2 Promise
public static void main(String[] args) throws Exception {
EventLoop loop = new NioEventLoopGroup().next();
DefaultPromise<Integer> promise = new DefaultPromise<Integer>(loop);
new Thread(() -> {
// 设置结果
promise.setSuccess(100);
}).start();
// 获取结果
promise.get();
}
5.Handler Pipeline
ChannelHandler用来处理Channel上的各种事件,分为入站、出站两种。
所有ChannelHandler被连成一串,就是Pipeline
- 入站处理器通常是ChannellnboundHandlerAdapter的子类,主要用来读取客户端数据,写回结果
- 出站处理器通常是ChannelOutboundHandlerAdapter的子类,主要对写回结果进行加工
打个比喻,每个Channel是一个产品的加工车间,Pipeline 是车间中的流水线
ChannelHandler 就是流水线上的各道工序,ByteBuf是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/**
* 处理器顺序
* head -> h1 -> h2 -> tail
*/
pipeline.addLast("h1", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
String name = buf.toString();
// 传递给下一个数据
super.channelRead(ctx, name);
}
});
pipeline.addLast("h2", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println();
super.channelRead(ctx, msg);
// 向前去找出栈的处理器
ctx.writeAndFlush(msg);
// 向后去找出栈的处理器
ch.writeAndFlush(ctx.alloc().buffer().writeBytes("write".getBytes(StandardCharsets.UTF_8)));
}
});
// 只有写的时候才触发
pipeline.addLast("h3", new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println();
super.write(ctx, msg, promise);
}
});
}
})
.bind(8080);
}
- 快速测试
public static void main(String[] args) {
ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
};
ChannelOutboundHandlerAdapter h2 = new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
super.write(ctx, msg, promise);
}
};
EmbeddedChannel channel = new EmbeddedChannel(h1, h2);
// 入栈
channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello world".getBytes(StandardCharsets.UTF_8)));
// 出栈
channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello world".getBytes(StandardCharsets.UTF_8)));
}
6.ByteBuf
池化的最大意义在于可以重用ByteBuf,优点有
- 没有池化,则每次都得创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加GC压力
- 有了池化,则可以重用池中ByteBuf实例,并且采用了与jemalloc类似的内存分配算法提升分配效率
- 高并发时,池化功能更节约内存,减少内存溢出的可能
池化功能是否开启,可以通过下面的系统环境变量来设置
-Dio. netty. allocator . type= - 4.1以后,非Android平台默认启用池化实现,Android平台启用非池化实现
- 4.1之前,池化功能还不成熟,默认是非池化实现
6. 组成
- 容量
- 最大容量
- 读写指针