1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="writing">
<title>Writing protocols</title>
<para>
This chapter discusses how to write custom protocols
</para>
<section>
<title>Anatomy of a protocol</title>
</section>
<section>
<title>Writing user defined headers</title>
<para>
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.
</para>
<para>
A header has to extend org.jgroups.Header, have an empty public
constructor and (currently) implement the Externalizable interface (writeExternal() and readExternal()
methods). Note that the latter requirement (Externalizable) will probably go away in 3.0.
</para>
<para>
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
<code>void writeTo(DataOutputStream out) throws IOException;</code>
and
<code>void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException;</code>.
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 off. 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 !
</para>
<para>
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 <code>ClassConfigurator.getInstance().put(1899, MyHeader.class)</code>.
</para>
<para>
The code below shows how an application defines a custom header, MyHeader, and uses it to attach additional
information to message sent (to itself):
<screen>
public class bla {
public static void main(String[] args) throws ChannelException, ClassNotFoundException {
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 message " + 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();
}
}
}
</screen>
</para>
<para>
The MyHeader class has an empty public constructor and implements the writeExternal() and readExternal()
methods with no-op implementations.
</para>
<para>
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().
</para>
<para>
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.
</para>
<para>
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.
</para>
<para>
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().
</para>
</section>
</chapter>
|