Android 和 .NET 之间的远程过程调用






4.85/5 (16投票s)
一个简单的示例,展示了基于 TCP 的 Android 和 .NET 之间的远程过程调用。
相关文章
- Android:如何通过 TCP 与 .NET 应用程序通信
- Android:使用 Protocol Buffers 与 .NET 进行快速通信
- Android:如何通过 Websocket 从多个 .NET 应用程序接收通知消息
Content
引言
远程过程调用与消息传递
运行示例代码
Android 上的 TCP
.NET 服务应用程序
Android 客户端应用程序
引言
下面的示例演示了使用 RPC(远程过程调用)在 Android 和 .NET 之间进行的简单客户端-服务器通信。.NET 应用程序是公开接口的服务,而 Android 应用程序是调用接口中远程方法的客户端。
该示例基于 Eneter Messaging Framework。该框架是免费的,可以从 http://www.eneter.net/ProductDownload.htm 下载。
有关该框架的更多技术信息,请访问 http://www.eneter.net/ProductInfo.htm。
远程过程调用与消息传递
RPC 是一种请求-响应通信场景,其中一个应用程序(通常是客户端)请求调用另一个应用程序(通常是服务)中的方法,并等待被调用的方法返回。
RPC 与消息传递的区别在于,RPC 面向功能,而消息传递面向消息。这意味着 RPC 从所需功能的角度建模进程间通信,而消息传递从应用程序之间流动的消息的角度建模。
因此,RPC 通信模型指定了由服务公开且可以从客户端执行的函数/方法。它通常是同步的请求-响应交互,每个请求只有一个响应。
通过消息传递建模通信有所不同。它不关注功能,而是关注消息以及这些消息如何在应用程序之间路由。它不将进程间交互与特定方法的执行耦合。这允许建模各种通信场景。例如,具有零个、一个或多个响应的请求-响应,或者例如发布-订阅通信。通信通常是异步的。
显然,消息传递在设计进程间通信方面比 RPC 提供了更多的可能性。代价是复杂性。在某些情况下,使用消息传递可能有点大材小用。例如,异步编程比同步编程更复杂且容易出错。另一方面,(滥用)RPC 来实现高级通信场景可能会导致变通和质量低下。
Eneter Messaging Framework 同时提供了这两种方法。消息传递和 RPC。实际上,RPC 是使用消息传递实现的。从框架概念的角度来看,RPC 是端点的一部分。它提供了 RpcClient 和 RpcService 组件,它们可以像其他消息传递组件一样附加和分离通信通道。

Eneter Messaging Framework 中 RPC 的目标不是假装进程间通信像本地调用一样。
其目标是为简单的请求-响应场景提供便利。RPC 允许通过声明方法接口(而不是声明多个消息类型并描述通信协议)来指定服务 API,并允许同步执行请求-响应通信(而不是异步接收响应消息)。
此外,Eneter Messaging Framework 中的 RPC 可跨多个平台工作。这意味着您可以调用远程方法,例如在 Android 和 .NET 之间,正如下面的示例所示。
运行示例代码
1. 下载 Eneter
本文的示例源代码不包含 Eneter 库。因此,您需要从 http://www.eneter.net/ProductDownload.htm 下载适用于 .NET 和 Android 的 Eneter Messaging Framework[^]
Eneter 库未包含在文章下载中的原因是 Eneter 一年有几次发布,要使所有已发布文章中的所有下载包保持最新非常困难。
2. 将 Eneter 库添加到项目中。
下载 Eneter 库后,您需要将它们添加到项目中。将 *Eneter.Messaging.Framework.dll* 添加到 RpcService 解决方案,并将 *eneter-messaging-android.jar* 添加到 AndroidRpcClient 项目。
请遵循以下步骤(适用于 Eclipse)将库添加到 Android 项目:(要将库添加到项目中,您需要导入它,而不是通过项目属性添加。另外,请确保 Java 合规级别设置为 6.0。*Properties -> Java Compiler -> JDK Compliance -> 1.6*。)然后,您需要将 Eneter 库添加到项目中并进行编译。
要将 eneter 添加到 Android 项目,请按照 Eclipse 的以下步骤操作
- 在项目中创建一个新文件夹 'libs'。(使用确切的名称 libs)
- 右键单击 'libs' 并选择 'Import...' -> 'General/File System' -> 'Next'。
- 然后点击“From directory”的“Browser”按钮,导航到要添加库的目录。
- 选择要添加的库的复选框。
- 按“Finish”
3. 编译和运行
将 Eneter 库添加到项目后,您可以进行编译和运行。
首先执行 RpcService,然后执行 AndroidRpcClient。
Android 上的 TCP
当您在 Android 上实现 TCP 通信时,您必须考虑两个特定问题
- 您必须为您的 Android 应用程序设置 INTERNET 权限!
- IP 地址 127.0.0.1(回送地址)不能在 Android 模拟器上用于与 .NET 应用程序通信!
模拟器充当一个独立设备。因此,IP 地址 127.0.0.1 是该设备的本地回送地址,不能用于与在模拟器运行的同一计算机上运行的其他应用程序进行通信。
相反,您必须使用计算机的真实 IP 地址,或者模拟器可以使用特殊地址 10.0.2.2,该地址被路由到计算机上的 127.0.0.1(回送地址)。在我的示例中,Android 模拟器使用 10.0.2.2,而 .NET 服务监听 127.0.0.1。
如果未设置权限,则不允许应用程序跨网络进行通信。要设置 INTERNET 权限,您必须将以下行添加到 *AndroidManifest.xml*。
<uses-permission android:name="android.permission.INTERNET"/>
允许跨网络通信的 *AndroidManifest.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>
.NET 服务应用程序
该服务是一个简单的控制台应用程序,公开了一个简单的 IMyService 接口。服务通过 RpcService 组件公开。RpcService 然后附加输入通道并开始通过 TCP 监听请求。收到请求后,它会被路由到服务方法,然后将返回值发送回客户端。
整个实现非常简单
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 客户端应用程序
客户端是一个简单的 Android 应用程序,它声明了与 .NET 服务应用程序相同的 IMyService 接口。IMyservice 接口类型随后提供给 RpcClient,后者生成代理。代理由 myRpcClient.getProxy() 检索。检索到的代理允许调用接口方法。然后,这些调用会被路由到服务并进行处理。
这是整个客户端实现
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);
}
};
}