An idiomatic Java wrapper around nng using JNA.
- Wrap nng in a way that can be used "naturally" via Java idioms so users can quickly spin up sockets, dial/listen, and send/receive data without all the pomp and circumstance seen in the C code (because Java has enough pomp and circumstance to deal with, amiright).
- Keep the wrapping (via JNA) at just the public API surface allowing it to track releases that might have internal (i.e. nni_*) changes only.
- Use a (somewhat) strong typing using JNA's extensible type system to keep usage of the library somewhat safe from memory issues.
- On the topic of memory issues, provide guardrails around protecting the native memory managed by nng from Java's garbage collector.
- Lastly, provide all the scalability constructs from nng (e.g. Aio) in a way that let's Java developers utilize nng's performance and thread safety.
My downline goal is to write my own protocol in an nng extension and use it in a Java application...but we'll see if/when I get there.
As of 8 Feb 2021, the core nng primitives are implemented allowing a Java developer to:
- instantiate and use
Socket
's that implement the Scalability Protocols - use
Context
's for multi-threaded, async use of a singleSocket
- use the
Aio
framework in 3 different ways for async operations:- in blocking (i.e. non-async) manner...the trivial use of a
Context
- in non-blocking manner using the Java
CompletableFuture
API - in an event-driver manner using
AioCallback
s
- in blocking (i.e. non-async) manner...the trivial use of a
- allocated
Message
s without worrying too much about memory management in some simpler cases
The AIO stuff needs some tire kicking, is a bit messy, and I'm not too happy with it yet. In short, the design lets you keep as much state in the JVM as possible (for callbacks and the like), but seems overly complicated. (Maybe it's just that way because of Java?)
Things not yet working or implemented:
- the http client api
- the http server api
- stream wrangling
- TLS support has not been tested
It's not quite clear how much of the HTTP stuff I want to implement, buf if there's anything required for WebSocket support it will be on the list.
- I've done nothing (yet) to package nng, so you'll need to build and install that yourself. It's really not that hard.
- A simple
gradlew build
should suffice, using the supplied Gradle wrapper scripts.
Once you have nng installed somewhere, if it's not installed in a sane default location, you might need to set one of the following to point JNA to the correct location:
- Java Property:
jna.library.path
- Environment variable:
JNA_LIBRARY_PATH
Note: The Java Property will override any value set in the environment
In general, most users should only need to utilize classes in the
io.sisu.nng
and io.sisu.nng.aio
namespaces. The direct calling of the nng
api should be wrapped for you.
For those looking to do low-level nng programming, most of the nng api is
accessible via io.sisu.nng.Nng
. Everything you need for low-level usage
should be in the io.sisu.nng.internal
namespace, with a few exceptions.
As part of my Java-fication of nng, I'm currently marrying Sockets to their protocols explicitly. (Maybe I'll go full on OOP and go overboard here...tbd.)
For detailed examples, check the demo project. The goal is to provide translations of the existing NNG demos into Java versions to illustrate any similarities or differences in usage (as well to provide a way to validate if the Java implementation is working).
A simple example of how the Java API works:
package io.sisu.nng;
import io.sisu.nng.reqrep.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
class Example {
public static void main(String[] argv) throws NngException {
final String url = "inproc://example";
Socket req = new Req0Socket();
Socket rep = new Rep0Socket();
rep.listen(url);
req.dial(url);
Message msg = new Message();
msg.append("hey man".getBytes(StandardCharsets.UTF_8));
req.sendMessage(msg);
// After sending, we no longer own the Message
assert(!msg.isValid());
Message msg2 = rep.receiveMessage();
assert(msg2.isValid());
String msg2Str = Charset.defaultCharset()
.decode(msg2.getBody()).toString();
assert("hey man".equalsIgnoreCase(msg2Str));
System.out.println("Rep socket heard: " + msg2Str);
}
}
And a simple example of using a Context
for asynchronous operations that is
the Java analog to nng's async
server.c
demo code:
package io.sisu.nng.demo.async;
import io.sisu.nng.*;
import java.util.ArrayList;
import java.util.List;
/**
* Java implementation of the NNG async demo server program.
*
* Unlike the C demo, the Java version uses the asynchronous event handler approach provided via
* the io.sisu.nng.aio.Context class.
*/
public class Server {
private static final int PARALLEL = 128;
private final String url;
public Server(String url) {
this.url = url;
}
public void start() throws Exception {
try (Socket socket = new Rep0Socket()) {
// keep Context references to prevent any possible gc
List<Context> contexts = new ArrayList<>(PARALLEL);
for (int i = 0; i < PARALLEL; i++) {
Context ctx = new Context(socket);
ctx.setRecvHandler((ctxProxy, msg) -> {
try {
int when = msg.trim32Bits();
ctxProxy.sleep(when);
ctxProxy.put("reply", msg);
} catch (NngException e) {
ctxProxy.receive();
}
});
ctx.setSendHandler((ctxProxy) -> ctxProxy.receive());
ctx.setWakeHandler((ctxProxy) -> ctxProxy.send((Message) ctxProxy.get("reply")));
// perform the initial receive operation to start the "event loop"
ctx.receiveMessage();
}
socket.listen(this.url);
System.out.println("Listening on " + this.url);
Thread.sleep(1000 * 60 * 20);
}
}
public static void main(String[] argv) {
if (argv.length != 1) {
System.err.println(String.format("Usage: server <url>"));
System.exit(1);
}
Server server = new Server(argv[0]);
try {
server.start();
} catch (Exception e) {
System.err.println(e);
e.printStackTrace();
}
}
}
Note: you'll notice the above uses the baked in event handling capabilities in the
Context
class. For a more like-for-like example, see the "raw" demo in the demos project that shows how to use your ownAioCallback
to make a like-for-like version of the nng raw.c demo.
Currently, the bare minimum to safely allocate/free native memory for nng
constructs exists, but it's not optimal in preventing things like native heap
fragmentation if a developer just lets the JVM garbage collect Message
s and
call the message's free()
method.
While it will probably only occur in long-running, server-based applications,
using the tire-kicking benchmarks project it's been observed
that services running that allocate hundreds of megabytes or gigabytes of
messages via the Java api that do not manually call free()
and rely on the
garbage collector will experience excessive memory pressure as nng_msg
objects stay allocated on the native heap, causing it to grow, and leading to
fragmentation that is not recoverable.
tl;dr: if you know you're done with a
Message
, just callfree()
for now.