1 概述
Netty中的ChannelPipeline類似於servlet,chanelHandler類似於filter。這類攔截器就是職責鏈設計模式,主要是事件攔截和用戶業務邏輯定製。演示代碼採用的是netty 3.10.5版本。調試步驟和測試代碼如下:
1 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 簡要流程圖
感覺有幫助請您賞一杯茶錢,金額隨意。您的鼓勵是我寫作的動力。