From: http://2006zhouxinfeng.blog.163.com/blog/static/5024021620115155106736/
前一段在網上看到了“淘寶伯巖”([email protected])的一份關於Java NIO 網絡編程的講義《NIO trick and trap——編寫高性能Java NIO網絡框架》。 其中裏面提到了Java NIO在網絡編程方面的應用和編程模型,同時也提到了Apache的一個開源網絡框架MINA。 正好自己對於NIO對網絡編程的應用也不是太熟悉,於是就簡單瞭解了下MINA。本文並不是針對於MINA這個框架如何使用(相關內容可以參見相關文 檔),而是整個MINA框架對於NIO的應用。
1、整體架構
首先我們來看一下MINA Server端的整體架構(圖片源自MINA官方文檔)。 其中的IoService用來負責實際的網絡連接,監聽用戶請求等功能。 對於每一個新的連接,MINA都會創建一個與之對應的Session。 MINA收到或者要發送的數據會經過一系列的Filter。過濾器可以用來對消息進行過濾和規範化。 最後MINA會調用用戶實現的一個IoHandler,來處理經過過濾器過濾的消息。 可以看出,對於框架的使用者來說,網絡的具體編程是對其透明的,用戶只需要實現相應的Handler,並且設置好Filter即可。
2、IoService細節
2.1、繼承關係
首先我們來看一下核心幾個類的繼承關係。
其中IoService爲整個框架的核心接口,其代表了網絡服務。
IoAcceptor爲IoService一個子接口,用來代表對用戶請求的接受和監聽。
SocketAcceptor爲IoAcceptor的一個子接口,用來代表基於Socket的網絡請求。
而AbstractIoService和AbstractIoAcceptor分別是對IoService和IoAcceptor的一個默認抽象實現。
AbstractPoolingIoAcceptor則是引入了池的概念,建立了處理線程池等。
最後NIOSocketAcceptor是AbstractPoolingIoAcceptor的NIO的一個實現。
2.2、整體流程
- 創建一個NIOSocketAcceptor對象。
- 調用IoAcceptor的bind方法,創建SocketChannel,並綁定到之前的Selector上。
- 創建一個新的線程,用來監聽用戶的連接。如果收到用戶的連接請求,則爲其創建一個Session,並把Session加入到一個Processor中等待處理。
- 系統中有若干個Processor,每個有自己的線程。在該線程中,也存在一個Selector,用來監聽所有該Processor上的Session。如果某個Session有數據可以讀取或者寫入,則將數據傳遞給一系列的Filter,並最終調用相應的Handler進行處理。
2.3、具體代碼分析
爲了簡明起見,下面的代碼只是整個代碼的截取,目的是爲了更好的說明問題。完整的代碼請詳見項目源文件。
2.3.1、構造
我們首先來看一下NioSocketAcceptor的構造函數。
-
public NioSocketAcceptor() {
-
super(new DefaultSocketSessionConfig(), NioProcessor.class);
-
((DefaultSocketSessionConfig) getSessionConfig()).init(this);
-
}
該構造函數傳給了父類構造函數兩個參數。首先是一個DefaultSocketSessionConfig的實例,用來表示一個配置對象。之後是NioProcessor.class,用來指明處理對象的類型。
接下來我們來看一下 NioSocketAcceptor的父類 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>:
-
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<NioSession>> processorClass) {
-
// 根據之前子類傳遞過來的processorClass,新建一個SimpleIoProcessorPool對象
-
// 並調用另一個構造函數
-
this(sessionConfig, null, new SimpleIoProcessorPool<NioSession>(processorClass), true);
-
}
-
private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor<NioSession> processor, boolean createdProcessor) {
-
// 調用父類構造函數
-
super(sessionConfig, executor);
-
// 調用初始化函數
-
init();
-
}
其中的init函數的實現在 NioSocketAcceptor中,代碼如下:
-
@Override
-
protected void init() throws Exception {
-
// 創建一個Selector對象
-
selector = Selector.open();
-
}
其中的SimpleIoProcessorPool<NioSession>的構造函數如下:
-
public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType) {
-
// 用默認參數調用另一構造函數
-
this(processorType, null, DEFAULT_SIZE);
-
}
-
public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType, Executor executor, int size) {
-
// 若executor爲null則新建,不然則使用傳入的對象。
-
createdExecutor = (executor == null);
-
if (createdExecutor) {
-
this.executor = Executors.newCachedThreadPool();
-
} else {
-
this.executor = executor;
-
}
-
// 新建一個IoProcessor池
-
pool = new IoProcessor[size];
-
// 根據傳入的processorType,利用Java的反射機制來創建對象,填填滿對象池
-
Constructor<? extends IoProcessor<S>> processorConstructor = processorType.getConstructor(ExecutorService.class);
-
for(int i = 0; i < size; i++) {
-
pool[i] = processorConstructor.newInstance(this.executor);
-
}
-
}
從以上代碼可以看出, SimpleIoProcessorPool<NioSession>根據傳入的IoProcessor的類型(這裏爲NioProcessor),創建了一個IoProcessor池,和一個Executor線程池,用來進行網絡IO的讀寫請求。
那我們就來看一下 NioProcessor的具體創建過程
-
public NioProcessor(Executor executor) {
-
super(executor);
-
this.selector = Selector.open();
-
}
可以看出,在NioProcessor創建的過程中,還創建了一個Selector對象,用來之後監聽網絡IO的讀寫請求。並把傳進來的Executor對象傳遞給父類進行保存。
之後我們再來看一下 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>的父類 AbstractIoAcceptor
-
protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
-
super(sessionConfig, executor);
-
}
可以看出該構造函數沒有進行額外的工作,而是直接調用父類。那我們就來看一下他的父類 AbstractIoService
-
protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {
-
// 保存配置對象
-
this.sessionConfig = sessionConfig;
-
// 若爲提供Executor對象,則新建一個。
-
if (executor == null) {
-
this.executor = Executors.newCachedThreadPool();
-
createdExecutor = true;
-
} else {
-
this.executor = executor;
-
createdExecutor = false;
-
}
-
}
至此, NIOSocketAcceptor對象的創建也就到此結束。在整個創建過程中,我們創建了一個 DefaultSocketSessionConfig實例,用來表示一些配置選項。創建了一個Selector對象, 和一個Executor線程池,用來負責監聽用戶的請求(詳見後文)。創建了一個 SimpleIoProcessorPool<NioSession>對象,其中包括一組 NioProcessor對象和一個Executor線程池對象,每個NioProcessor對象中還包括一個Selector對象,用來負責網絡IO的讀寫處理(詳見後文)。
2.3.2、bind
我們接着來看一下IoAcceptor中聲明的bind方法。其現實在AbstractIoAcceptor中。代碼如下:
-
public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
-
// 檢查地址的類型,並加入到localAddressCopy中
-
List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
-
for (SocketAddress a : localAddresses) {
-
checkAddressType(a);
-
localAddressesCopy.add(a);
-
}
-
// 調用bindInternal函數,返回成功綁定的地址。
-
Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
-
synchronized (boundAddresses) {
-
boundAddresses.addAll(addresses);
-
}
-
}
下面我們來看一下bindInternal的實現,該函數的實現在AbstractPollingIoAcceptor<NioSession, SocketChannel>中。
-
@Override
-
protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {
-
// 創建一個AcceptorOperationFuture對象,該對象的執行結果可以異步的通知
-
AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
-
// 將上文創建的對象加入隊列中,等待其他線程處理
-
registerQueue.add(request);
-
// 啓動Acceptor,進行地址的綁定
-
startupAcceptor();
-
// 喚起之前阻塞的Selector對象(詳見下文)
-
wakeup();
-
// 等待綁定完成
-
request.awaitUninterruptibly();
-
// 返回結果
-
Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
-
for (H handle : boundHandles.values()) {
-
newLocalAddresses.add(localAddress(handle));
-
}
-
return newLocalAddresses;
-
}
可以看出,在該函數中,首先創建了一個 AcceptorOperationFuture對象,並把其加入一個隊列中,等待其他線程處理。該對象類似Java中自帶的Future<?>對象,代表一個異步執行任務的結果。 自後調用了startupAcceptor來進行連接的監聽和綁定。下面我們就來看一下startupAcceptor函數的具體實現。
-
private void startupAcceptor() {
-
// 如果acceptorRef沒個賦值,則新建一個Acceptor對象進行賦值,並執行該acceptor。
-
Acceptor acceptor = acceptorRef.get();
-
if (acceptor == null) {
-
acceptor = new Acceptor();
-
if (acceptorRef.compareAndSet(null, acceptor)) {
-
executeWorker(acceptor);
-
}
-
}
從代碼可以看出,該函數進行了一個原子操作。如果acceptorRef沒被賦值,則新建對象進行賦值,並調用executeWroker函數。其中Acceptor實現了Runnable接口,而executorWorker函數的主要功能就是把該acceptor交給之前創建的Executor來執行。下面我們來看一下Acceptor的run函數,看一下它的具體執行過程。
-
public void run() {
-
int nHandles = 0;
-
while (selectable) {
-
// 等待用戶連接請求
-
int selected = select();
-
// 註冊新的地址綁定
-
nHandles += registerHandles();
-
// 沒有綁定的地址,可以退出
-
if (nHandles == 0) {
-
acceptorRef.set(null);
-
if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
-
break;
-
}
-
if (!acceptorRef.compareAndSet(null, this)) {
-
break;
-
}
-
}
-
// 處理用戶連接請求
-
if (selected > 0) {
-
processHandles(selectedHandles());
-
}
-
// 檢查是否有unbind請求.
-
nHandles -= unregisterHandles();
-
}
-
}
其中的select()函數的實現在 NIOSocketAcceptor類中:
-
@Override
-
protected int select() throws Exception {
-
return selector.select();
-
}
可以看出,select函數在之前創建的Selector上調用select函數。等待綁定在該Selector上的Channel狀態就緒。由於在第一次調用時,該Selector上並沒有綁定任何Channel,所以該函數會永遠阻塞住。這也就是爲什麼在之前的 bindInternal中,在調用 startAcceptor後,需要馬上調用wakeup函數。該函數的實現在 NIOSocketAcceptor類中:
-
@Override
-
protected void wakeup() {
-
selector.wakeup();
-
}
該函數會調用Selector的wakeup函數,用來喚醒阻塞的select函數。
接下來我們來看一下registerHandlers函數,該函數的作用是用來檢查是否有地址綁定的請求。並進行綁定。(unRegisterHandles與之相似,過程相反,這裏略去不提。)
-
private int registerHandles() {
-
for (;;) {
-
// 從隊列中獲得綁定任務
-
AcceptorOperationFuture future = registerQueue.poll();
-
// 隊列爲空,直接退出,返回0
-
if (future == null) {
-
return 0;
-
}
-
Map<SocketAddress, ServerSocketChannel> newHandles = new ConcurrentHashMap<SocketAddress, ServerSocketChannel>();
-
// 獲得需要綁定的地址
-
List<SocketAddress> localAddresses = future.getLocalAddresses();
-
// 一次綁定
-
for (SocketAddress a : localAddresses) {
-
// 爲每個地址創建ServerSocketChannel對象
-
ServerSocketChannel handle = open(a);
-
newHandles.put(localAddress(handle), handle);
-
}
-
// 更新已綁定的連接
-
boundHandles.putAll(newHandles);
-
// 通知 AcceptorOperationFuture任務已經完成
-
// 這樣之前調用awaitUninterruptibly()阻塞的線程將繼續執行。
-
future.setDone();
-
// 返回已綁定的個數
-
return newHandles.size();
-
}
-
}
該函數的作用是檢查是否有綁定的請求,然後爲每個地址建立一個連接,並綁定。其中創建連接調用的是open函數。該函數的實現在 NIOSocketAcceptor類中:
-
@Override
-
protected ServerSocketChannel open(SocketAddress localAddress) throws Exception {
-
// 創建一個新的ServerSocketChannel
-
ServerSocketChannel channel = ServerSocketChannel.open();
-
// 設置爲非阻塞
-
channel.configureBlocking(false);
-
// 綁定地址
-
ServerSocket socket = channel.socket();
-
socket.bind(localAddress, getBacklog());
-
// 在Selector對象中註冊該Channel。
-
channel.register(selector, SelectionKey.OP_ACCEPT);
-
return channel;
-
}
我們現在再把注意力移回之前的Acceptor的run函數。如果之前的select函數是正常返回,而不是被wakeup,那麼說明有用戶的連接請求。接下來就會執行processHandles函數。其實現如下:
-
private void processHandles(Iterator<ServerSocketChannel> handles) throws Exception {
-
while (handles.hasNext()) {
-
ServerSocketChannel handle = handles.next();
-
handles.remove();
-
// 接受用戶的請求,並創Session。
-
NioSession session = accept(processor, handle);
-
// 初始化Session
-
initSession(session, null, null);
-
// 把session添加到一個Processor的處理隊列中,等待IO讀寫處理
-
session.getProcessor().add(session);
-
}
-
}
下面我們來看一下accept函數。該函數接收兩個參數,一個是有連接請求的ServerSocketChannel,一個是之前創建的SimpleIoProcessorPool<NioSession>對象。
該函數的實現在NIOSocketAcceptor類中:
- @Override
- protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception {
- // 獲得用戶連接
- SocketChannel ch = handle.accept();
- // 創建Session
- return new NioSocketSession(this, processor, ch);
- }
之後我們來看一下Session在Processor上的註冊過程。及SimpleIoProcessorPool<NioSession>的add函數:
-
public final void add(S session) {
-
// 從Processor池中獲得一個Processor(NioProcessor),並註冊session.
-
getProcessor(session).add(session);
-
}
-
private IoProcessor<S> getProcessor(S session) {
-
// 如果該session已經註冊一個Processor,則返回該Processor
-
// 如果不存在,則從池中隨機選擇一個,並綁定到該session上
-
IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);
-
if(processor == null) {
-
processor = pool[Math.abs((int) session.getId()) % pool.length];
-
session.setAttributeIfAbsent(PROCESSOR, processor);
-
}
-
return processor;
-
}
接下來我們來看一下NioProcessor中的add函數。整個過程和之前監聽用戶連接請求的過程相似。故我們只關注其中不同的地方
-
public final void add(S session) {
-
newSessions.add(session); // 加入請求隊列
-
startupProcessor();
-
}
-
-
private void startupProcessor() {
-
Processor processor = processorRef.get();
-
if (processor == null) {
-
processor = new Processor();
-
if (processorRef.compareAndSet(null, processor)) {
-
executor.execute(new NamePreservingRunnable(processor, threadName));
-
}
-
}
-
wakeup();
-
}
-
-
public void run() {
-
int nSessions = 0;
-
for (;;) {
-
int selected = select(SELECT_TIMEOUT);
-
// 處理新的session的註冊
-
nSessions += handleNewSessions();
-
if (selected > 0) {
-
process();
-
}
-
// 刷新排隊的寫請求
-
flush(currentTime);
-
nSessions -= removeSessions();
-
if (nSessions == 0) {
-
processorRef.set(null);
-
if (newSessions.isEmpty() && isSelectorEmpty()) {
-
break;
-
}
-
if (!processorRef.compareAndSet(null, this)) {
-
break;
-
}
-
}
- }
接下來我們來看一下其中的 handleNewSessions函數
-
private int handleNewSessions() {
-
int addedSessions = 0;
-
for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {
-
if (addNow(session)) {
-
addedSessions++;
-
}
-
}
-
return addedSessions;
-
}
-
private boolean addNow(S session) {
-
boolean registered = false;
-
// 註冊session
-
init(session);
-
registered = true;
-
// 構建FilterChain
-
IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();
-
chainBuilder.buildFilterChain(session.getFilterChain());
-
return registered;
-
}
-
@Override
-
protected void init(NioSession session) throws Exception {
-
// 把Session對應的SocketChannel註冊在該session對應的processor的Selector。
-
SelectableChannel ch = (SelectableChannel) session.getChannel();
-
ch.configureBlocking(false);
-
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ,session));
-
}
-
private void process() throws Exception {
-
for (Iterator<S> i = selectedSessions(); i.hasNext();) {
-
S session = i.next();
-
process(session);
-
i.remove();
-
}
-
}
-
private void process(S session) {
-
// 讀請求
-
if (isReadable(session) && !session.isReadSuspended()) {
-
read(session);
-
}
-
// 寫請求
-
if (isWritable(session) && !session.isWriteSuspended()) {
-
if (session.setScheduledForFlush(true)) {
-
// 加入寫刷新隊列
-
flushingSessions.add(session);
-
}
-
}
-
}
-
-
private void read(S session) {
-
// 從SocketChannel中讀數據
-
// 這裏略去
-
// 如果讀到數據
-
if (readBytes > 0) {
-
// 把數據傳給FilterChain
-
IoFilterChain filterChain = session.getFilterChain();
-
filterChain.fireMessageReceived(buf);
-
}
-
}