php使用thrift做服务端开发

thrift采用接口描述语言定义和创建服务,用二进制格式传输数据,体积更小、效率更高,对于高并发、数据量大和多语言的环境有更好的支持。

Apache Thrift是啥?

Apache Thrift是FaceBook开发的一套可扩展的、跨语言的服务调用框架。简单的说就是先定义一个配置文件,不同的语言可以利用thrift基于这个配置文件生成各自语言的服务端,不管客户端用什么语言,都可以调用,也就是说基于thrift协议用java可以调用php的服务。目前支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi等语言之间相互调用。

相对于传统的xml和json等数据传输方式来说,thrift采用接口描述语言定义和创建服务,用二进制格式传输数据,体积更小、效率更高,对于高并发、数据量大和多语言的环境有更好的支持。

thrift安装环境要求

  • g++ 4.2

  • boost 1.53.0

  • lex and yacc(基于flex和bison)

如果没安装lex和yacc的话要先安装,否则会make失败,提示lex和yacc command not found错误(一般的机器貌似都没安,Ubuntu用apt-get install flex bision即可)。

安装thrift

下载最新版thrift:

wget http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.3/thrift-0.9.3.tar.gztar xvf thrift-0.9.3.tar.gzcd thrift-0.9.3

2.创建configure文件

// 创建./configure文件./bootstrap.sh// 配置并安装./configuremake// 检测是否有问题,如果机子没有安装python和java等可能会报错,不过本文主要讲php,安了php环境就行make checkmake install

编译选项

  • 使用./configure --help可以查看选项

  • 如果想禁用某个语言,可以用./configure --without-java

thrift for php安装环境要求

  • php版本>5.0,因为TBinaryProtocol协议用到了pack()和unpack()函数来序列化数据

  • 需要安装APC扩展,因为TSocketPool这个类用到了apc_fetch()和apc_store()函数进行apc缓存操作。

php使用thrift的时候,除了要将thrift/lib/php/lib里的基础文件copy到项目目录下,还需要将根据配置文件生成的php文件也copy到packages文件夹下,并引入到项目中,这个后续会详细讲。

类库说明

数据传输格式(protocol)

定义的了传输内容,对Thrift Type的打包解包,包括:

  • TBinaryProtocol,二进制格式,TBinaryProtocolAccelerated则是依赖于thrift_protocol扩展的快速打包解包。

  • TCompactProtocol,压缩格式

  • TJSONProtocol,JSON格式

  • TMultiplexedProtocol,利用前三种数据格式与支持多路复用协议的服务端(同时提供多个服务,TMultiplexedProcessor)交互

数据传输方式(transport)

定义了如何发送(write)和接收(read)数据,包括:

  • TBufferedTransport,缓存传输,写入数据并不立即开始传输,直到刷新缓存。

  • TSocket,使用socket传输

  • TFramedTransport,采用分块方式进行传输,具体传输实现依赖其他传输方式,比如TSocket

  • TCurlClient,使用curl与服务端交互

  • THttpClient,采用stream方式与HTTP服务端交互

  • TMemoryBuffer,使用内存方式交换数据

  • TPhpStream,使用PHP标准输入输出流进行传输

  • TNullTransport,关闭数据传输

  • TSocketPool在TSocket基础支持多个服务端管理(需要APC支持),自动剔除无效的服务器

开发流程

1、定义IDL(Interface description language)接口描述文件,后缀.thrift

IDL规范:http://thrift.apache.org/docs/idl

thrift types:http://thrift.apache.org/docs/types

2、服务端代码开发

3、客户端编写接入代码

IDL:
1.tutorial.thrift

include "shared.thrift"namespace php tutorialtypedef i32 MyIntegerconst i32 INT32CONSTANT = 9853const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}enum Operation {  ADD = 1,  SUBTRACT = 2,  MULTIPLY = 3,  DIVIDE = 4}struct Work {  1: i32 num1 = 0,  2: i32 num2,  3: Operation op,  4: optional string comment,}exception InvalidOperation {  1: i32 whatOp,  2: string why}service Calculator extends shared.SharedService {   void ping(),   i32 add(1:i32 num1, 2:i32 num2),   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),   oneway void zip()}

2.shared.thrift

namespace php sharedstruct SharedStruct {  1: i32 key  2: string value}service SharedService {  SharedStruct getStruct(1: i32 key)}

php服务端

<?phpnamespace tutorial\php;ini_set('display_errors',1);error_reporting(E_ALL);// 引入类自动加载文件require_once __DIR__.'/../../lib/php/lib/Thrift/ClassLoader/ThriftClassLoader.php';// 载入自动加载类use Thrift\ClassLoader\ThriftClassLoader;// 定义根据.thrift文件生成的php文件$GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php';// 注册thrift服务$loader = new ThriftClassLoader();$loader->registerNamespace('Thrift', __DIR__ . '/../../lib/php/lib');$loader->registerDefinition('shared', $GEN_DIR);$loader->registerDefinition('tutorial', $GEN_DIR);$loader->register();if (php_sapi_name() == 'cli') {  ini_set("display_errors", "stderr");}use Thrift\Protocol\TBinaryProtocol; // 二进制格式打包解包use Thrift\Transport\TPhpStream; // php流输入输出use Thrift\Transport\TBufferedTransport; // 使用缓存// 开始服务端逻辑class CalculatorHandler implements \tutorial\CalculatorIf {  protected $log = array();  public function ping() {    error_log("ping()");  }  // 相加  public function add($num1, $num2) {    error_log("add({$num1}, {$num2})");    return $num1 + $num2;  }  // 枚举计算类型  public function calculate($logid, \tutorial\Work $w) {    error_log("calculate({$logid}, {{$w->op}, {$w->num1}, {$w->num2}})");    switch ($w->op) {      case \tutorial\Operation::ADD:        $val = $w->num1 + $w->num2;        break;      case \tutorial\Operation::SUBTRACT:        $val = $w->num1 - $w->num2;        break;      case \tutorial\Operation::MULTIPLY:        $val = $w->num1 * $w->num2;        break;      case \tutorial\Operation::DIVIDE:        if ($w->num2 == 0) {          $io = new \tutorial\InvalidOperation();          $io->whatOp = $w->op;          $io->why = "Cannot divide by 0";          throw $io;        }        $val = $w->num1 / $w->num2;        break;      default:        $io = new \tutorial\InvalidOperation();        $io->whatOp = $w->op;        $io->why = "Invalid Operation";        throw $io;    }    $log = new \shared\SharedStruct();    $log->key = $logid;    $log->value = (string)$val;    $this->log[$logid] = $log;    return $val;  }  public function getStruct($key) {    error_log("getStruct({$key})");    // This actually doesn't work because the PHP interpreter is    // restarted for every request.    //return $this->log[$key];    return new \shared\SharedStruct(array("key" => $key, "value" => "PHP is stateless!"));  }  public function zip() {    error_log("zip()");  }};header('Content-Type', 'application/x-thrift');if (php_sapi_name() == 'cli') {  echo "\r\n";}$handler = new CalculatorHandler();$processor = new \tutorial\CalculatorProcessor($handler);// 客户端和服务端在同一个输入输出流上//1) cli 方式:php Client.php | php Server.php //2) cgi 方式:利用Apache或nginx监听http请求,调用php-fpm处理,将请求转换为PHP标准输入输出流$transport = new TBufferedTransport(new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W));$protocol = new TBinaryProtocol($transport, true, true);$transport->open();$processor->process($protocol, $protocol);$transport->close();//作为cli方式运行,非阻塞方式监听,基于libevent实现,非官方实现//$transportFactory = new TBufferedTransportFactory();//$protocolFactory = new TBinaryProtocolFactory(true, true);//$transport = new TNonblockingServerSocket('localhost', 9090);//$server = new TNonblockingServer($processor, $transport, $transportFactory, $transportFactory, $protocolFactory, $protocolFactory);//$server->serve();//作为cli方式运行,监听端口,官方实现//$transportFactory = new TBufferedTransportFactory();//$protocolFactory = new TBinaryProtocolFactory(true, true);//$transport = new TServerSocket('localhost', 9090);//$server = new TSimpleServer($processor, $transport, $transportFactory, $transportFactory, $protocolFactory, $protocolFactory);//$server->serve();

php客户端

<?phpnamespace tutorial\php;error_reporting(E_ALL);require_once __DIR__.'/../../lib/php/lib/Thrift/ClassLoader/ThriftClassLoader.php';use Thrift\ClassLoader\ThriftClassLoader;$GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php';$loader = new ThriftClassLoader();$loader->registerNamespace('Thrift', __DIR__ . '/../../lib/php/lib');$loader->registerDefinition('shared', $GEN_DIR);$loader->registerDefinition('tutorial', $GEN_DIR);$loader->register();use Thrift\Protocol\TBinaryProtocol;use Thrift\Transport\TSocket;use Thrift\Transport\THttpClient;use Thrift\Transport\TBufferedTransport;use Thrift\Exception\TException;// 以上配置跟服务端类似try {  if (array_search('--http', $argv)) {  // 使用http方式连接    $socket = new THttpClient('localhost', 8080, '/php/PhpServer.php');  } else {    // 使用socket连接    $socket = new TSocket('localhost', 9090);  }  $transport = new TBufferedTransport($socket, 1024, 1024);  $protocol = new TBinaryProtocol($transport);  $client = new \tutorial\CalculatorClient($protocol);  $transport->open();  $client->ping();  print "ping()\n";  $sum = $client->add(1,1);  print "1+1=$sum\n";  // 调试异常情况  $work = new \tutorial\Work();  $work->op = \tutorial\Operation::DIVIDE;  $work->num1 = 1;  $work->num2 = 0;  try {    $client->calculate(1, $work);    print "Whoa! We can divide by zero?\n";  } catch (\tutorial\InvalidOperation $io) {    print "InvalidOperation: $io->why\n";  }  $work->op = \tutorial\Operation::SUBTRACT;  $work->num1 = 15;  $work->num2 = 10;  $diff = $client->calculate(1, $work);  print "15-10=$diff\n";  $log = $client->getStruct(1);  print "Log: $log->value\n";  $transport->close();} catch (TException $tx) {  print 'TException: '.$tx->getMessage()."\n";}

输出:

// php client.php --httpping()1+1=2InvalidOperation: Cannot divide by 015-10=5Log: PHP is stateless!

更多相关文章

  1. 分享几种用PHP写99乘法表的方式
  2. 使用PHP来获取客户端和服务端IP
  3. php依赖注入的三种方式
  4. php中变量赋值的方式
  5. form表单在PHP中的实现方式

随机推荐

  1. 【读书笔记】【Android 开发艺术探索】第
  2. android SDK系统图片资源的路径。
  3. android 相对定位布局方向
  4. Android启动画面实现
  5. android 删除的警告对话框
  6. android usb host 读写USB设备
  7. Android下监听Home键
  8. 修改Android EditText光标颜色
  9. android九宫格
  10. :Android Market的 Loading效果