阿里云备案赞一个

背景:
我这个网站曾经在06年备案过,10年左右就挂载新浪sae了,一挂就到现在,后来在2012年左右备案号被注销了(大家懂的),我嫌备案麻烦,然后sae也不能直接访问还好有国外的节点,所以一直用的国外的线路,那就是一个字:慢。
本来sae承诺的开发者帐号会有免费的云豆发就忍忍吧,但是实时证明,所有互联网公司承诺的永久免费都是扯淡的。偶然间发现自己几万云豆消耗殆尽,所以还是老实备案吧。

这里并不是帮阿里云做广告,实际上阿里云同等配置相比传统IDC价格还是贵的。因为我的网站访问也不多,所以选了个最便宜的云引擎试了下,果然是性能一塌糊涂,所谓送的mysql和存储也不敢恭维,要想好的加钱买高配吧。

这里只赞备案体验(备案好了挂个最便宜的引擎在那也是可以的,一年才200块)

操作流程非常简单,真的是最方便的了,首先买好半年或1年的云引擎(或1个月的云主机,按量收费的都不支持备案的)然后备案管理里,申请一个备案服务号,申请好后,点开我要备案进入备案页面,填些个人资料,保存后1-2天就有人电话跟你确认下(初审),然后就可以上传身份证资料了,都很简单。
我办的是上海的,上海的备案如果你是外省市的户口需要居住证的(对一些人可能是门槛,不行就被户籍所在地的吧)
这里可能很多人关心的是幕布拍照,其实我一开始也是,不过发现备上海的话非常好,因为官方有个专门的app(只支持android)可以在线牌照,拍完照按流程保存后你的事情就完成了,阿里云会再电话确认后就提交管局了。
他们说要我保持电话畅通管局要打我电话核实,不过我没接到电话,周一提交的周二就通过拿到备案号了,整个过程耗时5天(中间还有个周末)真是极速了

那么如果不用上海管局的在线拍照APP则需要阿里云邮寄幕布过来,自己拍照上传,就稍微麻烦了些了。。。

此文献给像我一样需要备案的人

【后记】
后来我又添加了自己另一个新的网站,早上提交的信息,下午就得到备案号了,大赞。
刚刚收到阿里云的通知, 云引擎ACE 5月份就要下线了,还好我之前没怎么深度使用,所以又移到了ECS云服务器里了,还好,性能当然会比ACE要好,流量也是童叟无欺0.8元/G,最便宜的1年450

swoole_process 正常退出却 zm_deactivate_swoole: worker process is terminated by exit()/die().

不废话,先上一段代码

<?php

error_reporting(7);

$serv = new swoole_server("0.0.0.0", 9500, SWOOLE_PROCESS);
$serv->set([
    'worker_num' => 1,
    'task_worker_num' => 1,
]);

$serv->on('Receive', function(swoole_server $server, $fd, $from_id, $data)
{
    $server->task('a');
});


$serv->on('workerstart', function(swoole_server $server, $id)
{
});

//$serv->set([]);

$serv->on('Task', function($serv, $task_id, $from_id, $data)
{
    $arr = ['a','b','c','d'];
    $processes = [];
    foreach ($arr as $v)
    {
        echo "-------". (microtime(1))."\n";

        $process = new swoole_process(function(swoole_process $worker) use ($v)
        {
            echo $v."======". ($t = microtime(1))."\n";
            sleep(3);
            echo $v."++++++". (microtime(1) - $t)."\n";
            // $worker->daemon(true);
        }, false);
        $process->start();
        $processes[] = $process;
    }

    while(true)
    {
        # 等待回收,如果不回收进程会变成僵死进程,很可怕的
       if (false === swoole_process::wait())
        {
            break;
        }
    }

    echo "done\n";
});

$serv->on('Finish', function()
{
});


$serv->start();

(TO小白:保存成test.php文件,然后php test.php这样运行,再telnet 127.0.0.1 9500 上,随便输入个内容回车就可以执行了)

这个代码成功执行后,在控制台上会输出4个WARN的错误,类似

zm_deactivate_swoole: worker process is terminated by exit()/die().

经过测试发现,如果 new swoole_process 时第二个参数设置成 true(定向子进程的标准输入和输出),则不会报这样的错误。
但是我的程序就是需要接受输出那么怎么办呢?

在测试代码里有一个注释了的代码

// $worker->daemon(true);

,把注释去掉这样就解决了,不再报那个错误了。
daemon 方法可见文档,它是把子进程蜕变成一个守护进程用的。

最后,再强调一下,一定要:

    while(true)
    {
        # 等待回收,如果不回收进程会变成僵死进程,很可怕的
       if (false === swoole_process::wait())
        {
            break;
        }
    }

这样进行回收,因为测试代码里开了4个子进程,如果只回收1次,还会有3个进程得不到回收,变成僵死进程的。

Fluentd 的PHP客户端,支持ACK方式以及批量推送log的类库

fluentd是一个非常好用的日志分发处理的程序,网站 http://www.fluentd.org/

使用官方的php程序自己改了一个单文件的Fluent的类库,并且加入了更多的支持,比如 tcp 方式支持 require_ack_response 了,这个参数主要是用来请求是否推送成功用的,避免因为网络问题没有推送成功而无法知道。
但是这个功能我实测如果每条log都去ask一下本来推送10w条记录只需要3-5秒,用这个后可能要几分钟时间,所以性能上会大打折扣。
所以我加入了add()方法,可以加入n多条log后一次性push到服务器进行1次ack,这样性能就会很好了。
另外,官网的php程序我在使用中发现在大量日志的推送后会突然出现“incoming chunk is broken”这样的错误,这个应该也是解决了,具体我再多测几天看看情况。

[2016-03-20] 更新http协议支持更多内容的提交

使用方法:

$option = array
(
    'require_ack_response' => 1,    //开启ACK
    'max_buffer_length' => 2000     //每2000条add会自动push一次
);

$f = new Fluent('tcp://27x004.xd.com:2600', $option);
$t = microtime(1);
for($i = 1; $i<= 10000; $i++)
{
    // 批量加入,到达2000条会自动推送
    $f->add('test.abc', array('i'=>$i), time());  // 第3个time()参数可以不传
}
// 推送剩余的
var_dump($f->push('test.abc'));

echo "\n\n", microtime(1) - $t , "\n";

下面是源代码

<?php
/**
 * Fluent日志处理核心类
 *
 * 配置根目录 `$config['log']['fluent'] = 'tcp://127.0.0.1:24224/'` 后
 * 使用 `self::log('myapp.test.debug', $_SERVER)` 默认就可以调用本方法
 *
 *
 *      Fluent::instance('tcp://127.0.0.1:24224/')->push('xd.game.test', ["test"=>"hello"]);
 *
 *      Fluent::instance('unix:///full/path/to/my/socket.sock')->push('xd.game.test', ["test"=>"hello"]);
 *
 *
 * @see        https://github.com/fluent/fluent-logger-php
 * @author     呼吸二氧化碳 <jonwang@myqee.com>
 * @category   Core
 * @package    Classes
 * @copyright  Copyright (c) 2008-2016 myqee.com
 * @license    http://www.myqee.com/license.html
 */

class Fluent
{
    const CONNECTION_TIMEOUT = 3;
    const SOCKET_TIMEOUT     = 3;
    const MAX_WRITE_RETRY    = 5;

    /* 1000 means 0.001 sec */
    const USLEEP_WAIT = 1000;

    /**
     * 是否开启ACK
     *
     * @var bool
     */

    const REQUIRE_ACK_RESPONSE = true;

    /**
     * backoff strategies: default usleep
     *
     * attempts | wait
     * 1        | 0.003 sec
     * 2        | 0.009 sec
     * 3        | 0.027 sec
     * 4        | 0.081 sec
     * 5        | 0.243 sec
     **/

    const BACKOFF_TYPE_EXPONENTIAL = 0x01;
    const BACKOFF_TYPE_USLEEP      = 0x02;

    /**
     * 服务器
     *
     * 例如 `tcp://127.0.0.1:24224`
     *
     * @var string
     */

    protected $transport;

    /* @var resource */
    protected $socket;

    protected $is_http = false;

    protected $data = [];

    protected $options = array
    (
        'socket_timeout'       => self::SOCKET_TIMEOUT,
        'connection_timeout'   => self::CONNECTION_TIMEOUT,
        'backoff_mode'         => self::BACKOFF_TYPE_USLEEP,
        'backoff_base'         => 3,
        'usleep_wait'          => self::USLEEP_WAIT,
        'persistent'           => true,
        'retry_socket'         => true,
        'max_write_retry'      => self::MAX_WRITE_RETRY,
        'require_ack_response' => self::REQUIRE_ACK_RESPONSE,
        'max_buffer_length'    => 1000,
    );

    /**
     * @var Fluent
     */

    protected static $instance = array();

    function __construct($server, array $option = array())
    {
        $this->transport = $server;

        if (($pos = strpos($server, '://')) !== false)
        {
            $protocol = substr($server, 0, $pos);

            if (!in_array($protocol, array('tcp', 'udp', 'unix', 'http')))
            {
                throw new Exception("transport `{$protocol}` does not support");
            }

            if ($protocol === 'http')
            {
                # 使用HTTP推送
              $this->is_http = true;
                $this->transport = rtrim($this->transport, '/ ');
            }
        }
        else
        {
            throw new Exception("fluent config error");
        }

        if ($option)
        {
            $this->options = array_merge($this->options, $option);
        }
    }

    /**
     * destruct objects and socket.
     *
     * @return void
     */

    public function __destruct()
    {
        if ($this->data)
        {
            # 把遗留的数据全部推送完毕
          foreach (array_keys($this->data) as $tag)
            {
                $this->push($tag);
            }
        }

        if (!$this->get_option('persistent', false) && is_resource($this->socket))
        {
            @fclose($this->socket);
        }
    }

    /**
     * 返回Fluent处理对象
     *
     * @return Fluent
     */

    public static function instance($server)
    {
        if (!isset(Fluent::$instance[$server]))
        {
            Fluent::$instance[$server] = new Fluent($server);
        }

        return Fluent::$instance[$server];
    }

    /**
     * 添加数据,添加完毕后并不直接推送
     *
     * 当开启ack后,推荐先批量 add 后再push,当超过 max_buffer_length 后会自动推送到服务器
     *
     *      $fluent = new Fluent('tcp://127.0.0.1:24224/');
     *      $fluent->add('debug.test1', array('a' => 1));
     *      $fluent->add('debug.test2', array('a' => 2));
     *      $fluent->add('debug.test1', array('a' => 1));
     *
     *      var_dump($fluent->push('debug.test1'));
     *      var_dump($fluent->push('debug.test2'));
     *
     * @param string $tag tag内容
     * @param array $data 数据内容
     * @param int $time 标记日志的时间戳,不设置就是当前时间
     */

    public function add($tag, array $data, $time = null)
    {
        $this->_add($tag, $data, $time);

        if (count($this->data[$tag]) >= $this->options['max_buffer_length'])
        {
            return $this->push($tag);
        }

        return true;
    }

    protected function _add($tag, $data, $time)
    {
        if ($this->is_http)
        {
            if (!isset($data['time']))
            {
                $data['time'] = $time ? $time : time();
            }
            $this->data[$tag][] = $data;
        }
        else
        {
            $this->data[$tag][] = array($time ? $time : time(), $data);
        }
    }

    /**
     * 推送数据到服务器
     *
     * @param string $tag tag内容
     * @param array $data 数据内容
     * @param int $time 标记日志的时间戳,不设置就是当前时间
     * @return bool
     * @throws Exception
     */

    public function push($tag, $data = null, $time = null)
    {
        if ($data)
        {
            $this->_add($tag, $data, $time);
        }

        if (!isset($this->data[$tag]) || !$this->data[$tag])return true;

        if ($this->is_http)
        {
            $rs = $this->push_with_http($tag, $time);
        }
        else
        {
            $rs = $this->push_with_socket($tag);
        }

        if ($rs)
        {
            unset($this->data[$tag]);
        }

        return $rs;
    }

    protected function push_with_http($tag, $time)
    {
        $packed  = self::json_encode($this->data[$tag]);
        $opts = array(
            'http' => array
            (
                'method'  => 'POST',
                'content' => 'json='. urlencode($packed)
            )
        );
        $context = stream_context_create($opts);
        $url     = $this->transport .'/'. $tag;

        $ret = file_get_contents($url, false, $context);

        return ($ret !== false && $ret === '');
    }

    protected function push_with_socket($tag)
    {
        $data = $this->data[$tag];
       
        if ($ack = $this->get_option('require_ack_response'))
        {
            $ack_key = 'a'. (microtime(1) * 10000);
            $buffer = $packed = self::json_encode(array($tag, $data, array('chunk' => $ack_key)));
        }
        else
        {
            $ack_key = null;
            $buffer = $packed = self::json_encode(array($tag, $data));
        }

        $length = strlen($packed);
        $retry  = $written = 0;

        try
        {
            $this->reconnect();
        }
        catch (Exception $e)
        {
            $this->close();
            $this->process_error($tag, $data, $e->getMessage());

            return false;
        }

        try
        {
            // PHP socket looks weired. we have to check the implementation.
            while ($written < $length)
            {
                $nwrite = $this->write($buffer);

                if ($nwrite === false)
                {
                    // could not write messages to the socket.
                    // e.g) Resource temporarily unavailable
                    throw new Exception("could not write message");
                }
                else if ($nwrite === '')
                {
                    // sometimes fwrite returns null string.
                    // probably connection aborted.
                    throw new Exception("connection aborted");
                }
                else if ($nwrite === 0)
                {
                    if (!$this->get_option("retry_socket", true))
                    {
                        $this->process_error($tag, $data, "could not send entities");

                        return false;
                    }

                    if ($retry > $this->get_option("max_write_retry", self::MAX_WRITE_RETRY))
                    {
                        throw new Exception("failed fwrite retry: retry count exceeds limit.");
                    }

                    $errors = error_get_last();
                    if ($errors)
                    {
                        if (isset($errors['message']) && strpos($errors['message'], 'errno=32 ') !== false)
                        {
                            /* breaking pipes: we have to close socket manually */
                            $this->close();
                            $this->reconnect();

                            # 断开后重新连上后从头开始写,避免出现 incoming chunk is broken 的错误问题
                          $written = 0;
                            $buffer = $packed;
                            continue;
                        }
                        else if (isset($errors['message']) && strpos($errors['message'], 'errno=11 ') !== false)
                        {
                            // we can ignore EAGAIN message. just retry.
                        }
                        else
                        {
                            error_log("unhandled error detected. please report this issue to http://github.com/fluent/fluent-logger-php/issues: ". var_export($errors, true));
                        }
                    }

                    if ($this->get_option('backoff_mode', self::BACKOFF_TYPE_EXPONENTIAL) == self::BACKOFF_TYPE_EXPONENTIAL)
                    {
                        $this->backoff_exponential(3, $retry);
                    }
                    else
                    {
                        usleep($this->get_option("usleep_wait", self::USLEEP_WAIT));
                    }
                    $retry++;
                    continue;
                }

                $written += $nwrite;
                $buffer   = substr($packed, $written);
            }

            if ($ack)
            {
                $rs = @fread($this->socket, 25);
                if ($rs)
                {
                    $rs = @json_decode($rs, true);
                    if ($rs && isset($rs['ack']))
                    {
                        if ($rs['ack'] !== $ack_key)
                        {
                            $this->process_error($tag, $data, 'ack in response and chunk id in sent data are different');
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
        }
        catch (Exception $e)
        {
            $this->close();
            $this->process_error($tag, $data, $e->getMessage());

            return false;
        }

        return true;
    }


    /**
     * write data
     *
     * @param string $data
     * @return mixed integer|false
     */

    protected function write($buffer)
    {
        // We handle fwrite error on postImpl block. ignore error message here.
        return @fwrite($this->socket, $buffer);
    }

    /**
     * create a connection to specified fluentd
     *
     * @throws \Exception
     */

    protected function connect()
    {
        $connect_options = STREAM_CLIENT_CONNECT;
        if ($this->get_option("persistent", false))
        {
            $connect_options |= STREAM_CLIENT_PERSISTENT;
        }

        // could not suppress warning without ini setting.
        // for now, we use error control operators.
        $socket = @stream_socket_client($this->transport, $errno, $errstr, $this->get_option("connection_timeout", self::CONNECTION_TIMEOUT), $connect_options);

        if (!$socket)
        {
            $errors = error_get_last();
            throw new Exception($errors['message']);
        }

        // set read / write timeout.
        stream_set_timeout($socket, $this->get_option("socket_timeout", self::SOCKET_TIMEOUT));

        $this->socket = $socket;
    }

    /**
     * create a connection if Fluent Logger hasn't a socket connection.
     *
     * @return void
     */

    protected function reconnect()
    {
        if (!is_resource($this->socket))
        {
            $this->connect();
        }
    }

    /**
     * close the socket
     *
     * @return void
     */

    public function close()
    {
        if (is_resource($this->socket))
        {
            fclose($this->socket);
        }

        $this->socket = null;
    }

    /**
     * get specified option's value
     *
     * @param      $key
     * @param null $default
     * @return mixed
     */

    protected function get_option($key, $default = null)
    {
        $result = $default;
        if (isset($this->options[$key]))
        {
            $result = $this->options[$key];
        }

        return $result;
    }

    /**
     * backoff exponential sleep
     *
     * @param $base int
     * @param $attempt int
     */

    protected function backoff_exponential($base, $attempt)
    {
        usleep(pow($base, $attempt) * 1000);
    }

    /**
     * 处理错误
     *
     * @param $tag
     * @param $data
     * @param $error
     */

    protected function process_error($tag, $data, $error)
    {
        error_log(sprintf("%s %s: %s", $error, $tag, json_encode($data)));
    }

    protected static function json_encode(array $data)
    {
        try
        {
            // 解决使用 JSON_UNESCAPED_UNICODE 偶尔会出现编码问题导致json报错
            return defined('JSON_UNESCAPED_UNICODE') ? json_encode($data, JSON_UNESCAPED_UNICODE) : json_encode($data);
        }
        catch (Exception $e)
        {
            return json_encode($data);
        }
    }
}

榨干PHP性能之被遗忘的 if 中 == 和 === 判断的性能差异

对于

if ($a === $b)
{
    //coding...
}

if ($a == $b)
{
    //coding...
}

这2种最简单不过的判断,似乎很少有人关心他们的性能差别,包括我,写了快10年的php似乎从来没有在意过这些。
对我来说,只有在需要严格判断的情况下才会用 === 来判断,大多情况下都是用 == 来判断的。

今天闲来无事手贱测一下这些被遗忘的细节的性能差别,结果却有些让我出乎意料。

测试代码:

// 代码A, === 判断
$a = 'aaa';
$b = 'aaa';
$s = microtime(1);
for($i=0;$i<99999999;$i++) {
   if ($a===$b){$c=$a;}
}
echo microtime(1)-$s,"\n";
// 代码B, == 判断
$a = 'aaa';
$b = 'aaa';
$s = microtime(1);
for($i=0;$i<99999999;$i++) {
   if ($a==$b){$c=$a;}
}
echo microtime(1)-$s,"\n";

在我电脑上执行代码A和B的结果分别是:
=== 是 4.2317380905151
== 是 5.3882131576538
看来 === 胜出

千万别急着下结论,把代码$a, $b 的值换成 123 试试,结果却出乎我的意料

=== 是 3.3042049407959
== 是 3.091680765152
反而是 == 胜出了

那么再把 $a, $b 的值都赋值成一个 new stdClass(); 是什么结果呢,这个结果在我的意料之中不过也是意料之外
我把 99999999 次循环改成了 199999999 次,结果是:
=== 是 6.6931169033051
== 是 9.5787789821625
结果显示 === 比 == 快 1/3

以上似乎看不出什么差距,后来我又测了一个代码,发现问题来了

// 代码A, === 判断
$a = str_pad('', 99999);
$b = str_pad('', 99999);
$s = microtime(1);
for($i=0;$i<999999;$i++) {
   if ($a===$b){$c=$a;}
}
echo microtime(1)-$s,"\n";
// 代码B, == 判断
$a = str_pad('', 99999);
$b = str_pad('', 99999);
$s = microtime(1);
for($i=0;$i<999999;$i++) {
   if ($a==$b){$c=$a;}
}
echo microtime(1)-$s,"\n";

没用 99999999 次循环是因为我等了几分钟都还没出结果

最终的结果是:
=== 是 4.435389995575
== 是 57.722193956375

足足有10倍性能的差距。

可见对于字符串来说,字符越长, === 的性能优势越大于 ==

结论:
如果php的if判断是字符串判断,一定要用 ===, 比 == 不知快多少,而对于整型的判断,== 比 === 快些但是优势不大。
所以判断尽量用 ===

PS:以上测试环境是 5.5.14 ,Mac OS 10.10.1, 2.3 GHz Intel Core i7,16G 1600 MHz DDR3

做了一个 FirePHP For Chrome 的插件

FirePHP(http://www.firephp.org/)是一个php里不错的用于调试的工具,我在MyQEE里内置了这个功能,在线调试非常有用,只可惜做这个的作者好久没更新版本了,官方也只出了一个在Firefox里的插件。

而Chrome里的插件都是第三方做的,实际上很难用。这些天下狠心改了别人一款,改到了基本自己满意了,媲美FireFox里的插件,掌声在哪~~~~

本来是要公开发布出来的,可无奈google的应用商店第一次发布必须要付$5才行,我尝试的去付了下,可是支付失败,好像是不支持中国的信用卡,唉~~~~ 于是只好发布成私有的应用,私有的应用和公开的应用的差别在于只能通过指定的URL访问到,在google的应用市场里搜索不到,也罢~~~

应用的地址是: https://chrome.google.com/webstore/detail/firephp/gkkcbhnljobgoehijcpenalbacdmhaff?hl=zh-CN&utm_source=chrome-ntp-launcher&authuser=1

注:这个地址可能无法打开(被墙,你懂的)浏览是可能需要借助翻墙软件。

附截图:

如果无法打开地址,可以在这下载:

gkkcbhnljobgoehijcpenalbacdmhaff_main.crx

下载后安装方法:
首先解压缩压缩包,得到 gkkcbhnljobgoehijcpenalbacdmhaff_main.crx 文件,然后打开Chrome的扩展程序界面(入下图操作):

然后把刚刚解压得到的crx文件拖拽到这个“扩展程序”里(注意,只能是拖到这个页面,拖到别的页面是没用的),如下图操作:

这样就可以安装上了。

提供一个自动下载射手网电影字幕的脚本

射手网突然停止下载了,现在看个电影都很费事,还好射手网的接口没有关闭。看了下接口文档,自己用php写了个脚本,可以直接根据影片文件下载匹配的字幕。

把代码方在/usr/local/bin/目录,文件名zimu,并chmod +x /usr/local/bin/zimu,然后这样用:

zimu 电影文件名

就可以自动下载字幕了
可以加参数“debug”, “all”
比如

zimu test.mkv all

则下载全部匹配的字幕

window用户的话,可以保存成zimu.php然后使用

php zimu.php 电影文件名

这样来下载

不多说,直接上代码

#!/usr/bin/env php
<?php

$file = $_SERVER['argv'][1];

if (!$file)
{
 echo "缺少参数\n";
 exit;
}
if (!is_file($file))
{
 echo "文件不存在\n";
    exit;
}
$ftotallen = filesize($file);
if ($ftotallen<8192)
{
    echo "文件太小\n";
 exit;
}





$offsets[] = 4096;
$offsets[] = floor($ftotallen / 3 * 2);
$offsets[] = floor($ftotallen / 3);
$offsets[] = $ftotallen - 8192;

$fp = fopen($file, "r");

foreach($offsets as $offset)
{
    fseek($fp, $offset, SEEK_SET);
    $data  = fread($fp, 4096);
  $md5[] = md5($data);
}

fclose($fp);

echo $md5_str = implode(';', $md5);

echo "\n";

function curl_post($url, array $post = NULL, array $options = array())
{
    $defaults = array(
        CURLOPT_POST => 1,
        CURLOPT_HEADER => 0,
        CURLOPT_URL => $url,
        CURLOPT_FRESH_CONNECT => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FORBID_REUSE => 1,
        CURLOPT_TIMEOUT => 4,
        CURLOPT_POSTFIELDS => http_build_query($post)
    );

    $ch = curl_init();
    curl_setopt_array($ch, ($options + $defaults));
    if( ! $result = curl_exec($ch))
    {
        trigger_error(curl_error($ch));
    }
    curl_close($ch);
    return $result;
}

function curl_get($url, array $get = NULL, array $options = array())
{
    $defaults = array(
        CURLOPT_URL => $url. (strpos($url, '?') === FALSE ? '?' : ''). ($get ? http_build_query($get): ''),
        CURLOPT_HEADER => 0,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 4
    );

    $ch = curl_init();
    curl_setopt_array($ch, ($options + $defaults));
    if( ! $result = curl_exec($ch))
    {
        trigger_error(curl_error($ch));
    }
    curl_close($ch);
    return $result;
}


$data = array
(
    'filehash' => $md5_str,
   'pathinfo' => $file,
  'format'   => 'json',
   'lang'     => 'Chn',
);

$r = curl_post('https://www.shooter.cn/api/subapi.php', $data);

$rs = @json_decode($r, true);

if (!$rs)
{
  echo "获取字幕失败\n";
   exit;
}

if (in_array('debug', $_SERVER['argv']))
{
    print_r($rs);
}

$get_all = in_array('all', $_SERVER['argv']);

foreach ($rs as $key => $item)
{
    $url = $item['Files'][0];

    $content = curl_get($url['Link']);
    if (!strlen($content))
    {
     echo "获取失败 {$url['Link']}\n";
    }

    if (in_array('utf8', $_SERVER['argv']))
    {
        if (extension_loaded('mbstring'))
        {
            $content = mb_convert_encoding($content, 'UTF-8', 'GBK');
        }
        else
        {
            $content = iconv('UTF-8', 'gbk//IGNORE', $content);
        }
    }

    if (file_put_contents(substr($file, 0, strrpos($file, '.')).($key>0?'.'.$key:'').'.'.$url['Ext'], $content)>0)
    {
      echo "success {$url['Link']}\n";
    }
    else
    {
       echo "fail {$url['Link']}\n";
    }

    if (!$get_all)break;
}

echo "done.\n";

nginx+php-fpm环境下php输出图片、js、css等文件出现异常问题的原因及解决办法

【背景】
在我实际使用的环境中很少直接使用 nginx + php-fpm 方式搭建环境,大部分还都是使用apache,即便用到nginx,还只是用它监听80端口再代理apache的php做负载均衡器。
这次偶尔机会自己搭建了 nginx + php-fpm 环境,发现自己开发的MyQEE输出js、css以及image图片时会出现异常关闭的问题。

nginx配置了类似这样的rewrite

if (!-e $request_filename) {
    rewrite ^/.* /index.php last;
}

rewrite的意思是当请求的实际文件不存在时rewrite到index.php上

【那么问题来了】
由于有那个rewrite的存在,所以当请求类似 http://127.0.0.1/test.js 的URL,如果服务器上没有test.js,此时就会重定向导index.php,并不会直接返回404错误,而是由index.php来决定。

然后由这个index.php根据一些参数(例如uri=test.js)载入了另外一个js,并模拟页面输出,输出了一个200的头信息。
代码类似如下:

// index.php 文件,实际上的代码可能比这个复杂多,这里只是为了简单的说明问题
$file = 'my_other.js'; // 假设服务器上有另外一个my_other.js的文件
header('Content-Type: application/x-javascript');
header('Content-Length: '. filesize($file));
readfile($file);    // 把文件直接输出

这个看上去似乎没有任何问题,但是在nginx里却出现了奇怪问题。

浏览器再接受到部分内容输出后直接被关闭了。打开调试输出可以看到类似:
NET:ERR_CONTENT_LENGTH_MISMATCH
这样的错误。

如果在命令行里使用curl请求时,则会看到类似

curl: (18) transfer closed with 56097 bytes remaining to read

这样的错误。

【问题分析】
首先来看 nginx 的配置,类似如下:

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

这个实际上是nginx通过代理php-fpm来实现的输出,当一个css、js、图片等正常请求因为rewrite的原因被rewrite到了php上,nginx内部会认为是一个文档处理,然后对文档进行压缩获得了压缩后的内容长度,在输出达到这个长度后就错误的关闭了tcp连接,但是返回的header头信息的长度却是压缩前的长度,这样就导致了之前的错误。

【问题解决】
我尝试过把gzip的功能关闭但是实际上没有启到任何作用,最后我是通过这样的方法解决的,在gzip的gzip_types参数把对应的文件都加上这样就解决问题了,代码如下:

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
   
    # 以下解决用php输出js,css等文件导致出错的问题
    gzip on;
    gzip_min_length 1100;
    gzip_buffers 4 8k;
    gzip_types text/plain application/x-javascript text/css image;
}

2015-09-25更新
本人另外一个服务器实测也遇到这个问题,需要在加上才可以

proxy_buffering off

在nginx的默认配置里 gzip_types就只有 text/plain

【总结】
实际上的解决就是把对应输出的文件类型让nginx再压缩下即可,但是如果php输出的是图片或一些已经压缩过的二进制文件,那么这样做实际上是增加了服务器的负担。
所以如果有大量的图片、swf、zip等文件,建议还是通过sendfile的方式进行转发。
sendfile可参考:

cubieboard2 ubuntu 安装迅雷远程支持功能

使用迅雷官方推出了Xware的即可,官方论坛 http://luyou.xunlei.com/forum.php?mod=forumdisplay&fid=51&filter=typeid&typeid=1

官方提供了非常丰富的固件,选择cubieboard的版本即可,传到cb里面并解压开,会看到有4个文件,直接运行portal即可,其实就这么简单。

下面转载一些其它网站上看到的可能遇到的问题的解决办法:

但是,其中可能会遇到一些问题,比如
可执行文件无法运行
这可能是由于库文件的原因。使用个softlink来解决

ln -s /lib/arm-linux-gnueabihf/ld-linux.so.3 /lib/ld-linux.so.3


在lubuntu和debain环境下都验证可以运行。

2. 运行成功但是无法下载。
一种可能性是由于无法与远程服务器连接,所以出现了在远程 http://yuancheng.xunlei.com 网页上显示服务器离线。
一种可能性是没有挂载磁盘。迅雷是检测挂载的磁盘来作为下载的目录。所以要保证几个事情:

  1. 迅雷要具有写入权限;
  2. 必须要有挂载的磁盘,而且这个磁盘必须是 不是挂载到\ 即根目录的 。
sudo mkdir /mnt/xunlei #建立一个文件夹
sudo chomd 0777 /mnt/xunlei #更改文件夹的权限
sudo mount –-bind /home/cb2/ /mnt/xunlei #cb2是实际目录, /mnt/xunlei 是挂载

到的目录

接下来运行即可。
下载迅雷文件。使用一些工具上传到相应的目录运行。
这里给出的是直接在ssh命令端的

接下来看到迅雷运行成功之后,输入:

curl "http://192.168.1.120:9000/getsysinfo"

然后会看到这个

[ 0, 1, 1, 0, "7DHS94", 1, "201_2.1.3.121", "shdixang", 1 ]

其中有用的几项为:
第一项:0表示返回结果成功
第二项:1表示检测网络正常,0表示检测网络异常
第四项:1表示已绑定成功,0表示未绑定
第五项:未绑定的情况下,为绑定的需要的激活码
第六项:1表示磁盘挂载检测成功,0表示磁盘挂载检测失败
使用迅雷帐号登录迅雷远程下载页面 http://yuancheng.xunlei.com,点【添加】,选择【路由器】并填入激活码,点【确定】即绑定成功。
绑定之后大概可以看到类似于这样的

[ 0, 1, 1, 1, "", 1, "201_2.1.3.121", "shdixang", 1 ]

接下来就可以在 http://yuancheng.xunlei.com 添加下载任务了。

———————————
更新我遇到的问题
由于我并不是直接挂载的移动硬盘,而是timecapsule(苹果的时间胶囊)共享出来的网络硬盘,在搭建成功后,添加任务一直失败,提示#213013错误,但是这个错误在错误文档里又找不到,找了2天终于看到有人遇到类似的问题,并且迅雷的技术支持也回复了,说是硬盘问题,需要换硬盘或者是修复硬盘。但是我挂载的是网络硬盘怎么整呢?后来终于找到解决的办法了,如果你也是用timecapsule挂载的可以看看我的办法。
其实很简单,因为我之前是用 mount.cifs 进行挂载的,读写都没问题,这个是window的协议,后来我试了改用afp协议(苹果专用的网络共享协议)挂载就好了,首先需要安装mount_afp插件

apt-get install afpfs-ng-utils

安装好后,就有 mount_afp 命令了。
使用方法:

mount_afp afp://user:pass@ip/Data /mnt/xunlei

如果不想直接把密码输入到命令里,可以直接用横线-替代,即:

mount_afp afp://user:-@ip/Data /mnt/xunlei

回车后就提示你输入密码了。

挂载成功会提示success的,然后用 mount 命令就可以看到挂载信息了,比如我的是这样的:

root@cubieboard:~# mount
/dev/nandd on / type ext4 (rw)
none on /proc type proc (rw,noexec,nosuid,nodev)
none on /sys type sysfs (rw,noexec,nosuid,nodev)
none on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,noexec,nosuid,nodev)
none on /sys/fs/cgroup type tmpfs (rw)
none on /sys/fs/fuse/connections type fusectl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
none on /run/user type tmpfs (rw,noexec,nosuid,nodev,size=104857600,mode=0755)
jonwang's Time Capsule:Data on /mnt/xunlei type fuse.jonwang's Time Capsule:Data (rw,nosuid,nodev)

最后一条就是,和使用cifs挂载是不一样的。

这样挂载后,就可以成功下载了,现在屡试不爽~不过发现一个不是问题的问题,当下载速度很慢,一段时间没速度后,挂载的硬盘就会休眠,过后如果有速度了,就会提示#5的硬盘错误。只要在服务器里 ls /mnt/xunlei 等待数秒钟后就可以恢复连接了

希望以上能帮助到需要的人。

cubieboard2 ubuntu 13.06 中文列表显示???问号的问题的解决

在之前发过一个文章在cb2里安装ubuntu,地址 https://www.queyang.com/blog/archives/430

装好后无法使用中文字体,并且中文文件列表会显示???这样的问号。之前一直没注意,现在补充下解决办法,很简单:

vim /var/lib/locales/supported.d/local

将内容改成即可

zh_CN.UTF-8 UTF-8
zh_CN.GBK GBK
zh_CN.GB2312 GB2312
zh_CN.GB18030 GB18030

使用git的subtree将已有项目的某个目录分离成独立项目

git在1.8版本后加入了subtree的功能,这个功能比之前的submodule功能强大很多,而且很好用,还是老版本的赶紧升级下吧。

一些使用方法可参见这个页面: http://aoxuis.me/posts/2013/08/07/git-subtree/

当一个项目在开发若干时间后,希望将某个目录单独出一个项目来开发,此时就可以利用这个subtree的功能分离里。

然而直接用git subtree add 的命令会出现 prefix ‘***’ already exists. 这样的错误提示,
这是因为对应的目录已经存在,不能直接添加,需要按下面的方式把对应的目录剥离开然后再加入subtree

具体的操作方式是这样的:
1. 首先cd到需要处理的项目的目录:

pushd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>    # 将需要分离的目录的提交日志分离成一个独立的临时版本
popd

2. 创建一个新的repo

mkdir <new-repo>
pushd <new-repo>

git init
git pull </path/to/big-repo> <name-of-new-branch>

3. 连接的github

git remote add origin <git@github.com:my-user/new-repo.git>
git push origin -u master

其中第2, 3两步也可以直接clone已有的hub,这样做:

git clone <git@github.com:my-user/new-repo.git> <dir-of-tmp-repo>
git pull </path/to/big-repo> <name-of-new-branch>
git push origin -u master

4. 清理数据

popd # get out of <new-repo>
pushd <big-repo>

git rm -rf <name-of-folder>
git commit -m '移除相应模块'            # 提交删除申请
git branch -D <name-of-new-branch>     # 删除临时分支,也可以不移除,自己看情况

5. 添加subtree

git subtree add -P <name-of-folder> <git@github.com:my-user/new-repo.git> master
git push origin master

执行到第3步时,对应的目录已经剥离出来形成独立的项目了。第4,5步主要是把当前项目的对应的文件给删除,可以不执行,以后可以通过

# 更新
git subtree pull -P <name-of-folder> <git@github.com:my-user/new-repo.git> master

# 提交
git subtree push -P <name-of-folder> <git@github.com:my-user/new-repo.git> master

来执行相应的subtree操作