最近做的一個項目用到了開源的C/S應用的服務器框架MINA,當初做的時候資料非常少,只能自己不停的測試,總結出了一些規律經驗。
從網上看的資料上看,這個服務器框架還是比較穩定和支持的併發數還是很不錯的,不過沒有準確的數據,而且我做完的時候也沒有拿到真正的實際環境中測試過,用的時候也發現了很多優點和缺點,使用者可以自己去根據自己的使用需求去衡量是否使用該框架。
服務器是商業系統很重要的一部分,主要負責數據採集,文件分發,與端機的通信,和自動作業等任務,服務器大多是24小時運行的,因此服務器的實現必須強壯、穩定、安全,而速度雖然也是很重要,不過最重要的還是前三者。服務器框架MINA就是要爲這樣的服務器提供了一個網絡應用框架,當然這樣的服務器框架也可以自己去實現。MINA爲我們封裝了socket的底層通信實現,提供了日誌,線程池等功能,使用起來非常簡單、方便。
MINA是一個異步框架,是通過網絡事件激發的,它包含兩層:IO層和協議層。首先介紹IO層,要說明的是我用的版本是0.8.2,可能不同版本會稍有不同。
Client產生一個底層IO事件,比如說連接和發送數據包,IoAcceptor執行所有底層IO,將他們翻譯成抽象的IO事件,接着這裏可以添加(也可以部添加)一個IoFilters對IO事件進行過濾,並把翻譯過的事件或過濾過的事件和關聯的IoSession 發送給IoHandler。IoSession是一個有效的網絡連接會話,此會話將一直保持連接,除非網絡斷開或用戶主動斷開連接(session.close()),用戶可以通過IoSession獲得有關該會話連接的信息和配置會話的對象和屬性;IoHandler是網絡事件的監聽器,也就是說當有網絡事件發生時會通知IoHandler,用戶不用去主動接受數據。用戶只要實現此接口愛幹嗎幹嗎去吧。IoFilter:Io過濾器,對Io事件進行過濾,比如添加日誌過濾器和線程池過濾器。
使用說明:
import java.util.logging.Level;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.SessionConfig;
import org.apache.mina.io.IoHandlerAdapter;
import org.apache.mina.io.IoSession;
import org.apache.mina.io.socket.SocketSessionConfig;
import org.apache.mina.util.SessionLog;
public class ServerHandler extends IoHandlerAdapter {
public ServerHandler() {
}
public void dataRead(IoSession session, ByteBuffer buffer) throws Exception {
//當有數據讀入時此方法被調用,數據封裝在ByteBuffer中,可以用以下方法對出buffer的數據,ByteBuffer的數據讀出後內存中就沒有了。
//String message = "";
// byte[] bytes = new byte[rb.remaining()];
// int j = 0; // while (rb.hasRemaining()) {
// bytes[j++] = rb.get();
// }
// message = new String(bytes);
//接着可以進行邏輯處理
}
public void dataWritten(IoSession session, Object mark) throws Exception {
//當數據被寫入通道時此方法被調用,實際就是調用了session.write(IoSession,Object)方法
SessionLog.log(Level.INFO,session,mark.toString());//必要時打印所寫入的內容,mark的內容就是session.write(session,mark)中的第二個參數
}
public void exceptionCaught(IoSession session, Throwable arg1)
throws Exception {
//當出現異常時此方法被調用,從而進行各種異常處理,該方法可以捕捉網絡異常(如連接非正常關閉)和所有其他方法產生的異常,這裏要注意如果客戶端要保持與服務器端的連接時不要在這裏馬上重新連接不然會拋出CancelKeyException運行期異常直接導致程序死掉(特別是與服務器端有超過兩個連接時一定會發生並且此異常無法捕獲),建議的方法是啓動一個單獨的線程來完成與服務器端的重新連接,還有要注意的是如果網絡是正常關閉的,比如說是客戶端正常關閉連接,而此時服務器端是不願意關閉的話,這個異常本方法是捕捉不了的,因此只能在session.close()方法中處理這種情況。
session.close();
}
public void sessionClosed(IoSession session) throws Exception {
//當網絡連接被關閉是此方法被調用
SessionLog.log(Level.INFO,session,"Close a Session");//必要時打印出信息
}
public void sessionCreated(IoSession session) throws Exception {
//當網絡連接被創建時此方法被調用(這個肯定在sessionOpened(IoSession session)方法之前被調用),這裏可以對Socket設置一些網絡參數
SessionConfig cfg = session.getConfig();
if (cfg instanceof SocketSessionConfig) {
((SocketSessionConfig) cfg).setSessionReceiveBufferSize(2048);
((SocketSessionConfig) cfg).setKeepAlive(true);
((SocketSessionConfig) cfg).setSoLinger(true, 0);
((SocketSessionConfig) cfg).setTcpNoDelay(true);
((SocketSessionConfig) cfg).setWriteTimeout(1000 * 5);
}
}
public void sessionIdle(IoSession arg0, IdleStatus arg1) throws Exception {
// 當網絡通道空閒時此方法被調用,在這裏可以判斷是讀空閒、寫空閒還是兩個都空閒,以便做出正確的處理
一半的網絡通訊程序都要與服務器端保持長連接,所以這裏可以發一下網絡測試數據以保持與服務器端的連接
}
public void sessionOpened(IoSession session) throws Exception {
//當網絡連接被打開時此方法被調用,這裏可以對session設置一些參數或者添加一些IoFilter的實現,也可以對客戶端做一些認證之類的工作
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
}
}
//啓動監聽連接的服務器
import org.apache.mina.common.*;
import org.apache.mina.io.*;
import org.apache.mina.io.filter.*;
import org.apache.mina.registry.*;
public class Server{
/** Choose your favorite port number. */
private static final int PORT = 8080;
public static void main( String[] args ) throws Exception
{
ServiceRegistry registry = new SimpleServiceRegistry();
//可以添加各種過濾器,比如線程池過濾器,增加一個線程池處理來自不同的連接
IoAcceptor ioAcceptor = registry.getIoAcceptor();
IoThreadPoolFilter ioThreadPoolFilter = new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(10);
ioThreadPoolFilter.start();
ioAcceptor.getFilterChain().addLast("IoThreadPool",
ioThreadPoolFilter);
// Bind
Service service = new Service( "serviceName",
TransportType.SOCKET, PORT );
registry.bind( service, new ServerHandler() );
System.out.println( "Listening on port " + PORT );
}
}
//如果是連接服務器的可以如下啓動連接請求
import org.apache.mina.io.filter.IoThreadPoolFilter;
import org.apache.mina.io.socket.SocketConnector;
import java.net.InetSocketAddress;
public class Client{
public static void main( String[] args ) throws Exception
{
private static final int CONNECT_TIMEOUT = 3; //設置超時連接時間
//可以添加各種過濾器,比如線程池過濾器,增加一個線程池處理來自不同的連接
IoThreadPoolFilter ioThreadPoolFilter = new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(10);
ioThreadPoolFilter.start();
SocketConnector connector = new SocketConnector();
connector.getFilterChain().addFirst("threadPool",
ioThreadPoolFilter);
//初始化客戶端的監聽處理器
ClientHandler clientHandler = new ClientHandler();
InetSocketAddress address = new InetSocketAddress("serverIp",serverPort);
try {
connector.connect(address, CONNECT_TIMEOUT,
clientHandler);
System.out.println("connect sucessfully!");
} catch(Exception e){
System.err.println("Failed to connect.");
}
}
如果一個協議非常複雜,如果只用一個Io層是非常複雜的,因爲IO層沒有幫助你分離‘message解析’和‘實際的業務邏輯,MINA提供了一個協議層來解決這個問題。
使用協議層必須實現5個接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder:
第一步:實現ProtocolDecoder和ProtocolEncoder,當有IO事件時便先調用這兩個類的方法
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.protocol.ProtocolDecoderOutput;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.protocol.ProtocolViolationException;
import org.apache.mina.protocol.codec.MessageDecoder;
import org.apache.mina.protocol.codec.MessageDecoderResult;
import java.util.*;
public class ServerDecoder implements MessageDecoder {
public ServerTranInfoDecoder() {
}
public MessageDecoderResult decodable(ProtocolSession session, ByteBuffer in) {
//對接受的數據判斷是否與協議相同,如果相同返回MessageDecoderResult.OK,否則返回MessageDecoderResult.NOT_OK,這裏如果要從ByteBuffer讀出數據,需要重新用ByteBuffer.put(ByteBuffer)放回內存中,以便decode方法使用;
return MessageDecoderResult.OK;
return MessageDecoderResult.NOT_OK;
}
public MessageDecoderResult decode(ProtocolSession session, ByteBuffer in,
ProtocolDecoderOutput out) throws ProtocolViolationException {
//根據協議將介紹到的數據(放在ByteBuffer中)組裝成相對應的實體,調用out.write(Object)方法發送給協議層進行業務邏輯的處理,如果成功返回MessageDecoderResult.OK,否則返回MessageDecoderResult.NOT_OK;
out.write(object);
}
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.protocol.ProtocolEncoderOutput;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.protocol.ProtocolViolationException;
import org.apache.mina.protocol.codec.MessageEncoder;
import java.util.*;
public class ServerEncoder implements MessageEncoder{
public static Set TYPES;
public ServerEncoder(){
}
public Set getMessageTypes(){
HashSet set = new HashSet();
set.add(Send.class); //增加要進行編碼的實體類
TYPES = Collections.unmodifiableSet( set );
return TYPES;
}
public void encode( ProtocolSession session, Object message, ProtocolEncoderOutput out )
throws ProtocolViolationException {
//將回應報文實體message編碼層returnStr後發送到客戶端
byte[] bytes = returnStr.getBytes();
ByteBuffer rb = ByteBuffer.allocate(bytes.length);
rb.put(bytes);
rb.flip();
out.write(rb);
}
}
第二步:實現ProtocolCodecFactory
import org.apache.mina.protocol.codec.DemuxingProtocolCodecFactory;
public class ServerProtocolCodecFactory extends DemuxingProtocolCodecFactory {
public ServerProtocolCodecFactory(boolean server) {
if (server) {
super.register(ServerDecoder.class);
super.register(ServerEncoder.class);
}
}
}
第三步:實現ProtocolHandler,在有IO事件發生後,經過decode和encode的處理後就把協議實體交個這個處理器進行業務邏輯的處理,因此實現了協議解釋和業務邏輯的分離,它與IoHandler非常相似,不過這裏處理的是經過編碼與解碼後的對象實體。
import org.apache.mina.common.IdleStatus;
import org.apache.mina.protocol.ProtocolSession;
import org.apache.mina.util.SessionLog;
import org.apache.mina.protocol.handler.DemuxingProtocolHandler;
public class ServerSessionHandler extends DemuxingProtocolHandler {
public ServerSessionHandler() {
}
public void sessionCreated(ProtocolSession session) throws Exception {
}
public void sessionOpened(ProtocolSession session) throws Exception {
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
}
public void sessionClosed(ProtocolSession session) {
}
public void messageReceived(ProtocolSession session, Object message)
throws Exception {
//根據解碼後的message,進行業務邏輯的處理
session.close();
}
public void messageSent(ProtocolSession session, Object message) {
}
public void sessionIdle(ProtocolSession session, IdleStatus status)
throws Exception {
//網絡出現空閒時進行處理,並關掉連接
session.close();
}
public void exceptionCaught(ProtocolSession session, Throwable cause) {
cause.printStackTrace();
//處理所有Handler方法拋出的異常,和Mina架構拋出的異常,並關掉連接
session.close();
}
}
第四步:實現ProtocolProvider
import org.apache.mina.protocol.*;
public class ServerProtocolProvider implements ProtocolProvider {
private static final ProtocolCodecFactory CODEC_FACTORY = new SemsProtocolCodecFactory(
true);
private static final ProtocolHandler HANDLER = new ServerSessionHandler();
public ServerProtocolProvider() {
}
public ProtocolCodecFactory getCodecFactory() {
return CODEC_FACTORY;
}
public ProtocolHandler getHandler() {
return HANDLER;
}
}
這樣協議層便完成了,啓動時跟IO層的差不多,不過我們還可以在IO層和協議層用兩個線程池,如下:
public class Server {
//服務器的監聽端口號
public static final int SERVER_PORT = 8000;
public static void main(String[] args) {
//進行服務器的相關配置
ServiceRegistry registry = new SimpleServiceRegistry();
IoProtocolAcceptor protocolAcceptor = (IoProtocolAcceptor) registry
.getProtocolAcceptor(TransportType.SOCKET);
ProtocolThreadPoolFilter protocolThreadPoolFilter = new ProtocolThreadPoolFilter();
protocolThreadPoolFilter.setMaximumPoolSize(10);
protocolThreadPoolFilter.start();
protocolAcceptor.getFilterChain().addLast("IoProtocolThreadPool",
protocolThreadPoolFilter);
IoAcceptor ioAcceptor = protocolAcceptor.getIoAcceptor();
IoThreadPoolFilter ioThreadPoolFilter = new IoThreadPoolFilter();
ioThreadPoolFilter.setMaximumPoolSize(10);
ioThreadPoolFilter.start();
ioAcceptor.getFilterChain().addLast("IoThreadPool",
ioThreadPoolFilter);
Service service = new Service("TranServer", TransportType.SOCKET,
SERVER_PORT);
//綁定了剛剛實現的ServerProtocolProvider
registry.bind(service, new ServerProtocolProvider());
}
整個MINA框架經常用到的就是這些了,這樣的事件觸發框架和兩層框架使用起來非常方便,不過這種異步框架還是有些非常明顯的缺陷:
第一,MINA只會爲每個Session分配一個線程,也就是隻能一個一個事件按順序執行,就算你在某個方法執行時產生了新的事件,比如收到新的數據,MINA也會先將該事件緩衝起來,所以你在執行某個方法時是不可能執行dataRead方法的,所以MINA框架是不會阻塞的,要想在一個邏輯方法中實現交互是實現不了的,因此要想出另外的實現方法。
第二,如果客戶端發完一個數據給服務器就想馬上得到回覆,而不等整個業務邏輯執行完,也是實現不到的,因爲MINA框架要將整個接收事件處理完了,再把回覆信息發給客戶端。
第三,如果MINA是作爲服務器端等待連接的,當客戶端正常關閉後業務邏輯也可繼續正常執行,但是如果MINA是連接服務器的客戶端,則當服務器關閉後,MINA的session也會關閉。
最後要說明的是MINA使用的線程池是用Leader/Followers Tread Pool實現的,默認最大支持2G的線程。當然MINA框架是開源的,用戶可以根據自己的需要改寫代碼,而其MINA的功能也是不斷可以擴展的。
以上是我使用MINA的經驗總結,其實MINA的相關文檔和例子也介紹了很多了,我這裏算是一個總結吧,不過有很多地方只是我的個人見解,不一定正確,如果有不對的,希望高手可以提出。
(轉自)http://chinasun84.blog.sohu.com/25466493.html
另外:apache mina 這個nio非阻塞式框架的強大實現主要有openfire等,可以看看openfire connection manager