Ssdb.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: wangfanchang
  5. * Date: 18/1/10
  6. * Time: 下午2:13
  7. */
  8. namespace app\common\library;
  9. use think\Config;
  10. use think\Log;
  11. /**
  12. * Class Ssdb
  13. * @package app\common\library
  14. * @method bool hexists(string $name, string $key) 判断指定的 key 是否存在于 hashmap 中.
  15. * @method bool hset(string $name, string $key, string $value) 设置 hashmap 中指定 key 对应的值内容.
  16. * @method null|bool|string hget(string $name, string $key) 获取 hashmap 中指定 key 的值内容.
  17. * @method bool|array hkeys(string $name, string $key_start, string $key_end, int $limit) 列出 hashmap 中处于区间 (key_start, key_end] 的 key 列表.
  18. * @method bool hdel(string $name, string $key) 获取 hashmap 中的指定 key.
  19. * @method bool|array hscan(string $name, string $key_start, string $key_end, int $limit) 列出 hashmap 中处于区间 (key_start, key_end] 的 key-value 列表.
  20. * @method bool|array hgetall(string $name) 返回整个 hashmap.
  21. * @method bool|int hclear(string $name) 删除 hashmap 中的所有 key.
  22. * @method bool|int hsize(string $name) 返回 hashmap 中的元素个数.
  23. * @method bool|null|string get(string $name) 获取指定 key 的值内容.
  24. * @method bool exists(string $name) 判断指定的 key 是否存在.
  25. * @method bool|int incr(string $key, int $num = 1) 使 key 对应的值增加 num. 参数 num 可以为负数. 如果原来的值不是整数(字符串形式的整数), 它会被先转换成整数.
  26. * @method bool|int hincr(string $name, string $key, int $num = 1) 使 hashmap 中的 key 对应的值增加 num. 参数 num 可以为负数. 如果原来的值不是整数(字符串形式的整数), 它会被先转换成整数.
  27. * @method bool setx(string $key, string $value, int $ttl) 设置指定 key 的值内容, 同时设置存活时间.
  28. * @method bool|array scan(string $key_start, string $key_end, int $limit) 列出处于区间 (key_start, key_end] 的 key-value 列表.
  29. * @method bool|array hlist(string $name_start, string $name_end, int $limit) 列出名字处于区间 (name_start, name_end] 的 hashmap.
  30. * @method bool del(string $key) 删除指定的key
  31. * @method bool multi_del(array $keys) 批量删除一批 key 和其对应的值内容.
  32. * @method bool multi_hdel(string $name, array $keys) 批量删除 hashmap 中的 key.
  33. * @method mixed dbsize($first = [], $second = [])
  34. * @method mixed ping($first = [], $second = [])
  35. * @method mixed qset($first = [], $second = [])
  36. * @method mixed getbit($first = [], $second = [])
  37. * @method mixed setbit($first = [], $second = [])
  38. * @method mixed countbit($first = [], $second = [])
  39. * @method mixed strlen($first = [], $second = [])
  40. * @method mixed set($first = [], $second = [])
  41. * @method mixed setnx($first = [], $second = [])
  42. * @method mixed zset($first = [], $second = [])
  43. * @method mixed qpush($first = [], $second = [])
  44. * @method mixed qpush_front($first = [], $second = [])
  45. * @method mixed qpush_back($first = [], $second = [])
  46. * @method mixed qtrim_front($first = [], $second = [])
  47. * @method mixed qtrim_back($first = [], $second = [])
  48. * @method mixed zdel($first = [], $second = [])
  49. * @method mixed zsize($first = [], $second = [])
  50. * @method mixed qsize($first = [], $second = [])
  51. * @method mixed zclear($first = [], $second = [])
  52. * @method mixed qclear($first = [], $second = [])
  53. * @method mixed multi_set($first = [], $second = [])
  54. * @method mixed multi_hset($first = [], $second = [])
  55. * @method mixed multi_zset($first = [], $second = [])
  56. * @method mixed multi_zdel($first = [], $second = [])
  57. * @method mixed decr($first = [], $second = [])
  58. * @method mixed zincr($first = [], $second = [])
  59. * @method mixed zdecr($first = [], $second = [])
  60. * @method mixed hdecr($first = [], $second = [])
  61. * @method mixed zget($first = [], $second = [])
  62. * @method mixed zrank($first = [], $second = [])
  63. * @method mixed zrrank($first = [], $second = [])
  64. * @method mixed zcount($first = [], $second = [])
  65. * @method mixed zsum($first = [], $second = [])
  66. * @method mixed zremrangebyrank($first = [], $second = [])
  67. * @method mixed zremrangebyscore($first = [], $second = [])
  68. * @method mixed ttl($first = [], $second = [])
  69. * @method mixed expire($first = [], $second = [])
  70. * @method mixed zavg($first = [], $second = [])
  71. * @method mixed substr($first = [], $second = [])
  72. * @method mixed getset($first = [], $second = [])
  73. * @method mixed qget($first = [], $second = [])
  74. * @method mixed qfront($first = [], $second = [])
  75. * @method mixed qback($first = [], $second = [])
  76. * @method mixed qpop($first = [], $second = [])
  77. * @method mixed qpop_front($first = [], $second = [])
  78. * @method mixed qpop_back($first = [], $second = [])
  79. * @method mixed keys($first = [], $second = [])
  80. * @method mixed zkeys($first = [], $second = [])
  81. * @method mixed zlist($first = [], $second = [])
  82. * @method mixed qslice($first = [], $second = [])
  83. * @method mixed auth($first = [], $second = [])
  84. * @method mixed zexists($first = [], $second = [])
  85. * @method mixed multi_exists($first = [], $second = [])
  86. * @method mixed multi_hexists($first = [], $second = [])
  87. * @method mixed multi_zexists($first = [], $second = [])
  88. * @method mixed rscan($first = [], $second = [])
  89. * @method mixed zscan($first = [], $second = [])
  90. * @method mixed zrscan($first = [], $second = [])
  91. * @method mixed zrange($first = [], $second = [])
  92. * @method mixed zrrange($first = [], $second = [])
  93. * @method mixed hrscan($first = [], $second = [])
  94. * @method mixed multi_hsize($first = [], $second = [])
  95. * @method mixed multi_zsize($first = [], $second = [])
  96. * @method mixed multi_get($first = [], $second = [])
  97. * @method mixed multi_hget($first = [], $second = [])
  98. * @method mixed multi_zget($first = [], $second = [])
  99. * @method mixed zpop_front($first = [], $second = [])
  100. * @method mixed zpop_back($first = [], $second = [])
  101. */
  102. class Ssdb
  103. {
  104. /**
  105. * 单例对象
  106. */
  107. protected static $instance;
  108. /**
  109. * @var \SimpleSSDB
  110. */
  111. protected $handler;
  112. /**
  113. * SSDB 连接池
  114. * @var array
  115. */
  116. protected static $connections = [];
  117. /**
  118. * ssdb 区分链接的规则
  119. * @var array
  120. */
  121. protected static $rules = null;
  122. /**
  123. * 初始化
  124. *
  125. * @access public
  126. * @return Ssdb
  127. */
  128. public static function instance()
  129. {
  130. if (is_null(self::$instance)) {
  131. self::$instance = new static();
  132. }
  133. return self::$instance;
  134. }
  135. /**
  136. * @param string $key
  137. * @return SimpleSSDB
  138. */
  139. private static function getSsdb($key = '')
  140. {
  141. if (is_null(self::$rules)) {
  142. $rules = Config::get('ssdb.rules');
  143. $list = explode(';', $rules);
  144. foreach ($list as $item) {
  145. $con = explode('=', $item); // 0 编号 1 配置
  146. if (count($con) >= 2) {
  147. $c = explode(',', $con[1]); //规则
  148. foreach ($c as $it) {
  149. if ($it) {
  150. self::$rules[$con[0]][] = $it;
  151. }
  152. }
  153. }
  154. }
  155. }
  156. if ($key && count(self::$rules)) {
  157. foreach (self::$rules as $idx => $item) {
  158. foreach ($item as $v) {
  159. if (strpos($key, $v) === 0) { //查找到,且必须为第一个0
  160. if (isset(self::$connections[$idx])) {
  161. return self::$connections[$idx];
  162. }
  163. }
  164. }
  165. }
  166. }
  167. $default = Config::get('ssdb.default');
  168. if (isset(self::$connections[$default])) {
  169. return self::$connections[$default];
  170. } else {
  171. return self::$connections[0];
  172. }
  173. }
  174. /**
  175. * 构造函数
  176. *
  177. * @param array $options
  178. */
  179. public function __construct()
  180. {
  181. $config = Config::get('ssdb');
  182. $list = explode(';', $config['list']);
  183. foreach ($list as $item) {
  184. $conGroup = explode('=', $item);
  185. if (count($conGroup) >= 2) {
  186. $conGroupList = explode(':', $conGroup[1]);
  187. if (count($conGroupList) % 3 > 0) {
  188. Log::error('SSDB config error!冒号分隔的属性个数不是3的倍数');
  189. break;
  190. }
  191. for ($i = 0; $i < count($conGroupList); $i += 3) {
  192. $host = $conGroupList[$i];
  193. $port = $conGroupList[$i + 1];
  194. $pwd = $conGroupList[$i + 2];
  195. Log::info(sprintf('SSDB连接,host:%s,port:%s,pwd:%s'
  196. , $host, $port, $pwd));
  197. try {
  198. self::$connections[$conGroup[0]] = new SimpleSSDB($host, $port);
  199. self::$connections[$conGroup[0]]->easy();
  200. self::$connections[$conGroup[0]]->auth($pwd);
  201. break;
  202. } catch (\Exception $e) {
  203. Log::error(sprintf('SSDB连接失败,host:%s,port:%s,pwd:%s'
  204. , $host, $port, $pwd));
  205. continue;
  206. }
  207. }
  208. if (empty(self::$connections[$conGroup[0]])) {
  209. $errMsg = 'SSDB config error! num:' . $conGroup[0];
  210. Log::error($errMsg);
  211. throw new \Exception($errMsg);
  212. }
  213. }
  214. }
  215. if (count(self::$connections) == 0) {
  216. throw new \Exception('SSDB config error!');
  217. }
  218. }
  219. function __call($name, $arguments)
  220. {
  221. if (count($arguments)) {
  222. $ssdb = self::getSsdb($arguments[0]);
  223. } else {
  224. $ssdb = self::getSsdb();
  225. }
  226. return call_user_func_array([$ssdb, $name], $arguments);
  227. }
  228. }
  229. class SSDBException extends \Exception
  230. {
  231. }
  232. class SSDBTimeoutException extends SSDBException
  233. {
  234. }
  235. class SSDB_Response
  236. {
  237. public $cmd;
  238. public $code;
  239. public $data = null;
  240. public $message;
  241. function __construct($code='ok', $data_or_message=null){
  242. $this->code = $code;
  243. if($code == 'ok'){
  244. $this->data = $data_or_message;
  245. }else{
  246. $this->message = $data_or_message;
  247. }
  248. }
  249. function __toString(){
  250. if($this->code == 'ok'){
  251. $s = $this->data === null? '' : json_encode($this->data);
  252. }else{
  253. $s = $this->message;
  254. }
  255. return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s);
  256. }
  257. function ok(){
  258. return $this->code == 'ok';
  259. }
  260. function not_found(){
  261. return $this->code == 'not_found';
  262. }
  263. }
  264. class SimpleSSDB
  265. {
  266. private $debug = false;
  267. public $sock = null;
  268. private $_closed = false;
  269. private $recv_buf = '';
  270. private $_easy = false;
  271. public $last_resp = null;
  272. function __construct($host, $port, $timeout_ms=2000){
  273. $timeout_f = (float)$timeout_ms/1000;
  274. $this->sock = @stream_socket_client("$host:$port", $errno, $errstr, $timeout_f);
  275. if(!$this->sock){
  276. throw new SSDBException("$errno: $errstr");
  277. }
  278. $timeout_sec = intval($timeout_ms/1000);
  279. $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
  280. @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
  281. if(function_exists('stream_set_chunk_size')){
  282. @stream_set_chunk_size($this->sock, 1024 * 1024);
  283. }
  284. }
  285. function set_timeout($timeout_ms){
  286. $timeout_sec = intval($timeout_ms/1000);
  287. $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
  288. @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
  289. }
  290. /**
  291. * After this method invoked with yesno=true, all requesting methods
  292. * will not return a SSDB_Response object.
  293. * And some certain methods like get/zget will return false
  294. * when response is not ok(not_found, etc)
  295. */
  296. function easy(){
  297. $this->_easy = true;
  298. }
  299. function close(){
  300. if(!$this->_closed){
  301. @fclose($this->sock);
  302. $this->_closed = true;
  303. $this->sock = null;
  304. }
  305. }
  306. function closed(){
  307. return $this->_closed;
  308. }
  309. private $batch_mode = false;
  310. private $batch_cmds = array();
  311. function batch(){
  312. $this->batch_mode = true;
  313. $this->batch_cmds = array();
  314. return $this;
  315. }
  316. function multi(){
  317. return $this->batch();
  318. }
  319. function exec(){
  320. $ret = array();
  321. foreach($this->batch_cmds as $op){
  322. list($cmd, $params) = $op;
  323. $this->send_req($cmd, $params);
  324. }
  325. foreach($this->batch_cmds as $op){
  326. list($cmd, $params) = $op;
  327. $resp = $this->recv_resp($cmd, $params);
  328. $resp = $this->check_easy_resp($cmd, $resp);
  329. $ret[] = $resp;
  330. }
  331. $this->batch_mode = false;
  332. $this->batch_cmds = array();
  333. return $ret;
  334. }
  335. function request(){
  336. $args = func_get_args();
  337. $cmd = array_shift($args);
  338. return $this->__call($cmd, $args);
  339. }
  340. private $async_auth_password = null;
  341. function auth($password){
  342. $this->async_auth_password = $password;
  343. return null;
  344. }
  345. function __call($cmd, $params=array()){
  346. $cmd = strtolower($cmd);
  347. if($this->async_auth_password !== null){
  348. $pass = $this->async_auth_password;
  349. $this->async_auth_password = null;
  350. $auth = $this->__call('auth', array($pass));
  351. if($auth !== true){
  352. throw new Exception("Authentication failed");
  353. }
  354. }
  355. if($this->batch_mode){
  356. $this->batch_cmds[] = array($cmd, $params);
  357. return $this;
  358. }
  359. try{
  360. if($this->send_req($cmd, $params) === false){
  361. $resp = new SSDB_Response('error', 'send error');
  362. }else{
  363. $resp = $this->recv_resp($cmd, $params);
  364. }
  365. }catch(SSDBException $e){
  366. if($this->_easy){
  367. throw $e;
  368. }else{
  369. $resp = new SSDB_Response('error', $e->getMessage());
  370. }
  371. }
  372. if($resp->code == 'noauth'){
  373. $msg = $resp->message;
  374. throw new Exception($msg);
  375. }
  376. $resp = $this->check_easy_resp($cmd, $resp);
  377. return $resp;
  378. }
  379. private function check_easy_resp($cmd, $resp){
  380. $this->last_resp = $resp;
  381. if($this->_easy){
  382. if($resp->not_found()){
  383. return NULL;
  384. }else if(!$resp->ok() && !is_array($resp->data)){
  385. return false;
  386. }else{
  387. return $resp->data;
  388. }
  389. }else{
  390. $resp->cmd = $cmd;
  391. return $resp;
  392. }
  393. }
  394. function multi_set($kvs=array()){
  395. $args = array();
  396. foreach($kvs as $k=>$v){
  397. $args[] = $k;
  398. $args[] = $v;
  399. }
  400. return $this->__call(__FUNCTION__, $args);
  401. }
  402. function multi_hset($name, $kvs=array()){
  403. $args = array($name);
  404. foreach($kvs as $k=>$v){
  405. $args[] = $k;
  406. $args[] = $v;
  407. }
  408. return $this->__call(__FUNCTION__, $args);
  409. }
  410. function multi_zset($name, $kvs=array()){
  411. $args = array($name);
  412. foreach($kvs as $k=>$v){
  413. $args[] = $k;
  414. $args[] = $v;
  415. }
  416. return $this->__call(__FUNCTION__, $args);
  417. }
  418. function incr($key, $val=1){
  419. $args = func_get_args();
  420. return $this->__call(__FUNCTION__, $args);
  421. }
  422. function decr($key, $val=1){
  423. $args = func_get_args();
  424. return $this->__call(__FUNCTION__, $args);
  425. }
  426. function zincr($name, $key, $score=1){
  427. $args = func_get_args();
  428. return $this->__call(__FUNCTION__, $args);
  429. }
  430. function zdecr($name, $key, $score=1){
  431. $args = func_get_args();
  432. return $this->__call(__FUNCTION__, $args);
  433. }
  434. function zadd($key, $score, $value){
  435. $args = array($key, $value, $score);
  436. return $this->__call('zset', $args);
  437. }
  438. function zRevRank($name, $key){
  439. $args = func_get_args();
  440. return $this->__call("zrrank", $args);
  441. }
  442. function zRevRange($name, $offset, $limit){
  443. $args = func_get_args();
  444. return $this->__call("zrrange", $args);
  445. }
  446. function hincr($name, $key, $val=1){
  447. $args = func_get_args();
  448. return $this->__call(__FUNCTION__, $args);
  449. }
  450. function hdecr($name, $key, $val=1){
  451. $args = func_get_args();
  452. return $this->__call(__FUNCTION__, $args);
  453. }
  454. private function send_req($cmd, $params){
  455. $req = array($cmd);
  456. foreach($params as $p){
  457. if(is_array($p)){
  458. $req = array_merge($req, $p);
  459. }else{
  460. $req[] = $p;
  461. }
  462. }
  463. return $this->send($req);
  464. }
  465. private function recv_resp($cmd, $params){
  466. $resp = $this->recv();
  467. if($resp === false){
  468. return new SSDB_Response('error', 'Unknown error');
  469. }else if(!$resp){
  470. return new SSDB_Response('disconnected', 'Connection closed');
  471. }
  472. if($resp[0] == 'noauth'){
  473. $errmsg = isset($resp[1])? $resp[1] : '';
  474. return new SSDB_Response($resp[0], $errmsg);
  475. }
  476. switch($cmd){
  477. case 'dbsize':
  478. case 'ping':
  479. case 'qset':
  480. case 'getbit':
  481. case 'setbit':
  482. case 'countbit':
  483. case 'strlen':
  484. case 'set':
  485. case 'setx':
  486. case 'setnx':
  487. case 'zset':
  488. case 'hset':
  489. case 'qpush':
  490. case 'qpush_front':
  491. case 'qpush_back':
  492. case 'qtrim_front':
  493. case 'qtrim_back':
  494. case 'del':
  495. case 'zdel':
  496. case 'hdel':
  497. case 'hsize':
  498. case 'zsize':
  499. case 'qsize':
  500. case 'hclear':
  501. case 'zclear':
  502. case 'qclear':
  503. case 'multi_set':
  504. case 'multi_del':
  505. case 'multi_hset':
  506. case 'multi_hdel':
  507. case 'multi_zset':
  508. case 'multi_zdel':
  509. case 'incr':
  510. case 'decr':
  511. case 'zincr':
  512. case 'zdecr':
  513. case 'hincr':
  514. case 'hdecr':
  515. case 'zget':
  516. case 'zrank':
  517. case 'zrrank':
  518. case 'zcount':
  519. case 'zsum':
  520. case 'zremrangebyrank':
  521. case 'zremrangebyscore':
  522. case 'ttl':
  523. case 'expire':
  524. if($resp[0] == 'ok'){
  525. $val = isset($resp[1])? intval($resp[1]) : 0;
  526. return new SSDB_Response($resp[0], $val);
  527. }else{
  528. $errmsg = isset($resp[1])? $resp[1] : '';
  529. return new SSDB_Response($resp[0], $errmsg);
  530. }
  531. case 'zavg':
  532. if($resp[0] == 'ok'){
  533. $val = isset($resp[1])? floatval($resp[1]) : (float)0;
  534. return new SSDB_Response($resp[0], $val);
  535. }else{
  536. $errmsg = isset($resp[1])? $resp[1] : '';
  537. return new SSDB_Response($resp[0], $errmsg);
  538. }
  539. case 'get':
  540. case 'substr':
  541. case 'getset':
  542. case 'hget':
  543. case 'qget':
  544. case 'qfront':
  545. case 'qback':
  546. if($resp[0] == 'ok'){
  547. if(count($resp) == 2){
  548. return new SSDB_Response('ok', $resp[1]);
  549. }else{
  550. return new SSDB_Response('server_error', 'Invalid response');
  551. }
  552. }else{
  553. $errmsg = isset($resp[1])? $resp[1] : '';
  554. return new SSDB_Response($resp[0], $errmsg);
  555. }
  556. break;
  557. case 'qpop':
  558. case 'qpop_front':
  559. case 'qpop_back':
  560. if($resp[0] == 'ok'){
  561. $size = 1;
  562. if(isset($params[1])){
  563. $size = intval($params[1]);
  564. }
  565. if($size <= 1){
  566. if(count($resp) == 2){
  567. return new SSDB_Response('ok', $resp[1]);
  568. }else{
  569. return new SSDB_Response('server_error', 'Invalid response');
  570. }
  571. }else{
  572. $data = array_slice($resp, 1);
  573. return new SSDB_Response('ok', $data);
  574. }
  575. }else{
  576. $errmsg = isset($resp[1])? $resp[1] : '';
  577. return new SSDB_Response($resp[0], $errmsg);
  578. }
  579. break;
  580. case 'keys':
  581. case 'zkeys':
  582. case 'hkeys':
  583. case 'hlist':
  584. case 'zlist':
  585. case 'qslice':
  586. if($resp[0] == 'ok'){
  587. $data = array();
  588. if($resp[0] == 'ok'){
  589. $data = array_slice($resp, 1);
  590. }
  591. return new SSDB_Response($resp[0], $data);
  592. }else{
  593. $errmsg = isset($resp[1])? $resp[1] : '';
  594. return new SSDB_Response($resp[0], $errmsg);
  595. }
  596. case 'auth':
  597. case 'exists':
  598. case 'hexists':
  599. case 'zexists':
  600. if($resp[0] == 'ok'){
  601. if(count($resp) == 2){
  602. return new SSDB_Response('ok', (bool)$resp[1]);
  603. }else{
  604. return new SSDB_Response('server_error', 'Invalid response');
  605. }
  606. }else{
  607. $errmsg = isset($resp[1])? $resp[1] : '';
  608. return new SSDB_Response($resp[0], $errmsg);
  609. }
  610. break;
  611. case 'multi_exists':
  612. case 'multi_hexists':
  613. case 'multi_zexists':
  614. if($resp[0] == 'ok'){
  615. if(count($resp) % 2 == 1){
  616. $data = array();
  617. for($i=1; $i<count($resp); $i+=2){
  618. $data[$resp[$i]] = (bool)$resp[$i + 1];
  619. }
  620. return new SSDB_Response('ok', $data);
  621. }else{
  622. return new SSDB_Response('server_error', 'Invalid response');
  623. }
  624. }else{
  625. $errmsg = isset($resp[1])? $resp[1] : '';
  626. return new SSDB_Response($resp[0], $errmsg);
  627. }
  628. break;
  629. case 'scan':
  630. case 'rscan':
  631. case 'zscan':
  632. case 'zrscan':
  633. case 'zrange':
  634. case 'zrrange':
  635. case 'hscan':
  636. case 'hrscan':
  637. case 'hgetall':
  638. case 'multi_hsize':
  639. case 'multi_zsize':
  640. case 'multi_get':
  641. case 'multi_hget':
  642. case 'multi_zget':
  643. case 'zpop_front':
  644. case 'zpop_back':
  645. if($resp[0] == 'ok'){
  646. if(count($resp) % 2 == 1){
  647. $data = array();
  648. for($i=1; $i<count($resp); $i+=2){
  649. if($cmd[0] == 'z'){
  650. $data[$resp[$i]] = intval($resp[$i + 1]);
  651. }else{
  652. $data[$resp[$i]] = $resp[$i + 1];
  653. }
  654. }
  655. return new SSDB_Response('ok', $data);
  656. }else{
  657. return new SSDB_Response('server_error', 'Invalid response');
  658. }
  659. }else{
  660. $errmsg = isset($resp[1])? $resp[1] : '';
  661. return new SSDB_Response($resp[0], $errmsg);
  662. }
  663. break;
  664. default:
  665. return new SSDB_Response($resp[0], array_slice($resp, 1));
  666. }
  667. return new SSDB_Response('error', 'Unknown command: $cmd');
  668. }
  669. function send($data){
  670. $ps = array();
  671. foreach($data as $p){
  672. $ps[] = strlen($p);
  673. $ps[] = $p;
  674. }
  675. $s = join("\n", $ps) . "\n\n";
  676. if($this->debug){
  677. echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n";
  678. }
  679. try{
  680. while(true){
  681. $ret = @fwrite($this->sock, $s);
  682. if($ret === false || $ret === 0){
  683. $this->close();
  684. throw new SSDBException('Connection lost');
  685. }
  686. $s = substr($s, $ret);
  687. if(strlen($s) == 0){
  688. break;
  689. }
  690. @fflush($this->sock);
  691. }
  692. }catch(Exception $e){
  693. $this->close();
  694. throw new SSDBException($e->getMessage());
  695. }
  696. return $ret;
  697. }
  698. function recv(){
  699. $this->step = self::STEP_SIZE;
  700. while(true){
  701. $ret = $this->parse();
  702. if($ret === null){
  703. try{
  704. $data = @fread($this->sock, 1024 * 1024);
  705. if($this->debug){
  706. echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n";
  707. }
  708. }catch(Exception $e){
  709. $data = '';
  710. }
  711. if($data === false || $data === ''){
  712. if(feof($this->sock)){
  713. $this->close();
  714. throw new SSDBException('Connection lost');
  715. }else{
  716. throw new SSDBTimeoutException('Connection timeout');
  717. }
  718. }
  719. $this->recv_buf .= $data;
  720. # echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n";
  721. }else{
  722. return $ret;
  723. }
  724. }
  725. }
  726. const STEP_SIZE = 0;
  727. const STEP_DATA = 1;
  728. public $resp = array();
  729. public $step;
  730. public $block_size;
  731. private function parse(){
  732. $spos = 0;
  733. $epos = 0;
  734. $buf_size = strlen($this->recv_buf);
  735. // performance issue for large reponse
  736. //$this->recv_buf = ltrim($this->recv_buf);
  737. while(true){
  738. $spos = $epos;
  739. if($this->step === self::STEP_SIZE){
  740. $epos = strpos($this->recv_buf, "\n", $spos);
  741. if($epos === false){
  742. break;
  743. }
  744. $epos += 1;
  745. $line = substr($this->recv_buf, $spos, $epos - $spos);
  746. $spos = $epos;
  747. $line = trim($line);
  748. if(strlen($line) == 0){ // head end
  749. $this->recv_buf = substr($this->recv_buf, $spos);
  750. $ret = $this->resp;
  751. $this->resp = array();
  752. return $ret;
  753. }
  754. $this->block_size = intval($line);
  755. $this->step = self::STEP_DATA;
  756. }
  757. if($this->step === self::STEP_DATA){
  758. $epos = $spos + $this->block_size;
  759. if($epos <= $buf_size){
  760. $n = strpos($this->recv_buf, "\n", $epos);
  761. if($n !== false){
  762. $data = substr($this->recv_buf, $spos, $epos - $spos);
  763. $this->resp[] = $data;
  764. $epos = $n + 1;
  765. $this->step = self::STEP_SIZE;
  766. continue;
  767. }
  768. }
  769. break;
  770. }
  771. }
  772. // packet not ready
  773. if($spos > 0){
  774. $this->recv_buf = substr($this->recv_buf, $spos);
  775. }
  776. return null;
  777. }
  778. }