Skip to content

Protocols

Tigran Sargsyan edited this page Nov 22, 2020 · 9 revisions

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

Protocol

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.


Transfer Tunnel

This interface is injected in protocol for sending transfers from it.

For Client Side

public interface TransferTunnel {

	void send(Transfer transfer);
}

For Server Side

public interface TransferTunnel {

	void send(long clientId, Transfer 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

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 (CertainReadableByteChannel)

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.

Protocol example

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";
	}
}