Writing a TCP/UDP stack supporting the SIP protocol using the Grizzly Framework, part II
Posted by jfarcand on June 25, 2008 at 02:28 PM | Comments (6)
Back to technical blog after "buzzwording" for too long :-). Time to resume the tutorial about Grizzly and SIP. The first part was explaining Grizzly's server side components, this time I will concentrate on the client components. Like for the first part, I will not yet jump into the details of the SIP protocol.
As for the server side, you first need to create the entry point in Grizzly, which is the Controller. Controller controller = new Controller();
Next, we need to configure which transport we want to support. For this blog purpose, I will only explain how to support UDP and TCP, and talk about TLS later. In Grizzly, a transport is represented using the ConnectorHandler interface, and the framework support by default three implementation: TCPConnectorHandler, TLSConnectorHandler and UDPConnectorHandler. By default, the Controller support TCP, but to help understanding how it works, the code below will explicitly configure the Controller to support TCP and UDP: ConnectorHandler tcpConnector = controller.acquireConnectorHandler(Controller.Protocol.TCP);
ConnectorHandler udpConnector = controller.acquireConnectorHandler(Controller.Protocol.UDP);
Next we need to decide if we want to use a ProtocolChain for handling our request/response, or use a CallbackHandler to experiment with the bytes and the state of the connection. Since SIP is a two way protocol (the client can become the server and vice versa), let's first demonstrate how to use a ConnectorHandler with a ProtocolChain. For the purpose of this blog, let's add a (LogFilter) that output the bytes sent by the server: ProtocolChain protocolChain = controller.getProtocolChain();
protocolChain.addFilter(new ReadFilter());
protocolChain.addFilter(new LogFilter());
That means that once we are connected to a remote server, all the bytes we are getting back will be processed by the ReadFilter and LogFilter. So let's connect: tcpConnector.connect(new InetSocketAddress("localhost",8080),null);
tcpConnector.write(ByteBuffer.wrap(sipRequestBytes));
What is interesting here is the ProtocolChain used for reading the bytes can also be used on the server side. So let's say we wrote a SipParserProtocolFilter, we can use that ProtocolFilter for parsing the bytes send by a remote client and also we can use it to parse the response we are getting when connecting to a remote server:
1 package com.sun.grizzly.utils;
2 import com.sun.grizzly.ConnectorHandler;
3 import com.sun.grizzly.Controller;
4 import com.sun.grizzly.DefaultPipeline;
5 import com.sun.grizzly.Pipeline;
6 import com.sun.grizzly.ProtocolChain;
7 import com.sun.grizzly.ProtocolChainInstanceHandler;
8 import com.sun.grizzly.TCPSelectorHandler;
9 import com.sun.grizzly.UDPSelectorHandler;
10 import com.sun.grizzly.filter.LogFilter;
11 import com.sun.grizzly.filter.ReadFilter;
12
13 public class SipDemo {
14
15 public void startSIPServerDemo(){
16
17 Controller controller = new Controller();
18
19 TCPSelectorHandler tcpSelector = new TCPSelectorHandler();
20 tcpSelector.setPort(8080);
21 controller.addSelectorHandler(tcpSelector);
22
23 UDPSelectorHandler udpSelector = new UDPSelectorHandler();
24 udpSelector.setPort(8080);
25 controller.addSelectorHandler(udpSelector);
26
27 Pipeline mySharedPipeline = new DefaultPipeline();
28 mySharedPipeline.setMaxThreads(5);
29
30 controller.setPipeline(mySharedPipeline);
31
32 ProtocolChainInstanceHandler pciHandler =
33 new ProtocolChainInstanceHandler() {
34
35 final private ProtocolChain protocolChain = new DefaultProtocolChain();
36
37 public ProtocolChain poll() {
38 return protocolChain;
39 }
40
41 public boolean offer(ProtocolChain instance) {
42 return true;
43 }
44 };
45 controller.setProtocolChainInstanceHandler(pciHandler);
46
47 ProtocolChain protocolChain = pciHandler.poll();
48 protocolChain.addFilter(new ReadFilter());
49 protocolChain.addFilter(new LogFilter());
50
51 controller.start();
52 ConnectorHandler ch = controller.acquireConnectorHandler(Controller.Protocol.TCP);
53 ch.connect(new InetSocketAddress("localhost",8080),(CallbackHandler)null);
54 }
55
55 }
Graphically, it looks like the following:  Now let's assume we want to use the ConnectorHandler for read and write operations, but this time without using a ProtocolChain. The component we can use in that case is called a CallbackHandler. In the example above, we haven't created any CallbackHandler, but under the hood, Grizzly created one (the DefaultCallbackHandler), which dispatch all connect, read and write operations to the ProtocolChain. By default, the CallbackHandler interface, which is invoked by the Controller every time there is an asynchonous operations ready to be processed, looks like: 41 /**
42 * Callback handler for non blocking client operations.
43 *
44 * @param E object containing information about the current
45 * non blocking connection
46 * @author Jeanfrancois Arcand
47 */
48 public interface CallbackHandler extends Handler{
49
50 /**
51 * This method is called when an non blocking OP_CONNECT is ready
52 * to get processed. This method must invoke ConnectorHandler.finishConnect()
53 * to complete the connection operations.
54 *
55 * @param ioEvent an object containing information about the current
56 * non blocking connection.
57 */
58 public void onConnect(IOEvent ioEvent);
59
60
61 /**
62 * This method is called when an non blocking OP_READ is ready
63 * to get processed.
64 * @param ioEvent an object containing information about the current
65 * non blocking connection.
66 */
67 public void onRead(IOEvent ioEvent);
68
69
70 /**
71 * This method is called when an non blocking OP_WRITE is ready
72 * to get processed.
73 * @param ioEvent an object containing information about the current
74 * non blocking connection.
75 */
76 public void onWrite(IOEvent ioEvent);
77
78 }
As an example, here is a simple implementation of a CallbackHandler connect, read and write operations: 274 public void onConnect(IOEvent ioEvent) {
275 SelectionKey key = ioEvent.attachment().getSelectionKey();
276 try{
277 tcpConnector.finishConnect(key);
278 } catch (IOException ex){
279 ex.printStackTrace();
280 }
281 // Register for read events
282 ioEvent.attachment().getSelectorHandler().register(key,
283 SelectionKey.OP_READ);
284 }
285
286 public void onRead(IOEvent ioEvent) {
287 SelectionKey key = ioEvent.attachment().getSelectionKey();
288 SelectorHandler selectorHandler = ioEvent.attachment().
289 getSelectorHandler();
290 SocketChannel socketChannel = (SocketChannel)key.channel();
291
292 try {
293 int nRead = socketChannel.read(readBB);
294 // Do something with the bytes
295 // ...
296 // Ask for more bytes
297 selectorHandler.register(key, SelectionKey.OP_READ);
298 } catch (IOException ex){
299 selectorHandler.getSelectionKeyHandler().cancel(key);
300 }
301 }
302
303 public void onWrite(IOEvent ioEvent) {
304 SelectionKey key = ioEvent.attachment().getSelectionKey();
305 SelectorHandler selectorHandler = ioEvent.attachment().
306 getSelectorHandler();
307 SocketChannel socketChannel = (SocketChannel)key.channel();
308 try{
309 while(writeBB.hasRemaining()){
310 int nWrite = socketChannel.write(writeBB);
311
312 if (nWrite == 0){
313 selectorHandler.register(key, SelectionKey.OP_WRITE);
314 return;
315 }
316 }
317 } catch (IOException ex){
318 selectorHandler.getSelectionKeyHandler().cancel(key);
319 }
320
321 }
With the example above, you can always decide to delegate the processing of the bytes to your ProtocolChain, which wasn't the case with my first example. Graphically, it looks like:  To configure your CallbackHandler, you just need to pass an instance when executing the asynchronous connect operation: tcpConnector.connect(new InetSocketAddress("localhost",8080),new MyConnectorHandler());
The only difference here is instead of passing a null, we are passing an instance of CallbackHandler. What I like with CallbackHandler is that I can decide when I want to delegate the processing to a ProtocolChain, and completely ignore that component if I don't want to write ProtocolFilter. And when I write a non blocking client, I can always decide to either write a CallbackHandler, or a simple ProtocolFilter. OK, hopefully the next blog for that serie will happens sooner. Next time I will start digging about SIP and how we have implemented it in Sailfin.
technorati: grizzly sailfin glassfish
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
I think your sample doesn't compile
look at the lines
19 TCPSelectorHandler tcpSelector = new TCPSelectorHandler();
20 tcpSelectorHandler.setPort(8080);
21 controller.addSelectorHandler(tcpSelector);
the variable is tcpSelector and the line later it's called : tcpSelectorHandler
unless the variable tcpSelectorHandler is declare somewhere else.
same thing for the UDP
Posted by: survivant on July 03, 2008 at 11:27 AM
-
line 27 there is a ")" trailing at the end..
you should test your code before posting :)
Posted by: survivant on July 03, 2008 at 11:30 AM
-
LOL :-) Fixed! Thanks!
Posted by: jfarcand on July 03, 2008 at 11:36 AM
-
ok.. c'est plus fort que moi..
Pipeline mySharedPipeline = new DefaultPipeline();
28 pipeline.setMaxThreads(5);
29
should be :
28 mySharedPipeline .setMaxThreads(5);
One question... why you don't include a zip file of your samples in your blog ?
Posted by: survivant on July 04, 2008 at 05:30 PM
-
One more thing :)
Line 53,
ch.connect(new InetSocketAddress("localhost",8080),null);
Gives an error:
"The method connect(SocketAddress, CallbackHandler) is ambiguous for the type ConnectorHandler"
so just need to cast the null to a CallbackHandler:
connectorHandler.connect(new InetSocketAddress("localhost",8080), (CallbackHandler)null);
Cheers,
Mark
Posted by: kram on July 14, 2008 at 04:14 AM
-
Fixed. Many thanks Mark!!!
-- Jeanfrancois
Posted by: jfarcand on July 22, 2008 at 12:18 PM
|