Device discovery over Wifi for defined period of time - java

I am writing a code to send a UDP Multicast over Wifi from my mobile device. There is a server code running on other devices in the network. The servers will listen to the multicast and respond with their IP Address and Type of the system (Type: Computer, Mobile Device, Raspberry Pi, Flyports etc..)
On the mobile device which has sent the UDP Multicast, I need to get the list of the devices responding to the UDP Multicast.
For this I have created a class which will work as the structure of the device details.
DeviceDetails.class
public class DeviceDetails
{
String DeviceType;
String IPAddr;
public DeviceDetails(String type, String IP)
{
this.DeviceType=type;
this.IPAddr=IP;
}
}
I am sending the UDP Multicast packet at the group address of 225.4.5.6 and Port Number 5432.
I have made a class which will call a thread which will send the UDP Packets. And on the other hand I have made a receiver thread which implements Callable Interface to return the list of the devices responding.
Here is the code:
MulticastReceiver.java
public class MulticastReceiver implements Callable<DeviceDetails>
{
DatagramSocket socket = null;
DatagramPacket inPacket = null;
boolean check = true;
public MulticastReceiver()
{
try
{
socket = new DatagramSocket(5500);
}
catch(Exception ioe)
{
System.out.println(ioe);
}
}
#Override
public DeviceDetails call() throws Exception
{
// TODO Auto-generated method stub
try
{
byte[] inBuf = new byte[WifiConstants.DGRAM_LEN];
//System.out.println("Listening");
inPacket = new DatagramPacket(inBuf, inBuf.length);
if(check)
{
socket.receive(inPacket);
}
String msg = new String(inBuf, 0, inPacket.getLength());
Log.v("Received: ","From :" + inPacket.getAddress() + " Msg : " + msg);
DeviceDetails device = getDeviceFromString(msg);
Thread.sleep(100);
return device;
}
catch(Exception e)
{
Log.v("Receiving Error: ",e.toString());
return null;
}
}
public DeviceDetails getDeviceFromString(String str)
{
String type;
String IP;
type=str.substring(0,str.indexOf('`'));
str = str.substring(str.indexOf('`')+1);
IP=str;
DeviceDetails device = new DeviceDetails(type,IP);
return device;
}
}
The following code is of the activity which calls the Receiver Thread:
public class DeviceManagerWindow extends Activity
{
public void searchDevice(View view)
{
sendMulticast = new Thread(new MultiCastThread());
sendMulticast.start();
ExecutorService executorService = Executors.newFixedThreadPool(1);
List<Future<DeviceDetails>> deviceList = new ArrayList<Future<DeviceDetails>>();
Callable<DeviceDetails> device = new MulticastReceiver();
Future<DeviceDetails> submit = executorService.submit(device);
deviceList.add(submit);
DeviceDetails[] devices = new DeviceDetails[deviceList.size()];
int i=0;
for(Future<DeviceDetails> future :deviceList)
{
try
{
devices[i] = future.get();
}
catch(Exception e)
{
Log.v("future Exception: ",e.toString());
}
}
}
}
Now the standard way of receiving the packet says to call the receive method under an infinite loop. But I want to receive the incoming connections only for first 30seconds and then stop looking for connections.
This is similar to that of a bluetooth searching. It stops after 1 minute of search.
Now the problem lies is, I could use a counter but the problem is thread.stop is now depricated. And not just this, if I put the receive method under infinite loop it will never return the value.
What should I do.? I want to search for say 30 seconds and then stop the search and want to return the list of the devices responding.

Instead of calling stop(), you should call interrupt(). This causes a InterruptedException to be thrown at interruptable spots at your code, e.g. when calling Thread.sleep() or when blocked by an I/O operation. Unfortunately, DatagramSocket does not implement InterruptibleChannel, so the call to receive cannot be interrupted.
So you either use DatagramChannel instead of the DatagramSocket, such that receive() will throw a ClosedByInterruptException if Thread.interrupt() is called. Or you need to set a timeout by calling DatagramSocket.setSoTimeout() causing receive() to throw a SocketTimeoutException after the specified interval - in that case, you won't need to interrupt the thread.
Simple approach
The easiest way would be to simply set a socket timeout:
public MulticastReceiver() {
try {
socket = new DatagramSocket(5500);
socket.setSoTimeout(30 * 1000);
} catch (Exception ioe) {
throw new RuntimeException(ioe);
}
}
This will cause socket.receive(inPacket); to throw a SocketTimeoutException after 30 seconds. As you already catch Exception, that's all you need to do.
Making MulticastReceiver interruptible
This is a more radical refactoring.
public class MulticastReceiver implements Callable<DeviceDetails> {
private DatagramChannel channel;
public MulticastReceiver() {
try {
channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(5500));
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
public DeviceDetails call() throws Exception {
ByteBuffer inBuf = ByteBuffer.allocate(WifiConstants.DGRAM_LEN);
SocketAddress socketAddress = channel.receive(inBuf);
String msg = new String(inBuf.array(), 0, inBuf.capacity());
Log.v("Received: ","From :" + socketAddress + " Msg : " + msg);
return getDeviceFromString(msg);;
}
}
The DeviceManagerWindow looks a bit different; I'm not sure what you intend to do there, as you juggle around with lists and arrays, but you only have one future... So I assume you want to listen for 30 secs and fetch as many devices as possible.
ExecutorService executorService = Executors.newFixedThreadPool(1);
MulticastReceiver receiver = new MulticastReceiver();
List<DeviceDetails> devices = new ArrayList<DeviceDetails>();
long runUntil = System.currentTimeMillis() + 30 * 1000;
while (System.currentTimeMillis() < runUntil) {
Future<Object> future = executorService.submit(receiver);
try {
// wait no longer than the original 30s for a result
long timeout = runUntil - System.currentTimeMillis();
devices.add(future.get(timeout, TimeUnit.MILLISECONDS));
} catch (Exception e) {
Log.v("future Exception: ",e.toString());
}
}
// shutdown the executor service, interrupting the executed tasks
executorService.shutdownNow();
That's about it. No matter which solution you choose, don't forget to close the socket/channel.

I have solved it.. you can run your code in following fashion:
DeviceManagerWindow.java
public class DeviceManagerWindow extends Activity
{
public static Context con;
public static int rowCounter=0;
Thread sendMulticast;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device_manager_window);
WifiManager wifi = (WifiManager)getSystemService( Context.WIFI_SERVICE );
if(wifi != null)
{
WifiManager.MulticastLock lock = wifi.createMulticastLock("WifiDevices");
lock.acquire();
}
TableLayout tb = (TableLayout) findViewById(R.id.DeviceList);
tb.removeAllViews();
con = getApplicationContext();
}
public void searchDevice(View view) throws IOException, InterruptedException
{
try
{
sendMulticast = new Thread(new MultiCastThread());
sendMulticast.start();
sendMulticast.join();
}
catch(Exception e)
{
Log.v("Exception in Sending:",e.toString());
}
here is the time bound search.... and you can quit your thread using thread.join
//Device Will only search for 1 minute
for(long stop=System.nanoTime()+TimeUnit.SECONDS.toNanos(1); stop>System.nanoTime();)
{
Thread recv = new Thread(new MulticastReceiver());
recv.start();
recv.join();
}
}
public static synchronized void addDevice(DeviceDetails device) throws InterruptedException
{
....
Prepare your desired list here.
....
}
}
Dont add any loop on the listening side. simply use socket.receive
MulticastReceiver.java
public class MulticastReceiver implements Runnable
{
DatagramSocket socket = null;
DatagramPacket inPacket = null;
public MulticastReceiver()
{
try
{
socket = new DatagramSocket(WifiConstants.PORT_NO_RECV);
}
catch(Exception ioe)
{
System.out.println(ioe);
}
}
#Override
public void run()
{
byte[] inBuf = new byte[WifiConstants.DGRAM_LEN];
//System.out.println("Listening");
inPacket = new DatagramPacket(inBuf, inBuf.length);
try
{
socket.setSoTimeout(3000)
socket.receive(inPacket);
String msg = new String(inBuf, 0, inPacket.getLength());
Log.v("Received: ","From :" + inPacket.getAddress() + " Msg : " + msg);
DeviceDetails device = getDeviceFromString(msg);
DeviceManagerWindow.addDevice(device);
socket.setSoTimeout(3000)will set the listening time for the socket only for 3 seconds. If the packet dont arrive it will go further.DeviceManagerWindow.addDevice(device);this line will call the addDevice method in the calling class. where you can prepare your list
}
catch(Exception e)
{
Log.v("Receiving Error: ",e.toString());
}
finally
{
socket.close();
}
}
public DeviceDetails getDeviceFromString(String str)
{
String type;
String IP;
type=str.substring(0,str.indexOf('`'));
str = str.substring(str.indexOf('`')+1);
IP=str;
DeviceDetails device = new DeviceDetails(type,IP);
return device;
}
}
Hope that works.. Well it will work.
All the best. Let me know if any problem.

Related

Code of new thread after accepting the connection in TCP server isn't executed

I have the following tcp server:
public class Server {
private Connection db;
private Statement statement;
private ServerSocket socket;
public static void main(String[] args) {
Server server = new Server();
server.initializeServer();
System.out.println("Server initialized");
server.listenConnections();
}
private void initializeServer() {
try {
db = DriverManager.getConnection("jdbc:mysql://localhost:3306/courseworkschema" +
"?verifyServerCertificate=false" +
"&useSSL=false" +
"&requireSSL=false" +
"&useLegacyDatetimeCode=false" +
"&amp" +
"&serverTimezone=UTC",
"Sergei",
"12345");
statement = db.createStatement();
socket = new ServerSocket(1024);
} catch (SQLException | IOException e) {
e.printStackTrace();
}
}
private void listenConnections() {
System.out.println("Listening connections ... ");
while (true) {
try {
Socket client = socket.accept();
new Thread(() -> {
System.out.println("Client accepted");
try {
OutputStream outputStream = client.getOutputStream();
InputStream inputStream = client.getInputStream();
String clientAction;
String queryContent;
boolean flag = true;
while (flag) {
byte[] msg = new byte[100];
int k = inputStream.read(msg);
clientAction = new String(msg, 0, k);
clientAction = clientAction.trim();
msg = new byte[100];
k = inputStream.read(msg);
queryContent = new String(msg, 0, k);
queryContent = queryContent.trim();
System.out.println(clientAction);
System.out.println(queryContent);
if (clientAction.equalsIgnoreCase("END")) {
flag = false;
}
else if (clientAction.equalsIgnoreCase("LOGIN")) {
System.out.println("Login action");
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
This server is created to communicate with database. Here's the way how I try to connect to this serverL
public class LoginController {
private LoginWindow window;
private Socket socket;
private InputStream is;
private OutputStream os;
public LoginController() {
connectToServer();
}
public void logInUser(String login, String password) {
if (!login.isEmpty() && !password.isEmpty()) {
sendDataToServer("LOGIN");
sendDataToServer("");
} else {
window.showMessageDialog("Fill the fields!", JOptionPane.ERROR_MESSAGE);
}
}
public void attachView(LoginWindow window) {
this.window = window;
}
private void connectToServer() {
try {
socket = new Socket("127.0.0.1", 1024);
System.out.println("Connected");
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendDataToServer(String res) {
try {
os = socket.getOutputStream();
os.write(res.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
When I run the server and then client, I have such logs in server:
Server initialized
Listening connections ...
Process finished with exit code -1
So, I can't understand why server doesn't wait and accept a connection from client, but closes after initializing and listening. So, what's the matter? I will appreciate any help. Thanks in advance!
UPD
When I run my app it started to work but I found out that code in Thread block isn't executed. I even can't understand, why does it happen
In your private void listenConnections() you are creating a Thread object but you are not telling it to start after its created thus it wont execute.
Your thread creation line should look something like this:
new Thread(() -> {
//your code
}).start();
From the javadocs:
https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#start()
public void start()
Causes this thread to begin execution; the Java Virtual Machine calls
the run method of this thread. The result is that two threads are
running concurrently: the current thread (which returns from the call
to the start method) and the other thread (which executes its run
method).
It is never legal to start a thread more than once. In particular, a
thread may not be restarted once it has completed execution.
Throws: IllegalThreadStateException - if the thread was already
started.
See Also: run(), stop()

Reducing jitter in Android's WiFi direct communication using UDP transport protocol

I'm working on a real-time application that requires transmitting about 512 bytes of data every 5ms over a WiFi direct connection from a slave phone/s to a master (group owner). Average transmission delay is about 15ms, which is not that good, but the biggest issue is that I noticed a quite consistent pattern of periods where delay is as much as 100ms about every 2~3 seconds, plus periods of about 9 seconds every minute where throughput greatly reduces. Please take a look at the graph scatter graph of delay over time. I also attached my socket programming code. Both master and slave internal clocks are synchronized within a 1ms error (don't worry about clock drift).
Please let me know if you too have experienced these results and whether there is something that can be done to improve on this issue. Interestingly enough there is typically 0 data loss, and when a packet gets delayed all packets before it too (easy to see from the graph) even though this is UDP. I read somewhere that packets need to get acknowledged at the MAC layer even if UDP is used, and this is what might be causing delays of about 100ms.
The following is the code at the slave (sender):
public class DataTransmissionServiceV3 {
public static final String TAG = "DataTransmissionService";
private long mClockOffset;
private DatagramChannel mDatagramChannel;
private boolean running;
public void start(InetAddress remoteAddress, long clockOffset) {
if (!running) {
running = true;
mClockOffset = clockOffset;
Thread t = new Thread(new TransmissionTask(remoteAddress));
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
}
public void close() {
running = false;
if (mDatagramChannel != null) {
mDatagramChannel.socket().close();
}
}
private class TransmissionTask implements Runnable {
private int mCount;
private final ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(
Constants.DATA_PACKET_SIZE);
private final byte[] mBytes = new byte[Constants.DATA_PACKET_SIZE];
private final InetAddress mRemoteAddress;
public TransmissionTask(final InetAddress remoteAddress) {
mRemoteAddress = remoteAddress;
}
#Override
public void run() {
try {
mDatagramChannel = DatagramChannel.open();
mDatagramChannel.socket().connect(new InetSocketAddress(mRemoteAddress,
Constants.TRANS_MASTER_SERVER_PORT));
mDatagramChannel.configureBlocking(false);
} catch (IOException ioe) {
Log.e(TAG, "Exception while connecting", ioe);
}
while (running) {
TimeUtils.busyWait(5000000); // busy wait for 5ms
send();
}
}
private void send() {
mCount++;
DataPacket.setSequenceNumber(mBytes, mCount);
DataPacket.setTimestamp(mBytes, TimeUtils.getTimestamp() + mClockOffset); // synchronized timestamp to master device to measure delay
mByteBuffer.clear();
mByteBuffer.put(mBytes);
mByteBuffer.flip();
try {
mDatagramChannel.write(mByteBuffer);
} catch (Exception e) {
Log.e(TAG, "Exception while sending data packet", e);
running = false;
}
}
}
}
And the following is the code at the master (receiver):
public class DataTransmissionServerV3 {
public static final String TAG = "DataTransmissionServer";
private DatagramChannel mDatagramChannel;
private boolean mRunning;
private Handler mHandler;
public DataTransmissionServerV3(Handler handler) {
mHandler = handler;
}
public void start(InetAddress localAddress) {
if (!mRunning) {
mRunning = true;
Thread t = new Thread(new ReceiveDataTask(localAddress));
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
}
public void close() {
mRunning = false;
if (mDatagramChannel != null) {
mDatagramChannel.socket().close();
}
}
private class ReceiveDataTask implements Runnable {
private final InetAddress mLocalAddress;
private final ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(
Constants.DATA_PACKET_SIZE);
private final byte[] mBytes = new byte[Constants.DATA_PACKET_SIZE];
private int mCount;
public ReceiveDataTask(final InetAddress localAddress) {
mLocalAddress = localAddress;
}
#Override
public void run() {
try {
bind();
while (mRunning) {
receive();
}
} catch (IOException ioe) {
Log.e(TAG, "Exception while binding datagram socket", ioe);
}
}
private void bind() throws IOException {
mDatagramChannel = DatagramChannel.open();
mDatagramChannel.socket().bind(new InetSocketAddress(mLocalAddress,
Constants.TRANS_MASTER_SERVER_PORT));
mDatagramChannel.configureBlocking(true);
}
private boolean receive() {
mByteBuffer.clear();
try {
SocketAddress isa = mDatagramChannel.receive(mByteBuffer);
long t2 = TimeUtils.getTimestamp();
if (isa != null) {
mByteBuffer.flip();
mByteBuffer.get(mBytes);
mCount++;
TransmissionStat stat = TransmissionStat.get(mBytes, mCount, t2);
handlePacket(stat); // a statistic that is saved to file for later analysis (ignore this)
return true;
}
} catch (IOException ioe) {
Log.e(TAG, "Exception while receiving data", ioe);
mRunning = false;
}
return false;
}
private void handlePacket(TransmissionStat stat) {
Message msg = mHandler.obtainMessage();
msg.what = Constants.TRANSMISSION_PACKET_RECEIVED_CODE;
msg.obj = stat;
msg.sendToTarget();
}
}

Getting socket data on seperate thread and then passing it to main thread

Edited my question for clarification and code:
My goal is to pass my String data from my background thread, to my main application thread. Any help is appreciated.
Here is the code that creates the main background thread. This is located in my Server.java class
public class Server {
boolean isConnected = false;
Controller controller = new Controller();
public void startHost() {
Thread host = new Thread(() -> {
Controller controller = new Controller();
ServerSocket server = null;
try {
server = new ServerSocket(GeneralConstants.applicationPort);
} catch (BindException e2) {
System.out.println("Port Already in Use!");
} catch (IOException e) {
//do nothing
}
while (true) {
if (server == null) { break; }
try {
Socket client = server.accept();
System.out.println("Client Connected: " + isConnected);
if (!isConnected) {
controller.createClientHandler(client);
isConnected = true;
System.out.println("Client Connected: " + isConnected);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
host.setDaemon(true);
host.start();
}
Here is the code that is then called when a client is connected, located in my Controller.java class.
public synchronized void createClientHandler(Socket client) {
boolean alreadyConnected = false;
if (alreadyConnected) {
//do NOT assign multiple threads for each client
} else {
ClientHandler handleClients = new ClientHandler("client", client);
}
}
The program then creates two background threads for my client, one to manage receiving messages, and sending messages.
public ClientHandler(String name, Socket s) {
clientSocket = s;
clientName = name;
receiveThread = new Thread(this::receive);
sendThread = new Thread(this::send);
connected = clientSocket.isConnected();
receiveThread.start();
sendThread.start();
}
The thread then successfully creates the inputstream and passes the object to my controller. Which then process and grabs a string assigning it to a variable
public synchronized void handleReceivedPacket(String name, BufferedReader in) {
try {
data = in.readLine();
System.out.println("Successfully assigned data to: " + data);
} catch (IOException e) {
System.out.println("Unable to read result data");
}
}
How do I access my String data from the main thread without getting null?
Aka I can call (or something similar)
controller.returnData();
from my main application. From which it'll either return null (no data yet), or actually return my data. Right now, it's always null.
Edit, this is what's actually calling controller.returnData() {
I don't want to paste a massive amount of code for fear of reaching StackOverflow's code limit, so here's my application structure.
My JavaFX creates the scene, and creates a root gridpane, it then calls a method that creates sub gridpanes based the specified input. Aka, a user can press "Main Menu" that calls my method setScene() which removes the current "sub-root" gridpane and creates a "new" scene. Right now, I have a GameBoard.java class which on button press, calls controller.returnData()
PassOption.setOnAction(event -> {
System.out.println(controller.returnData());
});
There is no functional purpose for this besides testing. If I can receive the data, then I can expand on this using the data.
Start thinking about design. In network applications you typically have to manage the following responsibilites:
Connected clients and their state (connection state, heartbeats, ...)
Received messages from the clients
Messages to transmit to the clients
It makes sense to separate those responsibilities in order to keep the code clean, readable and maintainable.
Separation can mean both, thread-wise and class-wise.
For example, you could implement it as follows:
The class ClientAcceptor is responsible for opening the socket and accepting clients. As soon as a client has connected, it delegates the further work to a controller and then waits for other clients:
public class ClientAcceptor implements Runnable {
#Override
public void run() {
while (true) {
ServerSocket server;
try {
server = new ServerSocket(1992);
Socket client = server.accept();
if (client.isConnected()) {
controller.createClientHandler(client);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
The controller could then create a handler (if the controller decides to do so, e.g. it could also decline the client). The ClientHandler class could look as follows:
public class ClientHandler {
private Thread receiveThread;
private Thread sendThread;
private boolean connected;
private Socket clientSocket;
private String clientName;
private LinkedBlockingDeque<byte[]> sendQueue;
public ClientHandler(String name, Socket s) {
clientSocket = s;
clientName = name;
receiveThread = new Thread(() -> receive());
sendThread = new Thread(() -> send());
connected = clientSocket.isConnected();
receiveThread.start();
sendThread.start();
}
private void receive() {
BufferedInputStream in = null;
try {
in = new BufferedInputStream(clientSocket.getInputStream());
} catch (IOException e) {
connected = false;
}
while (connected) {
try {
byte[] bytes = in.readAllBytes();
if (bytes != null && bytes.length > 0) {
controller.handleReceivedPacket(clientName, bytes);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void send() {
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(clientSocket.getOutputStream());
} catch (IOException e) {
connected = false;
}
while (connected) {
byte[] toSend = sendQueue.getFirst();
if (toSend != null && toSend.length > 0) {
try {
out.write(toSend);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void send(byte[] packet) {
sendQueue.add(packet);
}
public void close() {
connected = false;
}
}
The ClientHandler is responsible for receiving and transmitting data. If a packet arrives it informes the controller, which parses the packet. The ClientHandler also provides a public API to send data (which is stored in a queue and handled by a thread) and close the connection.
The above code examples are neither tested, nor complete. Take it as a starting point.

Process items in queue as the items are added inside a while loop

I have a method where I listen for UDP packets in a while loop. I want to parse the packets using another method in a different class as they arrive and do many different parsing and analyzing of each packet in another part of the application. I am thinking it would be better to have the PacketParser methods process the Queue outside of the loop. Would it be possible to just add the packets to a Queue as they come in and then have another part of the application listen for items as they come into the Queue and perform other actions as the original while loop keeps listening for packets and adds them to the queue? I would like to have another function monitor the queue and process the packets, is there something in Java to monitor a Queue or Stack? Is there a better way to do this?
public void read(String multicastIpAddress, int multicastPortNumber) {
PacketParser parser = new PacketParser(logger);
InetAddress multicastAddress = null;
MulticastSocket multicastSocket = null;
final int PortNumber = multicastPortNumber;
try {
multicastAddress = InetAddress.getByName(multicastIpAddress);
multicastSocket = new MulticastSocket(PortNumber);
String hostname = InetAddress.getLocalHost().getHostName();
byte[] buffer = new byte[8192];
multicastSocket.joinGroup(multicastAddress);
System.out.println("Listening from " + hostname + " at " + multicastAddress.getHostName());
int numberOfPackets = 0;
while (true) {
numberOfPackets++;
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
multicastSocket.receive(datagramPacket);
// add to queue for another function to process the packets
}
} catch (SocketException socketException) {
System.out.println("Socket exception " + socketException);
} catch (IOException exception) {
System.out.println("Exception " + exception);
} finally {
if (multicastSocket != null) {
try {
multicastSocket.leaveGroup(multicastAddress);
multicastSocket.close();
} catch (IOException exception) {
System.out.println(exception.toString());
}
}
}
}
Ok, so I did some reading about the producer-consumer pattern and figured it out so here is what I did.
Basically the producer-consumer pattern involves three things: a producer, a consumer and a shared queue. In this context the PacketReader is the producer that takes in network packets and places them into the shared queue. The PacketParser is the consumer who processes the packets in the shared queue. So I created an instance of a LinkedBlockingQueue and passed that shared queue into an instance of the consumer (PacketReader) and an instance of the producer (PacketParser). Then the consumer and producer instances are each passed into an instance of the Thread class. Finally, call the start() method on each thread instance.
public class Main {
public static void main(String[] args) {
BlockingQueue<Packet> queue = new LinkedBlockingQueue<>();
ILogger logger = Injector.getLogger();
Thread reader = new Thread(new PacketReader(logger, queue, "239.1.1.1", 49410));
Thread parser = new Thread(new PacketParser(logger, queue));
reader.start();
parser.start();
}
}
The reason to use the LinkedBlockingQueue is because the put() method will block the queue if full and take() will block if queue if empty. The producer and consumer classes need to implement the Runnable interface and contain a method named run() that takes no parameters.
Consumer class
public class PacketParser implements Runnable {
private ILogger logger;
private BlockingQueue<Packet> queue;
private boolean running = true;
public PacketParser(ILogger logger, BlockingQueue<Packet> queue) {
this.logger = logger;
this.queue = queue;
}
public void stop() {
running = false;
}
public void run() {
while (running) {
Packet packet;
try {
packet = queue.take();
parse(packet);
} catch (InterruptedException exception) {
logger.Log(exception.getStackTrace().toString());
}
}
}
Producer class
public class PacketReader implements Runnable {
private ILogger logger;
private final Queue<Packet> queue;
private String multicastIpAddress;
private int multicastPortNumber;
private boolean running = true;
public PacketReader(ILogger logger, Queue<Packet> queue, String multicastIpAddress, int multicastPortNumber) {
this.logger = logger;
this.queue = queue;
this.multicastIpAddress = multicastIpAddress;
this.multicastPortNumber = multicastPortNumber;
}
public void stop() {
running = false;
}
public void run() {
InetAddress multicastAddress = null;
MulticastSocket multicastSocket = null;
try {
multicastAddress = InetAddress.getByName(multicastIpAddress);
multicastSocket = new MulticastSocket(multicastPortNumber);
String hostname = InetAddress.getLocalHost().getHostName();
byte[] buffer = new byte[8192];
multicastSocket.joinGroup(multicastAddress);
System.out.println("Listening from " + hostname + " at " + multicastAddress.getHostName());
int numberOfPackets = 0;
while (running) {
numberOfPackets++;
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
multicastSocket.receive(datagramPacket);
Packet packet = new Packet(numberOfPackets, datagramPacket);
queue.add(packet);
}
} catch (SocketException socketException) {
System.out.println("Socket exception " + socketException);
} catch (IOException exception) {
System.out.println("Exception " + exception);
} finally {
if (multicastSocket != null) {
try {
multicastSocket.leaveGroup(multicastAddress);
multicastSocket.close();
} catch (IOException exception) {
System.out.println(exception.toString());
}
}
}
}
}

Java Datagram send and receive not staying in sync

I have a device that replies to commands using UDP datagrams. I'm trying to come up with a way to guarantee the response is from a particular request. When I run the following code I get the first two, and sometimes third request, then it just hangs and times out the receive. I've tried a few different methods to resolve it (hence the synchronize and iQueuePointer receive timeout). Here is an example of a run:
Listening for X-Air responses
Sending X-Air requests
Servicing request 6
Send loop - bWait=true request.size=6 iQueuePointer=6
/info,ssssV0.04XR12-24-00-6EXR121.10 from 6
Removed 6
Servicing request 5
Send loop - bWait=true request.size=5 iQueuePointer=5
/ch/01/config/name,sPreach Mic from 5
Removed 5
Here's the code:
public static final Object socketLock = new Object();
public static DatagramSocket socket;
public static ArrayList<String> request = new ArrayList<>();
public static int iQueuePointer;
public static boolean bWait;
public static void main(String[] args) {
new Main();
}
private Main() {
try {
socket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
new Thread(receiveRunnable).start();
new Thread(sendRunnable).start();
iQueuePointer++;
request.add("/info");
iQueuePointer++;
request.add("/ch/01/config/name");
iQueuePointer++;
request.add("/ch/02/config/name");
iQueuePointer++;
request.add("/ch/03/config/name");
iQueuePointer++;
request.add("/ch/04/config/name");
iQueuePointer++;
request.add("/ch/05/config/name");
iQueuePointer++;
}
private Runnable sendRunnable = new Runnable() {
#Override
public void run() {
System.out.println("Sending requests");
while(socket != null) {
if(!bWait && request.size() > 0 && request.size() < iQueuePointer) {
iQueuePointer--;
bWait = true;
System.out.println("Servicing request " + iQueuePointer);
//synchronized (socketLock) {
try {
socket.send(new DatagramPacket(request.get(0).getBytes(),
request.get(0).getBytes().length,
InetAddress.getByName("192.168.0.180"), 10024));
} catch (IOException e) {
e.printStackTrace();
}
//}
}
}
System.out.println("sendRunnable ended");
}
};
private Runnable receiveRunnable = new Runnable() {
#Override
public void run() {
System.out.println("Listening for responses");
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
while(socket != null) {
//synchronized (socketLock) {
try {
//socket.setSoTimeout(5000);
socket.receive(packet);
if (packet.getLength() > 0 && socket != null) {
System.out.println(new String(Arrays.copyOf(packet.getData(), packet.getLength()))
+ " from " + iQueuePointer);
request.remove(0);
System.out.println("Removed " + iQueuePointer);
bWait = false;
}
} catch (Exception e) {
if (!e.toString().contains("Receive timed out")) {
e.printStackTrace();
} else {
System.out.println("Receive loop - bWait=" + bWait + " request.size=" + request.size()
+ " iQueuePointer=" + iQueuePointer);
}
}
//}
}
System.out.println("receiveRunnable ended");
}
};
In UDP you can't assume that the responses arrive in the same order as the requests were sent, or indeed at all, and you also can't assume they only arrive once.
You will have to use sequence numbers in both, to match them up, or else not have any pending requests, and keep issuing a request until you get its response, and then you still have to deal with duplicates somehow.
NB You need to either recreate the DatagramPacket or at least reset its length every time around the read loop: otherwise it can keep shrinking to the size of the smallest datagram received so far.

Categories

Resources