This library is based on uTP and it was originally forked from Tribler/utp4j.
This example demonstrates how to send and receive data between two local uTP clients over UDP using UTPClient
.
var readerExecutor = Executors.newVirtualThreadPerTaskExecutor();
var writerExecutor = Executors.newVirtualThreadPerTaskExecutor();
var readerTransport = new UDPTransportLayer(124);
var writerTransport = new UDPTransportLayer(123);
Sets up virtual thread executors for concurrent I/O operations and initializes UDP transport layers on different ports for the sender and receiver.
var utpReader = new UTPClient(readerTransport);
var utpWriter = new UTPClient(writerTransport);
var readerAddress = new UDPAddress("localhost", 124);
var writerAddress = new UDPAddress("localhost", 123);
Creates two
UTPClient
instances — one for receiving, one for sending — and defines their respective socket addresses.
readerExecutor.submit(receivingListener(utpReader, readerTransport, writerAddress));
writerExecutor.submit(receivingListener(utpWriter, writerTransport, readerAddress));
Starts background threads that continuously listen for incoming UDP packets and pass them to the
UTPClient
.
CompletableFuture<Bytes> receivedFuture = new CompletableFuture<>();
utpReader.startListening(111, writerAddress)
.thenCompose(__ -> utpReader.read(readerExecutor))
.thenAccept(receivedFuture::complete);
Configures the reader to listen on channel
111
and asynchronously read data into a future.
utpWriter.connect(111, readerAddress)
.thenCompose(__ -> utpWriter.write(getContentToSend("Hello from uTP!"), writerExecutor))
.get();
The sender connects to the receiver and writes a UTF-8 message. The
get()
call blocks until sending is complete.
Bytes received = receivedFuture.get();
System.out.println("✅ Received: " + new String(received.toArray(), StandardCharsets.UTF_8));
}
The receiver awaits incoming data and prints it to the console once received.
private static Runnable receivingListener(UTPClient utpClient, UDPTransportLayer transport, UDPAddress remoteAddress) {
return () -> {
while (true) {
try {
var packet = transport.onPacketReceive();
utpClient.receivePacket(packet, remoteAddress);
} catch (SocketException e) {
break;
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
private static Bytes getContentToSend(String input) {
byte[] byteArray = input.getBytes(StandardCharsets.UTF_8);
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
return Bytes.of(buffer.array());
}
}
receivingListener(...)
: Listens for incoming UDP packets and passes them to the uTP client.getContentToSend(...)
: Converts a string into aBytes
object suitable for transmission.
Received: Hello from uTP!
- There are two interfaces that need to be implemented before using UTP for reading or writing operations:
- TransportLayer:
- You define how a packet will be sent over the wire by receiving a UtpPacket and RemoteAddress
- You define what to do once the transfer has finished.
- TransportAddress:
- You define your own RemoteAddress needed to be sent by the TransportLayer previously defined.
- TransportLayer:
- Closing connection while sending/reading is not yet handled good enough
- High CPU consumption
- Probably some minor bugs.
- The uTP reference implementation deviates from the uTP specification on the initialization of the
ack_nr
when receiving theACK
of aSYN
packet. The reference implementation initializes this asc.ack_nr = pkt.seq_nr - 1
while the specification indicatesc.ack_nr = pkt.seq_nr
. This uTP specifications follows the uTP reference implementation:c.ack_nr = pkt.seq_nr - 1
.
utp is licensed under the Apache 2.0 [license].