Android和C#实现Thrift服务端和客户端

15 篇文章 0 订阅

现在一个项目需要实现安卓和服务器双向通信,因为服务器上的服务软件是C++写的,所以需要一种可以跨语言的通信框架。最基础的事Socket,但是不想用,用了RPC应该再也不会想用Socket了吧。扯完了,接下来进入正题。本文主要讲得内容:

  • C# Thrift服务端和客户端实现,并实现消息发送及接收
  • Android Thrift服务端和客户端实现,并实现消息发送及接收
  • Android发送消息到C#服务端
  • C#发送消息到Android服务端

在这些内容中会碰到几个问题,Android Thrift Client会碰到网络权限问题,Android Thrift Server实现会碰到安卓虚拟机与PC通信的端口映射问题,还有其他的问题开发完忘了。
由于对Android开发不熟悉,界面也没办法搞了,将就看看。

编写Thrift文件
写个简单的,有输入参数,无返回值,文件命名为 HelloWorld.thrift

service HelloWorldService{
    void SayHello(1:string msg);
}

根据Thrift文件生成对应的C#和java文件,生成的C#和java都只有一个文件。

这里写图片描述

可以看到我用的是Thrift 0.10.0版本

C# Thrift服务端和客户端实现,并实现消息发送及接收
创建一个WinForm应用程序,命名为Thrift_HelloWorld_Csharp。
将生成的C#文件拷到项目中,并包含在项目里。在Nuget中添加thrift-csharp的引用,0.10.0版本的。
目录结构如图:

这里写图片描述

最后要实现的效果是:当前程序启动一个Thrift服务,并且能通过当前程序调用Thrift Client发送消息到服务端。
效果如下:
这里写图片描述

点击【启动】即可启动C#服务端,填写完正确的端口号,点击【发送】即可完成消息发送,因为是在本机测试,所以Ip地址固定为127.0.0.1。输入Android映射出来的端口号即可完成与Android服务端通信。

Thrift Server 代码:

    public class HelloWorldServer : HelloWorldService.Iface
    {
        public static void Run(int port)
        {
            HelloWorldServer client = new HelloWorldServer();
            HelloWorldService.Processor processor = new global::HelloWorldService.Processor(client);
            TServerTransport transport = new TServerSocket(port);
            TServer server = new TThreadPoolServer(processor, transport);

            Console.WriteLine("Starting the server...");

            server.Serve();

            transport.Close();
        }

        public void SayHello(string msg)
        {
            Console.WriteLine(string.Format("{0:yyyy/MM/dd hh:mm:ss} {1}", DateTime.Now, msg));
        }
    }

调用Run方法即可启动Thrift服务,需另开一个线程运行此方法才不会阻塞下面的功能。

Thrift Client发送消息至服务端代码:

    public static class HelloWorldClient
    {
        public static void Say(int port, string msg, string ip = "127.0.0.1")
        {
            TTransport transport = new TSocket(ip, port);
            TProtocol protocol = new TBinaryProtocol(transport);
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);

            transport.Open();
            try
            {
                client.SayHello(msg);
            }
            catch (TApplicationException x)
            {
                Console.WriteLine(x.StackTrace);

            }
            finally
            {
                transport.Close();
            }
        }
    }

调用Say方法即可实现消息发送。当将port设置为服务端端口号时即可实现发送消息至当前服务端。

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发送消息到C#服务端

这个没什么好说的,将Ip和端口号设置为C# thrift服务端Ip及端口号即可完成消息发送。

C#发送消息到Android服务端

一样只需设置Ip及端口号即可。

源码下载
C#实现的Thrift服务端与客户端
Android实现的Thrift服务端与客户端

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nifty是facebook公司开源的,基于netty的thrift服务端客户端实现。 然后使用此包就可以快速发布出基于netty的高效的服务端客户端代码。 示例: public void startServer() { // Create the handler MyService.Iface serviceInterface = new MyServiceHandler(); // Create the processor TProcessor processor = new MyService.Processor<>(serviceInterface); // Build the server definition ThriftServerDef serverDef = new ThriftServerDefBuilder().withProcessor(processor) .build(); // Create the server transport final NettyServerTransport server = new NettyServerTransport(serverDef, new NettyConfigBuilder(), new DefaultChannelGroup(), new HashedWheelTimer()); // Create netty boss and executor thread pools ExecutorService bossExecutor = Executors.newCachedThreadPool(); ExecutorService workerExecutor = Executors.newCachedThreadPool(); // Start the server server.start(bossExecutor, workerExecutor); // Arrange to stop the server at shutdown Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { server.stop(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } Or the same thing using guice: public void startGuiceServer() { final NiftyBootstrap bootstrap = Guice.createInjector( Stage.PRODUCTION, new NiftyModule() { @Override protected void configureNifty() { // Create the handler MyService.Iface serviceInterface = new MyServiceHandler(); // Create the processor TProcessor processor = new MyService.Processor<>(serviceInterface); // Build the server definition ThriftServerDef serverDef = new ThriftServerDefBuilder().withProcessor(processor) .build(); // Bind the definition bind().toInstance(serverDef); } }).getInstance(NiftyBootstrap.class); // Start the server bootstrap.start(); // Arrange to stop the server at shutdown Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { bootstrap.stop(); } }); } 标签:Nifty
首先这个代码也是从别人那里下载的 但刚开始不能正确运行 通过自己的调试以后 可以正确运行 想在这里分享出来 帮助更多刚入门的同学 在这里想说一些自己的经验 来帮助大家可以更好的运行代码 主要包括两个文件夹: 一是HttpClient文件夹 是安卓客户端代码 要安装安卓环境才能正确运行 二是HelloSevlet文件夹 是安卓的服务器代码 在这个文件夹里只有Myserver java是通信用的 HelloWorldServlet java是用来测试用的 在这里说明几个在调试和测试过程中的关键点: HttpClient文件夹不多说了 安装了安卓环境都可以正确运行起来 主要说说服务器端 HelloSevlet文件夹 这个文件夹在运行时可以在本地的Apache服务器上部署 也可以在真正的服务器上部署 不管在哪部署 部署以后 通过http: ip:8080 Myserver来访问 如果在本地的IE上能访问 说明可以正确使用 如果在本地的IE都不能访问 说明路径还是有问题 还要再重新配置 当服务器端能够正确的在Apache上部署和运行以后 再通过安卓客户端代码访问 则可以正确通信了 PS: 代码里的服务器地址 要换成本地跑的服务器地址才可以 一般为127 0 0 1 ">首先这个代码也是从别人那里下载的 但刚开始不能正确运行 通过自己的调试以后 可以正确运行 想在这里分享出来 帮助更多刚入门的同学 在这里想说一些自己的经验 来帮助大家可以更好的运行代码 主要包括两个 [更多]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值