Apache Mina2.x网络通信框架使用入门
? ?關于Apache Mina的文章,資料已經非常多了,我想再多一篇也不過為。另外Main現在3.x版本正在開發中,且已經有M2(里程碑)發布了。
? ?本文中主要針對Mina2.0.9(這個版本也是最后一個2.x版本了)來記錄學習和使用的過程和體會。
? ?Mina2.x的文檔還算比較全面:http://mina.apache.org/mina-project/userguide/user-guide-toc.html?(有些部分可能沒有更新,未來講更多關注和使用3.x)。
開發服務器和客戶端網絡應用
使用Mina開發,客戶端連接器和服務端接收器有更多的相似之處,Mina在API設計的時候使用了更高的抽象如:IoService,對于創建服務器端接收器我們將關注IoAccepor,而客戶端連接器則是IoConnector.
? ? 下面圖1是關于Mina的基本使用的描述:
? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖1
??
? ? 這里對圖1做簡單的說明:
? ? 圖1由大的三部分組成,通過顏色就很容易區分和理解器表示的意思。對于服務器(Server)和客戶端(Client)而言它們都需要中間最大一塊的組成部分,其中包含了配置(Configure),會話數據工廠(SessionDataFactory),過濾器鏈(FilterChina,由多個過濾器組成),監聽器組(有多個Listener組成),處理器(IoHandler);第三部分則可以看出服務器對應的是綁定(bind),客戶端對應的是連接(connect)由此區分了服務器和客戶端。
? ? 說了這么多,就中間部分而言,Mina框架最大程度的解放了開發過程要進行的會話管理,會話數據管理,服務監聽處理,過濾器,服務配置等的實現,其都提供了默認實現,當然可以根據使用情況實現對應的接口來自行處理。
2.過濾器
? Mina中的過濾器的頂層接口是IoFilter,其自身提供了多種過濾器,比如:LoggingFilter,ExecutorFilter,BlacklistFilter(請求地址黑名單過濾),SSLFilter,編碼和解密過濾器等等(更多參考API http://mina.apache.org/mina-project/apidocs/index.html)。
?
? 過濾器是Mina框架中極其重要和有價值的部分,其提供了日志,安全,權限控制,統計等過濾器,并且其是可插拔的,可以通過不同的過濾器以不同的順序組成過濾器鏈將實現不同的功能。另外可以通過實現IoFilter接口或者繼承IoFilterAdapter來創建更多具體業務中需要的IoFilter,當然這么做之前,可以仔細看看Mina的org.apache.mina.filter包下是否已經提供了實現。
3.編碼和解碼
? 編碼和解碼作為網絡應用開發必須面對的問題,而Mina作為全功能的網絡通訊框架,實現對數據報文的編碼和解碼自然是其分內之事,具體使用者可更多關注IoHandler,即具體處理接收和發送報文的相關業務。
? 在org.apache.mina.filter.codec包下有更多的關于編碼和解碼的實現。
? 關于編碼其方式有很多種,比如Protobuf,serialization(對象序列化),JSON,BASE64等,而解碼則涉及到字節流分割的問題,下圖2是三種常用的字節流分割的方式:
?
??圖2
? ?上面三種方式中2和3在Mina中都有對應的實現,比如3特殊字符結尾標記對應的實現有TextLineEncoder和TextLineDecoder,兩者組成了TextLineCodecFactory; 2固定字節的head表示數據字節數有PrefixedStringEncoder和PrefixedStringDecoder,兩者組成了PrefixedStringCodecFactory。
第一種固定長度字節數這種主要應用在傳輸命令的場景中,其傳輸的字節數是固定,應用中可以自己根據具體情況來實現對應的編碼和解碼類。
4.一個具體案例來貫穿全文
? 本案例通過客戶端發送短信信息到服務器,然后服務器將其短信信息的發送者和接受者對調,短信內容設置"OK",發回給客戶端。
? 4.1定義短信格式(protobuf):
??
package?secondriver.mina.bo.protobuf;option?java_package?=?"secondriver.mina.bo.protobuf"; option?java_outer_classname?=?"SmsDataProtocal";message?Sms?{required?string?protocol? =?1;required?string?sender? =?2;required?string?receiver? =?3;required?string?content? =?4; }? 使用protoc命令將定義個消息生成Java類(使用方式可以參考:)。
? 4.2編寫Sms對象的編碼和解密類,這里我們直接編寫編碼解密工程類,其由編碼和解密類組合而成。
??
package?secondriver.mina.bo.protobuf;import?java.nio.charset.Charset; import?java.nio.charset.StandardCharsets;import?org.apache.mina.core.buffer.IoBuffer; import?org.apache.mina.core.session.IoSession; import?org.apache.mina.filter.codec.CumulativeProtocolDecoder; import?org.apache.mina.filter.codec.ProtocolCodecFactory; import?org.apache.mina.filter.codec.ProtocolDecoder; import?org.apache.mina.filter.codec.ProtocolDecoderOutput; import?org.apache.mina.filter.codec.ProtocolEncoder; import?org.apache.mina.filter.codec.ProtocolEncoderAdapter; import?org.apache.mina.filter.codec.ProtocolEncoderOutput;import?com.google.protobuf.ByteString;import?secondriver.mina.bo.protobuf.SmsDataProtocal.Sms;public?class?SmsDataCodecFactory?implements?ProtocolCodecFactory?{private?final?Charset?charset?=?StandardCharsets.UTF_8;private?int?prefixLength?=?4;private?int?maxDataLength?=?1024;@Overridepublic?ProtocolEncoder?getEncoder(IoSession?session)?throws?Exception?{return?new?ProtocolEncoderAdapter()?{@Overridepublic?void?encode(IoSession?session,?Object?message,ProtocolEncoderOutput?out)?throws?Exception?{Sms?sms?=?(Sms)?message;String?content?=?sms.toByteString().toStringUtf8();IoBuffer?buffer?=?IoBuffer.allocate(content.length()).setAutoExpand(true);buffer.putPrefixedString(content,?prefixLength,charset.newEncoder());if?(buffer.position()?>?maxDataLength)?{throw?new?IllegalArgumentException("Data?length:?"+?buffer.position());}buffer.flip();out.write(buffer);}};}@Overridepublic?ProtocolDecoder?getDecoder(IoSession?session)?throws?Exception?{return?new?CumulativeProtocolDecoder()?{@Overrideprotected?boolean?doDecode(IoSession?session,?IoBuffer?in,ProtocolDecoderOutput?out)?throws?Exception?{if?(in.prefixedDataAvailable(prefixLength,?maxDataLength))?{String?msg?=?in.getPrefixedString(prefixLength,charset.newDecoder());Sms?sms?=?Sms.parseFrom(ByteString.copyFrom(msg,charset.name()));out.write(sms);return?true;}return?false;}};} }??
?4.3參見文中1端來寫服務端
? ? 創建IoAccptor對象->設置過濾器->設置IoHandler->配置->綁定到指定IP和端口
? ??
package?secondriver.mina.server;import?java.io.IOException; import?java.net.InetSocketAddress; import?java.util.concurrent.Executors;import?org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import?org.apache.mina.core.service.IoAcceptor; import?org.apache.mina.core.service.IoHandlerAdapter; import?org.apache.mina.core.session.IdleStatus; import?org.apache.mina.core.session.IoSession; import?org.apache.mina.filter.codec.ProtocolCodecFilter; import?org.apache.mina.filter.executor.ExecutorFilter; import?org.apache.mina.filter.logging.LogLevel; import?org.apache.mina.filter.logging.LoggingFilter; import?org.apache.mina.transport.socket.nio.NioSocketAcceptor;import?secondriver.mina.bo.protobuf.SmsDataCodecFactory; import?secondriver.mina.bo.protobuf.SmsDataProtocal.Sms;public?class?SmsServer?{public?static?final?int?PORT?=?9001;public?static?void?main(String[]?args)?throws?IOException?{//?接收器IoAcceptor?acceptor?=?new?NioSocketAcceptor();//?過濾器鏈DefaultIoFilterChainBuilder?builder?=?new?DefaultIoFilterChainBuilder();LoggingFilter?loggingFilter?=?new?LoggingFilter();loggingFilter.setExceptionCaughtLogLevel(LogLevel.DEBUG);builder.addLast("logging",?loggingFilter);builder.addLast("codec",?new?ProtocolCodecFilter(new?SmsDataCodecFactory()));builder.addLast("threadPool",new?ExecutorFilter(Executors.newCachedThreadPool()));acceptor.setFilterChainBuilder(builder);//?設置處理器IoHandleracceptor.setHandler(new?IoHandlerAdapter()?{@Overridepublic?void?messageReceived(IoSession?session,?Object?message)throws?Exception?{Sms?sms?=?(Sms)?message;System.out.println("客戶端發來:");System.out.println(sms.toString());//?服務器發送Sms?serverSms?=?Sms.newBuilder().setProtocol(sms.getProtocol()).setContent("OK").setReceiver(sms.getSender()).setSender(sms.getSender()).build();session.write(serverSms);}});//?配置服務器(IoAccptor)acceptor.getSessionConfig().setReadBufferSize(2048);acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE,?10);//?綁定到指定IP和端口acceptor.bind(new?InetSocketAddress(PORT));} }?4.4 參見文中1端編寫客戶端
package?secondriver.mina.client;import?java.net.InetSocketAddress; import?java.util.Scanner; import?java.util.concurrent.Executors; import?java.util.concurrent.TimeUnit;import?org.apache.mina.core.RuntimeIoException; import?org.apache.mina.core.future.ConnectFuture; import?org.apache.mina.core.service.IoConnector; import?org.apache.mina.core.service.IoHandlerAdapter; import?org.apache.mina.core.session.IoSession; import?org.apache.mina.filter.codec.ProtocolCodecFilter; import?org.apache.mina.filter.executor.ExecutorFilter; import?org.apache.mina.transport.socket.nio.NioSocketConnector;import?secondriver.mina.bo.protobuf.SmsDataCodecFactory; import?secondriver.mina.bo.protobuf.SmsDataProtocal.Sms;public?class?SmsClient?{private?static?InetSocketAddress?server?=?new?InetSocketAddress("127.0.0.1",?9001);public?static?void?main(String[]?args)?throws?InterruptedException?{//?客戶端連接器IoConnector?connector?=?new?NioSocketConnector();//?過濾器connector.getFilterChain().addLast("codec",new?ProtocolCodecFilter(new?SmsDataCodecFactory()));connector.getFilterChain().addLast("threadPool",new?ExecutorFilter(Executors.newCachedThreadPool()));//?處理器connector.setHandler(new?IoHandlerAdapter()?{@Overridepublic?void?sessionCreated(IoSession?session)?throws?Exception?{}@Overridepublic?void?messageReceived(IoSession?session,?Object?message)throws?Exception?{System.out.println("服務器響應:");System.out.println(((Sms)?message).toString());}});//?建立會話SessionIoSession?session?=?null;while?(true)?{try?{ConnectFuture?future?=?connector.connect(server);future.awaitUninterruptibly(100,?TimeUnit.SECONDS);session?=?future.getSession();if?(null?!=?session)?{break;}}?catch?(RuntimeIoException?e)?{System.err.println("Failed?to?connect?with?"+?server.toString());e.printStackTrace();try?{Thread.sleep(5000);}?catch?(InterruptedException?e1)?{e1.printStackTrace();}}}//?客戶端輸入try?(Scanner?scanner?=?new?Scanner(System.in);)?{while?(true)?{String?sender?=?"1814453211";System.out.println("請輸入收信息手機號:");String?receiver?=?scanner.nextLine();System.out.println("請輸入信息內容:");String?content?=?scanner.nextLine();Sms?sms?=?Sms.newBuilder().setProtocol("ip.weixin.com?TC-C/2.0").setSender(sender).setReceiver(receiver).setContent(content).build();session.write(sms);Thread.sleep(10000);System.out.println("是否繼續,回車繼續?,?q?or?quit?退出:");String?line?=?scanner.nextLine();if?(line.trim().equalsIgnoreCase("q")||?line.trim().equalsIgnoreCase("quit"))?{break;}}}session.close(false);connector.dispose();} }? 4.5 啟動服務,啟動客戶端
? ? ? 圖3是運行的結果:
客戶端信息:
服務器信息:
??
?圖3
? ?說明:上面客戶端和服務器端的關于IoHandler直接使用了匿名類的方式對數據的接收做了相應的簡單處理。Sms對象轉換成UTF-8編碼的字符串,采用了3端中編碼和解密的第2中方式,并且傳輸的數據最大長度為1024byte(1k)。另外,Potobuf-java和Mina集成,mina3.x提供了對protobuf定義的消息的編碼和解碼提供了實現支持。
? ?為了需要更多關注Mina3.x,另外Netty的發展勢頭正旺,netty有種子承父業的感覺,也值得擁有!
? ?另外關于使用Mina2.x的一個多客戶端會話的示例見:https://code.csdn.net/snippets/546078.js?
總結
以上是生活随笔為你收集整理的Apache Mina2.x网络通信框架使用入门的全部內容,希望文章能夠幫你解決所遇到的問題。