Android實現Thrift服務端與客戶端
這一篇是將Android和C#實現Thrift服務端和客戶端中Android部分單獨拆分開來的,方便不需要C#的開發者使用。
編寫Thrift檔案
寫個簡單的,有輸入引數,無返回值,檔案命名為 HelloWorld.thrift
service HelloWorldService{
void SayHello(1:string msg);
}
根據Thrift檔案生成對應的java檔案,生成的java只有一個檔案。
可以看到我用的是Thrift 0.10.0版本
Android Thrift服務端和客戶端實現,並實現訊息傳送及接收
先看最後實現的效果
首先點選【啟動服務】按鈕,然後填寫正確的Ip、埠號,點選【傳送】即可完成訊息傳送。將Ip改成C#服務端ip(192.168.1.XX)和埠號即可完成與C#服務端之間的通訊。
新建一個Android專案:Thrift_HelloWorld_Android,將生成的Java檔案拷入專案的thrift_hellowrold_android資料夾內,目錄結構如下圖:
我使用IDE的Android Studio,新增thrift依賴項,類似eclipse的maven。在Gradle Scripts下的build.gradle(Module.app)檔案中新增:
compile 'org.apache.thrift:libthrift:0.10.0'
compile 'org.glassfish.main:javax.annotation:4.0-b33'
這裡還有一步比較重要,為thrift生成的java檔案新增包。因為生成的java檔案沒有設定package,而在java中沒有包是個很麻煩的事,我對java不熟,最簡單的方式就是為所有檔案新增package:
package com.david.thrift_helloworld_android;
Thrift準備工作完成了,我們開始做Android的介面展示部分。開啟app\res\activity_main.xml檔案,修改為:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.david.thrift_helloworld_android.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="15dp"
android:text="服務端埠號:"
/>
<EditText
android:id="@+id/edtSrvPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="100dp"
android:width="60dp"
android:text="1234"
/>
<Button
android:id="@+id/btnStart"
android:layout_y="0dp"
android:layout_x="170dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="啟動服務"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="55dp"
android:text="服務端地址"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="75dp"
android:layout_x="40dp"
android:text="IP:"
/>
<EditText
android:id="@+id/edtCliIP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="60dp"
android:layout_x="100dp"
android:width="160dp"
android:text="127.0.0.1"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="105dp"
android:layout_x="40dp"
android:text="埠號:"
/>
<EditText
android:id="@+id/edtCliPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="90dp"
android:layout_x="100dp"
android:width="60dp"
android:text="1234"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="135dp"
android:text="訊息內容:"
/>
<EditText
android:id="@+id/edtMsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="120dp"
android:layout_x="100dp"
android:width="260dp"
android:text="hello"
/>
<Button
android:id="@+id/btnSend"
android:layout_y="160dp"
android:layout_x="100dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="傳送"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_y="235dp"
android:text="接收到的訊息:"
/>
<TextView
android:id="@+id/tvMsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_x="0dp"
android:layout_y="258dp"
android:width="350dp"/>
</AbsoluteLayout>
此程式碼編寫了一個絕對的佈局,因為我對android開發不熟悉,先搞個最簡單的佈局湊合。佈局中有幾個輸入框、兩個點選按鈕、一個訊息展示控制元件。
首先在MainActivity中定義一個Handler,用於將訊息顯示到展示控制元件中。
public Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x8090) {
String str = (String) msg.getData().get("msg");
final TextView tvMsg = (TextView) findViewById(R.id.tvMsg);
tvMsg.setText(str);//Activity上的TextView元素顯示訊息內容
}
}
};
編寫Thrift Client程式碼,完成訊息傳送功能,此為MainActivity內部類。
public class HelloWorldClient {
public int Port;
public String IP;
public String Msg;
void CallServer(String ip, int port, String msg) throws TException {
Port = port;
IP = ip;
Msg = msg;
Runnable simple = new Runnable() {
public void run() {
TTransport transport = new TSocket(IP, Port);
try {
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
client.SayHello(Msg);
} catch (TException ex) {
Message msg = new Message();
msg.what = 0x8090;
msg.getData().putString("msg", ex.getMessage());
MainActivity.this.handler.sendMessage(msg);
} finally {
transport.close();
}
}
};
new Thread(simple).start();
}
}
編寫Thrift Server服務功能,首先實現服務介面,然後開啟執行緒執行服務。此為MainActivity內部類。
public class HelloWorldServiceImpl implements HelloWorldService.Iface {
@Override
public void SayHello(String str) throws TException {
Message msg = new Message();
msg.what = 0x8090;
msg.getData().putString("msg", str);
MainActivity.this.handler.sendMessage(msg);
}
}
public class HelloWorldServe {
public HelloWorldServiceImpl handler;
public HelloWorldService.Processor processor;
public void Run() {
try {
handler = new HelloWorldServiceImpl();
processor = new HelloWorldService.Processor(handler);
Runnable simple = new Runnable() {
public void run() {
simple(processor);
}
};
new Thread(simple).start();
} catch (Exception x) {
x.printStackTrace();
}
}
public void simple(HelloWorldService.Processor processor) {
try {
final EditText etPort = (EditText) findViewById(R.id.edtSrvPort);
String port = etPort.getText().toString();
int p = Integer.parseInt(port);
TServerTransport serverTransport = new TServerSocket(p);
//TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));
// Use this for a multithreaded server
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting the simple server...");
Message msg = new Message();
msg.what = 0x8090;
msg.getData().putString("msg", "Starting");
MainActivity.this.handler.sendMessage(msg);
server.serve();
} catch (Exception e) {
Message msg = new Message();
msg.what = 0x8090;
msg.getData().putString("msg", e.getMessage());
e.printStackTrace();
}
}
}
接來下為Button按鈕新增事件,有兩個按鈕,分別為【啟動服務】和【傳送】。程式碼在MainActivity中。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button btnServer = (Button) findViewById(R.id.btnStart);
btnServer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
HelloWorldServe srv = new HelloWorldServe();
srv.Run();
btnServer.setEnabled(false);
}
}
);
final Button btn = (Button) findViewById(R.id.btnSend);
//設定點選後TextView現實的內容
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
final EditText cPort = (EditText) findViewById(R.id.edtCliPort);
String port = cPort.getText().toString();
int p = Integer.parseInt(port);
final EditText cIp = (EditText) findViewById(R.id.edtCliIP);
String ip = cIp.getText().toString();
final EditText cMsg = (EditText) findViewById(R.id.edtMsg);
String msg = cMsg.getText().toString();
HelloWorldClient cli = new HelloWorldClient();
cli.CallServer(ip, p, msg);
} catch (TException ex) {
Toast.makeText(MainActivity.this, ex.getMessage(), Toast.LENGTH_LONG).show();
}
}
});
}
程式碼結構圖:
ok,完成程式碼編寫,接下來說幾個在執行時會發生的異常。
網路許可權
thrift client連線外部網路時需要申請系統網路許可權,設定的地方在AndroidManifest.xml檔案,具體如下圖:
<uses-permission android:name="android.permission.INTERNET" />
虛擬機器埠對映
還有一個很重要的地方,因為是在安卓虛擬機器上開發,在執行thrift server時申請的埠號並不是本機PC的埠號,會發現在本機中並沒有開啟此埠號。因為這個問題我搞了好久,一直以為是自己的服務開啟有問題,之後才找到原因是申請的埠號為安卓虛擬機器內埠號。可以參考一下Android筆記:模擬器之間的Socket通訊。簡單的說就是要把安卓虛擬機器內埠號對映為PC機埠號。接下來詳細介紹如何設定安卓虛擬機器埠對映。
1 檢視PC埠號
首先看一下PC埠號1234是否被佔用,因為安卓模擬器上開的是1234埠,PC也用1234埠,當然也可以改成其他埠號。
在PC命令列中輸入 netstat -ano 命令檢視所有被佔用的埠,確認沒有被佔用,如果被佔用則可以換其他埠。
也可以在PC命令列中輸入 netstat -ano | findstr 1234 檢視所有匹配的埠占用情況。
2 開啟虛擬機器
這個就沒什麼好講的了,要設定當然需要先把安卓虛擬機器啟動起來。
3 通過telnet進入虛擬機器
在PC命令列中輸入 telnet 127.0.0.1 5554 進入虛擬機器,5554為虛擬機器的埠號,不要和剛才的那個thrift server埠號搞混了。
可能會出現錯誤
telnet不是內部或外部命令
是因為PC沒有開啟telnet客戶端,參考Win7如何解決telnet不是內部或外部命令的方案!。
在輸入telnet 127.0.0.1 5554 後可能會出現
Android Console: Authentication required
解決方案:[tsackoverflow]Android Console: Authentication require
根據提示找到“C:\Users\Administrator.emulator_console_auth_token”檔案並開啟:
裡面就一串字串,複製該字串,在剛才telnet的命令列中輸入:
auth jSuaJlZp9P74fVh2
就完成了認證。若不完成此步,就不能成功設定埠對映。
4 埠對映
redir add tcp:1234:1234
ok,完成了這些。
若第3步沒有完成會提示錯誤:
KO: unknown command, try 'help'
完成埠對映後,開啟android thrift服務端,在PC命令列中輸入
netstat -ano | findstr 1234
檢視1234埠是否開啟,若開啟則對映成功。
ADB檢視埠占用
還有一個要說的,就是通過ADB檢視埠是否佔用,這裡有兩個意思,一是開啟服務前檢視需要使用的埠是否已經別佔用,二是在服務開啟後判斷服務是否開啟成功,通過檢視埠是否被佔用即可。
在PC中,通過命令列進入到adb.exe所在資料夾輸入
adb -s emulator-5554 shell
進入虛擬機器,輸入
netstat -apn | grep 1234
檢視埠1234是否被佔用。
原始碼地址:
Android上實現的Thrift服務端與客戶端
相關文章
- 實現客戶端與服務端的HTTP通訊客戶端服務端HTTP
- Thrift 客戶端-服務端 零XML配置 註解式配置客戶端服務端XML
- golang實現tcp客戶端服務端程式GolangTCP客戶端服務端
- 客戶端,服務端客戶端服務端
- 服務端,客戶端服務端客戶端
- Java的oauth2.0 服務端與客戶端的實現JavaOAuth服務端客戶端
- php原生socket實現客戶端與服務端資料傳輸PHP客戶端服務端
- ZooKeeper服務發現客戶端客戶端
- android客戶端與服務端互動的三種方式Android客戶端服務端
- Qt實現網路聊天室(客戶端,服務端)QT客戶端服務端
- Java建立WebService服務及客戶端實現JavaWeb客戶端
- .Net Remoting服務端與客戶端呼叫示例REM服務端客戶端
- rsync備份【基於客戶端與服務端】客戶端服務端
- Go基於gRPC實現客戶端連入服務端GoRPC客戶端服務端
- 模板,從服務端到客戶端服務端客戶端
- 服務端渲染和客戶端渲染服務端客戶端
- Java建立WebService服務及客戶端實現(轉)JavaWeb客戶端
- 檔案下載之斷點續傳(客戶端與服務端的實現)斷點客戶端服務端
- 客戶端與服務端Socket通訊原理詳解客戶端服務端
- HTML轉PDF的純客戶端和純服務端實現方案HTML客戶端服務端
- SSLSocket實現服務端和客戶端雙向認證的例子服務端客戶端
- 初探Thrift客戶端非同步模式客戶端非同步模式
- zeroc ice 客戶端與服務端通訊例子(c++)客戶端服務端C++
- SimpleRpc-客戶端與服務端工作模型探討RPC客戶端服務端模型
- Spring Boot 整合 WebSocket 實現服務端推送訊息到客戶端Spring BootWeb服務端客戶端
- socket實現服務端多執行緒,客戶端重複輸入服務端執行緒客戶端
- OSSEC服務端配置客戶端批次部署方案服務端客戶端
- python建立tcp服務端和客戶端PythonTCP服務端客戶端
- Go gRPC 系列二:一元客戶端與服務端GoRPC客戶端服務端
- Socket最簡單的客戶端與服務端通訊-Java客戶端服務端Java
- 【2】Windows C++ Redis服務端搭建與客戶端開發WindowsC++Redis服務端客戶端
- Android-TCP客戶端的實現AndroidTCP客戶端
- macOS 自帶的ftp服務端&vnc客戶端MacFTP服務端VNC客戶端
- MQTT伺服器搭建服務端和客戶端MQQT伺服器服務端客戶端
- Rest Post示例(java服務端、python客戶端)RESTJava服務端Python客戶端
- 使用多種客戶端消費WCF RestFul服務(一)——服務端客戶端REST服務端
- ZooKeeper服務發現客戶端--重連認證客戶端
- Android 客戶端與PC服務端socket通訊接收與傳送圖片(終結者)Android客戶端服務端