Author Archives: Jerry Wang

About Jerry Wang

谷粉,码农,教师

在CentOS Linux上安装和配置 PPTP VPN 客户端

最近的一个项目需要在CentOS上安装PPTP的vpn客户端,随手搜索了一下,各种说法五花八门,自己弄了半天才把PPTP client装好,为了防止再次遗忘,写个博文记下来吧。

系统: Linux  CentOS 6 /7, 步骤如下:

1:安装PPTP 客户端

sudo yum install pptp

2. 修改 /etc/ppp/chap-secrets文件  增加如下一行,该文件主要用来保存vpn用户的密码 (该文件权限限制很高)

 [用户名]  PPTPServer    [密码]  *  

其中用户名和密码是你要连接的VPN的账号和密码,  “PPTPServer” 表示远端PPTP服务器的名字,你可以用自己喜欢的名字,比如VPNServer。   *表示允许任何IP

3. 在 /etc/ppp/peers目录下创建一个配置文件,文件名为myvpn (或者其他名字也行,这个名字是后面我们要用来连接的VPN 名字)

并在该文件中输入如下内容:


pty "pptp  [VPN服务器地址,如205.22.33.44]  --nolaunchpppd"

name [用户名]

remotename PPTPServer  (注意,这个名字要对应于chap-secrets文件中的 PPTP服务器的名字!)

ipparam myvpn  (这个名字和文件名相同)

require-mppe-128

file /etc/ppp/options.pptp

4. 修改/etc/ppp/options.pptp文件, 确保下面的语句没有被注释掉


lock

noauth

refuse-pap

refuse-eap

refuse-chap

nobsdcomp

nodeflate

5.  注册ppp-mppe核心模块,运行

sudo modprobe ppp-mppe

6. 完成! 测试一下:

sudo pon myvpn
ip a | grep ppp

这时候应该看到类似的信息

[root@atl-vps ppp]# ip a | grep ppp
19: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1488 qdisc pfifo_fast state UNKNOWN qlen 3
    link/ppp
    inet 198.23.230.10 peer 10.255.254.0/32 scope global ppp0

如果有问题,可以通过 tail  /var/log/messages 来查看错误信息。

如何反编译Android 的apk/dex/odex,获得源码

最近因为工作的需要,要查看一下某个应用的源代码。本来我就不是做Android开发的,对这个也不熟悉,只好用Google反复查来查去,终于算是搞定了,在这里特地记下来,供自己也供其他人参考。

 

关于APK,DEX的介绍

当我们编译一个安卓项目的时候,整个项目会被打包成一个 .apk文件。这个文件其实是一个标准的zip文件,因此可以用解压缩工具打开。这个apk文件一般都包含程序的代码(在classes.dex文件中), 资源文件,  证书, manifest 文件等。 其中对我们最重要的是classes.dex文件,因为编译后的字节码(bytecode)都是放在这个文件中。我们后面讲的反编译就是针对这个dex文件来的。

反编译普通的APK文件:

对于普通的APK/DEX文件的反编译,其实工具有很多, 包括:

  1. ByteCode Viewer: 一个可视化的集成工具,说实话,不太好用,不够稳定,生成代码质量中等。
  2. dex2jar + jd_gui: 这两个工具组合还可以, 用起来比ByteCode Viewer麻烦一些,但比较稳定,生成代码质量中等。
  3. 在线反编译工具JADX: http://www.javadecompilers.com/apk ,  这是基于SourceForge上的JADX的开源工具来实现的。本来以为在线反编译质量不会好,但出人意料的是:JADX是我发现的最好的反编译工具, 不但使用简单(直接上传,转换,下载就ok),而且反编译出来的代码质量很高,特别是变量命名方面,可读性很不错。

反编译ODEX文件:

Android 5.0 Lollipop以后,Google用ART代替了以前的Dalvik,对于普通的app来说我们仍然可以用上面的方法来把dex文件反编译成Java源代码。但对于系统预装的App,特别是类似应用商店,播放器等, 你会发现这些应用的apk文件中找不到对应的classes.dex文件,而是会发现在其子目录下有个.odex文件。 那如何反编译这个odex文件呢?我通过google查了查,知道应该用baksmali,但从github上下载了几个版本都不行,报各种不同错误。经过反复搜索和尝试,终于找到了这篇文章 :  http://www.naldotech.com/how-to-deodex-applications-on-android-5-0-lollipop/   。 具体方法如下:

1.  从这里下载工具包, 解压缩到本地。 这里的baksmali的版本是2.0.3.   不同版本的baksmali针对的Android内核不同。有时候高版本反倒不好用。

2. 打开工具所在目录, 按住shift键, 点击鼠标右键,打开windows命令窗口

3. 把 odex文件拷贝到该目录

4. 在命令窗口运行: oat2dex.bat  *.odex.    正常情况下,应该显示OK等信息。如果报错的话,说明这个文件无法转换,后面的也不用试了。

5. 运行 oat2dex.bat *.odex temp.dex .   运行后会创建一个temp blog link.dex文件。

6. 运行 java -jar baksmali-2.0.3.jar -a 21 -x temp.dex -o source  . 运行后会创建一个source的文件夹,并将temp.dex反编译到该文件夹。-a 21 表明的是Android内核的版本21

7. 运行 java -jar small-2.0.3.jar -a 21 source -o classes.dex ,  反编译为classes.dex文件。

需要注意的是:由这种方式反编译成的classes.dex 文件相比原生的classes.dex 还是缺少了些信息,因此反编译这种classes.dex  文件后生成的java代码可读性会更差些。

8. 用在线工具JADX 来把 classes.dex  最终反编译为java代码。

 

 

PHP正则表达式的使用技巧

初学php,需要做一些信息抓取的工作,刚开始用的是Simple-Html-Dom做的网页的解析,发现虽然使用起来会比较方法,但是缺点也是致命的,速度慢,控制不精准,后来也接触了Snoopy,效果还是不怎么好,所以选择了使用Curl+正则来做网页的抓取和解析工作。

function dangdang ($keywords)
{
//$keywords="java";//搜索关键词
$urls='http://search.dangdang.com/?key='.$keywords.'&category_path=01.00.00.00.00.00&type=01.00.00.00.00.00/';
//echo $urls;
$st=curl_init();
curl_setopt($st, CURLOPT_URL, $urls);
curl_setopt($st, CURLOPT_RETURNTRANSFER,1);//返回内容为字符串
curl_setopt($st,CURLOPT_FOLLOWLOCATION,TRUE);
curl_setopt($st,CURLOPT_MAXREDIRS,2);//设置重定向最大两次
curl_setopt($st,CURLOPT_TIMEOUT,100);//等待时间不超过五秒
$dl_page=curl_exec($st);
$info_arr=curl_getinfo($st);
//var_dump($info_arr);
preg_match_all("/<a title=.*/",$dl_page,$matches_all);
//var_dump($matches_all);
$num= count($matches_all[0]);
for($count=0;$count<$num;$count++)
{
echo $count.'<br>';
preg_match_all("/\btitle\b=\"([^\"]*)\"\s*\bclass\b/",$matches_all[0][$count],$matches_title);
echo($matches_title[1][0]);
echo '<br>';
preg_match_all("/\bhttp\b\:\/\/img.*\bjpg\b/",$matches_all[0][$count],$matches_img);
echo ('<img src="'.$matches_img[0][0].'">');
echo '<br>';
preg_match_all("/\bhttp\b([^A]*)\bA\b/",$matches_all[0][$count],$matches_url);
echo($matches_url[0][0]);
echo '<br>';
preg_match_all("/\bhttp\b\:\/\/\bcomm\b([^\"]*)/",$matches_all[0][$count],$matches_plurl);
//preg_match_all("/<a\s*\bhref\b=\"([^\"]*)\"/",$matches_all[0][$count],$matches_plurl);
//var_dump($matches_plurl);//评论url
echo($matches_plurl[0][0]);
echo '<br>';
preg_match_all("/\bdetail\b\"\s*\>([^\<]*)\</",$matches_all[0][$count],$matches_jianjie);
echo($matches_jianjie[1][0]);
echo '<br>';
preg_match_all("/\&\byen\b\;\d+\.\d\d/",$matches_all[0][$count],$matches_price);
preg_match_all("/\d+\.\d\d/",$matches_price[0][0],$matches_price);
echo($matches_price[0][0]);
//echo($matches_price[0][0]);
echo '<br>';
preg_match_all("/num\b\"\>\d*/",$matches_all[0][$count],$matches_pinglun);
preg_match_all("/\d+/",$matches_pinglun[0][0],$matches_pinglun);
echo($matches_pinglun[0][0]);
echo '<br>';
preg_match_all("/\btitle\b=\'([^\']*)\'/",$matches_all[0][$count],$matches_price);
echo($matches_price[1][0]);
echo '<br>';
}
}

以上代码实现了对当当网搜索数据的抓取和分析,取出了书名,作者价格简介等信息,下面介绍一下正则的使用技巧。

以亚马逊网站为例:

查看亚马逊网站搜索结果页面的源码,找到搜索商品结果部分代码

<img class="alignnone size-medium wp-image-1168" src="http://www best task manager.androiddev.net/wp-content/uploads/2014/11/亚马逊-300×155.png” alt=”亚马逊” width=”300″ height=”155″ srcset=”https://www.androiddev.net/wp-content/uploads/2014/11/亚马逊-300×155.png 300w, https://www.androiddev.net/wp-content/uploads/2014/11/亚马逊-1024×531.png 1024w, https://www.androiddev.net/wp-content/uploads/2014/11/亚马逊-660×342.png 660w, https://www.androiddev.net/wp-content/uploads/2014/11/亚马逊.png 1240w” sizes=”(max-width: 300px) 100vw, 300px” />看到这个源码后相信大多数人头都大了,我们可以仔细分析网站页面的特点,我们发现每个商品的信息都是保存在li标签里面,而且每个li都有自己固定的IDli,这就是我们的突破口,即匹配li和ID即可确定每个商品的位置,还需要注意的是在每个li标签中还有换行和空行这样的地方普通的.*模糊匹配无法匹配,所以在进行模糊匹配时还需要进行换行\n和空行的匹配

preg_match_all("/\<\bli\b\s+\bid\b=\"result_[0-9]*.*([.\n]*)\\n.*\\n.*\\n[\\s\| ]*.*/",$dl_page,$matches_all);

这样就可以确定每个商品的信息,然后再进行商品名称和价格的匹配,这样可以减少错误的匹配。

 

 

参加Google DevFest大会有感

前些天,有幸被邀请参加今年Google的开发者的DevFest 大会, 北京的DevFest在北航的主楼会议中心举办, 头天晚上半夜才赶到北京, 大会当天晚上又连夜赶回烟台,在北京只待了一整天,但是收获还是不小。

这次DevFest 举办者非常用心,特别是Guokai Han, 可以看出花了非常多的精力和时间来准备。 Google中国公司为这次大会准备了不少礼物。 大会总共来了超过600名Google开发者,很高兴认识了很多有名的开源coder。

大会的主题是Android Wear 和 HTML5 这两个方向。前前后后有20多场技术讲座,我也应邀做了个Google Apps开发和CubeBackup产品介绍的闪电演讲。 这次北京之行,不但在技术方向上有了新的认识,而且真正见识了北京这种狂热的创新和创业的氛围。

废话不说,上几张图, 如果大家对Google的活动有兴趣,可以关注 Google中国开发者社区

4Y7A8649

我在台上做产品和技术介绍

 

all

组织人员和讲师的合影, 里面不少牛人 。

 

使用PHPMailer实现可视化邮件群发器

一.平台与工具

1.Ubuntu  14.04 LTS

2.Apache/2.4.7(Ubuntu)

3.PHP 5-5-9 下载页面  版本最好要>5.2.0,要用到json_encode()函数来编码json数据

4.jQuery 1.11.1 下载页面 javascript的一个函数库,文档出色

5.Bootstrap 3.2.0  下载页面  主要用到它的css库,几乎每个主要标签的样式都有好几种风格,美工的福音:-)

6.PHPMailer 下载页面  PHP的一个扩展库,用于更安全和容易的收发送邮件,这次用到的文件有class.phpmailer.php和class.smtp.php

二.实现思路

image

三.实现要点

1.将常用的或关键信息单独放在配置文件中
将必要的信息用php文件来保存,当需要的时候使用require或include包含进来即可(关于require和include的区别,可以看http://developer.51cto.com/art/200909/153687.htm
发件邮箱可以使用二维数组来存储,易于以后扩展及更改,如

    $address1 = array('mailhost' => 'smtp.gmail.com' ,
    'mailport' => '587', 'smtpsecure' => 'tls' , 'address' 
    => 'xxx@gmail.com' , 'pwd' => 'xxx');
    $address2 = array('mailhost' => 'smtp.sina.com' ,
    'mailport' => '25' ,'smtpsecure' => '' ,'address' 
    => 'xxx@sina.com', 'pwd' => 'xxx');
    $address3= array('mailhost' => 'smtp.163.com' ,
    'mailport' => '25', 'smtpsecure' => '' , 'address'
     => 'xxx@163.com' , 'pwd' => 'xxx');

    $address = array($address1 , $address2 , $address3 );

用define声明数据库相关信息

define("DATABASE_HOST",'localhost');//数据库信息常量		
define("DATABASE_USER","root");
define("DATABASE_PWD","xxxx");
define("DATABASE_NAME","xxxx");
define("TABLE_ONE","xxxx");		
define("TABLE_TWO","xxxx");

2.异步请求的发送
使用jQuery的ajax对象来实现异步请求,ajax对象的更多参数使用http://api.jquery.com/jquery.ajax/
因为需要php动态的更改查询字符串,以下代码写在php里,用echo输出

echo <<<EOF
var interval = function(){
    sent_times++;   /*请求发送次数自赠1,默认为0*/
    var rand_mail = sent_times % {$mail_count};  /*确定此次请求FROM邮箱的序号*/
    var count_per = {$count_per};  /*每次发送邮件的数量*/
    var request = $.ajax({
    type:GET,  /*要用到查询字符串,故用GET*/
    /*rand_mail:哪个邮箱进行发送,count_per:每次请求发送的邮件数*/
    url:'send_index.php?send_account=' + rand_mail + '&count_per=' + count_per ,  
    async:true,  /*默认为true,表示异步发送,若接下来的操作需要
    用到请求返回的数据,则使用false*/
   success:function(msg){
    if(msg == 'CONN_FAIL'){
        alert('sorry,database connect failed!');
    }
    else if(msg == 'QUERY_NONE'){
        alert('sorry,database hava no record!');
    }
    else{
        /*解析返回的JSON字符串 并用prepend()动态添加到table顶部*/
        var obj = $.parseJSON(msg);  
        for(var i =0;i<obj.length;i++){
            $(".table").prepend("<tr><td>" + obj[i].mail_from +"</td>
            <td>" + obj[i].mail_to + 
            "</td><td><button onclick='javascript:void(0);' 
            class='btn btn-primary'>" + obj[i].mail_status + "</button></td></tr>");
    }}
  });
});
EOF;

3.邮件发送
注意PHP全局变量的作用域不包括函数,如要在函数内部使用全局变量,方法一:传参,方法二,在函数内的变量前使用global修饰或使用$GLOBALS[‘变量名’]进行关联,此处使用方法一

function sendMail($mailhost,$mailport,$smtpsecure,$addr,
$pwd,$to,$firstname,$mysqli,$table,$subject) {
    /*检验To:mail有效性,减少发送至无效邮箱进入黑名单的风险*/
    if(!filter_var($to,FILTER_VALIDATE_EMAIL)){
        /*将无效电邮对应数据库记录中的isvalid字段置为0*/
        $sql = "UPDATE $table SET isvalid = 0  where address = '$to'";
	$mysqli->query($sql);
	return;
     }
    $mail = new PHPMailer();    /*实例化PHPMailer对象*/
    $mail->IsSMTP();    /*使用SMTP协议进行发送*/
    $mail->CharSet="UTF-8";	    /*字符集*/
    $mail->Encoding = "base64"; /*字符编码*/
    $mail->SMTPSecure = $smtpsecure;		
    $mail->Host =  $mailhost;    /*SMTP服务器*/
    $mail->Port = $mailport;	    /*SMTP端口*/
    //$mail->SMTPDebug = 1;    /*SMTP调试模式1*/
    $mail->Username = $addr;    /*用户名,一般填发件箱*/
    $mail->Password = $pwd;    /*邮箱密码*/
    $mail->SMTPAuth = true;    /*SMTP验证开启*/
    $mail->FromName = "Young";    /*发件人姓名,可以自定义*/
    $mail->From = $addr;    /*添加发送邮箱*/
    $mail->AddAddress($to);    /*添加接受邮箱*/
    $mail->IsHTML(true);    /**/
    /*若Firstname存在,将第一个字母设为大写*/
    if(!empty($firstname)) {$firstname = ucfirst($firstname);}		
    /*若Firstname为空,统一设为'Hi'*/
    $mail->Subject  = (empty($firstname)) ? "Hi,{$subject}" : "{$firstname},{$subject}";		
    $mailcontent = <<<EOF
        <div>		
            <p>Hello {$fname},</p>
            <p>this is a test.
            </p>
        </div>
EOF;
    $mail->Body  = $mailcontent;
    if(!$mail->Send()){
        /*将此次的FROM,TO,STATUS存入一维数组并push到二维数组*/
        $arr = array('mail_from'=>"$addr",'mail_to'=>"$to",'mail_status'=>'Failed');   
        /*二维数组保存发送情况,并在所有邮件发送完之后使用
        json_encode()编码为JSON字符串反馈到前台*/
        array_push($GLOBALS['responseArray'],$arr);  
    }
    else {
        $arr = array("mail_from" => "$addr","mail_to" => "$to","mail_status" => "Success");	
        array_push($GLOBALS['responseArray'],$arr);
        /*将电邮对应数据库记录中的sentcount字段自增1*/
        $sql = "UPDATE $table SET sentcount = sentcount + 1  where address = '$to'";
        $mysqli->query($sql);
    }
}

4.定时发送请求
定时发送使用的是javascript自带的setInterval()方法(它与setTimeout()的差别在于如果没有clearInterval()来停止或关闭浏览器,它的函数体一直周期性运行下去,而setTimeout则运行一次)
前台有两个按钮,id分别为’start’,’pause’,表示开始发送请求和暂停发送请求

$('#start').click(function(){
    $('#pause').css('color','#FFFFFF');	
    $('#start').css('color','#428BCA');	
    alert('Send start!');
    interval();    /*点击后立即发送请求,因为setInterval()需要在时间间隔到了后才运行*/
    clearIntervalFun  =  setInterval(interval,{$rand_time});    /*设置随机间隔来发送请求*/
 });
								
 $('#pause').click(function() {
    $('#start').css('color','#FFFFFF');	
    $('#pause').css('color','#428BCA');	
    alert('Send pause!');
    clearInterval(clearIntervalFun);	    /*取消发送请求*/
 });

四.实现效果
xg

PHP中几种HTTP请求的实现方法及比较: file_get_contents vs. cURL vs. PECL_HTTP

前一篇文章中介绍了 PHP中的对象复制以及深拷贝浅拷贝的问题,这里我们来探讨一下PHP中的HTTP库

在PHP中有多种进行HTTP请求的方法, 本文中介绍最常用的三种:

1)  文件流的方式:file_get_contents , 这种方式是PHP自带的。

2)cURL方式: cURL是PHP的一个第三方库 , 目前PHP4.0以上都自带cURL lib.

3) PECL_HTTP 扩展: 这是一个PECL的extension, 需要安装才能使用

下面分别以一个实际的应用:PayPal的PDT请求为例, 来分别比较一下这三种方法的优劣:

1. 文件流的方式: PHP的文件流本身就支持HTTP协议,因此我们可以象使用文件一样来进行HTTP的操作
这种方式适合比较简单的HTTP GET 或 POST请求, 但对于一些复杂的应用场景中的HTTP 请求还是有些力不从心的。


 if (!isset($_GET["tx"])) {
        header("Location: http://www.cubebackup.com");
        exit() ;
    }

    $post_array = array (
        "cmd" => "_notify-synch",
        "tx" => $_GET["tx"],
        "at" => PDT_IDTOKEN
    );

    $post_string = http_build_query($post_array);

    $opts = array(
        'http' => array(
            'method' => "POST",
            'header' => "Content-Type: application/x-www-form-urlencoded",
            'content'=> $post_string
        )
    );

    $context = stream_context_create($opts);
    $pdt_response = file_get_contents(PAYPALURL, false, $context);

    if ($pdt_response === FALSE) {
        header("Location: http://www.cubebackup.com");
        exit();
    }

    $response_array = preg_split("/\s+/" ,$pdt_response);
    $pdt_data = array();

    if ($response_array[0] === "SUCCESS") {
        foreach ($response_array as $value) {
            $pdt_pair = explode('=', $value);
            if (isset($pdt_pair[1])) {
                $pdt_data[$pdt_pair[0]] = urldecode($pdt_pair[1]);
            }
           //这里可以对数据进行处理,比如写入数据库,或者显示给用户等。。  

        }
    } else {
        header("Location: http://www have a peek at this web-site.cubebackup.com");
        exit();
    }

2. 采用curl来处理Paypal PDT 请求

HTTP cURL是基于 libcurl的c语言的类库封装而成。 curl/libcurl也是目前最流行的HTTP库,可以用于多种语言。 我曾经在C++编程中重度使用过curl/libcurl, 无论从功能,性能和编程风格/结构方面, curl都非常出色,尤其是一些复杂的HTTP请求,比如断点续传,协同编辑,文件传输进度反馈的回调等,cURL都完全没有问题,而且编程风格非常统一,优雅。

   if (!isset($_GET["tx"])) {
        header("Location: http://www.cubebackup.com");
        exit();
    };

    $post_array = array (
        "cmd" => "_notify-synch",
        "tx" => $_GET["tx"],
        "at" => PDT_IDTOKEN
    );
    $post_data = http_build_query($post_array);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, PAYPALURL);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);

    $res = curl_exec($ch);
    curl_close($ch);

    if ($res === FALSE)  {
        error_log("Paypal PDT request failed. ");
        header("Location: http://www.cubebackup.com");
        exit();
    } else {
        $response_array = preg_split("/\s+/" ,$res);

        if ($response_array[0] === "SUCCESS") {
            $pdt_data = array();

            foreach ($response_array as $value) {
                $pdt_pair = explode('=', $value);
                if (isset($pdt_pair[1])) {
                    $pdt_data[urldecode($pdt_pair[0])] = urldecode($pdt_pair[1]);
                }
            }

            //... save pdt_data to db or display to users

        } else {
            //write log
            error_log("Paypal PDT request error, response data is " . $res);
            header("Location: http://www.cubebackup.com");
        }
    }

3. 采用PECL_HTTP来处理同样的Paypal PDT 请求。
实际上PECL底层也是基于libcurl来实现的,实现的语言也是c,采用了PECL extension的发布形式。 目前PECL_HTTP扩展最大的问题是: 大部分的PHP和Linux并没有自带该扩展,需要手动自行安装。 因为该安装涉及了重新编译PHP代码等工作,因此必须有足够的权限才能进行,如果你使用的是共享主机,就放弃这个尝试吧。 另外,不同的系统安装PECL扩展步骤也有所不同,有的时候,库的依赖已经其前后顺序都会成为安装失败的原因。具体的安装,请参见: http://php.net/manual/en/install.pecl.php

PECL_HTTP version1 版本有 procedural and OO两个编程接口, 但 2.0 版本又采用了一个和1.0 完全不兼容的编程接口, 不知道为什么, 实在无力吐槽。

实现代码如下:


  if (!isset($_GET["tx"])) {
        header("Location: http://www.cubebackup.com");
        exit() ;
    }

    $post_string = "cmd=_notify-synch&tx=".$_GET['tx']."&at=".PDT_IDTOKEN;

    if (function_exists('http_post_data') == false ) {
        //PECL http extension 不存在或者安装有问题
        //采用 CURL 或者 filestream 方式
    }

    $opts= array(
        "redirect" => 10
    );    //因为该请求有302的redirect,所以设置redirect的次数为10

    $pdt_response = http_post_data(PAYPALURL, $post_string, $opts);

    if ($pdt_response === FALSE)
    {
        header("Location: http://www.cubebackup.com");
        exit();
    }

    $response_array = preg_split("/\s+/", $pdt_response);

    //下面的代码和上面的例子相同,就不再写了 。。。

总结:

1. 目前在PHP平台上, curl仍然是最好的HTTP库,没有之一。 可以解决任何复杂的应用场景中的HTTP 请求
2. 文件流式的HTTP请求比较适合处理简单的HTTP POST/GET请求,但不适用于复杂的HTTP请求
3. PECL_HTTP扩展写代码更加简洁,省事, 但成熟度不好,编程接口不统一,文档和实例匮乏。 我个人也没有使用它做过如断点续传等复杂的HTTP 应用,对于其处理复杂HTTP请求的能力也不清楚,为此我还在stackoverflow上发了个帖子专门询问一下PECL这个扩展的情况, 得到的答案是: 请用CURL!

欢迎大家对这个问题进行讨论。

PHP中的对象复制及__clone() 函数

在PHP中, 对象间的赋值操作实际上是引用操作 (事实上,绝大部分的编程语言都是如此! 主要原因是内存及性能的问题) , 比如 :

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data = "aaa";
$obj2 = $obj1;
$obj2->data ="bbb";     //$obj1->data的值也会变成"bbb"

因为$obj1和$obj2都是指向同一个内存区的引用,所以修改任何一个对象都会同时修改另外一个对象。

在有些时候,我们其实不希望这种reference式的赋值方式, 我们希望能完全复制一个对象,这是侯就需要用到 Php中的clone (对象复制)。

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data ="aaa";
$obj2 = clone $obj1;
$obj2->data ="bbb";     // $obj1->data的值仍然为"aaa"

因为clone的方式实际上是对整个对象的内存区域进行了一次复制并用新的对象变量指向新的内存, 因此赋值后的对象和源对象相互之间是基本来说独立的。

什么? 基本独立?!这是什么意思? 因为PHP的object clone采用的是浅拷贝(shallow copy)的方法, 如果对象里的属性成员本身就是reference类型的,clone以后这些成员并没有被真正复制,仍然是引用的。 (事实上,其他大部分语言也是这样实现的, 如果你对C++的内存,拷贝,copy constructor等概念比较熟悉,就很容易理解这个概念), 下面是一个例子来说明:


class myClass{
public $data;
}

$sss ="aaa";
$obj1 = new myClass();
$obj1->data =&$sss;   //注意,这里是个reference!
$obj2 = clone $obj1;
$obj2->data="bbb";  //这时,$obj1->data的值变成了"bbb" 而不是"aaa"!

var_dump($obj1);
var_dump($obj2);

我们再举一个更实用的例子来说明一下PHP clone这种浅拷贝带来的后果:

class testClass
{
   public $str_data;
   public $obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;

var_dump($obj1);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"

$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));      //给$obj2->obj_date 的时间增加了10天

var_dump($obj1);     // str_data:"aaa"   obj_data:"2014-07-15 00:00:00"  !!!!
var_dump($obj2);     // str_data:"bbb"   obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj)  // 2014-07-15 00:00:00"

这一下可以更加清楚的看到问题了吧。 一般来讲,你用clone来复制对象,希望是把两个对象彻底分开,不希望他们之间有任何关联, 但由于clone的shallow copy的特性, 有时候会出现非你期望的结果,上面的例子中,
1) $obj1->obj_data =$dateTimeObj 这句话实际上是个引用类型的赋值. 还记得前面提到的PHP中对象直接的赋值是引用操作么?除非你用$obj1->obj_dat = clone $dataTimeObj!

2) $obj2 = clone $obj1 这句话生成了一个obj1对象的浅拷贝对象,并赋给obj2. 由于是浅拷贝,obj2中的obj_data也是对$dateTimeObj的引用!

3)$dateTimeObj, $obj1->obj_data, $obj2->obj_data 实际上是同一个内存区对象数据的引用,因此修改其中任何一个都会影响其他两个!

 

如何解决这个问题呢? 采用PHP中的 __clone方法 把浅拷贝转换为深拷贝(这个方法给C++中的copy constructor概念上有些相似,但执行流程并不一样)

class testClass
{
 public $str_data;
 public $obj_data;

 public function __clone() {
   $this->obj_data = clone $this->obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;
var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));

var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj);  //"2014-07-05 00:00:00"

关于 __clone() , PHP官方的文档: Once the cloing is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.

按照这个定义,事实上__clone方法可以做很多事情,但我目前能想到的就只有把 浅拷贝变成深拷贝 这个场景的应用了, 如果有其他用法,欢迎大家提出来。

 

php中 upload操作返回HTTP 413 或者 “too large body”错误的处理方法

环境: CentOS Linux  +  Nginx  +  Php5

在PHP中,经常会遇到文件上传的问题, 由于这是一个对安全性要求较高的操作,因此首先你需要确保 上传目录的权限的设置是正确的 (应该给予Web进程用户,比如Apache, Nginx的读写权利,但其他用户应该无法访问), 另外要对上传的文件类型进行判断和筛选 (一般原则是可以先通过Mime type来筛选,然后检查扩展名).

但有时候即使权限和文件类型都匹配的情况下,仍然会遇到 HTTP 413 错误 ( Request Entity Too Large ).   如果你检查nginx的error log, 会发现 “client intended to send too large body“ 的错误log。  这种情况下,如何解决呢?

首先需要明确的是:该错误是由于你上传的文件大小 超出了PHP / Web服务器 允许的限定导致的。缺省情况下,对上传文件大小的限制比较严格,而目前很多高清图片都会超出缺省的限定值。 解决方法如下:

1. 修改 php.ini 文件  , vim /etc/php.ini


upload_max_filesize = 15M      //单个上传文件的大小限制。 这个值缺省情况下是2M ,对文件上传往往是不够的

post_max_size = 50M          //总的多个文件上传大小的限制

 

2.  修改Nginx的设置。 vim /etc/nginx/nginx.conf


http {

....

client_max_body_size 50m;    #缺省值是1m, 对文件上传操作是不够的。

....

}

需要注意的是:上面是针对该服务器只有一个Website的情况,如果有多个virtual host, 应该把client_max_body_size设置到对应的virtual host上面!

3. 重新启动Nginx和PHP_FPM


/etc/init.d/php-fpm restart

/etc/init.d/nginx restart

 

 

 

 

 

 

 

关于一个经典名言的解释,“Linux的内存是用的不是看的”

这几天折腾VPS的时候发现了一个很纠结的问题。自己的内存是512MB,每次运行完一些命令后,使用free 命令查看内存使用情况,总会发现自己的内存所剩无几。清空缓存或者reboot一下,内存又恢复如初。根据我多年使用Windows的经验,总想着有个啥啥啥清理大师来帮我一键优化,瞬间内存清爽的感觉。于是找了清除缓存代码,一运行,世界安静了。但是那么简单的命令,为啥屌爆的Linux内核自己不去调用呢?很神奇,于是找了一些资料,仔细研究了一下这个问题。

在Linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching。这个问题,貌似有不少人在问,不过都没有看到有什么很好解决的办法。

介绍这个之前,先来看看free命令吧:

free

 

其中,各种参数的意义

QQ截图20140620130248

可用的memory=free memory+buffers+cached

有了这个基础后,可以得知,我现在used为163MB,free为349MB,buffer和cached分别为10MB,94MB。
那么我们来看看,如果我执行复制文件,内存会发生什么变化.

在我命令执行结束后,used为244MB,free为4MB,buffers为8MB,cached为174MB,天呐,都被cached吃掉了。别紧张,这是为了提高文件读取效率的做法。

为了提高磁盘存取效率,Linux做了一些精心的设计,除了对dentry进行缓存(用于VFS,加速文件路径名到inode的转换),还采取了两种主要Cache方式:Buffer Cache和Page Cache。前者针对磁盘块的读写,后者针对文件inode的读写。这些Cache有效缩短了 I/O系统调用(比如read,write,getdents)的时间。

那么有人说过段时间,linux会自动释放掉所用的内存。等待一段时间后,我们使用free再来试试,看看是否有释放?

似乎没有任何变化。(实际情况下,内存的管理还与Swap有关)

那么我能否手动释放掉这些内存呢?回答是可以的!

/proc是一个虚拟文件系统,我们可以通过对它的读写操作做为与kernel实体间进行通信的一种手段。也就是说可以通过修改/proc中的文件,来对当前kernel的行为做出调整。那么我们可以通过调整/proc/sys/vm/drop_caches来释放内存。操作如下:

cat /proc/sys/vm/drop_caches

首先,/proc/sys/vm/drop_caches的值,默认为0。

手动执行sync命令(描述:sync 命令运行 sync 子例程。如果必须停止系统,则运行sync 命令以确保文件系统的完整性。sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)

将/proc/sys/vm/drop_caches值设为3

再来运行free命令,会发现现在的used为66MB,free为445MB,buffers为0MB,cached为11MB。那么有效的释放了buffer和cache。

 有关/proc/sys/vm/drop_caches的用法在下面进行了说明

引用/proc/sys/vm/drop_caches (since Linux 2.6.16)
Writing to this file causes the kernel to drop clean caches,
dentries and inodes from memory, causing that memory to become
free.

To free pagecache, use echo 1 > /proc/sys/vm/drop_caches; to
free dentries and inodes, use echo 2 > /proc/sys/vm/drop_caches;
to free pagecache, dentries and inodes, use echo 3 >
/proc/sys/vm/drop_caches.

Because this is a non-destructive operation and dirty objects
are not freeable, the user should run sync first.

我的意见

文章就长期以来很多用户对Linux内存管理方面的疑问,给出了一个比较“直观”的回复,我更觉得有点像是核心开发小组的妥协。
对于是否需要使用这个值,或向用户提及这个值,我还是不清楚:

引用1、从man可以看到,这值从2.6.16以后的核心版本才提供,也就是老版的操作系统,如红旗DC 5.0、RHEL 4.x之前的版本都没有;
2、若对于系统内存是否够用的观察,我还是原意去看swap的使用率和si/so两个值的大小;
用户常见的疑问是,为什么free这么小,是否关闭应用后内存没有释放?
但实际上,我们都知道这是因为Linux对内存的管理与Windows不同,free小并不是说内存不够用了,应该看的是free的第二行最后一个值:

-/+ buffers/cache: 58 191

这才是系统可用的内存大小。
实际项目中告诉我们,如果因为是应用有像内存泄露、溢出的问题,从swap的使用情况是可以比较快速可以判断的,但free上面反而比较难查看。
相反,如果在这个时候,我们告诉用户,修改系统的一个值,“可以”释放内存,free就大了。用户会怎么想?不会觉得操作系统“有问题”吗?
所以说,我觉得既然核心是可以快速清空buffer或cache,也不难做到(这从上面的操作中可以明显看到),但核心并没有这样做(默认值是0),我们就不应该随便去改变它。
一般情况下,应用在系统上稳定运行了,free值也会保持在一个稳定值的,虽然看上去可能比较小。
当发生内存不足、应用获取不到可用内存、OOM错误等问题时,还是更应该去分析应用方面的原因,如用户量太大导致内存不足、发生应用内存溢出等情况,否则,清空buffer,强制腾出free的大小,可能只是把问题给暂时屏蔽了

我觉得,排除内存不足的情况外,除非是在软件开发阶段,需要临时清掉buffer,以判断应用的内存使用情况;或应用已经不再提供支持,即使应用对内存的时候确实有问题,而且无法避免的情况下,才考虑定时清空buffer。(可惜,这样的应用通常都是运行在老的操作系统版本上,上面的操作也解决不了)。而生产环境下的服务器可以不考虑手工释放内存,这样会带来更多的问题。记住内存是拿来用的,不是拿来看的。不像windows, 无论你的真实物理内存有多少,他都要拿硬盘交换文件来读。这也就是windows为什么常常提示虚拟空间不足的原因,你们想想多无聊,在内存还有大部分的时候,拿出一部分硬盘空间来充当内存。硬盘怎么会快过内存,所以我们看linux,只要不用swap的交换空间,就不用担心自己的内存太少。如果常常swap用很多,可能你就要考虑加物理内存了,这也是linux看内存是否够用的标准哦。

记住Linux内存是拿来用的,不是拿来看的

内存近乎用尽时,kernel会自动释放无用的cache,为应用程序腾出空间,因而几乎完全不会影响应用程序的内存使用。就像timemars说的一样,内存空着也是空着,cache可以使用这些空余的内存来提高系统的I/O性能。如果您的计算机频繁进行I/O操作,drop_caches可能会导致I/O性能的下降–对某些服务器系统,可能就是非常严重的下降了。在free的输出中给出的空余内存,应该以去除cache的”-/+ buffers/cache”那一行为准。

一个SSH 公钥登录失败的问题及解决经验

一直使用阿里云的Centos做服务器,最近在服务器上新建了一个用户,为了免去每次SSH都要输入密码的麻烦,我通过 下面的命令为该用户建立SSH公钥/私钥 登录认证, 本来是个很简单的操作,没想到竟然出现了问题!


//客户端的linux机上

ssh-keygen

scp ~/.ssh/id_rsa.pub    aaa@ServerIP:~

//服务器上

用aaa用户登录

cat ~/id_rsa.pub >> ~/.ssh/authorized_keys 

配置完毕后,在客户机上通过ssh登录,竟然还提示要输入密码!? 因为其实以前已经配置过好几次SSH公钥登录,从来没有出现过问题。这一次很意外。反复检查了几遍也没有找到原因。

通过下面的debug方式得到信息如下:

 ssh -v aaa@serverIP 

Screen Shot 2014-06-07 at 3.45.54 PM

通过上述的SSH -v 命令可以看到,客户机已经把私钥发送到服务器端,但在服务器端没有验证成功,所以问题应该位于服务器端。

通过反复的Google,找到一些线索, 有的说是SELinux的原因, 但阿里云的CentOS服务器上并没有启用SELinux. 有的说是.ssh 目录以及authorized_key的权限问题,我把相关的文件和目录权限放的更加宽松,但还是失败!

没有办法,看log吧。CentOS 6的SSH登录信息记录在/var/log/secure文件中,找到下面的重要信息:
Screen Shot 2014-06-07 at 3.58.49 PM

服务器端的错误: “bad ownership or modes for directory…”

有了具体的错误信息,再次求助于Google, 原来我把 .ssh目录的权限设置成了775 (当时认为权限越宽松越不会有问题,哎),而系统安全方面,对于这个存放公钥的目录的权限要求是只有本人才可以读写的,应该是700!否则,缺省的情况下会拒绝进行authentication. 改成700后,再次SSH登录,问题解决!

通过这个小事情(折腾了我好几个小时其实), 学到了:

1. 仔细看log是发现问题原因的很重要的手段
2. 有的时候,权限不是越宽松就一定不影响具体操作, SSH公钥认证必须设置合适的权限 : .ssh目录的权限为 700, authorized_keys的权限为600