深入理解Apache Mina (1)---- Mina的几个类3 ]5 B# z5 @+ d' h# k8 m) I, e7 S! @4 b |
通过对IoService和BaseIoService的比较可以发现,除了 getDefaultConfig()这个方法没有在BaseIoService中实现之外,其他的方法都已经在BaseIoService实现了。这里就有一个问题,为什么BaseIoService只是实现了IoService的部分方法,而没有全部实现IoService的方法呢?通常都知道,接口中的方法是必须要由实现类来实现的,这点是毋庸置疑的。你可以写一个空方法,里面没有任何的逻辑处理,但是你的实现类中却不能没有该方法。但是在Mina 中作为实现类的BaseIoService却没有IoService指定的方法getDefaultConfig(),难道Mina真的有独到之处?不是!仔细看看( n: D3 G4 U; |1 g2 n& q
BaseIoService你就会知道,BaseIoService是一个抽象类,抽象类就是用来被继承的,它提供了一些其子类公用的一些方法,当抽象类实现一个接口时,抽象类可以有选择性的实现其所有子类都需要的实现的一些方法,对于接口中指定法方法,抽象类可以选择全部实现或者部分实现。在Mina中如果没有BaseIoService这个抽象类,而是由 BaseIoAcceptor和BaseIoConnector直接去实现BaseIoService接口,那么必然会导致这个两个实现类中都要重写相应的方法,这样就脱离了面向对象设计的本质,没有达到复用的目的。在BaseIoAcceptor/BaseIoConnector和 BaseIoService之间添加一个BaseIoService就是为了达到代码复用的目的。在这个问题上主要是要记住两点:
1)抽象类在实现接口的时候可以部分或者全部实现接口中的方法。但是当抽象类只是实 3 ^' U i& I4 v% _
现了接口中的部分方法的时候,抽象类的子类必须要实现抽象类中未实现的接口的方法。在此处,IoService的getDefaultConfig()方法在BaseIoService(BaseIoAcceptor
$ I7 X& H: S# k. C$ g; O) r. l
是BaseIoService的子类,但它也是一个抽象类,所以它也没有实现getDefaultConfig()),# O9 K: Z% U. \/ R( J+ G# B8 R5 n& @
getDefaultConfig() 是 由BaseIoAcceptor的子类们来实现的(如SocketAcceptor,这是一个
具体实现类)。所以接口的所有方法必须被具体的实现类实现和抽象类在实现接口的时候可以部分
或者全部实现接口中的方法是不矛盾的。) J! G+ M8 Q2 K M z" {
0 R2 o0 U7 y/ ]- s
2)注意代码的重用。在面向对象的编程语言中都提供了抽象类和接口,抽象类和接口最大的区别
1 V8 k2 c# z. i% \
就是抽象类提供了方法的具体实现,供其子类来调用;而接口只是提供了对方法的声明,其方
法的实现要由其具体实现类来做。在Java中一个子类只能有一个父类,但是却能实现多个接口。7 U4 ~9 v9 h4 R; |( N/ m# W
个人认为接口和抽象类各有特色,接口的使用比较灵活,不同的接口可以让其子类扮演不同的角
色,侧重于类的复用,在很大程度上解决了代码复用的问题;抽象类更侧重的是方法的复用,某
种意义上讲,抽象类的使用对于程序来说使用起来更加轻松,但是是使用抽象类还是接口要根据0 i+ a! b+ B. O- `" U! F4 y& ~% a$ P
具体的情况而定。
对于接口和抽象类的具体的用法请参考闫宏的《Java与模式》中相关部分的讲解。
之所以在这里罗列这么些问题,目的不仅仅是为了讲解Mina的原理,而是想从一个高的角度来看待的这个经典的开源项目,通过对Mina的学习和理解,能够真正的懂得什么是一个项目,什么是面向对象编程,更本质的东西是怎么灵活运用 Java来达到上面的两个目的。这个才是最重要的,哪怕是你在看完本文后对Mina的理解还是有点模糊,但是你至少要知道在编写一个程序的时候怎样从面向对象的角度上去思考一个问题,而不是在用着面向对象的语言写着结构化的程序。这些东西都是自己做开发这么长时间的一些心得,在这里总结出来,目的主要是用于交流和学习,不是在卖弄,只是想让更多的初学者少走一些弯路,懂得学习的方法。
还是回到对Mina的刚提出的那几个问题上来,现在,第一个问题已经解决了,为什么有了一个IoService还要再有一个BaseIoService?答案就是为了代码的复用。
其次,下面开始讨论第二个问题,BaseIoService和IoAcceptor(IoConnector)有什么区别?' c- e2 H9 q1 ], T, ]
在讨论这个问题之前,还是先给出这两个类(接口)提供的方法,如下图:
' d, S9 D2 I, M! P! T
, q& _1 L1 |, \* O5 L+ O8 M, @
在讨论第一个问题的时候我们已经看过了BaseIoService的方法了,但是没有对这些方法的功能做些梳理,现在就对这些方法做些简单的介绍:9 c0 ~- a3 R/ q" f
getFilterChainBuilder()和setFilterChainBuilder():这两个方法主要是对一个服务的IoFilter的操作,关于IoFilter的详细介绍会在后面给出,现在你可以将其理解为是一个处理业务逻辑的模块,例如:黑名单的处理、数据的转换、日志信息的处理等等都可以在这个IoFilter中实现,它的工作原理和Servlet中的过滤器很相似。) {3 X5 N1 j# w* B3 \
addListener()和removeListener():这两个方法通过名字看就可以理解了,就是给当前的服务添加和删除一个监听器,这个监听器主要是用于对当前连接到服务的IoSession进行管理,这个也会在后面做详细的讲解。
getManagerServiceAddress()和getManagerSessions():这两个方法的功能比较相似,一个是获取当前服务所管理的远程地址,一个是获取当前服务所管理的会话IoSession,IoSession对SocketAddress做了一个完整的封装,你也可以先将这两个方法的功能理解为是一回事,具体的区别会在后面给出。isManaged():检测某个SocketAddress是否处于被管理的状态。
getListeners():获取当前服务的监听器。" U, b' x3 c6 H) a
$ |8 W; }, v7 t, b
看了上面对BaseIoService功能的介绍,现在我们可以理解 BaseIoService提供的方法主要是用于对当前服务的管理。那么要管理一个服务,前提条件是这个服务必须存在,存在的前提是什么,就是要启动一个服务,或者是连接到一个远程主机上,这两个任务分别是IoAcceptor和IoConnector来完成的,此处要注意的是这两个对象都是接口,没有具体的实现,具体的实现会由下面介绍的它们相关的子类(SocketAcceptor等)来实现。这样IoAcceptor/IoConnector的功能我们就可以总结出来了,就是启动和停止一个服务。
0 w7 V, _$ A3 u6 O" E+ W9 [
对于一个完整的服务来说,既要有启动这样的前提条件,还要有对服务的管理和对服务响应的逻辑处理,这两个缺一不可,回到第二个问题,BaseIoService和IoAcceptor(IoConnector)有什么区别?区别就在于它们实现的功能不一样,但都是为了一个完整的服务来打基础的,两者缺一都不能称为一个完整的服务。这三个都是IoService子类(子接口),oService只是提供了一些服务应该具有多基本的方法,BaseIoService提供了IoService部分方法的具体实现,而 IoAcceptor(IoConnector)是对特定服务要具备的操作的做了一些扩展,这样一个服务完整的模型正在逐渐向我们清晰的展现出来。0 ~- e) l2 U6 A E2 M' w* g3 ~
再次,讨论一下第三个问题。 BaseIoAcceptor(BaseIoConnector)为什么不去直接实现IoService,而是又添加了 IoAcceptor(IoConnector)?这个问题其实在上面已经有所涉及,为了达到对象复用的目的,所以Mina的设计者给出了一个 BaseIoService,IoAcceptor(IoConnector)是实现一个特定服务必须要提供的一些方法。更具体一点,IoAcceptor(IoConnector)是为了一个特定的服务(服务器/客户端)而设计的,而IoService只是提供了一个服务应该具备的一些基本的方法。所以在Mina中给出了一个针对具体服务的一个接口IoAcceptor(IoConnector),这样 BaseIoAcceptor(BaseIoConnector)就提供了一个服务所必备的一些条件。因为它即实现了 IoAcceptor(IoConnector)接口又继承了抽象类BaseIoService,这样就实现了IoService中的所有方法,并且也添加了特定服务应该具有的方法(即IoAcceptor(IoConnector)中的方法)。以上就是第三个问题的答案。
Mina中提供的几个特定的服务0 n9 ]5 `0 V7 Y1 e
从上面的讨论中我们已经知道了Mina上层的类和接口的一些功能。即图中所示的已经在上面解释清楚了。* ~, e: ]* s8 B
! @: h* S7 ^3 a% r- p
, V- S+ N% a" P( I2 `& u- ]
7 R: x- o8 t* `( W' s6 L8 h# a% i
在此我们可以把Mina的上层结构简单的定义为Mina的“抽象层”,既然有了抽象层,肯定就会有其具体实现,抽象中最重要的两个类是BaseIoAcceptor和BaseIoConnector,它们分别是用于服务器和客户端的一个入口程序。
首先,说一下BaseIoAcceptor中的三个具体实现类:$ x- Y5 [: U6 P# S! T) B
DatagramAcceptorDelegate:数据报UDP通信的服务器入口程序。该类使用UDP协 议进行通信,UDP协议主要是用在视频、远程服务的监听(如心跳程序)中等数据传输 要求不是很高的地方。3 |- U$ ?1 `, w \' v6 |
VmPipeAcceptor:虚拟通道(VM)通信的服务器入口程序。虚拟管道协议主要用于无线通信方面。 ) _ C. t8 a3 F4 D' a
SocketAcceptor:TCP/IP通信的服务器入口程序。这个是比较常用的协议,该协议主要 数据传输要求较高的地方,比如实时系统、游戏服务器等。
BaseIoAcceptor及其子类" V- \% R' G7 h9 \3 _3 W1 `/ i( ?
与BaseIoAcceptor相对应的就是BaseIoConnector,该类主要用于客户端程序。其具体的子类就不再赘述,这里只给出BaseIoConnector及其子类的结构图。0 B1 w5 _/ H$ I( E/ ]; I8 `
. Z/ [6 U, @* h# F6 L; B6 r
- }" G/ A) x% }, Q1 ?- k+ L
BaseIoConnector及其子类
关于SocketAcceptor、IoFilter、IoProcessor、IoHandler等会有专门的文章来讨论。这里就不在对这些组件类做详细的说明了。
从名字上看知道IoFilter应该是一个过滤器,不错,它确实是一个过滤器,它和Servlet中的过滤器类似,主要用于拦截和过滤I/O操作中的各种信息。在Mina的官方文档中已经提到了IoFilter的作用:
(1)记录事件的日志(这个在本文中关于LoggingFilter的讲述中会提到): p! v9 v7 J; x* F: C& i' N) E& `
(2)测量系统性能
(3)信息验证
}* h& y7 ?2 M5 j" j: Q
(4)过载控制
! n' x. i# T6 a% O
(5)信息的转换 (例如:编码和解码,这个会在关于ProtocolCodecFilter的讲述中会提到)# D4 b9 p& }( _) d0 K1 ]* ?+ g
(6)和其他更多的信息+ C9 J! {3 A% r$ }" D
: ]( `4 X5 d/ O+ y9 }( ~
还是上一篇文档一样,先提出几个问题,然后沿着这几个问题的思路一个一个的对IoFilter进行讲解。7 K& A: ~' Q( [; S& p
/ T. h _! j* b2 m% o
(1)什么时候需要用到IoFilter,如果在自己的应用中不添加过滤器可以吗?
& R2 \! x, e; j+ K3 s$ g$ [
(2)如果在IoService中添加多个过滤器可以吗?若可以,如何进行添加,这多个过滤
) a8 }/ v3 _5 x+ W
器是如何工作的?1 d+ z8 D9 b$ d& x* ~! n6 L
1 N( y' J7 B2 D- r$ E0 P7 a9 a
(3)Mina中提供了协议编、解码器,IoFilter也可以实现IO数据的编解码功能,在实际
的使用中如何选择?# n% R1 s7 L$ p+ H% c* T; Q
在开始对上面的问题进行讨论前,为了对IoFilter提供的方法有一个具体的了解,先对Mina自身提供的一个最简单的过滤器进行一些讲解----LoggingFilter(源码在附件中,配有中文翻译)。
& T% U( k8 v+ r5 N b2 \
首先还是看一下LoggingFilter中提供的几个方法。列举如下(方法中的参数就不再给出,完整方法的实现请参考附件中LoggingFilter的源码):% x* }$ j5 W" D; u7 F# A3 N, F& Q
- O: j- a0 ^1 k. I7 j% c8 @6 ?
(1)sessionCreated()
) |3 P. r i/ f; A
(2)sessionOpened()
(3)sessionClosed()
B" y* u+ s/ }0 j
(4)sessionIdle()
(5)exceptionCaught()& q: V- B+ }' H5 k8 T
(6)messageReceived()9 O! I2 G. M" a6 t( S- g
& K$ K' X, u0 R" S. c: g
(7)messageSent()+ p0 v, K. H4 c& @* Y
% {& {- _8 t& q' g5 N
(8)filterWrite()# n* s+ n; Q' B. h& h: x8 S
(9)filterClose()
; \4 z5 x* D& k
这几个方法都由相应会话(或者说是连接的状态,读、写、空闲、连接的开闭等)的状态的改变来触发的。当一个会话开启时,LoggingFilter捕获到会话开启的事件,会触发sessionCreated()方法,记录该会话开启的日志信息。同样当一个会话发送数据时,Logging捕获到会话发送消息的事件会记录消息发送的日志信息。这里只是给出messageReceived()的完成方法的实现,其他方法的完整实现请参考附件中 LoggingFilter的源码。
/**
* 记录会话接收信息时的信息,然后将该信息传递到过滤器链中的下一个过滤器
* */
public void messageReceived(NextFilter nextFilter, IoSession session,9 Z6 C' Z) ?! F; _9 P. }" [
Object message) {
if (SessionLog.isInfoEnabled(session)) {8 ?& C$ ]1 a8 A2 Y+ W: f
SessionLog.info(session, "RECEIVED: " + message);
}
nextFilter.messageReceived(session, message);
} 7 `" q% v: C+ ?3 T5 N. y; a* s
LoggingFilter继承与IoFilterAdpater,IoFilterAdpater是IoFilter的一个实现类,该类只是提供了IoFilter方法的简单实现----将传递到各方法中的消息转发到下一个过滤器中。你可以根据自己的需求继承IoFilterAdpater,并重写相关的方法。LoggingFilter就是重写了上面提到的几个方法,用于记录当前的会话各种操作的日志信息。通过上面的例子,我们可以大体的了解了IoFilter的基本功能:根据当前会话状态,来捕获和处理当前会话中所传递的消息。$ }0 h t! f i0 Z% ?- ?
IoFilter的UML图如下:$ p( i2 ^: U: p2 |5 ^( ?
从上面的类图我们可以清晰的看到IoFilter是一个接口,它有两个具体的实现类:
IoFilterAdpater:该类提供了IoFilter所有方法的方法体,但是没有任何逻辑处理,你可以根据你具体的需求继承该类,并重写相关的方法。IoFilterAdpater是在过滤器中使用的较多的一个类。
ReferenceCountingIoFilter:该类封装IoFilter的实例,它使用监视使用该IoFilter的对象的数量,当没有任何对象使用该IoFilter时,该类会销毁该IoFilter。
IoFilterAdpater有三个子类,它们的作用分别如下:: ?! c9 {" J l9 N5 f5 N+ i* [
LoggingFilter:日志工具,该类处理记录IoFilter每个状态触发时的日志信息外不对数据做任何处理。它实现了IoFilter接口的所有方法。你可以通过阅读该类的源码学习如何实现你自己的IoFilter。 {1 C6 O5 f/ p$ b8 D6 n4 F1 b
ExcuterFilter:这个Mina自身提供的一个线程池,在 Mina中你可以使用这个类配置你自己的线程池,由于创建和销毁一个线程,需要耗费很多资源,特别是在高性能的程序中这点尤其重要,因此在你的程序中配置一个线程池是很重要的。它有助于你提高你的应用程序的性能。关于配置Mina的线程池在后续的文档中会给出详细的配置方法。: s( x* ^4 E0 ^8 o% |+ F
# h" j4 v- ?3 u" n- ?
ProtocolFilter:该类是Mina提供的一个协议编解码器,在socket通信中最重要的就是协议的编码和解码工作,Mina提供了几个默认的编解码器的实现,在下面的例子中使用了 ObjectSerializationCodecFactory,这是Mina提供的一个Java对象的序列化和反序列化方法。使用这个编解码器,你可以在你的Java客户端和服务器之间传递任何类型的Java对象。但是对于不同的平台之间的数据传递需要自己定义编解码器,关于这点的介绍会在后续的文档中给出。
; p$ k& v' o9 z# l$ e* @; O
为了更加清楚的理解这个过滤器的作用我们先来看一个简单的例子,这个例子的功能就是服务器在客户端连接到服务器时创建一个会话,然后向客户端发送一个字符串(完整的源码在附件中,这里只给出程序的简要内容):
* @6 Z1 j& u, t; t# }$ |
Java代码
ServerMain: - r2 m+ }8 n7 j+ v+ |% Y, e
public class ServerMain { + ^" P- }3 d9 t4 @4 v% Y
) a" n3 N$ I7 }) p: M
public static void main(String[] args) throws IOException {
SocketAddress address = new InetSocketAddress("localhost", 4321); 7 m2 P; e7 M( s) H1 d {. ~
IoAcceptor acceptor = new SocketAcceptor(); ( C( |! {, d( q8 M0 ~
IoServiceConfig config = acceptor.getDefaultConfig();
9 \& j2 `% P: y$ ~& ]
// 配置数据的编解码器 + K+ |3 l; @5 T- K* W! v
config.getFilterChain().addLast("codec", , k Y( }6 m( D# ^' @& Q7 w
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
" x+ l' w6 S; }& }2 w. ^
// 绑定服务器端口 5 y$ D! x) `+ ]- U" }, l
acceptor.bind(address, new ServerHandler());
System.out.println(" 服务器开始在 8000 端口监听 .......");
}
}
o+ n5 c0 U8 z$ j1 D2 ]$ d' _* A2 Z
ServerHandler: ( T- _4 Q: N8 o
public class ServerHandler extends IoHandlerAdapter {
' D6 |2 o, E7 d0 L0 l
// 创建会话 & u6 T3 [$ T0 I) w# b( r
public void sessionOpened(IoSession session) throws Exception { 2 Y1 R8 |% n1 k: t+ w1 A# Q
System.out.println(" 服务器创建了会话 ");
session.write(" 服务器创建会话时发送的信息 。");
}
/ j% M6 Y- F* f6 ]! `
// 发送信息 $ r# ~. }# j' v" B
public void messageSent(IoSession session, Object message) throws Exception { 9 E( ^& s; j$ b) H
} 6 m: \$ F4 V; L+ ]- A
( p0 t5 W2 O0 u3 E4 g& |) m
// 接收信息
public void messageReceived(IoSession session, Object message)
throws Exception { + Z* x. r3 y* Q0 ^" m
}
}
ClientMain: ( K' ~' \! v z
public class ClientMain {
public static void main(String[] args) { - D7 A# ^5 C7 E
9 X0 T' @7 t! N& p
SocketAddress address = new InetSocketAddress("localhost", 4321);
IoConnector connector = new SocketConnector();
IoServiceConfig config = connector.getDefaultConfig(); / P+ [0 I: j3 q
// 配置数据的编解码器 & B, M7 c9 J0 r+ c {
config.getFilterChain().addLast("codec", / W, ?8 N% T' l2 D
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
& }- `' }) H' N5 [" [$ G6 k; p
config.getFilterChain().addLast("logger", new LoggingFilter());
, ^" F4 c) K* Z5 y
// 连接到服务器
connector.connect(address, new ClientHandler()); 6 r; i7 r' u: ?1 ~
System.out.println(" 已经连接到了服务器 " + address); 7 K7 ?3 Y8 E' s) Z9 d* W" o% s/ L6 C0 I
} , j( Z2 d, \2 u' }9 C
} ; R# n; V, n9 Y: W& p) _
2 [4 ~) Z$ q2 h
ClientHandler:
public class ClientHandler extends IoHandlerAdapter { % u. X- O; t) u. _8 r3 h
// 发送信息 & [! w, n' z1 R& v1 B
public void messageSent(IoSession session, Object message) throws Exception {
}
8 v# ~! `+ g/ u1 r# K
// 接收信息
public void messageReceived(IoSession session, Object message) ~4 X0 g$ U4 ~
throws Exception {
System.out.println(" 客户端接收到的服务器的信息是 " + message); & Z! Q) i+ G/ T6 Q
}
}
其中ServerMain和ClientMain分别是服务器和客户端的主程序,ServerHandler和ClientHandler是服务器和客户端的数据处理句柄,关于IoHandler会在后面的文档中做详细的讲解,这里只是简单说明一下,IoHandler主要是对数据进行逻辑操作,也可以理解为程序的业务逻辑层。其中:
Java代码 5 C, E! d1 m( X% u# L/ m- M( A
// 配置数据的编解码器
config.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
这行代码的功能是将网络传输中的数据在发送时编码成二进制数据,解码时将二进制数据还原成一个对象或者是基本类型的数据。: w" b4 d7 C W, O) g8 d: N; M* D2 ^
Java代码7 a5 t6 C# q4 B) y
9 ~! K; c6 Q8 P
运行这个程序会得到如下结果:
已经连接到了服务器 localhost/127.0.0.1:4321
2009-7-9 23:36:46 org.apache.mina.util.SessionLog info
信息: [localhost/127.0.0.1:4321] CREATED
2009-7-9 23:36:46 org.apache.mina.util.SessionLog info
信息: [localhost/127.0.0.1:4321] OPENED
2009-7-9 23:36:46 org.apache.mina.util.SessionLog info
信息: [localhost/127.0.0.1:4321] RECEIVED: 服务器创建会话时发送的信息 。
客户端接收到的服务器的信息是 服务器创建会话时发送的信息 。
' i, p; _4 N# k3 }
其中的红字部分是LoggingFilter打印出的事件信息。黑体部分是程序中System.out的输出。在ClientMain中的这两行代码是向过滤器链中添加IoFilter:4 O2 a& I* n5 Y: D+ v. x) s
Java代码
. X* W' o ?: G- y6 _+ H/ G( J+ c4 V8 u
// 配置数据的编解码器
config.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); 6 b) F8 i) t! B3 t, Y
config.getFilterChain().addLast("logger", new LoggingFilter());//添加日志工具
上图表示了数据在本程序中通过过滤器链的过程,日志过滤器是根据会话 (IoSession)的状态(创建、开启、发送、接收、异常等等)来记录会话的事件信息的,编解码器是根据会话的接收和发送数据来触发事件的,从这里我们也可以了解通过过滤器我们可以专门针对会话的某个或某几个状态来专门处理相关的事件,如异常事件,我们可以专门定义一个Exception的 IoFilter来处理Mina在通信中所发生的异常信息。! z4 ]& t& [$ O
还有一个比较有意思的问题是,假如我们将上面过滤器的顺序该成下面的样子:
$ A1 }1 H' f2 m5 r* F7 n& D
Java代码
8 R9 Y4 T% J* Z0 x7 P# D
config.getFilterChain().addLast("logger", new LoggingFilter());//添加日志工具
// 配置数据的编解码器 2 g) z" j- v5 n, S2 O! c$ g& g
config.getFilterChain().addLast("codec", ' S Q" m. }( e/ {
new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); : D* S& _* B0 D: G! v" j
6 _# B- e% n2 W7 E$ c
程序的执行结果如下: ' P/ G- o }1 a1 T4 S5 K
已经连接到了服务器 localhost/127.0.0.1:4321
2009-7-10 0:30:12 org.apache.mina.util.SessionLog info
信息: [localhost/127.0.0.1:4321] CREATED , w5 g$ j% P q* Q* G1 }# F
2009-7-10 0:30:12 org.apache.mina.util.SessionLog info : t9 c% N: c8 b7 [% e
信息: [localhost/127.0.0.1:4321] OPENED
2009-7-10 0:30:12 org.apache.mina.util.SessionLog info
信息: [localhost/127.0.0.1:4321] RECEIVED: DirectBuffer[pos=0 lim=56 cap=1024: 00 00 00 34 AC ED 00 05 74 00 2D 20 20 E6 9C 8D E5 8A A1 E5 99 A8 E5 88 9B E5 BB BA E4 BC 9A E8 AF 9D
E6 97 B6 E5 8F 91 E9 80 81 E7 9A 84 E4 BF A1 E6 81 AF 20 E3 80 82] 3 L+ M9 i9 g! j% ?
客户端接收到的服务器的信息是 服务器创建会话时发送的信息 。
很明显的是在顺序变化了之后,日志中多了接收到的二进制数据,这是因为在上面数据已经有解码器将数据还原成了Java对象,所以我们就看不到二进制数据了,而在顺序变换后,由于先执行的是打印信息,此时的数据还没有还原成java对象,所以接收到的数据是二进制的。* Y, Q- \* i# A# `
在上面的例子中我们清楚了整个IoFilter或者是IoFilter的工作流程,那么IoFilter在Mina中的作用如何?所有的数据在发送到Mina程序中时,数据都是先通过IoFilter,经过处理后再转发到业务层。这里IoFilter就起到了一个承上启下的作用。! m3 S& z) Q0 n/ q5 l) T' g
. L6 k9 E0 O. I' H0 [% m
到这里我们就可以回答本文开始提到的问题了:4 O( B3 x* J3 J9 I
(1)什么时候需要用到IoFilter,如果在自己的应用中不添加过滤器可以吗?
在你自己的程序中可以添加过滤器,也可以不添加,但是在数据发送之前,所发送的数据必须转换成二进制数据,这个可以有IoFilter完成,也可以由ProtocolCodecFilter完成(关于这个问题会在后面的文章中详细讲述),否则Mina会抛出Write requests mustbe transformed to class org.apache.mina.common.ByteBuffer:异常。这是因为网络中传输的数据只能是二进制数据。因此无论添加不添加过滤器,都必须将要发送的数据转换成二进制数据。* w9 i+ V$ j, U X
(2)如果在IoService中添加多个过滤器可以吗?若可以,如何进行添加,这多个过滤器是如何工作的?
在IoService中可以添加多个过滤器,这个在上面的程序中已经给处理,添加的方式也很简单,通过程序一目了然。' @; z. j! M4 ]7 }8 I; x8 t, f9 S4 a+ c
(3)Mina中提供了协议编、解码器,IoFilter也可以实现IO数据的编解码功能,在实际的使用中如何选择?
Mina的编解码器在Mina的使用中是最关键的一个问题,特别是在不同语言之间进行通信的时候,比如Java和C/C++等,由于Mina自身没有提供这些编解码器,所以需要自己来实现。Mina提供了一个Decoder/Encoder,你可以实现两个类来完成不同平之间的通信。关于这个问题会在后面的文档给出具体的实习方法。
至此,关于IoFilter的作用就讲述完了,希望对你能有所帮助。
在上一篇文档中我们已经了解了IoFilter的用法和其在Mina中的作用,作为Mina数据传输过程中比较重要的组件,IoFilter起到了承上启下的作用----接收数据,编/解码,将数据传递到逻辑层,当数据传递地到逻辑层时,IoFilter的使命就完成了,那么逻辑层的数据由谁来处理呢?如何处理的?这就是本文要讲述的内容----IoHandler。
/ A6 N( `' K& n3 q* G2 z: i
在介绍IoFilter的时候,文中首先是从IoFilter 的结构和其在Mina中的作用谈起的,最后添加了一个使用IoFilter的例子,之前我将其传给几个同学看时,感觉这种方式比较晦涩,应该将例子提到前面,由于时间的关系我不能在对IoFilter的介绍做过多的修改,所以在本篇文档中我就先以一个例子开头,介绍IoHandler,希望这种讲述方式能对你理解Mina有更多的帮助。
好了,言归正传,我们的例子还是以上篇文档中的IoFilter的例子为基础,在此基础上着重突出IoHandler的作用。/ U2 T8 u. @ \$ @+ X9 T6 Y
% X0 h9 k [0 A6 x, o2 e4 n) D
Java代码
% f" y2 U4 U1 J* ~2 `) t3 M* c, L
ServerMain:
public class ServerMain { * ]6 C, Y- G9 y$ t* E
public static void main(String[] args) throws IOException { / I8 q' M& p" l7 N; _: D V
SocketAddress address = new InetSocketAddress
("localhost", 4321);
IoAcceptor acceptor = new SocketAcceptor();
IoServiceConfig config = acceptor.getDefaultConfig 8 B8 F8 T9 I) Z5 g0 p8 Z
9 @# X. e( R; ~* }) d9 Y
();
7 F! x& h9 g7 J5 h
// 配置数据的编解码器
config.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new
ObjectSerializationCodecFactory()));
config.getFilterChain().addLast("logger", new
LoggingFilter());
// 绑定服务器端口
acceptor.bind(address, new ServerHandler());
System.out.println(" 服务器开始在 8000 端口监听
......."); 9 D2 y' a4 r5 C" A1 D# q
}
}
ServerHandler:
public class ServerHandler extends IoHandlerAdapter { 3 _4 q% V3 G: J1 A0 X" Y8 p
// 创建会话
public void sessionOpened(IoSession session) throws 2 n9 o" q: s+ p, L% y
Exception {
System.out.println(" 服务器创建了会话 "); " f+ H! t! t6 v7 O3 y
session.write(" 服务器创建会话时发送的信息 。"); ; ^$ s2 L7 d' N- D/ {
}
// 发送信息
public void messageSent(IoSession session, Object message) 5 U) v: d6 S ^4 s" K2 w
throws Exception { 0 Q* e3 d! o+ d% C, S0 @
}
// 接收信息 0 Y6 c3 L4 a9 g2 r
public void messageReceived(IoSession session, Object / `4 E$ L( F1 v. i- F( f
6 Q P* R' X4 Z! \
message) ! u& r4 t' A4 \; ?
throws Exception { / }+ V* l! d% |9 ]1 ? E
}
} 2 o# _& c+ }! q @. z+ C+ @7 R
ClientMain: 5 _# a9 A: y t" J9 e9 k
public class ClientMain {
public static void main(String[] args) { % o, \) h0 [+ w! c1 J" I. N
SocketAddress address = new InetSocketAddress
+ }8 E7 c+ B; H5 y" O
("localhost", 4321); , y# s+ Y' ^- t( ? J6 @
IoConnector connector = new SocketConnector();
IoServiceConfig config =
connector.getDefaultConfig(); 7 ?9 O. ]8 b4 v; ]# H
// 配置数据的编解码器 5 o- A5 m$ X5 s% W4 B2 D/ W9 I/ t
config.getFilterChain().addLast("codec", ' g, t; X6 Y* l' j# j/ c3 I( V
new ProtocolCodecFilter(new
ObjectSerializationCodecFactory())); ' @5 E. ~6 C% h1 y
config.getFilterChain().addLast("logger", new 4 q0 Y# `2 p: ~. e! P
LoggingFilter()); * `% Q* y' e9 F+ f
// 连接到服务器
connector.connect(address, new ClientHandler());
System.out.println(" 已经连接到了服务器 " + address);
}
} ^' M H) _# o- b. \$ Y) e
ClientHandler: 6 \! V* z/ t7 ~4 Z2 S: V
public class ClientHandler extends IoHandlerAdapter {
// 发送信息 , {) w' o4 @# e/ q; C' V3 T
public void messageSent(IoSession session, Object message)
throws Exception { 1 c" u4 L7 P+ `7 ?7 L
}
// 接收信息 $ u1 w5 g2 V% w+ D
public void messageReceived(IoSession session, Object
$ H0 k4 o2 r" h6 g9 ]1 v
message)
throws Exception {
System.out.println(" 客户端接收到的服务器的信息是 "
+ message); ( \5 D3 U! X; w4 G2 N$ j
} 6 v+ N I; C7 {2 `1 Y# }
}
上面给出里这个例子中的主要的代码,当先后启动服务器和客户端后,服务器会在客户端连接到服务器后发送一个字符串的消息。这个消息的发送就是在 IoHandler中发送的。IoHandler在Mina中属于业务层,这里的 IoHandler更相是J2EE中的Servlet的作用,在IoHandler中你可以不用考虑底层数据的封装和转换,前提是你已经在 IoFilter中已经完成了数据的转换。这里需要提到的一个是,所谓数据的转换,是指将二进制数据转换成Java中的可用对象或者是基本类型的数据。由于网络传输中传输的都是二进制数据,这就需要有一个专门的数据转换层,就Mina中的编解码器来实现这个功能。如果使用RMI,对于这个问题应该不陌生,二进制和对象之间的转化过程其实就是对象的序列化和反序列化的过程。关于Mina中实现对象的序列化和反序列化会在后续的文档中详细介绍,在此不在赘述。
0 F4 B; F+ s5 T* Z) a
既然IoHandler是逻辑层,我们就用IoHandler实现一个简单的逻辑实现。先听一个小故事:一个淘气的小孩要去KFC买汉堡,由于KFC生意比较好,人比较多,服务员忙不过来,于是KFC专门设立了一个自动售汉堡的机器,这个机器只是一个简单的数据收发装置,(由于汉堡的价格时常变化,所以价格会实时更新,因此该机器需要和KFC的汉堡的价格服务器相连)小朋友买汉堡时只要向机器中投入硬币,机器就会查询服务器,看价格是否符合,若符合,则送给小朋友一个汉堡
,若不符合则提示小朋友钱不够,买不到这个汉堡。% P2 I+ B# J! H8 _* _. V
8 X' r% f. _2 } @
上面是我自己虚构的一个小故事,我们先不管现实中的KFC是如何运作的,我们就当故事是真的了,那么现在这个小的项目分配给了我们,我们需要抽象出它的需求:5 t( G0 c+ P+ Y$ B& X; m
客户端要向服务器发送数据,查询价格,根据价格是否合理给出相应的显示服务器接收客户度的价格查询请求,根据服务器中存储的价格信息,返回响应的结果。
根据上面的需求,我们使用Mina来实现上面的服务器和客户端,程序的代码如下(完整代码在附件中):
' j/ A8 n5 l" g# U- Q
Java代码
KFCFoodPriceHandler(服务器句柄):
public class KFCFoodPriceHandler extends IoHandlerAdapter { ) {0 T9 j9 w5 c5 @
// 创建会话 $ m+ L+ I+ ]6 a" ?5 [
public void sessionOpened(IoSession session) throws 7 ^# h3 a# @' Q" G
Exception { 8 b6 l$ A) f6 k7 P7 x7 @2 J) t6 P
// System.out.println(" 服务器创建了会话 "); 2 e' }1 M3 O- j& t
} 0 U" l0 k/ R9 h! _
2 g# S- z* k8 k( h& w& }6 a
// 接收信息 H" S# L5 y9 K/ Z& A: f
public void messageReceived(IoSession session, Object
/ b6 q' T2 r, t* ]7 b: ~1 r- Q
message) 0 a3 ]' K' s, @
throws Exception {
HashMap<String, Object> map = (HashMap<String, : E, c: ~4 j+ U8 b# R- x3 ]
6 h; E) A& i7 z- I, L, Z
Object>) message; . H8 C$ |9 _% p$ B# Q
String buythings = (String) map.get("购买"); 2 L( r3 N" F- V
// System.out.println(" 服务器接收到的信息 " + ( z- F& ?9 G) \% i0 i
) o! ^1 N6 j# J* Z1 x; B! n0 @5 O
buythings);
if (buythings.equals("汉堡")) {
HashMap<String, Object> map2 = new # x! ], t4 b- e+ n
. W- V& K$ D" d/ b% r: v" }
HashMap<String, Object>();
map2.put("食品", "汉堡"); - T* |! C! R5 l4 ^6 K
map2.put("价格", 4); 3 f* X& g& |1 h
session.write(map2);
} else if (buythings.equals("鸡翅")) {
HashMap<String, Object> map2 = new ( B# F s0 \5 Z3 r
' F- |2 m# @- I# n" A# s, }0 f
HashMap<String, Object>(); " }( ~: ]; Y3 u+ ^
map2.put("食品", "鸡翅"); % v! s# {7 K$ C
map2.put("价格", 5); - A1 |1 J% y$ g; e$ l9 y) a
session.write(map2); 5 f; K7 \5 D3 Q5 b+ H/ M- i
} else {
session.write(" 该种物品已经出售完毕,谢谢 $ B5 D9 a( R* v1 l2 x- K
) w3 D% r2 w! `. \, K
惠顾!");
} & \8 c# ?7 z' ~8 z. U
} 2 L, v, I4 ~1 F; |$ |4 ~1 }! o% Q
} ; r( Q7 f3 U9 A
6 u' Z5 B2 O/ r, I6 O5 X
KFCSellerHandler(客户端句柄): - ~5 q5 X7 V+ M7 [
public class KFCSellerHandler extends IoHandlerAdapter { ' F: a$ D3 ~+ B" |; e$ F( U
; L; G+ ~% o) E) o! I
private Integer childInputMoney_Ham = 4; $ | Q$ D& \$ [8 X9 i% T
private Integer childInputMoney_Chick = 5;
// 创建会话 " E7 e& [4 o+ g j. m: {
public void sessionOpened(IoSession session) throws . R& ?0 P: L% }2 i' u( _) y
/ m$ A8 B# p$ j# W% d3 x
Exception { 0 N# V0 e. R3 y3 X$ R; g3 ]; k* U
* g* u. v' Q Y9 i( ^: U
HashMap<String, Object> map = new
HashMap<String, Object>();
map.put("购买", "汉堡");
session.write(map);
}
. {1 k/ H5 @' i2 ~8 b
// 接收信息
public void messageReceived(IoSession session, Object
. t# ^# p* @, _' R; ]. C$ R- t8 j
message) ( F. E! E6 X5 j" m9 C& G
throws Exception { L% G( a9 H% }, t
// System.out.println(" 客户端接收到的服务器的信息是 " 6 ~2 o& o$ q7 f
// + (HashMap<String, Object>) * K- q; R) p8 i0 V
* V% P. J; u! u1 [! g4 d) T z; h
message);
HashMap<String, Object> priceInfor =
(HashMap<String, Object>) message; 5 z* K! L2 \' x' l
// System.out.println("============" +
priceInfor.get("食品"));
String foodName = (String) priceInfor.get("食品"); 7 Z9 v6 C( `; y: h3 A) w
if (foodName.equals("汉堡")) { 8 y: ~% \% T- O3 N1 Y
Integer foodPrice = (Integer) priceInfor.get & I) ~) n4 V/ t. S* s
("价格"); + u0 t& C! A' z+ S
if (foodPrice.equals ! X" e" N: y4 n# g3 l9 N
N" y3 d- ^& h
(childInputMoney_Ham)) {
System.out.println(" 您好,请收好
你的汉堡,欢迎下次光临!");
} else {
System.out.println(" 对不起,你投 + n9 K& }: j/ c- ]" r2 }, I/ F- I; O
如的钱币数量不够,钱已经如数归还,请收好!"); + ?: s- [5 p) p s
} 3 p5 j# ^7 R% S+ \. s5 t. p
} else if (foodName.equals("鸡翅")) {
Integer foodPrice = (Integer) priceInfor.get
/ M. k, |7 ]$ R. _6 W: F2 Q9 h
("价格"); 3 _$ [9 E' x; w0 `/ [
if (foodPrice.equals
/ w* S/ T% \3 {3 |' K0 \7 `
(childInputMoney_Chick)) {
System.out.println(" 您好,请收好 2 ~- Z0 p& e# b" p" c0 ]
你的汉堡,欢迎下次光临!");
} else { / Z4 u0 o: |) L- H- p
System.out.println(" 对不起,你投
! P# z1 t% t, G: P# k1 D" b$ x
如的钱币数量不够,钱已经如数归还,请收好!"); . P; g5 Q' t, q: E# M* u; m( P
} , R( F7 \, R( m# q$ p) t! z
}
} $ {9 t6 P5 w4 }
}
通过上面的程序我们可以看出Mina的中业务逻辑处理都可以在IoHandler中,而不需要考虑对象的序列化和反序列化问题。关于IoHandler的简单用法就说这么多。下面再看看与IoHandler相关的几个中要的类。' r1 k/ [$ ?, N$ w
按照惯例,还是先给出IoHandler及其相关类的类图:- |/ U" ~9 [" u d0 c* i7 Y
从上面的类图我们可以清晰的看到IoHandler是一个接口,它有两个子类:
IoHandlerAdpater:它只是提供了IoHandler中定义的方法体,没有任何的逻辑处理,你可以根据你自己的需求重写该类中的相关方法。这个类在实际的开发中使用的是较多的。我们上面写的例子都是继承于这个类来实现的。! C4 C( \1 P, P, A; u% g! L
SingleSessionIoHandlerDelegate:这是一个服务器和客户端只有一个会话时使用的类,在该类的方法中没有提供session的参数,该类在实际的开发中使用的较少,如果需要对该类进行更深入的了解,请参考Mina 1.1.7的API文档。
在Mina提供的IoHandler的具体实现中,大部分的实现类都是继承与IoHandlerApater,IoHandlerAdpater在Mina 1.1.7中的子类有三个:$ ?/ t q a# F# [8 z2 l5 n1 @( y
ChainedIoHandler:这个类主要是用于处理IoHandler的messageReceived事件,它和IoHandlerChain配合使用。当在业务逻辑中有多个IoHandler需要处理时,你可以将你的每个IoHandler添加到IoHandlerChain中,这个和过滤器
链比较相似,关于IoFilter和IoHandlerChain的具体用法和区别会在后续的文档中给出。1 J9 y8 E4 z1 @# s6 a5 s$ V, `
StreamIoHandler:该类也是用于处理IoHandler的messageReceived事件,它主要用于文件传输的系统中,比如FTP服务器中,如果需要对该类进行更深入的了解,请参考Mina 1.1.7的API文档。4 Z5 \' L9 \# Z- Y/ r0 c
DemuxingIoHandler:该类主要是用于处理多个IoHandler的messageReceived,由于在TCP/IP协议的数据传输中会出现数据的截断现象(由于socket传输的数据包的长度是固定的,当数据包大于该长度,数据包就会被截断),所以提供这个类主要是保证IoHandler所处理的数据包的完整性,这个和编解码器中的CumulativeProtocolDecoder类似,关于这两个类的具体介绍会在后续的文档中给出。
至此,关于IoHandler的作用就讲述完了,希望对你能有所帮助。
在《与IoFilter相关的几个类》和《与IoHandler相关的几个类》两篇文档中我们了解了IoFilter和IoHandler的基本用法,以及其相关类的作用和用途。在本文中主要探讨IoFilter和IoHandler的主要区别和联系。& e3 ^, M) D* q2 d) ?% \
在上面的两篇文档中都提到了IoFilter和IoHandler都是对服务器或客户端(IoAcceptor/IoConnector)接收到的数据进行处理。在Mina的官方文档《The high-performance protocol constructiontoolkit》给出了IoFilter和IoHandler在Mina数据传输中的执行顺序,如下图:
上图显示了IoService进行数据读写时,各主要组件的执行顺序:
(1)IoService读取数据时个组件的执行顺序是:IoProcessor-->IoFilter-->IoHandler。4 b0 g: q0 o1 a
(2)IoService发送数据时的执行数顺序:IoHandler-->IoFilter-->IoProcessor。" }% r: V, y3 Y. c6 S& c
IoProcessor是一个处理线程,它的主要作用是根据当前连接的状态的变化(创建会话、开启会话、接收数据、发送数据、发生异常等等),来将数据或事件通知到IoFilter,当IoFilter的相应的方法接收到该状态的变化信息是会对接收到的数据进行处理,处理完毕后会将该事件转发到IoHandler中,有IoHandler完成最终的处理。在这里IoProcessor的主要功能是创建资源(创建/分配线程给IoFilter)和数据转发(转发到IoFilter),IoFilter对数据进行基本的分类(如编解码),IoHandler则负责具体的逻辑实现。也就是说IoFilter对接收到的数据包的具体内容不做处理,而是有IoHandler来对所接收到的数据包进行处理,根据数据包的内容向客户端返回响应的信息。
我们以《与IoHandler相关的几个类》中KFC售货机的例子来做一个具体的解释,在该例子中,客户端需要想服务器发送查询价格的请求,服务器根据接收到的请求查询物品的价格,然后将该物品的价格返回到客户端。客户端在向服务器发送数据前会有IoFilterr将发送的信息序列化为二进制数据,然后有IoProcess发送出去,简化如下:
IoHandler发送客户端数据-->IoFilter进行序列化-->IoProcessor
上面是数据的发送过程,当服务器接收到客户端的的请求数据后,先有IoProcessor将该数据转发到IoFilter,IoFilter将对象进行反序列化,反序列化的结果完成后将数据转发到IoHandler中,过程简化如下:& H; y* p& P( i. k1 n7 F: \
IoProcessor接收客户度端的数据-->IoFilter进行反序列化-->IoHandler根据请求查询价格这样一个完整的数据请求的过程就完成了。# q& v, |- i- N+ L+ B8 ?
上面简单介绍了IoFilter和IoHandler在Mina中的作用,前者是数据的转换层,后者是业务层。但是两者在很多地方都有相似之处,为了将两者的区别做更详细的讨论,先给出两者的结构图:4 }/ T" J1 [% `7 h" w! c+ R
. Y, E" X/ _) T# j
5 [# a0 I" o6 p3 h
图中的IoFilter比IoHandler中多出的一个最重要的方法就是filterWriter(),该方法会在程序调用session.write()的时候触发,该方法的重要之处就在于它表明了IoFilter和IoHandler的重要区别,即进行IoFilter是数据的收发层,也可以说是一个数据的收发器,而IoHandler则是逻辑层,并不负责数据的收发,如果把IoProcessor说成是底层的数据收发层,则IoFilter则是一个上层的数据收发层。关于IoFilter中on*()的方法的使用和作用请参考帮助文档,这里不再给出具体的解释。到此我们就可以明白了IoFilter是一个数据收发和转化的装置,而 IoHandler则是一个单一的业务处理装置,你的所有业务逻辑都应该写在这个类中。如果没有在IoService中配置IoFilter,那么在 IoHandler中接收到的数据是一个ByteBuffer,你需要在你的IoHandler(业务层)中完成数据的转化,但是这样就破坏了Mina中各个组件层的关系,这样你的程序结构就不在清晰,因此建议在使用Mina时将数据的转化(即二进制与对象之间的转换放在IoFilter层来处理)。在 Mina中必须要配置IoHandler,因为Mina中提供的IoService中的bind方法必须要有一个IoHandler,因此 IoHandler不能省略。- {0 F" N1 a2 }" x, Y' O
到这里对于IoFilter和IoHandler的内容已经讲述完毕,下面的内容是对我在开发中遇到的一些问题的一些总结,顺便也给自己以前的问题写出答案:" Q6 G; e, y9 B, N. Q7 J
(1)IoHandler和IoHandlerCommand的区别和联系。0 b0 Z& G- H# J7 ]
3 o5 e# J" p8 Z( z9 J
IoHandler和IoHandlerCommand是两个接口,在开发中经常遇到的他们两个现类分别是IoHandlerAdpater和 IoHandlerChain,IoHandlerAdpater的子类ChainedIoHandler和IoHandlerChain结合使用可以实现多个逻辑功能,IoHandlerChain代表IoHandlerCommand)是业务逻辑的处理单元,而ChainedIoHandler(代表 Iohandler)则是处理这些逻辑单元的组件。因此它们的区别是:IoHandler是刀俎,而IoHandlerCommand则是鱼肉。他们的一般用法如下: 5 e+ y. m$ d& F, a- M( M' a
1 {6 x7 p/ R5 G
Java代码 # a/ k( ~) V7 b' m
# s0 t8 A, Z7 Q+ L5 B; i
IoHandlerChain chain = new IoHandlerChain();// 创建逻辑处理组件 # J* L4 ~* Y/ b
chain.addLast("first", new FistCommand);// 添加逻辑组件单元一
chain.addLast("second", new SecondCommand);// 逻辑组件单元二 q9 ]+ \/ N& e7 S; B4 m' f) w! G
ChainedIoHandler chained = new ChainedIoHandler(chain);// 创建逻辑组件执行模块 # n9 K+ }8 o' n* f2 f2 O% Y
chained.messageReceived(session, message);// 当messageReceived触发该事件
8 z2 g9 @' u2 A. X: J3 \
(2)IoFilter和IoHandler可以同时使用吗?# \8 o' g4 v( H3 v$ ^0 w% @' T
/ J2 z6 M v3 t" H1 u
IoFilter和IoHandler由于分工不同,因此他们需要同时使用,但是这不是绝对的,在Mina4 T9 [; w0 ]7 @" x
% `/ o8 Y4 L: ?# q6 A/ b- Y
的IoService中可以不配置IoFilter,但是必须配置IoHandler。但是,这不是提倡的方式,
6 Q9 x4 L% [- n! R- R
因为这破坏的mina的分层结构,因此建议在使用Mina的时候同时使用IoFilter和* |% p" ~ A! ]
& R& R- W# Y. [' S. S
IoHandler。
# Z% e+ [; x! N ]7 m5 B |
(3)IoFilter和IoHandlerCommand/IoHandler的区别和联系。% h: o0 N6 M# V' w3 f0 V. H/ v# ~
# n! C ` a( w. h, u
这个问题的答案请参考问题(1)和(2)给出的解释。
(4)IoHandlerAdpater和IoFilterAdpater的区别和联系。( a. U! G: Y5 u E4 [( V* a T
IoHandlerAdpater和IoFilterAdpater一个是业务逻辑层的监听器,一个数据传输层的监
听器,他们的区别就是IoHandler和IoFilter的区别,这个在上面已经讨论清楚了,不在
. X0 s" M* \8 D$ |+ x) {& W
详细说明。+ {; `: j. S% X- I
5 U' s- m3 d- ~4 G
(5)IoFilterChainBuilder和ChainedIoHandler的区别和联系。5 D# n! `7 c7 Z) c E
# M6 P4 ]1 q/ u
关于这个问题的讨论会在后续的文档中给出。
在 Mina的使用中,线程池的配置一个比较关键的环节,同时它也是Mina性能提高的一个有效的方法,在Mina的2.0以上版本中已经不再需要对Mina 线程池的配置了,本系列文章都是基于当前的稳定版本Mina 1.1.7版来进行讲述的,Mina的2.0以上版本现在还都是M(millestone,即里程碑)版的,在1.5版本上2.0M版为稳定版本,但是在 1.5+以上则为非稳定版本,所以,为了更好的进行讨论和学习,还是基于Mina 1.1.7版本进行讨论,如果使用Mina 2.0进行开发要注意JDK的版本问题,当然如果有能力的话也可以自行修改和编译Mina的2.0版本,这里对此就不再多说,使用2.0版本的同学可以不用理会本文的内容。2 E9 Y/ o) }# p" m& D3 ?" \! }4 x
上面的内容都是基于Apache Mina提供的文档讲述,如有需要,请自行查找相关资料,在此不再赘述。
下面开始对Mina的线程模型的配置、使用、及ExcutorFilter的基本原理进行简单的讲解。
4 E, T, c; `) }$ `4 V$ l7 h
配置Mina的三种工作线程
% f! V8 P1 E9 c
在Mina的NIO模式中有三种I/O工作线程(这三种线程模型只在NIO Socket中有效,在NIO数据包和虚拟管道中没有,也不需要配置):( k: `& g1 U* @1 M; S
; X6 m9 U y: P& }
Acceptor thread9 ]# c0 ~$ c6 Q3 _ U- [: {# l, q
该线程的作用是接收客户端的连接,并将客户端的连接导入到I/O processor线程模型中。所谓的I/O processor线程模型就是Mina的I/O processor thread。Acceptor thread在调用了Acceptor.bind()方法后启动。每个Acceptor只能创建一个Acceptor thread,该线程模型不能配置,它由Mina自身提供。+ Q1 M% I" N9 ]. q& _# ~2 B8 ?
Connector thread% Z' h3 C; b* L5 P( e+ Z
该线程模型是客户端的连接线程模型,它的作用和Acceptor thread类似,它将客户端与服务器的连接导入到I/O processor线程模型中。同样地,该线程模型也是由Mina的客户端自动创建,该线程模型也不能进行配置。
I/O processor thread# i/ i( b5 E0 w
4 h! R" I; k, N* W( C& |
该线程模型的主要作用就行接收和发送数据,所有的IO操作在服务器与客户端的连接建立后,所有的数据的接收和发送都是有该线程模型来负责的,知道客户端与服务器的连接关闭,该线程模型才停止工作。该线程模型可以由程序员根据需要进行配置。该线程模型默认的线程的数量为cpu的核数+1。若你的cpu为双核的,则你的I/O processor 线程的最大数量为3,同理若你的若你的cpu为四核的,那么你的I/O processor 线程的最大数量为5。 q; d H; }% H6 ~% l. C
由上面的内容我们可以知道在Mina中可以配置的线程数量只有I/O processor,对于每个IoService再创建其实例的时候可以配置该IoService的I/O processor的线程数量。在SokcetConnector和SocketAccpetor中I/O Processor的数量是由CPU的核数+1来决定的。
他们的配置方式如下:
Java代码 深入理解Apache Mina(5)---- 配置Mina的 线程模型
7 H# K. {) A& d- ]) P# ^
<span><span style="font-size: small;"> /*** ! T% A* K9 R8 f2 \+ R
* 配置SocketAcceptor监听器的I/O Processor的线程的数量,
* 此处的I/O Processor的线程数量由CPU的核数决定,但Acceptor
* 的线程数量只有一个,也就是接收客户端连接的线程数只有一个,
* Acceptor的线程数量不能配置。 $ N: H: N' P) g
* */ ; ~: N8 M* ?% J. U# X
SocketAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime() , Y t7 @5 T; }3 p
.availableProcessors() + 1, Executors.newCachedThreadPool()); 0 e$ \" ^; R, Z6 M8 T7 j& }7 A+ e
/*** 6 u E4 d* \5 I1 l+ j" U
* 配置SocketConnector监听器的I/O Processor的线程的数量, 6 l6 T8 d/ X# K# ]6 t1 _5 ^- _
* 此处的I/O Processor的线程数量由CPU的核数决定,但SocketConnector 6 Z, {% W: O4 [) B1 h
* 的线程数量只有一个,也就是接收客户端连接的线程数只有一个, ; K- n9 B5 ?' A+ W0 m
* SocketConnector的线程数量不能配置。
* */ % i" t+ Q V- T
SocketConnector connector = new SocketConnector(Runtime.getRuntime() % f, h" {( x* y- Q$ P- m: M# e
.availableProcessors() + 1, Executors.newCachedThreadPool()); + h, s- X; l9 z' n ~; x X( o
</span></span>
在上面的配置比较难以理解的地方就是Runtime.getRuntime().availableProcessors() + 1,它的意思就是由JVM根据系统的情况(即CPU的核数)来决定IO Processor的线程的数量。虽然这个线程的数量是在SocketAcceptor /SocketConnector 的构造器中进行的,但是对于SocketAcceptor /SocketConnector自身的线程没有影响,SocketAcceptor /SocketConnector的线程数量仍然为1。为SocketAcceptor /SocketConnector本身就封装了IO Processor,SocketAcceptor /SocketConnector只是由一个单独的线程来负责接收外部连接/向外部请求建立连接,当连接建立后,SocketAcceptor /SocketConnector会把数据收发的任务转交I/O Processor的线程。这个在本系列文章的《IoFilter和IoHandler的区别和联系》中的图示中可以看。
$ v M& C% u% q* L
图中清晰的显示了IO Processor就是位于IoService和IoFilter之间,IoService负责和外部建立连接,而IoFilter则负责处理接收到的数据,IoProcessor则负责数据的收发工作。- {* o9 m ~8 S \0 f; s
关于配置IO Processor的线程数量还有一种比较“笨”的办法,那就一个一个试,你可以根据你的PC的硬件情况从1开始,每次加1,然后得出IO Processor的最佳的线程的数量。但是这种方式个人建议最好不要用了,上面的方法足矣。配置方法如下:
Java代码 ) S6 w, ^& A/ i9 H, p8 a( p
<span><span style="font-size: small;">//从1--N开始尝试,N的最大数量为CPU核数+1
SocketAcceptor acceptor = new SocketAcceptor(N, Executors.newCachedThreadPool());
</span></span>
为Mina的IoFilterChain添加线程池* U k3 P( R" I
在Mina的 API中提供了一个ExecutorFilter,该线程池实现了IoFilter接口,它可以作为一个IoFilter添加到 IoFilterChain中,它的作用就是将I/O Processor中的事件通过其自身封装的一个线程池来转发到下一个过滤器中。在没有添加该线程模型时,I/O Processor的事件是通过方法来触发的,然后转发给IoHandler。在没有添加该线程池的时候,所有的事件都是在单线程模式下运行的,也就是说有的事件和处理(IO Processor,IoHandler,IoFilter)都是运行在同一个线程上,这个线程就是IO Processor的线程,但是这个线程的数量受到CPU核数的影响,因此系统的性能也直接受CPU核数的影响。! I. U9 ?: j3 K C4 a
) R) Y* a* E6 P6 b3 w
比较复杂的应用一般都会用到该线程池,你可以根据你的需求在IoFilterchain中你可以添加任意数量的线程池,这些线程池可以组合成一个事件驱动(SEDA)的处理模型。对于一般的应用来说不是线程的数量越多越好,线程的数量越多可能会加剧CPU切换线程所耗费的时间,反而会影响系统的性能,因此,线程的数量需要根据实际的需要由小到大,逐步添加,知道找到适合你系统的最佳线程的数量。ExcutorFilter的配置过程如下:# D7 I* B5 K1 g( p, k( r: u
, W# C9 v) {' e& `5 ~
Java代码
<span><span style="font-size: small;">SocketAcceptor acceptor = ...;
DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); . ]0 Z- c, Q& v3 u( O
filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()); % u/ O+ |& g- c- w% w! @
</span></span> * r% x" A7 Z( X
在配置该线程池的时候需要注意的一个问题是,当你使用自定的ProtocolCodecFactory时候一定要将线程池配置在该过滤器之后,如下所示:
Java代码
<span><span style="font-size: small;">DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); 9 `! f$ o4 `+ B
// 和CPU绑定的操作配置在过滤器的前面 # U/ O6 u1 z8 z+ N# B- M- J, o1 @* L
filterChainBuilder.addLast("codec", new ProtocolCodecFactory(...));
// 添加线程池 ( T( {9 \5 {! ^, h$ ]
filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()); . U2 b0 Y+ z0 ~0 p
</span></span>5 j- g. }% h2 i, { O* J
因为你自己实现的ProtocolCodecFactory直接读取和转换的是二进制数据,这些数据都是由和CPU绑定的I/O Processor来读取和发送的,因此为了不影响系统的性能,也应该将数据的编解码操作绑定到I/O Processor线程中,因为在Java中创建和线程切换都是比较耗资源的,因此建议将ProtocolCodecFactory配置在 ExecutorFilter的前面。关于ProtocolCodecFactory详细讲述会在后续的文档中给出,此处就不多说了。# U9 p6 U8 z0 m9 m2 M
- Z/ i! t5 g% R7 r
最后给出一个服务器线程模型完整配置的例子,该例子和KFCClient一起配置使用,详细代码在附件中,此处只给出代码的主要部分: , M, x- y. w4 n8 e% z1 ^1 X
c# ~: l2 Z# m8 w
Java代码 深入理解Apache Mina(5)---- 配置Mina的 线程模型! r B" m& ?: w4 i' k; L% R: O
$ F3 _/ F6 p; f
<span><span style="font-size: small;">SocketAddress address = new InetSocketAddress("localhost", 4321); . ~. L# ]% X& P
/***
* 配置SocketAcceptor监听器的I/O Processor的线程的数量, 此处的I/O 2 c1 Q3 C) x. m+ S
* Processor的线程数量由CPU的核数决定,但Acceptor 的线程数量只有一个,也就是接收客户端连接的线程数只有一个,
* Acceptor的线程数量不能配置。
* */ ' w; t! D9 d- C; N ?- ]/ N
IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime() 8 o( v4 _, E$ G8 W
.availableProcessors() + 1, Executors.newCachedThreadPool()); 7 S6 w7 m( c9 q( I2 ?
# g( Y' q. r3 i: _$ }2 e
acceptor.getDefaultConfig().setThreadModel(ThreadModel.MANUAL); 2 g& S& a% r- g& R' w& w
// 配置数据的编解码器 ; ?4 f5 s8 y; H- z/ l$ W
acceptor.getDefaultConfig().getFilterChain().addLast("codec", * \. d; A4 H3 t: u
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
# N+ ? b" K3 V$ V
// 此处为你自己实现的编解码器
// config.getFilterChain().addLast("codec", new
// ProtocolCodecFactory(...));
// 为IoFilterChain添加线程池
acceptor.getDefaultConfig().getFilterChain().addLast("threadPool", 6 G& t) r8 J% L& E2 X. G7 n7 s
new ExecutorFilter(Executors.newCachedThreadPool()));
acceptor.getDefaultConfig().getFilterChain().addLast("logger", 8 \6 @! j( G6 L# N: ~! X) V
new LoggingFilter());
// 绑定服务器端口 ! Z# R( s" J( @# n6 C# ?: n3 U( V+ m
acceptor.bind(address, new KFCFoodPriceHandler()); $ w0 |% e. @0 h& y0 V: c2 F, t7 S, }
System.out.println(" 服务器开始在 8000 端口监听 .......");
// ==========================================//
// 此处为客户端的I/O Processor线程数的配置,你可以模仿 //
// IoAcceptor配置来实现 // 0 ?4 E# d+ e+ ]5 J; M
// ==========================================//
/***
* 配置SocketConnector监听器的I/O Processor的线程的数量, 此处的I/O
* Processor的线程数量由CPU的核数决定,但SocketConnector 9 A; X6 A$ x) P/ y( d+ v7 @1 U1 t
* 的线程数量只有一个,也就是接收客户端连接的线程数只有一个, SocketConnector的线程数量不能配置。 # K8 D: }# i: h. T& B3 Q
* */
// SocketConnector connector = new SocketConnector(Runtime.getRuntime() 1 [& i/ N% O# n; T5 U
// .availableProcessors() + 1, Executors.newCachedThreadPool());
} </span></span><span><span style="font-size: small;"> </span></span>
为了对后续关于Mina 的 ProtocolFilter( 编解码器 ) 的编写有一个更好的理解,本文讲述一下关于 Mina ByteBuffer 和 Java Nio ByteBuffer 的区别。关于 Java Nio ByteBuffer 和 Mina ByteBuffer 及其子类的类图在附件中都已经给出了。因为 Mina 的 ByteBuffer 在 Mina 2.0 以上的版本中都改称 IoBuffer 。为了使后文关于 ByteBuffer 的名字不致混淆, Mina ByteBuffer 都统称 IoBuffer , Java Nio ByteBuffer 统称 ByteBuffer 。关于 IoBuffer 中的对 ByteBuffer 扩展及一些重要的方法都在 IoBuffer 的类图中用红色方框标出。详细的信息请参考附件中。" e6 {2 @8 D3 A' K8 E
在开始对 IoBuffer 的讨论前,先简单的讲述一下 ByteBuffer 的用法。 IoBuffer 是对 ByteBuffer 的一个封装。 IoBuffer 中的很多方法都是对 ByteBuffer 的直接继承。只是对 ByteBuffer 添加了一些扩展了更加实用的方法。% p$ I, e) Z5 M( o0 O) V. X
(1) ByteBuffer简介9 K( N: i/ S3 l, m* w( j
ByteBuffer继承于 Buffer 类, ByteBuffer 中存放的是字节,如果要将它们转换成字符串则需要使用 Charset , Charset 是字符编码。它提供了把字节流转换成字符串 ( 解码 ) 和将字符串转换成字节流 ( 编码 ) 的方法。这个和后面讲述的 Mina 的编解码的工作原理类似。对 ByteBuffer 的访问可以使用 read() , write() 等方法。
# A! J# C+ R1 x
ByteBuffer有一下三个重要的属性:
* H1 G& y" @# D/ A7 h8 n
1) 容量(capacity) :表示该缓存区可以存放多少数据。7 S, D$ H" Y. _1 u' Z6 E
2) 极限(limit) :表示读写缓存的位置,不能对超过位置进行数据的读或写操作。1 m1 u( J6 C; g# _% _6 f B P, m B
位置(position) :表示下一个缓存区的读写单元。每读写一次缓存区,位置都会变化。位置是一个非负整数。* J% ^% ]: T+ l: n7 M: G
# G$ m3 P* }1 |$ S: M
ByteBuffer的这三个属性相当于三个标记位,来表示程序可以读写的区域:
上面只是一个简单的演示程序,功能是实现对字符串的读写,比较“ 笨 ” ,呵呵。关于如何向 ByteBuffer 读写字符串会在 IoBuffer 中详细讲解。! X: q$ a7 y) K9 g* m
文章转载自:http://bbs.cnw.com.cn/viewthread.php?tid=211392&extra=&page=1 |