一、ChannelSink用于绑定地址端口
这个东西应该是netty里面最难理解的,或者最关键的组件了,这个我会慢慢的进行分析。在Pipeline传送完后,都必须都通ChannelSink进行处理。Sink默认处理了琐碎的操作,例如连接、读写等等。
ChannelSink这个组件是来处理downstream请求和产生upstream时间的一个组件, 是所有io操作的执行者。也就是传输的逻辑层吧。当channel创建的时候就有一个ChannelSink和它想绑定。
传输层的代码实现一般来说都是比较麻烦的,相比来说客户端的实现一般来说比服务端的实现要简单一些,服务端一般要处理状态变换和数据交换等,我们一点一点来看ChannelSink。
public interface ChannelSink {
/**
* Invoked by {@link ChannelPipeline} when a downstream {@link ChannelEvent}
* has reached its terminal (the head of the pipeline).
*/
void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception;
/**
* Invoked by {@link ChannelPipeline} when an exception was raised while
* one of its {@link ChannelHandler}s process a {@link ChannelEvent}.
*/
void exceptionCaught(ChannelPipeline pipeline, ChannelEvent e, ChannelPipelineException cause) throws Exception;
}
接口里面只定义了两个操作,一个是eventSunk,一个是exceptionCaught,eventSunk 我不知道怎么翻译,姑且理解为让event处理吧,一般来说应该有一个abstract骨架程序实现,我们就来看吧。
AbstractChannelSink里面只实现了exceptionCaught,来看一眼:
public void exceptionCaught(ChannelPipeline pipeline,
ChannelEvent event, ChannelPipelineException cause) throws Exception {
Throwable actualCause = cause.getCause();
if (actualCause == null) {
actualCause = cause;
}
fireExceptionCaught(event.getChannel(), actualCause);
}
这个实际上是向上层报告一个DefaultExceptionEvent的upstream事件。
下来我们就直奔主题来看NioClientSocketPipelineSink这个类吧。
扫描二维码关注公众号,回复:
632854 查看本文章
首先来看局部变量
private static final AtomicInteger nextId = new AtomicInteger();
final int id = nextId.incrementAndGet();
final Executor bossExecutor;
private final Boss[] bosses;
private final NioWorker[] workers;
private final AtomicInteger bossIndex = new AtomicInteger();
private final AtomicInteger workerIndex = new AtomicInteger();
上面是sink的id,这个地方用到了AtomicInteger。还有Boss和NioWorker,boss的个数现在 默认是1,NioWorker的个数是处理器的个数的2倍。后面两个是bossIndex和workerIndex。
在构造函数里面初始化了Boss和NioWorker
NioClientSocketPipelineSink(
Executor bossExecutor, Executor workerExecutor,
int bossCount, int workerCount) {
this.bossExecutor = bossExecutor;
bosses = new Boss[bossCount];
for (int i = 0; i < bosses.length; i ++) {
bosses[i] = new Boss(i + 1);
}
workers = new NioWorker[workerCount];
for (int i = 0; i < workers.length; i ++) {
workers[i] = new NioWorker(id, i + 1, workerExecutor);
}
}
如果是一个网络会话最末端的事件,比如messageRecieve,那么可能在某个handler里面就直接结束整个会话,并把数据交给上层应用,但是如果是网络会话的中途事件,比如connect事件,那么当触发connect事件时,经过pipeline流转,最终会到达挂载pipeline最底下的ChannelSink实例中,这类实例主要作用就是发送请求和接收请求,以及数据的读写操作。
NIO方式ChannelSink一般会有1个BOSS实例(implements Runnable),以及若干个worker实例(不设置默认为cpu cores*2个worker),这在前面已经提起过,BOSS线程在客户端类型的ChannelSink和服务器端类型的ChannelSink触发条件不一样,客户端类型的BOSS线程是在发生connect事件时启动,主要监听connect是否成功,如果成功,将启动一个worker线程,将connected的channel交给这个线程继续下面的工作,而服务器端的BOSS线程是发生在bind事件时启动,它的工作也相对比较简单,对于channel.socket().accept()进来的请求向Nioworker进行工作分配即可。这里需要提到的是,Server端ChannelSink实现比较特别,无论是NioServerSocketPipelineSink 还是OioServerSocketPipelineSink的eventSunk方法实现都将channel分为 ServerSocketChannel和SocketChannel分开处理。这主要原因是Boss线程accept()一个新的连接生成一个 SocketChannel交给Worker进行数据接收。
ChannelPipeline pipeline, ChannelEvent e) throws Exception { |
Channel channel = e.getChannel(); |
if (channel instanceof NioServerSocketChannel) { |
} else if (channel instanceof NioSocketChannel) { |
NioWorker worker = nextWorker(); |
worker.register( new NioAcceptedSocketChannel( |
channel.getFactory(), pipeline, channel, |
NioServerSocketPipelineSink. this , acceptedSocket, |
worker, currentThread), null ); |
另外两者实例化时都会走一遍如下流程:
fireChannelBound( this , getLocalAddress()); |
fireChannelConnected( this , getRemoteAddress()); |
而对应的ChannelSink里面的处理代码就不同于ServerSocketChannel了,因为走的是 handleAcceptedSocket(e)这一块代码,从默认实现代码来说,实例化调用 fireChannelOpen(this);fireChannelBound(this,getLocalAddress());fireChannelConnected(this,getRemoteAddress())没有什么意义,但是对于自己实现的ChannelSink有着特殊意义。具体的用途我没去了解,但是可以让用户插手Server accept连接到准备读写数据这一个过程的处理。
if (Boolean.FALSE.equals(value)) { |
channel.worker.close(channel, future); |
channel.worker.close(channel, future); |
channel.worker.setInterestOps(channel, future, ((Integer) value).intValue()); |