Point-to-point exchange of Glob messages over plain blocking sockets, one
thread per connection. No HTTP, no broker, no IDL: what travels on the wire is a Glob serialized with
globs-bin-serialisation (a TLV format), and the
contract between the two ends is the GlobType itself.
Two APIs, for two shapes of traffic:
| Package | Shape | Entry points |
|---|---|---|
network.rpc.direct |
request/response — the client asks, the server answers one Glob | GlobsServer, RpcGlobClient |
network.exchange |
streaming — either side pushes Globs, with optional acknowledgement, over one or several servers | GlobsServer, GlobSingleClient, GlobMultiClient |
- Java 21
org.globsframework:globsandglobs-bin-serialisation- every field of an exchanged
GlobTypemust carry aFieldNumberannotation — that is what the binary format keys on:
GlobTypeBuilder typeBuilder = GlobTypeBuilderFactory.create("Exchange");
id = typeBuilder.declareIntegerField("id", FieldNumber.create(1));
data = typeBuilder.declareStringField("data", FieldNumber.create(2));
TYPE = typeBuilder.build();<dependency>
<groupId>org.globsframework</groupId>
<artifactId>globs-rpc-direct</artifactId>
<version>5.3.0</version>
</dependency>Note the artifactId: the repository is globs-network, the artifact is globs-rpc-direct.
The server exposes endpoints, each one a path plus the GlobType it expects; the client calls a path and
says which type it wants back.
GlobsServer server = GlobsServer.create();
ExposedEndPoint endPoint = server.addEndPoint("localhost", 3000);
endPoint.addReceiver("/", (data, globInstantiator) ->
CompletableFuture.completedFuture(data), DummyObject.TYPE);
RpcGlobClient client = RpcGlobClient.create("localhost", 3000);
Glob response = client.request("/",
DummyObject.TYPE.instantiate()
.set(DummyObject.id, 1)
.set(DummyObject.name, "test"),
DummyObject.TYPE).join();
server.shutdown();Both sides are asynchronous under the socket: the receiver returns a CompletableFuture<Glob>, and
request gives one back, so a slow handler does not hold the connection's reader.
A client connects to a path and gets an Exchange to push on; the server hands a Receiver per client
and an OnData to push the other way.
GlobsServer server = GlobsServer.create("localhost", 0).with(executor).build();
server.onPath("/path", onData -> new MyReceiver(onData), ExchangeData.TYPE);
GlobMultiClient client = GlobMultiClient.create();
client.add("localhost", server.getPort()); // one Endpoint per server
client.waitForActifServer(1, 1000);
Exchange exchange = client.connect("/path", myDataReceiver, ExchangeData.TYPE,
GlobClient.AckOption.WITH_ACK_AFTER_CLIENT_CALL,
GlobClient.SendOption.SEND_TO_ALL);
exchange.send(ExchangeData.create("d", 1, System.nanoTime())).join();send returns a CompletableFuture<Boolean>, which is how the acknowledgement is surfaced. Two knobs
decide what it means:
AckOption |
The ack is sent |
|---|---|
NO_ACK |
never — nothing is tracked, the future is not waited on |
WITH_ACK_BEFORE_READ_DATA |
on reception, before the payload is even decoded |
WITH_ACK_BEFORE_CLIENT_CALL |
declared, but the server sends no ack for it today |
WITH_ACK_AFTER_CLIENT_CALL |
once the receiver has returned |
A receiver that throws, or a payload that does not decode against the declared GlobType, comes back to the
sender as an error on the same request id rather than as a dropped connection.
SendOption |
Where a message goes when several servers are registered |
|---|---|
SEND_TO_ALL |
to every active server |
SEND_TO_FIRST |
to the first registered one |
SEND_TO_ANY |
to one at random |
SEND_TO_ACTIVE |
to the one marked active (Endpoint.setActive()) |
Servers can be added and removed while traffic runs — GlobMultiClient.add / Endpoint.unregister, with
waitForActifServer(count, timeoutMs) to synchronize on the current membership. GlobSingleClient is the
one-server case with the same interface.
MultiClientSendReplyWithDisruptor in the test sources is the variant driving the send loop through an LMAX
disruptor.
mvn -o test # JUnit 5; the tests bind real sockets on localhost
mvn -o test -Dtest=MultiClientTestAsyncSimpleServerTest and PerfTest also print latency percentiles (dropwizard metrics) — they are as much
benchmarks as tests.
Apache License 2.0 — see https://www.apache.org/licenses/LICENSE-2.0.txt.