00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00064
00065
00066
00067
00068
00069
00073 define(
"MEMCACHE_SERIALIZED", 1<<0);
00074
00078 define(
"MEMCACHE_COMPRESSED", 1<<1);
00079
00080
00081
00085 define(
"COMPRESSION_SAVINGS", 0.20);
00086
00087
00088
00089
00096 class memcached
00097 {
00098
00099
00100
00107 var
$stats;
00108
00109
00110
00111
00118 var
$_cache_sock;
00119
00126 var
$_debug;
00127
00134 var
$_host_dead;
00135
00142 var
$_have_zlib;
00143
00150 var
$_compress_enable;
00151
00158 var
$_compress_threshold;
00159
00166 var
$_persistant;
00167
00174 var
$_single_sock;
00175
00182 var
$_servers;
00183
00190 var
$_buckets;
00191
00198 var
$_bucketcount;
00199
00206 var
$_active;
00207
00208
00209
00210
00211
00212
00213
00222 function
memcached ($args)
00223 {
00224 $this->
set_servers(@$args['servers']);
00225 $this->_debug = @
$args['debug'];
00226 $this->stats = array();
00227 $this->_compress_threshold = @
$args['compress_threshold'];
00228 $this->_persistant = array_key_exists('persistant', $args) ? (@
$args['persistant']) :
false;
00229 $this->_compress_enable =
true;
00230 $this->_have_zlib = function_exists(
"gzcompress");
00231
00232 $this->_cache_sock = array();
00233 $this->_host_dead = array();
00234 }
00235
00236
00237
00238
00250 function
add ($key, $val, $exp = 0)
00251 {
00252
return $this->
_set('add', $key, $val, $exp);
00253 }
00254
00255
00256
00257
00267 function
decr ($key, $amt=1)
00268 {
00269
return $this->
_incrdecr('decr', $key, $amt);
00270 }
00271
00272
00273
00274
00284 function
delete ($key, $time = 0)
00285 {
00286
if (!$this->_active)
00287
return false;
00288
00289
$sock = $this->
get_sock($key);
00290
if (!is_resource($sock))
00291
return false;
00292
00293 $key = is_array($key) ? $key[1] : $key;
00294
00295 @$this->stats['
delete']++;
00296 $cmd =
"delete $key $time\r\n";
00297
if(!fwrite($sock, $cmd, strlen($cmd)))
00298 {
00299 $this->
_dead_sock($sock);
00300
return false;
00301 }
00302
$res = trim(fgets($sock));
00303
00304
if ($this->_debug)
00305 $this->
_debugprint(sprintf(
"MemCache: delete %s (%s)\n", $key, $res));
00306
00307
if (
$res ==
"DELETED")
00308
return true;
00309
return false;
00310 }
00311
00312
00313
00314
00320 function
disconnect_all ()
00321 {
00322 foreach ($this->_cache_sock as $sock)
00323 fclose($sock);
00324
00325 $this->_cache_sock = array();
00326 }
00327
00328
00329
00330
00338 function
enable_compress ($enable)
00339 {
00340 $this->_compress_enable = $enable;
00341 }
00342
00343
00344
00345
00351 function
forget_dead_hosts ()
00352 {
00353 $this->_host_dead = array();
00354 }
00355
00356
00357
00358
00367 function
get ($key)
00368 {
00369
if (!$this->_active)
00370
return false;
00371
00372
$sock = $this->
get_sock($key);
00373
00374
if (!is_resource($sock))
00375
return false;
00376
00377 @$this->stats['get']++;
00378
00379 $cmd =
"get $key\r\n";
00380
if (!fwrite($sock, $cmd, strlen($cmd)))
00381 {
00382 $this->
_dead_sock($sock);
00383
return false;
00384 }
00385
00386 $val = array();
00387 $this->
_load_items($sock, $val);
00388
00389
if ($this->_debug)
00390 foreach ($val as $k => $v)
00391 $this->
_debugprint(@sprintf(
"MemCache: sock %s got %s => %s\r\n", serialize($sock), $k, $v));
00392
00393
return @$val[$key];
00394 }
00395
00396
00397
00398
00407 function
get_multi ($keys)
00408 {
00409
if (!$this->_active)
00410
return false;
00411
00412 $this->stats['get_multi']++;
00413
00414 foreach ($keys as $key)
00415 {
00416
$sock = $this->
get_sock($key);
00417
if (!is_resource($sock))
continue;
00418 $key = is_array($key) ? $key[1] : $key;
00419
if (!isset($sock_keys[$sock]))
00420 {
00421 $sock_keys[
$sock] = array();
00422 $socks[] =
$sock;
00423 }
00424 $sock_keys[
$sock][] = $key;
00425 }
00426
00427
00428 foreach ($socks as $sock)
00429 {
00430 $cmd =
"get";
00431 foreach ($sock_keys[$sock] as $key)
00432 {
00433 $cmd .=
" ". $key;
00434 }
00435 $cmd .=
"\r\n";
00436
00437
if (fwrite($sock, $cmd, strlen($cmd)))
00438 {
00439 $gather[] =
$sock;
00440 }
else
00441 {
00442 $this->
_dead_sock($sock);
00443 }
00444 }
00445
00446
00447 $val = array();
00448 foreach ($gather as $sock)
00449 {
00450 $this->
_load_items($sock, $val);
00451 }
00452
00453
if ($this->_debug)
00454 foreach ($val as $k => $v)
00455 $this->
_debugprint(sprintf(
"MemCache: got %s => %s\r\n", $k, $v));
00456
00457
return $val;
00458 }
00459
00460
00461
00462
00472 function
incr ($key, $amt=1)
00473 {
00474
return $this->
_incrdecr('incr', $key, $amt);
00475 }
00476
00477
00478
00479
00490 function
replace ($key, $value, $exp=0)
00491 {
00492
return $this->
_set('replace', $key, $value, $exp);
00493 }
00494
00495
00496
00497
00514 function
run_command ($sock, $cmd)
00515 {
00516
if (!is_resource($sock))
00517
return array();
00518
00519
if (!fwrite($sock, $cmd, strlen($cmd)))
00520
return array();
00521
00522
while (
true)
00523 {
00524
$res = fgets($sock);
00525 $ret[] =
$res;
00526
if (preg_match('/^END/', $res))
00527
break;
00528
if (strlen($res) == 0)
00529
break;
00530 }
00531
return $ret;
00532 }
00533
00534
00535
00536
00548 function
set ($key, $value, $exp=0)
00549 {
00550
return $this->
_set('set', $key, $value, $exp);
00551 }
00552
00553
00554
00555
00563 function
set_compress_threshold ($thresh)
00564 {
00565 $this->_compress_threshold = $thresh;
00566 }
00567
00568
00569
00570
00580 function
set_debug ($dbg)
00581 {
00582 $this->_debug = $dbg;
00583 }
00584
00585
00586
00587
00597 function
set_servers ($list)
00598 {
00599 $this->_servers =
$list;
00600 $this->_active = count($list);
00601 $this->_buckets = null;
00602 $this->_bucketcount = 0;
00603
00604 $this->_single_sock = null;
00605
if ($this->_active == 1)
00606 $this->_single_sock = $this->_servers[0];
00607 }
00608
00609
00610
00611
00612
00613
00621 function
_close_sock ($sock)
00622 {
00623 $host = array_search($sock, $this->_cache_sock);
00624 fclose($this->_cache_sock[$host]);
00625 unset($this->_cache_sock[$host]);
00626 }
00627
00628
00629
00630
00641 function
_connect_sock (&$sock, $host, $timeout = 0.25)
00642 {
00643 list ($ip, $port) = explode(
":", $host);
00644
if ($this->_persistant == 1)
00645 {
00646
$sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout);
00647 }
else
00648 {
00649
$sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
00650 }
00651
00652
if (!
$sock)
00653
return false;
00654
return true;
00655 }
00656
00657
00658
00659
00667 function
_dead_sock ($sock)
00668 {
00669 $host = array_search($sock, $this->_cache_sock);
00670 @list ($ip, $port) = explode(
":", $host);
00671 $this->_host_dead[$ip] = time() + 30 + intval(rand(0, 10));
00672 $this->_host_dead[$host] = $this->_host_dead[$ip];
00673 unset($this->_cache_sock[$host]);
00674 }
00675
00676
00677
00678
00687 function
get_sock ($key)
00688 {
00689
if (!$this->_active)
00690
return false;
00691
00692
if ($this->_single_sock !== null)
00693
return $this->
sock_to_host($this->_single_sock);
00694
00695 $hv = is_array($key) ? intval($key[0]) : $this->
_hashfunc($key);
00696
00697
if ($this->_buckets === null)
00698 {
00699 foreach ($this->_servers as $v)
00700 {
00701
if (is_array($v))
00702 {
00703
for (
$i=0;
$i<$v[1];
$i++)
00704 $bu[] = $v[0];
00705 }
else
00706 {
00707 $bu[] = $v;
00708 }
00709 }
00710 $this->_buckets = $bu;
00711 $this->_bucketcount = count($bu);
00712 }
00713
00714 $realkey = is_array($key) ? $key[1] : $key;
00715
for ($tries = 0; $tries<20; $tries++)
00716 {
00717 $host = $this->_buckets[$hv % $this->_bucketcount];
00718
$sock = $this->
sock_to_host($host);
00719
if (is_resource($sock))
00720
return $sock;
00721 $hv += $this->
_hashfunc($tries . $realkey);
00722 }
00723
00724
return false;
00725 }
00726
00727
00728
00729
00738 function
_hashfunc ($key)
00739 {
00740
return crc32($key);
00741 }
00742
00743
00744
00745
00756 function
_incrdecr ($cmd, $key, $amt=1)
00757 {
00758
if (!$this->_active)
00759
return null;
00760
00761
$sock = $this->get_sock($key);
00762
if (!is_resource($sock))
00763
return null;
00764
00765 $key = is_array($key) ? $key[1] : $key;
00766 $this->stats[$cmd]++;
00767
if (!fwrite($sock,
"$cmd $key $amt\r\n"))
00768
return $this->_dead_sock($sock);
00769
00770 stream_set_timeout($sock, 1, 0);
00771
$line = fgets($sock);
00772
if (!preg_match('/^(\d+)/', $line, $match))
00773
return null;
00774
return $match[1];
00775 }
00776
00777
00778
00779
00788 function
_load_items ($sock, &$ret)
00789 {
00790
while (1)
00791 {
00792 $decl = fgets($sock);
00793
if ($decl ==
"END\r\n")
00794 {
00795
return true;
00796 } elseif (preg_match('/^VALUE (\S+) (\d+) (\d+)\r\
n$/', $decl, $match))
00797 {
00798 list($rkey, $flags, $len) = array($match[1], $match[2], $match[3]);
00799 $bneed = $len+2;
00800 $offset = 0;
00801
00802
while ($bneed > 0)
00803 {
00804 $data = fread($sock, $bneed);
00805
$n = strlen($
data);
00806
if (
$n == 0)
00807
break;
00808 $offset +=
$n;
00809 $bneed -=
$n;
00810 @$ret[$rkey] .= $data;
00811 }
00812
00813
if ($offset != $len+2)
00814 {
00815
00816
if ($this->_debug)
00817 $this->
_debugprint(sprintf(
"Something is borked! key %s expecting %d got %d length\n", $rkey, $len+2, $offset));
00818
00819 unset($ret[$rkey]);
00820 $this->_close_sock($sock);
00821
return false;
00822 }
00823
00824 $ret[$rkey] = rtrim($ret[$rkey]);
00825
00826
if ($this->_have_zlib && $flags &
MEMCACHE_COMPRESSED)
00827 $ret[$rkey] = gzuncompress($ret[$rkey]);
00828
00829
if ($flags &
MEMCACHE_SERIALIZED)
00830 $ret[$rkey] = unserialize($ret[$rkey]);
00831
00832 }
else
00833 {
00834 $this->
_debugprint(
"Error parsing memcached response\n");
00835
return 0;
00836 }
00837 }
00838 }
00839
00840
00841
00842
00854 function
_set ($cmd, $key, $val, $exp)
00855 {
00856
if (!$this->_active)
00857
return false;
00858
00859
$sock = $this->get_sock($key);
00860
if (!is_resource($sock))
00861
return false;
00862
00863 @$this->stats[$cmd]++;
00864
00865 $flags = 0;
00866
00867
if (!is_scalar($val))
00868 {
00869 $val = serialize($val);
00870 $flags |=
MEMCACHE_SERIALIZED;
00871
if ($this->_debug)
00872 $this->
_debugprint(sprintf(
"client: serializing data as it is not scalar\n"));
00873 }
00874
00875 $len = strlen($val);
00876
00877
if ($this->_have_zlib && $this->_compress_enable &&
00878 $this->_compress_threshold && $len >= $this->_compress_threshold)
00879 {
00880 $c_val = gzcompress($val, 9);
00881 $c_len = strlen($c_val);
00882
00883
if ($c_len < $len*(1 - COMPRESS_SAVINGS))
00884 {
00885
if ($this->_debug)
00886 $this->
_debugprint(sprintf(
"client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len));
00887 $val = $c_val;
00888 $len = $c_len;
00889 $flags |=
MEMCACHE_COMPRESSED;
00890 }
00891 }
00892
if (!fwrite($sock,
"$cmd $key $flags $exp $len\r\n$val\r\n"))
00893
return $this->_dead_sock($sock);
00894
00895
$line = trim(fgets($sock));
00896
00897
if ($this->_debug)
00898 {
00899
if ($flags &
MEMCACHE_COMPRESSED)
00900 $val = 'compressed
data';
00901 $this->
_debugprint(sprintf(
"MemCache: %s %s => %s (%s)\n", $cmd, $key, $val, $line));
00902 }
00903
if (
$line ==
"STORED")
00904
return true;
00905
return false;
00906 }
00907
00908
00909
00910
00919 function
sock_to_host ($host)
00920 {
00921
if (isset($this->_cache_sock[$host]))
00922
return $this->_cache_sock[$host];
00923
00924 $now = time();
00925 list ($ip, $port) = explode (
":", $host);
00926
if (isset($this->_host_dead[$host]) && $this->_host_dead[$host] > $now ||
00927 isset($this->_host_dead[$ip]) && $this->_host_dead[$ip] > $now)
00928
return null;
00929
00930
if (!$this->_connect_sock($sock, $host))
00931
return $this->_dead_sock($host);
00932
00933
00934 stream_set_write_buffer($sock, 0);
00935
00936 $this->_cache_sock[$host] =
$sock;
00937
00938
return $this->_cache_sock[$host];
00939 }
00940
00941 function
_debugprint($str){
00942 print($str);
00943 }
00944
00945
00946
00947
00948 }
00949
00950
00951 ?>