学习使用netty
netty详细介绍1:https://www.infoq.cn/article/netty-high-performance/#anch111813
netty详细介绍2:https://blog.csdn.net/sun7545526/category_7685695.html
netty使用:https://blog.csdn.net/haoyuyang/article/details/53243785
1. netty简介
Netty 是一个高性能、异步事件驱动的 NIO 框架,它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于 Netty 的 NIO 框架构建。
Netty有很多重要的特性,主要特性如下:
- 优雅的设计
- 统一的API接口,支持多种传输类型,例如OIO,NIO
- 简单而强大的线程模型
- 丰富的文档
- 卓越的性能
- 拥有比原生Java API 更高的性能与更低的延迟
- 基于池化和复用技术,使资源消耗更低
- 安全性
- 完整的SSL/TLS以及StartTLS支持
- 可用于受限环境,如Applet以及OSGI
Netty的以上特性,比较适合客户端数据较大的请求/处理场景,例如web服务器等,要想知道有哪些系统使用了Netty,可以参考:http://netty.io/wiki/adopters.html
2. bio和nio已经netty
BIO:阻塞io:在早期的java中使用:
https://blog.csdn.net/sqlgao22/article/details/102858119
NIO:非阻塞io:在java4引入
https://blog.csdn.net/sqlgao22/article/details/103087676
NIO相比于BIO,该模型有以下特点:
1.使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销
2.当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务.
虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就时Netty上场的时间了。
netty
Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升
1.使用多路复用技术,提高处理连接的并发性
2.零拷贝:Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝,并且Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
3.Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
4. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
5.使用主从Reactor多线程模型,提高并发性
6.采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
7. 默认使用Protobuf的序列化框架
8. 灵活的TCP参数配置
详细说明,可参考: http://www.infoq.com/cn/articles/netty-high-performance#anch111813
3. netty组件
Netty中主要组件包括:
- Channel:代表了一个链接,与EventLoop一起用来参与IO处理。
- ChannelHandler:为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。(主要的内容)
- ChannelPipeline:提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站
和出站事件流的 API。 - EventLoop:Channel处理IO操作,一个EventLoop可以为多个Channel服务。
- EventLoopGroup:会包含多个EventLoop。
1.一个 EventLoopGroup 包含一个或者多个 EventLoop;
2.一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
3.所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
4.一个 Channel 在它的生命周期内只注册于一个 EventLoop,而EventLoop可以处理多个channel数据;
5.一个Channel对应一个ChannelPipeline(管理ChannelHandle)。
6.一个ChannelPipeline中可以注册多个ChannelHandle.
7.一个Channel可以使用多个ChannelHandle.
3.1 Channel
在Java中(Socket类),基本的 I/O 操作(bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的功能。 Netty 的 Channel 接
口所提供的 API降低了直接使用 Socket 类的复杂性。Netty中也提供了使用多种方式连接的Channel:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
3.2 EventLoop&EventLoopGroup
Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端 Channel.EventLoop主要用于处理连接的生命周期中所发生的事件
3.3 ChannelHandler&ChannelHandlerPipline
ChannelHandler
对于开发一个Netty的应用而言,主要开发的组件可能就是 ChannelHandler, 它充当了所有处理入站和出站数据的应用程序逻辑的容器,根据数据流向的不同,ChannelHandler可以分为ChannelInboundHandler以及ChannelOutboundHandler.
如果将两个类别的 ChannelHandler都混合添加到同一个 ChannelPipeline 中时,Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。
对于ChannelHandler的实现类而言,可能不需要关注事件处理周期的每个环节,如果要把Inbound或是Outboud接口的每个方法都实现,就会额外的带来很多的工作量,Netty对于该种情况提供了几种Adapter的解决方案:
– ChannelHandlerAdapter
– ChannelInboundHandlerAdapter
– ChannelOutboundHandlerAdapter
– ChannelDuplexHandler
ChannelPipeline
ChannelPipeline是管理ChannelHandle的容器,由于一个channel可以使用多个ChannelHandle,所以使用ChannelPipeline进行管理.
使用过程:
1.创建一个ChannelInitializer对象.
2.重写ChannelInitializer.initChannel()方法.
3.在ChannelInitializer.initChannel()方法中将自定义的ChannelHandle注册上.
4.将ChannelInitializer对象注册到Bootstrap配置类上.
3.4 解码器/编码器
当通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。
入站消息会被解码:从字节转换为另一种格式,通常是一个 Java 对象。
如果是出站消息,则会发生编码:将从它的当前格式被编码为字节。
Netty默认已经提供了一堆的编/解码器,一般需求已经可以满足,如果不满足可以通继承ByteToMessageDecoder 或 MessageToByteEncoder来实现自己的编/解码器。通过查看类的继承结构可以看出 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。
对于解码器Handler而言,其重写了channelRead方法,对于每个从入站
Channel 读取的消息,这个方法都将会被调用。随后,它将调用由解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。OutboundHandler采用相反的处理方式。
3.5 引导程序ServerBootstrap/Bootstrap
可以理解为netty的配置类.
ServerBootstrap是服务端配置.
Bootstrap是客户端配置.
需要注意的是,引导一个客户端只需要一个 EventLoopGroup(workGroup);
但是一个ServerBootstrap(服务端) 则需要两个(bossGroup和workerGroup).第一个EventLoopGroup用来专门负责绑定到端口监听连接事件,而把第二个EventLoopGroup用来处理每个接收到的连接。对于Server端,如果仅由一个EventLoopGroup处理所有请求和连接的话,在并发量很大的情况下,这个EventLoopGroup有可能会忙于处理已经接收到的连接而不能及时处理新的连接请求,用两个的话,会有专门的线程来处理连接请求,不会导致请求超时的情况,大大提高了并发处理能力.
4. netty使用
4.1 服务端
public class ServerTest {
public static void main(String[] args) {
//用于处理client的连接
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//用于处理每一个连接
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
//创建一个辅助工具类,帮助进行网络的配置
ServerBootstrap bootstrap = new ServerBootstrap();
//注册两个连接,意思是:这两个连接就使用当前的bootstrap的配置
bootstrap.group(bossGroup, workGroup);
//指定连接是nio模式(还有oio,epoll,local等模式)
bootstrap.channel(NioServerSocketChannel.class);
//配置数据具体的处理
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline channelPipeline = socketChannel.pipeline();
//这里一定要配置编码和解码器,否则字符串消息无法识别
//字符串解码器
channelPipeline.addLast(new StringDecoder());
//字符串编码器
channelPipeline.addLast(new StringEncoder());
//配置数据具体处理类ChannelInboundHandlerAdapter
channelPipeline.addLast(new ServerChannelHandel());
}
});
//设置TCP参数(这里有个对参数的解释)
//1.链接缓冲池的大小(ServerSocketChannel的设置)
bootstrap.option(ChannelOption.SO_BACKLOG,1024);
//维持链接的活跃,清除死链接(SocketChannel的设置)
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
//关闭延迟发送
bootstrap.childOption(ChannelOption.TCP_NODELAY,true);
//绑定端口
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务端开始接受请求");
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的关闭
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
class ServerChannelHandel extends ChannelInboundHandlerAdapter {
//服务端读取客户端发来的请求
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String byteBuf = (String) msg;
System.out.println("服务端收到的消息是==" + byteBuf);
ctx.channel().writeAndFlush("收到了消息");
}
//也可重写客户端断开连接channelInActive方法/以及异常处理方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("这里是收到客户端连接后执行的方法----channelActive");
}
}
/**
* 对于ChannelOption.SO_BACKLOG的解释:
* 客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),
* 服务器端接收到客户端发送的SYN时,向客户端发送SYN+ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,
* 最后客户端回复服务器ACK,当服务端接收到后就建立连接(第三次握手),此时TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。
* 也就是说accept从B队列中取出完成了三次握手的连接。A队列和B队列的长度之和就是backlog。
* 当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
* 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。
* 要注意的是,backlog对程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的连接
*/
4.2 客户端
public class ClientTest {
public static void main(String[] args){
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline channelPipeline = socketChannel.pipeline();
channelPipeline.addLast(new StringDecoder());
channelPipeline.addLast(new StringEncoder());
channelPipeline.addLast(new ClientChannelHandel());
}
});
//发起异步连接操作
ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8888)).sync();
//等待客户端链路关闭
futrue.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}
class ClientChannelHandel extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String byteBuf = (String) msg;
System.out.println("服务端回复=="+byteBuf);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("这里是与服务端建立连接后执行的方法(发送数据)----channelActive");
Channel channel = ctx.channel();
channel.writeAndFlush("第一个使用netty发的消息");
}
}
4.3 执行结果
服务端输出:
客户端输出:
5.netty其他
netty的数据传输:https://blog.csdn.net/eric_sunah/article/details/80436184
netty的数据模型:https://blog.csdn.net/eric_sunah/article/details/80437025
netty的数据容器:https://blog.csdn.net/eric_sunah/article/details/80497831