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.

560 lines
17 KiB

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