Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Remote Procedure Calls between Android and .NET

4.85/5 (16 votes)
27 Mar 2014CPOL5 min read 53.6K   1.3K  
Simple example showing Remote Procedure Calls between Android and .NET based on TCP.

Related articles:


Content

Introduction
Remote Procedure Calls vs Messaging
To Run Example Code
Tcp on Android
.NET Service Application
Android Client Application

Introduction

The example bellow demonstrates a simple client-server communication between Android and .NET using RPC (Remote Procedure Calls). The .NET application is a service exposing the interface and the Android application is a client calling remote methods from the interface.

The example is based on Eneter Messaging Framework. The framework is free and can be downloaded from http://www.eneter.net/ProductDownload.htm.
More technical info about the framework can be found at http://www.eneter.net/ProductInfo.htm.

Remote Procedure Calls vs Messaging

RPC is a request-response communication scenario where one application (typically client) requests to invoke a method in another application (typically service) and waits until the invoked method returns.

The difference between RPC and messaging is that RPC is functionality oriented and the messaging is message oriented. It means RPC models the interprocess communication from the perspective of required functionality and the messaging models it from the perspective of messages flowing between applications.

Therefore the RPC communication model specifies functions/methods which are exposed by a service and can be executed from clients. It is typically synchronous request-response interaction with one response per request.

Modeling the communication via messaging is different. Instead of functionality it focuses on messages and also how these messages are routed between applications. It does not couple the interprocess interaction with execution of certain methods. This allows to model various communication scenarios. E.g. request-response with zero, one or multiple responses per request or e.g. publish-subscribe communication. The communication is typically asynchronous.

It is obvious the messaging provides more possibilities in designing interprocess communication than RPC. The price for possibilities is the complexity. In some cases using the messaging can be an overkill. E.g. asynchronous programming is more complicated and error prone than synchronous. On the other hand (ab)using RPC for advanced communication scenarios can result into workarounds and poor quality.

Eneter Messaging Framework provides both approaches. Messaging as well as RPC. In fact RPC is implemented using the messaging. From the framework concept perspective RPC is part of end-points. It provides RpcClient and RpcService components which can attach and detach communication channels as any other messaging component.

749534/RPCCommunicationAndroidNET.png

The goal of RPC in Eneter Messaging Framework is not to pretend the interprocess communication is like a local call.
The goal is to provide a convenience for simple request-response scenarios. RPC allows to specify the service API by declaring a method interface (instead of declaring multiple message types and describing the communication protocol) and allows to perform request-response communication synchronously (instead of receiving response messages asynchronously).

In addition RPC in Eneter Messaging Framework works across multiple platforms. It means you can call remote methods e.g. between Android and .NET as demonstrated in the example bellow.

To Run Example Code

1. Download Eneter

The example source code from this article does not contain Eneter libraries. Therefore you need to download Eneter Messaging Framework for .NET and also for Android from http://www.eneter.net/ProductDownload.htm[^]

The reason why Eneter libraries are not included inside the article download is that Eneter has several releases during the year and it would be very difficult to maintain all download packages in all published articles up to date.

2. Add Eneter Libraries to the project.

When you have downloaded Eneter libraries you need to add them to projects. Add Eneter.Messaging.Framework.dll to the RpcService solution and eneter-messaging-android.jar into the AndroidRpcClient project.

Please follow this procedure (for Eclipse) to add libraries into the Android project: (To add a library into the project you need to import it instead of adding it via project properties. Also ensure Java compliance level is set to 6.0. Properties -> Java Compiler -> JDK Compliance -> 1.6.) Then you need to add Eneter libraries to projects and compile it.
To add eneter to the Android project please follow this procedure for Eclipse:

  1. Create a new folder 'libs' in your project. (use exactly name libs)
  2. Right click on 'libs' and choose 'Import...' -> 'General/File System' -> 'Next'.
  3. Then click 'Browser' button for 'From directory' and navigate to directory with libraries you want to add.
  4. Select check boxes for libraries you want to add.
  5. Press 'Finish'

3. Compile and Run

When you have added Eneter libraries into the project you can compile it and run it.
First execute RpcService and then AndroidRpcClient.

TCP on Android

When you implement the communication via TCP on Android, you must count with two specifics:

  1. You must set INTERNET permission for your Android application!
  2. If the permission is not set, the application is not allowed to communicate across the network. To set the INTERNET permission you must add the following line to AndroidManifest.xml.

    XML
    <uses-permission android:name="android.permission.INTERNET"/>

    An example of AndroidManifest.xml allowing communication across the network:

    XML
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="eneter.androidrpcclient"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="7"
            android:targetSdkVersion="17" />
        <uses-permission android:name="android.permission.INTERNET"/>
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="eneter.androidrpcclient.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>
  3. The IP address 127.0.0.1 (loopback) cannot be set on the Android emulator to communicate with the .NET application!

    The emulator acts as a separate device. Therefore, the IP address 127.0.0.1 is the loopback of that device and cannot be used for the communication with other applications running on the same computer as the emulator.

    Instead of that you must use a real IP address of the computer or the emulator can use the special address 10.0.2.2 that is routed to 127.0.0.1 (loopback) on the computer. In my example, the Android emulator uses 10.0.2.2 and the .NET service is listening to 127.0.0.1.

.NET Service Application

The service is a simple console application exposing a simple IMyService interface. The service is exposed using the RpcService component. RpcService then attaches the input channel and starts to listen to requests via TCP. When a request is received it is routed to the service method and the return value is then send back to the client.

The whole implementation is very simple:

C#
using System;
using Eneter.Messaging.EndPoints.Rpc;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace Service
{
    // Service interface.
    public interface IMyService
    {
        int Calculate(int a, int b);
        string GetEcho(string text);
    }

    // Service implementation.
    public class MyService : IMyService
    {
        public int Calculate(int a, int b)
        {
            int aResult = a + b;
            Console.WriteLine("{0} + {1} = {2}", a, b, aResult);

            return aResult;
        }

        public string GetEcho(string text)
        {
            Console.WriteLine("Received text = {0}", text);

            return text;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Use single-instance service.
            // It means all connected clients share the same instance of the service.
            // Note: if needed you can choose per-client-instance mode too.
            IRpcFactory aFactory = new RpcFactory();
            IRpcService<IMyService> aService =
                aFactory.CreateSingleInstanceService<IMyService>(new MyService());

            // Use TCP for the communication.
            // You also can use other protocols e.g. WebSockets.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
            IDuplexInputChannel anInputChannel =
                aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8032/");

            // Attach the input channel to the RpcService and start listening.
            aService.AttachDuplexInputChannel(anInputChannel);

            Console.WriteLine("RPC service is running. Press ENTER to stop.");
            Console.ReadLine();

            // Detach the input channel and release the listening thread.
            aService.DetachDuplexInputChannel();
        }
    }
}

Android Client Application

The client is a simple Android application which declares the same IMyService interface as the .NET service application. The IMyservice interface type is then provided to RpcClient which generates the proxy. The proxy is retrieved by myRpcClient.getProxy(). The retrieved proxy allows to call interface methods. The calls are then routed to the service where they are processed.

Here is the whole client implementation:

Java
package eneter.androidrpcclient;

import eneter.messaging.diagnostic.EneterTrace;
import eneter.messaging.endpoints.rpc.*;
import eneter.messaging.messagingsystems.messagingsystembase.*;
import eneter.messaging.messagingsystems.tcpmessagingsystem.TcpMessagingSystemFactory;
import android.os.Bundle;
import android.app.Activity;
import android.view.*;
import android.view.View.OnClickListener;
import android.widget.*;

public class MainActivity extends Activity
{
    // Interface exposed by the service.
    // Note: names of methods are case sensitive.
    // So keep capitals as declared in C#.
    public static interface IMyService
    {
        int Calculate(int a, int b);

        String GetEcho(String text);
    }

    // UI controls
    private EditText myNumber1EditText;
    private EditText myNumber2EditText;
    private EditText myResultEditText;
    private Button myCalculateButton;
    private EditText myEchoEditText;
    private Button myGetEchoButton;
    private EditText myEchoedEditText;

    // Eneter communication.
    IRpcClient<IMyService> myRpcClient;

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

        // Get UI widgets.
        myNumber1EditText = (EditText) findViewById(R.id.number1editText);
        myNumber2EditText = (EditText) findViewById(R.id.number2EditText);
        myResultEditText = (EditText) findViewById(R.id.resultEditText);
        myEchoEditText = (EditText) findViewById(R.id.echoTexteditText);
        myEchoedEditText = (EditText) findViewById(R.id.echoedEditText);

        myCalculateButton = (Button) findViewById(R.id.caculateBtn);
        myCalculateButton.setOnClickListener(myOnCalculateButtonClick);

        myGetEchoButton = (Button) findViewById(R.id.getEchoBtn);
        myGetEchoButton.setOnClickListener(myOnGetEchoButtonClick);

        // Open the connection in another thread.
        // Note: From Android 3.1 (Honeycomb) or higher
        // it is not possible to open TCP connection
        // from the main thread.
        Thread anOpenConnectionThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                openConnection();
            }
        });
        anOpenConnectionThread.start();
    }

    @Override
    public void onDestroy()
    {
        closeConnection();

        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    private void openConnection()
    {
        try
        {
            // Instantiate RPC client.
            RpcFactory aFactory = new RpcFactory();
            myRpcClient = aFactory.createClient(IMyService.class);

            // Use TCP for the communication.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();

            // Note: special IP address used by the Android simulator.
            // 10.0.2.2 is routed by the simulator to 127.0.0.1 of machine where
            // the simulator is running.
            // Use real IP address if you run it on a real Android device!
            IDuplexOutputChannel anOutputChannel = aMessaging
                    .createDuplexOutputChannel("tcp://10.0.2.2:8032/");

            // Attach the output channel to the RPC client and be able to
            // communicate.
            myRpcClient.attachDuplexOutputChannel(anOutputChannel);
        }
        catch (Exception err)
        {
            EneterTrace.error("Failed to open connection with the service.",
                    err);
        }
    }

    private void closeConnection()
    {
        // Stop listening to response messages.
        if (myRpcClient != null)
        {
            myRpcClient.detachDuplexOutputChannel();
        }
    }

    private void onCalculateButtonClick(View v)
    {
        int aNumber1 = Integer.parseInt(myNumber1EditText.getText().toString());
        int aNumber2 = Integer.parseInt(myNumber2EditText.getText().toString());

        try
        {
            // Invoke the remote method.
            int aResult = myRpcClient.getProxy().Calculate(aNumber1, aNumber2);

            myResultEditText.setText(Integer.toString(aResult));
        }
        catch (Exception err)
        {
            EneterTrace.error("Failed to invoke the remote method.", err);
        }
    }

    private void onGetEchoButtonClick(View v)
    {
        try
        {
            String aText = myEchoEditText.getText().toString();
            String anEchoedText = myRpcClient.getProxy().GetEcho(aText);

            myEchoedEditText.setText(anEchoedText);
        }
        catch (Exception err)
        {
            EneterTrace.error("Failed to invoke the remote method.", err);
        }
    }

    private OnClickListener myOnCalculateButtonClick = new OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            onCalculateButtonClick(v);
        }
    };

    private OnClickListener myOnGetEchoButtonClick = new OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            onGetEchoButtonClick(v);
        }
    };
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)