[转]MemCached的PHP客户端操作类一



MemCached的PHP客户端操作类一

<?php
//
//+---------------------------------------------------------------------------+
//|memcachedclient,PHP|
//+---------------------------------------------------------------------------+
//|Copyright(c)2003RyanT.Dean<rtdean@cytherianage.net>|
//|Allrightsreserved.|
//||
//|Redistributionanduseinsourceandbinaryforms,withorwithout|
//|modification,arepermittedprovidedthatthefollowingconditions|
//|aremet:|
//||
//|1.Redistributionsofsourcecodemustretaintheabovecopyright|
//|notice,thislistofconditionsandthefollowingdisclaimer.|
//|2.Redistributionsinbinaryformmustreproducetheabovecopyright|
//|notice,thislistofconditionsandthefollowingdisclaimerinthe|
//|documentationand/orothermaterialsprovidedwiththedistribution.|
//||
//|THISSOFTWAREISPROVIDEDBYTHEAUTHOR``ASIS''ANDANYEXPRESSOR|
//|IMPLIEDWARRANTIES,INCLUDING,BUTNOTLIMITEDTO,THEIMPLIEDWARRANTIES|
//|OFMERCHANTABILITYANDFITNESSFORAPARTICULARPURPOSEAREDISCLAIMED.|
//|INNOEVENTSHALLTHEAUTHORBELIABLEFORANYDIRECT,INDIRECT,|
//|INCIDENTAL,SPECIAL,EXEMPLARY,ORCONSEQUENTIALDAMAGES(INCLUDING,BUT|
//|NOTLIMITEDTO,PROCUREMENTOFSUBSTITUTEGOODSORSERVICES;LOSSOFUSE,|
//|DATA,ORPROFITS;ORBUSINESSINTERRUPTION)HOWEVERCAUSEDANDONANY|
//|THEORYOFLIABILITY,WHETHERINCONTRACT,STRICTLIABILITY,ORTORT|
//|(INCLUDINGNEGLIGENCEOROTHERWISE)ARISINGINANYWAYOUTOFTHEUSEOF|
//|THISSOFTWARE,EVENIFADVISEDOFTHEPOSSIBILITYOFSUCHDAMAGE.|
//+---------------------------------------------------------------------------+
//|Author:RyanT.Dean<rtdean@cytherianage.net>|
//|HeavilyinfluencedbythePerlmemcachedclientbyBradFitzpatrick.|
//|PermissiongrantedbyBradFitzpatrickforrelicenseofportedPerl|
//|clientlogicunder2-clauseBSDlicense.|
//+---------------------------------------------------------------------------+
//
//$TCAnet$
//

/**
*ThisisthePHPclientformemcached-adistributedmemorycachedaemon.
*Moreinformationisavailableathttp://www.danga.com/memcached/
*
*Usageexample:
*
*require_once'memcached.php';
*
*$mc=newmemcached(array(
*'servers'=>array('127.0.0.1:10000',
*array('192.0.0.1:10010',2),
*'127.0.0.1:10020'),
*'debug'=>false,
*'compress_threshold'=>10240,
*'persistant'=>true));
*
*$mc->add('key',array('some','array'));
*$mc->replace('key','somerandomstring');
*$val=$mc->get('key');
*
*@authorRyanT.Dean<rtdean@cytherianage.net>
*@packagememcached-client
*@version0.1.2
*/

//{{{requirements
//}}}

//{{{constants
//{{{flags

/**
*Flag:indicatesdataisserialized
*/
define("MEMCACHE_SERIALIZED",1<<0);

/**
*Flag:indicatesdataiscompressed
*/
define("MEMCACHE_COMPRESSED",1<<1);

//}}}

/**
*Minimumsavingstostoredatacompressed
*/
define("COMPRESSION_SAVINGS",0.20);

//}}}

//{{{classmemcached
/**
*memcachedclientclassimplementedusing(p)fsockopen()
*
*@authorRyanT.Dean<rtdean@cytherianage.net>
*@packagememcached-client
*/
classmemcached
{
//{{{properties
//{{{public

/**
*Commandstatistics
*
*@vararray
*@accesspublic
*/
var$stats;

//}}}
//{{{private

/**
*CachedSocketsthatareconnected
*
*@vararray
*@accessprivate
*/
var$_cache_sock;

/**
*Currentdebugstatus;0-noneto9-profiling
*
*@varboolean
*@accessprivate
*/
var$_debug;

/**
*Deadhosts,assocarray,'host'=>'unixtimewhenoktocheckagain'
*
*@vararray
*@accessprivate
*/
var$_host_dead;

/**
*Iscompressionavailable?
*
*@varboolean
*@accessprivate
*/
var$_have_zlib;

/**
*Dowewanttousecompression?
*
*@varboolean
*@accessprivate
*/
var$_compress_enable;

/**
*Athowmanybytesshouldwecompress?
*
*@varinterger
*@accessprivate
*/
var$_compress_threshold;

/**
*Areweusingpersistantlinks?
*
*@varboolean
*@accessprivate
*/
var$_persistant;

/**
*Ifonlyusingoneserver;containsip:porttoconnectto
*
*@varstring
*@accessprivate
*/
var$_single_sock;

/**
*Arraycontainingip:portorarray(ip:port,weight)
*
*@vararray
*@accessprivate
*/
var$_servers;

/**
*Ourbitbuckets
*
*@vararray
*@accessprivate
*/
var$_buckets;

/**
*Total#ofbitbucketswehave
*
*@varinterger
*@accessprivate
*/
var$_bucketcount;

/**
*#oftotalserverswehave
*
*@varinterger
*@accessprivate
*/
var$_active;

//}}}
//}}}
//{{{methods
//{{{publicfunctions
//{{{memcached()

/**
*Memcacheinitializer
*
*@paramarray$argsAssociativearrayofsettings
*
*@returnmixed
*@accesspublic
*/
functionmemcached($args)
{
$this->set_servers($args['servers']);
$this->_debug=$args['debug'];
$this->stats=array();
$this->_compress_threshold=$args['compress_threshold'];
$this->_persistant=isset($args['persistant'])?$args['persistant']:false;
$this->_compress_enable=true;
$this->_have_zlib=function_exists("gzcompress");

$this->_cache_sock=array();
$this->_host_dead=array();
}

//}}}
//{{{add()

/**
*Addsakey/valuetothememcacheserverifoneisn'talreadysetwith
*thatkey
*
*@paramstring$keyKeytosetwithdata
*@parammixed$valValuetostore
*@paraminterger$exp(optional)Timetoexpiredataat
*
*@returnboolean
*@accesspublic
*/
functionadd($key,$val,$exp=0)
{
return
$this->_set('add',$key,$val,$exp);
}

//}}}
//{{{decr()

/**
*Decrimentavaluestoredonthememcacheserver
*
*@paramstring$keyKeytodecriment
*@paraminterger$amt(optional)Amounttodecriment
*
*@returnmixedFALSEonfailure,valueonsuccess
*@accesspublic
*/
functiondecr($key,$amt=1)
{
return
$this->_incrdecr('decr',$key,$amt);
}

//}}}
//{{{delete()

/**
*Deletesakeyfromtheserver,optionallyafter$time
*
*@paramstring$keyKeytodelete
*@paraminterger$time(optional)Howlongtowaitbeforedeleting
*
*@returnbooleanTRUEonsuccess,FALSEonfailure
*@accesspublic
*/
functiondelete($key,$time=0)
{
if(!
$this->_active)
return
false;

$sock=$this->get_sock($key);
if(!
is_resource($sock))
return
false;

$key=is_array($key)?$key[1]:$key;

$this->stats['delete']++;
$cmd="delete$key$time/r/n";
if(!
fwrite($sock,$cmd,strlen($cmd)))
{
$this->_dead_sock($sock);
return
false;
}
$res=trim(fgets($sock));

if(
$this->_debug)
printf("MemCache:delete%s(%s)/n",$key,$res);

if(
$res=="DELETED")
return
true;
return
false;
}

//}}}
//{{{disconnect_all()

/**
*Disconnectsallconnectedsockets
*
*@accesspublic
*/
functiondisconnect_all()
{
foreach(
$this->_cache_sockas$sock)
fclose($sock);

$this->_cache_sock=array();
}

//}}}
//{{{enable_compress()

/**
*Enable/Disablecompression
*
*@paramboolean$enableTRUEtoenable,FALSEtodisable
*
*@accesspublic
*/
functionenable_compress($enable)
{
$this->_compress_enable=$enable;
}

//}}}
//{{{forget_dead_hosts()

/**
*Forgetaboutallofthedeadhosts
*
*@accesspublic
*/
functionforget_dead_hosts()
{
$this->_host_dead=array();
}

//}}}
//{{{get()

/**
*Retrievesthevalueassociatedwiththekeyfromthememcacheserver
*
*@paramstring$keyKeytoretrieve
*
*@returnmixed
*@accesspublic
*/
functionget($key)
{
if(!
$this->_active)
return
false;

$sock=$this->get_sock($key);

if(!
is_resource($sock))
return
false;

$this->stats['get']++;

$cmd="get$key/r/n";
if(!
fwrite($sock,$cmd,strlen($cmd)))
{
$this->_dead_sock($sock);
return
false;
}

$val=array();
$this->_load_items($sock,$val);

if(
$this->_debug)
foreach(
$valas$k=>$v)
printf("MemCache:sock%sgot%s=>%s/r/n",$sock,$k,$v);

return
$val[$key];
}

//}}}
//{{{get_multi()

/**
*Getmultiplekeysfromtheserver(s)
*
*@paramarray$keysKeystoretrieve
*
*@returnarray
*@accesspublic
*/
functionget_multi($keys)
{
if(!
$this->_active)
return
false;

$this->stats['get_multi']++;

foreach(
$keysas$key)
{
$sock=$this->get_sock($key);
if(!
is_resource($sock))continue;
$key=is_array($key)?$key[1]:$key;
if(!isset(
$sock_keys[$sock]))
{
$sock_keys[$sock]=array();
$socks[]=$sock;
}
$sock_keys[$sock][]=$key;
}

//Sendouttherequests
foreach($socksas$sock)
{
$cmd="get";
foreach(
$sock_keys[$sock]as$key)
{
$cmd.="".$key;
}
$cmd.="/r/n";

if(
fwrite($sock,$cmd,strlen($cmd)))
{
$gather[]=$sock;
}else
{
$this->_dead_sock($sock);
}
}

//Parseresponses
$val=array();
foreach(
$gatheras$sock)
{
$this->_load_items($sock,$val);
}

if(
$this->_debug)
foreach(
$valas$k=>$v)
printf("MemCache:got%s=>%s/r/n",$k,$v);

return
$val;
}

//}}}
//{{{incr()

/**
*Increments$key(optionally)by$amt
*
*@paramstring$keyKeytoincrement
*@paraminterger$amt(optional)amounttoincrement
*
*@returnintergerNewkeyvalue?
*@accesspublic
*/
functionincr($key,$amt=1)
{
return
$this->_incrdecr('incr',$key,$amt);
}

//}}}
//{{{replace()

/**
*Overwritesanexistingvalueforkey;onlyworksifkeyisalreadyset
*
*@paramstring$keyKeytosetvalueas
*@parammixed$valueValuetostore
*@paraminterger$exp(optional)Experiationtime
*
*@returnboolean
*@accesspublic
*/
functionreplace($key,$value,$exp=0)
{
return
$this->_set('replace',$key,$value,$exp);
}

//}}}
//{{{run_command()

/**
*Passesthrough$cmdtothememcacheserverconnectedby$sock;returns
*outputasanarray(nullarrayifnooutput)
*
*NOTE:duetoapossiblebuginhowPHPreadswhileusingfgets(),each
*linemaynotbeterminatedbya/r/n.Morespecifically,mytesting
*hasshownthat,onFreeBSDatleast,eachlineisterminatedonly
*witha/n.ThisiswiththePHPflagauto_detect_line_endingsset
*tofalase(thedefault).
*
*@paramresource$sockSockettosendcommandon
*@paramstring$cmdCommandtorun
*
*@returnarrayOutputarray
*@accesspublic
*/
functionrun_command($sock,$cmd)
{
if(!
is_resource($sock))
returnarray();

if(!
fwrite($sock,$cmd,strlen($cmd)))
returnarray();

while(
true)
{
$res=fgets($sock);
$ret[]=$res;
if(
preg_match('/^END/',$res))
break;
if(
strlen($res)==0)
break;
}
return
$ret;
}

//}}}
//{{{set()

/**
*Unconditionallysetsakeytoagivenvalueinthememcache.Returnstrue
*ifsetsuccessfully.
*
*@paramstring$keyKeytosetvalueas
*@parammixed$valueValuetoset
*@paraminterger$exp(optional)Experiationtime
*
*@returnbooleanTRUEonsuccess
*@accesspublic
*/
functionset($key,$value,$exp=0)
{
return
$this->_set('set',$key,$value,$exp);
}

//}}}
//{{{set_compress_threshold()

/**
*Setsthecompressionthreshold
*
*@paraminterger$threshThresholdtocompressiflargerthan
*
*@accesspublic
*/
functionset_compress_threshold($thresh)
{
$this->_compress_threshold=$thresh;
}

//}}}
//{{{set_debug()

/**
*Setsthedebugflag
*
*@paramboolean$dbgTRUEfordebugging,FALSEotherwise
*
*@accesspublic
*
*@seememcahced::memcached
*/
functionset_debug($dbg)
{
$this->_debug=$dbg;
}

//}}}
//{{{set_servers()

/**
*Setstheserverlisttodistributekeygetsandputsbetween
*
*@paramarray$listArrayofserverstoconnectto
*
*@accesspublic
*
*@seememcached::memcached()
*/
functionset_servers($list)
{
$this->_servers=$list;
$this->_active=count($list);
$this->_buckets=null;
$this->_bucketcount=0;

$this->_single_sock=null;
if(
$this->_active==1)
$this->_single_sock=$this->_servers[0];
}

//}}}
//}}}
//{{{privatemethods
//{{{_close_sock()

/**
*Closethespecifiedsocket
*
*@paramstring$sockSockettoclose
*
*@accessprivate
*/
function_close_sock($sock)
{
$host=array_search($sock,$this->_cache_sock);
fclose($this->_cache_sock[$host]);
unset(
$this->_cache_sock[$host]);
}

//}}}
//{{{_connect_sock()

/**
*Connects$sockto$host,timingoutafter$timeout
*
*@paraminterger$sockSockettoconnect
*@paramstring$hostHost:IPtoconnectto
*@paramfloat$timeout(optional)Timeoutvalue,defaultsto0.25s
*
*@returnboolean
*@accessprivate
*/
function_connect_sock(&$sock,$host,$timeout=0.25)
{
list(
$ip,$port)=explode(":",$host);
if(
$this->_persistant==1)
{
$sock=@pfsockopen($ip,$port,$errno,$errstr,$timeout);
}else
{
$sock=@fsockopen($ip,$port,$errno,$errstr,$timeout);
}

if(!
$sock)
return
false;
return
true;
}

//}}}
//{{{_dead_sock()

/**
*Marksahostasdeaduntil30-40secondsinthefuture
*
*@paramstring$sockSockettomarkasdead
*
*@accessprivate
*/
function_dead_sock($sock)
{
$host=array_search($sock,$this->_cache_sock);
list(
$ip,$port)=explode(":",$host);
$this->_host_dead[$ip]=time()+30+intval(rand(0,10));
$this->_host_dead[$host]=$this->_host_dead[$ip];
unset(
$this->_cache_sock[$host]);
}

//}}}
//{{{get_sock()

/**
*get_sock
*
*@paramstring$keyKeytoretrievevaluefor;
*
*@returnmixedresourceonsuccess,falseonfailure
*@accessprivate
*/
functionget_sock($key)
{
if(!
$this->_active)
return
false;

if(
$this->_single_sock!==null)
return
$this->sock_to_host($this->_single_sock);

$hv=is_array($key)?intval($key[0]):$this->_hashfunc($key);

if(
$this->_buckets===null)
{
foreach(
$this->_serversas$v)
{
if(
is_array($v))
{
for(
$i=0;$i<$v[1];$i++)
$bu[]=$v[0];
}else
{
$bu[]=$v;
}
}
$this->_buckets=$bu;
$this->_bucketcount=count($bu);
}

$realkey=is_array($key)?$key[1]:$key;
for(
$tries=0;$tries<20;$tries++)
{
$host=$this->_buckets[$hv%$this->_bucketcount];
$sock=$this->sock_to_host($host);
if(
is_resource($sock))
return
$sock;
$hv+=$this->_hashfunc($tries.$realkey);
}

return
false;
}

//}}}
//{{{_hashfunc()

/**
*Createsahashintergerbasedonthe$key
*
*@paramstring$keyKeytohash
*
*@returnintergerHashvalue
*@accessprivate
*/
function_hashfunc($key)
{
$hash=0;
for(
$i=0;$i<strlen($key);$i++)
{
$hash=$hash*33+ord($key[$i]);
}

return
$hash;
}

//}}}
//{{{_incrdecr()

/**
*Performincrement/decrimenton$key
*
*@paramstring$cmdCommandtoperform
*@paramstring$keyKeytoperformiton
*@paraminterger$amtAmounttoadjust
*
*@returnintergerNewvalueof$key
*@accessprivate
*/
function_incrdecr($cmd,$key,$amt=1)
{
if(!
$this->_active)
return
null;

$sock=$this->get_sock($key);
if(!
is_resource($sock))
return
null;

$key=is_array($key)?$key[1]:$key;
$this->stats[$cmd]++;
if(!
fwrite($sock,"$cmd$key$amt/r/n"))
return
$this->_dead_sock($sock);

stream_set_timeout($sock,1,0);
$line=fgets($sock);
if(!
preg_match('/^(/d+)/',$line,$match))
return
null;
return
$match[1];
}

//}}}
//{{{_load_items()

/**
*Loaditemsinto$retfrom$sock
*
*@paramresource$sockSockettoreadfrom
*@paramarray$retReturnedvalues
*
*@accessprivate
*/
function_load_items($sock,&$ret)
{
while(
1)
{
$decl=fgets($sock);
if(
$decl=="END/r/n")
{
return
true;
}elseif(
preg_match('/^VALUE(/S+)(/d+)(/d+)/r/n$/',$decl,$match))
{
list(
$rkey,$flags,$len)=array($match[1],$match[2],$match[3]);
$bneed=$len+2;
$offset=0;

while(
$bneed>0)
{
$data=fread($sock,$bneed);
$n=strlen($data);
if(
$n==0)
break;
$offset+=$n;
$bneed-=$n;
$ret[$rkey].=$data;
}

if(
$offset!=$len+2)
{
//Somethingisborked!
if($this->_debug)
printf("Somethingisborked!key%sexpecting%dgot%dlength/n",$rkey,$len+2,$offset);

unset(
$ret[$rkey]);
$this->_close_sock($sock);
return
false;
}

$ret[$rkey]=rtrim($ret[$rkey]);

if(
$this->_have_zlib&&$flags&MEMCACHE_COMPRESSED)
$ret[$rkey]=gzuncompress($ret[$rkey]);

if(
$flags&MEMCACHE_SERIALIZED)
$ret[$rkey]=unserialize($ret[$rkey]);

}else
{
if(
$this->_debug)
print(
"Errorparsingmemcachedresponse/n");
return
0;
}
}
}

//}}}
//{{{_set()

/**
*Performstherequestedstorageoperationtothememcacheserver
*
*@paramstring$cmdCommandtoperform
*@paramstring$keyKeytoacton
*@parammixed$valWhatweneedtostore
*@paraminterger$expWhenitshouldexpire
*
*@returnboolean
*@accessprivate
*/
function_set($cmd,$key,$val,$exp)
{
if(!
$this->_active)
return
false;

$sock=$this->get_sock($key);
if(!
is_resource($sock))
return
false;

$this->stats[$cmd]++;

$flags=0;

if(!
is_scalar($val))
{
$val=serialize($val);
$flags|=MEMCACHE_SERIALIZED;
if(
$this->_debug)
printf("client:serializingdataasitisnotscalar/n");
}

$len=strlen($val);

if(
$this->_have_zlib&&$this->_compress_enable&&
$this->_compress_threshold&&$len>=$this->_compress_threshold)
{
$c_val=gzcompress($val,9);
$c_len=strlen($c_val);

if(
$c_len<$len*(1-COMPRESS_SAVINGS))
{
if(
$this->_debug)
printf("client:compressingdata;was%dbytesisnow%dbytes/n",$len,$c_len);
$val=$c_val;
$len=$c_len;
$flags|=MEMCACHE_COMPRESSED;
}
}
if(!
fwrite($sock,"$cmd$key$flags$exp$len/r/n$val/r/n"))
return
$this->_dead_sock($sock);

$line=trim(fgets($sock));

if(
$this->_debug)
{
if(
$flags&MEMCACHE_COMPRESSED)
$val='compresseddata';
printf("MemCache:%s%s=>%s(%s)/n",$cmd,$key,$val,$line);
}
if(
$line=="STORED")
return
true;
return
false;
}

//}}}
//{{{sock_to_host()

/**
*Returnsthesocketforthehost
*
*@paramstring$hostHost:IPtogetsocketfor
*
*@returnmixedIOStreamorfalse
*@accessprivate
*/
functionsock_to_host($host)
{
if(isset(
$this->_cache_sock[$host]))
return
$this->_cache_sock[$host];

$now=time();
list(
$ip,$port)=explode(":",$host);
if(isset(
$this->_host_dead[$host])&&$this->_host_dead[$host]>$now||
isset(
$this->_host_dead[$ip])&&$this->_host_dead[$ip]>$now)
return
null;

if(!
$this->_connect_sock($sock,$host))
return
$this->_dead_sock($host);

//Donotbufferwrites
stream_set_write_buffer($sock,0);

$this->_cache_sock[$host]=$sock;

return
$this->_cache_sock[$host];
}

//}}}
//}}}
//}}}
}

//}}}
?>

你可能感兴趣的:(memcached)