php语言实现redis的客户端
为了更好的了解redis协议,我们用php来实现一个支持大部份命令的客户端类.
redis的协议可参考这个文章http://redis.cn/topics/protocol.html
代码如下:
<?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("xtgxiso",'abc'));
var_dump($redis->get("xtgxiso"));
*/
通过实现,我们基本了解redis的协议。
前面我们花了一段时间来搭建高性能的socket服务,可以同时处理大量的连接,但这是在没有具体业务的情况下。
如果我们启用了一个单进程的server,但里面的一个业务耗时1秒,那么在这1秒内是阻塞的,后续的请求会等待,如果并发三个请求,那么三个请求的执行时间会分别昌1秒,2秒,3秒.提高并发的方法有以下几种:
1:多启动进程,提高并发数
2:优化业务,减少耗时间相当于减少阻塞时间,提高并发数
3:异步编程,避免阻塞,提高并发数
这里我们重点介绍第三种方法,以访问第三方http为例。
代码如下:
<?php
//同步读取
function get_data_blocking(){
$socket = stream_socket_client("tcp://test.raventech.cn:80", $errno, $errstr, 6);
fwrite($socket, "GET /sleep1.php HTTP/1.0\r\nHost: test.raventech.cn\r\nAccept: */*\r\n\r\n");
$str = "";
while (!feof($socket)) {
$str .= fgets($socket, 1024);
}
fclose($socket);
return $str;
}
//异步读取
function get_data_unblocking(){
$socket = stream_socket_client("tcp://test.raventech.cn:80", $errno, $errstr, 6);
stream_set_blocking($socket, 0);
fwrite($socket, "GET /sleep1.php HTTP/1.0\r\nHost: test.raventech.cn\r\nAccept: */*\r\n\r\n");
$write = NULL;
$except = NULL;
while( $socket ){
$read = array($socket);
$num_changed_streams = stream_select($read, $write, $except, 0);
if ( $num_changed_streams > 0 ) {
foreach($read as $r){
$str = fread($r,2048);
fclose($socket);
$socket = false;
return $str;
}
}
usleep(100);
}
}
//真正的异步读取--利用server的IO复用事件来提高并发
class Get_data_event{
public $onMessage = null;
private $str='';
function __construct(&$server){
$socket = stream_socket_client("tcp://test.xtgxiso.cn:80", $errno, $errstr, 6);
stream_set_blocking($socket, 0);
fwrite($socket, "GET /sleep1.php HTTP/1.0\r\nHost: test.xtgxiso.cn\r\nAccept: */*\r\n\r\n");
$server->add_socket($socket, array($this, 'read'));
}
public function read($socket){
while (1) {
$buffer = fread($socket, 1024);
if ($buffer === '' || $buffer === false) {
break;
}
$this->str .= $buffer;
}
if( $this->onMessage && $this->str ) {
call_user_func($this->onMessage, $this->str);
}
$this->str = '';
return false;
}
}
/**
* 单进程IO复用select
*/
class Xtgxiso_server
{
public $socket = false;
public $master = array();
public $onConnect = null;
public $onMessage = null;
public $other_socket_callback = array();
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);
stream_set_blocking($this->socket,0);
$id = (int)$this->socket;
$this->master[$id] = $this->socket;
}
public function add_socket($socket,$callback){
$id = (int)$socket;
$this->master[$id] = $socket;
$this->other_socket_callback[$id] = $callback;
}
public function run(){
$read = $this->master;
$receive = array();
echo "start run...\n";
while ( 1 ) {
$read = $this->master;
//echo "waiting...\n";
$mod_fd = @stream_select($read, $_w = NULL, $_e = NULL, 60);
if ($mod_fd === FALSE) {
break;
}
foreach ( $read as $k => $v ) {
$id = (int)$v;
if ( $v === $this->socket ) {
//echo "new conn\n";
$conn = stream_socket_accept($this->socket);
if ($this->onConnect) {
call_user_func($this->onConnect, $conn);
}
$id = (int)$conn;
$this->master[$id] = $conn;
} else if ( @$this->other_socket_callback[$id] ){
call_user_func_array($this->other_socket_callback[$id], array($v));
} else {
//echo "read data\n";
if ( !isset($receive[$k]) ){
$receive[$k]="";
}
$buffer = fread($v, 1024);
//echo $buffer."\n";
if ( strlen($buffer) === 0 ) {
if ( $this->onClose ){
call_user_func($this->onClose,$v);
}
fclose($v);
$id = (int)$v;
unset($this->master[$id]);
} else if ( $buffer === FALSE ) {
if ( $this->onClose ){
call_user_func($this->onClose, $this->master[$key_to_del]);
}
fclose($v);
$id = (int)$v;
unset($this->master[$id]);
} else {
$pos = strpos($buffer, "\r\n\r\n");
if ( $pos === false) {
$receive[$k] .= $buffer;
//echo "received:".$buffer.";not all package,continue recdiveing\n";
}else{
$receive[$k] .= trim(substr ($buffer,0,$pos+4));
$buffer = substr($buffer,$pos+4);
if($this->onMessage) {
call_user_func($this->onMessage,$v,$receive[$k]);
}
$receive[$k]='';
}
}
}
}
usleep(10000);
}
}
}
$server = new Xtgxiso_server();
$server->onConnect = function($conn){
echo "onConnect -- accepted " . stream_socket_get_name($conn,true) . "\n";
};
$server->onMessage = function($conn,$msg) use ( $server ) {
/*
$respone ="";//响应内容
$respone = "HTTP/1.1 200 OK\r\n";
$respone .= "Server: openresty\r\n";
$respone .= "Content-Type: text/html; charset=utf-8\r\n";
$body = time().rand(111111,999999);
$len = strlen($body);
$respone .= "Content-Length:$len\r\n";
$respone .= "Connection: close\r\n";
$respone .= "\r\n$body\r\n\r\n";
echo "onMessage --" . $msg . "\n";
*/
//同步读取
//$respone = get_data_blocking();
//fwrite($conn,$respone);
//异步读取
//$respone = get_data_unblocking();
//fwrite($conn,$respone);
//真正异步
$data = new Get_data_event($server);
$data->onMessage = function($str) use($conn){
fwrite($conn,$str);
};
};
$server->onClose = function($conn){
echo "onClose --" . "\n";
};
$server->run();
第三方服务sleep1.php的代码比较简单
<?php
sleep(1);//模拟耗时
echo "OK";
通过以上代码示例,我们分别注释运行 同步读取,异步读取,真正异步,来观察server的并发.测试方法可以写个test.html来模拟三个并发.
<script src="http://127.0.0.1:1215/?id=1"></script>
<script src="http://127.0.0.1:1215/?id=2"></script>
<script src="http://127.0.0.1:1215/?id=3"></script>
通过测试发现,真正异步的是并发的,每个请求耗时1秒,这样我们总算明白什么是真正的非阻塞异步编程了,关键就在共用IO复用.
大家都知道redis是用C来实现的,现在我用php来实现一个简单的仅支持SET和GET命令的redis服务端,主要是为了更好的了解redis的服务端和php的网络编程.
代码如下:
<?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 10.170.233.221 -p 1215 -t set -n 80000 -q
看来还是不错的,大家有兴趣可以再实现其他命令!
PHP 调用CURL提示错误:SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed。这个是因为证书过期导致的。
SSL-ERROR
需要到 http://curl.haxx.se/ca/cacert.pem 下载信任证书。下载后,使其生效有两种方法:
第一种方法:修改 php.ini,找到 [curl]节点,如果没有,可以在php.ini底部添加。
[curl]
;修改为 http://curl.haxx.se/ca/cacert.pem 保存的路径
curl.cainfo="d:/php/cacert.pem"
第二种方法:保存到 程序所在目录,在调用curl的时候,增加一句:
<?php
#cacert.pem 到 http://curl.haxx.se/ca/cacert.pem 下载
curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
?>
可以使用下面代码,自动识别https或http
function curlPost($url, $data, $timeout = 30)
{
$ssl = substr($url, 0, 8) == "https://" ? TRUE : FALSE;
$ch = curl_init();
$opt = array(
CURLOPT_URL => $url,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_POSTFIELDS => (array)$data,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => $timeout,
);
if ($ssl)
{
$opt[CURLOPT_SSL_VERIFYHOST] = 1;
$opt[CURLOPT_SSL_VERIFYPEER] = FALSE;
}
curl_setopt_array($ch, $opt);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
$data = curlPost('https://www.111cn.net', array('p'=>'hello'));
echo ($data);
不废话上代码,使用方法就是
代码如下 | 复制代码 |
$user_agent = $_SERVER['HTTP_USER_AGENT']; $is_mobile = false; |
网友补充了一个
代码如下 | 复制代码 |
<?php |
国外人喜欢写类,有一个Mobile Detect
Mobile_Detect 简单使用实例
代码如下 | 复制代码 |
include 'Mobile_Detect.php'; |
相关文章
- php语言实现redis的客户端与服务端有一些区别了因为前面介绍过服务端了这里我们来介绍客户端吧,希望文章对各位有帮助。 为了更好的了解redis协议,我们用php来实现...2016-11-25
- 有时我们在页面上需要选择数值范围,如购物时选取价格区间,购买主机时自主选取CPU,内存大小配置等,使用直观的滑块条直接选取想要的数值大小即可,无需手动输入数值,操作简单又方便。HTML首先载入jQuery库文件以及jRange相关...2015-03-15
- 这篇文章主要介绍了详解如何清理redis集群的所有数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18
- 这篇文章主要介绍了Redis连接池配置及初始化实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-29
- 本文实例讲述了JS实现的简洁纵向滑动菜单(滑动门)效果。分享给大家供大家参考,具体如下:这是一款纵向布局的CSS+JavaScript滑动门代码,相当简洁的手法来实现,如果对颜色不满意,你可以试着自己修改CSS代码,这个滑动门将每一...2015-10-21
详解redis desktop manager安装及连接方式
这篇文章主要介绍了redis desktop manager安装及连接方式,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-15- 这篇文章主要介绍了浅谈redis key值内存消耗以及性能影响,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-07
jQuery+slidereveal实现的面板滑动侧边展出效果
我们借助一款jQuery插件:slidereveal.js,可以使用它控制面板左右侧滑出与隐藏等效果,项目地址:https://github.com/nnattawat/slideReveal。如何使用首先在页面中加载jquery库文件和slidereveal.js插件。复制代码 代码如...2015-03-15- 最近在工作中遇到了一个问题,通过查找相关资料才得知原因是因为返回结果的问题,下面这篇文章主要给大家介绍了关于lua读取redis数据的null判断的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2020-06-30
- 翻板抽奖的实现流程:前端页面提供6个方块,用数字1-6依次表示6个不同的方块,当抽奖者点击6个方块中的某一块时,方块翻转到背面,显示抽奖中奖信息。看似简单的一个操作过程,却包含着WEB技术的很多知识面,所以本文的读者应该熟...2015-10-21
- 这篇文章主要介绍了SpringBoot集成Redis实现消息队列的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-10
redis setIfAbsent和setnx的区别与使用说明
这篇文章主要介绍了redis setIfAbsent和setnx的区别与使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-04SQLMAP结合Meterpreter实现注入渗透返回shell
sqlmap 是一个自动SQL 射入工具。它是可胜任执行一个广泛的数据库管理系统后端指印, 检索遥远的DBMS 数据库等,下面我们来看一个学习例子。 自己搭建一个PHP+MYSQ...2016-11-25- 这篇文章主要介绍了Redis的Expire与Setex区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-15
- 复制代码 代码如下: // 第一种写法 $da = date("w"); if( $da == "1" ){ echo "今天是星期一"; }else if( $da == "2" ){ echo "今天是星期二"; }else if( $da == "3" ){ echo "今天是星期三"; }else if( $da == "4"...2013-10-04
- js里面设置DOM节点透明度的函数属性:filter= "alpha(opacity=" + value+ ")"(兼容ie)和opacity=value/100(兼容FF和GG)。 先来看看设置透明度的兼容性代码: 复制代码 代码如下: function setOpacity(ele, opacity) { if (...2014-06-07
- 这篇文章主要介绍了微信小程序(应用号)开发新闻客户端实例的相关资料,需要的朋友可以参考下...2016-10-25
- Redis 是一个开源、高性能的Key-Value数据库,被广泛应用在服务器各种场景中。本文介绍几个查看Redis内存信息的命令,包括常用的info memory、info keyspace、bigkeys等。...2021-01-15
- 这篇文章主要介绍了在页面中输出当前客户端时间javascript实例代码的相关资料,需要的朋友可以参考下...2016-03-03
- 网上有关蓝牙接收的资料很多,使用起来也很简单,但是我觉得还是有必要把这些知识总结下来,蓝牙开发需要用到一个第三方的库InTheHand.Net.Personal.dll,感兴趣的朋友可以了解下,或许对你有所帮助...2020-06-25