1. 程式人生 > >thrift入門-Android

thrift入門-Android

1、什麼是thrift?

thirft是Facebook公佈的一款開源跨語言的RPC框架。
thrift通過一箇中間語言IDL(介面定義語言)來定義RPC的資料型別和介面,這些內容寫在以.thrift結尾的檔案中,然後通過特殊的編譯器來生成不同語言的程式碼,以滿足不同需要的開發者,比如可以生成java程式碼,生成c++程式碼,生成的程式碼中不但包含目標語言的介面定義,方法,資料型別,還包含有RPC協議層和傳輸層的實現程式碼。以下就是它支援的資料型別。

1.基本型別

bool:布林值 (true or false), one byte
i16:16位有符號整型
i32:32位有符號整型
double:64位浮點型
string:編碼或者二進位制的字串

2.容器(Containers)
Thrift容器與流行程式語言的容器型別相對應,採用Java泛型風格。它有3種可用容器型別:

list: 元素型別為t1的有序表,容許元素重複。
set:元素型別為t1的無序表,不容許元素重複。
map

namespace java mythrift
enum HotelType {
   YILONG,
   JINJIANG=2
} 
struct Hotel {
  1:  optional i32 id;
  2:  optional string hotelname;
  3:  required HotelType HotelType.YILONG;
}

namespace(可省略):名稱空間,一方面他可以解決名字衝突的問題,另一方面可以指定生成檔案的路徑及包名。
required、optional:規範的struct定義中的每個域均會使用required或者optional關鍵字進行標識。如果required標識的域沒有賦值,Thrift將給予提示;如果optional標識的域沒有賦值,該域將不會被序列化傳輸。

3.傳輸方式:

  1. TSocket:阻塞型 socket,用於客戶端,採用系統函式 read 和 write 進行讀寫資料。
  2. TServerSocket:非阻塞型 socket,用於伺服器端,accecpt 到的 socket 型別都是 TSocket(即阻塞型 socket)。

2、安裝(windows平臺)

官網:http://thrift.apache.org/download
Thrift compiler for Windows (thrift-0.11.0.exe)可執行檔案用於在Windows下生成目標語言的樁程式碼,下載該exe應用程式。

3、開發

該Demo功能為:Android端請求服務端,服務端返回資料給Android端
該Demo不考慮效能、執行緒、優化等內容,僅僅做最簡單的Thrift通訊
通過該Demo,我個人對RPC或Thrift理解就是:客戶端和服務端約定好介面,服務端實現介面,客戶端根據介面傳入引數,遠端呼叫服務端的實現方法,從而實現遠端的方法呼叫。Android中存在的AIDL就類似於RPC。

(1)寫介面定義檔案.thrift檔案

namespace csharp TestLibrary

struct ContextObject{
    1:string RequestId,
    2:string MainSignal,
    3:map<string,string> Params
}
struct ServerResult{
    1:i32 Code,
    2:string Msg,
    3:string Data,
    4:string Sign
}
enum Operation{
    Increase=1,
    Decrease=-1
}
service UserStorage{
    ServerResult UpdateStar(1: i64 senderId, 2: i64 star,3: Operation op,4: ContextObject context);
}
service OrderStorage{
    ServerResult SendGift(1: i64 senderId, 2: i64 receiveId, 3: i64 star,4: ContextObject context);
}

service ChangeLogStorage{
    ServerResult AddLog(1: i64 userId,2: i64 changeStar,3: Operation op,4: ContextObject context);
}

(2)生成目標語言程式碼檔案

把官網下載到的檔案thrift-0.11.1.exe和test.thrift放到一個目錄(thirft)下
開啟cmd命令列視窗,進入到這個目錄,執行命令:
C:\Users\admin\Desktop\thirft>thrift-0.11.1.exe -gen java test.thrift
執行成功,在thirft目錄下,生成一個gen-java資料夾。
這裡寫圖片描述

(3)建立工程

新建Android工程,gradle中引入thrift

 compile 'org.apache.thrift:libthrift:0.11.0'

把剛才生成的介面檔案拷貝到專案中,接下來可以使用thrift進行遠端呼叫了。
這裡寫圖片描述

總結下它的呼叫步驟:

// 建立 TTransport傳輸協議
mTransport = ThriftManager.createTTransport();
// 開啟 TTransport
ThriftManager.openTTransport(mTransport);
// 建立客戶端
mUserClient = ThriftManager.createClient(mTransport);
// 呼叫服務
mUserClient.UpdateStar(senderId,star,operation,null);

程式碼如下:

package com.xy.cjy.thriftclient;

import android.support.test.espresso.core.deps.guava.base.Objects;

import com.xy.cjy.thriftclient.thrift.UserStorage;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

/**
 * thrift管理類
 *
 * Created by cjy on 2018/3/29.
 */

public class ThriftManager {
    private static int port = 9090;
    private static String ip = "192.168.78.92";

    /**
     * 建立 TTransport
     * @return
     */
    public static TTransport createTTransport(){
        TSocket tscoket = new TSocket(ip, port);
        tscoket.setConnectTimeout(3000);
        tscoket.setSocketTimeout(3000);
        tscoket.setTimeout(5000);
        TTransport transport = tscoket;
        return transport;
    }
    /**
     * 開啟 TTransport
     * @param transport
     * @throws TTransportException
     */
    public static void openTTransport(TTransport transport) throws TTransportException {
        if(Objects.equal(transport, null)){
            return;
        }
        transport.open();
    }
    /**
     * 關閉 TTransport
     * @param transport
     */
    public static void closeTTransport(TTransport transport){
        if(Objects.equal(transport, null)){
            return;
        }
        transport.close();
    }
    /**
     * 建立客戶端
     * @return
     */
    public static UserStorage.Client createClient(TTransport transport){
        if(Objects.equal(transport, null)){
            return null;
        }
        TProtocol protocol = new TBinaryProtocol(transport);
        if(Objects.equal(protocol, null)){
            return null;
        }
        UserStorage.Client client = new UserStorage.Client(protocol);
        return client;
    }
}

MainActivity.java:

package com.xy.cjy.thriftclient;

import android.os.Handler;
import android.os.Message;
import android.support.test.espresso.core.deps.guava.base.Objects;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.xy.cjy.thriftclient.thrift.ContextObject;
import com.xy.cjy.thriftclient.thrift.Operation;
import com.xy.cjy.thriftclient.thrift.ServerResult;
import com.xy.cjy.thriftclient.thrift.UserStorage;

import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransport;

import java.util.HashMap;

/**
 * 測試thrift
 * client不關閉,迴圈加1,9000次耗時 mBtnIncrease
 * client不關閉,迴圈-1,10000次耗時 mBtnDecrease
 * 每次open,然後呼叫updateStar,然後close,這樣迴圈10000次 mBtnOpenClose
 * 測試是否能夠監控到服務端連線中斷:open,不呼叫方法,伺服器連線斷開看看 mBtnConnect
 * 伺服器連線斷開了,呼叫方法看看 mBtnDisConnect
 *
 * 總結:請求的過程中,如果伺服器中斷連線,會丟擲異常:E/MainActivity: java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by peer)
 *
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private final String TAG = "MainActivity";
    private Button mBtnIncrease;
    private Button mBtnDecrease;
    private Button mBtnOpenClose;
    private Button mBtnConnect;
    private Button mBtnDisConnect;
    private TextView mTvMsg;

    private Handler mServerResultHandler;
    private TTransport mTransport;
    private UserStorage.Client mUserClient;

    private StringBuffer mSbResultText;
    private boolean mIsThreadRunning = false;

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

        findControl();
        initControl();
    }

    private void findControl(){
        mBtnIncrease = findViewById(R.id.btn_increase_thrift);
        mBtnDecrease = findViewById(R.id.btn_decrease_thrift);
        mBtnOpenClose = findViewById(R.id.btn_openclose_thrift);
        mBtnConnect = findViewById(R.id.btn_connect_thrift);
        mBtnDisConnect = findViewById(R.id.btn_disconnect_thrift);
        mTvMsg = findViewById(R.id.tv_msg);
    }

    private void initControl(){
        mSbResultText = new StringBuffer();

        mServerResultHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                String resultData = (String) msg.obj;
                mSbResultText.append(resultData+"\n");
                mTvMsg.setText(mSbResultText.toString());
            }
        };

        mBtnIncrease.setOnClickListener(this);
        mBtnDecrease.setOnClickListener(this);
        mBtnOpenClose.setOnClickListener(this);
        mBtnConnect.setOnClickListener(this);
        mBtnDisConnect.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id){
            case R.id.btn_increase_thrift:{
                btnIncreaseThrift();
                break;
            }
            case R.id.btn_decrease_thrift:{
                btnDecreaseThrift();
                break;
            }
            case R.id.btn_openclose_thrift:{
                btnOpenCloseThrift();
                break;
            }
            case R.id.btn_connect_thrift:{
                btnConnectThrift();
                break;
            }
            case R.id.btn_disconnect_thrift:{
                btnDisConnectThrift();
                break;
            }
        }
    }

    /**
     * 初始化Transport
     */
    private void initService(){
        try {
            if (mTransport == null) {
                // 建立 TTransport
                mTransport = ThriftManager.createTTransport();
                // 開啟 TTransport
                ThriftManager.openTTransport(mTransport);
                // 建立客戶端
                mUserClient = ThriftManager.createClient(mTransport);
                // 呼叫服務
                if (Objects.equal(mUserClient, null)) {
                    Log.e(TAG, "建立客戶端失敗...");
                    return;
                }
            }
        } catch (TException e) {
            Log.e(TAG,e.getMessage());
        }
    }

    /**
     * 加;迴圈每次+1,迴圈9000次
     * 結果:迴圈+1 9000次的耗時: 65365
     */
    private void increase(){
        try {
            //1-10 10個使用者
            long senderId = 1;
            //每個使用者都有10000個
            long star = 1;
            Operation operation = Operation.findByValue(1);
            ContextObject contextObject = new ContextObject();
            //兩個屬性
            contextObject.Params = new HashMap<String, String>();
            contextObject.RequestId = "uuid001";

            long clickTime = System.currentTimeMillis();

            ServerResult result = null;
            String resultData ="等待結果中...";
            //更新星值
            for (int i =0; i < 9000; i++){
                star++;
                result = mUserClient.UpdateStar(senderId,star,operation,null);

                resultData = JsonUtil.objectToString(result);
                Log.d(TAG,resultData);
            }
            long lastTime = System.currentTimeMillis();
            String wasteTime = (lastTime-clickTime)+"";
            Log.d("increase迴圈9000次的耗時",wasteTime);

            Message message = mServerResultHandler.obtainMessage();
            message.obj = "increase迴圈9000次的耗時:"+wasteTime;
            mServerResultHandler.sendMessage(message);

            mIsThreadRunning = false;

        } catch (TException e) {
            Log.e(TAG,e.getMessage());
        }
    }

    /**
     * 減:每次-1,迴圈10000次
     * 結果:迴圈-1 10000次的耗時: 72375
     */
    private void decrease(){
        try {
            //1-10 10個使用者
            long senderId = 1;
            //每個使用者都有10000個
            long star = 10000;
            Operation operation = Operation.findByValue(2);
            ContextObject contextObject = new ContextObject();
            //兩個屬性
            contextObject.Params = new HashMap<String, String>();
            contextObject.RequestId = "uuid001";

            long clickTime = System.currentTimeMillis();

            ServerResult result = null;
            String resultData ="等待結果中...";
            //更新星值
            for (int i =0; i < 10000; i++){
                star--;
                result = mUserClient.UpdateStar(senderId,star,operation,null);

                resultData = JsonUtil.objectToString(result);
                Log.d(TAG,resultData);
            }
            long lastTime = System.currentTimeMillis();
            String wasteTime = (lastTime-clickTime)+"";
            Log.d("decrease迴圈10000次的耗時",wasteTime);

            Message message = mServerResultHandler.obtainMessage();
            message.obj = "decrease迴圈10000次的耗時:"+wasteTime;
            mServerResultHandler.sendMessage(message);

            mIsThreadRunning = false;

        } catch (TException e) {
            Log.e(TAG,e.getMessage());
        }
    }

    /**
     * 每次open,然後呼叫updateStar,然後close
     */
    private void openClose(){
        try {
            // 建立 TTransport
            mTransport = ThriftManager.createTTransport();
            // 開啟 TTransport
            ThriftManager.openTTransport(mTransport);
            // 建立客戶端
            mUserClient = ThriftManager.createClient(mTransport);
            // 呼叫服務
            if (Objects.equal(mUserClient, null)) {
                Log.e(TAG, "建立客戶端失敗...");
                return;
            }

            //1-10 10個使用者
            long senderId = 1;
            //每個使用者都有10000個
            long star = 1;
            Operation operation = Operation.findByValue(1);
            ContextObject contextObject = new ContextObject();
            //兩個屬性
            contextObject.Params = new HashMap<String, String>();
            contextObject.RequestId = "uuid001";

            ServerResult result = null;
            String resultData ="等待結果中...";
            //更新星值
            result = mUserClient.UpdateStar(senderId,star,operation,null);

            resultData = JsonUtil.objectToString(result);
            Log.d(TAG,resultData);

            Message message = mServerResultHandler.obtainMessage();
            message.obj = resultData;
            mServerResultHandler.sendMessage(message);

        } catch (TException e) {
            e.printStackTrace();
        } finally {
            ThriftManager.closeTTransport(mTransport);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mTransport != null) {
            // 關閉 TTransport
            ThriftManager.closeTTransport(mTransport);
        }
        if (mServerResultHandler != null){
            mServerResultHandler = null;
        }
    }

    private void btnIncreaseThrift(){
        if (mIsThreadRunning == false) {
            mIsThreadRunning = true;
            //發起thrift請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    initService();
                    increase();
                }
            }).start();
        }
    }

    private void btnDecreaseThrift(){
        if (mIsThreadRunning == false) {
            mIsThreadRunning = true;
            //發起thrift請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    initService();
                    decrease();
                }
            }).start();
        }
    }

    private void btnOpenCloseThrift(){
        if (mIsThreadRunning == false) {
            mIsThreadRunning = true;
            //發起thrift請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long clickTime = System.currentTimeMillis();
                    //每次open,然後呼叫updateStar,然後close,這樣迴圈10000次,看看時間
                    //結果:open-close迴圈10000次的耗時: 118265
                    for (int i = 0; i < 10000; i++) {
                        openClose();
                    }
                    long lastTime = System.currentTimeMillis();
                    String wasteTime = (lastTime - clickTime) + "";
                    Log.d("open-close迴圈10000次的耗時", wasteTime);

                    Message message = mServerResultHandler.obtainMessage();
                    message.obj = "open-close迴圈10000次的耗時:" + wasteTime;
                    mServerResultHandler.sendMessage(message);

                    mIsThreadRunning = false;

                }
            }).start();
        }
    }

    private void btnConnectThrift(){
        if (mIsThreadRunning == false) {
            mIsThreadRunning = true;
            //發起thrift請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    initService();
                }
            }).start();
        }
    }

    private void btnDisConnectThrift(){
        if (mIsThreadRunning == false) {
            mIsThreadRunning = true;
            //發起thrift請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    initService();
                    decrease();
                }
            }).start();
        }
    }
}

有興趣的朋友可以下載原始碼看看。