反序列化存入数据库里面的session数据

session数据存取的方法可通过session.serialize_handler方法来判断,反序列化可通过下面的unserialize方法,参考http://stackoverflow.com/questions/27813619/unserializing-php-session-data-from-db-table

public static function unserialize($session_data) {
    $method = ini_get("session.serialize_handler");
    switch ($method) {
        case "php":
            return self::unserialize_php($session_data);
            break;
        case "php_binary":
            return self::unserialize_phpbinary($session_data);
            break;
        default:
            throw new Exception("Unsupported session.serialize_handler: " . $method . ". Supported: php, php_binary");
    }
}

private static function unserialize_php($session_data) {
    $return_data = array();
    $offset = 0;
    while ($offset < strlen($session_data)) {
        if (!strstr(substr($session_data, $offset), "|")) {
            throw new Exception("invalid data, remaining: " . substr($session_data, $offset));
        }
        $pos = strpos($session_data, "|", $offset);
        $num = $pos - $offset;
        $varname = substr($session_data, $offset, $num);
        $offset += $num + 1;
        $data = unserialize(substr($session_data, $offset));
        $return_data[$varname] = $data;
        $offset += strlen(serialize($data));
    }
    return $return_data;
}

private static function unserialize_phpbinary($session_data) {
    $return_data = array();
    $offset = 0;
    while ($offset < strlen($session_data)) {
        $num = ord($session_data[$offset]);
        $offset += 1;
        $varname = substr($session_data, $offset, $num);
        $offset += $num;
        $data = unserialize(substr($session_data, $offset));
        $return_data[$varname] = $data;
        $offset += strlen(serialize($data));
    }
    return $return_data;
}

 

MySQL锁机制和PHP锁机制

模拟准备–如何模拟高并发访问一个脚本:apache安装文件的bin/ab.exe可以模拟并发量 -c 模拟多少并发量 -n 一共请求多少次 http://请求的脚本
例如:cmd: apache安装路径/bin/ab.exe -c 10 -n 10 http://web.test.com/test.php

【切入正题】
MYSQL中的锁:
语法 :
LOCK TABLE 表名1 READ|WRITE, 表名2 READ|WRITE ……………… 【锁表】
UNLOCK TABLES 【释放表】

Read:读锁|共享锁 : 所有的客户端只能读这个表不能写这个表
Write:写锁|排它锁: 所有当前锁定客户端可以操作这个表,其他客户端只能阻塞
注意:在锁表的过程中只能操作被锁定的表,如果要操作其他表,必须把所有要操作的表都锁定起来!

PHP中的文件锁 (锁的是文件,不是表)
文件锁的文件与表有什么关系?:一点关系也没有,与令牌相似,谁拿到谁操作。所以表根本没锁。
测试时,有个文件就行,叫什么名无所谓。

总结:
项目中应该只使用PHP中的文件锁,尽量避免锁表,因为如果表被锁定了,那么整个网站中所有和这个表相关的功能都被拖慢了(例如:前台很多用户一直下订单,商品表mysql锁表,其他与商品表相关的操作一直处于阻塞状态【读不出来商品表】,因为一个功能把整个网站速度拖慢)。

我的一个项目就是O2O外卖,中午12-2点,晚上6点都是订单高并发时,这种情况下,MySQL锁显然是不考虑的,用户体验太差。其实根据实际的需求,外卖可以不用设计库存量的,当然除了秒杀活动模块还是需要php文件锁的。

应用场景:
1. 高并发下单时,减库存量时要加锁
2. 高并发抢单、抢票时要使用

MySQL锁示例代码:

    <?php  
    /** 
    模拟秒杀活动-- 商品100件 
    CREATE TABLE a 
    ( 
        id int comment '模拟100件活动商品的数量' 
    ); 
    INSERT INTO a VALUES(100); 
    模仿:以10的并发量访问这个脚本!    使用apache自带的ab.exe软件 
     */  
    error_reporting(0);  
    mysql_connect('localhost','root','admin123');  
    mysql_select_db('test');  
      
    # mysql 锁  
    mysql_query('LOCK TABLE a WRITE');// 只有一个客户端可以锁定表,其他客户端阻塞在这  
    $rs = mysql_query('SELECT id FROM a');  
    $id = mysql_result($rs, 0, 0);  
    if($id > 0)  
    {  
        --$id;  
        mysql_query('UPDATE a SET id='.$id);  
    }  
      
    # mysql 解锁  
    mysql_query('UNLOCK TABLES');  

PHP文件锁示例代码:

    <?php  
    /** 
    模拟秒杀活动-- 商品100件 
    CREATE TABLE a 
    ( 
        id int comment '模拟100件活动商品的数量' 
    ); 
    INSERT INTO a VALUES(100); 
    模仿:以10的并发量访问这个脚本!    使用apache自带的ab.exe软件 
     */  
    error_reporting(0);  
    mysql_connect('localhost','root','admin123');  
    mysql_select_db('test');  
    # php中的文件锁  
    $fp = fopen('./a.lock', 'r'); // php的文件锁和表没关系,随便一个文件即可  
    flock($fp, LOCK_EX);// 排他锁  
      
      
    $rs = mysql_query('SELECT id FROM a');  
    $id = mysql_result($rs, 0, 0);  
    if($id > 0)  
    {  
        --$id;  
        mysql_query('UPDATE a SET id='.$id);  
    }  
    # php的文件锁,释放锁  
    flock($fp, LOCK_UN);  
    fclose($fp);  

其实还有一种最简单的方案,一个sql就可以解决这个事情。

@$mysql = mysql_connect('localhost','root','');
mysql_query('set names utf8');
mysql_select_db('test');
mysql_query('UPDATE warehouse SET `stock` = `stock` -1 WHERE `stock` > 0');  //可以避免库存为负数

测试的方法是,找到Apache下的ab.exe,拖入CMD终端,然后输入指定参数测试。

具体参数说明Google一下你就知道,比如耗时之类的…这里不做详细说明。

 

其他参考:

php mysql 锁表和解锁详细语句http://blog.sina.com.cn/s/blog_6e637ea701016r01.html

mysql锁(行锁,表锁)同一用户同一秒操作保持唯一性https://blog.csdn.net/webnoties/article/details/22874431

MySQL锁机制和PHP锁机制 http://phpkim.iteye.com/blog/2294464

Mysql的锁机制与PHP文件锁处理高并发简单思路https://www.cnblogs.com/yuandongdong/p/7560327.html

在不改变php.ini中 session.save_handler = files 的前提下,实现不生成文件session,后期可写入到数据库。

<?php
//原创:xuduowei,想学习php开发的可以找我。微信号:weilanweb
class session_mysql{
		//private static $db=null;
		private static $ip=null;
		private static $lifetime=null;
		private static $time=null;

		private static function init(){
			//self::$db=$db;
			self::$ip=!empty($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "unknown";
			self::$lifetime=ini_get('session.gc_maxlifetime');
			self::$time=time();
		}

		public static function start(){
			self::init();
			
			session_set_save_handler(
				array(__CLASS__, "open"),
				array(__CLASS__, "close"),
				array(__CLASS__, "read"),
				array(__CLASS__, "write"),
				array(__CLASS__, "destroy"),
				array(__CLASS__, "gc")
			);
			session_start();
		}

		public static function open($path, $name){
			//return true;
		}
	
		public static function close() {
			//return true;
		}
		
		public static function read($id){
			echo "read读取信息<br>";			
		}
		
		public static function write($id ,$data){
			echo "write写信息<br>";			
			//return true;
		}
		
		public static function destroy($id){
			echo "destroy清除信息<br>";
			
		}
		
		private static function gc($lifetime){
			echo "gc回收信息<br>";
		
		}

	
}

session_mysql::start();
echo session_name().'='.session_id()."<br>";
?>

输出结果:

read读取信息
PHPSESSID=2jo24tkjjqgu8jq1p6u2aijnd3
write写信息
=========================================
以phpstudy环境为例:默认的J:\phpStudy\tmp\tmp session存储路径下并不会有session文件的存储。php.ini配置文件中依然是默认的:session.save_handler = files
=========================================

这样做的目的就是方便后期把session写入数据库中。

以下为数据库融合版【xuduowei 原创】:

class db{
	var $conn;
	function __construct(){
		$conn=mysqli_connect("主机","用户名","密码");//为了方便,我这直接写。
		if(!$conn){
			echo "数据库连接失败";
			die;
		}
		mysqli_select_db($conn,"数据库");//为了方便,我这直接写。
		$this->conn=$conn;	
		//return $conn;
	}

	function select($sql){	
		$rst=mysqli_query($this->conn,$sql);
		$arr=array();
		while($rs=mysqli_fetch_assoc($rst)){
			$arr[]=$rs;
		}
		return $arr;
	}

	function query($sql){
		//用户数据库的删除操作	
		return mysqli_query($this->conn,$sql);		
	}
}//end db


class session_mysql{
		private static $db=null;
		private static $ip=null;
		private static $lifetime=null;
		private static $time=null;

		private static function init($db){
			self::$db=$db;
			self::$ip=!empty($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "unknown";
			self::$lifetime=ini_get('session.gc_maxlifetime');
			self::$time=time();
		}

		public static function start($db){
			self::init($db);
			
			session_set_save_handler(
				array(__CLASS__, "open"),
				array(__CLASS__, "close"),
				array(__CLASS__, "read"),
				array(__CLASS__, "write"),
				array(__CLASS__, "destroy"),
				array(__CLASS__, "gc")
			);
			session_start();
		}

		public static function open($path, $name){
			return true;
		}
	
		public static function close() {
			return true;
		}
		
		public static function read($id){
				echo "read读取信息<br>";
			
				$sql="select sessionid,updatetime,data from xdw_session where sessionid='".$id."'";	
				$result=self::$db->select($sql);
				if(!$result[0]){//若没有值,就返回空。
					return "";
				}

				if ((strtotime($result[0]["updatetime"])+self::$lifetime) < self::$time){
					//echo "时间过期";
					self::destroy($id);
					return '';
				}

				return $result[0]['data'];
		 
				
		}

	
		public static function write($id ,$data){//sessionid和session 值
			echo "write写信息<br>";	

			$sql="select sessionid,updatetime,data from xdw_session where sessionid='".$id."'";	
			
			$result=self::$db->select($sql);
			//若有值
			if($result[0]){
				//若session内容不一致,或者超出一段时间300秒了【还没有过期】。我们就重新更新一次。
				if($result[0]['data'] != $data || self::$time > (strtotime($result[0]['updatetime'])+300)){
					$sql="update xdw_session set updatetime = '".date("Y-m-d H:i:s")."', data ='".$data."' where sessionid ='".$id."'";
					self::$db->query($sql);
				}

			}else{//若没有值,就所以这个sessionid不存在。我们就重新写入一次。
				$sql="insert into xdw_session set updatetime = '".date("Y-m-d H:i:s")."', data ='".$data."' ,sessionid ='".$id."'";
				self::$db->query($sql);
			}

			return true;
		}
	

		public static function destroy($id){
			echo "destroy清除信息<br>";				
			$sql="delete from xdw_session where sessionid ='".$id."'";			
			self::$db->query($sql);

			return true;				
			
		}
		
		private static function gc($lifetime){
			echo "gc回收信息<br>";//也就是删除过期的

			$time=self::$time-$lifetime;
			$sql = "delete from xdw_session where unix_timestamp(updatetime) < ".$time;
			self::$db->query($sql);		
			return true;
		
		}

	
}

session_mysql::start(new db());
echo session_name().'='.session_id()."<br>";

$_SESSION['username']="xuduowei";
附上数据库表结构:
#有想学习php开发的,可以联系我。电话:15309695130  php著名老师:xuduowei

DROP TABLE IF EXISTS `xdw_session`;
CREATE TABLE `xdw_session` (
  `session_id` varchar(32) NOT NULL,
  `data` text NOT NULL,
  `expire` datetime NOT NULL,
  PRIMARY KEY (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

xuduowei 原创作品,请勿转载,谢谢合作。

php 控制同一个用户只能同时一个人在线

有时候项目要求:控制同一个用户只能同时一个人在线

网上有说到很多种方法。不管哪种方法,都要注意2个特殊情况:

1、关闭浏览器;2、电脑断电【计算机系统中断】;

徐多蔚,徐老师,合肥php老师 这里分享一个稳定,高效,安全的解决方案:

核心效果:类似QQ登录一样,以最后一次登录为准。

若你当前在A电脑登录中【login_session 表记录:session_id,登录名,状态=1】,然后又在B电脑登录【首先:删除login_session表中和当前用户名同名的记录,或者更新和当前用户名同名的记录登陆状态为0;然后login_session 表记录:session_id,登录名,状态=1】,当在A电脑操作的时候,会判断当前登录状态为1才可以操作。这个时候发现为0了,则提示你的账号在其他地方登陆。

徐多蔚,徐老师,合肥php老师

ajax中获得处理接口传过来的xml

$.ajax({
   type: "POST",
   url:"http://127.0.0.1/jk/xml.php",
 
   data: "",
   success: function(xml){
			obj=$(xml).find("teacher");
			alert(obj.text());//弹出内容 xuduowei
			


   }
});

xml.php 接口 code:

<?php
$xmlstring=<<<XML

 
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<student>xue</student>
<teacher>xuduowei</teacher>
</persons>
 
XML;

echo $xmlstring;
?>

 

ajax中 json字符串或者json对象的处理。

若对应的json传递过来的是json对象【php中json_encode($arr) 就是json对象】。

ajax,js中,可以用 var json=JSON.parse(msg);处理转成数组。注意:JSON.parse兼容性不好,还是推荐用:eval(“(“+msg+”)”);转成对象。

 

但是若接口文件php中直接输出的是json字符串,则我们需要使用:

//我们需要将eval()方法将这个字符串包一下,就可以转成JSON对象了

var json = eval(‘(‘ + msg + ‘)’);

如果用错了,会报错的!

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON

一定要区分开!

 

模拟JS代码如下:

<script src="jquery.min.js"></script>
<script>
	$.ajax({
   type: "POST",
   url:"http://127.0.0.1/jk/json.php",
  //url: "https://api.miaodiyun.com/20150822/industrySMS/sendSMS",
 //  data: "accountSid=c754405c64bd493188b56a5b781dc5ff&smsContent=【秒嘀科技】您的验证码是345678,30分钟输入有效。    &to=13896543210&timestamp=20150821100312&sig=96d3adac3d2f43b8a85b0aeb201ab2be&respDataType=JSON",
   success: function(msg){
    // alert( "Data Saved: " + msg );
			//var json=JSON.parse(msg);//这个不行,用下面的
		
			var json = eval('(' + msg + ')');
			// alert(json.respDesc);
			 var respDesc=json.respDesc;
				alert(respDesc);
			// if(respDesc=="成功"){
				//alert('接收成功');
			// }

			//alert(11);

   }
});
</script>

模拟接口文件代码:

<?php
$str=<<<xdw
{
"respCode":"00000",
"respDesc":"成功",
"failCount":"1",
"failList":
[
    {
        "phone":"13896543210",
        "respCode":"00111",
        "respDesc":"匹配到黑名单",
    }
],
"smsId":"913945fec0204b1e94baa75a5c013f59"
}
xdw;


echo $str;//这里输出的是json字符串,不是对象。


?>

 

2个自定义的PHP in_array 函数,解决大量数据判断in_array的效率问题

大家可能都用过in_array来判断一个数据是否在一个数组中,一般我们的数组可能数据都比较小,对性能没什么影响,所以也就不会太在意。

但是如果数组比较大的时候,性能就会下降,运行的就会久一点,那如果针对在大数组情况下做优化呢,下面说两种方法(都是通过自定义函数来实现):

1.数组key与value翻转,通过isset判断key是否存在于数组中

/**
 * in_array is too slow when array is large
 */
function inArray($item, $array) {
    $flipArray = array_flip($array);
    return isset($flipArray[$item]);
}

大家可能也会问为什么不用 array_key_exists 来做判断二用isset呢? 下面看下array_key_exists() 与 isset() 的对比:
isset()对于数组中为NULL的值不会返回TRUE,而array_key_exists()会。

<?php
$search_array = array('first' => null, 'second' => 4);
// returns false
isset($search_array['first']);
// returns true
array_key_exists('first', $search_array);
?>

2.用implode连接,直接用strpos判断

用implode函数+逗号连起来,直接用strpos判断。php里面字符串取位置速度非常快,尤其是在大数据量的情况下。不过需要注意的是首尾都要加”,” ,这样比较严谨。如: ,user1,user2,user3, 查找的时候,查,user1,。还有strpos要用!== false,因为第一个会返回0。示例如下:

return strpos($str,(string)$item)==false?false:true;**
 * in_array is too slow when array is large
 */
function inArray($item, $array) {
    $str = implode(',', $array);    
    return strpos($str,(string)$item)==false?false:true;
}

提示:以上2个函数均有bug!方法一数组翻转中,若原始数组值有null的,是会出问题的;方法二的只能验证字符串类型的。无法验证整形数字。总结:还是in_array($str,$arr,true) 验证方式最安全可靠。虽然性能相对底一点。

 

 

 

php自带的sort排序和用php实现排序算法的性能比较?

我今天特地试验了一下两者的性能
php自带的排序函数 100000的数据 排序 平均耗时0.068s
microtime() //php自带排序sort()耗时:0.12000608444214 
返回当前 Unix 时间戳的微秒数。
 for ($i = 0; $i<100000;$i++){

    $arr[] = rand(0,10000);
}

 $t1 = microtime(true);

sort($arr);

$t2 = microtime(true);
echo "php自带排序sort()耗时:".($t2-$t1); 

自己写的快速排序 平均耗时1.0s

//快速排序耗时:1.7631008625031

 for ($i = 0; $i<100000;$i++){

    $arr[] = rand(0,100000);
}

 $t1 = microtime(true);

$returnAr = quickSort($arr);

$t2 = microtime(true);
echo "快速排序耗时:".($t2-$t1); 
//快速排序
function quickSort($arr) {
    //先判断是否需要继续进行
    $length = count($arr);
    if($length <= 1) {
        return $arr;
    }
    //选择第一个元素作为基准
    $base_num = $arr[0];
    //遍历除了标尺外的所有元素,按照大小关系放入两个数组内
    //初始化两个数组
    $left_array = array();  //小于基准的
    $right_array = array();  //大于基准的
    for($i=1; $i<$length; $i++) {
        if($base_num > $arr[$i]) {
            //放入左边数组
            $left_array[] = $arr[$i];
        } else {
            //放入右边
            $right_array[] = $arr[$i];
        }
    }
    //再分别对左边和右边的数组进行相同的排序处理方式递归调用这个函数
    $left_array = quickSort($left_array);
    $right_array = quickSort($right_array);
    //合并
    return array_merge($left_array, array($base_num), $right_array);
}

明显是php自带的函数排序速度快很多。
但重点是,为什么还有那么多问题是问 如何用php实现快速排序等算法?

php四排序-冒泡排序

算法和数据结构是一个编程工作人员的内功,技术牛不牛,一般都会看这两点。作为php程序员, 提升技能当然也得学习算法。

下面介绍四种入门级排序算法: 冒泡排序、选择排序、插入排序、快速排序。

一、冒泡排序

原理:对一组数据,比较相邻数据的大小,将值小数据在前面,值大的数据放在后面。   (以下都是升序排列,即从小到大排列)

举例说明: $arr = array(6, 3, 8, 2, 9, 1);

$arr 有6个数据,按照两两比较大小如下,注意  比较轮数 和 每轮比较次数

 第一轮排序:

第一次比较  6和3比较 结果:3    6   8   2   9   1

第二次比较  6和8比较 结果:3    6   8   2   9   1

第三次比较  8和2比较 结果:3    6   2   8   9   1

第四次比较  8和9比较 结果:3    6   2   8   9   1

第五次比较  9和1比较 结果:3    6   2   8   1   9

第一轮比较总结:1.排序第1轮、比较5次,没有获得从小到大的排序   2.因为每次比较都是大数往后靠,所以比较完成后,可以确定大数排在最后(9 已经冒泡冒出来了,下轮比较可以不用比较了 )

第二轮排序:

第一次比较  3和6比较 结果:3    6   2   8   1   9

第二次比较  6和2比较 结果:3    2   6   8   1   9

第三次比较  6和8比较 结果:3    2   6   8   1   9

第四次比较  8和1比较 结果:3    2   6   1   8   9

第二轮比较总结:1.排序第2轮、比较4次,没有获得从小到大的排序   2.冒泡出了 8,下轮不用比较8 了

第三轮排序:

第一次比较  3和2比较 结果:2    3   6   1   8   9

第二次比较  3和6比较 结果:2    3   6   1   8   9

第三次比较  6和1比较 结果:2    3   1   6   8   9

第三轮比较总结:1.排序第3轮、比较3次,没有获得从小到大的排序   2.冒泡出了 6,下轮不用比较6 了

  第四轮排序:

第一次比较  2和3比较 结果:2    3   1   6   8   9

第二次比较  3和1比较 结果:2    1   3   6   8   9

第四轮比较总结:1.排序第4轮、比较2次,没有获得从小到大的排序   2.冒泡出了 3,下轮不用比较3 了

  第五轮排序:

第一次比较  2和1比较 结果:1   2   3   6   8   9

第五轮比较总结:1.排序第5轮、比较1次,没有获得从小到大的排序   2.冒泡出了 2,由于还剩一个1,不用再比较了,至此通过5轮排序,完成整个排序。

  通过以上五轮排序,若干次比较,我们有理由推断出一个结论:

  对于一个长度为N的数组,我们需要排序 N-1 轮,每 i 轮 要比较 N-i 次。对此我们可以用双重循环语句,外层循环控制循环轮次,内层循环控制每轮的比较次数。

<?php 

function getpao($arr)
{ 
 $len=count($arr);
 //设置一个空数组 用来接收冒出来的泡
 //该层循环控制 需要冒泡的轮数
 for($i=1;$i<$len;$i++)
 { //该层循环用来控制每轮 冒出一个数 需要比较的次数
 for($k=0;$k<$len-$i;$k++)
 {
 if($arr[$k]>$arr[$k+1])
 {
 $tmp=$arr[$k+1];
 $arr[$k+1]=$arr[$k];
 $arr[$k]=$tmp;
 }
 }
 }
 return $arr;
}


 $arr= array(6,3,8,2,9,1);
$res =  getpao($arr);
print_r($res);