-
Notifications
You must be signed in to change notification settings - Fork 0
Protocols
For any communication, sonder uses user defined protocols. But of course there are built-in protocols, which will be enough in most cases. Built-In Protocols
The protocol is just an implementation of the interface com.github.tix320.sonder.api.client.ClientSideProtocol (for client side) or com.github.tix320.sonder.api.server.ServerSideProtocol (for server side). Both are extend the interface com.github.tix320.sonder.api.common.communication.Protocol.
public interface Protocol {
void reset();
String getName();
}
public interface ServerSideProtocol{
void init(TransferTunnel transferTunnel, EventListener eventListener);
void handleIncomingTransfer(long clientId, Transfer transfer) throws IOException;
}
public interface ClientSideProtocol{
void init(TransferTunnel transferTunnel, EventListener eventListener);
void handleIncomingTransfer(Transfer transfer) throws IOException;
}
Each incoming or outgoing packets we are call a transfer, representing by interface com.github.tix320.sonder.api.common.communication.Transfer.
- init - This method will be called once on server/client instance creation for some protocol initialization, TransferTunnel is used for sending transfers, EventListener described in Events.
- handleIncomingTransfer - handler function for incoming transfers of this protocol.
- reset - This method will be called on server/client instance close or connection lost for some cleanup. Here you need to reset protocol to state, so that can work properly after reconnect.
- getName - Returns the name of this protocol. Protocol name must be unique in server/client instance scope.
Important: Protocol implementation must be a thread safe, because any method can be called in any thread at the any time.
This interface is injected in protocol for sending transfers from it.
public interface TransferTunnel {
void send(Transfer transfer);
}
public interface TransferTunnel {
void send(long clientId, Transfer transfer);
}
Transfers are represented by headers and content.
Headers - interface com.github.tix320.sonder.api.common.communication.Headers.
Content is a byte channel com.github.tix320.sonder.api.common.communication.CertainReadableByteChannel.
There are two implementations of transfer, which you can use.
- ChannelTransfer - Headers + CertainReadableByteChannel.
- StaticTransfer - Headers + byte[].
Headers are used for some key-value metadata, for example to specify content type.
Keys are strings, values one of [Boolean, Number, String]. Other type headers will be dropped.
Headers usage:
Headers headers = Headers.builder()
.header("myHeader1", "foo")
.header("myHeader2", true)
.build();
headers.getNonNullLong("any"); // throws RuntimeException
headers.getBoolean("myHeader2"); // true
headers.getNumber("myHeader1"); // throws RuntimeException
Content is a any binary data as a channel with known length.
Important: Protocol must read every incoming transfer content in synhronous way in method handleIncomingTransfer, this is because the content channel is just wrapper over socket channel (to avoid unnecessary copy), so transfer data will be dropped after method call.
public interface CertainReadableByteChannel extends ReadableByteChannel {
long getContentLength();
long getRemaining();
@Ovveride
int read(ByteBuffer dst) throws IOException;
byte[] readAll() throws IOException;
void readRemainingInVain() throws IOException;
}
- getContentLength - Returns bytes count of channel.
- getRemaining - Returns bytes count available in channel at that time.
- read - Read bytes to given buffer.
- readAll - Read all bytes and return as an array.
- readRemainingInVain - Read remaining bytes in vain, and close channel, this can be used to empty channel, it shouldn't be useful for the user.
There are several implementations of this channel, which you can use.
- ReadableByteArrayChannel - Wrapper of byte array.
- LimitedReadableByteChannel - Wrapper of any java.nio.channels.ReadableByteChannel.
- EmptyReadableByteChannel - Empty channel.
Bellow a complete example of plain text protocol.
import java.io.IOException;
import java.nio.charset.Charset;
import com.github.tix320.sonder.api.common.communication.*;
import com.github.tix320.sonder.api.common.event.EventListener;
public class TextProtocol implements Protocol {
private TransferTunnel transferTunnel;
@Override
public void init(TransferTunnel transferTunnel, EventListener eventListener) {
this.transferTunnel = transferTunnel; // hold transferTunnel for transfers sending in the future
}
public void sendText(String text, long clientId) {
Charset charset = Charset.defaultCharset();
byte[] bytes = text.getBytes(charset); // convert string to bytes by current charset
Headers headers = Headers.builder()
.header(Headers.DESTINATION_ID, clientId) // specify destination client id
.header("charset", charset.name()) // specify charset to help decoding on other side
.build();
Transfer transfer = new StaticTransfer(headers, bytes);
transferTunnel.send(transfer);
}
@Override
public void handleIncomingTransfer(Transfer transfer) throws IOException {
Headers headers = transfer.getHeaders();
long sourceId = headers.getNonNullLong(Headers.SOURCE_ID);
String charset = headers.getNonNullString("charset"); // Get the charset for decoding content
byte[] bytes = transfer.channel().readAll(); // read all content to byte array
String text = new String(bytes, charset); // convert bytes to string according to charset
System.out.printf("Message from %s was received: %s", sourceId, text);
}
@Override
public void reset() {
}
@Override
public String getName() {
return "text-protocol";
}
}