最近公司有新的业务需求,需要把大量计算放到服务器上,既要尽可能小的侵入业务,又要实现服务端的代码复用,领导提出用RPC协议来实现。对于没接触过服务器的我来说,听到这个也是一头雾水,不过好在网上有现成的资料,看了几份资料后,发现Android中的AIDL机制和RPC是有点相似的。如果你对AIDL机制有所了解,那么RPC对你来说会是很简单的。而本文讲述的Thrift框架则实现了RPC服务,并且可与服务器进行跨语言(C++, Java, Python, PHP等)通信。
关于Thrift的详解,请参看:和 Thrift 的一场美丽邂逅,本文基于该博客进行简化讲解,下文中的代码基于thrift-0.10.0版本。
〇、Demo说明
- 该Demo功能为:Android端传递字符串到服务端,服务端由Thrift把字符串反转后(模拟计算操作)返回给Android端
- 该Demo不考虑性能、线程、优化等内容,仅仅做最简单的Thrift通讯
- 通过该Demo,我个人对RPC或Thrift理解就是:客户端和服务端约定好接口,服务端实现接口,客户端根据接口传入参数,远程调用服务端的实现方法,从而实现远程的方法调用。Android中存在的AIDL就类似于RPC。
一、配置Thrift
1 . 下载Windows版本的thrift.exe文件:点我下载
2 . 把该文件重命名为thrift.exe,放置到一个英文路径下。(我放到了C:\Program Files\Tomcat文件夹中)
3 . 把thrift.exe所在目录添加到环境变量中
4 . 运行cmd,输入”thrift“后回车,来检查是否正确配置。如果配置正确,会显示如下提示:
二、定义接口
1 . 建立thrift接口:桌面上新建文件ReverseIface.thrift,在该文件中按以下格式定义一个reverseString(1:string strParam)接口,注意该接口文件为thrift版的原始接口文件,并且string为小写。
/**
* 文件名为ReverseIface.thrift
* 实现功能:创建一个服务接口service,用于反转字符串
* 基于:thrift-0.10.0
**/
namespace java com.younghong.thriftTest //定义命名空间,和java工程的包名一致
service ReverseIface{
/**
* 定义接口,参数中传入一个字符串
* @param strParam 待反转的字符串
*/
string reverseString(1:string strParam)
}
2 . 把上面的thrift接口编译成java接口:运行cmd,输入“cd desktop”后回车,切换到ReverseIface.thrift所在的桌面路径。然后接着输入“thrift -gen java ReverseIface.thrift”后回车,会在桌面生成gen-java文件夹,该文件夹中的ReverseIface.java文件就是我们需要的java接口。
三、搭建服务端
1 . 建立Maven工程,在pom.xml文件中的<dependencies>
标签中添加以下内容:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
2 . 把刚才的java接口文件ReverseIface.java拷贝到项目中,然后新建类ReverseImp,实现ReverseIface.Iface接口。也就是说,我们要在这里实现刚才定义的反转方法,让它能够在服务器端执行。
package com.younghong.thriftTest;
import java.util.Collections;
import org.apache.thrift.TException;
public class ReverseImp implements ReverseIface.Iface {
/**
* 我们在C中定义的接口,通过thrift编译后,成为了对应的java接口Iface
* C接口中的方法,也对应的成为了java接口中的方法
*/
public String reverseString(String strParam) throws TException {
// 参数为空,返回空
if (strParam == null || "".equals(strParam)) {
return "";
}
StringBuilder sb = new StringBuilder();//反转拼接
char[] charArray = strParam.toCharArray();//取出原字符数组
for (int i = charArray.length - 1; i >= 0; i--) {//反向取值,存入sb中
sb.append(charArray[i]);
}
return sb.toString();//返回反转结果
}
}
3 . 在程序入口main函数中,编写启动服务的代码 ,编写完成后启动服务端,我们的反转函数就在服务端待命了,等待远程调用。
package com.younghong.thriftTest;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TTransportException;
/**
1. Hello world!
2. */
public class App {
public static void main(String[] args) {
try {
// 实现反转字符串的Processor,传入实现类ReverseImp
ReverseIface.Processor processor = new ReverseIface.Processor(new ReverseImp());
// 构建Thrift通信协议,设置其中的参数
TNonblockingServerSocket socket = new TNonblockingServerSocket(8080);
TNonblockingServer.Args arg = new TNonblockingServer.Args(socket);
arg.protocolFactory(new TBinaryProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
// 根据设定好的参数,构建server,并启动server
TServer server = new TNonblockingServer(arg);
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
四、Android平台实现客户端远程调用
1 . 新建Android工程,gradle中引入thrift和javax注解
compile 'org.apache.thrift:libthrift:0.10.0'
compile 'org.glassfish.main:javax.annotation:4.0-b33'
2 .在MainActivity的布局中添加一个EditText和一个Button,EditText用于输入字符串,Button用于远程调用服务器的方法。
3 .把刚才的java接口文件ReverseIface.java拷贝到项目中,注意包名仍要保持一致。添加INTERNET
权限,然后在MainActivity.java中添加如下代码实现远程调用:
package com.younghong.thriftdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
//获取输入框的信息
EditText editText = (EditText) findViewById(R.id.editText);
final String strParam = editText.getText().toString();
//开启线程,执行网络远程调用
new Thread(new Runnable() {
@Override
public void run() {
try {
//构建Thrift传输和协议
TTransport tTransport = getTTransport();
TProtocol protocol = new TBinaryProtocol(tTransport);
//构建客户端,传入字符串进行反转操作
ReverseIface.Client client = new ReverseIface.Client(protocol);
String reversedStr = client.reverseString(strParam);
System.out.println("原字符串=" + strParam + "\t反转结果=" + reversedStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
//构建Thrift传输协议
private TTransport getTTransport() throws Exception {
try {
//主机、端口、超时
TSocket tSocket = new TSocket("172.16.36.146", 8080, 5000);
//根据socket构建thrift
TTransport tTransport = new TFramedTransport(tSocket);
if (!tTransport.isOpen()) {
tTransport.open();
}
return tTransport;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4 .安装apk到手机上,点击Button后,观看控制台,能看到“原字符串=Hello World! 反转结果=!dlroW olleH”的输出就证明远程调用成功。
五、总结
你可能会说直接用Http的形式不也能调用吗?对于Http来说,我们只能把数据封装成xml、json等形式,通过网络传输后再分别解析操作。而thrift实现RPC机制,完成了客户端对服务端方法的远程调用,无需拼接字符串等操作。
对于thrift,我们定义好方法的接口,然后在服务端实现接口,客户端指定服务端的网络地址和端口,然后就可以像调用普通方法那样来调用服务端的方法。本例中仅仅讲解了简单参数String类型,你也可以通过定义类类型,比如User来作为参数,无序xml化或json化,直接传递到服务端调用远程方法,就好像在调用本地方法一样简单。
至此,我们实现了简单的RPC调用,其中有不正确的地方欢迎留言指出。