I built a server and client using NIO package with server socket channel and socket channel. The basic setup works. but when I try to move my code handling each connection in a separate thread using executor service I start getting weird errors where I am getting partial message or sometimes blank messages.
This is my code where am I passing my selection key to a thread with executor service
private void readAndRespond(SelectionKey selectionKey) {
this.executorService.submit(new Worker(selectionKey));
}
private class Worker implements Runnable {
private SelectionKey selectionKey;
private Worker(SelectionKey selectionKey) {
this.selectionKey = selectionKey;
}
#Override
public void run() {
SocketChannel socketChannel;
ArrayList<ByteBuffer> buffers;
String data, reply;
ByteBuffer responseBuffer;
socketChannel = (SocketChannel) selectionKey.channel();
try {
buffers = this.readRequest(socketChannel);
data = this.extractData(buffers);
if (!data.isEmpty()) {
reply = responseManager.reply(data);
responseBuffer = ByteBuffer.wrap(reply.getBytes());
socketChannel.write(responseBuffer);
}
}
catch (IOException e) {
System.out.println("Unable to process response " + e.getMessage());
}
}
private ArrayList<ByteBuffer> readRequest(SocketChannel socketChannel) throws IOException {
int counter;
ByteBuffer current;
ArrayList<ByteBuffer> buffers;
counter = 2;
buffers = new ArrayList<>();
current = ByteBuffer.allocate(minBuffer);
buffers.add(current);
while (socketChannel.read(current) > 0) {
if (!current.hasRemaining()) {
current = ByteBuffer.allocate(minBuffer * 2 * counter);
buffers.add(current);
counter++;
}
}
return buffers;
}
private String extractData(ArrayList<ByteBuffer> buffers) throws UnsupportedEncodingException {
StringBuilder stringBuilder;
stringBuilder = new StringBuilder();
for(ByteBuffer buffer : buffers) {
stringBuilder.append(new String(buffer.array(), "UTF-8"));
}
return stringBuilder.toString().trim();
}
}
Needed to add this before handing off to thread for reading
selectionKey.interestOps( selectionKey.interestOps() & ~SelectionKey.OP_READ );
Related
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();
}
}
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());
}
}
}
}
}
I am new to Netty and trying to writing a server client project using it. I am able to send the request from client to the server successfully and am able to process it too using my listeners. But the issue am having is when I try to write the response back to the channel for the client processing on the server side using channel.write(), I am unable to get it back at client.
This is my first time on stackoverflow, please forgive if I make some mistake while asking the question or for indentation issues while posting the code.
This is my server :
public class SocketIOServer {
private Logger log = Logger.getLogger(getClass());
private ServerBootstrap bootstrap;
private Channel serverChannel;
private int port;
private boolean running;
public SocketIOServer(int port) {
this.port = port;
this.running = false;
}
public boolean isRunning() {
return this.running;
}
public void start() {
bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
try {
EncryptionManager encryptionManager = EncryptionManager.getInstance();
} catch (Exception ex) {
java.util.logging.Logger.getLogger(SocketIOServer.class.getName()).log(Level.SEVERE, null, ex);
}
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
#Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decode", new FrameDecoder(){
#Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
// Make sure if the length field was received.
if (buf.readableBytes() < 4) {
// The length field was not received yet - return null.
// This method will be invoked again when more packets are
// received and appended to the buffer.
return null;
}
// The length field is in the buffer.
// Mark the current buffer position before reading the length field
// because the whole frame might not be in the buffer yet.
// We will reset the buffer position to the marked position if
// there's not enough bytes in the buffer.
buf.markReaderIndex();
// Read the length field.
int length = buf.readInt();
// Make sure if there's enough bytes in the buffer.
if (buf.readableBytes() < length) {
// The whole bytes were not received yet - return null.
// This method will be invoked again when more packets are
// received and appended to the buffer.
// Reset to the marked position to read the length field again
// next time.
buf.resetReaderIndex();
return null;
}
// There's enough bytes in the buffer. Read it.
ChannelBuffer frame = buf.readBytes(length);
// Successfully decoded a frame. Return the decoded frame.
return frame;
}
});
pipeline.addLast("handler", new GameServerHandler());
return pipeline;
}
});
bootstrap.setOption("backlog", 1024);
// Bind and start to accept incoming connections.
this.serverChannel = bootstrap.bind(new InetSocketAddress(port));
this.running = true;
log.info("Server Started at port [" + port + "]");
System.out.println("Server Started at port [" + port + "]");
}
public static void main(String[] args) {
SocketIOServer server = new SocketIOServer(8888);
server.start();
}
Server Handler class :
public class GameServerHandler extends SimpleChannelUpstreamHandler {
#Override
public void handleUpstream(
ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
if (e instanceof ChannelStateEvent) {
logger.info(e.toString());
}
super.handleUpstream(ctx, e);
}
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception{
System.out.println("Following data recieved at the server");
ConnectionManager connectionManager = ConnectionManager.getInstance();
List<Object> paramsList = new ArrayList<Object>();
NetworkClient client = connectionManager.getNetworkClient(e.getChannel().getId());
ChannelBuffer ebuf = (ChannelBuffer)e.getMessage();
if( client == null ) {
client = new NetworkClient(e.getChannel());
connectionManager.addNetworkClient(e.getChannel().getId(), client);
}
byte [] encryptedData = ebuf.array();
System.out.println("encrypted data size : "+ encryptedData.length);
System.out.println("encrypted data : "+ encryptedData);
byte [] decrpytedData = null;
try {
decrpytedData = EncryptionManager.getInstance().decrypt(encryptedData, EncryptionManager.getInstance().getPrivateKey());
}catch (Throwable ee){
ee.printStackTrace();
}
ChannelBuffer buf = ChannelBuffers.buffer(decrpytedData.length);
buf.writeBytes(decrpytedData);
while(buf.readable()) {
long gameTableId = buf.readLong();
GameTable gameTable = gameTableController.getTablePeer(gameTableId);
if(gameTable == null) {
GameTable newGameTable = new GameTable();
newGameTable.setTableId(gameTableId);
newGameTable.registerListeners();
gameTableController.storeTablePeer(gameTableId, newGameTable);
}
int eventHash = buf.readInt();
String eventName = getEventNameFromEventHash(eventHash);
int paramCount = buf.readInt();
if(paramCount > 0) {
for(int count=0;count<paramCount;count++) {
populateParamList(buf, paramsList);
}
if(!NetworkMessenger.broadcastToAllFromNetwork(eventName, client, paramsList.toArray(new Object[paramsList.size()]))) {
logger.debug( "Unhandled Data:" + eventName);
System.out.println("Unhandled Data:" + eventName);
}
logger.debug( "Data processed successfully for " + eventName + " game table id : " + gameTableId);
System.out.println("Data processed successfully for " + eventName + " game table id : " + gameTableId);
}
break;
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
logger.log(
Level.WARN,
"Unexpected exception from downstream.",
e.getCause());
Channel ch = e.getChannel();
ch.close();
}
#Override
public void channelConnected(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
ChannelFuture cf = e.getFuture();
cf.addListener(new Greeter());
}
#Override
public void channelDisconnected(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
// Unregister the channel from the global channel list
// so the channel does not receive messages anymore.
channels.remove(e.getChannel());
}
private static final class Greeter implements ChannelFutureListener {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// Once session is secured, send a greeting.
String welcomeMsg = "Welcome";
ChannelBuffer cb = ChannelBuffers.dynamicBuffer();
cb.writeBytes(welcomeMsg.getBytes());
future.getChannel().write(cb);
// Register the channel to the global channel list
// so the channel received the messages from others.
channels.add(future.getChannel());
} else {
future.getChannel().close();
}
}
}
}
This is my client class :
public class Clientbot {
public static void main(String[] args) throws IOException {
String host = "localhost";
int port = 8888;
final ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
// Configure the client.
ChannelFactory factory =
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ClientBootstrap bootstrap = new ClientBootstrap(factory);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decode", new FrameDecoder(){
#Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
// Make sure if the length field was received.
if (buf.readableBytes() < 4) {
return null;
}
buf.markReaderIndex();
// Read the length field.
int length = buf.readInt();
// Make sure if there's enough bytes in the buffer.
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return null;
}
// There's enough bytes in the buffer. Read it.
ChannelBuffer frame = buf.readBytes(length);
// Successfully decoded a frame. Return the decoded frame.
return frame;
}
});
pipeline.addLast("handler", new ClientHandler());
return pipeline;
}
});
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
// Wait until the connection attempt succeeds or fails.
Channel channel = future.awaitUninterruptibly().getChannel();
if (!future.isSuccess()) {
future.getCause().printStackTrace();
bootstrap.releaseExternalResources();
return;
}
ChannelFuture lastWriteFuture = null;
try {
lastWriteFuture = writeSecureSampleData(channel, buf);
} catch (Exception ex) {
ex.printStackTrace();
}
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null) {
lastWriteFuture.awaitUninterruptibly();
}
// Close the connection. Make sure the close operation ends because
// all I/O operations are asynchronous in Netty.
channel.close().awaitUninterruptibly();
// Shut down all thread pools to exit.
bootstrap.releaseExternalResources();
}
private static ChannelFuture writeSecureSampleData(Channel channel, ChannelBuffer buffer) throws Exception {
long gameId = 1234;
ChannelBuffer buf = ChannelBuffers.buffer(256);
buf.writeLong(gameId);
writeParamsForLogin(buf);
byte [] data = buf.array();
byte [] encryptedData = EncryptionManager.getInstance().encrypt(data, EncryptionManager.getInstance().getPublicKey());
int size = encryptedData.length;
buffer.writeInt(size);
buffer.writeBytes(encryptedData);
ChannelFuture writeFuture = channel.write(buffer);
return writeFuture;
}
private static void writeParamsForLogin(ChannelBuffer buf) {
int eventHash = getEventHash("Login");
buf.writeInt(eventHash);
int paramCount = 3 ; // in case of PlayerToken 2 parameters to be send : player id + player token
buf.writeInt(paramCount);
// version , E , N
String version = "1.0";
buf.writeInt(dataType_String);
buf.writeInt(version.length());
buf.writeBytes(version.getBytes());
String E = "61";
buf.writeInt(dataType_ByteArray);
buf.writeInt(E.length());
buf.writeBytes(E.getBytes());
String N = "53";
buf.writeInt(dataType_ByteArray);
buf.writeInt(N.length());
buf.writeBytes(N.getBytes());
}
}
and the client handler class :
public class ClientHandler extends SimpleChannelUpstreamHandler {
#Override
public void messageReceived(
ChannelHandlerContext ctx, MessageEvent e) {
System.err.println(e.getMessage());
System.out.println("Message Recieved");
ChannelBuffer buf = (ChannelBuffer)e.getMessage();
while (buf.readable()) {
System.out.println((char) buf.readByte());
System.out.flush();
}
}
#Override
public void exceptionCaught(
ChannelHandlerContext ctx, ExceptionEvent e) {
logger.log(
Level.WARNING,
"Unexpected exception from downstream.",
e.getCause());
e.getCause().printStackTrace();
e.getChannel().close();
}
}
On receiving the request data on the server, I am processing it and writing back the response using the following method :
private static boolean sendMessage(NetworkClient targetObject, String eventName, Object[] params) {
logger.debug("Sending message for the event : " + eventName);
if(targetObject == null) {
sendMessageToAll(eventName, params);
} else {
if (targetObject.getClientChannel() == null) {
logger.error("Target not defined.");
return false;
}
Channel clientChannel = targetObject.getClientChannel();
ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
long hash = getHashString(eventName);
buf.writeInt(512);
buf.writeLong(hash);
if(params != null) {
buf.writeInt(params.length);
for(Object param : params) {
int type = getTypeOfObject(param);
buf.writeInt(type);
writeParamToBuffer(buf, type, param);
}
}
ChannelFuture cf = null;
try {
cf = clientChannel.write(buf);
if(cf.isSuccess()){
System.out.println("Written to client successfully");
}
} catch (Exception e) {
logger.error("Error in broadcasting for event : " + eventName, e);
} finally {
}
}
return false;
}
This code is still a work in progress, so you might find lot of "not required" stuff in there. I just wanted to show the logic that I am trying to use and want to know why it is not working.
I have taken help from examples at http://docs.jboss.org/netty/3.2/xref/org/jboss/netty/example/ for writing this.
Thanks in advance for any help.
Very complete code.
An easy way to write text to a websockets channel is to do this:
ChannelFuture writeFuture = channel.write(new TextWebSocketFrame("My Text here!");
You don't need to deal with the ChannelBuffers directly. That may resolve your problem.
I have a server app. Java NIO
I have Runnable class - EventHandler - that process incoming messages. If message == "Bye" -> EventHandler close related SocketServer and SelectorKey
I have one Runnable object - Acceptor - that is activated on OP_ACCEPT events. It creates new SocketChannel and new EventHandler to process messages from this channel
I have a problem.
First client connect. Send messages. Disconnect. Everything is ok
After first client disconnected Second client connect. Here problem begins - Acceptor object isn't invoked, therefore SocketChannel and EventHandler are not created for new client.
What is wrong in my code? SocketChannel closed improperly?
I changed the code to fix the errors that were noted in the comments. Now it works fine
Reactor. Class with the main loop
public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocketChannel;
Reactor(int port) throws IOException {
//configure server socket channel
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.socket().bind(new InetSocketAddress(port));
this.serverSocketChannel.configureBlocking(false);
//start acceptor
this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT, new Acceptor(this.serverSocketChannel, this.selector));
}
public void run() {
System.out.println("Server is listening to port: " + serverSocketChannel.socket().getLocalPort());
try {
while (!Thread.currentThread().isInterrupted()) {
if (this.selector.select() > 0) {
Set<SelectionKey> selected = this.selector.selectedKeys();
for (SelectionKey selectionKey : selected) {
dispatch(selectionKey);
}
selected.clear(); //clear set (thanks to EJP for comment)
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable) (k.attachment());
if (r != null) {
r.run();
}
}
}
Acceptor
public class Acceptor implements Runnable {
final ServerSocketChannel serverSocketChannel;
final Selector selector;
public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
this.serverSocketChannel = serverSocketChannel;
this.selector = selector;
}
public void run() {
try {
SocketChannel socketChannel = this.serverSocketChannel.accept();
if (socketChannel != null) {
new EventHandler(this.selector, socketChannel);
System.out.println("Connection Accepted");
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
EventHandler
public class EventHandler implements Runnable {
EventHandler(Selector selector, SocketChannel socketChannel) throws IOException {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
this.selectionKey = this.socketChannel.register(selector, SelectionKey.OP_READ, this);
//selector.wakeup(); //we don't need to wake up selector (thanks to EJP for comment)
}
#Override
public void run() {
try {
if (this.state == EventHandlerStatus.READING) {
read();
} else if (this.state == EventHandlerStatus.SENDING) {
send();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Reading client message
*
* #throws IOException
*/
void read() throws IOException {
int readCount = this.socketChannel.read(this.input);
//check whether the result is equal to -1, and close the connection if it is (thanks to EJP for comment)
if(readCount == -1){
this.socketChannel.close();
System.out.println("Stream is closed. Close connection.");
return;
}
if (readCount > 0) {
processMessage(readCount);
}
if(this.clientMessage.equalsIgnoreCase("Bye")){
this.socketChannel.close();
//this.selectionKey.cancel(); //we don't need to cancel selectionKey if socketChannel is just closed (thanks to EJP for comment)
System.out.println("Client said Bye. Close connection.");
return;
}
this.state = EventHandler.Status.SENDING;
this.selectionKey.interestOps(SelectionKey.OP_WRITE); //mark that we interested in writing
}
/**
* Processing of the read message.
*
* #param readCount Number of bytes to read
*/
synchronized void processMessage(int readCount) {
this.input.flip();
StringBuilder sb = new StringBuilder();
sb.append(new String(Arrays.copyOfRange(input.array(), 0, readCount))); // Assuming ASCII (bad assumption but simplifies the example)
this.clientMessage = sb.toString().trim();
this.input.clear();
System.out.println("Client said: " + this.clientMessage);
}
/**
* Sending response to client
*
* #throws IOException
*/
void send() throws IOException {
System.out.println("Answer to client: " + this.clientMessage);
this.socketChannel.write(ByteBuffer.wrap((this.clientMessage + "\n").getBytes()));
this.state = EventHandler.Status.READING;
this.selectionKey.interestOps(SelectionKey.OP_READ); //mark that we interested in reading
}
//----------------------------------------------------------------------------------------------------------------------
// Fields
//----------------------------------------------------------------------------------------------------------------------
final SocketChannel socketChannel;
final SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
EventHandlerStatus state = EventHandler.Status.READING;
String clientMessage = "";
//----------------------------------------------------------------------------------------------------------------------
// Enum to mark current status of EventHandler
//----------------------------------------------------------------------------------------------------------------------
enum Status {
READING, SENDING
}
}
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.