Saturday, April 20, 2013

How to write a Custom Codec in Apache MINA?

Whenever we have to develop a socket based enterprise application. Few frameworks come to our mind. To name a few:


If I go by the book,


Netty:
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.


Whereas,


Apache MINA:
Is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract event-driven asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO.

Data Packets:
If I talk about sending packets over network there are mainly two approaches being used:

  1. Connect - Send - Process - ByeBye
    • Client make a socket connection with server 
    • Sends data over the channel 
    • Server Processes it
    • Send results or acknowledgement
    • Objective complete socket closed
  2. Connect - Send - Process - Send ... and so on
    • Client establishes the connection for the first time with server
    • Sends data over the channel
    • Server Processes it
    • Send results or acknowledgement
    • Channel still remains open for further communication.

Now we have the question which approach to go for, remember both approaches have its pros and cons.

Lets first discuss:
There's always an overhead of socket connections establishment. If same client keeps on sending a large number of packets, then we will be wasting considerable CPU cycles in this process. So if we have certain specific number of client only then we can think of moving to approach#2

But if we have large number of clients then we can say approach#2 will easily become the cause of bottleneck.

Now lets come to the main reason why this blog is written, there can be a number of times that while doing socket connections, developer have to create their own protocol for the clients. This gives independence of customization.

But if I merge approach#2 for socket communication with custom protocol, well that's definitely something to look at.

I am going to explain the method of writing a custom codec with Encoder and Decoder. This is somewhat I did though I changed the actual protocol because of some reasons I can't disclode ;) .

The code appears to be self explanatory, but I would still like to put up few points.
Here I have used cumulative protocol, because I might required more packets for merging in order to get complete information. 

I am open to inputs as well as if anyone want more details.


/**
* User: Himanshu
* Date: 29 Aug, 2010
* Time: 10:59:11 PM
*/
public class CustomProtocolCodecFactory extends TextLineEncoder implements ProtocolCodecFactory {
/**
* A protocol without encoder and docoder is a waste.
* Encode mentioned here
*/
public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
return new RequestEncoder();
}
/**
* A protocol without encoder and docoder is a waste.
* Encode mentioned here
*/
public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {
return new RequestDecoder();
}
}
view raw gistfile1.java hosted with ❤ by GitHub
public class MinaServer {
private static final int PORT = 8892;
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
//Here I am mentioning the that I am using a custom protocol
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new CustomProtocolCodecFactory()));
acceptor.setHandler(new MinaServerHandler());
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
acceptor.bind(new InetSocketAddress(PORT));
}
}
view raw gistfile2.java hosted with ❤ by GitHub
public class RequestDecoder extends CumulativeProtocolDecoder {
@Override
protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput protocolDecoderOutput) throws Exception {
//Write your business logic here how to translate a data-packet into what the server understands,
//NOTE it is highly possible that the data is not complete in 1 packet and might required a re-combination
//If packet is not complete return false to reiterate with next packet. ELse return true if packet is complete
//I have mentioned a sample code
ioBuffer.setAutoExpand(true);
ioBuffer.setAutoShrink(true);
label1:
while (ioBuffer.hasRemaining()) {
char testChar = (char)ioBuffer.get(ioBuffer.limit()-1);
if (testChar != '\n' && testChar != '\r' && testChar != REQUEST_DELIMITER_CHAR) {
//NOOP
return false;
} else {
if (testChar == '\n' || testChar == '\r') {
char delimiterChar = (char)ioBuffer.get(ioBuffer.limit()-3);
if (delimiterChar == REQUEST_DELIMITER_CHAR) {
char[] charArray = new char[ioBuffer.limit() - ioBuffer.position()];
label2:
for (int i=ioBuffer.position(); i< ioBuffer.limit(); i++) {
char ch = (char)ioBuffer.get(i);
if(ch == '\r') {
System.out.println("0D found");
} else if (ch == '\n') {
System.out.println("0A found");
} else if (ch == REQUEST_DELIMITER_CHAR) {
System.out.println("DELIMITER found");
} else {
charArray[i] = ch;
}
}
ioBuffer.position(ioBuffer.limit());
String outputString = new String(charArray);
outputString = outputString.trim();
protocolDecoderOutput.write(outputString);
return true;
} else {
return false;
}
} else {
char delimiterChar = (char)ioBuffer.get(ioBuffer.limit()-1);
if (delimiterChar == REQUEST_DELIMITER_CHAR) {
char[] charArray = new char[ioBuffer.limit() - ioBuffer.position()];
label2:
for (int i=ioBuffer.position(); i< ioBuffer.limit(); i++) {
char ch = (char)ioBuffer.get(i);
if(ch == '\r') {
System.out.println("0D found");
} else if (ch == '\n') {
System.out.println("0A found");
} else if (ch == REQUEST_DELIMITER_CHAR) {
System.out.println("DELIMITER found");
} else {
charArray[i] = ch;
}
}
ioBuffer.position(ioBuffer.limit());
String outputString = new String(charArray);
outputString = outputString.trim();
protocolDecoderOutput.write(outputString);
return true;
} else {
return false;
}
}
}
}
return false;
}
}
view raw gistfile3.java hosted with ❤ by GitHub
public class RequestEncoder extends ProtocolEncoderAdapter implements ProtocolEncoder {
public void encode(IoSession ioSession, Object o, ProtocolEncoderOutput protocolEncoderOutput) throws Exception {
o = o.toString()+RESPONSE_DELIMITER_CHAR;
IoBuffer ioBuffer = IoBuffer.allocate(3, false);
ioBuffer.setAutoExpand(true);
ioBuffer.setAutoShrink(true);
byte[] responseByteArr = o.toString().getBytes();
ioBuffer.put(responseByteArr);
ioBuffer.flip(); //Flip it or there will be nothing to send
protocolEncoderOutput.write(ioBuffer);
protocolEncoderOutput.flush();
}
}
view raw gistfile4.java hosted with ❤ by GitHub

5 comments:

  1. wow!!!its wonderful.the great blog.
    the blog is very interesting and very informative.
    thanks for sharing the info.
    keep blogging.

    Kill Captcha

    ReplyDelete
  2. Amazing explanation. Just great.

    ReplyDelete
  3. Some code missing here , kindly check the code .

    ReplyDelete
  4. How to Pass our Own Line Delimeter ?

    ReplyDelete