Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

...

This tutorial tries to explain why and how to use a ProtocolCodecFilter.

...

  • ImageRequest: a simple POJO representing a request to our ImageServer.
  • ImageRequestEncoder: encodes ImageRequest objects into protocol-specific data (used by the client)
  • ImageRequestDecoder: decodes protocol-specific data into ImageRequest objects (used by the server)
  • ImageResponse: a simple POJO representing a response from our ImageServer.
  • ImageResponseEncoder: used by the server for encoding ImageResponse objects
  • ImageResponseDecoder: used by the client for decoding ImageResponse objects
  • ImageCodecFactory: this class creates the necesarry encoders and decoders

...

Encoding is usually simpler than decoding, so let's start with the ImageRequestEncoder:

...

...

Remarks:

  • 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.
  • We allocate a new IoBuffer 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)
  • You do not have to release the buffer, MINA will do it for you, see http://mina.apache.org/report/trunk/apidocs/org/apache/mina/common/IoBuffer.html
  • 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.

Now let's have a look at the decoder. The CumulativeProtocolDecoder is a great help for writing your own decoder: it will buffer all incoming data until your decoder decides it can do something with it.
In this case the message has a fixed size, so it's easiest to wait until all data is available:

...

...

Remarks:

  • 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
  • you are not responsible for releasing the IoBuffer
  • when there is not enough data available to decode a message, just return false

The response is also a very simple POJO:

...

...

Encoding the response is also trivial:

...

...

Remarks:

  • when it is impossible to calculate the length of the IoBuffer beforehand, you can use an auto-expanding buffer by calling buffer.setAutoExpand(true);

Now let's have a look at decoding the response:

...

...

Remarks:

  • 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:
    • every IoSession would need its own Decoder instance
    • 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).
    • 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.
      (Thanks to Adam Fisk for pointing this out)
      see http://www.nabble.com/Tutorial-on-ProtocolCodecFilter,-state-and-threads-t3965413.html
  • IoBuffer.prefixedDataAvailable() is very convenient when your protocol uses a length-prefix; it supports a prefix of 1, 2 or 4 bytes.
  • don't forget to reset the decoder state when you've decoded a response (removing the session attribute is another way to do it)

If the response would consist of a single image, we would not need to store decoder state:

...

...

Now let's glue it all together:

...

Remarks:

  • for every new session, MINA will ask the ImageCodecFactory for an encoder and a decoder.
  • since our encoders and decoders store no conversational state, it is safe to let all sessions share a single instance.

This is how the server would use the ProtocolCodecFactory:

...

...

Usage by the client is identical:

...

For completeness, I will add the code for the server-side IoHandler:

...

...

The complete code for this tutorial (basic server + Swing client) can be found here.

...