Sunday, June 15, 2014

Android TCP/IP client-server socket program (part one)

In this post I’m going to illustrate how we can create an Android server program and client program which can communicate via plain java TCP/IP sockets . Here to connect the server and the client I used a Wi-Fi connection.

In the first part of the post I describe the Server program and in the second part I describe the Client program. These two programs are running on two mobile phones which are connected by same Wi-Fi network and communicate each other through java sockets. In my case I created Wi-Fi hotspot using one pone and I ran the server program on that phone. Then I connected another phone to the Wi-Fi host spot and that phone has the client program. 

First let’s see how server program works. Server program has one Activity and one layout. You can download the complete application from here.


                                                   Quick overview of the server application

Here is the code for the main activity of the server program:

package com.codeoncloud.androidserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
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.support.v7.app.ActionBarActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {
 private TextView tvClientMsg,tvServerIP,tvServerPort;
 private final int SERVER_PORT = 8080; //Define the server port
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  tvClientMsg = (TextView) findViewById(R.id.textViewClientMessage);
  tvServerIP = (TextView) findViewById(R.id.textViewServerIP);
  tvServerPort = (TextView) findViewById(R.id.textViewServerPort);
  tvServerPort.setText(Integer.toString(SERVER_PORT));
  //Call method
  getDeviceIpAddress();
  //New thread to listen to incoming connections
  new Thread(new Runnable() {

   @Override
   public void run() {
    try {
     //Create a server socket object and bind it to a port
     ServerSocket socServer = new ServerSocket(SERVER_PORT);
     //Create server side client socket reference
     Socket socClient = null;
     //Infinite loop will listen for client requests to connect
     while (true) {
      //Accept the client connection and hand over communication to server side client socket
      socClient = socServer.accept();
      //For each client new instance of AsyncTask will be created
      ServerAsyncTask serverAsyncTask = new ServerAsyncTask();
      //Start the AsyncTask execution 
      //Accepted client socket object will pass as the parameter
      serverAsyncTask.execute(new Socket[] {socClient});
     }
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }).start();
 }
/**
 * Get ip address of the device 
 */
 public void getDeviceIpAddress() {
  try {
   //Loop through all the network interface devices
   for (Enumeration<NetworkInterface> enumeration = NetworkInterface
     .getNetworkInterfaces(); enumeration.hasMoreElements();) {
    NetworkInterface networkInterface = enumeration.nextElement();
    //Loop through all the ip addresses of the network interface devices
    for (Enumeration<InetAddress> enumerationIpAddr = networkInterface.getInetAddresses(); enumerationIpAddr.hasMoreElements();) {
     InetAddress inetAddress = enumerationIpAddr.nextElement();
     //Filter out loopback address and other irrelevant ip addresses 
     if (!inetAddress.isLoopbackAddress() && inetAddress.getAddress().length == 4) {
      //Print the device ip address in to the text view 
      tvServerIP.setText(inetAddress.getHostAddress());
     }
    }
   }
  } catch (SocketException e) {
   Log.e("ERROR:", e.toString());
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }
/**
 * AsyncTask which handles the commiunication with clients
 */
 class ServerAsyncTask extends AsyncTask<Socket, Void, String> {
  //Background task which serve for the client
  @Override
  protected String doInBackground(Socket... params) {
   String result = null;
   //Get the accepted socket object 
   Socket mySocket = params[0];
   try {
    //Get the data input stream comming from the client 
    InputStream is = mySocket.getInputStream();
    //Get the output stream to the client
    PrintWriter out = new PrintWriter(
      mySocket.getOutputStream(), true);
    //Write data to the data output stream
    out.println("Hello from server");
    //Buffer the data input stream
    BufferedReader br = new BufferedReader(
      new InputStreamReader(is));
    //Read the contents of the data buffer
    result = br.readLine();
    //Close the client connection
    mySocket.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
   return result;
  }

  @Override
  protected void onPostExecute(String s) {
   //After finishing the execution of background task data will be write the text view
   tvClientMsg.setText(s);
  }
 }
}

Basically server program involves in three main functions.
1. Get the device IP address and print it 
2.  Listen for client connections 
3. Send a message to the client and get a message from the client

1. Get the device IP and print it.
For the client program we need to know the server ip address and port to connect the client with the server. So I use getDeviceIpAddress [line 65 to 84] method to get the ip address of the device which acts as the server

2. Listen for client connections.
The second main function of the server program is listening to the assigned port for client connection requests. To do this I created separate thread [line 37]. This thread will be started from the ui thread and in this thread, an infinite while loop [line 47] is used to listen for client requests. Because of the infinite loop this can serve for multiple client connections. Whiting this thread ServerSocket object will be created and that will bind to the assigned port (8080) [line 43]. You can use your own port number. When client connection is accepted server side client socket object will be created [line 49] and an instance of AsyncTask is also created [line 52]. Then the communication with this client will be handover to the AsyncTask by passing newly created client socket object to the AsyncTask [line 54]. For each accepted connection new AsyncTask object will be created. After starting the execution of the AsyncTask the thread will available to accept new client connections.

3. Send message to the client and get message from the client.
This is about handling the communication with a client. Communication part will be done by the AsyncTask,  ServerAsyncTask. In the background process of the AsyncTask first another client object reference mySocket will be created and that reference will use to refer to the server side client object previously created socClient. Data input stream from the client will be get and data which is sending from the client will be read by using the input stream [line 112,119,122]. To send data to the client a data output stream will be opened [line 114] and data that needed to be send to the client will be write in to the output stream using the printwriter object created [line 117]. After finishing the execution of background task in the onPostExecute method the message sent by the client will be print in to the text view [line 134].
Note that to run the server program first create Wi-Fi hotspot from your device which is going to use as the server device and then run the server program on that device. After running the server program you can see the ip address and the port number of the server.

Here is the code of ActivityMain layout :


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.codeoncloud.androidserver02.MainActivity"
    tools:ignore="MergeRootFrame" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="25dp"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/textViewSrvrIP"
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="Server IP"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewSrvrIP1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=":"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewServerIP"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="50dp"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/textViewSrvrPort"
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="Server Port"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewSrvrPort1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=":"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewServerPort"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="50dp"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/textViewClMsg"
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="Client msg"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewClMsg1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=":"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/textViewClientMessage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="20dp"
                android:textAppearance="?android:attr/textAppearanceMedium" />
        </LinearLayout>
    </LinearLayout>

</FrameLayout>
In order to access certain network functionalists we have to give few uses permissions in Manifest file. i.e
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

Here is the Manifest file after adding all the permissins.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.codeoncloud.androidserver"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.codeoncloud.androidserver.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Note that to run the server program first create Wi-Fi hotspot from your device which is going to use as the server device and then run the server program on that device. After running the server program you can see the ip address and the port number of the server. In the next part of this application I describe the implementation of the client application. 

Happy coding :)

2 comments:

  1. I have tried this example of yours. I am afraid that there is a problem and it is not working.
    I am trying on two different phones connected to a same WiFi connection.

    I made a little change in your change i.e. in function getDeviceIpAddress(), I declared and initialized
    NetworkInterface networkInterface = null;
    InetAddress inetAddress = null;
    before the for loops. In the parameters of the second for loop, you wrote:
    enumerationIpAddr = networkInterface.getInetAddresses() as a condition of this loop. I want to ask you that how could you access "networkInterface" here as it is being declared inside the previous for loop and thus, is out of scope and must give error of not being declared. That is why I took the liberty of taking it at the beginning of the function and use it like this.

    However, it is not working. Would you like to give some suggestion?

    ReplyDelete
  2. There was a connection problem. The two phones could not connect. No messages

    ReplyDelete