netty 笔记
1,NioEventLoopGroup
/***
* NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
* Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。 在这个例子中我们实现了一个服务端的应用,
* 因此会有2个NioEventLoopGroup会被使用。 第一个经常被叫做‘boss’,用来接收进来的连接。
* 第二个经常被叫做‘worker’,用来处理已经被接收的连接, 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
* 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
* 并且可以通过构造函数来配置他们的关系。
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
在NioEventLoopGroup初始化的过程中,其实就是初始化了一堆可执行的Executor数组,然后根据某种chooser策略,来选择下一个可用的executor。
1、NioEventLoopGroup初始化时未指定线程数,那么会使用默认线程数,即 线程数 = CPU核心数 * 2;
2、每个NioEventLoopGroup对象内部都有一组可执行的NioEventLoop(NioEventLoop对象内部包含的excutor对象为ThreadPerTaskExecutor类型)
3、每个NioEventLoopGroup对象都有一个NioEventLoop选择器与之对应,其会根据NioEventLoop的个数,动态选择chooser(如果是2的幂次方,则按位运算,否则使用普通的轮询)
所以通过上面的分析,我们得出NioEventLoopGroup主要功能就是为了选择NioEventLoop,而真正的重点就在NioEventLoop中,它是整个netty线程执行的关键。
2,Reactor模式
Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。
Reactor模式实现
在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式,这个Logging Server的实现完全遵循这里对Reactor描述,因而放在这里以做参考。Logging Server中的Reactor模式实现分两个部分:Client连接到Logging Server和Client向Logging Server写Log。因而对它的描述分成这两个步骤。
Client连接到Logging Server
-
Logging Server注册LoggingAcceptor到InitiationDispatcher。
-
Logging Server调用InitiationDispatcher的handle_events()方法启动。
-
InitiationDispatcher内部调用select()方法(Synchronous Event Demultiplexer),阻塞等待Client连接。
-
Client连接到Logging Server。
-
InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的连接到来。
-
LoggingAcceptor调用accept方法accept这个新连接。
-
LoggingAcceptor创建新的LoggingHandler。
-
新的LoggingHandler注册到InitiationDispatcher中(同时也注册到Synchonous Event Demultiplexer中),等待Client发起写log请求。
Client向Logging Server写Log
1.Client发送log到Logging server。
2. InitiationDispatcher监测到相应的Handle中有事件发生,返回阻塞等待,根据返回的Handle找到LoggingHandler,并回调LoggingHandler中的handle_event()方法。
3. LoggingHandler中的handle_event()方法中读取Handle中的log信息。
4. 将接收到的log写入到日志文件、数据库等设备中。3.4步骤循环直到当前日志处理完成。
5. 返回到InitiationDispatcher等待下一次日志写请求。
Reactor模式的优点
1,关注点分离:Reactor模式将与应用程序无关的解复用和调度机制与特定于应用程序的钩子方法功能分离。独立于应用程序的机制成为可重用的组件,它们知道如何解复用事件并分派由事件处理程序定义的适当的钩子方法。相反,hook方法中特定于应用程序的功能知道如何执行特定类型的服务。
2,改进事件驱动应用程序的模块化,可重用性和可配置性:该模式将应用程序功能分离到单独的类中。例如,日志记录服务器中有两个单独的类:一个用于建立连接,另一个用于接收和处理日志记录。这种解耦使得能够为不同类型的面向连接的服务(例如文件传输,远程登录和视频点播)重用连接建立类。因此,修改或扩展日志记录服务器的功能只会影响日志记录处理程序类的实现。
3,提高应用程序的可移植性:Initiation Dispatcher的接口可以独立于执行事件多路分解的OS系统调用重用。这些系统调用检测并报告可能在多个事件源上同时发生的一个或多个事件的发生。常见的事件源可能包括I / O句柄,计时器和同步对象。在UNIX平台上,事件多路分解系统调用称为select和poll [1]。在Win32 API [16]中,WaitForMultipleObjects系统调用执行事件多路分解。
4,提供粗粒度并发控制:Reactor模式在事件多路分解和在进程或线程内调度的级别上序列化事件处理程序的调用。 Initiation Dispatcher级别的序列化通常消除了在应用程序进程中进行更复杂的同步或锁定的需要。
Reactor模式的缺点
1,相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
2,Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
3,Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。
3,ServerBootstrap
1、服务端由两种线程池,用于Acceptor的React主线程和用于I/O操作的React从线程池; 客户端只有用于连接及IO操作的React的主线程池;
2、ServerBootstrap中定义了服务端React的"从线程池"对应的相关配置,都是以child开头的属性。 而用于"主线程池"channel的属性都定义在AbstractBootstrap中;
1、 首先看看服务端的b.group(bossGroup, workerGroup):
调用ServerBootstrap的group方法,设置react模式的主线程池 以及 IO 操作线程池,ServerBootstrap中的group代码如下:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}
在group方法中,会继续调用父类的group方法,而通过类继承图我们知道,super.group(parentGroup)其实调用的就是AbstractBootstrap的group方法。AbstractBootstrap中group代码如下:
public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
}
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return self();
}
通过以上分析,我们知道了AbstractBootstrap中定义了主线程池group的引用,而子线程池childGroup的引用是定义在ServerBootstrap中。
当我们查看客户端Bootstrap的group方法时,我们发现,其是直接调用的父类AbstractBoostrap的group方法。
//穿插介绍泛型类
//定义
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
//尖括号中,你传进去的是什么,T就代表什么类型。这就是泛型的最大作用,我们只需要考虑逻辑实现,就能拿给各种类来用。
//多泛型变量定义
class MorePoint<T,U> {
private T x;
private T y;
private U name;
public void setX(T x) {
this.x = x;
}
public T getX() {
return this.x;
}
…………
public void setName(U name){
this.name = name;
}
public U getName() {
return this.name;
}
}
//使用
MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();
morePoint.setName("harvic");
//泛型函数定义及使用
public class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
//静态方法 推荐方法二
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
//常规方法 推荐方法二
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
//返回值中存在泛型
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
服务端代码
package cmm.netty;/*
* @Package
* @author yaq.c@sunyard.com
* @date 2018\12\10 0010 11:23
* @version 1.0
* Copyright (c) 2018 SUNYARD Inc. All rights reserved.
*/
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
public class DiscardServer {
private int port;
public DiscardServer(int port) {
super();
this.port = port;
}
public void run() throws Exception {
/***
* NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
* Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。 在这个例子中我们实现了一个服务端的应用,
* 因此会有2个NioEventLoopGroup会被使用。 第一个经常被叫做‘boss’,用来接收进来的连接。
* 第二个经常被叫做‘worker’,用来处理已经被接收的连接, 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
* 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
* 并且可以通过构造函数来配置他们的关系。
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("准备运行端口:" + port);
try {
/**
* ServerBootstrap 是一个启动NIO服务的辅助启动类 你可以在这个服务中直接使用Channel
*/
ServerBootstrap b = new ServerBootstrap();
/**
* 这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException: group not
* set异常
*/
b = b.group(bossGroup, workerGroup);
/***
* ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
* 这里告诉Channel如何获取新的连接.
*/
b = b.channel(NioServerSocketChannel.class);
/***
* 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。 ChannelInitializer是一个特殊的处理类,
* 他的目的是帮助使用者配置一个新的Channel。
* 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
* 或者其对应的ChannelPipeline来实现你的网络程序。 当你的程序变的复杂时,可能你会增加更多的处理类到pipline上,
* 然后提取这些匿名类到最顶层的类上。
*/
b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());// demo1.discard
// ch.pipeline().addLast(new
// ResponseServerHandler());//demo2.echo
// ch.pipeline().addLast(new
// TimeServerHandler());//demo3.time
}
});
/***
* 你可以设置这里指定的通道实现的配置参数。 我们正在写一个TCP/IP的服务端,
* 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
* 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
*/
b = b.option(ChannelOption.SO_BACKLOG, 128);
/***
* option()是提供给NioServerSocketChannel用来接收进来的连接。
* childOption()是提供给由父管道ServerChannel接收到的连接,
* 在这个例子中也是NioServerSocketChannel。
*/
b = b.childOption(ChannelOption.SO_KEEPALIVE, true);
/***
* 绑定端口并启动去接收进来的连接
*/
ChannelFuture f = b.bind(port).sync();
/**
* 这里会一直等待,直到socket被关闭
*/
f.channel().closeFuture().sync();
} finally {
/***
* 关闭
*/
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
//将规则跑起来
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8023;
}
new DiscardServer(port).run();
System.out.println("server:run()");
}
}