NIO框架之MINA原始碼解析(五):NIO超級陷阱和使用同步IO與MINA通訊

chaofanwei發表於2014-09-15

NIO框架之MINA原始碼解析(一):背景


NIO框架之MINA原始碼解析(二):mina核心引擎


NIO框架之MINA原始碼解析(三):底層通訊與責任鏈模式應用


NIO框架之MINA原始碼解析(四):粘包與斷包處理及編碼與解碼



1、NIO超級陷阱


   之所以說NIO超級陷阱,就是因為我在本系列開頭的那句話,因為使用缺陷導致客戶業務系統癱瘓。當然,我對這個問題進行了很深的追蹤,包括對MINA原始碼的深入瞭解,但其實之所以會出現這個問題,它的根不是MINA的原因,而是JDK底層的問題。


JDK底層在實現nio時,為了能夠喚醒等待在io上的執行緒,在windows平臺使用了兩個埠建立連線發訊息實現。看如下程式碼:


public class NIOTest {

	@Test
	public void test1(){	

		    final int MAXSIZE=1000;		   
	        Selector [] sels = new Selector[ MAXSIZE];
            try{
                for( int i = 0 ;i< MAXSIZE ;++i ) {
                    sels[i] = Selector.open();
                    Thread.sleep(1000*10);
                }		                
            }catch( Exception ex ){
                ex.printStackTrace();
            }
	}
}


也就是說每呼叫一次Selector.open(),就會佔用兩個隨機可用埠,相互通訊--這就是問題的根源

    當然對於我們的專案來說,這個問題的解決需要分兩步,首先限制埠數到使用者可接受範圍內,這個比較容易,我們可用只呼叫一次Selector.open()方法即可,使用單利模式;第二步,因為Selector.open()方法每次都是使用的系統當前可用的隨機埠,所以就有可能導致佔用客戶業務埠的情況,因此我們必須把Selector.open()所開的埠限制在一定範圍內,最好可以通過程式碼指定使用哪幾個埠,但是直到現在我們都沒有找到,如果親們有方法的話,很感謝能夠告訴我....,。


Selector總結如下:
Windows下,Selector.open()會自己和自己建立兩條TCP連結。不但消耗了兩個TCP連線和埠,同時也消耗了檔案描述符。
Linux下,Selector.open()會自己和自己建兩條管道。同樣消耗了兩個系統的檔案描述符。
來源於:http://blog.csdn.net/haoel/article/details/2224055

    所以,現在我們現在就乾脆用傳統IO與MINA server通訊吧。



2、使用同步IO與MINA通訊


其實非常簡單,唯一的難點就在於使用同步IO與MINA通訊時,怎麼編碼和解碼,還是看下面程式碼吧。


server 端(用的MINA)

/**
	 * 初始化設定,啟動服務
	 */
	public void startServer() {
		logger.debug("mina server start");
		int port = SysEvnVar.managerPort; 
		logger.debug("manager埠號:" + port);
		IoAcceptor acceptor = new NioSocketAcceptor();
		/** 日誌設定 */
		acceptor.getFilterChain().addLast("logger", new LoggingFilter());
		//編碼與解碼工廠,使用的技術就是JDK提供的ObjectOutStream
		ObjectSerializationCodecFactory objsCodec=new ObjectSerializationCodecFactory(); 
		objsCodec.setDecoderMaxObjectSize(DEFAULTDECODER);
		objsCodec.setEncoderMaxObjectSize(DEFAULTDECODER);
		/** 資料轉換,編碼設定 */
		acceptor.getFilterChain().addLast(
				"codec",
				new ProtocolCodecFilter(objsCodec));
		/** 設定業務處理類 */
		acceptor.setHandler(serverHandler);
		/** 設定buffer容量 */
		acceptor.getSessionConfig().setReadBufferSize(DEFAULTBUFFERSIZE);
		/** 設定空閒時間 */
		acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, DEFAULTIDLETIME);

		try {
			/** 繫結埠 */
			acceptor.bind(new InetSocketAddress(port));
			logger.info("mina server bind port ("+port+") sucess");
		} catch (IOException e) {
			logger.error("mina server bind port ("+port+") fail:" + e.getMessage(),e);
		}
	}

client端(使用同步IO)


public void sendMessageToManager(HyRequest request) {

		Socket socket = null;
		BufferedInputStream inBuff = null;
		OutputStream outBuff = null;
		try {
			Integer port = Integer.parseInt(hyGlobalConfigureCacheImpl.get(
					"managerPort").toString());
			String host = hyGlobalConfigureCacheImpl.get("managerIp")
					.toString();
			socket = new Socket(host, port);

			inBuff = new BufferedInputStream(new DataInputStream(
					socket.getInputStream()));
			outBuff = socket.getOutputStream();

			String json = HyJsonUtil.reqeustToJsonStr(request);

			//編碼開始,使MINA能夠解析
			IoBuffer buf = IoBuffer.allocate(64);
			buf.setAutoExpand(true);
			buf.putObject(json);

			buf.flip();
			byte[] bytes =new byte[buf.limit()]; 			
			buf.get(bytes);  
			//編碼結束,直接輸出到客戶端
			
			outBuff.write(bytes);
			outBuff.flush();

			if(request.getOperation() != null && !"quit".equals(request.getOperation())){
				String allreadstr = new String();

				byte[] b = new byte[512 * 1024];
				int len;
				byte[] tmp = new byte[0];
				if ((len = inBuff.read(b)) != -1) {
					tmp = new byte[len];
					System.arraycopy(b, 0, tmp, 0, len);
				}
				loger.debug("len:"+len);

				//解碼開始,byte[]為MINA傳過來的資料
				IoBuffer in = IoBuffer.wrap(tmp);
				Object obj = null;
				try {
					loger.debug("in:"+in);
					obj = in.getObject();
					//解碼結束
					loger.debug("parse success");
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					loger.debug("go to exception");
					loger.warn("nio parse exception",e);
					obj = "exception";
				}
				allreadstr = (String) obj;

				
				loger.info("receive message from "
						+ socket.getRemoteSocketAddress().toString() + ",message:"
						+ allreadstr);
				// 請求訊息轉換為HyResponse物件
				HyResponse response = HyJsonUtil.getHyResponse(allreadstr);
				HyRequest hyrequest = new HyRequest();
				hyResponseDispatcher = hyClientHandler.getHyResponseDispatcher();
				hyResponseDispatcher.responseProcess(hyrequest, response);
				if (hyrequest.getOperation() != null) {					
					sendMessageToManager2(hyrequest);
				}

			}
			

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(null != outBuff)
				outBuff.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			try {
				if(null != inBuff)
				inBuff.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
				}
			}
		}

	}

看程式碼裡面的註釋,怎麼編碼和解碼,其實我是直接看MINA原始碼,把MINA的編碼和解碼方法工具類直接拷到了我們的client端,這樣就輕鬆的實現使用同步IO和MINA進行通訊。





相關文章