JBoss.orgCommunity Documentation

Chapter 6. Writing protocols

6.1. Writing user defined headers

This chapter discusses how to write custom protocols

Headers are mainly used by protocols, to ship additional information around with a message, without having to place it into the payload buffer, which is often occupied by the application already. However, headers can also be used by an application, e.g. to add information to a message, without having to squeeze it into the payload buffer.

A header has to extend org.jgroups.Header, have an empty public constructor and implement the Streamable interface (writeTo() and readFrom() methods).

A header should also override size(), which returns the total number of bytes taken up in the output stream when an instance is marshalled using Streamable. Streamable is an interface for efficient marshalling with methods



public interface Streamable {
    /** Write the entire state of the current object (including superclasses)
        to outstream. Note that the output stream must not be closed */
    void writeTo(DataOutput out) throws IOException;
    /** Read the state of the current object (including superclasses) from
        instream. Note that the input stream must not be closed */
    void readFrom(DataInput in) throws IOException,
                                       IllegalAccessException,
                                       InstantiationException;
}
        

Method writeTo() needs to write all relevant instance variables to the output stream and readFrom() needs to read them back in.

It is important that size() returns the correct number of bytes, because some components (such a message bundling in the transport) depend on this, as they need to measure the exact number of bytes before sending a message. If size() returns fewer bytes than what will actually be written to the stream, then it is possible that (if we use UDP with a 65535 bytes maximum) the datagram packet is dropped by UDP !

The final requirement is to add the newly created header class to jg-magic-map.xml (in the ./conf directory), or - if this is not a JGroups internal protocol - to add the class to ClassConfigurator. This can be done with method

ClassConfigurator.getInstance().put(1899, MyHeader.class)
.

The code below shows how an application defines a custom header, MyHeader, and uses it to attach additional information to message sent (to itself):



public class bla {
    public static void main(String[] args) throws Exception {
        JChannel ch=new JChannel();
        ch.connect("demo");
        ch.setReceiver(new ReceiverAdapter() {
            public void receive(Message msg) {
                MyHeader hdr=(MyHeader)msg.getHeader("x");
                System.out.println("-- received " + msg +
                                   ", header is " + hdr);
            }
        });
        ClassConfigurator.getInstance().add((short)1900, MyHeader.class);
        int cnt=1;
        for(int i=0; i < 5; i++) {
            Message msg=new Message();
            msg.putHeader((short)1900, new MyHeader(cnt++));
            ch.send(msg);
        }
        ch.close();
    }
    public static class MyHeader extends Header implements Streamable {
        int counter=0;
        public MyHeader() {
        }
        private MyHeader(int counter) {
            this.counter=counter;
        }
        public String toString() {
            return "counter=" + counter;
        }
        public int size() {
            return Global.INT_SIZE;
        }
       public void writeTo(DataOutputStream out) throws IOException {
            out.writeInt(counter);
       }
       public void readFrom(DataInputStream in) throws IOException,
                                                       IllegalAccessException,
                                                       InstantiationException {
            counter=in.readInt();
       }
    }
}
        

The MyHeader class has an empty public constructor and implements the writeExternal() and readExternal() methods with no-op implementations.

The state is represented as an integer counter. Method size() returns 4 bytes (Global.INT_SIZE), which is the number of bytes written by writeTo() and read by readFrom().

Before sending messages with instances of MyHeader attached, the program registers the MyHeader class with the ClassConfigurator. The example uses a magic number of 1900, but any number greater than 1024 can be used. If the magic number was already taken, an IllegalAccessException would be thrown.

The final part is adding an instance of MyHeader to a message using Message.putHeader(). The first argument is a name which has to be unique across all headers for a given message. Usually, protocols use the protocol name (e.g. "UDP", "NAKACK"), so these names should not be used by an application. The second argument is an instance of the header.

Getting a header is done through Message.getHeader() which takes the name as argument. This name of course has to be the same as the one used in putHeader().