Netty是一个高性能、高可扩展性的异步事件驱动的网络应用程序框架,它极大地简化了TCP和UDP客户端和服务器开发等网络编程。
Netty四个重要内容:
Netty线程模型:
为了让NIO处理更好的利用多线程特性,Netty实现了Reactor线程模型。
Reactor模型中有四个核心概念:
EventLoopGroup初始化过程:
构造函数-》确定线程数量:默认cous*2-》new Executor:构建线程执行器->for->newChild():构建EventLoop-》new EvenrExecutorChooser
其代码如下:
MultithreadEventExecutorGroup->
this.children[i] = this.newChild((Executor)executor, args);-> NioEventLoopGroup->protected EventLoop newChild(Executor executor, Object… args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider)args[0], ((SelectStrategyFactory)args[1]).newSelectStrategy(), (RejectedExecutionHandler)args[2]);
}
// 创建EventLoopGroup accept线程组 NioEventLoop
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
进入会发现其继承了Executor方法
其Executor方法时由SingleThreadEventExecutor实现
EventLoop是包含了nio和Reactor
EventLoop的启动
EventLoop自身实现了Executor接口,当调用executor方法提交任务时,则判断是否启动,未启动则调用内置的executor创建新线程来触发run方法执行。
通过: ChannelFuture f = b.bind(PORT).sync();方法
Bind绑定端口过程:
bind(端口):AbstactBootstap->创建和初始化Channel->注册到EventLoop的Selector上->提交任务到EventLoop执行。注册完成后再继续绑定->doBind()->channl.bind->pipeline.bind->HeadContext.bind->AbstarctUsafe.bind->NioServerSocketChannel.doBind
其代码如下:
在AbstactBootStrap类中
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();// : 创建/初始化ServerSocketChannel对象,并注册到Selector
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// : 等注册完成之后,再绑定端口。 防止端口开放了,却不能处理请求
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);// : 实际操作绑定端口的代码
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// : (一开始初始化的group)MultithreadEventLoopGroup里面选择一个eventLoop进行绑定
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);// 调用channel中unsafe对象的注册方法(AbstractUnsafe)
return promise;
}
到AbstarctChannel的register方法执行 eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
执行SingleEventExecutor类的 execute(方法添加任务对列
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 判断execute方法的调用者是不是EventLoop同一个线程
boolean inEventLoop = inEventLoop();
addTask(task);// 增加到任务队列
if (!inEventLoop) {// : 不是同一个线程,则调用启动方法
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
// The task queue does not support removal so the best thing we can do is to just move on and
// hope we will be able to pick-up the task before its completely terminated.
// In worst case we will log on termination.
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
Channel概念:
netty中的Channel是一个抽象的概念,可以理解为对JDKNIO Channel的增强和扩展。增加了很多属性和方法,完整信息可以看代码注释,下面罗列几个常见的属性和方法。
设计模式-责任链模式
责任链模式为请求创建了一个处理对象的链
发起请求和具体处理请求的过程进行解耦:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递。
实现责任链模式
实现责任链模式4个要素:处理器抽象类、具体的处理器实现类、保存处理器信息、处理执行。netty还有用链表形头部 next式
Netty中的ChannelPipeline责任链
Pipeline管道保存了通道所有处理器信息。创建新Channel时自动创建一个专有的pipeline。入站事件和出栈操作会调用pipeline的处理器:
入站事件和出站事件
入站事件:通常指i/o线程生成了入站数据。
(通俗理解:从socket底层自己往上冒上来的事件都是入站)比如EventLoop收到selector的op_read事件,入站处理器调用socektChannel.Read(ByteBuffer)接收到数据后,这将导致通道的ChannelPipeline中包含的下一个中的channelRead方法被调用。
出站事件:经常是指i/o线程执行实际的输出操作。
(通俗理解:想主动往socket底层操作的事件都是出站)比如bind方法用意时请求server socket绑定到给定的SocketAddress,这将导致通道的ChannelPipeline中包含的下一个出站处理器中的bind方法被调用。
下面时netty的事件定义:
read入站事件的处理:
用户在管道中有一个或多个channelhandler来接收i/o事件(例如读取)和请求i/o操作(例如写入和关闭)
一个典型的服务器哎每个通道的管道中都有以下处理程序,但是根据协议和业务逻辑的复杂性和特征,可能会有所不同:
协议编码器–将二进制数据(例如ByteBuf)转换为java对象。
协议编码器–将java对象转换为二进制数据。
业务逻辑处理程序–执行事件的业务逻辑(例如数据库访问)。
Pipeline中的handler是什么
channelHandler:用于处理i/o事件或拦截i/o操作,并转发到ChannelPipeline中国的下一个处理器。这个顶级接口定义功能很弱,实际使用时会去实现下面两大子接口:
处理入站i/o事件的ChannelinboundHandler处理出站i/o操作的ChannelOutboundHandler
适配器类:为了方便,避免所有handler去实现一遍接口方法,Netty提供了简单的实现类:
ChannelInboundHandlerAdapter处理入站i/o事件
ChannelOutboundHandlerAdapter处理出站i/o事件
ChannelDuolexHandler来支持同时处理入站和出站事件
ChannelHandlerContext:实际存储在pipeline中的对象并非ChannelHandler,而时上下文对象。
将handler,包裹在上下文对象中,通过上下文对象与它所属的ChannelPipeline交互,向上或向下传递事件或者修改pipeline都是通过上下文对象。
ChannePipeline是线程安全的,ChannelHandler可以在任何时候添加或删除。例如:你可以在即将交换敏感信息时插入加密处理程序,并在交换后删除它。一般操作,初始化的时候增加进去,较少删除。下面是Pipeline管理handler的api
复用handler
handler处理过程中线程安全问题
1、减少内存占用
2、加快连接建立的速度
业务耗时操作采取独立的线程运行
大型数据报的相依、要多次write、让i/o更均匀处理多个连接的响应数据包。
Bytebuf对象复用机制
用完bytebuf要记得release。上线之前自己简单测试一些
handler继承SimpleChannelinboundHandler可以简单的规章。