cache|客户端MemCached的PHP客户端操作类二/** MemCached PHP client* Copyright (c) 2003* Ryan Gilfether* http://www.gilfether.com** Originally translated from Brad Fitzpatrick'sMemCached Perl client* See the memcached website:* http://www.danga.com/memcached/** This module is Copyright (c) 2003 Ryan Gilfether.* All rights reserved.* You may distribute under the terms of the GNU General Public License* This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.**//*** version string*/define("MC_VERSION", "1.0.10");/*** int, buffer size used for sending and receiving* data from sockets*/define("MC_BUFFER_SZ", 1024);/*** MemCached error numbers*/define("MC_ERR_NOT_ACTIVE", 1001); // no active serversdefine("MC_ERR_SOCKET_WRITE", 1002); // socket_write() faileddefine("MC_ERR_SOCKET_READ", 1003); // socket_read() faileddefine("MC_ERR_SOCKET_CONNECT", 1004); // failed to connect to hostdefine("MC_ERR_DELETE", 1005); // delete() did not recieve DELETED commanddefine("MC_ERR_HOST_FORMAT", 1006); // sock_to_host() invalid host formatdefine("MC_ERR_HOST_DEAD", 1007); // sock_to_host() host is deaddefine("MC_ERR_GET_SOCK", 1008); // get_sock() failed to find a valid socketdefine("MC_ERR_SET", 1009); // _set() failed to receive the STORED responsedefine("MC_ERR_GET_KEY", 1010); // _load_items no values returned for key(s)define("MC_ERR_LOADITEM_END", 1011); // _load_items failed to receive END responsedefine("MC_ERR_LOADITEM_BYTES", 1012); // _load_items bytes read larger than bytes available/*** MemCached PHP client Class.** Communicates with the MemCached server, and executes the MemCached protocol* MemCached available at http://www.danga.com/memcached** @author Ryan Gilfether* @package MemCachedClient* @access public* @version 1.0.10*/class MemCachedClient{/*** array of servers no long available* @var array*/var $host_dead;/*** array of open sockets* @var array*/var $cache_sock;/*** determine if debugging is either on or off* @var bool*/var $debug;/*** array of servers to attempt to use, "host:port" string format* @var array*/var $servers;/*** count of currently active connections to servers* @var int*/var $active;/*** error code if one is set* @var int*/var $errno;/*** string describing error* @var string*/var $errstr;/*** size of val to force compression; 0 turns off; defaults 1* @ var int*/var $compress = 1;/*** temp flag to turn compression on/off; defaults on* @ var int*/var $comp_active = 1;/*** array that contains parsed out buckets* @ var array*/var $bucket;/*** Constructor** Creates a new MemCachedClient object* Takes one parameter, a array of options. The most important key is* $options["servers"], but that can also be set later with the set_servers()* method. The servers must be an array of hosts, each of which is* either a scalar of the form or an array of the* former and an integer weight value. (the default weight if* unspecified is 1.) It's recommended that weight values be kept as low* as possible, as this module currently allocates memory for bucket* distribution proportional to the total host weights.* $options["debug"] turns the debugging on if set to true** @access public* @param array $option an array of servers and debug status* @return object MemCachedClient the new MemCachedClient object*/function MemCachedClient($options = 0){if(is_array($options)){$this->set_servers($options["servers"]);$this->debug = $options["debug"];$this->compress = $options["compress"];$this->cache_sock = array();}$this->errno = 0;$this->errstr = "";}/*** sets up the list of servers and the ports to connect to* takes an array of servers in the same format as in the constructor** @access public* @param array $servers array of servers in the format described in the constructor*/function set_servers($servers){$this->servers = $servers;$this->active = count($this->servers);}/*** if $do_debug is set to true, will print out* debugging info, else debug is turned off** @access public* @param bool $do_debug set to true to turn debugging on, false to turn off*/function set_debug($do_debug){$this->debug = $do_debug;}/*** remove all cached hosts that are no longer good** @access public*/function forget_dead_hosts(){unset($this->host_dead);}/*** disconnects from all servers** @access public*/function disconnect_all(){foreach($this->cache_sock as $sock)socket_close($sock);unset($this->cache_sock);$this->active = 0;}/*** removes the key from the MemCache* $time is the amount of time in seconds (or Unix time) until which* the client wishes the server to refuse "add" and "replace" commands* with this key. For this amount of item, the item is put into a* delete queue, which means that it won't possible to retrieve it by* the "get" command, but "add" and "replace" command with this key* will also fail (the "set" command will succeed, however). After the* time passes, the item is finally deleted from server memory.* The parameter $time is optional, and, if absent, defaults to 0* (which means that the item will be deleted immediately and further* storage commands with this key will succeed).* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_DELETE** @access public* @param string $key the key to delete* @param timestamp $time optional, the amount of time server will refuse commands on key* @return bool TRUE on success, FALSE if key does not exist*/function delete($key, $time = 0){if(!$this->active){$this->errno = MC_ERR_NOT_ACTIVE;$this->errstr = "No active servers are available";if($this->debug)$this->_debug("delete(): There are no active servers available.");return FALSE;}$sock = $this->get_sock($key);if(!is_resource($sock)){$this->errno = MC_ERR_GET_SOCK;$this->errstr = "Unable to retrieve a valid socket.";if($this->debug)$this->_debug("delete(): get_sock() returned an invalid socket.");return FALSE;}if(is_array($key))$key = $key[1];$cmd = "delete $key $time\r\n";$cmd_len = strlen($cmd);$offset = 0;// now send the commandwhile($offset {$result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);if($result !== FALSE)$offset += $result;else if($offset {$this->errno = MC_ERR_SOCKET_WRITE;$this->errstr = "Failed to write to socket.";if($this->debug){$sockerr = socket_last_error($sock);$this->_debug("delete(): socket_write() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));}return FALSE;}}// now read the server's responseif(($retval = socket_read($sock, MC_BUFFER_SZ, PHP_NORMAL_READ)) === FALSE){$this->errno = MC_ERR_SOCKET_READ;$this->errstr = "Failed to read from socket.";if($this->debug){$sockerr = socket_last_error($sock);$this->_debug("delete(): socket_read() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));}return FALSE;}// remove the \r\n from the end$retval = rtrim($retval);// now read the server's responseif($retval == "DELETED")return TRUE;else{// something went wrong, create the error$this->errno = MC_ERR_DELETE;$this->errstr = "Failed to receive DELETED response from server.";if($this->debug)$this->_debug("delete(): Failed to receive DELETED response from server. Received $retval instead.");return FALSE;}}/*** Like set(), but only stores in memcache if the key doesn't already exist.* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_SET** @access public* @param string $key the key to set* @param mixed $val the value of the key* @param timestamp $exptime optional, the to to live of the key* @return bool TRUE on success, else FALSE*/function add($key, $val, $exptime = 0){return $this->_set("add", $key, $val, $exptime);}/*** Like set(), but only stores in memcache if the key already exists.* returns TRUE on success else FALSE* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_SET** @access public* @param string $key the key to set* @param mixed $val the value of the key* @param timestamp $exptime optional, the to to live of the key* @return bool TRUE on success, else FALSE*/function replace($key, $val, $exptime = 0){return $this->_set("replace", $key, $val, $exptime);}/*** Unconditionally sets a key to a given value in the memcache. Returns true* if it was stored successfully.* The $key can optionally be an arrayref, with the first element being the* hash value, as described above.* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_SET** @access public* @param string $key the key to set* @param mixed $val the value of the key* @param timestamp $exptime optional, the to to live of the key* @return bool TRUE on success, else FALSE*/function set($key, $val, $exptime = 0){return $this->_set("set", $key, $val, $exptime);}/*** Retrieves a key from the memcache. Returns the value (automatically* unserialized, if necessary) or FALSE if it fails.* The $key can optionally be an array, with the first element being the* hash value, if you want to avoid making this module calculate a hash* value. You may prefer, for example, to keep all of a given user's* objects on the same memcache server, so you could use the user's* unique id as the hash value.* Possible errors set are:* MC_ERR_GET_KEY** @access public* @param string $key the key to retrieve* @return mixed the value of the key, FALSE on error*/function get($key){$val =& $this->get_multi($key);if(!$val){$this->errno = MC_ERR_GET_KEY;$this->errstr = "No value found for key $key";if($this->debug)$this->_debug("get(): No value found for key $key");return FALSE;}return $val[$key];}/*** just like get(), but takes an array of keys, returns FALSE on error* Possible errors set are:* MC_ERR_NOT_ACTIVE** @access public* @param array $keys the keys to retrieve* @return array the value of each key, FALSE on error*/function get_multi($keys){$sock_keys = array();$socks = array();$val = 0;if(!$this->active){$this->errno = MC_ERR_NOT_ACTIVE;$this->errstr = "No active servers are available";if($this->debug)$this->_debug("get_multi(): There are no active servers available.");return FALSE;}if(!is_array($keys)){$arr[] = $keys;$keys = $arr;}foreach($keys as $k){$sock = $this->get_sock($k);if($sock){$k = is_array($k) ? $k[1] : $k;if(@!is_array($sock_keys[$sock]))$sock_keys[$sock] = array();// if $sock_keys[$sock] doesn't exist, create itif(!$sock_keys[$sock])$socks[] = $sock;$sock_keys[$sock][] = $k;}}if(!is_array($socks)){$arr[] = $socks;$socks = $arr;}foreach($socks as $s){$this->_load_items($s, $val, $sock_keys[$sock]);}if($this->debug){while(list($k, $v) = @each($val))$this->_debug("MemCache: got $k = $v\n");}return $val;}/*** Sends a command to the server to atomically increment the value for* $key by $value, or by 1 if $value is undefined. Returns FALSE if $key* doesn't exist on server, otherwise it returns the new value after* incrementing. Value should be zero or greater. Overflow on server* is not checked. Be aware of values approaching 2**32. See decr.* ONLY WORKS WITH NUMERIC VALUES* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ** @access public* @param string $key the keys to increment* @param int $value the amount to increment the key bye* @return int the new value of the key, else FALSE*/function incr($key, $value = 1){return $this->_incrdecr("incr", $key, $value);}/*** Like incr, but decrements. Unlike incr, underflow is checked and new* values are capped at 0. If server value is 1, a decrement of 2* returns 0, not -1.* ONLY WORKS WITH NUMERIC VALUES* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ** @access public* @param string $key the keys to increment* @param int $value the amount to increment the key bye* @return int the new value of the key, else FALSE*/function decr($key, $value = 1){return $this->_incrdecr("decr", $key, $value);}/*** When a function returns FALSE, an error code is set.* This funtion will return the error code.* See error_string()** @access public* @return int the value of the last error code*/function error(){return $this->errno;}/*** Returns a string describing the error set in error()* See error()** @access public* @return int a string describing the error code given*/function error_string(){return $this->errstr;}/*** Resets the error number and error string** @access public*/function error_clear(){// reset to no error$this->errno = 0;$this->errstr = "";}/*** temporarily sets compression on or off* turning it off, and then back on will result in the compression threshold going* back to the original setting from $options* @param int $setting setting of compression (0=off|1=on)*/function set_compression($setting=1) {if ($setting != 0) {$this->comp_active = 1;} else {$this->comp_active = 0;}}/** PRIVATE FUNCTIONS*//*** connects to a server* The $host may either a string int the form of host:port or an array of the* former and an integer weight value. (the default weight if* unspecified is 1.) See the constructor for details* Possible errors set are:* MC_ERR_HOST_FORMAT* MC_ERR_HOST_DEAD* MC_ERR_SOCKET_CONNECT** @access private* @param mixed $host either an array or a string* @return resource the socket of the new connection, else FALSE*/function sock_to_host($host){if(is_array($host))$host = array_shift($host);$now = time();// seperate the ip from the port, index 0 = ip, index 1 = port$conn = explode(":", $host);if(count($conn) != 2){$this->errno = MC_ERR_HOST_FORMAT;$this->errstr = "Host address was not in the format of host:port";if($this->debug)$this->_debug("sock_to_host(): Host address was not in the format of host:port");return FALSE;}if(@($this->host_dead[$host] && $this->host_dead[$host] > $now) ||@($this->host_dead[$conn[0]] && $this->host_dead[$conn[0]] > $now)){$this->errno = MC_ERR_HOST_DEAD;$this->errstr = "Host $host is not available.";if($this->debug)$this->_debug("sock_to_host(): Host $host is not available.");return FALSE;}// connect to the server, if it fails, add it to the host_dead below$sock = socket_create (AF_INET, SOCK_STREAM, getprotobyname("TCP"));// we need surpress the error message if a connection failsif(!@socket_connect($sock, $conn[0], $conn[1])){$this->host_dead[$host]=$this->host_dead[$conn[0]]=$now+60+intval(rand(0, 10));$this->errno = MC_ERR_SOCKET_CONNECT;$this->errstr = "Failed to connect to ".$conn[0].":".$conn[1];if($this->debug)$this->_debug("sock_to_host(): Failed to connect to ".$conn[0].":".$conn[1]);return FALSE;}// success, add to the list of sockets$cache_sock[$host] = $sock;return $sock;}/*** retrieves the socket associated with a key* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK** @access private* @param string $key the key to retrieve the socket from* @return resource the socket of the connection, else FALSE*/function get_sock($key){if(!$this->active){$this->errno = MC_ERR_NOT_ACTIVE;$this->errstr = "No active servers are available";if($this->debug)$this->_debug("get_sock(): There are no active servers available.");return FALSE;}$hv = is_array($key) ? intval($key[0]) : $this->_hashfunc($key);if(!$this->buckets){$bu = $this->buckets = array();foreach($this->servers as $v){if(is_array($v)){for($i = 1; $i $bu[] = $v[0];}else$bu[] = $v;}$this->buckets = $bu;}$real_key = is_array($key) ? $key[1] : $key;$tries = 0;while($tries {$host = @$this->buckets[$hv % count($this->buckets)];$sock = $this->sock_to_host($host);if(is_resource($sock))return $sock;$hv += $this->_hashfunc($tries.$real_key);++$tries;}$this->errno = MC_ERR_GET_SOCK;$this->errstr = "Unable to retrieve a valid socket.";if($this->debug)$this->_debug("get_sock(): Unable to retrieve a valid socket.");return FALSE;}/*** increments or decrements a numerical value in memcached. this function is* called from incr() and decr()* ONLY WORKS WITH NUMERIC VALUES* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ** @access private* @param string $cmdname the command to send, either incr or decr* @param string $key the key to perform the command on* @param mixed $value the value to incr or decr the key value by* @return int the new value of the key, FALSE if something went wrong*/function _incrdecr($cmdname, $key, $value){if(!$this->active){$this->errno = MC_ERR_NOT_ACTIVE;$this->errstr = "No active servers are available";if($this->debug)$this->_debug("_incrdecr(): There are no active servers available.");return FALSE;}$sock = $this->get_sock($key);if(!is_resource($sock)){$this->errno = MC_ERR_GET_SOCK;$this->errstr = "Unable to retrieve a valid socket.";if($this->debug)$this->_debug("_incrdecr(): Invalid socket returned by get_sock().");return FALSE;}if($value == "")$value = 1;$cmd = "$cmdname $key $value\r\n";$cmd_len = strlen($cmd);$offset = 0;// write the command to the serverwhile($offset {$result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);if($result !== FALSE)$offset += $result;else if($offset {$this->errno = MC_ERR_SOCKET_WRITE;$this->errstr = "Failed to write to socket.";if($this->debug){$sockerr = socket_last_error($sock);$this->_debug("_incrdecr(): socket_write() returned FALSE. Error $errno: ".socket_strerror($sockerr));}return FALSE;}}// now read the server's responseif(($retval = socket_read($sock, MC_BUFFER_SZ, PHP_NORMAL_READ)) === FALSE){$this->errno = MC_ERR_SOCKET_READ;$this->errstr = "Failed to read from socket.";if($this->debug){$sockerr = socket_last_error($sock);$this->_debug("_incrdecr(): socket_read() returned FALSE. Socket Error $errno: ".socket_strerror($sockerr));}return FALSE;}// strip the /r/n from the end and return valuereturn trim($retval);&nbs, p; }/*** sends the command to the server* Possible errors set are:* MC_ERR_NOT_ACTIVE* MC_ERR_GET_SOCK* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_SET** @access private* @param string $cmdname the command to send, either incr or decr* @param string $key the key to perform the command on* @param mixed $value the value to set the key to* @param timestamp $exptime expiration time of the key* @return bool TRUE on success, else FALSE*/function _set($cmdname, $key, $val, $exptime = 0){if(!$this->active){$this->errno = MC_ERR_NOT_ACTIVE;$this->errstr = "No active servers are available";if($this->debug)$this->_debug("_set(): No active servers are available.");return FALSE;}$sock = $this->get_sock($key);if(!is_resource($sock)){$this->errno = MC_ERR_GET_SOCK;$this->errstr = "Unable to retrieve a valid socket.";if($this->debug)$this->_debug("_set(): Invalid socket returned by get_sock().");return FALSE;}$flags = 0;$key = is_array($key) ? $key[1] : $key;$raw_val = $val;// if the value is not scalar, we need to serialize itif(!is_scalar($val)){$val = serialize($val);$flags |= 1;}if (($this->compress_active) && ($this->compress > 0) && (strlen($val) > $this->compress)) {$this->_debug("_set(): compressing data. size in:".strlen($val));$cval=gzcompress($val);$this->_debug("_set(): done compressing data. size out:".strlen($cval));if ((strlen($cval) 2048)){$flags |= 2;$val=$cval;}unset($cval);}$len = strlen($val);if (!is_int($exptime))$exptime = 0;// send off the request$cmd = "$cmdname $key $flags $exptime $len\r\n$val\r\n";$cmd_len = strlen($cmd);$offset = 0;// write the command to the serverwhile($offset {$result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);if($result !== FALSE)$offset += $result;else if($offset {$this->errno = MC_ERR_SOCKET_WRITE;$this->errstr = "Failed to write to socket.";if($this->debug){$errno = socket_last_error($sock);$this->_debug("_set(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));}return FALSE;}}// now read the server's responseif(($l_szResponse = socket_read($sock, 6, PHP_NORMAL_READ)) === FALSE){$this->errno = MC_ERR_SOCKET_READ;$this->errstr = "Failed to read from socket.";if($this->debug){$errno = socket_last_error($sock);$this->_debug("_set(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));}return FALSE;}if($l_szResponse == "STORED"){if($this->debug)$this->_debug("MemCache: $cmdname $key = $raw_val");return TRUE;}$this->errno = MC_ERR_SET;$this->errstr = "Failed to receive the STORED response from the server.";if($this->debug)$this->_debug("_set(): Did not receive STORED as the server response! Received $l_szResponse instead.");return FALSE;}/*** retrieves the value, and returns it unserialized* Possible errors set are:* MC_ERR_SOCKET_WRITE* MC_ERR_SOCKET_READ* MC_ERR_GET_KEY* MC_ERR_LOADITEM_END* MC_ERR_LOADITEM_BYTES** @access private* @param resource $sock the socket to connection we are retriving from* @param array $val reference to the values retrieved* @param mixed $sock_keys either a string or an array of keys to retrieve* @return array TRUE on success, else FALSE*/function _load_items($sock, &$val, $sock_keys){$val = array();$cmd = "get ";if(!is_array($sock_keys)){$arr[] = $sock_keys;$sock_keys = $arr;}foreach($sock_keys as $sk)$cmd .= $sk." ";$cmd .="\r\n";$cmd_len = strlen($cmd);$offset = 0;// write the command to the serverwhile($offset {$result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);if($result !== FALSE)$offset += $result;else if($offset {$this->errno = MC_ERR_SOCKET_WRITE;$this->errstr = "Failed to write to socket.";if($this->debug){$errno = socket_last_error($sock);$this->_debug("_load_items(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));}return FALSE;}}$len = 0;$buf = "";$flags_array = array();// now read the response from the serverwhile($line = socket_read($sock, MC_BUFFER_SZ, PHP_BINARY_READ)){// check for a socket_read errorif($line === FALSE){$this->errno = MC_ERR_SOCKET_READ;$this->errstr = "Failed to read from socket.";if($this->debug){$errno = socket_last_error($sock);$this->_debug("_load_items(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));
