Reputation: 1054
THE GOAL
I am currently developing my first "real" Android application which accesses a server running on my own cluster. The base functionality is perfectly working but now I wanted to implement an automatic reconnect every 5 minutes.
THE PROBLEM
For this purpose, I made a TimerTask executing a method which handles the reconnect stuff. The server output tells me that the reconnect works fine because I can see "Login Successful". But as soon as the reconnect task executes, I see this exception in the ADB output:
08-13 14:15:18.805 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ java.net.SocketException: Socket closed
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at libcore.io.Posix.recvfromBytes(Native Method)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at libcore.io.Posix.recvfrom(Posix.java:141)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at libcore.io.BlockGuardOs.recvfrom(BlockGuardOs.java:164)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at libcore.io.IoBridge.recvfrom(IoBridge.java:506)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.net.PlainSocketImpl.read(PlainSocketImpl.java:489)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.net.PlainSocketImpl.access$000(PlainSocketImpl.java:46)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.net.PlainSocketImpl$PlainSocketInputStream.read(PlainSocketImpl.java:241)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.io.InputStreamReader.read(InputStreamReader.java:231)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.io.BufferedReader.fillBuf(BufferedReader.java:145)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.io.BufferedReader.readLine(BufferedReader.java:397)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at net.dreamcode.android.dreampush.PushActivity$3.run(PushActivity.java:228)
08-13 14:15:18.810 19568-19617/net.dreamcode.android.dreampush W/System.err﹕ at java.lang.Thread.run(Thread.java:841)
It is pretty clear what it means, I am not new to network programming and Java in general. But I can't see the point where I am closing the socket before starting the listener thread.
Of course, on reconnect, I close the socket but I reopen it and as I already stated, also the login via Authentication Token works perfectly so the reopening of the socket also works.
CODE
Here are the methods involved in the process (shortened code):
private void initServerConnection() {
try {
Thread serverConnectThread = new Thread(new Runnable() {
@Override
public void run() {
try {
socketToServer = new Socket();
socketToServer.connect(new InetSocketAddress("emerald.dream-code.net", 25565));
if (socketToServer.isConnected()) {
connectionState = true;
}
}
catch (Exception ex) {
connectionState = false;
ex.printStackTrace();
}
}
});
serverConnectThread.start();
serverConnectThread.join();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public void doLoginProcedure(final String username, final String password) throws Exception {
writer = new BufferedWriter(new OutputStreamWriter(socketToServer.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socketToServer.getInputStream()));
// Save the username for further usage
PushActivity.username = username;
// Write the login command in additional thread to have the GUI free of lags
Thread loginThread = new Thread(new Runnable() {
@Override
public void run() {
try {
writer.write("LOGIN " + username + " " + password + "\n");
writer.flush();
// Read the response which is "failure" or "success" depending on the correctness of the login credentials
String loginResponse = reader.readLine();
if (loginResponse.equals("success")) {
// Set the login flag
isLoggedIn = true;
// Receive the login token for automatic logins in this session
authToken = reader.readLine();
// Get the old messages
messages.clear();
int messagesToReceive = Integer.parseInt(reader.readLine());
List<String> notifiableMessages = new ArrayList<>();
for (int i = 1; i <= messagesToReceive; i++) {
String messageFromServer = reader.readLine();
messages.add(messageFromServer);
if (i > readMessageCount) {
notifiableMessages.add(messageFromServer);
}
}
notifyUser(notifiableMessages);
readMessageCount = messages.size();
}
else {
isLoggedIn = false;
socketToServer.close();
initServerConnection();
// Notifiy user of failed login
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
});
loginThread.start();
loginThread.join();
if (isLoggedIn() && !isListenerRunning()) {
displayMainContent();
reconnectTask.startReconnectTask();
}
}
private void displayMainContent() throws Exception {
this.setContentView(R.layout.main);
// Register the list view
listView = (ListView) findViewById(R.id.listView);
Button reconnectBtn = (Button) findViewById(R.id.button);
reconnectBtn.setOnClickListener(new ReconnectButtonListener(this));
ArrayAdapter<String> contentAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);
Log.d("SWAGGERINO", "" + messages.size());
listView.setAdapter(contentAdapter);
if (!this.isListenerRunning()) {
this.startListener();
}
}
private void startListener() {
isListenerRunning = true;
final Handler handler = new Handler();
Runnable listenerRunnable = new Runnable() {
@Override
public void run() {
try {
String input;
while (!(input = reader.readLine()).equals("exit")) {
if (!isListenerRunning()) {
break;
}
final String tmp = input;
handler.post(new Runnable() {
public void run() {
addListContent(tmp);
if (!getPushActivity().isInForeground()) {
List<String> notification = new ArrayList<>(1);
notification.add(tmp);
notifyUser(notification);
}
}
});
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
};
Thread listenerThread = new Thread(listenerRunnable);
listenerThread.start();
}
private void stopListener() {
try {
socketToServer.close();
reader = null;
writer = null;
socketToServer = null;
}
catch (Exception ex) {
ex.printStackTrace();
}
}
And of course the method which handles reconnect:
public void doRelogin() {
stopListener();
initServerConnection();
try {
doLoginProcedure(username, authToken);
}
catch (Exception ex) {
ex.printStackTrace();
}
startListener();
}
ALREADY CHECKED
The flow is: doLoginProcedure()
and then startListener()
. In doLoginProcedure()
, everything works fine because the user gets logged in on the server. So I checked if the server closed the connection after login, but it wasn't.
I also have a cleanup task on the server for deleting doubled/inactive clients and checked if the server maybe deleted the wrong clients, which also wasn't the case.
I am really out of clues how to fix this problem and would appreciate help. If I missed some code, please comment and I will add it.
Thanks in advance
Upvotes: 0
Views: 621
Reputation: 311028
This exception means that you closed the socket. It doesn't indicate that the peer has disconnected.
So, it's a logic flaw in your code. You are indeed closing the socket at at least one point.
I suggest setting the socket to null after you do that. Then this exception will become an NPE, and you'll have to fix that :-|
NB closing either the input stream or output stream of a socket closes the other stream and the socket.
Upvotes: 0
Reputation: 719436
I'm trying to get my head around the logic of the code ...
But one thing that I've noticed is that you do this kind of thing in a couple of places:
Thread thread = new Thread(new Runnable(){ ... });
thread.start();
thread.join();
This usage of threads is pointless. You could achieve exactly the same effect by executing the body of the Runnable.run()
method.
Indeed, it is worse than pointless, because if the run()
method throws some unchecked exception that isn't caught and logged in the run()
method itself, then that exception is liable to go unnoticed. And, it is possible that that might be happening here ...
But even if not, the redundant threading is expensive, and makes your code more complicated and harder to read.
The only other thing I can think of is that the client side may not be getting the correct login response line. That would cause it to think that login had failed.
Apart from that, the best suggesting I can make is to run the client-side code in a debugger or add more trace-printing or logging. (For example, look at what Socket.isClosed()
returns at various points ....)
Upvotes: 1