1、缘起
偶然翻看openresty安装文件的目录,看到lualib/resty目录下有一些用lua写的模块,其中有个memcached.lua,原来是memcache客户端的源码,突然想起memcached协议是基于命令行的。他这个模块用到了ngx.socket.tcp模块,粗略看了下,发现也不是特别复杂,就是用socket收发数据。于是心血来潮,百度了下memcached协议,来造个轮子。
2、代码
memcached协议可参考:memcached协议中文版,讲得挺详细的。
废话少说,上代码(只实现了get,set,add,replace,flush_all共5个命令)。
/** 封装的异常类 */
class MemcacheException extends Exception {
public function __construct($message, $code = 0) {
parent::__construct($message, $code);
}
}
class MyMemcacheClient {
private $host;
private $port;
private $socket;
public function __construct() {
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
}
/**
* 获取最后一次的socket错误
* @return string 最后一次socket错误字符串
*/
public function getSocketError() {
return socket_strerror(socket_last_error($this->socket));
}
/**
* 抛出异常封装函数
* @return MemcacheException MemcacheException实例
*/
private function throwException() {
throw new MemcacheException($this->getSocketError());
}
/**
* 链接memcached服务器
* @param string $host memcached监听的ip
* @param integer $port memcached监听的端口
* @return boolean true表示连接成功,false表示连接失败
*/
public function connect($host = '127.0.0.1', $port = 11211) {
$this->host = $host;
$this->port = $port;
$result = socket_connect($this->socket, $host, $port);
if ($result === false) {
return false;
} else {
return true;
}
}
/**
* 执行set|add|replace命令
* @param string $cmd 命令(set|add|replace)
* @param string $key 键
* @param string $value 值
* @param nteger $ttl 生存时间
* @return boolean true for success, false for fail
*/
private function _set_add_replace($cmd, $key, $value, $ttl = 10) {
$line1 = sprintf("$cmd %s 0 %d %d\r\n", $key, $ttl, strlen($value));
$line2 = $value . "\r\n";
$data = $line1 . $line2;
$result = socket_write($this->socket, $data, strlen($data));
if ($result === false) {
$this->throwException();
}
$response = socket_read($this->socket, 1024, PHP_NORMAL_READ);
/** 读取最后一个 \n 字符 */
socket_read($this->socket, 1, PHP_BINARY_READ);
if ($response === false) {
$this->throwException();
}
/** 操作成功会返回STORED\r\n */
if (!strncmp($response, 'STORED', 6)) {
return true;
}
return false;
}
public function set($key, $value, $ttl = 10) {
return $this->_set_add_replace('set', $key, $value, $ttl);
}
public function add($key, $value, $ttl = 10) {
return $this->_set_add_replace('add', $key, $value, $ttl);
}
public function replace($key, $value, $ttl = 10) {
return $this->_set_add_replace('replace', $key, $value, $ttl);
}
/**
* 获取一个键的值
* @param string $key 键
* @return string|boolean 值, false表示没有这个键或者已过期
*/
public function get($key) {
$data = sprintf("get %s\r\n", $key);
$result = socket_write($this->socket, $data, strlen($data));
if ($result === false) {
$this->throwException();
}
$line1 = socket_read($this->socket, 1024, PHP_NORMAL_READ);
/** 读取最后一个 \n 字符 */
socket_read($this->socket, 1, PHP_BINARY_READ);
if (!$line1) {
$this->throwException();
}
/** 获取成功,第一行返回 VALUE <key> <flags> <bytes>\r\n */
if (!strncmp($line1, "VALUE", 5)) {
$line1 = rtrim($line1, "\r\n");
$arr = explode(' ', $line1);
/** 获取数据长度 */
$dataLen = intval(end($arr));
/** 获取数据 */
$response = socket_read($this->socket, $dataLen, PHP_BINARY_READ);
/** 读取最后7个字符 \r\nEND\r\n */
socket_read($this->socket, 7, PHP_BINARY_READ);
if ($response === false) {
$this->throwException();
}
return $response;
} else {
return false;
}
}
/**
* 设置所有的键过期
* @return boolean success
*/
public function flushAll() {
$data = "flush_all\r\n";
$result = socket_write($this->socket, $data, strlen($data));
/** 读取返回结果,固定为 OK\r\n */
socket_read($this->socket, 4, PHP_BINARY_READ);
return true;
}
}
3、测试
- 测试代码
try {
$memcache = new MyMemcacheClient();
$memcache->connect();
$memcache->flushAll();
echo "a=", $memcache->get("a"), PHP_EOL;
$memcache->set("a", "This is a");
$data = $memcache->get("a");
echo "a: ", $data, PHP_EOL;
$memcache->set("a", "This is modified a");
echo "a: ", $memcache->get("a"), PHP_EOL;
$memcache->add("b", "This is b");
echo "b: ", $memcache->get("b"), PHP_EOL;
$memcache->replace("b", "This is replaced b");
echo "b: ", $memcache->get("b"), PHP_EOL;
} catch (MemcacheException $e) {
echo $e->getMessage();
}
- 测试结果
a=
a: This is a
a: This is modified a
b: This is b
b: This is replaced b