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:
- 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
- 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.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
wow!!!its wonderful.the great blog.
ReplyDeletethe blog is very interesting and very informative.
thanks for sharing the info.
keep blogging.
Kill Captcha
Amazing explanation. Just great.
ReplyDeleteSome code missing here , kindly check the code .
ReplyDeleteHow to Pass our Own Line Delimeter ?
ReplyDeletenice blog!!.
ReplyDelete