IoHandlerjava
MINA的內部實現了一個事件模型,而IoHanlder則是全部事件最終產生響應的位置。每個方法的名字很明確代表該事件的含義。messageReceived是接收客戶端消息的事件,咱們應該在這裏實現業務邏輯。messageSent是服務器發送消息的事件,通常狀況下咱們不會使用它。sessionClosed是客戶端斷開鏈接的事件,能夠在這裏進行一些資源回收等操做。值得留意的是,客戶端鏈接有兩個事件,sessionCreated和sessionOpened,二者稍有不一樣,sessionCreated是由I/O processor線程觸發的,而sessionOpened在其後,由業務線程觸發的,因爲MINA的I/O processor線程很是少,所以若是咱們真的須要使用sessionCreated,也必須是耗時短的操做,通常狀況下,咱們應該把業務初始化的功能放在sessionOpened事件中。git
細心的讀者可能會發現,咱們剛剛的例子繼承的是IoHandlerAdapter,IoHandlerAdapter其實就是一個 IoHanlder的空的實現,這樣咱們就能夠不用重載不感興趣的事件。spring
2.IoSession數據庫
在這裏,筆者把IoSession的方法大體分紅三類服務器
第一類,鏈接操做功能。網絡
最主要的方法有兩個,向客戶端發送消息和斷開鏈接。能夠看的出,write接受的變量是一個Object,可是實際上應該傳入什麼類型呢?具體還得看你是否使用了ProtocolCodecFilter(下面會詳細介紹),若是使用了ProtocolCodecFilter,那這個message將多是一個String,或者是一個用戶定義的JavaBean。默認的狀況,message是一個ByteBuffer。ByteBuffer是MINA的一個類,跟java.nio.ByteBuffer類同名,MINA 2.0將會將它改爲IoBuffer,以免討論上的誤會。session
另外一個值得留意的是Future類,MINA是一個非阻塞的通訊框架,其中一個明顯的體現就是調用IoSession.write方法是不會阻塞的。用戶調用了write方法以後,消息內容會發到底層等候發送,至於何時發出,就不得而知了。固然,實際上調用了write以後,數據幾乎是馬上發出的,這得益與NIO的高性能。可是,若是咱們必須確認了消息發出,而後進行某些處理,咱們就須要使用Future類,如下是一個很常見的代碼。多線程
經過調用future.join,程序就會阻塞,直至消息處理結束。咱們還能經過future.isWritten得知消息是否成功發送。架構
在這裏,筆者順便說一個實際使用的發現,消息發送是會自動合併的,簡單來講,若是在很短的時間裏,對同一個IoSession進行了兩次write操做,客戶端有可能只收到一條消息,而這條消息就是服務器發出的兩條消息先後接起來。這樣的設計能夠在高併發的時候節省網絡開銷,而筆者的實際使用過程當中,效果也至關好。可是若是這樣行爲會致使客戶端工做不正常,你也能夠經過參數關閉它。併發
第二類,屬性存儲操做。
一般來講,咱們的系統是有用戶狀態的,咱們就須要在鏈接上存儲用戶屬性,IoSession的Attribute就是這樣一個功能。例如兩個鏈接同時連入服務器,一個鏈接是用戶A,用戶ID是13,另外一個鏈接是用戶B,用戶ID是14,咱們就能夠在用戶登陸成功以後,調用IoSession.setAttribute(「login_id」,13),而後在其後的操做中,經過IoSession.getAttribute(「login_id」)得到當前登陸用戶ID,並進行相應的操做。簡單來講,就是一個相似HttpSession的功能,固然具體的實現方法不同。
第三類,鏈接狀態。
這裏就很少說了,從方法名上咱們就能知道它具體的功能。
IoFilter
過濾器是MINA的一個很重要的功能。IoFilter也是一個接口,可是相對比較複雜,這裏就不列舉它的方法了。簡單來講IoFilter就像ServletFilter,在事件被IoHandler處理以前或以後進行一些特定的操做,可是它比ServletFilter複雜,能夠處理不少種事件,除了包括IoHandler的7個事件之外,還有一些內部的事件能夠進行操做。
MINA提供了一些經常使用的IoFilter實現,例若有LoggingFilter(日誌功能)、BlacklistFilter(黑名單功能)、CompressionFilter(壓縮功能)、SSLFilter(SSL支持),這些過濾器比較簡單,經過閱讀它們的源代碼,可以更進一步理解過濾器的實現。筆者在這裏要重點介紹兩個過濾器,ProtocolCodecFilter和ExecutorFilter
3.ProtocolCodecFilter
ObjectSerializationCodecFactory是Java Object序列化以後的內容直接跟ByteBuffer互相轉化,比較適合兩端都是Java的狀況使用。TextLineCodecFactory就是String跟ByteBuffer的轉化,說白了就是文本,例如你要實現一個SMTP服務器,或者POP服務器,就可使用它。而筆者的實際使用,大多數狀況都是使用
TextLineCodecFactory
這裏說起一下IoFilter的順序問題,IoFilter是有加入順序的,例如,先加入LoggingFilter再加入ProtocolCodecFilter,和先加入ProtocolCodecFilter再加入LoggingFilter的效果是不同的,前者LoggingFilter寫入日誌的內容是ByteBuffer,然後者寫入日誌的是轉換後具體的類,例如String。實際使用的時候,必定要處理好過濾器的順序。
自定義ProtocolCodecFilter, 自定義編碼解碼方式,可現實文件的傳輸
ExecutorFilter
另外一個重要的過濾器就是ExecutorFilter。這裏,我須要先說明一下MINA的線程工做模式,MINA默認是單線程處理全部客戶端的消息,也就是說,即便你在一臺8CPU的機器上面跑,可能也只用到一個CPU,另外,若是某次消息處理太耗時,就會致使其餘消息等待,總體的吞吐量降低。不少朋友抱怨MINA的性能差,實際上是由於他們沒有加入ExecutorFilter的緣故。ExecutorFilter設計的很精巧,你們能夠仔細閱讀一下源代碼,它會將同一個鏈接的消息合併起來按順序調用,不會出現兩個線程同時處理同一個鏈接的狀況。
1.IoAcceptor acceptor = ...; 2.IoServiceConfig acceptorConfig = acceptor.getDefaultConfig(); 3.acceptorConfig.setThreadModel(ThreadModel.MANUAL);
這裏再次說起IoFitler的順序問題,通常狀況下,咱們會將ExecutorFilter放在ProtocolCodecFilter以後,由於咱們不須要多線程地執行ProtocolCodec操做,用單一線程來進行ProtocolCodec性能會比較高,而具體的業務邏輯可能還設計數據庫操做,所以更適合放在不一樣的線程中運行。
4.優化建議
MINA默認配置的性能並非很高的,部分緣由是MINA目前還保留初期版本的架構,另一個緣由是由於JVM的發展。
1.IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
首先咱們關閉默認的ThreadModel設置 ThreadModel是一個很簡單的線程實現,用於IoService。可是它實在太弱,以致於在併發環境產生大量問題。在MINA 2.0中,ThreadModel直接被取消。你應該使用ExecutorFilter來實現線程。
acceptor.getDefaultConfig().getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());
而後咱們增長I/O處理線程
每個Acceptor/Connector都使用一個線程來處理鏈接,而後把鏈接發送給I/O processor進行讀寫操做,咱們只能夠修改I/O processor使用的線程數,用如下代碼設置 固然是要將ExecutorFilter加入,上文已經很詳細地描述了 筆者在開發過程當中,屢次遇到OutOfMemoryError,通過研究以後才發現緣由。MINA默認是使用direct memory實現ByteBuffer池的方案(如下簡稱direct buffer),經過JNI在內存開闢一段空間來使用,該方案在早期的MINA版本中是一個很是好的特性,那是由於MINA開發初期,JVM並無如今的強大,帶有池效果的direct buffer性能比較好。可是當咱們使用-Xms -Xmx等指令增長JVM可以使用的內存,那僅僅增長了堆的內存空間,而direct memory的空間並無增長,致使MINA實際使用的時候常常出現OutOfMemoryError。若是你的確想使用direct memory,能夠經過-XX:MaxDirectMemorySize選項來設置。不過筆者不建議這樣作,由於最新的測試代表,在現代的JVM裏面,direct memory比堆的表現更差。這裏可能有讀者會以爲奇怪,爲何不用池,而要用堆呢,並且還須要gc。那是由於如今的JVM gc能力已經很強了,並且在併發環境裏面,pool的同步也是一個性能的問題。咱們能夠經過這樣的代碼進行設置 MINA 2.0已經默認把直接內存分配改爲堆,爲了提供最好的性能和穩定性。
ByteBuffer.setUseDirectBuffers(false); ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
最後一條優化技巧就是,把你的應用部署在Linux上,而且打開Java NIO使用Linux epoll的功能。可能你還沒聽過epoll,可是你應該聽過Lighttpd、Nginx、Squid等,得益於epoll,它們提供很高的網絡性能,還佔用很是少的系統資源。JDK6已經默認把epoll配置打開,所以筆者建議把你的應用部署在JDK6上面,也同時由於JDK6還有別的優化特性。若是你的應用必須部署在JDK5上,你也能夠經過參數把epoll支持打開。
最後附上項目地址:http://git.oschina.net/bob4j/spring_mina/tree/master