Related articles:
Content
Introduction
Remote Procedure Calls vs Messaging
To Run Example Code
Tcp on Android
.NET Service Application
Android Client Application
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.
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.
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.
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:
- Create a new folder 'libs' in your project. (use exactly name libs)
- Right click on 'libs' and choose 'Import...' -> 'General/File System' -> 'Next'.
- Then click 'Browser' button for 'From directory' and navigate to directory with libraries you want to add.
- Select check boxes for libraries you want to add.
- 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.
When you implement the communication via TCP on Android, you must count with two specifics:
- You must set INTERNET permission for your Android application!
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.
<uses-permission android:name="android.permission.INTERNET"/>
An example of AndroidManifest.xml allowing communication across the network:
="1.0"="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>
- 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.
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:
using System;
using Eneter.Messaging.EndPoints.Rpc;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
namespace Service
{
public interface IMyService
{
int Calculate(int a, int b);
string GetEcho(string text);
}
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)
{
IRpcFactory aFactory = new RpcFactory();
IRpcService<IMyService> aService =
aFactory.CreateSingleInstanceService<IMyService>(new MyService());
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexInputChannel anInputChannel =
aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8032/");
aService.AttachDuplexInputChannel(anInputChannel);
Console.WriteLine("RPC service is running. Press ENTER to stop.");
Console.ReadLine();
aService.DetachDuplexInputChannel();
}
}
}
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:
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
{
public static interface IMyService
{
int Calculate(int a, int b);
String GetEcho(String text);
}
private EditText myNumber1EditText;
private EditText myNumber2EditText;
private EditText myResultEditText;
private Button myCalculateButton;
private EditText myEchoEditText;
private Button myGetEchoButton;
private EditText myEchoedEditText;
IRpcClient<IMyService> myRpcClient;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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);
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)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private void openConnection()
{
try
{
RpcFactory aFactory = new RpcFactory();
myRpcClient = aFactory.createClient(IMyService.class);
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexOutputChannel anOutputChannel = aMessaging
.createDuplexOutputChannel("tcp://10.0.2.2:8032/");
myRpcClient.attachDuplexOutputChannel(anOutputChannel);
}
catch (Exception err)
{
EneterTrace.error("Failed to open connection with the service.",
err);
}
}
private void closeConnection()
{
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
{
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);
}
};
}