Tutorial on ProtocolCodecFilter
Why use a ProtocolCodecFilter?
- TCP guarantess delivery of all packets in the correct order.
But there is no guarantee that one write operation on the sender-side will result in one read event on the receiving side.
TCP保证收发的数据包遵照正确的次序。
但并不保证发送方的写操作会在接收方产生读事件。
In MINA terminology: without a ProtocolCodecFilter one call of IoSession.write(Object message) by the sender can result in multiple messageReceived(IoSession session, Object message) events on the receiver; and multiple calls of IoSession.write(Object message) can lead to a single messageReceived event. You might not encounter this behavior when client and server are running on the same host (or an a local network) but your applications should be able to cope with this.
MINA技术:不用ProtocolCodecFilter的话,发送方的一次写调用IoSession.write(Object message)可能会在接收方产生多个接收事件;多次写调用IoSession.write(Object message)也可能会在接收方导致一个接收事件。当客户端和服务器运行在同一台主机或局域网时,你可能会遇到上述情况,但你的应用程序应该能够处理。 - Most network applications need a way to find out where the current message ends and where the next message starts.
大多数网络应用程序需要识别出当前消息在那儿结束,以及下一个消息在那儿开始。 - You could implement all this logic in your IoHandler, but adding a ProtocolCodecFilter will make your code much cleaner and easier to maintain.
It allows you to separate your protocol logic from your business logic (IoHandler).
你可以将所有的逻辑在IoHandler中实现,但如果加入ProtocolCodecFilter的话,将会使你的代码更清晰,更易维护。
ProtocolCodecFilter允许你将协议逻辑从业务逻辑(IoHandler)中分离出来。
How ?
Your application is basically just receiving a bunch of bytes and you need to convert these bytes into messages (higher level objects).
There are three common techniques for splitting the stream of bytes into messages:
你的应用程序主要就是接收一串字节,你需要将它们转换成消息(高层对象)。
有三种通用技术将字节流分解到消息中:
- use fixed length messages
使用定长消息 - use a fixed length header that indicates the length of the body
使用定长的报文头,在其中某个域指明报文长度 - using a delimiter; for example many text-based protocols append a newline (or CR LF pair) after every message (http://www.faqs.org/rfcs/rfc977.html
)
使用定界符;例如:许多基于文本的协议会在每个消息包的末尾追加一个换行符(或者一对回车换行符)
实现ProtocolEncoder编程要点
- MINA will call the encode function for all messages in the IoSession's write queue. Since our client will only write ImageRequest objects, we can safely cast message to ImageRequest.
MINA将对位于IoSession写队列中的所有消息调用encode函数。因为客户端只发送ImageRequest对象,我们可以安全地将message转为ImageRequest对象。 - We allocate a new ByteBuffer from the heap. It's best to avoid using direct buffers, since generally heap buffers perform better.
see http://issues.apache.org/jira/browse/DIRMINA-289)
我们从堆栈中分配新的ByteBuffer。最好避免使用direct buffers,通常堆栈中的buffers表现得更好。 - You do not have to release the buffer, MINA will do it for you, see http://mina.apache.org/report/1.1/apidocs/org/apache/mina/common/ByteBuffer.html
千万不要释放buffer,应由MINA自动释放。 - In the dispose() method you should release all resources acquired during encoding for the specified session. If there is nothing to dispose you could let your encoder inherit from ProtocolEncoderAdapter.
在dispose方法中,你应该释放指定的session编码期间获得的所有资源。如果没有东西要dispose,你可以让你的编码器从ProtocolEncoderAdapter派生。 - when it is impossible to calculate the length of the ByteBuffer beforehand, you can use an auto-expanding buffer by calling buffer.setAutoExpand(true);
如果不能事先计算出ByteBuffer的长度,你可以使用一个自动扩展的buffer,调用buffer.setAutoExpand(true)即可。
实现ProtocolDecoder编程要点
- everytime a complete message is decoded, you should write it to the ProtocolDecoderOutput; these messages will travel along the filter-chain and eventually arrive in your IoHandler.messageReceived method
每次只要一个完整的消息解出来,你就应该把它写入ProtocolDecoderOutput;这些消息将会在过滤器链(filter-chain)中流转,最终到达你的IoHandler.messageReceived方法中。 - you are not responsible for releasing the ByteBuffer
你不用负责释放ByteBuffer - when there is not enough data available to decode a message, just return false
当没有接收到足够的数据去解消息时,仅返回false就行了。
- We store the state of the decoding process in a session attribute. It would also be possible to store this state in the Decoder object itself but this has several disadvantages:
我们在session的属性中存储解码过程的状态信息。这些信息也可以存储在解码器对象本身,但这样做有几个缺点:
- every IoSession would need its own Decoder instance
每个IoSession都需要它自己的解码器实例。 - MINA ensures that there will never be more than one thread simultaneously executing the decode() function for the same IoSession, but it does not guarantee that it will always be the same thread. Suppose the first piece of data is handled by thread-1 who decides it cannot yet decode, when the next piece of data arrives, it could be handled by another thread. To avoid visibility problems, you must properly synchronize access to this decoder state (IoSession attributes are stored in a ConcurrentHashMap, so they are automatically visible to other threads).
MINA确保对每一个IoSession同一时刻只有一个线程在执行decode() 函数——不允许多线程并发地执行解码函数,但它并不能保证每次解码过程都是同一线程在执行(两次解码用的可能是不同的线程)。假设第一块数据被线程1管理,这时还没接收到足够的数据以供解码,当接收到第二块数据时,被另一个线程管理,为了避免可视化方面的问题,你就必须适当地同步对解码器状态的访问(IoSession的属性存储在支持并发访问的ConcurrentHashMap中,因此它们对其他线程是自动可见的)。 - a discussion on the mailing list has lead to this conclusion: choosing between storing state in the IoSession or in the Decoder instance itself is more a matter of taste. To ensure that no two threads will run the decode method for the same IoSession, MINA needs to do some form of synchronization => this synchronization will also ensure you can't have the visibility problem described above.
在邮件列表的一次讨论得出这样的结论:选择存储状态信息到IoSession还是到解码器实例本身多少有些味道。为确保没有两个线程对同一IoSession运行解码方法,MINA需要做一些形式的同步 => 这种同步也将确保你不会碰到上面讨论的可视化问题。
see http://www.nabble.com/Tutorial-on-ProtocolCodecFilter,-state-and-threads-t3965413.html
- every IoSession would need its own Decoder instance
- ByteBuffer.prefixedDataAvailable() is very convenient when your protocol uses a length-prefix; it supports a prefix of 1, 2 or 4 bytes.
当你的协议使用长度前缀时,ByteBuffer.prefixedDataAvailable() 用起来很方便,它支持1、2或4个字节的前缀。 - don't forget to reset the decoder state when you've decoded a response (removing the session attribute is another way to do it)
当你解码一个回应包时,别忘了重置解码器状态(也可以删除session的属性)。