Apache Mina Server 是一個網絡通信應用框架,也就是說,它主要是對基於TCP/IP、UDP/IP協議棧的通信框架(當然,也可以提供JAVA 對象的序列化服務、虛擬機管道通信服務等),Mina 可以幫助我們快速開發高性能、高擴展性的網絡通信應用,Mina 提供了事件驅動、異步(Mina 的異步IO 默認使用的是JAVA NIO 作爲底層支持)操作的編程模型。Mina 主要有1.x 和2.x 兩個分支,這裏我們講解最新版本2.0,如果你使用的是Mina 1.x,那麼可能會有一些功能並不適用。學習本文檔,需要你已掌握 JAVA IO、JAVA NIO、JAVASocket、JAVA 線程及併發庫(java.util.concurrent.*)的知識。Mina 同時提供了網絡通信的Server 端、Client 端的封裝,無論是哪端,Mina 在整個網通通信結構中都處於如下的位:
可見Mina 的API 將真正的網絡通信與我們的應用程序隔離開來,你只需要關心你要發送、接收的數據以及你的業務邏輯即可。同樣的,無論是哪端,Mina 的執行流程如下所示:
(1.) IoService:這個接口在一個線程上負責套接字的建立,擁有自己的Selector,監聽是否有連接被建立。
(2.) IoProcessor:這個接口在另一個線程上負責檢查是否有數據在通道上讀寫,也就是說它也擁有自己的Selector,這是與我們使用JAVA NIO 編碼時的一個不同之處,通常在JAVA NIO 編碼中,我們都是使用一個Selector,也就是不區分IoService與IoProcessor 兩個功能接口。另外,IoProcessor 負責調用註冊在IoService 上的過濾器,並在過濾器鏈之後調用IoHandler。
(3.) IoFilter:這個接口定義一組攔截器,這些攔截器可以包括日誌輸出、黑名單過濾、數據的編碼(write 方向)與解碼(read 方向)等功能,其中數據的encode 與decode是最爲重要的、也是你在使用Mina 時最主要關注的地方。
(4.) IoHandler:這個接口負責編寫業務邏輯,也就是接收、發送數據的地方。
1.簡單的TCPServer:
(1) 第一步:編寫IoService按照上面的執行流程,我們首先需要編寫IoService,IoService 本身既是服務端,又是客戶端,我們這裏編寫服務端,所以使用IoAcceptor 實現,由於IoAcceptor 是與協議無關的,因爲我們要編寫TCPServer,所以我們使用IoAcceptor 的實現NioSocketAcceptor,實際上底層就是調用java.nio.channels.ServerSocketChannel 類。當然,如果你使用了Apache 的APR 庫,那麼你可以選擇使用AprSocketAcceptor 作爲TCPServer 的實現,據傳說Apache APR庫的性能比JVM 自帶的本地庫高出很多。那麼IoProcessor 是由指定的IoService 內部創建並調用的,我們並不需要關心。
public class MyServer {
main方法:
IoAcceptor acceptor=new NioSocketAcceptor();
acceptor.getSessionConfig().setReadBufferSize(2048 );
acceptor.getSessionConfig.setIdleTime(IdleStatus.BOTH_IDLE,10 );
acceptor.bind(new InetSocketAddress(9123 ));
}
這段代碼我們初始化了服務端的TCP/IP 的基於NIO 的套接字,然後調用IoSessionConfig設置讀取數據的緩衝區大小、讀寫通道均在10 秒內無任何操作就進入空閒狀態。
(2) 第二步:編寫過濾器
這裏我們處理最簡單的字符串傳輸,Mina 已經爲我們提供了TextLineCodecFactory 編解碼器工廠來對字符串進行編解碼處理。
acceptor.getFilterChain ().addLast ("codec" ,
new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.forName ("UTF-8" ),
LineDelimeter.WINDOWS .getValue (),
LineDelimiter. WINDOWS.getValue ()
)
)
)
這段代碼要在acceptor.bind()方法之前執行,因爲綁定套接字之後就不能再做這些準備工作了。
這裏先不用清楚編解碼器是如何工作的,這個是後面重點說明的內容,這裏你只需要清楚,我們傳輸的以換行符爲標識的數據,所以使用了Mina 自帶的換行符編解碼器工廠。
(3.) 第三步:編寫IoHandler這裏我們只是簡單的打印Client 傳說過來的數據。
public class MyIoHandler extends IoHandlerAdapter {
private final static Logger log = LoggerFactory.getLogger(MyIoHandler.class);
@Override
public void messageReceived (IoSession session, Object message)
throws Exception {
String str = message.toString();
log.info("The message received is [" + str + "]" );
if (str.endsWith("quit" )) {
session.close(true );
return ;
}
}
}
然後我們把這個IoHandler 註冊到IoService:acceptor.setHandler(new MyIoHandler());當然這段代碼也要在acceptor.bind()方法之前執行。
然後我們運行MyServer 中的main 方法,你可以看到控制檯一直處於阻塞狀態,此時,我們用telnet 127.0.0.1 9123 訪問,然後輸入一些內容,當按下回車鍵,你會發現數據在Server 端被輸出,但要注意不要輸入中文,因爲Windows 的命令行窗口不會對傳輸的數據進行UTF-8 編碼。當輸入quit 結尾的字符串時,連接被斷開。這裏注意你如果使用的操作系統,或者使用的Telnet 軟件的換行符是什麼,如果不清楚,可以刪掉第二步中的兩個紅色的參數,使用TextLineCodec 內部的自動識別機制。
2.簡單的TCPClient:
這裏我們實現Mina 中的TCPClient,因爲前面說過無論是Server 端還是Client 端,在Mina中的執行流程都是一樣的。唯一不同的就是IoService 的Client 端實現是IoConnector。
(1.) 第一步:編寫IoService並註冊過濾器
public class MyClient {
main方法:
IoConnector connector=new NioSocketConnector();
connector.setConnectTimeoutMillis(30000 );
connector.getFilterChain().addLast("codec" ,
new ProtocolCodecFilter(
new TextLineCodecFactory(
Charset.forName("UTF-8" ),
LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue()
)
)
);
connector.connect(new InetSocketAddress("localhost" , 9123 ));
}
(2.) 第三步:編寫IoHandler
public class ClientHandler extends IoHandlerAdapter {
private final static Logger LOGGER = LoggerFactory.getLogger(ClientHandler.class);
private final String values;
public ClientHandler (String values) {
this .values = values;
}
@Override
public void sessionOpened (IoSession session) {
session.write(values);
}
}
註冊IoHandler:
connector.setHandler(new ClientHandler("你好!\r\n 大家好!"));
然後我們運行MyClient,你會發現MyServer 輸出如下語句:
The message received is [你好!]
The message received is [大家好!]
我們看到服務端是按照收到兩條消息輸出的,因爲我們用的編解碼器是以換行符判斷數據是否讀取完畢的。
總結:
我們在這裏已經簡單的完成了使用mina進行網絡通信,client和server之間傳輸一些數據,當然這是最基本的使用,後續我們會詳細的介紹一些api以及更加具體複雜的案例。