I'm making a client socket app that connects to an ESP8266-01 and the only thing i cant get my head around is how to detect a broken socket without pinging the server (ESP8266-01).
So far I found 3 cases in which i consider the socket to be broken because the client app can't detect them:
1) Abruptly disconnecting ESP8266 from a power source without properly closing the socket
2) Disconnecting the wifi to which both my app and ESP8266 are connected.
3) Calling AT+CIPCLOSE from ESP8266.
If any of the above occurs and i send data with SendData() an exception will be thrown and handled except for case 1. in which it simply goes undetected. I tried using blocking io before and it takes care of case 3. however every call to Disconnect will cause an exception to be thrown due to Thread.interrupt() calls.
import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.Scanner;
public class TCPClient {
private Socket client_socket_ = null;
private OutputWriterThread output_writer_ = null;
private InputReaderThread input_reader_ = null;
private LinkedBlockingQueue<String> send_queue_ = null;
private boolean connected_ = false;
public TCPClient()
{
send_queue_ = new LinkedBlockingQueue<String>();
}
private class OutputWriterThread extends Thread
{
@Override
public void run()
{
try (BufferedOutputStream data_out = new BufferedOutputStream(client_socket_.getOutputStream())){
while (!Thread.currentThread().isInterrupted()) {
if(send_queue_.size() > 0) {
data_out.write(send_queue_.poll().getBytes());
data_out.flush();
}
}
} catch(IOException ex) {
System.out.println("OutputWriterThread exception: " + ex.getMessage());
Disconnect();
}
}
}
private class InputReaderThread extends Thread
{
@Override
public void run()
{
try (BufferedInputStream data_in = new BufferedInputStream(client_socket_.getInputStream())) {
ByteArrayOutputStream parser = new ByteArrayOutputStream(2048);
byte[] buffer = new byte[2048];
while (!Thread.currentThread().isInterrupted()) {
if(data_in.available() > 0) {
parser.write(buffer, 0, data_in.read(buffer));
DataReceived(parser.toString("UTF-8"));
parser.reset();
}
}
} catch(IOException ex) {
System.out.println("InputReaderThread exception: " + ex.getMessage());
Disconnect();
}
}
}
public synchronized void Connect(final String ip, final String port, final int timeout)
{
System.out.println("connect attempted");
if(!connected_) {
try {
System.out.println("starting connect function");
client_socket_ = new Socket();
client_socket_.connect(new InetSocketAddress(ip, Integer.parseInt(port)), timeout);
output_writer_ = new OutputWriterThread();
input_reader_ = new InputReaderThread();
output_writer_.start();
input_reader_.start();
connected_ = true;
System.out.println("ending connect function");
} catch(IOException ex) {
System.out.println("Connect:" + ex.getMessage());
}
}
}
public void SendData(final String data)
{
try {
if(connected_) send_queue_.offer(data);
} catch(NullPointerException ex) {
System.out.println("SendData:" + ex.getMessage());
}
}
public void DataReceived(String data)
{
System.out.println(data);
}
public boolean IsConnected()
{
return connected_;
}
public synchronized void Disconnect()
{
System.out.println("disconnect attempted");
if(connected_) {
try {
System.out.println("starting disconnect function");
output_writer_.interrupt();
input_reader_.interrupt();
System.out.println("Before closing the socket");
client_socket_.close();
connected_ = false;
System.out.println("ending disconnect function");
} catch(IOException ex) {
System.out.println("Disconnected exception:" + ex.getMessage());
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
TCPClient client = new TCPClient();
client.Connect("10.0.0.8", "9001", 5000);
while(true) {
String msg = sc.nextLine();
if(msg.equals("exit"))
break;
client.SendData(msg);
}
client.Disconnect();
}
}
To be more specific I want to know is there any way to check if a socket is broken (like the 3 cases i pointed above) without pinging the server.
UPDATE:
I re-wrote the code and now I can easily detect a broken socket and the 3 cases i couldn't detect earlier are resolved.
CURRENT PROBLEM
Whenever I send "exit" from the app it calls the Disconnect() method (Correct behavior). However, it causes an exception to be thrown: "InputReaderThread exception: Socket closed" (ERROR).
I understand it happens because I call client_socket_.close() in the Disconnect() method and because inputStream.read(byte[] b) is a blocking method (inside InputReaderThread class), it detects that the socket was closed and throws "Socket closed" exception because reading isn't possible from a closed socket.
I tried swapping:
data_out_.close();
data_in_.close();
With:
client_socket_.shutdownOutput();
client_socket_.shutdownInput();
And even though the function description of Socket.shutdownInput() is:
Places the input stream for this socket at "end of stream". Any data sent to the input stream side of the socket is acknowledged and then silently discarded. If you read from a socket input stream after invoking shutdownInput() on the socket, the stream will return EOF.
Calling read(byte[] b) after shutdownInput() doesn't actually return EOF when it should and I also tested it by calling join() on input_reader after calling client_socket_.shutdownInput() and as a result the program stopped at while ((bytes_read = data_in_.read(buffer)) != -1) line and no EOF was ever read.
Am I missing something obvious? or is it just normal behavior for java ?
NEW CODE
import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
private Socket client_socket_ = null;
private BufferedOutputStream data_out_ = null;
private BufferedInputStream data_in_ = null;
private boolean connected_ = false;
private class InputReaderThread extends Thread
{
@Override
public void run()
{
try {
ByteArrayOutputStream parser = new ByteArrayOutputStream(2048);
byte[] buffer = new byte[2048];
int bytes_read;
while ((bytes_read = data_in_.read(buffer)) != -1) {
parser.write(buffer, 0, bytes_read);
DataReceived(parser.toString("UTF-8"));
parser.reset();
}
Disconnect();
} catch(IOException ex) {
System.out.println("InputReaderThread exception: " + ex.getMessage());
}
}
}
public synchronized void Connect(final String ip, final String port, final int timeout)
{
System.out.println("connect attempted");
if(!connected_) {
try {
System.out.println("starting connect function");
client_socket_ = new Socket();
client_socket_.connect(new InetSocketAddress(ip, Integer.parseInt(port)), timeout);
data_out_ = new BufferedOutputStream(client_socket_.getOutputStream());
data_in_ = new BufferedInputStream(client_socket_.getInputStream());
InputReaderThread input_reader = new InputReaderThread();
input_reader.start();
connected_ = true;
System.out.println("ending connect function");
} catch(IOException ex) {
System.out.println("Connect:" + ex.getMessage());
}
}
}
public synchronized void SendData(final String data)
{
if(connected_)
{
Thread output_writer = new Thread() {
@Override
public void run()
{
try {
data_out_.write(data.getBytes());
data_out_.flush();
} catch(IOException ex) {
System.out.println("SendData exception: " + ex.getMessage());
Disconnect();
}
}
};
output_writer.start();
}
}
public void DataReceived(String data)
{
System.out.println(data);
}
public boolean IsConnected()
{
return connected_;
}
public synchronized void Disconnect()
{
System.out.println("disconnect attempted");
if(connected_) {
try {
System.out.println("starting disconnect function");
data_out_.close();
data_in_.close();
client_socket_.close();
connected_ = false;
System.out.println("ending disconnect function");
} catch(IOException ex) {
System.out.println("Disconnected exception:" + ex.getMessage());
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
TCPClient client = new TCPClient();
client.Connect("10.0.0.6", "9001", 5000);
while(true) {
String msg = sc.nextLine();
if(msg.equals("exit"))
break;
client.SendData(msg);
}
client.Disconnect();
}
}