netty介绍
网络通讯框架 mina、netty
应用案例:阿里RPC框架:Dubbo
netty官网:https://netty.io/
netty特点:
1、是高性能、异步事件驱动模型,他提供对TCP、UDP、HTTP和文件传输的支持
2、使用更加高效的socket底层,对epoll空轮训引起CPU飙升问题在netty内部进行处理,避免的直接使用NIO的陷阱
3、采用了多种编码、解码的支持、对TCP的粘包、分包做了自动化处理
netty组件
Bootstrap:
netty的启动辅助类,是客户端和服务端的入口,Bootstrap是建客户端连接的启动器,ServerBootStrap是监听服务端端口的启动器。
Channel:
常用的是NioServerSocketChannel和NioSocketChannel
NioServerSocketChannel负责监听一个tcp的端口,有连接时会获取一个NioSocketChannel的连接实例
NioSocketChannel负责读写事件,连接服务端
EventLoop:
核心组件之一
通过EventLoopGroup(是在启动辅助类中设置)生成EventLoop,内部是一个无限循环,维护了一个selector。处理所有的注册到selector上的IO操作,在这里就维护了一个线程连接多个连接的工作
ChannelPipeline:
核心组件之一:channelHandler的容器,netty中的IO操作的通道channel,与channelHandler组成责任链,将所有的读时间、写事件、连接事件依次通过channelPipeline,处理事件
ChannelHandler
是IO事件处理的真正单元、可以自定义的channelhandler的定义来处理自己的逻辑,完全控制处理方式
channelhandler和ChannelPipeline组成的责任链
channelHandler分为inbound和outbound,对应的IO的read和write的执行链
执行链底层实现是将channelHandler组装成双向链表,提供head和tail属性
read操作的话从channelPipeline的head到tail的过程
netty编程
多客户端和服务端通信:echo命令
引入netty依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.10.Final</version>
</dependency>
服务端
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup boss = null;
NioEventLoopGroup worker = null;
try{
//需要eventLoopggrop
/**
* NioEventLoopGroup是用来处理IO操作以及接收客户端连接的事件循环组
* 给定两个事件循环组
* 一个称之为boss,用来接收连接
* 一个称之为worker,用来处理已经接受的连接
*/
boss = new NioEventLoopGroup(1);
worker = new NioEventLoopGroup();//默认是CPU个数乘以2
/**
* 启动辅助类:ServerBootSttap
* 将配置信息配置在辅助类上,用来启动或者设置相关属性:TCP
* */
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
//指定事件循环组
.group(boss,worker)
//主事件循环组接收的通道的实例类型
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//存放channelHandler的容器
ChannelPipeline pipeline = ch.pipeline();
//字符串解码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//自定义channelHandler
pipeline.addLast(new ServerHandler());
}
});
//启动服务端,通过同步阻塞方式来启动服务端,即调用sync会阻塞直至服务端绑定端口后才返回
ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
System.out.println("服务器启动了");
//使用同步方法来管理程序
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程资源
if(boss!=null){
boss.shutdownGracefully();
}
if(worker!=null){
worker.shutdownGracefully();
}
}
}
}
/**
* 读取数据的提供了接口:ChannelInboundHandler
*/
class ServerHandler extends SimpleChannelInboundHandler<String> {
//接收数据的发方法
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
System.out.println("channelRead0"+msg);
}
//连接成功触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+"连接服务端成功");
}
//断开连接触发给方法
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+" 断开和服务端连接");
}
//接收数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(ctx.channel().remoteAddress()+":"+msg);
ctx.channel().writeAndFlush("echo:" + msg);
}
}
客户端
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//创建事件循环组
NioEventLoopGroup loopGroup = new NioEventLoopGroup();
//创建启动辅助类
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(loopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//获取pipeline的实例
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//自定义channelHandler 接收服务端返回的消息
pipeline.addLast(new ClientHandler());
}
});
//客户端连接服务端 通过同步方式来保证连接成功
Channel channel = bootstrap.connect("127.0.0.1", 9999).sync().channel();
//给服务端通信发送消息
Scanner scanner = new Scanner(System.in);
scanner.useDelimiter("\n");
while (true) {
String msg = scanner.nextLine();
if ("exit".equals(msg)) break;
//发送消息给服务端
channel.writeAndFlush(msg);
}
channel.closeFuture().sync();
}
}
class ClientHandler extends SimpleChannelInboundHandler<String>{
//接收消息
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
}
Selector Bug问题
epoll特定情况下会发生空轮训,CPU使用100%?
NioEventLoop类下的run()方法
解决方案:对Selector的select操作周期进行统计:selectCnt,每完成一次空的select对selectCnt加1,变量selectCnt会随着连续的空轮训会逐渐变大,到达了阈值(默认是512),则执行rebuildSelector()进行重新构建
会新建一个selector实例,将旧的selector实例上所有的注册的有效事件全部重新注册到新的selector实例上,会将旧的selector调用close方法关闭掉
Bootstrap介绍
serverBootstrap
//指定事件循环组
.group(boss, worker)
//主事件循环组接收的通道的实例类型
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_SNDBUF, 1024) //发送缓冲区
.childOption(ChannelOption.AUTO_READ, true)
.attr(AttributeKey.valueOf("key"),"1023") //自定义的参数设置
.childAttr(AttributeKey.valueOf("key1"),"1024") //附带数据
.childHandler(new ChannelInitializer <NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
//存放channelHandler的容器
ChannelPipeline pipeline = ch.pipeline();
//字符串解码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//自定义channelHandler
pipeline.addLast(new Server212Handler());
}
})