Redis 通訊協議

wuzhc發表於2019-02-16

redis命令傳送格式:

*<引數數量> CRLF
$<引數 1 的位元組數量> CRLF
<引數 1 的資料> CRLF

$<引數 N 的位元組數量> CRLF
<引數 N 的資料> CRLF
其中CRLF表示 rn

舉個例子:set name wuzhc
格式化輸出:

*3
$3
set
$4
name
$5
wuzhc

說明:
  • *開頭,表示有多少個引數,例如*3表示有3個引數(set, name, wuzhc)

  • $開頭,表示引數的位元組長度,例如$3表示set有3個位元組,$4表示name有4個位元組

  • 每行rn結尾

通訊協議為:
*3
$3
set
$4
name
$5
wuzhc

Redis 回覆

  • 狀態回覆(status reply)的第一個位元組是 “+”,例如+OK

  • 錯誤回覆(error reply)的第一個位元組是 “-“,例如-No such key

  • 整數回覆(integer reply)的第一個位元組是 “:”,例如:1

  • 批量回復(bulk reply)的第一個位元組是 “$”,例如 $5
    wuzhc

  • 多條批量回復(multi bulk reply)的第一個位元組是 “*”,例如*2
    $5
    wuzhc
    $3r
    age

PHP 實現Redis客戶端

<?php
/**
 * Created by PhpStorm.
 * User: wuzhc2016@163.com
 * Date: 2017年09月12日
 * Time: 9:08
 */

class Client
{
    private $_socket = null;

    public function __construct($ip, $port) 
    {
        $this->_socket = stream_socket_client(
            "tcp://{$ip}:{$port}",
            $errno,
            $errstr,
            1,
            STREAM_CLIENT_CONNECT
        );
        if (!$this->_socket) {
            exit($errstr);
        }
    }

    /**
     * 執行redis命令
     * @param $command
     * @return array|bool|string
     */
    public function exec($command)
    {      
        // 拼裝傳送命令格式
        $command = $this->_execCommand($command);

        // 傳送命令到redis
        fwrite($this->_socket, $command);

        // 解析redis響應內容
        return $this->_parseResponse();
    }

    /**
     * 將字元改為redis通訊協議格式
     * 例如mget name age 格式化為 *3
$4
mget
$4
name
$3
age

     * @param $command
     * @return bool|string
     */
    private function _execCommand($command)
    {
        $line = ``;
        $crlf = "
";
        $params = explode(` `, $command);
        if (empty($params)) {
            return $line;
        }

        // 引數個數
        $line .= `*` . count($params) . $crlf;

        // 各個引數拼裝
        foreach ((array)$params as $param) {
            $line .= `$` . mb_strlen($param, `8bit`) . $crlf;
            $line .= $param . $crlf;
        }

        return $line;
    }

    /**
     * 解析redis回覆
     * @return array|bool|string
     */
    private function _parseResponse()
    {
        $line = fgets($this->_socket); 
        $type = $line[0]; 
        $msg = mb_substr($line, 1, -2, `8bit`); 

        switch ($type) {
            // 狀態回覆
            case `+`:
                if ($msg == `OK` || $msg == `PONG`) {
                    return true;
                } else {
                    return $msg;
                }
            // 錯誤回覆
            case `-`:
                exit($msg);
            // 整數回覆
            case `:`:
                return $msg;
            // 批量回復
            case `$`: // $後面跟資料位元組數(長度)
                $line = fread($this->_socket, (int)$msg + 2); // 資料位元組數 + (
)兩個位元組
                return mb_substr($line, 0, -2, `8bit`); // 去除最後兩個位元組
            // 多條批量回復
            case `*`: // *表示後面有多少個引數
                $data = [];
                for ($i = 0; $i < $msg; $i++) {
                    $data[] = $this->_parseResponse();
                }
                return $data;
        }
    }
}

// demo
$client = new Client(`127.0.0.1`, 6379);
$client->exec(`set name wuzhc`);
$res = $client->exec(`get name`);
var_dump($res);

相關文章