Thursday, August 7, 2014

Prevent program blocked by DataInputStream.readUTF()

The former show a simple example of "Bi-directional communication between Client and Server, using ServerSocket, Socket, DataInputStream and DataOutputStream". The program flow will be blocked if dataInputStream.readUTF() is empty. This example show how to prevent it.


In Server side:
  • A CheckBox is added. If uncheck, no welcome message ("Hello from Android, you are #") will not be sent, such that dataInputStream.readUTF() in client side will be empty.
  • After serverSocket.accept(), check available() before call to prevent dataInputStream.readUTF() block the program.
    (updated@2014-08-10: after further test, it will lose data sometimes...cannot solve the problem!)
  • Alternatively, can start another thread to handle DataInputStream.readUTF() and other jobs, such that it will not block the main flow. Reference to the example of "Java/JavaFX Server link to Android Client".
In Client side:
  • If "Say hello..." box empty, no dataOutputStream.writeUTF(msgToServer) will be called; such that dataInputStream.readUTF() in server side will be empty.
  • If dataInputStream is empty, dataInputStream.readUTF() will block the current thread, but not the UI thread (or called Main Thread). Toast of "MyClientTask ended" will not be shown.
  • Another problem is running multi AsyncTask in parallel: Starting with HONEYCOMB, AsyncTask are executed on a single thread to avoid common application errors caused by parallel execution - read Run multi AsyncTask at the same time.
    So call myClientTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) if it is running on device higher than HONEYCOMB.

Example code in Server Side:
package com.example.androidserversocket;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;

import android.os.Bundle;
import android.app.Activity;
import android.widget.CheckBox;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView info, infoip, msg;
 String message = "";
 ServerSocket serverSocket;
 
 CheckBox optWelcome;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  info = (TextView) findViewById(R.id.info);
  infoip = (TextView) findViewById(R.id.infoip);
  msg = (TextView) findViewById(R.id.msg);
  
  optWelcome = (CheckBox)findViewById(R.id.optwelcome);

  infoip.setText(getIpAddress());

  Thread socketServerThread = new Thread(new SocketServerThread());
  socketServerThread.start();
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();

  if (serverSocket != null) {
   try {
    serverSocket.close();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

 private class SocketServerThread extends Thread {

  static final int SocketServerPORT = 8080;
  int count = 0;

  @Override
  public void run() {
   Socket socket = null;
   DataInputStream dataInputStream = null;
   DataOutputStream dataOutputStream = null;

   try {
    serverSocket = new ServerSocket(SocketServerPORT);
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      info.setText("I'm waiting here: "
        + serverSocket.getLocalPort());
     }
    });

    while (true) {
     socket = serverSocket.accept();
     dataInputStream = new DataInputStream(
       socket.getInputStream());
     dataOutputStream = new DataOutputStream(
       socket.getOutputStream());

     String messageFromClient = "";
     
     //Check available() before readUTF(),
     //to prevent program blocked if dataInputStream is empty
     if(dataInputStream.available()>0){
      messageFromClient = dataInputStream.readUTF();
     }
     
     count++;
     message += "#" + count + " from " + socket.getInetAddress()
       + ":" + socket.getPort() + "\n"
       + "Msg from client: " + messageFromClient + "\n";

     MainActivity.this.runOnUiThread(new Runnable() {

      @Override
      public void run() {
       msg.setText(message);
      }
     });
     
     if(optWelcome.isChecked()){
      String msgReply = "Hello from Android, you are #" + count;
      dataOutputStream.writeUTF(msgReply);
     }

    }
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    final String errMsg = e.toString();
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      msg.setText(errMsg);
     }
    });
    
   } finally {
    if (socket != null) {
     try {
      socket.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataInputStream != null) {
     try {
      dataInputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataOutputStream != null) {
     try {
      dataOutputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
   }
  }

 }

 private String getIpAddress() {
  String ip = "";
  try {
   Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
     .getNetworkInterfaces();
   while (enumNetworkInterfaces.hasMoreElements()) {
    NetworkInterface networkInterface = enumNetworkInterfaces
      .nextElement();
    Enumeration<InetAddress> enumInetAddress = networkInterface
      .getInetAddresses();
    while (enumInetAddress.hasMoreElements()) {
     InetAddress inetAddress = enumInetAddress.nextElement();

     if (inetAddress.isSiteLocalAddress()) {
      ip += "SiteLocalAddress: "
        + inetAddress.getHostAddress() + "\n";
     }

    }

   }

  } catch (SocketException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   ip += "Something Wrong! " + e.toString() + "\n";
  }

  return ip;
 }
}

Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/infoip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <CheckBox
        android:id="@+id/optwelcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="Send Welcome when connect" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <TextView
            android:id="@+id/msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>

download filesDownload the files.

Client side:
package com.example.androidclient;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.annotation.TargetApi;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

 TextView textResponse;
 EditText editTextAddress, editTextPort;
 Button buttonConnect, buttonClear;
 
 EditText welcomeMsg;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  editTextAddress = (EditText) findViewById(R.id.address);
  editTextPort = (EditText) findViewById(R.id.port);
  buttonConnect = (Button) findViewById(R.id.connect);
  buttonClear = (Button) findViewById(R.id.clear);
  textResponse = (TextView) findViewById(R.id.response);
  
  welcomeMsg = (EditText)findViewById(R.id.welcomemsg);

  buttonConnect.setOnClickListener(buttonConnectOnClickListener);

  buttonClear.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    textResponse.setText("");
   }
  });
 }

 OnClickListener buttonConnectOnClickListener = new OnClickListener() {

  @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override
  public void onClick(View arg0) {
   
   String tMsg = welcomeMsg.getText().toString();
   if(tMsg.equals("")){
    tMsg = null;
    Toast.makeText(MainActivity.this, "No Welcome Msg sent", Toast.LENGTH_SHORT).show();
   }
   
   MyClientTask myClientTask = new MyClientTask(editTextAddress
     .getText().toString(), Integer.parseInt(editTextPort
     .getText().toString()),
     tMsg);
   
   /*
    * Handle multi AsyncTask at the same time
    * Refer: http://android-er.blogspot.com/2014/04/run-multi-asynctask-as-same-time.html
    */
   //myClientTask.execute();
   if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
    myClientTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
   else
    myClientTask.execute();
  }
 };

 public class MyClientTask extends AsyncTask<Void, Void, Void> {

  String dstAddress;
  int dstPort;
  String response = "";
  String msgToServer;

  MyClientTask(String addr, int port, String msgTo) {
   dstAddress = addr;
   dstPort = port;
   msgToServer = msgTo;
  }

  @Override
  protected Void doInBackground(Void... arg0) {

   Socket socket = null;
   DataOutputStream dataOutputStream = null;
   DataInputStream dataInputStream = null;

   try {
    socket = new Socket(dstAddress, dstPort);
    dataOutputStream = new DataOutputStream(
      socket.getOutputStream());
    dataInputStream = new DataInputStream(socket.getInputStream());
    
    if(msgToServer != null){
     dataOutputStream.writeUTF(msgToServer);
    }
    
    //If dataInputStream is empty,
    //readUTF() will block the current thread,
    //but not the UI thread (or main thread).
    response = dataInputStream.readUTF();
    
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      Toast.makeText(MainActivity.this, 
       "MyClientTask ended", 
       Toast.LENGTH_SHORT).show();
     }
    });

   } catch (UnknownHostException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    response = "UnknownHostException: " + e.toString();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    response = "IOException: " + e.toString();
   } finally {
    if (socket != null) {
     try {
      socket.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataOutputStream != null) {
     try {
      dataOutputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataInputStream != null) {
     try {
      dataInputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
   }
   return null;
  }

  @Override
  protected void onPostExecute(Void result) {
   textResponse.setText(response);
   super.onPostExecute(result);
  }

 }

}

Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    <EditText 
        android:id="@+id/welcomemsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Say hello to server" />
    <EditText 
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="dstAddress" />
    <EditText 
        android:id="@+id/port"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="dstPort" />
    <Button 
        android:id="@+id/connect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Connect..."/>
    <Button 
        android:id="@+id/clear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Clear"/>
    <TextView
        android:id="@+id/response"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

download filesDownload the files.

No comments: