来自:http://fbi.taobao.org/?p=64
看的主要是Netty的3.X版本,先贴一段Netty初始化代码
- // ChannelFactory中主要是线程资源
- ClientBootstrap bootstrap = new ClientBootstrap(
- new NioClientSocketChannelFactory(
- Executors.newCachedThreadPool(),
- Executors.newCachedThreadPool()));
- // 设置业务处理Pipeline>Handler
- bootstrap.setPipelineFactory(new TelnetClientPipelineFactory());
- // Start the connection attempt.
- ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
- // 释放资源
- bootstrap.releaseExternalResources();
Pipeline就是这个Channel注册的管道,里面表示每一个Handler,是一种链式结构
SocketChannel就是NIO包中进行socket操作的通道
work的选择通过轮转的方式,Client端的bossExecuter主要是用来关注CONNECT事件的(这一部分的处理有很多重复的代码,在Netty4中统一了些)
Worker
netty中真正干底层脏活累活的都是work类,他的结构如下:
WorkPool表示了一个缓冲池,这也限制了使用的线程数,下面是WorkPool的初始化代码
- AbstractNioWorkerPool(Executor workerExecutor, int workerCount, boolean allowShutDownOnIdle) {
- if (workerExecutor == null) {
- throw new NullPointerException(“workerExecutor”);
- }
- if (workerCount <= 0) {
- throw new IllegalArgumentException(
- “workerCount (“ + workerCount + “) ” +
- “must be a positive integer.”);
- }
- //创建指定数量的work数组,实例是NioWork,每个Work持有的资源见上图
- //默认是Runtime.getRuntime().availableProcessors() * 2个Work
- workers = new AbstractNioWorker[workerCount];
- for (int i = 0; i < workers.length; i++) {
- workers[i] = createWorker(workerExecutor,
- allowShutDownOnIdle);
- }
- this.workerExecutor = workerExecutor;
- }
可以看到会创建指定个数的work,这里的每个work其实表示的是Selector和Thread一种组合,每次创建一个连接,都会选择一个work,并且注册到wokr里的Selector,work会进行loop,不断地关注通道事件,也就是会说每个work(selector)下会注册多个Channel,并且不断地去Select关注的Ops
连接过程
下面看下channel注册到work(seelct)的代码,连接的时候是在内部的一个Boss类里处理的
所有的连接connect操作都被封装成一个RegisterTask对象,Boss类持有registerTask队列,在loop中不断的去进行select
- private static final class RegisterTask implements Runnable {
- private final Boss boss;
- private final NioClientSocketChannel channel;
- RegisterTask(Boss boss, NioClientSocketChannel channel) {
- this.boss = boss;
- this.channel = channel;
- }
- public void run() {
- try {
- // 注册connect事件
- channel.channel.register(
- boss.selector, SelectionKey.OP_CONNECT, channel);
- } catch (ClosedChannelException e) {
- channel.worker.close(channel, succeededFuture(channel));
- }
- int connectTimeout = channel.getConfig().getConnectTimeoutMillis();
- if (connectTimeout > 0) {
- channel.connectDeadlineNanos = System.nanoTime() + connectTimeout * 1000000L;
- }
- }
- }
- void register(NioClientSocketChannel channel) {
- // 封装一个RegisterTask
- Runnable registerTask = new RegisterTask(this, channel);
- Selector selector;
- synchronized (startStopLock) {
- if (!started) {
- // Open a selector if this worker didn’t start yet.
- try {
- this.selector = selector = Selector.open();
- } catch (Throwable t) {
- throw new ChannelException(
- “Failed to create a selector.”, t);
- }
- …..
- assert selector != null >> selector.isOpen();
- started = true;
- boolean offered = registerTaskQueue.offer(registerTask);
- assert offered;
- }
- if (wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- }
RegisterTask,放到Boss的registerTaskQueue之后,Boss会从boss executer线程池取出一个线程loop不断地处理队列、选择准备就绪的键等。
run方法不断处理感兴趣的事件
- public void run() {
- boolean shutdown = false;
- Selector selector = this.selector;
- long lastConnectTimeoutCheckTimeNanos = System.nanoTime();
- for (;;) {
- try {
- int selectedKeyCount = selector.select(10);
- //在网上有人提问为啥用timeout的select方法,
- http://stackoverflow.com/questions/10189603/netty-architecture-questions-about-nioworker-loop 作者给出了回答,因为在loop中除了select感兴趣的事件还要处理一些TaskQueue,里面主要是注册感兴趣的事件,所以如果不是用timeout的方法,则有可能永远无法注册
- processRegisterTaskQueue();
- if (selectedKeyCount > 0) {
- processSelectedKeys(selector.selectedKeys());
- }
- …
- } catch (Throwable t) {
- …
- }
- }
- }
在loop中,processRegisterTaskQueue会处理需要注册的任务,processSelectedKeys处理连接事件:
- private void processSelectedKeys(Set<SelectionKey> selectedKeys) {
- for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
- SelectionKey k = i.next();
- i.remove();
- if (!k.isValid()) {
- close(k);
- continue;
- }
- if (k.isConnectable()) {
- connect(k);
- }
- }
- }
- private void connect(SelectionKey k) {
- NioClientSocketChannel ch = (NioClientSocketChannel) k.attachment();
- try {
- if (ch.channel.finishConnect()) {
- k.cancel();
- //将连接上的通道注册到work中,交给work去注册read和write(注册reaad/write的模式是类似的),后面再详细说
- ch.worker.register(ch, ch.connectFuture);
- }
- } catch (Throwable t) {
- ch.connectFuture.setFailure(t);
- fireExceptionCaught(ch, t);
- k.cancel(); // Some JDK implementations run into an infinite loop without this.
- ch.worker.close(ch, succeededFuture(ch));
- }
- }
现在总结下在Client连接过程中事件发生顺序:
UpStream.ChannelState.OPEN(已经open)—–>DownStream.ChannelState.BOUND(需要绑定 localhost)——>DownStream.CONNECTED(需要连接,应该是注册Selector的意思)——–>UpStream.ChannelState.BOUND(已经绑定 localhost)——->UpStream.CONNECTED(连接成功)
在这一系列初始化都完成之后,channel就可以拿来write和接收read数据了,最后给client的模式来个总结