<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>神仙的仙居</title>
	<atom:link href="http://xiezhenye.com/feed" rel="self" type="application/rss+xml" />
	<link>http://xiezhenye.com</link>
	<description>这里是神仙，也就是谢振业的blog。走过路过，记得要回贴 ^_^</description>
	<lastBuildDate>Sat, 21 Jan 2012 05:23:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>读取 mysql binlog 开始和结束时间</title>
		<link>http://xiezhenye.com/2012/01/%e8%af%bb%e5%8f%96-mysql-binlog-%e5%bc%80%e5%a7%8b%e5%92%8c%e7%bb%93%e6%9d%9f%e6%97%b6%e9%97%b4.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e8%25af%25bb%25e5%258f%2596-mysql-binlog-%25e5%25bc%2580%25e5%25a7%258b%25e5%2592%258c%25e7%25bb%2593%25e6%259d%259f%25e6%2597%25b6%25e9%2597%25b4</link>
		<comments>http://xiezhenye.com/2012/01/%e8%af%bb%e5%8f%96-mysql-binlog-%e5%bc%80%e5%a7%8b%e5%92%8c%e7%bb%93%e6%9d%9f%e6%97%b6%e9%97%b4.html#comments</comments>
		<pubDate>Sat, 21 Jan 2012 05:22:42 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[binlog]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3695</guid>
		<description><![CDATA[mysql binlog 记录了所有可能涉及更新的操作，可以用来作为增量备份的一种选择。为了管理 binlog ，需要读取每个 binlog 文件的准确的开始和结束时间。用 mysqlbinlog 工具可以解析 binlog 文件，所以也可以通过分析输出结果来获取。但是 mysqlbinlog 只能顺序读取记录，如果只是分析开始时间还好，要分析结束时间，就必须等它把整个 binlog 处理完。在 binlog 文件体积大的时候，代价就大了些。好在 mysql 对 binlog 文件的格式是公开的，所以我们可以直接通过解析文件自己实现。 binlog 文件的格式在 http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log 可以找到。每个 binlog 文件都有相同的开头：0xfe 0&#215;62 0&#215;69 0x6e 。也就是 0xfe 后面加上 bin 。之后，就是一个个事件数据。binlog 的事件类型有很多种，但每个 binlog 文件的第一个事件一定是格式描述事件（format description event），描述了 binlog 文件格式版本信息；最后一个时间一定是轮转事件（rotate event），记录了下一个 binlog 的文件名和事件开始偏移位置。每个事件都有一个一致的事件头，其中就有事件的时间戳、事件类型等。读取第一个事件和最后一个事件的信息就可以获取 binlog 文件的准确开始和结束时间了。 读取第一个事件 format description event 要容易一些，seek 跳过文件头，读取事件头就行了。读取最后一个事件的时间要稍麻烦些。因为事件的长度是不固定的。对于轮转事件来说，除了事件头以外，后面还有一个 64位整数的开始位置偏移量以及下一个 binlog [...]]]></description>
			<content:encoded><![CDATA[<p>mysql binlog 记录了所有可能涉及更新的操作，可以用来作为增量备份的一种选择。为了管理 binlog ，需要读取每个 binlog 文件的准确的开始和结束时间。用 mysqlbinlog 工具可以解析 binlog 文件，所以也可以通过分析输出结果来获取。但是 mysqlbinlog 只能顺序读取记录，如果只是分析开始时间还好，要分析结束时间，就必须等它把整个 binlog 处理完。在 binlog 文件体积大的时候，代价就大了些。好在 mysql 对 binlog 文件的格式是公开的，所以我们可以直接通过解析文件自己实现。</p>
<p>binlog 文件的格式在 http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log 可以找到。每个 binlog 文件都有相同的开头：0xfe 0&#215;62 0&#215;69 0x6e 。也就是 0xfe 后面加上 bin 。之后，就是一个个事件数据。binlog 的事件类型有很多种，但每个 binlog 文件的第一个事件一定是格式描述事件（format description event），描述了 binlog 文件格式版本信息；最后一个时间一定是轮转事件（rotate event），记录了下一个 binlog 的文件名和事件开始偏移位置。每个事件都有一个一致的事件头，其中就有事件的时间戳、事件类型等。读取第一个事件和最后一个事件的信息就可以获取 binlog 文件的准确开始和结束时间了。</p>
<p>读取第一个事件 format description event 要容易一些，seek 跳过文件头，读取事件头就行了。读取最后一个事件的时间要稍麻烦些。因为事件的长度是不固定的。对于轮转事件来说，除了事件头以外，后面还有一个 64位整数的开始位置偏移量以及下一个 binlog 的文件名。长度不确定的部分就是最后的文件名部分。好在那个偏移量是一个固定的值：4（也就是跳过文件头），所以可以从后往前读取，用它来作为标记，检查是否读完了文件名。然后就可以跳过文件名和偏移量，读取最后一个事件的事件头了。</p>
<p>php 代码如下：</p>
<pre>
&lt;?php
/**
 * read binlog info
 *
 * A mysql binlog file is begin with a head "\xfebin" and then log evnets. The
 * first event is a format description event, the last event is a rotate event.
 *
 * For more infomation about mysql binlog format, see http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
 */
class BinlogInfo {
    const EVENT_HEAD_SIZE = 19;
    const FORMAT_DESCRIPTION_EVENT_DATA_SIZE = 59;
    const BINLOG_HEAD = "\xfebin";
    const FORMAT_DESCRIPTION_EVENT = 15;
    const ROTATE_EVENT = 4;

    private $eventHeadPackStr = '';
    private $formatDescriptionEventDataPackStr = '';

    function __construct() {
        $this-&gt;eventHeadPackStr = $this-&gt;eventHeadPackStr();
        $this-&gt;formatDescriptionEventDataPackStr = $this-&gt;formatDescriptionEventDataPackStr();
    }

    protected function eventHeadPackStr() {
        $event_header_struct = array(
            'timestamp' =&gt; 'l',
            'type_code' =&gt; 'c',
            'server_id' =&gt; 'l',
            'event_length' =&gt; 'l',
            'next_position' =&gt; 'l',
            'flags' =&gt; 's',
        );
        return $this-&gt;toPackStr($event_header_struct);
    }

    protected function formatDescriptionEventDataPackStr() {
        $format_description_event_data_struct = array(
            'binlog_version' =&gt; 's',
            'server_version' =&gt; 'a50',
            'create_timestamp' =&gt; 'l',
            'head_length' =&gt; 'c'
        );
        return $this-&gt;toPackStr($format_description_event_data_struct);
    }

    protected function toPackStr($arr) {
        $ret = '';
        foreach ($arr as $k=&gt;$v) {
            $ret.= '/'.$v.$k;
        }
        $ret = substr($ret, 1);
        return $ret;
    }

    /**
     * @param resource $file
     *
     * Mysql binlog file begin with a 4 bytes head: "\xfebin".
     */
    protected function isBinlog($file) {
        rewind($file);
        $head = fread($file, strlen(self::BINLOG_HEAD));
        return $head == self::BINLOG_HEAD;
    }

    /**
     * @param resource $file
     *
     * Format description event is the first event of a binlog file
     */
    protected function readFormatDescriptionEvent($file) {
        fseek($file, strlen(self::BINLOG_HEAD), SEEK_SET);
        $head_str = fread($file, self::EVENT_HEAD_SIZE);
        $head = unpack($this-&gt;eventHeadPackStr, $head_str);
        if ($head['type_code'] != self::FORMAT_DESCRIPTION_EVENT) {
            return null;
        }
        $data_str= fread($file, self::FORMAT_DESCRIPTION_EVENT_DATA_SIZE);
        $data = unpack($this-&gt;formatDescriptionEventDataPackStr, $data_str);

        return array('head'=&gt;$head, 'data'=&gt;$data);
    }

    /**
     * @param resource $file
     *
     * Rotate event is the last event of a binglog.
     * After event header, there is a 64bit int indicate the first event
     * position of next binlog file and next binlog file name without \0 at end.
     * The position is always be 4 (hex: 0400000000000000).
     *
     */
    protected function readRotateEvent($file)
    {
        /**
         * Rotate event size is 19(head size) + 8(pos) + len(filename).
         * 100 bytes can contain a filename which length less than 73 bytes and
         * it is short than the length of format description event so filesize -
         * bufsize will never be negative.
         */
        $bufsize = 100;
        $size_pos = 8;
        fseek($file, -$bufsize, SEEK_END);
        $buf = fread($file, $bufsize);
        $min_begin = strlen(self::BINLOG_HEAD) + self::EVENT_HEAD_SIZE + $size_pos;
        $ok = false;
        for ($i = $bufsize - 1; $i &gt; $min_begin; $i--) {
            if ($buf[$i] == "\0") {
                $ok = true;
                break;
            }
        }
        if (!$ok) {
            return null;
        }
        $next_filename = substr($buf, $i + 1);

        $head_str = substr($buf, $i + 1 - $size_pos - self::EVENT_HEAD_SIZE, self::EVENT_HEAD_SIZE);
        $head = unpack($this-&gt;eventHeadPackStr, $head_str);
        if ($head['type_code'] != self::ROTATE_EVENT) {
            return null;
        }
        return array('head'=&gt;$head, 'nextFile'=&gt;$next_filename);
    }

    /**
     * @param string $path path to binlog file
     */
    function read($path) {
        $file = fopen($path, 'r');
        if (!$file) {
            return null;
        }
        if (!$this-&gt;isBinlog($file)) {
            fclose($file);
            return null;
        }

        $fde = $this-&gt;readFormatDescriptionEvent($file);
        $re = $this-&gt;readRotateEvent($file);
        fclose($file);
        return array(
            'beginAt' =&gt; $fde['head']['timestamp'],
            'endAt' =&gt; $re['head']['timestamp'],
            'nextFile' =&gt; $re['nextFile'],
            'serverVersion' =&gt; $fde['data']['server_version'],
        );
    }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2012/01/%e8%af%bb%e5%8f%96-mysql-binlog-%e5%bc%80%e5%a7%8b%e5%92%8c%e7%bb%93%e6%9d%9f%e6%97%b6%e9%97%b4.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>三亚</title>
		<link>http://xiezhenye.com/2012/01/%e4%b8%89%e4%ba%9a.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e4%25b8%2589%25e4%25ba%259a</link>
		<comments>http://xiezhenye.com/2012/01/%e4%b8%89%e4%ba%9a.html#comments</comments>
		<pubDate>Fri, 06 Jan 2012 16:04:42 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[旅行]]></category>
		<category><![CDATA[三亚]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3656</guid>
		<description><![CDATA[元旦去了三亚。之前想着念着很久，这回终于下定决心出行，实现了在冬天到三亚去的愿望。 一下飞机，立马从冬天变成了夏天。在上海穿羽绒服，到了三亚就得是短袖了。 三亚湾和大东海的沙滩还是很软的。虽然海水一般，但在沙滩上坐着走着看海玩沙看日落，在沙滩上走，让海水漫过脚，一切都还是很惬意的，在大东海，还碰到一场沙滩婚礼。在三亚还有好多俄国的大叔大妈帅哥美女…… 第二天我们上蜈支洲岛。船接近码头的时候，就看到了真正色彩明亮的海水。从沙滩开始，海水从透明到蓝绿色再到深蓝色。 沙滩上面还有潮水冲上来的碎珊瑚。虽然走起来会有些硌脚，但却让沙滩变得洁白。在沙滩上捡珊瑚、贝壳，也是件很有意思的事情。 第二天坐环岛观光车，看到了岛的另一面。与之前的洁白的沙滩美丽的海水完全不同。这边礁石嶙峋，惊涛拍岸。 而在我们要离开的时候，云层散开，阳光直射海面。海水的颜色更加艳丽。看着真不想离开了~ 最后回到上海的时候，一下子感觉，好冷啊…………………………]]></description>
			<content:encoded><![CDATA[<p>元旦去了三亚。之前想着念着很久，这回终于下定决心出行，实现了在冬天到三亚去的愿望。</p>
<p>一下飞机，立马从冬天变成了夏天。在上海穿羽绒服，到了三亚就得是短袖了。</p>
<p>三亚湾和大东海的沙滩还是很软的。虽然海水一般，但在沙滩上坐着走着看海玩沙看日落，在沙滩上走，让海水漫过脚，一切都还是很惬意的，在大东海，还碰到一场沙滩婚礼。在三亚还有好多俄国的大叔大妈帅哥美女……</p>
<p><a href="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1858.jpg"><img class="alignnone size-full wp-image-3670" title="IMGP1858" src="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1858.jpg" alt="" width="600" height="399" /></a></p>
<p><a href="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP2006.jpg"><img title="IMGP2006" src="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP2006.jpg" alt="" width="600" height="399" /></a></p>
<p>第二天我们上蜈支洲岛。船接近码头的时候，就看到了真正色彩明亮的海水。从沙滩开始，海水从透明到蓝绿色再到深蓝色。</p>
<p><a href="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1883.jpg"><img class="alignnone size-full wp-image-3662" title="IMGP1883" src="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1883.jpg" alt="" width="600" height="399" /></a></p>
<p>沙滩上面还有潮水冲上来的碎珊瑚。虽然走起来会有些硌脚，但却让沙滩变得洁白。在沙滩上捡珊瑚、贝壳，也是件很有意思的事情。</p>
<p><a href="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1983.jpg"><img class="alignnone size-full wp-image-3666" title="IMGP1983" src="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP1983.jpg" alt="" width="600" height="400" /></a></p>
<p>第二天坐环岛观光车，看到了岛的另一面。与之前的洁白的沙滩美丽的海水完全不同。这边礁石嶙峋，惊涛拍岸。</p>
<p><a href="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP19991.jpg"><img class="alignnone size-full wp-image-3668" title="IMGP1999" src="http://xiezhenye.com/wp-content/uploads/2012/01/IMGP19991.jpg" alt="" width="600" height="399" /></a></p>
<p>而在我们要离开的时候，云层散开，阳光直射海面。海水的颜色更加艳丽。看着真不想离开了~</p>
<p>最后回到上海的时候，一下子感觉，好冷啊…………………………</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2012/01/%e4%b8%89%e4%ba%9a.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>bash 中有效建立锁</title>
		<link>http://xiezhenye.com/2011/12/bash-%e4%b8%ad%e6%9c%89%e6%95%88%e5%bb%ba%e7%ab%8b%e9%94%81.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=bash-%25e4%25b8%25ad%25e6%259c%2589%25e6%2595%2588%25e5%25bb%25ba%25e7%25ab%258b%25e9%2594%2581</link>
		<comments>http://xiezhenye.com/2011/12/bash-%e4%b8%ad%e6%9c%89%e6%95%88%e5%bb%ba%e7%ab%8b%e9%94%81.html#comments</comments>
		<pubDate>Wed, 21 Dec 2011 09:02:02 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[锁]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3650</guid>
		<description><![CDATA[有时候需要防止一段代码在被同时执行，就需要使用锁来防止代码重入。常常见到这样的代码： if [ -f /var/lock/mylock ]; then touch /var/lock/mylock ... rm -f /var/lock/mylock fi 但实际上，这样是有问题的。如果两个进程在 test ( [ ) 和 touch 之间，另一个进程同时执行，就会出现竞争问题，最后就可能出现同时运行的情况。要避免这种情况出现，就得改一下加锁的方式。可以用 mkdir 代替 touch，这样在锁目录以及存在的时候，会直接出错； if mkdir /var/lock/mylock 2>/dev/null; then ... rm -rf /var/lock/mylock fi 或者先用 set -C ，让 > 重定向在文件已经存在时出错，然后用 echo &#8230; > 来生成锁文件 if ( set -C; echo $$> /var/lock/mylock 2>/dev/null); then ... [...]]]></description>
			<content:encoded><![CDATA[<p>有时候需要防止一段代码在被同时执行，就需要使用锁来防止代码重入。常常见到这样的代码：</p>
<pre>
if [ -f /var/lock/mylock ]; then
  touch /var/lock/mylock
  ...
  rm -f /var/lock/mylock
fi
</pre>
<p>但实际上，这样是有问题的。如果两个进程在 test ( [ ) 和 touch 之间，另一个进程同时执行，就会出现竞争问题，最后就可能出现同时运行的情况。要避免这种情况出现，就得改一下加锁的方式。可以用 mkdir 代替 touch，这样在锁目录以及存在的时候，会直接出错；</p>
<pre>
if mkdir /var/lock/mylock 2>/dev/null; then
  ...
  rm -rf /var/lock/mylock
fi
</pre>
<p>或者先用 set -C ，让 > 重定向在文件已经存在时出错，然后用 echo &#8230; > 来生成锁文件</p>
<pre>
if ( set -C; echo $$> /var/lock/mylock 2>/dev/null); then
  ...
  rm -f /var/lock/mylock
fi
</pre>
<p>这两种方法可以保证加锁和检测锁是一个原子操作，避免竞争问题。</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/12/bash-%e4%b8%ad%e6%9c%89%e6%95%88%e5%bb%ba%e7%ab%8b%e9%94%81.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>坑爹的 phpunit</title>
		<link>http://xiezhenye.com/2011/12/%e5%9d%91%e7%88%b9%e7%9a%84-phpunit.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e5%259d%2591%25e7%2588%25b9%25e7%259a%2584-phpunit</link>
		<comments>http://xiezhenye.com/2011/12/%e5%9d%91%e7%88%b9%e7%9a%84-phpunit.html#comments</comments>
		<pubDate>Thu, 15 Dec 2011 10:16:50 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[phpunit php]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3643</guid>
		<description><![CDATA[最新的 phpunit 是 3.6 。之前用的是 3.5 。在一个新机器上安装，按官网的方法，装了新版本后，运行，出错！ Call to undefined method PHP_CodeCoverage_Filter::getInstance() 新版本的 CodeCoverage 改变了调用方法。从 phpunit 3.4 升级到 3.5 的时候，CodeCoverage 的使用也发生过改变。这回又来了…… 这次不想改测试代码，于是打算用旧的 PHP_CodeCoverage 。于是 sudo pear install -f http://pear.phpunit.de/get/PHP_CodeCoverage-1.0.5.tgz 结果，还是出错 Class &#8216;File_Iterator&#8217; not found 看看 pear 的目录里，明明有 File/Iterator.php 啊，再看看那个 Iterator/Factory.php ，里面没有 require。。。 试着加一条 require_once 'File/Iterator.php'; 这回 ok 了。不过改代码这事也挺恶心。于是试着把 File_Iterator 也用个旧版本 sudo pear install -f http://pear.phpunit.de/get/File_Iterator-1.2.6.tgz [...]]]></description>
			<content:encoded><![CDATA[<p>最新的 phpunit 是 3.6 。之前用的是 3.5 。在一个新机器上安装，按官网的方法，装了新版本后，运行，出错！</p>
<p>Call to undefined method PHP_CodeCoverage_Filter::getInstance()</p>
<p>新版本的 CodeCoverage 改变了调用方法。从 phpunit 3.4 升级到 3.5 的时候，CodeCoverage 的使用也发生过改变。这回又来了…… 这次不想改测试代码，于是打算用旧的 PHP_CodeCoverage 。于是</p>
<pre>sudo pear install -f http://pear.phpunit.de/get/PHP_CodeCoverage-1.0.5.tgz</pre>
<p>结果，还是出错</p>
<p>Class &#8216;File_Iterator&#8217; not found</p>
<p>看看 pear 的目录里，明明有 File/Iterator.php 啊，再看看那个 Iterator/Factory.php ，里面没有 require。。。 试着加一条</p>
<pre>require_once 'File/Iterator.php';</pre>
<p>这回 ok 了。不过改代码这事也挺恶心。于是试着把 File_Iterator 也用个旧版本</p>
<pre>sudo pear install -f http://pear.phpunit.de/get/File_Iterator-1.2.6.tgz</pre>
<p>然后就又 ok 了。</p>
<p>这货为啥总是改接口呢？还不保持向后兼容呢…… </p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/12/%e5%9d%91%e7%88%b9%e7%9a%84-phpunit.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>让 php 用 nginx 打包 zip</title>
		<link>http://xiezhenye.com/2011/11/%e8%ae%a9-php-%e7%94%a8-nginx-%e6%89%93%e5%8c%85-zip.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e8%25ae%25a9-php-%25e7%2594%25a8-nginx-%25e6%2589%2593%25e5%258c%2585-zip</link>
		<comments>http://xiezhenye.com/2011/11/%e8%ae%a9-php-%e7%94%a8-nginx-%e6%89%93%e5%8c%85-zip.html#comments</comments>
		<pubDate>Thu, 01 Dec 2011 07:09:07 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[php zip nginx]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3621</guid>
		<description><![CDATA[php 本身有 zip 模块，可以生产 zip 文件。但是这个 zip 模块只能使用本地文件来打包。如果需要打包输出的文件来自网络，就得先保存临时文件。在文件数量多或者文件大的时候就很杯具。另外，由 php 来输出大的打包文件会占用 php 进程大量时间，影响并发能力。 nginx 有一个第三方模块，mod_zip 。同样可以输出 zip 包。和 X-Accel-Redirect 有点类似，只需要 php 输出相应文件的路径等信息，然后给一个特殊的响应头即可。 nginx zip 模块使用的响应头是 X-Archive-Files: zip 。加上这个响应头，nginx zip 模块就会处理响应正文，完成打包输出。 比如： printf("%s %d %s %s\n", $crc32, $size, $url, $path ); 逐条输出要打包的文件。 $crc32 是 16 进制的文件 crc32 值。也可以不提供，用 &#8220;-&#8221; 代替。不过这样就没法用 Range 分块下载，断点续传了。 $size 是文件大小的十进制整数。 $url 是要打包的源地址。如果要打包一个本地文件，可以先在 nginx [...]]]></description>
			<content:encoded><![CDATA[<p>php 本身有 zip 模块，可以生产 zip 文件。但是这个 zip 模块只能使用本地文件来打包。如果需要打包输出的文件来自网络，就得先保存临时文件。在文件数量多或者文件大的时候就很杯具。另外，由 php 来输出大的打包文件会占用 php 进程大量时间，影响并发能力。</p>
<p>nginx 有一个第三方模块，<a href="http://wiki.nginx.org/NgxZip" target="_blank">mod_zip</a> 。同样可以输出 zip 包。和 X-Accel-Redirect 有点类似，只需要 php 输出相应文件的路径等信息，然后给一个特殊的响应头即可。</p>
<p>nginx zip 模块使用的响应头是 X-Archive-Files: zip 。加上这个响应头，nginx zip 模块就会处理响应正文，完成打包输出。</p>
<p>比如：</p>
<pre>
printf("%s %d %s %s\n", $crc32, $size, $url, $path );
</pre>
<p>逐条输出要打包的文件。</p>
<p>$crc32 是 16 进制的文件 crc32 值。也可以不提供，用 &#8220;-&#8221; 代替。不过这样就没法用 Range 分块下载，断点续传了。<br />
$size 是文件大小的十进制整数。<br />
$url 是要打包的源地址。如果要打包一个本地文件，可以先在 nginx 中做一个 internal path。<br />
$path 是 zip 包中的路径。</p>
<p>不过这样没法创建空目录。一方面，zip 格式开始就没有定义空目录，后来的标准和软件都是通过加一个 / 结尾的 0 大小文件来实现的。这时，就需要先在 nginx 中做一个 internal 的 0 大小文件，比如位于 /_0 。然后输出</p>
<pre>
printf("%s %d %s %s\n", '00000000', 0, '/_0', $path.'/');
</pre>
<p>如果要支持中文路径，可以使用 X-Archive-Charset: utf8 这样的响应头，内容为你输出的编码。nginx zip 模块会按标准转换成 utf8 的标准格式。不过各个软件对这个 zip 的标准支持不一，比如 windows 的 zip 目录就不支持，只能以 gbk 编码直接输出。其他软件对编码支持效果也各不相同。测试过的 winrar，7zip，windows zip 目录中，winrar 倒是都可以很好支持。7zip 可能会把部分中文空目录变成 0 大小文件。所以，这点还需要自己斟酌处理。</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/11/%e8%ae%a9-php-%e7%94%a8-nginx-%e6%89%93%e5%8c%85-zip.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>go 语言 Makefile 指定依赖包位置</title>
		<link>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80-makefile-%e6%8c%87%e5%ae%9a%e4%be%9d%e8%b5%96%e5%8c%85%e4%bd%8d%e7%bd%ae.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=go-%25e8%25af%25ad%25e8%25a8%2580-makefile-%25e6%258c%2587%25e5%25ae%259a%25e4%25be%259d%25e8%25b5%2596%25e5%258c%2585%25e4%25bd%258d%25e7%25bd%25ae</link>
		<comments>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80-makefile-%e6%8c%87%e5%ae%9a%e4%be%9d%e8%b5%96%e5%8c%85%e4%bd%8d%e7%bd%ae.html#comments</comments>
		<pubDate>Mon, 28 Nov 2011 03:06:54 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[go make]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3604</guid>
		<description><![CDATA[编译 go 程序可以使用自带的一些 Makefile 脚本来简化编写 Makefile 。官方的文档过于简略，没提到需要指定依赖包位置的方法。翻过那几个脚本代码后，发现原来有 LDIMPORTS 和 GCIMPORTS 可以指定。 比如： include $(GOROOT)/src/Make.inc LDIMPORTS=-L ./pkg/_obj GCIMPORTS=-I ./pkg/_obj TARG=tool GOFILES=\ tool.go\ include $(GOROOT)/src/Make.cmd GCIMPORTS 指定编译阶段的参数，对 Make.cmd，Make.pkg 都有效。LDIMPORTS 指定链接阶段的参数，这个对 Make.pkg 就没用了。 另外，还可以用类似 CLEANFILES+= pkg/_obj ，在 make clean 的时候来清理更多的东西。 以及 all: pkg/_obj tool pkg/_obj: cd pkg; make 这样的方法在依赖包未编译时，自动编译依赖包。]]></description>
			<content:encoded><![CDATA[<p>编译 go 程序可以使用自带的一些 Makefile 脚本来简化编写 Makefile 。官方的文档过于简略，没提到需要指定依赖包位置的方法。翻过那几个脚本代码后，发现原来有 LDIMPORTS 和 GCIMPORTS 可以指定。</p>
<p>比如：</p>
<pre>
include $(GOROOT)/src/Make.inc

LDIMPORTS=-L ./pkg/_obj
GCIMPORTS=-I ./pkg/_obj

TARG=tool
GOFILES=\
	tool.go\

include $(GOROOT)/src/Make.cmd
</pre>
<p>GCIMPORTS 指定编译阶段的参数，对 Make.cmd，Make.pkg 都有效。LDIMPORTS 指定链接阶段的参数，这个对 Make.pkg 就没用了。</p>
<p>另外，还可以用类似 CLEANFILES+= pkg/_obj ，在 make clean 的时候来清理更多的东西。</p>
<p>以及</p>
<pre>
all: pkg/_obj tool

pkg/_obj:
	cd pkg; make
</pre>
<p>这样的方法在依赖包未编译时，自动编译依赖包。</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80-makefile-%e6%8c%87%e5%ae%9a%e4%be%9d%e8%b5%96%e5%8c%85%e4%bd%8d%e7%bd%ae.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>让 linux 交互式命令行程序支持方向键等功能</title>
		<link>http://xiezhenye.com/2011/11/%e8%ae%a9-linux-%e4%ba%a4%e4%ba%92%e5%bc%8f%e5%91%bd%e4%bb%a4%e8%a1%8c%e7%a8%8b%e5%ba%8f%e6%94%af%e6%8c%81%e6%96%b9%e5%90%91%e9%94%ae%e7%ad%89%e5%8a%9f%e8%83%bd.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e8%25ae%25a9-linux-%25e4%25ba%25a4%25e4%25ba%2592%25e5%25bc%258f%25e5%2591%25bd%25e4%25bb%25a4%25e8%25a1%258c%25e7%25a8%258b%25e5%25ba%258f%25e6%2594%25af%25e6%258c%2581%25e6%2596%25b9%25e5%2590%2591%25e9%2594%25ae%25e7%25ad%2589%25e5%258a%259f%25e8%2583%25bd</link>
		<comments>http://xiezhenye.com/2011/11/%e8%ae%a9-linux-%e4%ba%a4%e4%ba%92%e5%bc%8f%e5%91%bd%e4%bb%a4%e8%a1%8c%e7%a8%8b%e5%ba%8f%e6%94%af%e6%8c%81%e6%96%b9%e5%90%91%e9%94%ae%e7%ad%89%e5%8a%9f%e8%83%bd.html#comments</comments>
		<pubDate>Mon, 14 Nov 2011 02:41:37 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[linux rlwrap]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3509</guid>
		<description><![CDATA[自己写交互式命令行程序，通常都是从标准输入输出读写。运行的时候，只能敲完整一个命令，然后回车。想要按方向键移动光标，按 del 往后删除都只会出现个 ^[[A 这样的东西。要实现那些功能，得处理终端命令，这个活并不轻松。虽然有 readline 这样的库，但还是会带来很多麻烦。 好在 unix 的哲学就是一个程序干一件事情，并把它做到最好。于是就有一个 rlwrap 的程序。用这个程序启动你的工具，你的程序就立马有了那些功能。还可以按上下键翻阅历史命令，ctrl+r 来搜索历史输入等。 &#160;]]></description>
			<content:encoded><![CDATA[<p>自己写交互式命令行程序，通常都是从标准输入输出读写。运行的时候，只能敲完整一个命令，然后回车。想要按方向键移动光标，按 del 往后删除都只会出现个 ^[[A 这样的东西。要实现那些功能，得处理终端命令，这个活并不轻松。虽然有 readline 这样的库，但还是会带来很多麻烦。</p>
<p>好在 unix 的哲学就是一个程序干一件事情，并把它做到最好。于是就有一个 rlwrap 的程序。用这个程序启动你的工具，你的程序就立马有了那些功能。还可以按上下键翻阅历史命令，ctrl+r 来搜索历史输入等。</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/11/%e8%ae%a9-linux-%e4%ba%a4%e4%ba%92%e5%bc%8f%e5%91%bd%e4%bb%a4%e8%a1%8c%e7%a8%8b%e5%ba%8f%e6%94%af%e6%8c%81%e6%96%b9%e5%90%91%e9%94%ae%e7%ad%89%e5%8a%9f%e8%83%bd.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>go 语言并发机制 goroutine 初探</title>
		<link>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80%e5%b9%b6%e5%8f%91%e6%9c%ba%e5%88%b6-goroutine-%e5%88%9d%e6%8e%a2.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=go-%25e8%25af%25ad%25e8%25a8%2580%25e5%25b9%25b6%25e5%258f%2591%25e6%259c%25ba%25e5%2588%25b6-goroutine-%25e5%2588%259d%25e6%258e%25a2</link>
		<comments>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80%e5%b9%b6%e5%8f%91%e6%9c%ba%e5%88%b6-goroutine-%e5%88%9d%e6%8e%a2.html#comments</comments>
		<pubDate>Fri, 11 Nov 2011 10:38:22 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[go goroutine]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3501</guid>
		<description><![CDATA[go 语言的一个很大的优势就是可以方便地编写并发程序。go 语言内置了 goroutine 机制。这是一种类似 coroutaine（协程） 的东西。但是又不完全相同。 比如这个例子： package main import ( "fmt"; "strconv" ) func main() { ch := make(chan int) task("A", ch) task("B", ch) fmt.Printf("begin\n") &#60;-ch &#60;-ch } func task(name string, ch chan int) { go func() { i:= 1 for { fmt.Printf("%s %d\n", name, i) i++ } ch &#60;- 1 }(); } [...]]]></description>
			<content:encoded><![CDATA[<p>go 语言的一个很大的优势就是可以方便地编写并发程序。go 语言内置了 goroutine 机制。这是一种类似 coroutaine（协程） 的东西。但是又不完全相同。</p>
<p>比如这个例子：</p>
<pre>
package main
import (
    "fmt";
    "strconv"
)
func main() {
    ch := make(chan int)
    task("A", ch)
    task("B", ch)
    fmt.Printf("begin\n")
    &lt;-ch
    &lt;-ch
}
func task(name string, ch chan int) {
    go func() {
        i:= 1
        for {
            fmt.Printf("%s %d\n", name, i)
            i++
        }
        ch &lt;- 1
    }();
}
</pre>
<p>运行以后，发现会 A B 两个 goroutine 会交替执行，并像传统的协程需要手动 schedule 。看起来很神奇。</p>
<p>稍稍改一下代码，把</p>
<pre>
fmt.Printf("%s %d\n", name, i)
</pre>
<p>改成</p>
<pre>
print(name + " " + strconv.Itoa(i) + "\n")
</pre>
<p>再看看。神奇的效果消失了，只有 A 被运行。</p>
<p>那么 fmt.Printf 和 print 有什么差别，导致了这个结果呢？</p>
<p>大致翻了一下 go 的代码，看出 go 语言在对 c lib 的包装上用了个 cgo 的方式。而在通过 cgo 调用 c 库的时候，会在调用时自动 schedule 切换走，在调用结束的时候再返回。这两个结果的差异就在于，fmt.Printf 是通过 cgo 包装的，而 print 则是原生实现的。所以在调用 fmt.Printf 的时候，就自动实现了调度。</p>
<p>传统的 coroutaine 在访问网络、数据库等 io 操作时仍然会阻塞，失去并发能力，所以通常需要结合异步 io 来实现。而现有的库如果本身未提供异步功能，就很难办，往往需要重新实现。而且，即使有异步 io 功能，也需要额外的开发，才能在表现上和以往顺序程序相同的方式。</p>
<p>go 语言的这种实现方式很好的解决了这个问题，可以充分利用现有的大量 c 库来包装。</p>
<p>同时，也还是可以使用 runtime.Gosched() 来手动调度。在运算量大的场景下，也还是必要的。</p>
<p>在使用 print 的例子里，如果使用 runtime.GOMAXPROCS(2)，又可以重新并行起来。这时，两个 goroutine 是在两个独立的线程中运行的。这又是 goroutine 和协程的一个不同点。不过启用多个线程并不见得能让程序更快。因为跨线程的上下文切换代价也是很大的。在上面那个简单的例子里，还会让程序变得更慢。降低上下文切换开销也是协程的一个优势所在。</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/11/go-%e8%af%ad%e8%a8%80%e5%b9%b6%e5%8f%91%e6%9c%ba%e5%88%b6-goroutine-%e5%88%9d%e6%8e%a2.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>让 wget 用上 socks 代理</title>
		<link>http://xiezhenye.com/2011/11/%e8%ae%a9-wget-%e7%94%a8%e4%b8%8a-socks-%e4%bb%a3%e7%90%86.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e8%25ae%25a9-wget-%25e7%2594%25a8%25e4%25b8%258a-socks-%25e4%25bb%25a3%25e7%2590%2586</link>
		<comments>http://xiezhenye.com/2011/11/%e8%ae%a9-wget-%e7%94%a8%e4%b8%8a-socks-%e4%bb%a3%e7%90%86.html#comments</comments>
		<pubDate>Mon, 07 Nov 2011 02:53:42 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[tsocks]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3497</guid>
		<description><![CDATA[由于众所周知的原因，国内访问国外网络经常会有问题，必须通过代理服务器。而在服务器上下载，通常都使用 wget ，而不会有 gui 环境来运行 firefox 之类的浏览器。wget 只能使用 http 代理，而无法直接使用 socks 代理。 有一个 tsocks 的工具，可以让其他程序使用上 socks  代理。对 debian 系 linux ，可以通过 apt-get install tsocks 来安装。其他发行版也很容易。 然后修改 /etc/tsocks.conf ： server = 127.0.0.1 server_type = 5 server_port = 1080 设置代理服务器地址，类型，端口。 然后给 wget 套上 tsocks 运行，就可以访问代理了（如果之前设置了 http 代理，要先清除掉）。 tsocks wget http://&#8230;&#8230; 这个方法不仅仅能用于 wget ，对于其他的本身不支持代理的工具也有效，比如某些软件包管理器如 pear， gems 等。]]></description>
			<content:encoded><![CDATA[<p>由于众所周知的原因，国内访问国外网络经常会有问题，必须通过代理服务器。而在服务器上下载，通常都使用 wget ，而不会有 gui 环境来运行 firefox 之类的浏览器。wget 只能使用 http 代理，而无法直接使用 socks 代理。</p>
<p>有一个 tsocks 的工具，可以让其他程序使用上 socks  代理。对 debian 系 linux ，可以通过 apt-get install tsocks 来安装。其他发行版也很容易。</p>
<p>然后修改 /etc/tsocks.conf ：</p>
<p>server = 127.0.0.1<br />
server_type = 5<br />
server_port = 1080</p>
<p>设置代理服务器地址，类型，端口。</p>
<p>然后给 wget 套上 tsocks 运行，就可以访问代理了（如果之前设置了 http 代理，要先清除掉）。</p>
<p>tsocks wget http://&#8230;&#8230;</p>
<p>这个方法不仅仅能用于 wget ，对于其他的本身不支持代理的工具也有效，比如某些软件包管理器如 pear， gems 等。</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/11/%e8%ae%a9-wget-%e7%94%a8%e4%b8%8a-socks-%e4%bb%a3%e7%90%86.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>blog 迁移到这里</title>
		<link>http://xiezhenye.com/2011/10/blog-%e8%bf%81%e7%a7%bb%e5%88%b0%e8%bf%99%e9%87%8c.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=blog-%25e8%25bf%2581%25e7%25a7%25bb%25e5%2588%25b0%25e8%25bf%2599%25e9%2587%258c</link>
		<comments>http://xiezhenye.com/2011/10/blog-%e8%bf%81%e7%a7%bb%e5%88%b0%e8%bf%99%e9%87%8c.html#comments</comments>
		<pubDate>Wed, 26 Oct 2011 07:05:11 +0000</pubDate>
		<dc:creator>神仙</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://xiezhenye.com/?p=3325</guid>
		<description><![CDATA[原先在 blogbus ，现在搬到这个独立 blog 。 文章评论都用 wget 统统爬下来，再转成了 wp 的导入格式导了进来。但是还有文章里引用的图片。手动保存重新上传实在是太费时间，而且程序员最讨厌机械劳动，就想办法用机器来搞。先用一个 wp 插件 WP RegEx Replace ，把图片地址替换一下，比如 http://photo([0-9]).bababian.com/ 替换为/photo$1.bababian.com/ 。然后用 nginx 的proxy_store ，代理回来存放起来。 location ~ ^/photo[0-9]\.bababian\.com { if ($request_uri ~ &#8220;^/(photo[0-9]\.bababian\.com)/(.+)&#8221; ) { set $bbb_host $1; set $bbb_uri $2; } proxy_store $document_root/$bbb_host/$bbb_uri; proxy_set_header Referer http://www.bababian.com; #过防盗链 if (!-f $request_filename) { proxy_pass http://$bbb_host/$bbb_uri; add_header X-GO-BBB 1; #验证之后的请求使用了保存后的文件 [...]]]></description>
			<content:encoded><![CDATA[<p>原先在 blogbus ，现在搬到这个独立 blog 。</p>
<p>文章评论都用 wget 统统爬下来，再转成了 wp 的导入格式导了进来。但是还有文章里引用的图片。手动保存重新上传实在是太费时间，而且程序员最讨厌机械劳动，就想办法用机器来搞。先用一个 wp 插件 WP RegEx Replace ，把图片地址替换一下，比如 http://photo([0-9]).bababian.com/ 替换为/photo$1.bababian.com/ 。然后用 nginx 的proxy_store ，代理回来存放起来。</p>
<p>location ~ ^/photo[0-9]\.bababian\.com {<br />
if ($request_uri ~ &#8220;^/(photo[0-9]\.bababian\.com)/(.+)&#8221; ) {<br />
set $bbb_host $1;<br />
set $bbb_uri $2;<br />
}<br />
proxy_store $document_root/$bbb_host/$bbb_uri;<br />
proxy_set_header Referer http://www.bababian.com; #过防盗链<br />
if (!-f $request_filename) {<br />
proxy_pass http://$bbb_host/$bbb_uri;<br />
add_header X-GO-BBB 1; #验证之后的请求使用了保存后的文件<br />
}<br />
}</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://xiezhenye.com/2011/10/blog-%e8%bf%81%e7%a7%bb%e5%88%b0%e8%bf%99%e9%87%8c.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

