PHP与Redis可以通过socket进行通信,前提是PHP需要实现Redis的协议

RESP协议描述:

    • 字符串 \r\n : 表示一个正确的状态信息,具体信息是'+’后面的字符(Simple Strings)
    • 错误前缀 错误信息 \r\n : 表示一个错误信息,具体信息是当前行'-'后面的字符(Errors)
  • $ 字符串的长度 \r\n 字符串 \r\n : 表示字符串(Bulk Strings)
    • 数组元素个数 \r\n 其他所有类型 : 表示消息体总共有多少行(array)
  • : 数字\r\n:表示返回一个数值,:后面是相应的数字 (integer)

详细描述参考:https://redis.io/topics/protocol

1、PHP与redis建立连接

通过PHP的stream_socket_client函数可以建立一个socket连接,然后PHP就可以通过组装符合Redis协议格式的字符串,然后将消息发送给Redis

所以建立连接的代码如下:

    public function open()
{
if($this->_socket !== false){
return;
}
//socket要连接的地址
$remoteSocket = 'tcp://127.0.0.1:6379';
//socket连接建立超时时间
$timeout = ini_get('default_socket_timeout');
//创建socket连接
$this->_socket = @stream_socket_client($remoteSocket, $errorNumber, $errorDescription, $timeout, STREAM_CLIENT_CONNECT);
}

2、执行redis操作命令

执行操作命令首先根据协议进行命令的构造,比如我们执行SET name xiaoming,那么对应在PHP中调用函数的方式可能是setValue($key, $value),PHP是怎么处理的呢?

首先,这一条set命令包含了多个字段:set、key、value,可能还有expire过期时间,所以需要用到协议中的也就是Redis的RESP Arrays,可以包含多个字符串,如果没有过期时间,那么这个set操作转换之后的命令就是:

$command = "*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$8\r\nxiaoming\r\n”;

解释一下就是:

  • 消息数组包含三个元素
  • 第一个是个字符串,长度为3,值为SET
  • 第二个是个字符串,长度为4,值为name
  • 第三个是个字符串,长度为8,值为xiaoming

然后写入socket:

fwrite($this->_socket, $command);

这样一条set key value的命令就执行完成了

这里要注意的一点是,关于命令中字符串长度的计算,Redis文档中的描述:* A "$" byte followed by the number of bytes composing the string (a prefixed length), terminated by CRLF. 也就是说这个长度是按照byte字节来计算的,计算字符串有多少个字节,那么在php中计算字符串长度的时候就不能简单的用strlen了,而需要使用mb_strlen($str, '8bit'),来计算

3、解析Redis返回的消息

在向socket发送了消息之后,Redis执行之后会返回一些信息,同样写入这个socket中,我们要做的是按照协议格式进行消息的解析:

$line = fgets($this->_socket);

$line[0]就是消息的类型,对应上面协议中的:+、 - 、 $ 、 * 、 : 这五种

根据类型的不同再对$line剩余的部分进行解析

  • 如果为+,正确的消息,PONG | OK 返回true,否则返回redis返回的内容
  • 如果为-,错误的消息,说明Redis那边执行这条命令发生了错误,应该抛出异常
  • 如果为$,返回的是个字符串,先获取字符串的长度,也就是紧跟在$后面的数字,然后向后读取相应长度的字符串
  • 如果为:,返回的是数字,直接返回就好
  • 如果是,是个数组,解析获得数组中元素的个数,也就是紧跟在后面的数字,然后,递归的去解读每一行

4、完整的示例代码

class Redis
{
//保存socket连接
private $_socket = false; //redis server 的地址,可以是ip或者主机名
public $hostname = '127.0.0.1'; //端口
public $port = 6379; //redis登录密码
public $password; //redis 数据库 默认为0
public $database = 0; /**
* 建立一个Redis socket连接
*/
public function open()
{
if($this->_socket !== false){
return;
}
//socket要连接的地址
$remoteSocket = 'tcp://' . $this->hostname . ':' . $this->port;
//socket连接建立的超时时间
$timeout = ini_get('default_socket_timeout');
//创建socket连接
$this->_socket = @stream_socket_client($remoteSocket, $errorNumber, $errorDescription, $timeout, STREAM_CLIENT_CONNECT);
if($this->_socket){
//如果有密码,使用密码以授权访问
if($this->pasword){
$this->executeCommand('AUTH', [$this->password]);
}
//选择数据库
$this->executeCommand('SELECT', [$this->database]);
}else{
throw new Exception("创建redis连接失败");
}
}
/**
* 关闭与Redis的socket连接
*/
public function close()
{
$this->executeCommand('QUIT');
stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
$this->_socket = null;
}
/**
* 执行Redis命令
*/
public function executeCommand($name, $params = [])
{
$this->open();
//操作命令加到params中,以计算数组元素个数,也就是消息总共多少行
array_unshift($params, $name);
//按照redis通信协议组装消息
$command = '*' . count($params) . "\r\n";
foreach($params as $arg){
$command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n";
}
//向socket中写入消息
fwrite($this->_socket, $command); return $this->parseResponse(implode(' ', $params));
}
/**
* 解析Redis返回的信息
*/
public function parseResponse($command)
{
if(($line = fgets($this->_socket)) === false){
throw new Exception("redis socket 没有返回任何数据");
}
//根据redis协议解析返回的消息
$type = $line[0];
//去除末尾的\r\n
$line = mb_substr($line, 1, -2, '8bit');
//按照type解析redis返回的消息类型
switch($type){
//返回的是正确的消息
case '+':
if($line == 'OK' || $line == 'PONG'){
return true;
}else{
return $line;
}
//返回的是错误的消息
case '-':
throw new Exception("Redis error: $line");
//返回的是一个数字
case ':':
return $line;
//返回的是一个字符串
case '$':
//根据redis协议,如果返回的是-1 代表null "Null Bulk String"
if($line == -1){
return null;
}
//读取字符串 /**
* 加2是因为,字符串的协议是:$字符串长度字符串\r\n
* 也就是+2 加的是\r\n的长度
*/
$length = $line + 2;
//读取的数据
$data = '';
//这里用while循环处理字符串长度为0的情况,后面可能有多个\r\n,如文档中的:"$0\r\n\r\n"
while($length > 0){
if(($block = fread($this->_socket, $length)) === false){
throw new Exception("读取redis返回的字符串消息失败");
}
$data .= $block;
//长度减去$length,如果为空那就是减去一个\r\n,也就是2
$length -= mb_strlen($block, '8bit');
}
return mb_substr($data, 0, -2, '8bit');
//返回的是一个数组消息
case '*':
$count = (int)$line;
$data = [];
for($i = 0; $i < $count; $i++){
$data[] = $this->parseResponse($command);
}
return $data;
default:
throw new Exception("redis返回了错误的消息标志");
}
}
//get 命令示例
public function getValue($key)
{
return $this->executeCommand('GET', [$key]);
}
//set 命令示例
public function setValue($key, $value, $expire)
{
if($expire == 0){
return $this->executeCommand('SET', [$key, $value]);
}else{
$expire = (int)$expire*1000;
return $this->executeCommand('SET', [$key, $value, 'PX', $expire]);
}
} }
//使用示例 $redis = new Redis();
$redis->setValue("blank","\r\n\r\n",100);

更多的命令在Yii2.0中的实现方式是定义一个数组,里面包含了所有的Redis操作命令,然后实现了__call方法,在__call中判断命令是否存在于数组中,存在则直接executeCommand

参考:Yii2.0的Redis实现

原文链接http://www.cnblogs.com/skyfynn/p/8980322.html

Yii2.0源码阅读-PHP如何与redis通信?的更多相关文章

  1. Yii2.0源码阅读-一次请求的完整过程

    Yii2.0框架源码阅读,从请求发起,到结束的运行步骤 其实最初阅读是从yii\web\UrlManager这个类开始看起,不断的寻找这个类中方法的调用者,最终回到了yii\web\Applicati ...

  2. Yii2.0源码阅读-从路由到控制器

    之前的文章弄清了一次请求的开始到结束.主要讲了Yii Applicaton实例的创建.初始化,UrlManager如何返回Yii中的路由信息,到runAction,最后将Response发送给客户端. ...

  3. Yii2.0源码阅读-视图(View)渲染过程

    之前的文章我们根据源码的分析,弄清了Yii如何处理一次请求,以及根据解析的路由如何调用控制器中的action,那接下来好奇的可能就是,我在控制器action中执行了return $this->r ...

  4. Yii2.0源码阅读-behavior的实现原理

    Yii2.0中的一个思想就是组件化的思想,所以.大多数的类都直接或间接的继承自yii\base\Component,而组件的三大功能:属性.事件.行为. 行为的目的是为了方便的扩展一个类的功能,而不需 ...

  5. Yii2.0源码分析之——控制器文件分析(Controller.php)创建动作、执行动作

    在Yii中,当请求一个Url的时候,首先在application中获取request信息,然后由request通过urlManager解析出route,再在Module中根据route来创建contr ...

  6. Vue2.0源码阅读笔记(四):nextTick

      在阅读 nextTick 的源码之前,要先弄明白 JS 执行环境运行机制,介绍 JS 执行环境的事件循环机制的文章很多,大部分都阐述的比较笼统,甚至有些文章说的是错误的,以下为个人理解,如有错误, ...

  7. Vue2.0源码阅读笔记--生命周期

    一.Vue2.0的生命周期 Vue2.0的整个生命周期有八个:分别是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6 ...

  8. Vue2.0源码阅读笔记--双向绑定实现原理

    上一篇 文章 了解了Vue.js的生命周期.这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理. 一.实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向 ...

  9. Vue2.0源码阅读笔记(二):响应式原理

      Vue是数据驱动的框架,在修改数据时,视图会进行更新.数据响应式系统使得状态管理变的简单直接,在开发过程中减少与DOM元素的接触.而深入学习其中的原理十分有必要,能够回避一些常见的问题,使开发变的 ...

随机推荐

  1. 5.Qt自定义Button按钮的实现

     1.编写自定义按钮 MyButton.h #ifndef MYBUTTON_H #define MYBUTTON_H #include <QWidget> /** * @brief ...

  2. 【一天一道LeetCode】#374. Guess Number Higher or Lower

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 We are ...

  3. Java基础---Java---面试题---交通灯管理系统(面向对象、枚举)

    交通灯管理系统的项目需求: 模拟实现十字路口的交通灯管理系统逻辑,具体需求如下: 1.异步随机生成按照各个路线行驶的车辆  例如:   由南向而来去往北向的车辆-----直行车辆   由西向而来去往南 ...

  4. javascript之cookie对象

    属性 name          唯一必须设置的属性,表示cookie的名称 expires       指定cookie的存活周期,如不设置,浏览器关闭自动失效 path           决定c ...

  5. Cocos2D iOS之旅:如何写一个敲地鼠游戏(七):弹出地鼠

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  6. android fragement报nullexcption错误

    ,这题目起的够骚情了,原创傲慢的上校哦,转载请标明:http://blog.csdn.net/aomandeshangxiao/article/details/7753421 其实有些方法也是从网上找 ...

  7. 【VSTS 日志】VSTS 所有功能,看这个页面就够了!

    随着Connect();//2015大会的结束,一大波的好消息随之而来.今天小编刚刚发现了Visual Studio Team Services / Team Foundation Server 的完 ...

  8. 利用openssl管理证书及SSL编程第2部分:在Windows上编译 openssl

    利用openssl管理证书及SSL编程第2部分:在Windows上编译 openssl 首先mingw的环境搭建,务必遵循下文: http://blog.csdn.net/ubuntu64fan/ar ...

  9. IDEA中运行DirectKafkaWordCount程序

    1,将SPARK_HOME中的DirectKafkaWordCount程序复制到idea中. 2,由于在KafkaWordCount中已引入相关jar包,此步可略过 3,配置configuration ...

  10. XML解析之sax解析案例(二)使用sax解析把 xml文档封装成对象

    Demo1类: import java.io.File; import java.util.List; import javax.xml.parsers.SAXParser; import javax ...