2016年2月

Redis协议笔记

redis协议相当简单好理解。

redis协议支持类型:

  • 正确 Simple Strings
  • 错误 Errors
  • 整数 Integers
  • 字符块 Bulk Strings
  • 数组 Arrays

Simple Strings(+)
Simple Strings的呈现以+开始,以\r\n结尾,一个只含有'OK'的表示为+OK\r\n。所以简单的字符串不能包含连续的\r\n字符。

Errors(-)
Errors 的呈现以-开始,以\r\n结尾,一条表示Error message错误信息的字符为-Error message\r\n

Integers(:)
Integers 的呈现以:开始,以\r\n结尾,比如:1000\r\n表示数字1000

Bulk Strings($)
Bulk Strings 数据头同时表示下一行数据长度,不包括换行符长度\r\n,$后面则是对应的长度的数据。它还有一个特殊用途,用长度为-1的空字符表示NUll值

Arrays(*)
Arrays 头以 * 开始,后边接一个Integer类型,表示消息体总共有多少行,不包括当前行,*后面是具体的行数,也就是数组长度,之后接着各自的数据类型。

发送方式

客户端将命令用块字符的方式发送给服务端,比如:

SET xyz abcde
*3\r\n
$3\r\n
SET\r\n
$3\r\n
xyz\r\n
$5\r\n
abcde\r\n

成功或者失败:

+OK\r\n
-错误信息\r\n

如果是内建的命令操作,可以直接发送命令给服务器。比如PING, EXISTS mykey等.使用telnet 127.0.0.1 6379连上默认的redis server, 敲入PING就可以返回结果+PONG

php实现redis客户端和服务端 string功能:

  • 服务端
<?php
/**
 * 多进程阻塞式
 */
class Xtgxiso_server
{
    private $socket = false;
    private $process_num = 100;
    public $redis_kv_data = array();
    public $onMessage = null;
 
    function __construct($host="0.0.0.0",$port=1215)
    {
        $this->socket = stream_socket_server("tcp://".$host.":".$port,$errno, $errstr);
        if (!$this->socket) die($errstr."--".$errno);
        echo "listen $host $port \r\n";
        ini_set("memory_limit", "128M");
    }
 
    private function parseRESP(&$conn){
        $line = fgets($conn);
        if($line === '' || $line === false)
        {
            return null;
        }
        $type = $line[0];
        $line = mb_substr($line,1,-2);
        switch ( $type ){
            case "*":
                $count = (int) $line;
                $data = array();
                for ($i = 1; $i <= $count; $i++) {
                    $data[] = $this->parseRESP($conn);
                }
                return $data;
            case "$":
                if ($line == '-1') {
                    return null;
                }
                $length = $line + 2;
                $data = '';
                while ($length > 0) {
                    $block = fread($conn, $length);
                    if ($length !== strlen($block)) {
                        throw new Exception('RECEIVING');
                    }
                    $data .= $block;
                    $length -= mb_strlen($block);
                }
                return mb_substr($data, 0, -2);
        }
        return $line;
    }
 
    private function start_worker_process(){
        $pid = pcntl_fork();
        switch ($pid) {
            case -1:
                echo "fork error : {$i} \r\n";
                exit;
            case 0:
                while ( 1 ) {
                    echo  "waiting...\n";
                    $conn = stream_socket_accept($this->socket, -1);
                    if ( !$conn ){
                        continue;
                    }
                    //"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
                    while(1){
                        $arr = $this->parseRESP($conn);
                        if ( is_array($arr) ) {
                            if ($this->onMessage) {
                                call_user_func($this->onMessage, $conn, $arr);
                            }
                        }else if ( $arr ){
                            if ($this->onMessage) {
                                call_user_func($this->onMessage, $conn, $arr);
                            }
                        }else{
                            fclose($conn);
                            break;
                        }
                    }
                }
            default:
                $this->pids[$pid] = $pid;
                break;
        }
    }
 
    public function run(){
        for($i = 1; $i <= $this->process_num; $i++){
            $this->start_worker_process();
        }
 
        while(1){
            foreach ($this->pids as $i => $pid) {
                if($pid) {
                    $res = pcntl_waitpid($pid, $status,WNOHANG);
 
                    if ( $res == -1 || $res > 0 ){
                        $this->start_worker_process();
                        unset($this->pids[$pid]);
                    }
                }
            }
            sleep(1);
        }
    }
 
}
$server =  new Xtgxiso_server();
$server->onMessage = function($conn,$info) use($server){
    if ( is_array($info) ){
        if ( $info["0"] == "SET" ) {
            $key = $info[1];
            $val = $info[2];
            $server->redis_kv_data[$key] = $val;
            fwrite($conn, "+OK\r\n");
        }else if ( $info["0"] == "GET" ){
            $key = $info[1];
            fwrite($conn, "$".strlen($server->redis_kv_data[$key])."\r\n".$server->redis_kv_data[$key]."\r\n");
        }else{
            fwrite($conn,"+OK\r\n");
        }
    }else{
        fwrite($conn,"+OK\r\n");
    }
};
$server->run();

通过如下命令来测试PHP实现的性能:

redis-benchmark -h 127.0.0.1 -p 1215 -t set -n 80000 -q
  • 客户端
<?php
namespace xtgxiso;
class Redis {
    private $redis_socket = false;
    private $cmd = '';
    public function __construct($host='127.0.0.1',$port=6379,$timeout = 3) {
        $this->redis_socket = stream_socket_client("tcp://".$host.":".$port, $errno, $errstr,  $timeout);
        if ( !$this->redis_socket) {
            throw new Exception("{$errno} - {$errstr}");
        }
    }
    public function __destruct() {
        fclose($this->redis_socket);
    }
    public function __call($name, $args) {
        $crlf = "\r\n";
        array_unshift($args,$name);
        $command = '*' . count($args) . $crlf;
        foreach ($args as $arg) {
            $command .= '$' . strlen($arg) . $crlf . $arg . $crlf;
        }
        $fwrite = fwrite($this->redis_socket,$command);
        if ($fwrite === FALSE || $fwrite <= 0) {
            throw new Exception('Failed to write entire command to stream');
        }
        return $this->readResponse();
    }
    private function readResponse() {
        $reply = trim(fgets($this->redis_socket, 1024));
        switch (substr($reply, 0, 1)) {
            case '-':
                throw new Exception(trim(substr($reply, 4)));
                break;
            case '+':
                $response = substr(trim($reply), 1);
                if ($response === 'OK') {
                    $response = TRUE;
                }
                break;
            case '$':
                $response = NULL;
                if ($reply == '$-1') {
                    break;
                }
                $read = 0;
                $size = intval(substr($reply, 1));
                if ($size > 0) {
                    do {
                        $block_size = ($size - $read) > 1024 ? 1024 : ($size - $read);
                        $r = fread($this->redis_socket, $block_size);
                        if ($r === FALSE) {
                            throw new Exception('Failed to read response from stream');
                        } else {
                            $read += strlen($r);
                            $response .= $r;
                        }
                    } while ($read < $size);
                }
                fread($this->redis_socket, 2); /* discard crlf */
                break;
            /* Multi-bulk reply */
            case '*':
                $count = intval(substr($reply, 1));
                if ($count == '-1') {
                    return NULL;
                }
                $response = array();
                for ($i = 0; $i < $count; $i++) {
                    $response[] = $this->readResponse();
                }
                break;
            /* Integer reply */
            case ':':
                $response = intval(substr(trim($reply), 1));
                break;
            default:
                throw new RedisException("Unknown response: {$reply}");
                break;
        }
        return $response;
    }
}
/*
$redis = new Client_test();
var_dump($redis->auth("123456"));
var_dump($redis->set("a",'b'));
var_dump($redis->get("a"));
*/

参考:
http://redis.cn/topics/protocol.html
http://www.01happy.com/php-redis-server/
http://www.01happy.com/php-redis-client/

c#实现一个简单浏览器 和 dns查询软件

初中的时候看别人开发的软件很有意思,非常想学,但苦于c的难度,最后转学web来提升成就感,而且一做就这么多年,这两年因为想做游戏的原因又开始由php往底层学c c++ c# java,虽然软件跟互联网或者游戏相比被看做夕阳产业,但能实现小时候的梦想也挺有意思的,刚入门的小东西大家随便看看哈。

dns查询软件,dns协议分析并实现
源码:https://github.com/forthxu/mydns
下载程序:https://github.com/forthxu/mydns/blob/master/exe.rar?raw=true
设计和实施 DNS 服务器和客户端服务时可能用到的RFC相关规范:
RFC
标题
1034
域名 - 概念和工具
1035
域名 - 实现和规范
1123
Internet 主机 - 应用和支持的要求
1886
支持 IP 版本 6 的 DNS 扩展名
1995
DNS 中的增量区域传输
1996
提示通知区域更改的机制 (DNS NOTIFY)
2136
域名系统中的动态更新 (DNS UPDATE)
2181
对 DNS 规范的说明
2308
DNS 查询的负缓存 (DNS NCACHE)
2535
域名系统安全扩展 (DNSSEC)
2671
DNS 的扩展机制 (EDNS0)
2782
指定服务位置的 DNS RR (DNS SRV)
2930
DNS 的密钥建立 (TKEY RR)
3645
DNS (GSS-TSIG) 密钥事务身分验证的通用安全服务算法
3646
IPv6 (DHCPv6) 动态主机配置协议的 DNS 配置选项

浏览器,简单的调用控件
源码:https://github.com/forthxu/forthxu_browser
下载程序:https://github.com/forthxu/forthxu_browser/blob/master/exe/Forthxu_browser.exe?raw=true