I have written a Bluetooth API for connecting with an external accessory.
The way that the API is designed is that there are a bunch of blocking calls such as getTime, setTime, getVolume, setVolume, etc.
The way these work is that they create a payload to send and call a method called sendAndReceive() which does some prep work and eventually does the following:
byte[] retVal = null;
BluetoothSocket socket = getSocket();
// write
socket.getOutputStream().write(payload);
// read response
if(responseExpected){
byte[] buffer = new byte[1024]; // buffer store for the stream
int readbytes = socket.getInputStream().read(buffer);
retVal = new byte[readbytes];
System.arraycopy(buffer, 0, retVal, 0, readbytes);
}
return retVal;
The problem is that sometimes this device becomes slow or non-responsive so I would like to put a timeout on this call.
I have tried several methods of putting this code in a thread\future task and running it with a timeout, for example:
FutureTask<byte[]> theTask = null;
// create new task
theTask = new FutureTask<byte[]>(
new Callable<byte[]>() {
#Override
public byte[] call() {
byte[] retVal = null;
BluetoothSocket socket = getSocket();
// write
socket.getOutputStream().write(payload);
// read response
if(responseExpected){
byte[] buffer = new byte[1024]; // buffer store for the stream
int readbytes = socket.getInputStream().read(buffer);
retVal = new byte[readbytes];
System.arraycopy(buffer, 0, retVal, 0, readbytes);
}
return retVal;
}
});
// start task in a new thread
new Thread(theTask).start();
// wait for the execution to finish, timeout after 6 secs
byte[] response;
try {
response = theTask.get(6L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new CbtException(e);
} catch (ExecutionException e) {
throw new CbtException(e);
} catch (TimeoutException e) {
throw new CbtCallTimedOutException(e);
}
return response;
}
The problem with this approach is that I can't re-throw exceptions in the call method and since some of the methods throw exceptions I want to forward back to the API's client I can't use this methodology.
Can you recommend some other alternative?
Thanks!
You're saving you can't use the Future<> method because you want to re-throw the exception but in fact this is possible.
Most examples online do implement Callable with the prototype public ? call() but just change it to public ? call() throws Exception and all will be fine: you'll get the exception in the theTask.get() call and you can rethrow it to callers.
I have personally used Executors exactly for bluetooth socket timeout handling on android:
protected static String readAnswer(...)
throws Exception {
String timeoutMessage = "timeout";
ExecutorService executor = Executors.newCachedThreadPool();
Callable<String> task = new Callable<String>() {
public String call() throws Exception {
return readAnswerNoTimeout(...);
}
};
Future<String> future = executor.submit(task);
try {
return future.get(SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
future.cancel(true);
throw new Exception(timeoutMessage);
}
}
Why not try something like
public class ReadTask extends Thread {
private byte[] mResultBuffer;
private Exception mCaught;
private Thread mWatcher;
public ReadTask(Thread watcher) {
mWatcher = watcher;
}
public void run() {
try {
mResultBuffer = sendAndReceive();
} catch (Exception e) {
mCaught = e;
}
mWatcher.interrupt();
}
public Exception getCaughtException() {
return mCaught;
}
public byte[] getResults() {
return mResultBuffer;
}
}
public byte[] wrappedSendAndReceive() {
byte[] data = new byte[1024];
ReadTask worker = new ReadTask(data, Thread.currentThread());
try {
worker.start();
Thread.sleep(6000);
} catch (InterruptedException e) {
// either the read completed, or we were interrupted for another reason
if (worker.getCaughtException() != null) {
throw worker.getCaughtException();
}
}
// try to interrupt the reader
worker.interrupt();
return worker.getResults;
}
There is an edge case here that the Thread calling wrappedSendAndReceive() may get interrupted for some reason other than the interrupt from the ReadTask. I suppose a done bit could be added to the ReadTask to allow the other thread to test if the read finished or the interrupt was caused by something else, but I'm not sure how necessary this is.
A further note is that this code does contain the possibility for data loss. If the 6 seconds expires and some amount of data has been read this will end up being discarded. If you wanted to work around this, you'd need to read one byte at a time in ReadTask.run() and then appropriately catch the InterruptedException. This obviously requires a little rework of the existing code to keep a counter and appropriately resize the read buffer when the interrupt is received.
Related
I want to communicate as a TCP Server on Port 2000 and 2001 with my TCP Client (Machine which sends Bytestreams).
Therefore I programmed a Spring Boot Application in Java.
This Question is only for Port 2001:
I use Camunda as BPMN-Engine for executing and orchestrating.
I start Threads like this:
package com.example.workflow;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
public class StartTCPServersDelegate implements JavaDelegate {
#Override
public void execute(DelegateExecution delegateExecution) throws Exception {
Runnable serverZyklisch = new ServerZyklisch();
Runnable serverAzyklisch = new ServerAzyklisch((String) delegateExecution.getVariable("param"));
Thread t1 = new Thread(serverZyklisch);
t1.start();
System.out.println("Thread Zyklisch gestartet");
Thread t2 = new Thread(serverAzyklisch);
t2.start();
System.out.println("Thread Azyk. gestartet");
String val1 = (String) delegateExecution.getVariable("param");
int valueParam = Integer.parseInt(val1);
System.out.println("Param ist: "+valueParam);
}
}
This is my ServerAzyklisch Class:
public class ServerAzyklisch implements Runnable, JavaDelegate {
private ServerSocket ssocket;
String param;
HexToByteConverter hexToByteConverter = new HexToByteConverter();
public ServerAzyklisch(String Pparam) throws IOException {
ssocket = new ServerSocket(2000);
param = Pparam;
}
public void run() {
System.out.println(param+"Paraaam");
InputStream in;
OutputStream out = null;
Socket socket;
while(true){
try {
socket = ssocket.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
byte []data = new byte[132];
int numBytes = 0;
byte[]durch = hexToByteConverter.hexStringToByteArray("333333330041006400040000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
byte[]durchlauf = hexToByteConverter.hexStringToByteArray("333333330041006400040000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
byte[]Pressen1hexdump111 = hexToByteConverter.hexStringToByteArray("33333333003d0064000600000004004001c9c78900010000006f00000000000000000000000000010000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005");
byte[]Pressen1hexdump110 = hexToByteConverter.hexStringToByteArray("33333333003d0064000600000004004001c9c78900010000006e0000000000000000000000000001000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"+param);
byte[]Pressen2hexdump = hexToByteConverter.hexStringToByteArray("3333333300400065000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
byte[]Pressen3hexdump = hexToByteConverter.hexStringToByteArray("3333333300400065001400000000003d01c9c7890001000000c9000000000000000000000000000100000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
byte[]Pressen3hexdumpNextBohrer = hexToByteConverter.hexStringToByteArray("3333333300400065001400000000003f01c9c789000100000078000000000000000000000000000100000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002");
byte[]Pressen4hexdumpNextRSCIDBohrer = hexToByteConverter.hexStringToByteArray("33333333003f0065001400000000003d01c9c78900010000007a000000000000000000000000000100000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
//gleichen Stream zurückschicken, der angekommen ist, für Durchlauf
while((numBytes = in.read(data)) != -1){
System.out.println(Arrays.toString(data));
out.write(Pressen1hexdump110);
out.write(Pressen2hexdump);
out.write(Pressen3hexdumpNextBohrer);
//out.write(durchlauf);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void execute(DelegateExecution delegateExecution) throws IOException {
}
}
I get everytime a different Result to my Client, so the behaviour is always another. But I want to send once all three bytearrays to my Client. I think something is wrong with my while loop.
Do you have any idea ?
By the comments, the communication is based on request-response pairs. You need to read 3 messages from the client, and return a response for each message. To do this, replace the while loop with:
readMessage(in, data);
out.write(Pressen1hexdump110);
readMessage(in, data);
out.write(Pressen2hexdump);
readMessage(in, data);
out.write(Pressen3hexdumpNextBohrer);
where the readMessage method is a new method you must add, that reads a complete request from the client.
If the client requests are always 128 bytes, there is a convenient method in DataInputStream that you can use:
void readMessage(InputStream in, byte[] buffer) throws IOException {
new DataInputStream(in).readFully(buffer, 0, 128);
}
In the general case the readMessage method would have to look something like this in pseudo-code:
void readMessage(InputStream in, byte[] buffer) {
// Read a message
while message is not complete:
read from "in" into "buffer"
if "in" was closed: throw an exception because the connection was closed mid-request
else: incorporate newly read data from "buffer" in message
done
}
I've written a simple asynchronous tcp server with the nio.
The server should be able to read and write at the same time for each client.
This I've implemented with a simple packet queue.
public class TcpJobHandler {
private BlockingQueue<TcpJob> _packetQueue = new LinkedBlockingQueue<TcpJob>();
private Thread _jobThread;
private final ReentrantLock _lock = new ReentrantLock();
public TcpJobHandler(){
_jobThread = new Thread(new Runnable() {
#Override
public void run() {
jobLoop();
}
});
_jobThread.start();
}
private void jobLoop(){
while(true){
try {
_lock.lock();
TcpJob job = _packetQueue.take();
if(job == null){
continue;
}
job.execute();
} catch (Exception e) {
AppLogger.error("Failed to dequeue packet from job queue.", e);
}finally{
_lock.unlock();
}
}
}
public void insertJob(TcpJob job){
try{
_packetQueue.put(job);
}catch(InterruptedException e){
AppLogger.error("Failed to queue packet to the tcp job queue.", e);
}
}
}
What this code do, is just checking for a new packet. If a new packet is available, this packet will be send to the client.
In the class tcp job, there is just the packet to send and a write class whichs writes the packet into the client stream.
As you can see, only one thread should be able to write a packet into a client stream.
This is the point, why I dont understand, why I'm getting this error? If I'm right, this exception says, that I try to send data into a stream, but there is already a thread which is writing data into this stream. But why?
//Edit:
I'm getting this exception:
19:18:41.468 [ERROR] - [mufisync.server.data.tcp.handler.TcpJobHandler] : Failed to dequeue packet from job queue. Exception: java.nio.channels.WritePendingException
at sun.nio.ch.AsynchronousSocketChannelImpl.write(Unknown Source)
at sun.nio.ch.AsynchronousSocketChannelImpl.write(Unknown Source)
at mufisync.server.data.tcp.stream.OutputStreamAdapter.write(OutputStreamAdapter.java:35)
at mufisync.server.data.tcp.stream.OutputStreamAdapter.write(OutputStreamAdapter.java:26)
at mufisync.server.data.tcp.stream.BinaryWriter.write(BinaryWriter.java:21)
at mufisync.server.data.tcp.TcpJob.execute(TcpJob.java:29)
at mufisync.server.data.tcp.handler.TcpJobHandler.jobLoop(TcpJobHandler.java:40)
at mufisync.server.data.tcp.handler.TcpJobHandler.access$0(TcpJobHandler.java:32)
at mufisync.server.data.tcp.handler.TcpJobHandler$1.run(TcpJobHandler.java:25)
at java.lang.Thread.run(Unknown Source)
The TcpJob looks like this:
public class TcpJob {
private BasePacket _packet;
private BinaryWriter _writer;
public TcpJob(BasePacket packet, BinaryWriter writer){
_packet = packet;
_writer = writer;
}
public void execute(){
try {
if(_packet == null){
AppLogger.warn("Tcp job packet is null");
return;
}
_writer.write(_packet.toByteArray());
} catch (IOException e) {
AppLogger.error("Failed to write packet into the stream.", e);
}
}
public BasePacket get_packet() {
return _packet;
}
}
The BinaryStream is just coupled to a AsynchronousSocketChannel which calls the write(byte[]) method from the socket channel.
You are using asynchronous NIO2. When you use asynchronous IO you cannot call write() until the last write has completed. From the Javadoc
* #throws WritePendingException
* If a write operation is already in progress on this channel
e.g. if you have used
public abstract Future<Integer> write(ByteBuffer src);
you cannot write again until this Future.get() returns.
If you use
public abstract <A> void write(ByteBuffer src,
long timeout,
TimeUnit unit,
A attachment,
CompletionHandler<Integer,? super A> handler);
You cannot write again until the CompletionHandler is called.
Note: you cannot be performing two reads at once either.
In your case you want something like
ByteBuffer lastBuffer = null;
Future<Integer> future = null;
public void execute(){
try {
if(_packet == null){
AppLogger.warn("Tcp job packet is null");
return;
}
// need to wait until the last buffer was written completely.
while (future != null) {
future.get();
if (lastBuffer.remaining() > 0)
future = _writer.write(lasBuffer);
else
break;
}
// start another write.
future = _writer.write(lastBuffer = _packet.toByteArray());
} catch (IOException e) {
AppLogger.error("Failed to write packet into the stream.", e);
}
}
I have a data producer that runs in a separate thread and pushes generated data into PipedOutputStream which is connected to PipedInputStream. A reference of this input stream is exposed via public API so that any client can use it. The PipedInputStream contains a limited buffer which, if full, blocks the data producer. Basically, as the client reads data from the input stream, new data is generated by the data producer.
The problem is that the data producer may fail and throw an exception. But as the consumer is running in a separate thread, there is no nice way to get the exception to the client.
What I do is that I catch that exception and close the input stream. That will result in a IOException with message "Pipe closed" on the client side but I would really like to give the client the real reason behind that.
This is a rough code of my API:
public InputStream getData() {
final PipedInputStream inputStream = new PipedInputStream(config.getPipeBufferSize());
final PipedOutputStream outputStream = new PipedOutputStream(inputStream);
Thread thread = new Thread(() -> {
try {
// Start producing the data and push it into output stream.
// The production my fail and throw an Exception with the reason
} catch (Exception e) {
try {
// What to do here?
outputStream.close();
inputStream.close();
} catch (IOException e1) {
}
}
});
thread.start();
return inputStream;
}
I have two ideas how to fix that:
Store the exception in the parent object and expose it to the client via API. I. e. if the reading fails with an IOException, the client could ask the API for the reason.
Extend / re-implement the piped streams so that I could pass a reason to the close() method. Then the IOException thrown by the stream could contain that reason as a message.
Any better ideas?
Coincidentally I just wrote similar code to allow GZip compression of a stream. You don't need to extend PipedInputStream, just FilterInputStream will do and return a wrapped version, e.g.
final PipedInputStream in = new PipedInputStream();
final InputStreamWithFinalExceptionCheck inWithException = new InputStreamWithFinalExceptionCheck(in);
final PipedOutputStream out = new PipedOutputStream(in);
Thread thread = new Thread(() -> {
try {
// Start producing the data and push it into output stream.
// The production my fail and throw an Exception with the reason
} catch (final IOException e) {
inWithException.fail(e);
} finally {
inWithException.countDown();
}
});
thread.start();
return inWithException;
And then InputStreamWithFinalExceptionCheck is just
private static final class InputStreamWithFinalExceptionCheck extends FilterInputStream {
private final AtomicReference<IOException> exception = new AtomicReference<>(null);
private final CountDownLatch complete = new CountDownLatch(1);
public InputStreamWithFinalExceptionCheck(final InputStream stream) {
super(stream);
}
#Override
public void close() throws IOException {
try {
complete.await();
final IOException e = exception.get();
if (e != null) {
throw e;
}
} catch (final InterruptedException e) {
throw new IOException("Interrupted while waiting for synchronised closure");
} finally {
stream.close();
}
}
public void fail(final IOException e) {
exception.set(Preconditions.checkNotNull(e));
}
public void countDown() {complete.countDown();}
}
This is my implementation, taken from above accepted answer https://stackoverflow.com/a/33698661/5165540 , where I don't use the CountDownLatch complete.await() as it would cause a deadlock if the InputStream gets abruptly closed before the writer has finished writing the full content.
I still set the exception caught when PipedOutpuStream is being used, and I create the PipedOutputStream in the spawn thread, using a try-finally-resource pattern to ensure it gets closed, waiting in the Supplier until the 2 streams are piped.
Supplier<InputStream> streamSupplier = new Supplier<InputStream>() {
#Override
public InputStream get() {
final AtomicReference<IOException> osException = new AtomicReference<>();
final CountDownLatch piped = new CountDownLatch(1);
final PipedInputStream is = new PipedInputStream();
FilterInputStream fis = new FilterInputStream(is) {
#Override
public void close() throws IOException {
try {
IOException e = osException.get();
if (e != null) {
//Exception thrown by the write will bubble up to InputStream reader
throw new IOException("IOException in writer", e);
}
} finally {
super.close();
}
};
};
Thread t = new Thread(() -> {
try (PipedOutputStream os = new PipedOutputStream(is)) {
piped.countDown();
writeIozToStream(os, projectFile, dataFolder);
} catch (final IOException e) {
osException.set(e);
}
});
t.start();
try {
piped.await();
} catch (InterruptedException e) {
t.cancel();
Thread.currentThread().interrupt();
}
return fis;
}
};
Calling code is something like
try (InputStream is = streamSupplier.getInputStream()) {
//Read stream in full
}
So when is InputStream is closed this will be signaled in the PipedOutputStream causing eventually a "Pipe closed" IOException, ignored at that point.
If I keep instead the complete.await() line in the FilterInputStreamclose() I could suffer from deadlock (PipedInputStream trying to close, waiting on complete.await(), while PipedOutputStream is waiting forever on PipedInputStreamawaitSpace )
I have a simple Server-Client socket connection. I encapsulate all my data in objects which are sent backward and forward between the sockets, sent through ObjectStreams.
I have created a "HeartBeat" monitor, which runs in a separate thread, where both the server and the client, every 500ms, send a HeartBeat (empty object) backward and forward to check for connectivity, which works great. However, because of this, when I want to send other data between the server and client, it is mixed up with these HeartBeat objects.
For example my Server is expecting a Login object, but instead gets an object of instance HeartBeat.
My code is a simple client/server setup, so I don't think it'd be necessary to post their code, however, the HeartBeat code is as follows:
private static final int HEARTBEAT_INTERVAL = 500;
private void addHeartBeatMonitor(final Socket socket) {
this.heartBeatTimer = new Timer();
this.heartBeatTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
try {
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
os.writeObject(new HeartBeat());
ObjectInputStream is = new ObjectInputStream(socket.getInputStream());
if (!(is.readObject() instanceof HeartBeat)) { throw new IOException(); }
} catch (IOException e) {
LOG.info("Received disconnect from " + getClientSocket().getInetAddress());
heartBeatTimer.cancel();
if (clientSocket != null) {
try {
clientSocket.close();
} catch (IOException e1) {}
}
} catch (ClassNotFoundException e) {}
}
}, 0, HEARTBEAT_INTERVAL);
}
My options seem to be to as follows:
Ditch the HeartBeat functionality, although there seems to be no other reliable way to check the connection status.
Find some other kind of Socket implementation which will magically fix all of this for me.
Have a synchronized method which oversees all reads and writes to the socket, which discards HeartBeats and sends other objects where they're meant to be.
Some kind of synchronization magic.
Thanks in advance for any help!
EDIT:
Code which reads the Login object (server side):
User result = null;
try {
ObjectInputStream is = new ObjectInputStream(this.getInputStream());
Login request = (Login) is.readObject(); ### ERROR ###
result = this.mongoService.login(request);
ObjectOutputStream os = new ObjectOutputStream(this.getOutputStream());
os.writeObject(result);
} catch (IOException e) {
} catch (ClassNotFoundException e) {}
return result;
Exception as follows:
Exception in thread "Thread-0" java.lang.ClassCastException: model.HeartBeat cannot be cast to model.Login
at socket.SocketServerWorker.login(SocketServerWorker.java:78)
at socket.SocketServerWorker.<init>(SocketServerWorker.java:47)
at socket.SocketServer$2.run(SocketServer.java:50)
at java.lang.Thread.run(Thread.java:744)
Consider doing something like this. I just threw this together, so it's obviously untested, but I'm sure you'll get the idea:
public class HeartBeatMonitor
{
final Map<Class,Consumer> handlers = new HashMap<> ();
final Socket sock;
final ObjectInputStream is;
final ObjectOutputStream os;
public HeartBeatMonitor (final Socket sock)
{
try
{
this.sock = sock;
this.is = new ObjectInputStream (sock.getInputStream ());
this.os = new ObjectOutputStream (sock.getOutputStream ());
}
catch (final IOException e)
{
throw new RuntimeException (e);
}
}
public <T> void setHandler (final Class<T> type, final Consumer<? super T> handler)
{
this.handlers.put (type, handler);
}
// This would be called in a loop
void accept () throws ClassNotFoundException, IOException
{
final Object o = this.is.readObject ();
final Consumer handler = this.handlers.get (o.getClass ());
if (handler != null)
handler.accept (o);
// Else default handler?
}
}
I started learning networking with the main networking package in JDK, it's pretty simple and easy after a few examples. But now I am interested into making multi-client applications like a chat system.
My structure idea so far is like this:
Connection handler class, which handles incoming connections, and holds the list of clients.
If new connection was found, create a new client object, start it's thread (Client object will implement runnable, so it will start it's own looping service, it will loop for new packets received), and add it to the list.
I create a new thread for each client instead of looping through all clients because the reading from client process stops the whole execution and will wait for the client to send data, which is kinda annoys me and this is my issue there.
I have created a simple console app that receives messages from the client, but now I want to detect disconnections. I read that bufferedReader .read() method returns -1 if user is not connected, so I thought I could loop and do that every number of seconds to every client, but the thing is, the client must send a packet in order to .read() it, so let's say if you do .read() it will wait & stop the whole thread until packet is received, (I think).
This is my current code which gets messages from client:
public boolean isConnected() {
try {
this.in.read();
this.lastCheck = System.currentTimeMillis();
return true;
} catch (IOException e) {
if (!inConnection()) {
System.out.println("User disconnected");
try {
this.destruct();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
return false;
}
private boolean inConnection() {
return System.currentTimeMillis() - lastCheck < this.maxTime;
}
public void startClientService() throws IOException {
while(!this.session.isClosed()) {
if (System.currentTimeMillis() - this.checkTime > 600) {
System.out.println(System.currentTimeMillis() - this.checkTime);
if (this.isConnected()) {
int packetType = this.dataIn.readInt();
packets.getPacket(packetType);
}
}
}
}
public void destruct() throws IOException {
this.session.close();
this.connection.removeClient(this);
System.out.println("Session killed");
}
Basically what happens here, I am sending a integer packed from the client, I might have many things to do so therefore I can set many unique packet ID's, so if I want to receive and process a chat message, the packet id is 216, the client sends a int 216, server reads the packet, enters the switch loop of all packet ids and detects if its really 216, if yes it gets the instance of the packed class that handles messages & gets the bytes of the received message like this:
public class Chat implements Packet {
#Override
public void processPacket(Session c) {
String message = readMessage(c);
System.out.println("Message: " + message);
}
private String readMessage(Session c) {
byte[] data = c.readBytes();
String message = null;
try {
message = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return message;
}
}
And this is how I read bytes:
public byte[] readBytes() {
int len;
byte[] data = null;
try {
len = this.dataIn.readInt();
data = new byte[len];
if (len > 0) {
this.dataIn.readFully(data);
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
Okay my problem:
after adding the is disconnected detection, when I send my message, nothing happens. This is probably due to the .read() it stops and is waiting for a response. BUT if I write a message again, I will get the message in server.
This is my temporary, ugly client:
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1", 43594);
Scanner r = new Scanner(System.in);
PrintWriter out = new PrintWriter(socket.getOutputStream());
String input;
while(true) {
input = r.next();
if (input != null) {
sendMessage(input, out);
}
}
}
public static void sendMessage(String message, PrintWriter out) {
byte[] encoded = encode(message);
out.write(0);
out.println(encoded + "\n");
out.flush();
}
public static byte[] encode(String s) {
return DatatypeConverter.parseBase64Binary(s);
}
public static String decode(byte[] s) {
return DatatypeConverter.printBase64Binary(s);
}
}
My question is: What is a better way of reading data from client without making the application wait for it and actually loop everytime? OR maybe should I have a new thread for checking if user is online so it's 2 threads per 1 client?
If someone needs my session object (client object):
public class Session extends Thread implements Runnable {
private Socket session;
private Client client;
private PrintWriter out;
private BufferedReader in;
private PacketHandler packets;
private DataInputStream dataIn;
private ConnectionHandler connection;
private final int checkTime = 1600;
private final int maxTime = 22000;
private long lastCheck;
public Session(Socket session) {
this.session = session;
this.client = new Client(this);
try {
this.setStream();
} catch (IOException e) {
e.printStackTrace();
}
this.packets = new PacketHandler(this);
System.out.println("[New session created]: " + session.getRemoteSocketAddress());
}
public void setConnectionHandler(ConnectionHandler c) {
this.connection = c;
}
public void run() {
try {
this.startClientService();
} catch (IOException e) {
e.printStackTrace();
}
}
public void setStream() throws IOException {
this.out = new PrintWriter(this.session.getOutputStream());
this.in = new BufferedReader(new InputStreamReader(this.session.getInputStream()));
this.dataIn = new DataInputStream(this.session.getInputStream());
}
public Client getClient() {
return this.client;
}
public byte[] readBytes() {
int len;
byte[] data = null;
try {
len = this.dataIn.readInt();
data = new byte[len];
if (len > 0) {
this.dataIn.readFully(data);
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public String readMessage() {
try {
return this.in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public boolean isConnected() {
try {
this.in.read();
this.lastCheck = System.currentTimeMillis();
return true;
} catch (IOException e) {
if (!inConnection()) {
System.out.println("User disconnected");
try {
this.destruct();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
return false;
}
private boolean inConnection() {
return System.currentTimeMillis() - lastCheck < this.maxTime;
}
public void startClientService() throws IOException {
while(!this.session.isClosed()) {
if (System.currentTimeMillis() - this.checkTime > 600) {
System.out.println(System.currentTimeMillis() - this.checkTime);
if (this.isConnected()) {
int packetType = this.dataIn.readInt();
packets.getPacket(packetType);
}
}
}
}
public void destruct() throws IOException {
this.session.close();
this.connection.removeClient(this);
System.out.println("Session killed");
}
}
Thanks!
While I don't have time to look over all the code, here are two things that could help you out.
1) Use a defined message header. Define X number of bytes of each message that the client will send to the server. Use these bytes to define how long the message will be, and what type of message it is. The server knows the length and layout of this header, and uses it to process the message in a particular way. Example could be a header of one byte. A value of 1 could be a I'm connected message. 2 could be I'm about to disconnect. 3 could be I'm currently away, and 4 could be an incoming chat message.
2) There are 2 ways you can handle the input. First is to use blocking IO, and create a separate thread to receive messages from each client. I believe this is what you are currently doing. The second is to use non-blocking IO, and have a separate thread iterate over the open sockets and do a read. Non-blocking will check if there is data to read, but if there is not, the thread will continue executing.