Netty學習6-ChanelHandler【2】調用過程源碼分析

1 概述

Netty中的ChannelPipeline類似於servlet,chanelHandler類似於filter。這類攔截器就是職責鏈設計模式,主要是事件攔截和用戶業務邏輯定製。演示代碼採用的是netty 3.10.5版本。調試步驟和測試代碼如下:

netty源代碼下載完成後,導入爲maven項目,命名爲MyNettySource

2 測試代碼在configure build path時不要直接導入netty3的jar包,而是直接導入project項目,選擇MyNettySource項目

3 對關注的方法打斷點

4 跟蹤調用鏈

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;

/**
 * netty服務端入門
 */
public class Server {

	public static void main(String[] args) {

		// 服務類
		ServerBootstrap bootstrap = new ServerBootstrap();
		// boss線程監聽端口,worker線程負責數據讀寫
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		// 設置niosocket工廠
		bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
		// 設置管道的工廠
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("decoder", new StringDecoder());
				pipeline.addLast("xyHelloHandler", new HelloHandler()); // StringDecoder和HelloHandler都是Netty4中的inboundHandler
				return pipeline;
			}
		});
		bootstrap.bind(new InetSocketAddress(10101));
		System.out.println("server3 start!!!");
	}
}

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

/**
 * 消息接受處理類
 */
public class HelloHandler extends SimpleChannelHandler {

	// 接收消息
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		String messageReceived = (String) e.getMessage();
		System.out.println(messageReceived);
		super.messageReceived(ctx, e);
	}

	// 捕獲異常
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
			throws Exception {
		System.out.println("exceptionCaught");
		super.exceptionCaught(ctx, e);
	}

	// 新連接
	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("channelConnected");
		super.channelConnected(ctx, e);
	}

	// 必須是鏈接已經建立,關閉通道的時候纔會觸發
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx,
			ChannelStateEvent e) throws Exception {
		System.out.println("channelDisconnected");
		super.channelDisconnected(ctx, e);
	}

	// channel關閉的時候觸發
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("channelClosed");
		super.channelClosed(ctx, e);
	}
}



2 server啓動

handler是通過pipeline的addLast方法添加的,那首先將斷點定位在DefaultChannelPileline的addLast方法。

利用debug方式運行Server,注意查看調用鏈從調用鏈中可看出入口是ServerBootStrap的bindAsync方法。


[1] 方法DefaultChannelPileline@addLast

public ChannelFuture bindAsync(final SocketAddress localAddress) {
      if (localAddress == null) {
          throw new NullPointerException("localAddress");
      }
      Binder binder = new Binder(localAddress);
      ChannelHandler parentHandler = getParentHandler();


      ChannelPipeline bossPipeline = pipeline();
      bossPipeline.addLast("binder", binder);


      if (parentHandler != null) {
          bossPipeline.addLast("userHandler", parentHandler);
      }
}

bossPipeline.addLast("binder", binder)賦值了一個名爲binder的處理器。繼續跟進addLast方法。

private final Map<String, DefaultChannelHandlerContext> name2ctx =new HashMap<String, DefaultChannelHandlerContext>(4);
public synchronized void addLast(String name, ChannelHandler handler) {
        if (name2ctx.isEmpty()) {
            init(name, handler);
        } else {
            checkDuplicateName(name);
            DefaultChannelHandlerContext oldTail = tail;
            DefaultChannelHandlerContext newTail = new DefaultChannelHandlerContext(oldTail, null, name, handler);
            callBeforeAdd(newTail);
            oldTail.next = newTail;
            tail = newTail;
            name2ctx.put(name, newTail);
            callAfterAdd(newTail);
        }
}


[2] 方法DefaultChannelPileline@init

name2ctx是保存handlerContext的集合對象,此時是空的。斷點走進init方法,逐行分析方法內的這幾行代碼。


private void init(String name, ChannelHandler handler) {
    DefaultChannelHandlerContext ctx = new DefaultChannelHandlerContext(null, null, name, handler);
    callBeforeAdd(ctx);
    head = tail = ctx;
    name2ctx.clear();
    name2ctx.put(name, ctx);
    callAfterAdd(ctx);
}

行1 聲明瞭DefaultChannelHandlerContext對象,傳入name值是binder,handler是名爲binder的處理器,這兩個值初始化的地方就在上文看到的調用鏈的BootStrap的bind方法中。先看DefaultChannelHandlerContext該類使用到的屬性。很明顯這是鏈表的數據結構,prev指向上個對象,next指向下個對象。

private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
     volatile DefaultChannelHandlerContext next;
     volatile DefaultChannelHandlerContext prev;
     private final String name;
     private final ChannelHandler handler;
}

行2 binder並不是LifeCycleAwareChannelHandler類型,直接return掉。

private static void callBeforeAdd(ChannelHandlerContext ctx) {
    if (!(ctx.getHandler() instanceof LifeCycleAwareChannelHandler)) {
        return;
}

行2 將鏈表中的第1個head和最後一個tail都賦值給了第1行中聲明的對象。

private volatile DefaultChannelHandlerContext head;
private volatile DefaultChannelHandlerContext tail;

行4 清除Map對象中的值。

行5 將第1行聲明的對象賦值給map集合。

行6 binder並不是LifeCycleAwareChannelHandler類型,直接return掉。第一個需要關注的斷點到此結束。

private void callAfterAdd(ChannelHandlerContext ctx) {
    if (!(ctx.getHandler() instanceof LifeCycleAwareChannelHandler)) {
        return;
    }
}



3 client啓動

啓動client的方法由很多種,可以寫Client的代碼進行訪問。還有一個簡便的方法是利用telnet模擬客戶端。

telnet 127.0.0.1 10101。設置斷點在Server類中的Channels.pipeline()位置。


[1] 方法channels.pipeline()


新生成ChannelPipeline對象,從調用鏈可看出:

[1] boss線程池負責接收連接。

[2] 上游NioServerBoss@registerAcceptedChannel表明在建立連接時,該ChannelPipeline的所有handler將被設置完成。

private static void registerAcceptedChannel(NioServerSocketChannel parent, SocketChannel acceptedSocket,Thread currentThread) {
try {
          ChannelSink sink = parent.getPipeline().getSink();
          ChannelPipeline pipeline =parent.getConfig().getPipelineFactory().getPipeline();
    }
}


[2] 方法pipeline.addLast(decoder)

添加StringDecoder這個處理器,流程和2.2中的所有步驟一致都走init方法,同樣在callBeforeAdd和callAfterAdd中return了。唯一不同的是name叫做decoder。


[3] 方法pipeline.addLast(xyHelloHandler)

name2ctx不再empty,進入else分支。


checkDuplicateName檢查是否有重名,有重名則直接拋出異常。
oldTail表示原來尾對象,新申明的xyHelloHandler將被作爲新尾對象。再把原oldTail的next指向xyHelloHandler,再放入name2ctx集合。


[4] 方法AbstractNioSelector@select

繼續跟着斷點走,會走到AbstractNioSelector的select方法會阻塞,等待下一次事件。



4 client發送信息

在telnet窗口按下ctrl+] 會進入發送窗口。

現在關注以下2個方法的調用鏈,StringDecoder@decode、HelloHandler@messageReceived斷點到這兩個方法。


[1] 方法StringDecoder@decode



從NioWorker的read方法開始,執行到DefaultChannelPipeline的sendUpstream。標註559處。

  public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext head = getActualUpstreamContext(this.head); // 獲取該pipeline的HeadHandler就是StringDecoder
        if (head == null) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "The pipeline contains no upstream handlers; discarding: " + e);
            }
            return;
        }
        sendUpstream(head, e); // 進入該方法
}

進入該方法後會先調用StringDecoder的父類OnetoOneDecoder的handleUpStream(ctx,event)方法,標註559的下一行。

    public void handleUpstream(
            ChannelHandlerContext ctx, ChannelEvent evt) throws Exception {
        if (!(evt instanceof MessageEvent)) {
            ctx.sendUpstream(evt);
            return;
        }
        MessageEvent e = (MessageEvent) evt;
        Object originalMessage = e.getMessage();
        Object decodedMessage = decode(ctx, e.getChannel(), originalMessage);
        if (originalMessage == decodedMessage) {
            ctx.sendUpstream(evt);
        } else if (decodedMessage != null) {
            fireMessageReceived(ctx, decodedMessage, e.getRemoteAddress()); // 向下傳遞
        }
    }

父類方法調用子類的decode方法,如此StringDecoder的decode方法就被調用到了。fireMessageReceived(ctx, decodedMessage, e.getRemoteAddress())方法會向下傳遞,進入該方法最終會執行到DefaultChannelPipeline的內部類DefaultChannelHandlerContext的sendUpstream方法

        public void sendUpstream(ChannelEvent e) {
            DefaultChannelHandlerContext next = getActualUpstreamContext(this.next); // 獲取next
            if (next != null) {
                DefaultChannelPipeline.this.sendUpstream(next, e);
            }
        }


[2] 方法HelloHandler@messageReceived

next相當於獲取了head的下一個handler對象即HelloHanlder。

	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		String messageReceived = (String) e.getMessage();
		System.out.println(messageReceived);
		super.messageReceived(ctx, e); // helloHandler若希望繼續向下傳遞,則繼續調用鏈式方法
	}

從代碼可看出handler往下傳遞對象的方法是sendUpstream(event)下方代碼進行測試:

public class Client {
	public static void main(String[] args) throws Exception {
		Socket socket = new Socket("127.0.0.1", 10101);
		socket.getOutputStream().write("hello".getBytes());
		socket.close();
	}
}

public class Server {
	public static void main(String[] args) {
		//服務類
		ServerBootstrap bootstrap = new ServerBootstrap();	
		//boss線程監聽端口,worker線程負責數據讀寫
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		//設置niosocket工廠
		bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
		//設置管道的工廠
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("handler1", new MyHandler1());
				pipeline.addLast("handler2", new MyHandler2());
				return pipeline;
			}
		});
		bootstrap.bind(new InetSocketAddress(10101));	
		System.out.println("start!!!");
	}
}

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.UpstreamMessageEvent;
public class MyHandler1 extends SimpleChannelHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		ChannelBuffer buffer = (ChannelBuffer)e.getMessage();
		byte[] array = buffer.array();
		String message = new String(array);
		System.out.println("handler1:" + message);
		
		// 傳遞給handler2。由於直接傳遞的是string那麼handler2直接接收String即可
		ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), "abc", e.getRemoteAddress()));
		ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), "efg", e.getRemoteAddress()));
	}
}

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
public class MyHandler2 extends SimpleChannelHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		String message = (String)e.getMessage();
		System.out.println("handler2:" + message);
	}
}



5 簡要流程圖


流程圖地址 http://blog.csdn.net/zxhoo/article/details/17264263

感覺有幫助請您賞一杯茶錢,金額隨意。您的鼓勵是我寫作的動力。


發佈了535 篇原創文章 · 獲贊 1165 · 訪問量 451萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章