链街Dcat后台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

617 lines
14 KiB

  1. <?php
  2. /**
  3. * Copyright (c) 2012, ideawu
  4. * All rights reserved.
  5. * @author: ideawu
  6. * @link: http://www.ideawu.com/
  7. *
  8. * SSDB PHP client SDK.
  9. */
  10. class SSDBException extends Exception
  11. {
  12. }
  13. class SSDBTimeoutException extends SSDBException
  14. {
  15. }
  16. /**
  17. * All methods(except *exists) returns false on error,
  18. * so one should use Identical(if($ret === false)) to test the return value.
  19. */
  20. class SimpleSSDB extends SSDB
  21. {
  22. function __construct($host, $port, $timeout_ms=2000){
  23. parent::__construct($host, $port, $timeout_ms);
  24. $this->easy();
  25. }
  26. }
  27. class SSDB_Response
  28. {
  29. public $cmd;
  30. public $code;
  31. public $data = null;
  32. public $message;
  33. function __construct($code='ok', $data_or_message=null){
  34. $this->code = $code;
  35. if($code == 'ok'){
  36. $this->data = $data_or_message;
  37. }else{
  38. $this->message = $data_or_message;
  39. }
  40. }
  41. function __toString(){
  42. if($this->code == 'ok'){
  43. $s = $this->data === null? '' : json_encode($this->data);
  44. }else{
  45. $s = $this->message;
  46. }
  47. return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s);
  48. }
  49. function ok(){
  50. return $this->code == 'ok';
  51. }
  52. function not_found(){
  53. return $this->code == 'not_found';
  54. }
  55. }
  56. // Depricated, use SimpleSSDB instead!
  57. class SSDB
  58. {
  59. private $debug = false;
  60. public $sock = null;
  61. private $_closed = false;
  62. private $recv_buf = '';
  63. private $_easy = false;
  64. public $last_resp = null;
  65. function __construct($host, $port, $timeout_ms=2000){
  66. $timeout_f = (float)$timeout_ms/1000;
  67. $this->sock = @stream_socket_client("$host:$port", $errno, $errstr, $timeout_f);
  68. if(!$this->sock){
  69. throw new SSDBException("$errno: $errstr");
  70. }
  71. $timeout_sec = intval($timeout_ms/1000);
  72. $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
  73. @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
  74. if(function_exists('stream_set_chunk_size')){
  75. @stream_set_chunk_size($this->sock, 1024 * 1024);
  76. }
  77. }
  78. function set_timeout($timeout_ms){
  79. $timeout_sec = intval($timeout_ms/1000);
  80. $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
  81. @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
  82. }
  83. /**
  84. * After this method invoked with yesno=true, all requesting methods
  85. * will not return a SSDB_Response object.
  86. * And some certain methods like get/zget will return false
  87. * when response is not ok(not_found, etc)
  88. */
  89. function easy(){
  90. $this->_easy = true;
  91. }
  92. function close(){
  93. if(!$this->_closed){
  94. @fclose($this->sock);
  95. $this->_closed = true;
  96. $this->sock = null;
  97. }
  98. }
  99. function closed(){
  100. return $this->_closed;
  101. }
  102. private $batch_mode = false;
  103. private $batch_cmds = array();
  104. function batch(){
  105. $this->batch_mode = true;
  106. $this->batch_cmds = array();
  107. return $this;
  108. }
  109. function multi(){
  110. return $this->batch();
  111. }
  112. function exec(){
  113. $ret = array();
  114. foreach($this->batch_cmds as $op){
  115. list($cmd, $params) = $op;
  116. $this->send_req($cmd, $params);
  117. }
  118. foreach($this->batch_cmds as $op){
  119. list($cmd, $params) = $op;
  120. $resp = $this->recv_resp($cmd, $params);
  121. $resp = $this->check_easy_resp($cmd, $resp);
  122. $ret[] = $resp;
  123. }
  124. $this->batch_mode = false;
  125. $this->batch_cmds = array();
  126. return $ret;
  127. }
  128. function request(){
  129. $args = func_get_args();
  130. $cmd = array_shift($args);
  131. return $this->__call($cmd, $args);
  132. }
  133. private $async_auth_password = null;
  134. function auth($password){
  135. $this->async_auth_password = $password;
  136. return null;
  137. }
  138. function __call($cmd, $params=array()){
  139. $cmd = strtolower($cmd);
  140. if($this->async_auth_password !== null){
  141. $pass = $this->async_auth_password;
  142. $this->async_auth_password = null;
  143. $auth = $this->__call('auth', array($pass));
  144. if($auth !== true){
  145. throw new Exception("Authentication failed");
  146. }
  147. }
  148. if($this->batch_mode){
  149. $this->batch_cmds[] = array($cmd, $params);
  150. return $this;
  151. }
  152. try{
  153. if($this->send_req($cmd, $params) === false){
  154. $resp = new SSDB_Response('error', 'send error');
  155. }else{
  156. $resp = $this->recv_resp($cmd, $params);
  157. }
  158. }catch(SSDBException $e){
  159. if($this->_easy){
  160. throw $e;
  161. }else{
  162. $resp = new SSDB_Response('error', $e->getMessage());
  163. }
  164. }
  165. if($resp->code == 'noauth'){
  166. $msg = $resp->message;
  167. throw new Exception($msg);
  168. }
  169. $resp = $this->check_easy_resp($cmd, $resp);
  170. return $resp;
  171. }
  172. private function check_easy_resp($cmd, $resp){
  173. $this->last_resp = $resp;
  174. if($this->_easy){
  175. if($resp->not_found()){
  176. return NULL;
  177. }else if(!$resp->ok() && !is_array($resp->data)){
  178. return false;
  179. }else{
  180. return $resp->data;
  181. }
  182. }else{
  183. $resp->cmd = $cmd;
  184. return $resp;
  185. }
  186. }
  187. function multi_set($kvs=array()){
  188. $args = array();
  189. foreach($kvs as $k=>$v){
  190. $args[] = $k;
  191. $args[] = $v;
  192. }
  193. return $this->__call(__FUNCTION__, $args);
  194. }
  195. function multi_hset($name, $kvs=array()){
  196. $args = array($name);
  197. foreach($kvs as $k=>$v){
  198. $args[] = $k;
  199. $args[] = $v;
  200. }
  201. return $this->__call(__FUNCTION__, $args);
  202. }
  203. function multi_zset($name, $kvs=array()){
  204. $args = array($name);
  205. foreach($kvs as $k=>$v){
  206. $args[] = $k;
  207. $args[] = $v;
  208. }
  209. return $this->__call(__FUNCTION__, $args);
  210. }
  211. function incr($key, $val=1){
  212. $args = func_get_args();
  213. return $this->__call(__FUNCTION__, $args);
  214. }
  215. function decr($key, $val=1){
  216. $args = func_get_args();
  217. return $this->__call(__FUNCTION__, $args);
  218. }
  219. function zincr($name, $key, $score=1){
  220. $args = func_get_args();
  221. return $this->__call(__FUNCTION__, $args);
  222. }
  223. function zdecr($name, $key, $score=1){
  224. $args = func_get_args();
  225. return $this->__call(__FUNCTION__, $args);
  226. }
  227. function zadd($key, $score, $value){
  228. $args = array($key, $value, $score);
  229. return $this->__call('zset', $args);
  230. }
  231. function zRevRank($name, $key){
  232. $args = func_get_args();
  233. return $this->__call("zrrank", $args);
  234. }
  235. function zRevRange($name, $offset, $limit){
  236. $args = func_get_args();
  237. return $this->__call("zrrange", $args);
  238. }
  239. function hincr($name, $key, $val=1){
  240. $args = func_get_args();
  241. return $this->__call(__FUNCTION__, $args);
  242. }
  243. function hdecr($name, $key, $val=1){
  244. $args = func_get_args();
  245. return $this->__call(__FUNCTION__, $args);
  246. }
  247. private function send_req($cmd, $params){
  248. $req = array($cmd);
  249. foreach($params as $p){
  250. if(is_array($p)){
  251. $req = array_merge($req, $p);
  252. }else{
  253. $req[] = $p;
  254. }
  255. }
  256. return $this->send($req);
  257. }
  258. private function recv_resp($cmd, $params){
  259. $resp = $this->recv();
  260. if($resp === false){
  261. return new SSDB_Response('error', 'Unknown error');
  262. }else if(!$resp){
  263. return new SSDB_Response('disconnected', 'Connection closed');
  264. }
  265. if($resp[0] == 'noauth'){
  266. $errmsg = isset($resp[1])? $resp[1] : '';
  267. return new SSDB_Response($resp[0], $errmsg);
  268. }
  269. switch($cmd){
  270. case 'dbsize':
  271. case 'ping':
  272. case 'qset':
  273. case 'getbit':
  274. case 'setbit':
  275. case 'countbit':
  276. case 'strlen':
  277. case 'set':
  278. case 'setx':
  279. case 'setnx':
  280. case 'zset':
  281. case 'hset':
  282. case 'qpush':
  283. case 'qpush_front':
  284. case 'qpush_back':
  285. case 'qtrim_front':
  286. case 'qtrim_back':
  287. case 'del':
  288. case 'zdel':
  289. case 'hdel':
  290. case 'hsize':
  291. case 'zsize':
  292. case 'qsize':
  293. case 'hclear':
  294. case 'zclear':
  295. case 'qclear':
  296. case 'multi_set':
  297. case 'multi_del':
  298. case 'multi_hset':
  299. case 'multi_hdel':
  300. case 'multi_zset':
  301. case 'multi_zdel':
  302. case 'incr':
  303. case 'decr':
  304. case 'zincr':
  305. case 'zdecr':
  306. case 'hincr':
  307. case 'hdecr':
  308. case 'zget':
  309. case 'zrank':
  310. case 'zrrank':
  311. case 'zcount':
  312. case 'zsum':
  313. case 'zremrangebyrank':
  314. case 'zremrangebyscore':
  315. case 'ttl':
  316. case 'expire':
  317. if($resp[0] == 'ok'){
  318. $val = isset($resp[1])? intval($resp[1]) : 0;
  319. return new SSDB_Response($resp[0], $val);
  320. }else{
  321. $errmsg = isset($resp[1])? $resp[1] : '';
  322. return new SSDB_Response($resp[0], $errmsg);
  323. }
  324. case 'zavg':
  325. if($resp[0] == 'ok'){
  326. $val = isset($resp[1])? floatval($resp[1]) : (float)0;
  327. return new SSDB_Response($resp[0], $val);
  328. }else{
  329. $errmsg = isset($resp[1])? $resp[1] : '';
  330. return new SSDB_Response($resp[0], $errmsg);
  331. }
  332. case 'get':
  333. case 'substr':
  334. case 'getset':
  335. case 'hget':
  336. case 'qget':
  337. case 'qfront':
  338. case 'qback':
  339. if($resp[0] == 'ok'){
  340. if(count($resp) == 2){
  341. return new SSDB_Response('ok', $resp[1]);
  342. }else{
  343. return new SSDB_Response('server_error', 'Invalid response');
  344. }
  345. }else{
  346. $errmsg = isset($resp[1])? $resp[1] : '';
  347. return new SSDB_Response($resp[0], $errmsg);
  348. }
  349. break;
  350. case 'qpop':
  351. case 'qpop_front':
  352. case 'qpop_back':
  353. if($resp[0] == 'ok'){
  354. $size = 1;
  355. if(isset($params[1])){
  356. $size = intval($params[1]);
  357. }
  358. if($size <= 1){
  359. if(count($resp) == 2){
  360. return new SSDB_Response('ok', $resp[1]);
  361. }else{
  362. return new SSDB_Response('server_error', 'Invalid response');
  363. }
  364. }else{
  365. $data = array_slice($resp, 1);
  366. return new SSDB_Response('ok', $data);
  367. }
  368. }else{
  369. $errmsg = isset($resp[1])? $resp[1] : '';
  370. return new SSDB_Response($resp[0], $errmsg);
  371. }
  372. break;
  373. case 'keys':
  374. case 'zkeys':
  375. case 'hkeys':
  376. case 'hlist':
  377. case 'zlist':
  378. case 'qslice':
  379. if($resp[0] == 'ok'){
  380. $data = array();
  381. if($resp[0] == 'ok'){
  382. $data = array_slice($resp, 1);
  383. }
  384. return new SSDB_Response($resp[0], $data);
  385. }else{
  386. $errmsg = isset($resp[1])? $resp[1] : '';
  387. return new SSDB_Response($resp[0], $errmsg);
  388. }
  389. case 'auth':
  390. case 'exists':
  391. case 'hexists':
  392. case 'zexists':
  393. if($resp[0] == 'ok'){
  394. if(count($resp) == 2){
  395. return new SSDB_Response('ok', (bool)$resp[1]);
  396. }else{
  397. return new SSDB_Response('server_error', 'Invalid response');
  398. }
  399. }else{
  400. $errmsg = isset($resp[1])? $resp[1] : '';
  401. return new SSDB_Response($resp[0], $errmsg);
  402. }
  403. break;
  404. case 'multi_exists':
  405. case 'multi_hexists':
  406. case 'multi_zexists':
  407. if($resp[0] == 'ok'){
  408. if(count($resp) % 2 == 1){
  409. $data = array();
  410. for($i=1; $i<count($resp); $i+=2){
  411. $data[$resp[$i]] = (bool)$resp[$i + 1];
  412. }
  413. return new SSDB_Response('ok', $data);
  414. }else{
  415. return new SSDB_Response('server_error', 'Invalid response');
  416. }
  417. }else{
  418. $errmsg = isset($resp[1])? $resp[1] : '';
  419. return new SSDB_Response($resp[0], $errmsg);
  420. }
  421. break;
  422. case 'scan':
  423. case 'rscan':
  424. case 'zscan':
  425. case 'zrscan':
  426. case 'zrange':
  427. case 'zrrange':
  428. case 'hscan':
  429. case 'hrscan':
  430. case 'hgetall':
  431. case 'multi_hsize':
  432. case 'multi_zsize':
  433. case 'multi_get':
  434. case 'multi_hget':
  435. case 'multi_zget':
  436. case 'zpop_front':
  437. case 'zpop_back':
  438. if($resp[0] == 'ok'){
  439. if(count($resp) % 2 == 1){
  440. $data = array();
  441. for($i=1; $i<count($resp); $i+=2){
  442. if($cmd[0] == 'z'){
  443. $data[$resp[$i]] = intval($resp[$i + 1]);
  444. }else{
  445. $data[$resp[$i]] = $resp[$i + 1];
  446. }
  447. }
  448. return new SSDB_Response('ok', $data);
  449. }else{
  450. return new SSDB_Response('server_error', 'Invalid response');
  451. }
  452. }else{
  453. $errmsg = isset($resp[1])? $resp[1] : '';
  454. return new SSDB_Response($resp[0], $errmsg);
  455. }
  456. break;
  457. default:
  458. return new SSDB_Response($resp[0], array_slice($resp, 1));
  459. }
  460. return new SSDB_Response('error', 'Unknown command: $cmd');
  461. }
  462. function send($data){
  463. $ps = array();
  464. foreach($data as $p){
  465. $ps[] = strlen($p);
  466. $ps[] = $p;
  467. }
  468. $s = join("\n", $ps) . "\n\n";
  469. if($this->debug){
  470. echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n";
  471. }
  472. try{
  473. while(true){
  474. $ret = @fwrite($this->sock, $s);
  475. if($ret === false || $ret === 0){
  476. $this->close();
  477. throw new SSDBException('Connection lost');
  478. }
  479. $s = substr($s, $ret);
  480. if(strlen($s) == 0){
  481. break;
  482. }
  483. @fflush($this->sock);
  484. }
  485. }catch(Exception $e){
  486. $this->close();
  487. throw new SSDBException($e->getMessage());
  488. }
  489. return $ret;
  490. }
  491. function recv(){
  492. $this->step = self::STEP_SIZE;
  493. while(true){
  494. $ret = $this->parse();
  495. if($ret === null){
  496. try{
  497. $data = @fread($this->sock, 1024 * 1024);
  498. if($this->debug){
  499. echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n";
  500. }
  501. }catch(Exception $e){
  502. $data = '';
  503. }
  504. if($data === false || $data === ''){
  505. if(feof($this->sock)){
  506. $this->close();
  507. throw new SSDBException('Connection lost');
  508. }else{
  509. throw new SSDBTimeoutException('Connection timeout');
  510. }
  511. }
  512. $this->recv_buf .= $data;
  513. # echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n";
  514. }else{
  515. return $ret;
  516. }
  517. }
  518. }
  519. const STEP_SIZE = 0;
  520. const STEP_DATA = 1;
  521. public $resp = array();
  522. public $step;
  523. public $block_size;
  524. private function parse(){
  525. $spos = 0;
  526. $epos = 0;
  527. $buf_size = strlen($this->recv_buf);
  528. // performance issue for large reponse
  529. //$this->recv_buf = ltrim($this->recv_buf);
  530. while(true){
  531. $spos = $epos;
  532. if($this->step === self::STEP_SIZE){
  533. $epos = strpos($this->recv_buf, "\n", $spos);
  534. if($epos === false){
  535. break;
  536. }
  537. $epos += 1;
  538. $line = substr($this->recv_buf, $spos, $epos - $spos);
  539. $spos = $epos;
  540. $line = trim($line);
  541. if(strlen($line) == 0){ // head end
  542. $this->recv_buf = substr($this->recv_buf, $spos);
  543. $ret = $this->resp;
  544. $this->resp = array();
  545. return $ret;
  546. }
  547. $this->block_size = intval($line);
  548. $this->step = self::STEP_DATA;
  549. }
  550. if($this->step === self::STEP_DATA){
  551. $epos = $spos + $this->block_size;
  552. if($epos <= $buf_size){
  553. $n = strpos($this->recv_buf, "\n", $epos);
  554. if($n !== false){
  555. $data = substr($this->recv_buf, $spos, $epos - $spos);
  556. $this->resp[] = $data;
  557. $epos = $n + 1;
  558. $this->step = self::STEP_SIZE;
  559. continue;
  560. }
  561. }
  562. break;
  563. }
  564. }
  565. // packet not ready
  566. if($spos > 0){
  567. $this->recv_buf = substr($this->recv_buf, $spos);
  568. }
  569. return null;
  570. }
  571. }