netty入门以及主要组件介绍
NIO
NIO优点
- 事件驱动模型
- 避免多线程
- 单线程处理多任务
- 非阻塞IO,IO读写不再阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更加高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了java网络应用的可伸缩性和实用性
netty
有了NIO,为什么还要学习netty
- 并发量大
Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高 - 传输快
Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。我们知道,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会造成不必要的资源浪费。
Netty针对这种情况,使用了NIO中的另一大特性——零拷贝,当他需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。 - 对NIO做了进一步的封装
简化了编程。
netty组件调用关系图
netty主要组件介绍
NIO编程回顾
Netty主要组件
- 1.Transport Channel ---- 对应NIO中的channel
- 2.EventLoop ---- 对应于NIO中的while循环
- EventLoopGroup: 多个EventLoop
- 3.ChannelHandler和ChannelPipeline — 对应于NIO中的客户逻辑实现handleRead/handleWrite (interceptor pattern)
- 4.ByteBuf ---- 对应于NIO 中的ByteBuffer
- 5.Bootstrap 和 ServerBootstrap — 对应NIO中的Selector、ServerSocketChannel等的创建、配置、启动等
Netty Server启动主要流程
- 设置服务端ServerBootStrap启动参数
- group(parentGroup, childGroup):
- channel(NioServerSocketChannel): 设置通道类型
- handler():设置NioServerSocketChannel的ChannelHandlerPipeline
- childHandler(): 设置NioSocketChannel的ChannelHandlerPipeline
- 通过ServerBootStrap的bind方法启动服务端,bind方法会在parentGroup中注册NioServerScoketChannel,监听客户端的连接请求
- 会创建一个NioServerSocketChannel实例,并将其在parentGroup中进行注册
Netty Server执行主要流程
- Client发起连接CONNECT请求,parentGroup中的NioEventLoop不断轮循是否有新的客户端请求,如果有,ACCEPT事件触发。
- ACCEPT事件触发后,parentGroup中NioEventLoop会通过NioServerSocketChannel获取到对应的代表客户端的NioSocketChannel,并将其注册到childGroup中。
- childGroup中的NioEventLoop不断检测自己管理的NioSocketChannel是否有读写事件准备好,如果有的话,调用对应的ChannelHandler进行处理。
Netty Transport-传输层
- 提供了统一的API,支持不同类型的传输层
- OIO-阻塞IO
- NIO-Java nio
- Epoll - Linux Epoll(JNI)
- Local Transport - IntraVM调用
- Embedded Transport - 供测试使用的嵌入传输
- UDS - Unix套接字的本地传输
Netty EventLoop——对应于NIO中的while循环
- ServerBootstrap:包括2个不同类型的EventLoopGroup:
- Parent EventLoop:负责处理Accept事件,接收请求
- Child EventLoop:负责处理读写事件
- EventLoopGroup
- 包括多个EventLoop
- 多个EventLoop之间不交互
- EventLoop:
- 每个EventLoop对应一个线程
- 所有连接(channel)都将注册到一个EventLoop,并且只注册到一个,整个生命周期中都不会变化
- 每个EventLoop管理着多个连接(channel)
- EventLoop来处理连接(Channel)上的读写事件
Netty Buffers——ByteBuf
- 相比JDK ByteBuffer,更加的易于使用
- 为读写分别维护单独的指针,不需要通过flip()进行读写模式切换
- 容量自动伸缩(类似ArrayList,StringBuilder)
- Fluent API(链式调用)
- 更好地性能
- 通过内置的CompositeBuffer来减少数据拷贝(Zero copy)
- 支持内存池,减少GC压力
ByteBuf分配
- 不直接通过new来创建,而是通过ByteBufAllocator来创建
- Unpooled的工具类,它提供了静态的辅助方法来创建未池化的ByteBuf实例
Netty ChannelHandler——对应于NIO中的客户逻辑实现handleRead/handleWrite (interceptor pattern)
ChannelHandler——业务处理核心逻辑,用户自定义
ChannelHandler介绍
Netty 提供2个重要的 ChannelHandler 子接口:
ChannelInboundHandler - 处理进站数据和所有状态更改事件
ChannelOutboundHandler - 处理出站数据,允许拦截各种操作
注意:我们一般继承ChannelInboundHandlerAdapter,就可以不用实现所有的方法,只处理我们需要的方法,其余方法采用默认实现
Netty Channel状态及转换
- Channel的状态及其转换如下图所示:
- 当 ChannelHandler 添加到 ChannelPipeline,或者从ChannelPipeline 移除后,对应的方法将会被调用
Netty ChannelInboundHandler
ChannelInboundHandler 回调方法在下表中:
- 当接收到数据或者与之关联的 Channel 状态改变时调用
- 与 Channel 的生命周期接近
Netty ChannelOutboundHandler
Netty——ChannelPipeline
ChannelPipeline 是ChannelHandler容器
- 包括一系列的ChannelHandler 实例,用于拦截流经一个 Channel 的入站和出站事件
- 每个Channel都有一个其ChannelPipeline
- 可以修改 ChannelPipeline 通过动态添加和删除 ChannelHandler
- 定义了丰富的API调用来回应入站和出站事件
Netty—— ChannelHandlerContext
ChannelHandlerContext表示 ChannelHandler 和ChannelPipeline 之间的关联
在 ChannelHandler 添加到 ChannelPipeline 时创建
netty编程示例一
pom文件中引入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.30.Final</version>
</dependency>
服务器端代码:
package test13;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(
"服务器接收到消息:" + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package test13;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 EchoServer {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
// 主线程组, 用于接受客户端的连接,但是不做任何具体业务处理,像老板一样,负责接待客户,不具体服务客户
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 类ServerBootstrap用于配置Server相关参数,并启动Server
ServerBootstrap b = new ServerBootstrap();
//链式调用
//配置parentGroup和childGroup
b.group(bossGroup, workerGroup)
//配置Server通道
.channel(NioServerSocketChannel.class)
//配置通道的ChannelPipeline
.childHandler(new ChildChannelHandler());
// 绑定端口,并启动server,同时设置启动方式为同步
ChannelFuture f = b.bind(port).sync();
System.out.println(EchoServer.class.getName() +
" 启动成功,在地址[" + f.channel().localAddress() +"]上等待客户请求......");
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new EchoServerHandler());
}
}
}
客户端代码
package test13;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("这是一个Netty示例程序!\n", CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println("客户端接收到消息: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package test13;
import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
public class EchoClient {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new EchoClient().connect(port, "127.0.0.1");
}
public void connect(int port, String host) throws Exception {
// 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 释放线程池资源
group.shutdownGracefully();
}
}
}
运行结果如下
服务器端
客户端:
netty编程示例二
package com.demo.netty.bytebuf;
import io.netty.buffer.*;
import io.netty.util.ByteProcessor;
import java.nio.charset.Charset;
public class ByteBufExamples {
public static void main(String[] args) {
byteBufSetGet();
System.out.println("============");
byteBufWriteRead();
System.out.println("============");
writeAndRead();
System.out.println("============");
byteBufSlice();
System.out.println("============");
byteBufCopy();
System.out.println("============");
byteBufComposite();
System.out.println("============");
directBuffer();
System.out.println("============");
heapBuffer();
System.out.println("============");
}
public static void byteBufSetGet() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
System.out.println((char)buf.getByte(0));
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);
buf.setByte(0, (byte)'B');
System.out.println((char)buf.getByte(0));
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
}
public static void byteBufWriteRead() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
System.out.println((char)buf.readByte());
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);
buf.writeByte((byte)'?');
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
buf.readByte();
System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
}
public static void writeAndRead() {
ByteBuf buffer = Unpooled.buffer(20); //get reference form somewhere
int i = 0;
while (buffer.writableBytes() >= 4) {
buffer.writeInt(i++);
}
while (buffer.isReadable()) {
System.out.println(buffer.readInt());
}
}
public static void byteProcessor() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buffer = Unpooled.copiedBuffer("Netty\r in Action rocks! ", utf8);
int index = buffer.forEachByte(ByteProcessor.FIND_CR);
}
public static void byteBufSlice() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf sliced = buf.slice(0, 15);
System.out.println(sliced.toString(utf8));
buf.setByte(0, (byte)'J');
System.out.println(sliced.toString(utf8));
}
public static void byteBufCopy() {
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
ByteBuf copy = buf.copy(0, 15);
System.out.println(copy.toString(utf8));
buf.setByte(0, (byte)'J');
System.out.println(copy.toString(utf8));
}
public static void byteBufComposite() {
CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
Charset utf8 = Charset.forName("UTF-8");
ByteBuf headerBuf = Unpooled.copiedBuffer("Header", utf8);
ByteBuf bodyBuf = Unpooled.copiedBuffer("This is body", utf8);
messageBuf.addComponents(headerBuf, bodyBuf);
for (ByteBuf buf : messageBuf) {
System.out.println(buf.toString());
}
messageBuf.removeComponent(0); // remove the header
for (ByteBuf buf : messageBuf) {
System.out.println(buf.toString());
}
}
public static void heapBuffer() {
ByteBuf heapBuf = Unpooled.buffer(16);
if (heapBuf.hasArray()) {
int i = 0;
while (heapBuf.writableBytes() > 0) {
heapBuf.writeByte(i++);
}
byte[] array = heapBuf.array();
int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
int length = heapBuf.readableBytes();
handleArray(array, offset, length);
}
}
public static void directBuffer() {
ByteBuf directBuf = Unpooled.directBuffer(16);
if (!directBuf.hasArray()) {
int i = 0;
while (directBuf.writableBytes() > 0) {
directBuf.writeByte(i++);
}
int length = directBuf.readableBytes();
byte[] array = new byte[length];
directBuf.getBytes(directBuf.readerIndex(), array);
handleArray(array, 0, length);
}
}
public static void byteBufCompositeArray() {
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
int length = compBuf.readableBytes();
byte[] array = new byte[length];
compBuf.getBytes(compBuf.readerIndex(), array);
handleArray(array, 0, array.length);
}
private static void handleArray(byte[] array, int offset, int len) {
for(int i = 0; i<len; i++) {
System.out.println(array[offset+i]);
}
}
}