Thrift(PHP)入门无错篇章
Posted 咚..咚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thrift(PHP)入门无错篇章相关的知识,希望对你有一定的参考价值。
一、安装篇
博主注:截至2017-10-10,官网上thrift最新版0.10.0一直无法成功编译。所以,请选择0.9.3版本,避免走各种弯路:
wget http://apache.fayea.com/thrift/0.9.3/thrift-0.9.3.tar.gz
1、安装开发平台工具
yum -y groupinstall "Development Tools"
2、安装autoconf
wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
tar xvf autoconf-2.69.tar.gz
cd autoconf-2.69
./configure --prefix=/usr/local
make
make install
3、安装automake
wget http://ftp.gnu.org/gnu/automake/automake-1.14.tar.gz
tar xvf automake-1.14.tar.gz
cd automake-1.14
./configure --prefix=/usr/local
make
make install
4、安装bison
wget http://ftp.gnu.org/gnu/bison/bison-2.5.1.tar.gz
tar xvf bison-2.5.1.tar.gz
cd bison-2.5.1
./configure --prefix=/usr/local
make
make install
5、安装C++库依赖包
yum -y install libevent-devel zlib-devel openssl-devel
6、安装boost
wget http://sourceforge.net/projects/boost/files/boost/1.53.0/boost_1_53_0.tar.gz
tar xvf boost_1_53_0.tar.gz
cd boost_1_53_0
./bootstrap.sh
./b2
7、安装其它依赖包
yum install gcc gcc-c++ bzip2 bzip2-devel bzip2-libs python-devel -y
8、安装thrift(基本的编译套路)
cd thrift
./bootstrap.sh
./configure --with-lua=no --prefix=/alidata/server/thrift
make
make install
二、Hello World篇
使用套路总结:
1、用IDL语法,定义自己的数据结构与服务接口:
https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=tutorial/tutorial.thrift
https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=tutorial/shared.thrift
将这两个文件下载到本地,分别保存为tutorial.thrift和shared.thrift,并将这两个文件传到与thrift同目录下。
附:
IDL详细教程:http://thrift.apache.org/docs/idl
IDL示例(够用了):https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=tutorial/tutorial.thrift
https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=tutorial/shared.thrift
2、用thrift将上面的thrift的IDL文件,转成对应语言的类:
thrift -gen php:server tutorial.thrift
上面这一句是对应服务端的转换,客户端的转换不用:server;但是服务端方式生成的类文件可以供客户端使用。
3、转换完成后,在本目录下会得到一个gen-php目录,里面有三个文件tutorial.php、shared.php和Types.php。
tutorial.php里面有一个CalculatorIf接口,这个类是我们要实现的服务端业务接口。
shared.php被引用在tutorial.php文件中。
Types.php里面是自定义数据类型。
4、实现server端,即需要创建一个Handler类实再业务接口CalculatorIf
注意:这里的lib文件在thrift源码包lib里,我们移到自己的php需要的路径中。代码中require_once部分、$GEN_DIR部分、Thrift命名空间注册部分的路径需要根据实际情况进行更改
<?php namespace 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(); if (php_sapi_name() == \'cli\') { ini_set("display_errors", "stderr"); } use Thrift\\Protocol\\TBinaryProtocol; use Thrift\\Transport\\TPhpStream; 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); $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();
5、实现client端:
同样需要注意:这里的lib文件在thrift源码包lib里,我们移到自己的php需要的路径中。代码中require_once部分、$GEN_DIR部分、Thrift命名空间注册部分的路径需要根据实际情况进行更改
<?php namespace 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)) { $socket = new THttpClient(\'localhost\', 8080, \'/php/PhpServer.php\'); } else { $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"; } ?>
6、因为php的server端代码没有进行端口侦听的网络服务功能,所以要依靠python来提供这个功能。我们把下面的脚本命名为s.py,需要与server.php放在同一个目录下(/alidata/www/thrift)
#!/bin/py #coding=utf-8 import os import BaseHTTPServer import CGIHTTPServer # chdir(2) into the tutorial directory. os.chdir(\'/alidata/www/thrift\') # 指定目录 ,如果目录错误 请求会失败 class Handler(CGIHTTPServer.CGIHTTPRequestHandler): cgi_directories = [\'/\'] BaseHTTPServer.HTTPServer((\'\', 8080), Handler).serve_forever()
7、到现在为止,hello world级别的工程已经完成。
运行服务端s.py
运行客户端php client.php --http
便可以看到响应的实际效果
三、生产使用篇
hello world级别的工程,只有一个进程进行侦听和服务,在多核CPU的时代,只有一个进程不但无法充分利用CPU的性能,也无法为用户提供最高效的服务。
方案一:用php自己实现网络服务方案(这是一个牛人写的多进程服务demo,完全可以参照) http://blog.csdn.net/flynetcn/article/details/47837975
<?php /** * 多进程形式的server. * @package thrift.server * @author flynetcn */ namespace Thrift\\Server; use Thrift\\Server\\TServer; use Thrift\\Transport\\TTransport; use Thrift\\Exception\\TException; use Thrift\\Exception\\TTransportException; class TMultiProcessServer extends TServer { /** * 捕获的信号编号 */ static $catchQuitSignal = 0; /** * worker进程数量 */ private $workProcessNum = 4; /** * 每个worker进程处理的最大请求数 */ private $maxWorkRequestNum = 2000; /** * 当前worker进程已处理的请求数 */ private $currentWorkRequestNum = 0; /** * 当前连接调用次数 */ private $currentConnectCallNum = 0; /** * 发送超时 */ private $sendTimeoutSec = 1; /** * 接收超时 */ private $recvTimeoutSec = 1; /** * 当前进程pid */ private $pid = 0; /** * Flag for the main serving loop */ private $stop_ = false; /** * List of children. */ protected $childrens = array(); /** * 服务器日志文件 */ protected static $logFiles; protected static $pidFile; /** * run */ public function serve($daemon=false, array $config=array()) { if (isset($config[\'workProcessNum\'])) { $this->workProcessNum = intval($config[\'workProcessNum\']); } if ($this->workProcessNum < 1) { self::log(1, "child workProcessNum can not be less than 1"); throw new TException(\'child workProcessNum can not be less than 1\'); } if (isset($config[\'maxWorkRequestNum\'])) { $this->maxWorkRequestNum = intval($config[\'maxWorkRequestNum\']); } if ($this->maxWorkRequestNum < 1) { self::log(1, "child maxWorkRequestNum can not be less than 1"); throw new TException(\'child maxWorkRequestNum can not be less than 1\'); } if (isset($config[\'sendTimeoutSec\'])) { $this->sendTimeoutSec = intval($config[\'sendTimeoutSec\']); } if (isset($config[\'recvTimeoutSec\'])) { $this->recvTimeoutSec = intval($config[\'recvTimeoutSec\']); } if ($daemon) { $this->daemon(); $this->registerSignalHandler(); self::$logFiles = isset($config[\'logFiles\']) && is_array($config[\'logFiles\']) ? $config[\'logFiles\'] : array(); self::$pidFile = isset($config[\'pidFile\']) ? $config[\'pidFile\'] : \'\'; declare(ticks=3); } $this->pid = posix_getpid(); self::createPidFile($this->pid); self::log(0, "manage process({$this->pid}) has started"); $this->transport_->listen(); while (!$this->stop_) { while ($this->workProcessNum > 0) { try { $pid = pcntl_fork(); if ($pid > 0) { $this->handleParent($pid, $this->workProcessNum); } else if ($pid === 0) { $this->pid = posix_getpid(); $this->handleChild($this->workProcessNum); } else { self::log(1, "Failed to fork"); throw new TException(\'Failed to fork\'); } $this->workProcessNum--; } catch (Exception $e) { } } $this->collectChildren(); sleep(2); if (\\Thrift\\Server\\TMultiProcessServer::$catchQuitSignal) { $this->stop(); } } } public function getCurrentWorkRequestNum() { return $this->currentWorkRequestNum; } public function getCurrentConnectCallNum() { return $this->currentConnectCallNum; } /** * Code run by the parent * * @param int $pid * @param int $num 进程编号 * @return void */ private function handleParent($pid, $num) { $this->childrens[$pid] = $num; } /** * Code run by the child. * * @param int $num 进程编号 * @return void */ private function handleChild($num) { self::log(0, "child process($this->pid) has started"); $this->childrens = array(); while (!$this->stop_) { try { $transport = $this->transport_->accept(); if ($transport != null) { $transport->setSendTimeout($this->sendTimeoutSec * 1000); $transport->setRecvTimeout($this->recvTimeoutSec * 1000); $this->currentWorkRequestNum++; $this->currentConnectCallNum = 0; $inputTransport = $this->inputTransportFactory_->getTransport($transport); $outputTransport = $this->outputTransportFactory_->getTransport($transport); $inputProtocol = $this->inputProtocolFactory_->getProtocol($inputTransport); $outputProtocol = $this->outputProtocolFactory_->getProtocol($outputTransport); while ($this->processor_->process($inputProtocol, $outputProtocol)) { $this->currentConnectCallNum++; } @$transport->close(); } } catch (TTransportException $e) { } catch (Exception $e) { self::log(1, $e->getMessage().\'(\'.$e->getCode().\')\'); } if (\\Thrift\\Server\\TMultiProcessServer::$catchQuitSignal) { $this->stop(); } if ($this->currentWorkRequestNum >= $this->maxWorkRequestNum) { self::log(0, "child process($this->pid) has processe {$this->currentWorkRequestNum} requests will be exit"); $this->stop(); break; } } exit(0); } /** * Collects any children we may have * * @return void */ private function collectChildren() { foreach ($this->childrens as $pid => $num) { if (pcntl_waitpid($pid, $status, WNOHANG) > 0) { unset($this->childrens[$pid]); $this->workProcessNum++; } } } /** * @return void */ public function stop() { $this->transport_->close(); $this->stop_ = true; foreach ($this->childrens as $pid => $num) { if (!posix_kill($pid, SIGTERM)) { } } } /** * 附加信号处理 */ public static function sig_handler($signo) { switch ($signo) { case SIGTERM: case SIGHUP: case SIGQUIT: case SIGTSTP: $pid = posix_getpid(); self::log(0, "process($pid) catch signo: $signo"); \\Thrift\\Server\\TMultiProcessServer::$catchQuitSignal = $signo; break; default: } } /** * 附加信号处理 */ private function registerSignalHandler() { pcntl_signal(SIGTERM, \'\\Thrift\\Server\\TMultiProcessServer::sig_handler\'); pcntl_signal(SIGHUP, \'\\Thrift\\Server\\TMultiProcessServer::sig_handler\'); pcntl_signal(SIGQUIT, \'\\Thrift\\Server\\TMultiProcessServer::sig_handler\'); pcntl_signal(SIGTSTP, \'\\Thrift\\Server\\TMultiProcessServer::sig_handler\'); declare(ticks=3); } /** * 附加守护进程方式 */ private function daemon() { if (!function_exists(\'posix_setsid\')) { return; } if (($pid1 = pcntl_fork()) != 0) { exit; } posix_setsid(); if (($pid2 = pcntl_fork()) != 0) { exit; } } public static function log($type, $msg) { static $fds; $msg = date(\'Y-m-d H:i:s\')." $type {$msg}\\n"; if (isset(self::$logFiles[$type]) && self::$logFiles[$type]) { if (file_exists(self::$logFiles[$type])) { if (empty($fds[$type])) { $fds[$type] = fopen(self::$logFiles[$type], \'a\'); } if (!$fds[$type]) { $fds[$type] = fopen(\'php://stdout\', \'w\'); fwrite($fds[$type], date(\'Y-m-d H:i:s\')." WARNING fopen(".self::$logFiles[$type].") failed\\n"); } } else { if (!is_dir(dirname(self::$logFiles[$type])) && !mkdir(dirname(self::$logFiles[$type]), 0755, true)) { $fds[$type] = fopen(\'php://stdout\', \'w\'); fwrite($fds[$type], date(\'Y-m-d H:i:s\')." WARNING mkdir(".self::$logFiles[$type].") failed\\n"); } elseif (!($fds[$type] = fopen(self::$logFiles[$type], \'a\'))) { $fds[$type] = fopen(\'php://stdout\', \'w\'); fwrite($fds[$type], date(\'Y-m-d H:i:s\')." WARNING fopen(".self::$logFiles[$type].") failed\\n"); } } } else { $fds[$type] = fopen(\'php://stdout\', \'w\'); } $ret = fwrite($fds[$type], $msg); if (!$ret && self::$logFiles[$type]) { fclose($fds[$type]); $fds[$type] = fopen(self::$logFiles[$type], \'a\'); $ret = fwrite($fds[$type], $msg); } return true; } public static function createPidFile($pid=0) { if (!$pid) { $pid = posix_getpid(); } if (file_exists(self::$pidFile)) { $fd = fopen(self::$pidFile, \'w\'); if (!$fd) { self::log(1, "fopen(".self::$pidFile.") failed");以上是关于Thrift(PHP)入门无错篇章的主要内容,如果未能解决你的问题,请参考以下文章
环境初始化 Build and Install the Apache Thrift IDL Compiler Install the Platform Development Tools(代码片段