0

I'm a beginner in java. I have a device that sends UDP broadcast ping packets every 5s, and rarely sends an alert (a kind of alarm).

I want to have a java program that listens for the ping packets and shows how long since the last ping, and sets off an alarm of some kind either if a ping is not received for a certain amount of time, or if an actual alert from the sensor is received.

I've shamelessly copied this code from stackoverflow, but the serverSocket.receive(receivePacket) blocks, so the counter does not work.

I think that I need to spawn a thread to open the socket and wait for received data, and then communicate back to the main thread somehow without blocking so the timer can run, but I have no idea how to achieve that.

Here's the code I have so far - it works but the timer does not work to show the time since the last ping:

package com.testing;

import java.io.IOException;
import java.net.*;
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class Receiver {  // from https://stackoverflow.com/a/13780614/1340782

    public static void main(String[] args) {
        int port = args.length == 0 ? 8266 : Integer.parseInt(args[0]);
        new Receiver().run(port);
    }

    public void run(int port) {
        try {
            DatagramSocket serverSocket = new DatagramSocket(port);
            byte[] receiveData = new byte[8];
            String sendString = "pong";
            byte[] sendData = sendString.getBytes("UTF-8");

            System.out.printf("Listening on udp:%s:%d%n",
                    InetAddress.getLocalHost().getHostAddress(), port);
            DatagramPacket receivePacket = new DatagramPacket(receiveData,
                    receiveData.length);


            LocalTime lastPing = java.time.LocalTime.now();
            String lastMessage = "";

            while(true)
            {
                serverSocket.receive(receivePacket);
                String sentence = new String( receivePacket.getData(), 0,
                        receivePacket.getLength() );
//                System.out.println(java.time.LocalTime.now().truncatedTo(ChronoUnit.SECONDS) + " RECEIVED: " + sentence);
                LocalTime now = java.time.LocalTime.now();
                lastMessage = now.format(DateTimeFormatter.ofPattern("HH:mm:ss"))
                        + " RECEIVED: " + sentence;
                if (sentence.equals("ping")) {
                    lastPing = java.time.LocalTime.now();
                }
                lastMessage = lastMessage + " - " + Duration.between(lastPing,now).toMillis() + "ms since last ping";
                System.out.print(lastMessage);
                System.out.print("\r");
            }
        } catch (IOException e) {
            System.out.println(e);
        }
        // should close serverSocket in finally block
    }
}

Thanks everyone

localhost
  • 1,253
  • 4
  • 18
  • 29
  • If you're a beginner in Java, you might have a hard time understanding multi-threading programming, which is required for your example to work unless you use async Java NIO. Perhaps that would be easier than learning multi-threading... unless you have experience with multi-threading from other languages? – Renato Jul 12 '21 at 15:19
  • @Renato I'm not a complete beginner but I don't know much about threading, except a very simple example socket server that I did understand, but in that each thread only used sysout to print messages, not exchanging messages with a main thread. How can I achieve what I want using async Java NIO? – localhost Jul 13 '21 at 00:04
  • Async IO's API is pretty difficult to use, try to google around for examples, e.g. http://thushw.blogspot.com/2011/06/asynchronous-udp-server-using-java-nio.html ... I posted some code using the blocking socket API (which is much simpler) that should get you unstuck, while using very simple multi-threading constructs so you should be able to get a grip on it without too much problem. – Renato Jul 13 '21 at 07:56

1 Answers1

1

You are using blocking IO in this example, so if you want something to be running at the same time while your socket is waiting for data, you will need another Thread, as you figured out already.

In this case, it looks like you don't really need to manage a Thread yourself, just use Java's repeating scheduler service instead, so that you can get a task to run repeatedly every p period of time.

Your code doing networking doesn't require any changes, just pull out the code that manages the pings into a separate method that will do something like this:

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

class Scratch
{
    // AtomicReference helps exchange data between threads safely
    static final AtomicReference<Instant> lastPing = new AtomicReference<>();

    public static void main(String[] args) throws InterruptedException
    {

        // the lambda below will run every 2 seconds
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            Instant lastPingTime = lastPing.get();
            if (lastPingTime == null)
            {
                System.out.println("No ping yet");
            }
            else
            {
                System.out.println("Last ping was " + Duration.between(lastPingTime, Instant.now()) + " ago");
            }
        }, 0, 2, TimeUnit.SECONDS);

        // simulate a ping from the main Thread after 5 seconds
        Thread.sleep(5_000L);

        lastPing.set(Instant.now());
    }
}

You can run this code and it should print something like this:

No ping yet
No ping yet
No ping yet
Last ping was PT0.999S ago
Last ping was PT2.997S ago
Last ping was PT4.995S ago
...

Hopefully this is enough to get you unblocked. There's much to read about multi-threading in Java, but knowing you can use AtomicReference as well as its primitive cousins (AtomicBoolean, AtomicInteger...) to safely exchange data between different Threads should already be enough for you to implement what you want now.

Learning about the Java ExecutorService (I used Executors above to create one) is also important, so you don't need to create Threads yourself directly (the service will do it for you). Notice that because the ExecutorService usually keeps a thread-pool internally, you need to make sure to call close() on it once you're done, otherwise your application will never finish running (might be ok for GUIs and servers, but not for CLIs and short-running processes).

Renato
  • 12,940
  • 3
  • 54
  • 85
  • Thanks so much for the useful answer, I got it working quite nicely and it solved my problem. I would for the future like to be able to use threads properly because I won't always need to just keep running the same thread like in this simple example. Are AtomicReferences the "usual" way to exchange data with the main thread? – localhost Jul 13 '21 at 12:56
  • This example shows a "proper" way of using Threads. Using Threads directly should be uncommon in Java because Executors are usually the best way to go... you can ofc just do `new Thread(() -> {/* code here runs in thread*/}).start();` if you want though.... and yeah, the `Atomic*` classes are good ways of sharing data between Threads. – Renato Jul 13 '21 at 21:18