快速理解Netty
netty的简单介绍
Netty是由Trustin Lee(韩国人 Line公司)开发
- 本质:网络应用程序框架
- 实现:异步、事件驱动
- 特性:高性能、可维护、快速开发
- 应用:客户端可服务器
要想了解netty最好先是去了解BIO、NIO,因为NIO的多线程模式就是很接近netty了。而且现在很多项目都是用netty来做的,因为netty真的是个很好的东西,相同的功能NIO可能需要20行,但是netty确能少很多,而且在github上可以看到netty的bug和JDK里NIO的bug数量比较,就能明白为什么这么多人用netty了。
Netty其实也是支持BIO、NIO、AIO这三种模式的,但是如果用BIO、AIO的话,会有横线就是过时了已经,主要是支持NIO的。
但是为什么只支持NIO了呢?
因为如果用BIO的话,在连接数很多的情况下,是非常消耗资源的。
那为什么又不支持AIO呢?
因为一般项目都是部署在linux上的,但是呢,linux上AIO的实现不是那么完善,虽然说windows上的AIO实现还可,但是很少用windows来做服务器的,而且,在linux下NIO的性能提升比AIO明显,所以netty就支持NIO了。
而且netty对NIO是有多种实现的在不同的系统下。要问为什么,就是netty对自己的实现更加自信。
注意一下,并不是说有了NIO,BIO就不行了,在特殊的情况下 ,比如说你连接数就一点点,那就不用去用NIO,用NIO代码还多,而且用NIO的话需要多多线程和网络知识有一个很扎实的基础,才能写出高质量的程序,而且NIO定位具体的错误比较困难。
Reactor的三种模式:
Reactor 单线程模式;
简单的说就是一个线程既处理连接也处理读写编解码等事件。这样的话会出现很多问题,假如这个线程挂掉的话,那基本也就全都挂了。这个时候就出现了多线程的解决方法。
Reactor 多线程模式;
简单的说就是,一个线程负责连接和简单的读写,然后把耗时的操作放到线程池里去做。
Reactor 主从模式;
简单的说,就是mainReactor只负责连接请求,把IO的读取放到subReactor这个线程里去做,具体的业务处理放到worker线程池里去处理,这个subReactor是可以有好几个的。
工作过程:
1)Reactor对象通过select监控客户端请求事件,收到事件后,通过dispatch进行分发
2)如果建立连接请 求,则右Acceptor通过accept处理连接请求,然后创建一-个Handler对象处理完成连接后的各种事件
3)如果不是连接请求,则由reactor分发调用连接对应的handler来处理
4)handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
5) worker 线程池会分配独立线程完成真正的业务,并将结果返回给handler
6)handler收到响应后,通过send将结果返回给client
Netty的线程模型
netty其实就是基于Reactor主从模型做的一个改善,Reactor变多了。
BossGroup 的执行步骤
1.BossGroup里的每个线程都有一个selector,这个selector会去轮询accept()事件。
2.如果有客户端建立连接,就会生成一个NIOSocketChannel,并将这个通道注册到WorkerGroup的某一个线程的selector上,具体是注册到哪一个线程上的,这个会根据一个算法,在源码里有两种实现方法。
3.处理任务队列的任务
WorkerGroup 的执行步骤
1.轮询read、write事件
2.执行I/O事件,具体的业务逻辑会在pipleline里处理,这个里面会有很多的处理器。
3.处理任务队列里的任务。
具体流程如下图所示:
具体创建服务端案列代码如下:
补充说明一下:
这个EventLoopGroup里的线程数默认是你的CPU的核数的2倍。为什么是两倍呢? 源码 里就是这么定义的。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
但是这里还有一个问题,这种情况下有多少个线程在运行呢?
答案不是6个而是4个 why?
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
EventLoopGroup workerGroup = new NioEventLoopGroup(3);
因为bossgroup里一般只会用到一个线程,如果用多了会有惊群这个问题产生,那问题又来了什么是惊群?
惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应。
public class HelloNetty {
public static void main(String[] args) {
new NettyServer(8888).serverStart();
}
}
class NettyServer {
int port = 8888;
public NettyServer(int port) {
this.port = port;
}
public void serverStart() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new Handler());
}
});
try {
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class Handler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//super.channelRead(ctx, msg);
System.out.println("server: channel read");
ByteBuf buf = (ByteBuf)msg;
System.out.println(buf.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush(msg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端案例代码如下:
public class Client {
public static void main(String[] args) {
new Client().clientStart();
}
private void clientStart() {
EventLoopGroup workers = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workers)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("channel initialized!");
ch.pipeline().addLast(new ClientHandler());
}
});
try {
System.out.println("start to connect...");
ChannelFuture f = b.connect("127.0.0.1", 8888).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workers.shutdownGracefully();
}
}
}
class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel is activated.");
final ChannelFuture f = ctx.writeAndFlush(Unpooled.copiedBuffer("HelloNetty".getBytes()));
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("msg send!");
//ctx.close();
}
});
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf)msg;
System.out.println(buf.toString());
} finally {
ReferenceCountUtil.release(msg);
}
}
}
本人学艺不精,主要是写了用来方便自己记忆的,如果能帮助到其他小伙伴最好,哈哈,不喜勿喷。