Below the basics of CoralReactor:
You can implement your own UDP and TCP clients and servers through the classes AbstractUdpClient
, AbstractTcpClient
and AbstractTcpServer
. See further below this article for an example of an UDP client implementation.
It also comes with some clients and servers you can use out of the box. They are: LineTcpClient/LineTcpServer (messages delimited by the newline character), BytePayloadTcpClient/BytePayloadTcpServer (payload protocol with a byte size) and ShortPayloadTcpClient/ShortPayloadTcpServer (payload protocol with a short size). Below an example of a client that extends LineTcpClient
to receive messages delimited by the newline character and print them to the console.
package com.coralblocks.coralreactor.client.print; import java.nio.ByteBuffer; import com.coralblocks.coralbits.util.ByteBufferUtils; import com.coralblocks.coralreactor.client.Client; import com.coralblocks.coralreactor.client.line.LineTcpClient; import com.coralblocks.coralreactor.nio.NioReactor; import com.coralblocks.coralreactor.util.Configuration; import com.coralblocks.coralreactor.util.MapConfiguration; public class PrintLineClient extends LineTcpClient { public PrintLineClient(NioReactor nio, String host, int port, Configuration config) { super(nio, host, port, config); } @Override protected void handleOpened() { // the client was open and will now start trying to connect to its destination... // do whatever you want here... } @Override protected void handleClosed() { // the client was closed and will now do nothing (won't connect to anywhere and just sit idle) // do whatever you want here... } @Override protected void handleConnectionEstablished() { // a network connection was established // for TCP this is a socket connection // for UDP that means a packet was received so a "connection" can be assumed // do whatever you want here... } @Override protected void handleConnectionOpened() { // any special protocol login, handshake, authentication, etc. was successfully done // client is now ready to start exchanging messages // by default a connection is opened as soon as it is established but some special clients require // some initial login, handshake, authentication, etc. before they can start sending/receiving messages // do whatever you want here... send("Hello there!"); } @Override protected void handleConnectionTerminated() { // the connection was broken or terminated for any reason... // for TCP this is a socket termination or the disconnect() method was explicitly called on the client // for UDP this is a read timeout (lack of heartbeat and data for some time) or the disconnect() method was explicitly called // after the connection is terminated the client will automatically try to reconnect unless the close() method is called // do whatever you want here... } @Override protected void handleMessage(ByteBuffer msg) { // here is an application message received by this client... // it does NOT include the newline character ByteBufferUtils.println(msg); } public static void main(String[] args) { NioReactor nio = NioReactor.create(); MapConfiguration config = new MapConfiguration(); Client client = new PrintLineClient(nio, "localhost", 45151, config); client.open(); nio.start(); } }
You can easily simulate a server with netcat:
The log output from the client is:
21:20:39.023705-INFO PrintClient-localhost:45151 Client opened! sequence=1 session=null 21:20:39.083586-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 21:20:39.085061-INFO PrintClient-localhost:45151 Connection established! 21:20:39.085096-INFO PrintClient-localhost:45151 Connection opened! [hi] [this is a test] [how are you?]
The code for a server is very similar, the difference being that all methods have a client argument since the server can handle many clients simultaneously. To make it more interesting we add a timer on each client to send a message every N seconds.
package com.coralblocks.coralreactor.server.print; import java.nio.ByteBuffer; import com.coralblocks.coralbits.util.ByteBufferUtils; import com.coralblocks.coralreactor.client.Client; import com.coralblocks.coralreactor.nio.NioReactor; import com.coralblocks.coralreactor.server.Server; import com.coralblocks.coralreactor.server.line.LineTcpServer; import com.coralblocks.coralreactor.util.Configuration; import com.coralblocks.coralreactor.util.MapConfiguration; public class PrintLineServer extends LineTcpServer { private final int interval; public PrintLineServer(NioReactor nio, int port, Configuration config) { super(nio, port, config); this.interval = config.getInt("interval", 1000); // 1 second by default } @Override protected void handleOpened() { // the server was open and will now start accepting connections (i.e. clients) // do whatever you want here... } @Override protected void handleClosed() { // the server was closed, all clients was closed and the server will not accept any more connections // do whatever you want here... } @Override protected void handleConnectionEstablished(Client client) { // a network connection was established with the given client // do whatever you want here... } @Override protected void handleConnectionOpened(Client client) { // any special protocol login, handshake, authentication, etc. was successfully done // client is now ready to start exchanging messages // by default a connection is opened as soon as it is established but special clients require // some initial login, handshake, authentication, etc. before they can start sending/receiving messages // do whatever you want here... client.setEventTimeout(interval); } @Override public void handleEventTimeout(Client client, long now, int timeout) { client.send("Hello"); client.setEventTimeout(timeout); // timers trigger just once so re-register for another trigger } @Override protected void handleConnectionTerminated(Client client) { // the connection with the given client was closed // do whatever you want here... } @Override protected void handleMessage(Client client, ByteBuffer msg) { // here is an application message received by the given client... // it does NOT include the newline character if (ByteBufferUtils.equals(msg, "bye")) { client.close(); return; } System.out.print("Message from "); System.out.print(client); System.out.print(" "); ByteBufferUtils.println(msg); } public static void main(String[] args) { NioReactor nio = NioReactor.create(); MapConfiguration config = new MapConfiguration(); config.add("interval", 2000); // 2 seconds Server server = new PrintLineServer(nio, 45151, config); server.open(); nio.start(); } }
To connect to the server we can use the PrintLineClient we just wrote. For simplicity we just use netcat:
The log output from the server is:
21:50:51.014509-INFO PrintLineServer-45151 Server opened! host=0.0.0.0 port=45151 21:50:51.017752-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 21:51:13.404784-INFO TcpServerClient-127.0.0.1:54391 Client connection established! 21:51:13.404828-INFO TcpServerClient-127.0.0.1:54391 Client connection opened! Message from TcpServerClient-127.0.0.1:54391 [Hi] 21:51:30.109062-INFO PrintLineServer-45151 Client disconnected: TcpServerClient-127.0.0.1:54391
You can also code your own clients and servers through the AbstractUdpClient
, AbstractTcpClient
and AbstractTcpServer
. Below an example of an UDP client that receives and prints packets.
package com.coralblocks.coralreactor.client.print; import static com.coralblocks.corallog.Log.*; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import com.coralblocks.coralreactor.client.AbstractUdpClient; import com.coralblocks.coralreactor.client.Client; import com.coralblocks.coralreactor.nio.NioReactor; import com.coralblocks.coralreactor.util.Configuration; import com.coralblocks.coralreactor.util.MapConfiguration; public class PrintPacketClient extends AbstractUdpClient { public PrintPacketClient(NioReactor nio, String host, int port, Configuration config) { super(nio, host, port, config); } @Override protected void handleOpened() { // optional (here for clarity) } @Override protected void handleClosed() { // optional (here for clarity) } @Override protected void handleConnectionEstablished() { // optional (here for clarity) } @Override protected void handleConnectionOpened() { // optional (here for clarity) } @Override protected void handleConnectionTerminated() { // optional (here for clarity) } @Override protected void handleMessage(InetSocketAddress from, ByteBuffer msg) { Info.log(name, "Got packet:", msg); } public static void main(String[] args) { NioReactor nio = NioReactor.create(); MapConfiguration config = new MapConfiguration(); Client client = new PrintPacketClient(nio, "0.0.0.0", 45151, config); client.open(); nio.start(); } }
To code an UDP client that only sends packets, we can extend SenderUdpClient
as below:
package com.coralblocks.coralreactor.client.sender; import static com.coralblocks.corallog.Log.*; import com.coralblocks.coralreactor.client.Client; import com.coralblocks.coralreactor.nio.NioReactor; import com.coralblocks.coralreactor.util.Configuration; import com.coralblocks.coralreactor.util.MapConfiguration; public class SenderPacketClient extends SenderUdpClient { private final int interval; public SenderPacketClient(NioReactor nio, String host, int port, Configuration config) { super(nio, host, port, config); this.interval = config.getInt("interval", 1000); // 1 second by default } @Override protected void handleOpened() { // optional (here for clarity) } @Override protected void handleClosed() { // optional (here for clarity) } @Override protected void handleConnectionEstablished() { // optional (here for clarity) } @Override protected void handleConnectionOpened() { setEventTimeout(interval); } @Override protected void handleConnectionTerminated() { // optional (here for clarity) } @Override protected void handleEventTimeout(long now, int timeout) { Info.log(name, "Sending packet..."); send("Hello there!"); if (isConnectionOpen()) { // just in case there was an error sending the UDP packet and the client was closed setEventTimeout(timeout); // re-register so it happens again (timers trigger only once) } } public static void main(String[] args) { NioReactor nio = NioReactor.create(); MapConfiguration config = new MapConfiguration(); config.add("sendAddress", "localhost"); config.add("sendPort", 45151); String bindAddress = "0.0.0.0"; int bindPort = 0; // choose any free port to bind (ephemeral port) Client client = new SenderPacketClient(nio, bindAddress, bindPort, config); client.open(); nio.start(); } }
Now if we run both clients, we get the following log output:
02:53:41.328129-INFO SenderPacketClient-localhost:45151 Bound datagram channel to /0.0.0.0:55365 02:53:41.329297-INFO SenderPacketClient-localhost:45151 Client opened! sequence=1 session=null 02:53:41.331682-INFO SenderPacketClient-localhost:45151 Connection established! 02:53:41.333870-INFO SenderPacketClient-localhost:45151 Connection opened! 02:53:41.334212-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 02:53:41.800535-INFO SenderPacketClient-localhost:45151 Sending packet... 02:53:42.801833-INFO SenderPacketClient-localhost:45151 Sending packet... 02:53:43.802194-INFO SenderPacketClient-localhost:45151 Sending packet...
03:00:08.926596-INFO PrintPacketClient-0.0.0.0:45151 Bound datagram channel to /0.0.0.0:45151 03:00:08.927742-INFO PrintPacketClient-0.0.0.0:45151 Client opened! sequence=1 session=null 03:00:08.937026-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 03:00:08.943330-INFO PrintPacketClient-0.0.0.0:45151 Connection established! 03:00:08.943557-INFO PrintPacketClient-0.0.0.0:45151 Connection opened! 03:00:08.943622-INFO PrintPacketClient-0.0.0.0:45151 Got packet: Hello there! 03:00:09.838106-INFO PrintPacketClient-0.0.0.0:45151 Got packet: Hello there! 03:00:10.838293-INFO PrintPacketClient-0.0.0.0:45151 Got packet: Hello there!