<?xml version="1.0" encoding="gb2312"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>云风的 BLOG</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/" />
    <link rel="self" type="application/atom+xml" href="http://blog.codingnow.com/atom.xml" />
   <id>tag:blog.codingnow.com,2010://1</id>
    <link rel="service.post" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1" title="云风的 BLOG" />
    <updated>2010-09-02T11:18:43Z</updated>
    <subtitle>思绪来得快去得也快，偶尔会在这里停留</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.2b5</generator>
 
<entry>
    <title>backtrace-mingw 更新</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/09/update_backtrace-mingw.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=616" title="backtrace-mingw 更新" />
    <id>tag:blog.codingnow.com,2010://1.616</id>
    
    <published>2010-09-02T11:16:31Z</published>
    <updated>2010-09-02T11:18:43Z</updated>
    
    <summary>backtrace-mingw 今天更新了一下。原来的版本不能正确显示 dll 里的符号信息。现在可以了。只是打了个补丁，所以代码比较乱。 不知道 backtrace-mingw 的同学，可以看这里 。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="技术" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p><a href="http://code.google.com/p/backtrace-mingw/">backtrace-mingw</a> 今天更新了一下。原来的版本不能正确显示 dll 里的符号信息。现在可以了。只是打了个补丁，所以代码比较乱。</p>

<p>不知道 backtrace-mingw 的同学，可以<a href="http://blog.codingnow.com/2010/07/mingw_stack_backtrace.html">看这里</a> 。</p>
]]>
        

    </content>
</entry>
<entry>
    <title>从数组里删除一个元素</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/array_erase.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=615" title="从数组里删除一个元素" />
    <id>tag:blog.codingnow.com,2010://1.615</id>
    
    <published>2010-08-31T08:14:32Z</published>
    <updated>2010-08-31T08:27:56Z</updated>
    
    <summary>去年介绍过我在项目中实现的一个动态数组模块的接口。 实际上，我为它提供的接口要更多一些，比如删除一个元素。 void array_erase(struct array *, seqi iter); 原来的语义就是删除 iter 引用的元素。但这里引出一个问题：删除后，iter 是否应该保持有效？ 从语义上说，iter 应该在调用完毕后变成一个无效引用。但实际应用中，往往需要在迭代 array 的过程中，删除符合条件的元素。让迭代器失效的做法，用起来很不方便。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>去年介绍过我在项目中实现的一个<a href="http://blog.codingnow.com/2009/11/array_c.html">动态数组模块的接口</a>。</p>

<p>实际上，我为它提供的接口要更多一些，比如删除一个元素。</p>

<pre>
  void array_erase(struct array *, seqi iter);
</pre>

<p>原来的语义就是删除 iter 引用的元素。但这里引出一个问题：删除后，iter 是否应该保持有效？</p>

<p>从语义上说，iter 应该在调用完毕后变成一个无效引用。但实际应用中，往往需要在迭代 array 的过程中，删除符合条件的元素。让迭代器失效的做法，用起来很不方便。</p>
]]>
        <![CDATA[<p>因为 array 其实是以一个双向队列的形式实现。以前我取了一个巧，删除这个操作实际上是把当前位置的元素和 array 头部的元素交换，然后将 array 头 pop 出去。</p>

<p>这样，iter 还是指向原地，只是指向的值是已经遍历过的元素。这样，循环则可以继续下去。</p>

<p>今天发现一个问题，如果 iter 一开始就指向头部。那么，在 pop 操作后，iter 还是被设置成无效了。这不是一个 bug ，但是实际效果非常讨厌。思考了一下，决定修改 <code>array_erase</code> 的定义。</p>

<p><code>array_erase</code> 新的行为被定义成：删除 iter 引用的元素，并将 iter 向后移动。（如果已经到尾部，则变成空引用）</p>

<p>btw, C++ 在处理这个问题时， remove 算法不会做删除操作，而是把符合条件的元素交换到容器尾部，再调用 erase 方法真正删除。也是为了回避类似问题。不过我不太喜欢 <code>remove/remove_if</code>  那种用法。在函数式语言中，那很自然；但对函数式编程支持不足的 C++ 中，使用蹩脚的 template 方法，就不太讨人喜爱了。</p>
]]>
    </content>
</entry>
<entry>
    <title>在游戏引擎中播放视频</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/libvpx.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=614" title="在游戏引擎中播放视频" />
    <id>tag:blog.codingnow.com,2010://1.614</id>
    
    <published>2010-08-30T17:19:08Z</published>
    <updated>2010-08-31T07:07:07Z</updated>
    
    <summary>美术同学给我们的游戏做了段片头视频，正要加到产品中去时，才发现我们的引擎居然没有提供视频播放的功能。我想这个东西开源库一大堆，那做起来还不是小菜一碟。可没想到还是折腾了一整天才搞定。 第一件事是考察 License 。结果用的人做多的 ffmpeg 是 GPL 的，不适合我这种商业应用。虽然有一部分功能可以以 LGPL 的方式使用，但是遵守起来也是麻烦一大堆。想了一下还是做罢。我可不想学暴风影音和 QQ Player 那样上耻辱榜。 最终考察结果，居然没太多的选择。还是 google 的 vp8 最好。License 最为宽松，所以就去下载了一份 libvpx 的最新版试用。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="Google" />
            <category term="技术" />
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>美术同学给我们的游戏做了段片头视频，正要加到产品中去时，才发现我们的引擎居然没有提供视频播放的功能。我想这个东西开源库一大堆，那做起来还不是小菜一碟。可没想到还是折腾了一整天才搞定。</p>

<p>第一件事是考察 License 。结果用的人做多的 <a href="http://www.ffmpeg.org">ffmpeg</a> 是 GPL 的，不适合我这种商业应用。虽然有一部分功能可以以 LGPL 的方式使用，但是<a href="http://www.ffmpeg.org/legal.html">遵守起来也是麻烦一大堆</a>。想了一下还是做罢。我可不想学暴风影音和 QQ Player 那样上<a href="http://www.ffmpeg.org/shame.html">耻辱榜</a>。</p>

<p>最终考察结果，居然没太多的选择。还是 google 的 vp8 最好。License 最为宽松，所以就去下载了一份 <a href="http://www.webmproject.org/code/">libvpx</a> 的最新版试用。</p>
]]>
        <![CDATA[<p>README 有点老旧，里面说在 Windows 下编译必须 cygwin ，我很听话的在 cygwin 下 build 了一份出来。反正就是个 libvpx.a ，打算复制到 Mingw 里去用。可在 Mingw 里链接却出了问题，缺少 pthread 库。mingw32 是不带这个库的，虽然在网上找了一份 pthread-win32 ，但是看了几眼，被那一大坨 dll 吓怕了，犹豫了一下还是没继续尝试。</p>

<p>重新回头研究了一下 libvpx 的 source ，发现它并不一定要使用 pthread 的 api ，貌似在 windows  下还可以使用 win32 原生 api ，看起来是为 vs 编译准备的。我估摸着 mingw 也能搞定，接着装了份 msys 试了一下，果然 ok。</p>

<p>现在写个玩具播放器还需要一个以 vp8 格式压缩的数据文件。看了一下， libvpx 里带了 ivfenc 。输入是 yuv 格式，输出是 ivf 格式。而 libvpx 主页上没看到 api 文档，只好去读它自带的 simple decode 的代码。看了一眼发现很好理解，用起来还是很简单的。只是这个测试用的 ivf 文件怎么生成略有头痛。</p>

<p>yuv 格式似乎是一种不压缩的 YUV 色彩空间的位图序列。ffmpeg 可以生成。又下了一份 ffmpeg 编译出来，把美术交过来的视频编码成了 .yuv 文件。然后在用 ivfenc 转换成了 .ivf 。</p>

<p>本来想播放一下转换的成果看看先。google 了一下，没有什么现成的软件可以播放之。无奈之下，只好闷头先写播放程序。写出来后怎么都不对，非常郁闷。</p>

<p>开始怀疑是不是 .ivf 压缩错了。好在我曾经有过 jpeg 的编解码经验，对这块有点感觉。猜测到可能是 yuv 文件不对。想了一下，原来 .yuv 文件应该只是一个纯粹的数据流，里面没有任何格式信息。比如视频的帧率，视频的尺寸等等。所以网上很难找到所谓 .yuv 的播放器。而且 ivfenc 在压缩时也必须在命令行指定视频的大小。因为 .yuv 的输入流里根本就没这个信息。一开始还想当然的以为 ivfenc 会根据命令行指定尺寸对数据源做缩放，后来手工计算了一下视频应该有的帧数和实际压缩后帧数严重不符，才发现问题。</p>

<p>同理，.ivf 也仅仅是一图象压缩流。并非完整的视频文件。所以也没有所谓 ivf 的 codec 了。完整的视频文件格式还需要额外的描述信息以及音频流数据等。</p>

<p>明白了这些就知道该怎么做了。用 ffmpeg 输入 .yuv 数据时，可以加上 -f yuv4mpegpipe 生成指定的 yuv 信息，并且指定  <code>-pix_fmts</code> 为 yuv420p ，这个是 ivfenc 认的 yuv 格式。（yuv 有多种组合方式，在 jpeg 的压缩中也是如此）。之后，使用 ivfenc 时指定正确的宽高，就能生成有效的 ivf 文件了。</p>

<hr />

<p>播放视频的时候通常需要把像素数据从 YUV 转到 RGB 。这个程序我十年前用 MMX 优化过，今天再翻出来用用即可。C 的优化版本也还凑合。今天还 google 到<a href="http://lestourtereaux.free.fr/papers/data/yuvrgb.pdf">一篇 paper 专门谈这个优化</a>的。有兴趣的同学可以参考一下。</p>

<p>不过在 3d engine 里还有更好的方法：就是利用 GPU 。只需要为 YUV 各创建一张单通道帖图，然后在 ps 里做转换即可。之所以不用一张三通道帖图干这事，是因为默认的 ivf 展开的数据，YUV 是 4:1:1 储存的。</p>

<p>当然单纯播放视频的话，也可以用 YUV 格式的覆盖表面来做。在 DirectX 里容易实现，我还不清楚 OpenGL 里该怎么干。</p>

<hr />

<p>今天一开始有同学建议我嵌一个媒体播放器或是 Flash player 的控件在 Engine 中。我很不愿意考虑这种方案。不解释了。其实用开源库的方法，实际消耗的人力也没有预想的大。</p>
]]>
    </content>
</entry>
<entry>
    <title>记一个 Bug</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/bug.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=613" title="记一个 Bug" />
    <id>tag:blog.codingnow.com,2010://1.613</id>
    
    <published>2010-08-28T15:30:50Z</published>
    <updated>2010-08-29T03:50:43Z</updated>
    
    <summary>今天周末，桌游店里却没客人，昨天打电话预约的朋友没来，所以我就奔到办公室测试上周写的代码。 上周的工作主要是设计了一个新的包格式，然后整合入前段时间实现的虚拟文件系统中。 这个工作和前段实现的 zipfs 有相似之处，所以做起来也很快。不过前面没仔细测试。今天比较闲，就设计了几组复杂的测试数据，感觉覆盖了各种边界情况。一测试果然发现了 Bug 。 这个 Bug 有点启发意义，所以在解决掉之后，决定记录一下。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="技术" />
            <category term="杂记" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天周末，<a href="http://bg.codingnow.com">桌游店</a>里却没客人，昨天打电话预约的朋友没来，所以我就奔到办公室测试上周写的代码。</p>

<p>上周的工作主要是<a href="http://blog.codingnow.com/2010/08/resource_pack.html">设计了一个新的包格式</a>，然后整合入前段时间实现的<a href="http://blog.codingnow.com/2010/04/vfs.html">虚拟文件系统</a>中。</p>

<p>这个工作和前段实现的 zipfs 有相似之处，所以做起来也很快。不过前面没仔细测试。今天比较闲，就设计了几组复杂的测试数据，感觉覆盖了各种边界情况。一测试果然发现了 Bug 。</p>

<p>这个 Bug 有点启发意义，所以在解决掉之后，决定记录一下。</p>
]]>
        <![CDATA[<hr />

<p>和 zip 格式一样，我的包格式内部是平坦结构的，没有分目录。所有文件都是保存的完整的路径名聚合在一起。比如包里可能有如下文件：</p>

<pre>
bar.txt
bar/bar.txt
bar/foo.txt
foo/bar.txt
foo/foo.txt
foobar.txt
</pre>

<p>这个列表我经过了排序，是想在检索的时候可以快一点。有序的文件名可以二分检索。</p>

<p>我的虚拟文件系统的框架要求每个 fs 需要提供虚拟目录遍历的能力，比如在这个包内，如果需要遍历 bar 目录，就需要依次返回 bar.txt foo.txt 。如果需要遍历 / ，则返回 bar.txt bar foo foobar.txt 。</p>

<p>遍历的 api 形式上我用的一个 C 函数，叫做 <code>enum_next</code> 传入空指针的时候，返回第一项。传入非空指针的时候，返回下一项。若传入的是最后一项，则返回空。</p>

<p>这个 api 对实现上性能的要求并不算太高。因为在框架内，对遍历结果 cache 到了一张 hash 表内，下次查询的时候，是不用再次调用的。</p>

<p>我多年来养成的坏习惯，让我直觉上排斥愚笨的挨个比较的方法，（如果是这样，也不用排序了），而是采用了二分查找（这可以明显的提高检索性能一个数量级）。</p>

<p>由于有时候需要处理中间的子目录里的文件，在 <code>enum_next</code> 的时候就需要跳过。这就在跳过的时候再次使用一次二分查找。而且，这个跳过操作是需要二分查找返回一段数据，而不是一个的。</p>

<p>二分查找这件事，在 C++ 的 algorithm 模版中，是有 <code>binary_search</code> 来干这件事的。但是如果需要找到一个区间，则需要用 <code>lower_bound</code> 和 <code>upper_bound</code> ；而在 C 语言里，则有 bsearch 来做，我暂时不太清楚有没有对应的 <code>lower_bound</code> 等函数来获得区间的功能。</p>

<p>还是我的坏习惯，导致我第一反应就是自己来写这段查找代码。我对自己的编码能力否则自信，觉得完全不会出错。这样，我就不需要去编写那个比较用的回调函数了。甚至可以把整个复杂操作放在一个大循环里高效的完成。</p>

<p>可惜最终还是出了 bug ，倒不是出在二分查找的循环上，而是我想当然的认为，一个目录下的文件名，经过排序后，子目录里的文件名一定排在前面。然后在默认这个前提下，写了一大坨代码。最后出错了，回头又怀疑自己算法实现有误，前后查了一个多小时 :( 。比如在这个例子中，bar.txt 就排在了 bar/bar.txt 的前面。</p>

<hr />

<p>其实这个 bug 是完全可以在编写的时候避免的。我犯了许多经典错误：</p>

<ol>
<li><p>过早优化（这个可以商榷，虽然不是热点，这种明显的优化我任为还是可以顺手做的。不然永远都不会有人做。即使在某种特定环境下，它变成了热点）</p></li>
<li><p>没有尽可能的分解算法。（觉得这个问题足够简单，不需要分的太细。性能方面的思考作祟，想尽量减少不必要的层次）</p></li>
<li><p>没有使用现成的算法库。这一点跟 C 库在这方面略显单薄有关。不过如果用 C++ ，估计我也会比较反感那些需要自定义比较函数的算法模板。</p></li>
</ol>
]]>
    </content>
</entry>
<entry>
    <title>游戏资源的压缩、打包与补丁更新</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/resource_pack.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=612" title="游戏资源的压缩、打包与补丁更新" />
    <id>tag:blog.codingnow.com,2010://1.612</id>
    
    <published>2010-08-25T17:40:19Z</published>
    <updated>2010-08-25T17:41:32Z</updated>
    
    <summary>9 年前，我设计了网易游戏的资源包以及补丁包的数据格式。 当初的设计目的是：方便解析，快速定位资源包内的文件，方便更新、每次更新尽可能的节约带宽。这些年来，虽然各个项目修修补补的改进了资源包的格式，但本质上并没有特别大的修改。 一开始我们直接把需要打包的文件连接起来，在文件末尾附上文件索引表。当初为了快速定位文件名，文件名做了 hash 处理，可以用 hash 值直接定位文件。而资源包里并没有储存文件名信息，而是保存在一个额外的 index 文件中。这个 index 文件并不对外发布。所以直接对资源包解包是无法准确还原文件名的。 btw, 暴雪的 mpq 文件也是作类似处理的。除非你猜测出文件名，否则也很难对文件名还原。网上许多 mpq 解包工具都针对特定游戏附了一个额外的文件名列表。 和许多其它游戏 Client （比如暴雪的 MPQ 文件）不同。我们的包格式里文件与文件之间是允许有空洞的。这是考虑到资源包文件都比较大。如果用传统的打包软件运作的方式：从包内删除一个文件，就重新打包或移动内部数据。在玩家更新资源的时候，就会有大量的文件 IO 操作。比如 WOW 或 SC2 在更新的时候，下载更新包的时间往往只占整个更新时间的一小部分，大部分时间花在把补丁打在已有的资源包上。 如果频繁更新客户端，对于用户，这会有很讨厌的等待。 所以当初考虑到这个因素，我们在删除包内文件时，并不移动资源包内的数据，而是把空间留下来。如果新增加的文件较之小，就重复利用这个空间。如果利用不上，就浪费在那里。这有点像内存管理算法，时间久了，资源包内会有一些空洞，但也是可以接受的。 同时，还有另一个方式更新新的资源。那就是将需要更新的文件单独打包，以相同文件名（后缀不同）保存在用户硬盘上。游戏引擎在读取资源的时候，优先在更新的资源包内检索。这个方式在 Id soft 的 Quake/Doom 系列中也有采用。 为了保证用户补丁更新速度。我们的补丁中并不是保存的资源包内的小文件。而是在开发机上以增量方式重新打包。补丁文件其实是整个资源包的 diff 文件。由于前面所述的打包方案，这个...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>9 年前，我设计了网易游戏的资源包以及补丁包的数据格式。</p>

<p>当初的设计目的是：方便解析，快速定位资源包内的文件，方便更新、每次更新尽可能的节约带宽。这些年来，虽然各个项目修修补补的改进了资源包的格式，但本质上并没有特别大的修改。</p>

<p>一开始我们直接把需要打包的文件连接起来，在文件末尾附上文件索引表。当初为了快速定位文件名，文件名做了 hash 处理，可以用 hash 值直接定位文件。而资源包里并没有储存文件名信息，而是保存在一个额外的 index 文件中。这个 index 文件并不对外发布。所以直接对资源包解包是无法准确还原文件名的。</p>

<p>btw, 暴雪的 mpq 文件也是作类似处理的。除非你猜测出文件名，否则也很难对文件名还原。网上许多 mpq 解包工具都针对特定游戏附了一个额外的文件名列表。</p>

<p>和许多其它游戏 Client （比如暴雪的 MPQ 文件）不同。我们的包格式里文件与文件之间是允许有空洞的。这是考虑到资源包文件都比较大。如果用传统的打包软件运作的方式：从包内删除一个文件，就重新打包或移动内部数据。在玩家更新资源的时候，就会有大量的文件 IO 操作。比如 WOW 或 SC2 在更新的时候，下载更新包的时间往往只占整个更新时间的一小部分，大部分时间花在把补丁打在已有的资源包上。</p>

<p>如果频繁更新客户端，对于用户，这会有很讨厌的等待。</p>

<p>所以当初考虑到这个因素，我们在删除包内文件时，并不移动资源包内的数据，而是把空间留下来。如果新增加的文件较之小，就重复利用这个空间。如果利用不上，就浪费在那里。这有点像内存管理算法，时间久了，资源包内会有一些空洞，但也是可以接受的。</p>

<p>同时，还有另一个方式更新新的资源。那就是将需要更新的文件单独打包，以相同文件名（后缀不同）保存在用户硬盘上。游戏引擎在读取资源的时候，优先在更新的资源包内检索。这个方式在 Id soft 的 Quake/Doom 系列中也有采用。</p>

<p>为了保证用户补丁更新速度。我们的补丁中并不是保存的资源包内的小文件。而是在开发机上以增量方式重新打包。补丁文件其实是整个资源包的 diff 文件。由于前面所述的打包方案，这个 2 进制 diff 文件其实可以做到很小。尤其对某些文件的局部修改，对整个资源包的影响很小。</p>

<p>在公司，有后来的同事质疑过这种方式，觉得其对减少补丁体积的作用不大。反而增量打包增加了许多制作补丁包的时间。主张直接在补丁中放入更新的小文件，然后让最最终用户机上以小文件为单位做 patching 。</p>

<p>的确，2 进制 diff 的作用有限，现在很多项目改用文本数据格式，很小的修改就会影响整个文件的 diff 结果。不过原始的设计也有其历史原因。因为 10 年前硬盘 I/O 速度很慢，而大话西游在设计时又需要实现无缝加载的大地图。所以地图文件的格式是经过特别设计的。这种方式很适合地图文件的修改和更新。另外，对于未压缩的图片文件的更新也有其意义。</p>
]]>
        <![CDATA[<hr />

<p>总结完旧有设计后。我希望在新引擎中对资源包格式做一定的改进。</p>

<p>首先是加入内置的压缩。原有格式是只负责打包而不管压缩的。若数据需要压缩，由上层模块去负责。这么做跟大话西游中的地图文件需要随机访问有关。另外和增量打包也有点关系。如果对每个小文件统一做压缩，2 进制 diff 就几乎无效了。</p>

<p>这次希望以文件系统的方式来管理资源包，而不是简单的将文件连接起来。把大文件分块，以类似 FAT 表的形式来管理大文件。每个块则可以单独选择压缩或不压缩。这样即能压缩数据，又可以提供文件随机访问的能力。（能够方便的实现数据包的嵌套）</p>

<p>在资源包内支持链接功能。</p>

<p>开发期，我们可以让所有资源都不用理会复杂的引用关系，而是各自有独立的一份。比如模型文件引用的贴图都可以依附在模型文件的目录下。这样在开发期很方便管理这些数据。而打包时，可以比较所有需打包文件，把内容相同的文件剔除，只是做一个链接。同时引擎也能识别出引用关系，同样的资源只加载一次。</p>

<p>关于增量打包的问题，开发人员发布补丁的效率的确需要考虑。其实在开发期，有个简便快速的制作流程，也方便搭建每日构建。虽然按原有方式也有许多方法加快制作补丁的速度（比如预先计算所有文件的 md5 值保存起来，加块增量打包软件的分析速度）。我希望可以制作时直接生成补丁文件。这个补丁则可以是每个小文件的 diff 信息。另外再制作一个 patch 工具，将补丁打上去。</p>

<p>这个 patch 工具的工作流程可以是从旧的包中抽取出需要 patch 的小文件，和补丁中的 diff 信息合并，得到一个需要更新的包。然后将旧包中那些小文件删除，并向前压缩掉空洞。最后将新旧两个包连接起来。</p>

<p>压缩空洞的这个过程会占用用户机的一些文件 IO 时间。打补丁的速度会慢一些。不过我觉得影响不会太大。因为经常更新的文件会趋向于放在资源包的末尾（每次更新都抽取出来，并连接到末尾），所以压缩空洞时需要搬移的数据有很大机会并不多。</p>

<p>这样设计补丁包的格式，也会更干净一点。</p>
]]>
    </content>
</entry>
<entry>
    <title>继续完善 protobuf 库</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/protobuf_for_lua.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=610" title="继续完善 protobuf 库" />
    <id>tag:blog.codingnow.com,2010://1.610</id>
    
    <published>2010-08-17T16:09:51Z</published>
    <updated>2010-08-17T16:26:50Z</updated>
    
    <summary>又仔细推敲了两天，把 lua 版的 protobuf 库完善了一下。主要是做了两个工作： protobuf 本身的格式，google 是自描述的。定义为 google.protobuf.descriptor 。我先自己实现的 parser 图方便，用了自己的中间交换格式。为了日后更通用，稍微修改了一下，可以生成于官方相同的结构。解析的性能稍有下降，不过应该兼容性更好。 一开始实现的 api 虽然性能非常好。（经简单测试，是 python 库的 30~40 倍，和 C++ 库性能相当）但若消息格式复杂，实现起来稍有麻烦。所以我做了点小封装。即为每个消息生成一对函数，可以用来打包和解包完整的消息，映射到 lua 的 table 结构上。lua 生成代码供自己调用的技巧在 lua 社区广泛使用。比如 kepler 项目。这使得 lua 可以用很短的代码行数完成很复杂的工作，不失性能。我这个封装层只有 100 行代码左右，一大半代码是为了解决消息展开时有递归定义的情况，否则更简短。（message 中有一些 field 的类型是自己，这是一种不多见的用法，但 protobuf 似乎并没有拒绝这种用法）...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>又仔细推敲了两天，把 lua 版的 protobuf 库完善了一下。主要是做了两个工作：</p>

<ol>
<li><p>protobuf 本身的格式，google 是自描述的。定义为 google.protobuf.descriptor 。我先自己实现的 parser 图方便，用了自己的中间交换格式。为了日后更通用，稍微修改了一下，可以生成于官方相同的结构。解析的性能稍有下降，不过应该兼容性更好。</p></li>
<li><p>一开始实现的 api 虽然性能非常好。（经简单测试，是 python 库的 30~40 倍，和 C++ 库性能相当）但若消息格式复杂，实现起来稍有麻烦。所以我做了点小封装。即为每个消息生成一对函数，可以用来打包和解包完整的消息，映射到 lua 的 table 结构上。lua 生成代码供自己调用的技巧在 lua 社区广泛使用。比如 kepler 项目。这使得 lua 可以用很短的代码行数完成很复杂的工作，不失性能。我这个封装层只有 100 行代码左右，一大半代码是为了解决消息展开时有递归定义的情况，否则更简短。（message 中有一些 field 的类型是自己，这是一种不多见的用法，但 protobuf 似乎并没有拒绝这种用法）</p></li>
</ol>
]]>
        <![CDATA[<p>在性能非常重要的场合，使用原始的 api 对简单的数据包处理，加上 lua-jit ，应该可以达到和 C 相近的性能。而另一些情况下，需要定义比较复杂的数据结构，并序列化。这个时候用封装过的 api 就好了。</p>

<hr />

<p>这两天还有些想法。想做一个类似 <a href="http://keplerproject.github.com/rings/">rings</a> 的多 state 库，和 rings 不同，我希望 states 间交换数据更为高效。</p>

<p>考察了一下 lua 的源代码，我觉得稍做改动就可以让多个 states 间共享一个 string table 。这样，states 间传递 string 就能从 O(n) 变为 O(1) 。</p>

<p>另外，借助 debug hook 和 coroutine 机制，可以让 lua 代码运行一小段片段后自动交回控制权。不过在不修改 lua 代码之前暂时还不能这么干。这是因为 yield 有一些限制。比较期待 lua 5.2 的发布，以后 yield 的限制会少很多。</p>

<p>使用多 states 对长期运行的服务器程序非常有帮助。可以回避许多不谨慎造成的内存泄露问题。</p>
]]>
    </content>
</entry>
<entry>
    <title>Proto Buffers in Lua</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/proto_buffers_in_lua.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=608" title="Proto Buffers in Lua" />
    <id>tag:blog.codingnow.com,2010://1.608</id>
    
    <published>2010-08-10T16:06:18Z</published>
    <updated>2010-08-10T16:07:13Z</updated>
    
    <summary>Google 的 Jeff Dean 同学说，设计分布式系统一定要有 Protocol Description Language。 Google Proto Buffers 的意义在于，定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。 这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考，怎样的接口才是最适合 lua 使用的。 大多数语言下的 proto buffers 实现，都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ，这是最高效的形式。但对于动态语言，那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此，但我依然想考虑一下，是否有更高效的方式来做这件事。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p><a href="http://www.tektalk.org/2010/08/04/%E6%9D%A5%E8%87%AAjeff-dean%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">Google 的 Jeff Dean 同学说，设计分布式系统一定要有 Protocol Description Language</a>。</p>

<p><a href="http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html">Google Proto Buffers</a> 的意义在于，定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。</p>

<p>这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考，怎样的接口才是最适合 lua 使用的。</p>

<p>大多数语言下的 proto buffers 实现，都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ，这是最高效的形式。但对于动态语言，那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此，但我依然想考虑一下，是否有更高效的方式来做这件事。</p>
]]>
        <![CDATA[<p>lua ，最高效的数据处理交换是利用语言的栈。虽然 table 也足够快，但复杂且深层次的 table 往往容易成为最终的性能瓶颈。btw, google 的 js V8 引擎，在部分场合表现的比 lua jit 更为出色，很大程度上是因为它极大的加快了 table 的查询。这几乎会成为所有解释型动态语言的性能热点。而且不断了生成临时 table ，还会给 gc 造成较大的负担。那么，能不能找到一种方式可以回避在 lua 中构造复杂数据结构，而又能方便的使用由 proto buffers 定义出来的结构化数据呢？</p>

<p>我花了三四天初步实现了一个 lua proto buffers 的库，全部是以 C 代码完成的。最终提供给 lua 的接口只有四个。</p>

<ol>
<li><p>load 能加载一个 proto buffers 的元数据，对协议本身做一个描述。并生成一个 C 对象 (userdata) 返回到 lua state 中。这个 C 对象包含了一组 proto 描述信息，除了其中的字符串信息外，全部储存在一块连续的内存中。作为 lua 的 userdata ，不需要任何 gc 方法。其中引用的字符串全部记录在这个 userdata 的环境表中，在 C 对象里只记录字符串表里的序号。这个方式可以很大的提高 lua 运行时的字符串传递性能。</p></li>
<li><p>pattern 可以为某个 message 生成一个模式对象 (还是 C 对象)，这个模式对象可以用来解码或编码数据。pattern 的概念是整个库的核心概念。库会依据 pattern 来编码或解码数据。pattern 本身的生成比较重量，但在实际应用中，往往是一次性的，而不需要对每个数据包处理时都重新生成 pattern 。做一些小手脚，就可以在 lua 层面做进一步的简单封装。用字符串的形式来 cache 并索引 pattern 。</p></li>
<li><p>unpack 可以对一个数据块进行解包工作。unpack 必须提供数据块以及相应的 pattern 对象。它会按 pattern 的指示把结构化数据解到栈上。这种方式回避了每次解包都生成临时的 table 。当然，如果使用者还是喜欢构造一个 table 来表达数据块中的数据结构的话，依然可以通过几行 lua 封装代码来办到。</p></li>
<li><p>pack 可以根据一个 pattern 对数据块打包。同样从栈接收数据源。不需要构造临时的 table 用于打包。</p></li>
</ol>

<hr />

<p>我在做这个库之前，做了一些工作来解析 Google Proto Buffers 定义的协议描述文件。如上所说，我认为，协议定义及描述是整个 Proto Buffers 的核心，而其实现以及二进制编码规则则是次要的东西。</p>

<p>google 提供了开源工具，把文本协议描述文件编译成 proto buffers 自身形式的结构化数据。这做了很漂亮，也方便了对协议本身的解析。不过若是抛开二进制编码，若想让整套协议自举，而整个过程和具体编码形式无关的话，更有趣的做法是自己来实现文本协议描述的解析。</p>

<p>好在用 lua 来做这项工作并不太难，proto buffers 定义的语言也相当简单，容易解析。我大约写了 100 来行 lua 代码来完成基本的语义分析。当然，用了强大的 <a href="http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html">lpeg 库</a>。话说，<a href="http://en.wikipedia.org/wiki/Parsing_expression_grammar">PEG</a> 是个相当强大的东西，强烈推荐没学过的同学们好好学习一下。用顺手的话，为自己的项目定义 DSL 那是易如反掌。</p>

<p>整个解析的实际运行过程非常繁重，但最终只是生成一个很小的 C 数据对象。我用了一个有趣的方案来干这件事：单独开一个 lua State 来完成协议的解析。这个独立的 lua State 总共所消耗的内存可以事先预估，不会太大（跟 proto 文件的规模以及复杂度有关）。所以我为这个独立的 State 定制了一个只分配不释放的内存管理器。这个内存管理器工作在独立的系统内存页上，方便最后一次回收。这样，每次解析一个协议文件，只需要消耗一些临时内存，而在解析完后，能够绝对干净的清除痕迹。我统计了一下一个最终大约 70K 的 C 数据对象，处理过程消耗了大约 1.5M 内存。</p>

<p>灵活使用多个 lua State 应该是一个良好的习惯。尤其在较大规模的需要长期运行程序中，把各个模块分配到独立的 lua State 空间中运行，可以使任务完成后，内存得到准确的回收。</p>

<hr />

<p>这几天做的这个东西，还很初步，所以就不细写了。等成熟后再考虑开源。</p>

<p>ps. 在 lua 社区，设计些小的模式语言来完成数据处理，似乎是一个很流行的做法。lua 自带的字符串匹配如此；<a href="http://cosmo.luaforge.net/">cosmo</a> 也是个有趣的小玩意；lpeg 也提供一个 <a href="http://www.inf.puc-rio.br/~roberto/lpeg/re.html">The re Module</a> 来模拟正则表达式的语法。等等这些启发我设计出这个 proto buffers 的 pattern 。</p>
]]>
    </content>
</entry>
<entry>
    <title>Windows 下调试问题一则</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/08/debug_in_windows.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=607" title="Windows 下调试问题一则" />
    <id>tag:blog.codingnow.com,2010://1.607</id>
    
    <published>2010-08-06T04:25:17Z</published>
    <updated>2010-08-06T04:32:58Z</updated>
    
    <summary>今天跑昨天晚上写的一个小程序，莫名其妙的不能运行。开 gdb 调试没有任意异常，直接退出，连 main 函数都没有进。注释掉大量代码后依旧。把 gdb 从 6.3 升级到 6.8 后，再调试，捕获了一个不知名的异常。 又把代码注释干净，程序正常了。仔细考虑了一下，最后注释掉的代码间接 link 了 lua 的库。而 lua 的库引用了 lua 的动态库。程序运行时可能是因为没有找到 dll 而退出。 把 lua 的 dll 加到 path 中就正常了。但是奇怪的是，系统并没有弹出对话框提示。仔细想了一下，原来是我没有从 explore 启动控制台，而是在编辑器里启动的，可能是编辑器软件没有写好，把本应该系统弹出的缺少 dll 的对话框拦截了。 Soloist 同学说，他也碰到过一次 Total Commander 吃掉缺少 dll 无法启动程序的对话框。记录一下这个问题，祭奠我找问题逝去的半小时。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="技术" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天跑昨天晚上写的一个小程序，莫名其妙的不能运行。开 gdb 调试没有任意异常，直接退出，连 main 函数都没有进。注释掉大量代码后依旧。把 gdb 从 6.3 升级到 6.8 后，再调试，捕获了一个不知名的异常。</p>

<p>又把代码注释干净，程序正常了。仔细考虑了一下，最后注释掉的代码间接 link 了 lua 的库。而 lua 的库引用了 lua 的动态库。程序运行时可能是因为没有找到 dll 而退出。</p>

<p>把 lua 的 dll 加到 path 中就正常了。但是奇怪的是，系统并没有弹出对话框提示。仔细想了一下，原来是我没有从 explore 启动控制台，而是在编辑器里启动的，可能是编辑器软件没有写好，把本应该系统弹出的缺少 dll 的对话框拦截了。</p>

<p>Soloist 同学说，他也碰到过一次 Total Commander 吃掉缺少 dll 无法启动程序的对话框。记录一下这个问题，祭奠我找问题逝去的半小时。</p>
]]>
        

    </content>
</entry>
<entry>
    <title>mingw 下的 stack backtrace</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/07/mingw_stack_backtrace.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=606" title="mingw 下的 stack backtrace" />
    <id>tag:blog.codingnow.com,2010://1.606</id>
    
    <published>2010-07-28T10:01:25Z</published>
    <updated>2010-07-28T10:14:14Z</updated>
    
    <summary>我们的项目的 Windows 版本是用 MinGW 开发的。当程序在 Windows 下挂掉后，固然可以用 gdb 调试，看到调用栈。但有些时候还是不够方便。 比如说今天，我们写的模型编辑器发到广州美术同事使用时，就出了问题。3d 程序在不同显卡环境下的确容易出故障，异地调试程序非常困难。这个时候，多么想看看调用栈啊。 The GNU C Library 是提供了 Backtraces 的，可惜 MinGW 不支持 :( 。最后打算自己写一个。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="技术" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>我们的项目的 Windows 版本是用 MinGW 开发的。当程序在 Windows 下挂掉后，固然可以用 gdb 调试，看到调用栈。但有些时候还是不够方便。</p>

<p>比如说今天，我们写的模型编辑器发到广州美术同事使用时，就出了问题。3d 程序在不同显卡环境下的确容易出故障，异地调试程序非常困难。这个时候，多么想看看调用栈啊。</p>

<p>The GNU C Library 是提供了 <a href="http://www.delorie.com/gnu/docs/glibc/libc_665.html">Backtraces</a> 的，可惜 MinGW 不支持 :( 。最后打算自己写一个。</p>
]]>
        <![CDATA[<p>获取 stack backtrace 调试信息的基本原理就是利用 windows 的 imagehlp.dll 取得正在运行的 exe 或 dll 被系统加载到内存后的镜像信息（可能被重定位了）。然后利用 libbfd 按偏移量取得源码的相关信息。</p>

<p>在崩溃时获取调用栈信息使用的是 SetUnhandledExceptionFilter ，可以注册一个函数拿到 context 继续处理。</p>

<p>搜索一番后，发现<a href="http://www.mr-edd.co.uk/blog/more_stack_trace_for_mac_and_win">其实有个哥们实现了个 C++ 版本</a> 。不过我还是用我自己写的 C 版本吧。</p>

<p>使用非常简单，只需要把事先编译好的 backtrace.dll 在需要追踪的程序前面加载一下即可，然后在 crash 时就会向 strerr 输出详细的堆栈信息。</p>

<p>有兴趣的同学可以看这里：<a href="http://code.google.com/p/backtrace-mingw">我把它在 code.google.com 上开源了</a>。</p>
]]>
    </content>
</entry>
<entry>
    <title>换了个新手机</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/07/cellphone.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=604" title="换了个新手机" />
    <id>tag:blog.codingnow.com,2010://1.604</id>
    
    <published>2010-07-27T13:41:05Z</published>
    <updated>2010-07-27T13:41:47Z</updated>
    
    <summary>我的那个 Palm Treo 650 终于寿终正寝了。去年曾经买了个 HTC Hero 送老爸，玩过几天 Andriod ，还是有点好感，这次打算选一款 Andriod 系统的。 曾经也考虑过 iPhone 或是 Palm Pre ，请教了我公司的手机大神，大神同学说，他买过两款 iPhone ，用一段时间总想换，不推荐长期使用。至于 Palm Pre ，大神同学也买过一个，评价是极其垃圾，完全比不上 Treo 系列。他最近半年买过 Milestone ，Desire ，Nexus One ，X10i ，i9000 等等几款，每个都有不同的缺憾，最终比较下来，给我推荐了三星 i9000 。 我比较信任亲身使用者的感受，尤其是对手机如此挑剔，以至于每一两个月就情不自禁的买新手机的同学的现身说法。没太多犹豫就去 taobao 下了单。 结果…… 等了 8...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="我爱折腾" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>我的那个 Palm Treo 650 终于寿终正寝了。去年曾经买了个 HTC Hero 送老爸，玩过几天 Andriod ，还是有点好感，这次打算选一款 Andriod 系统的。</p>

<p>曾经也考虑过 iPhone 或是 Palm Pre ，请教了我公司的手机大神，大神同学说，他买过两款 iPhone ，用一段时间总想换，不推荐长期使用。至于 Palm Pre ，大神同学也买过一个，评价是极其垃圾，完全比不上 Treo 系列。他最近半年买过 Milestone ，Desire ，Nexus One ，X10i ，i9000 等等几款，每个都有不同的缺憾，最终比较下来，给我推荐了三星 i9000 。</p>

<p>我比较信任亲身使用者的感受，尤其是对手机如此挑剔，以至于每一两个月就情不自禁的买新手机的同学的现身说法。没太多犹豫就去 taobao 下了单。</p>

<p>结果…… 等了 8 天还是没寄来，卖家一直说缺货。昨天忍不住退了款，还是很干脆的。再一看，原来是涨价了，从 3450 涨到了 3600 。换了一家重新下单，今天一早就寄到了。</p>
]]>
        <![CDATA[<hr />

<p>最为头痛的问题是导联系人。我一下找不到 palm 的联机线，只好去找了个 palm 版的 syncML 程序。把联系人同步到 google 。结果悲剧了，那个软件是个老外写的，完全没考虑编码问题。而 palm treo 一直用的国人开发的外挂的中文系统，内码是 GBK 而不是 Unicode 。然后一片乱码。</p>

<p>从 gmail 下载了我的联系人名单，打开仔细分析了一下。google 用的 Unicode 做内码，下载下来为 csv 文件。由于微软的 Office 标准是用 UTF-16 来编码 Unicode 文件，所以这份文件是 UTF-16 格式的。Palm 上的软件在同步的时候，完全没做转换，直接把中文按 GBK 上传。google 的 syncML 服务器在接收的时候，直接把每个汉字拆分为两个独立的字节，转换成 UTF-16 编码后，插入了字节 0 每个汉字变成了 4 字节。</p>

<p>然后，我的联系人名单就变成了两种编码的混合文件。</p>

<p>想了一下，没想到什么工具可以辅助我解决这个问题。只好打开编辑器，花了半个小时写了一个 C 程序来做转换。好在我对问题了解的比较清楚，一次性就把乱码问题搞定了。</p>

<p>接下来的悲剧是 Andriod 2.1 的系统的联系人名单貌似不能按中文拼音分类排序。导致我所有的中文联系人都被排到了 Z 类之后。喜剧的是，原来 Palm 系统也有类似问题，所以我曾经写过一个程序，自动给所有联系人名字前面加上拼音首字母。</p>

<p>两个系统的区别在于，Palm Treo 默认的姓在前，名在后。到了 Andriod 中，默认变成了名在前。我原来加的英文被跟在汉字后面，没能起到作用。修改了设置后，次序就正常了。</p>

<p>btw, 最近碰到过好几起汉字编码问题了。其中还有一次就出在我们内部用的 ftp 系统上。在 Linux 和 Windows 系统间通过 ftp 交换文件时，汉字文件名就出过许多问题。Samba 在这个问题上表现就好的多 :D</p>

<hr />

<p>第二件大事是 vpn 。我换手机的一大动力就是老的机器不支持 vpn 。结果手机读 google reader 老是被郁闷。换了新手机赶紧的试一下 vpn 。我自己架了个 pptpd 服务在 <a href="http://blog.codingnow.com/2010/02/move.html">linode 处买的主机上</a>。 以前用的挺好的。今天死活连不上。跑去看了下 log ，感觉跟防火墙有点关系，不支持 GRE 。</p>

<p>还好有别的选择。三下两下架了个 xl2tpd 服务。试了下 vpn 连接，感觉很好。</p>

<p>btw, 其实，卖 vpn 也能致富吧？嗯，一条只能做不能说的生财之道。</p>
]]>
    </content>
</entry>
<entry>
    <title>游戏多服务器架构的一点想法</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/07/game_network.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=603" title="游戏多服务器架构的一点想法" />
    <id>tag:blog.codingnow.com,2010://1.603</id>
    
    <published>2010-07-19T15:07:54Z</published>
    <updated>2010-07-19T15:10:08Z</updated>
    
    <summary>把网络游戏服务器分拆成多个进程，分开部署。这种设计的好处是模块自然分离，可以单独设计。分担负荷，可以提高整个系统的承载能力。 缺点在于，网络环境并不那么可靠。跨进程通讯有一定的不可预知性。服务器间通讯往往难以架设调试环境，并很容易把事情搅成一团糨糊。而且正确高效的管理多连接，对程序员来说也是一项挑战。 前些年，我也曾写过好几篇与之相关的设计。这几天在思考一个问题：如果我们要做一个底层通用模块，让后续开发更为方便。到底要解决怎样的需求。这个需求应该是单一且基础的，每个应用都需要的。 正如 TCP 协议解决了互联网上稳定可靠的点对点数据流通讯一样。游戏世界实际需要的是一个稳定可靠的在游戏系统内的点对点通讯需要。 我们可以在一条 TCP 连接之上做到这一点。一旦实现，可以给游戏服务的开发带来极大的方便。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="网络与安全" />
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>把网络游戏服务器分拆成多个进程，分开部署。这种设计的好处是模块自然分离，可以单独设计。分担负荷，可以提高整个系统的承载能力。</p>

<p>缺点在于，网络环境并不那么可靠。跨进程通讯有一定的不可预知性。服务器间通讯往往难以架设调试环境，并很容易把事情搅成一团糨糊。而且正确高效的管理多连接，对程序员来说也是一项挑战。</p>

<p>前些年，我也曾写过好几篇与之相关的设计。这几天在思考一个问题：如果我们要做一个底层通用模块，让后续开发更为方便。到底要解决怎样的需求。这个需求应该是单一且基础的，每个应用都需要的。</p>

<p>正如 TCP 协议解决了互联网上稳定可靠的点对点数据流通讯一样。游戏世界实际需要的是一个稳定可靠的在游戏系统内的点对点通讯需要。</p>

<p>我们可以在一条 TCP 连接之上做到这一点。一旦实现，可以给游戏服务的开发带来极大的方便。</p>
]]>
        <![CDATA[<p>可以把游戏系统内的各项服务，包括并不限于登陆，拍卖，战斗场景，数据服务，等等独立服务看成网络上的若干终端。每个玩家也可以是一个独立终端。它们一起构成一个网络。在这个网络之上，终端之间可以进行可靠的连接和通讯。</p>

<p>实现可以是这样的：每个虚拟终端都在游戏虚拟网络(Game Network)上有一个唯一地址 (Game Network Address , GNA) 。这个地址可以预先设定，也可以动态分配。每个终端都可以通过游戏网络的若干接入点 ( GNAP ) 通过唯一一条 TCP 连接接入网络。接入过程需要通过鉴权。</p>

<p>鉴权过程依赖内部的安全机制，可以包括密码证书，或是特别的接入点区分。（例如，玩家接入网络就需要特定的接入点，这个接入点接入的终端都一定是玩家）</p>

<p>鉴权通过后，网络为终端分配一个固定的游戏域名。例如，玩家进入会分配到 player.12345 这样的域名，数据库接入可能分配到 database 。</p>

<p>游戏网络默认提供一个域名查询服务（这个服务可以通过鉴权的过程注册到网络中），让每个终端都能通过域名查询到对应的地址。</p>

<p>然后，游戏网络里所有合法接入的终端都可以通过其地址相互发起连接并通讯了。整个协议建立在 TCP 协议之上，工作于唯一的这个 TCP 连接上。和直接使用 TCP 连接不同。游戏网络中每个终端之间相互发起连接都是可靠的。不仅玩家可以向某个服务发起连接，反过来也是可以的。玩家之间的直接连接也是可行的（是否允许这样，取决于具体设计）。</p>

<p>由于每个虚拟连接都是建立在单一的 TCP 连接之上。所以减少了互连网上发起 TCP 连接的各种不可靠性。鉴权过程也是一次性唯一的。并且我们提供域名反查服务，我们的游戏服务可以清楚且安全的知道连接过来的是谁。</p>

<p>系统可以设计为，游戏网络上每个终端离网，域名服务将广播这条消息，通知所有人。这种广播服务在互联网上难以做到，但无论是广播还是组播，在这个虚拟游戏网络中都是可行的。</p>

<p>在这种设计上。在逻辑层面，我们可以让玩家直接把聊天信息从玩家客互端发送到聊天服务器，而不需要建立多余的 TCP 连接，也不需要对转发处理聊天消息做多余的处理。聊天服务器可以独立的存在于游戏网络。也可以让广播服务主动向玩家推送消息，由服务器向玩家发起连接，而不是所有连接请求都是由玩家客互端发起。</p>

<p>虚拟游戏网络的构成是一个独立的层次，完全可以撇开具体游戏逻辑来实现，并能够单独去按承载量考虑具体设计方案。非常利于剥离出具体游戏项目来开发并优化。</p>

<p>最终，我们或许需要的一套 C 库，用于游戏网络内的通讯。api 可以和 socket api 类似。额外多两条接入与离开游戏网络即可。</p>
]]>
    </content>
</entry>
<entry>
    <title>C 语言中统一的函数指针</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/07/function_c.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=602" title="C 语言中统一的函数指针" />
    <id>tag:blog.codingnow.com,2010://1.602</id>
    
    <published>2010-07-12T12:45:36Z</published>
    <updated>2010-07-13T12:49:32Z</updated>
    
    <summary>有时候，我们需要把多个模块粘合在一起。而这些模块的接口参数上有少许的不同。在 C 语言中，参数（或是返回值）不同的函数指针属于不同的类型，如果混用，编译器会警告你类型错误。 在 C 语言中，函数定义是可以不写参数的。比如： void foo(); 这个函数定义表示了一个返回 void 的函数，参数未定。也就是说，它是个弱类型，诸如： void foo(int); void foo(void *); 这些类型都可以无害的转换成它。正如在 C 语言中，具体的指针类型如 int * ，char * 都可以转换为 void * 一样。 注1：如果要严格定义一个无参数的函数，应该写成 void foo(void); 注2：如果有部分参数固定，而其后的参数可变，则定义看起来是这样： void foo(int , ...); 这表示第一个参数为 int ，从第 2 个参数开始可变。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>有时候，我们需要把多个模块粘合在一起。而这些模块的接口参数上有少许的不同。在 C 语言中，参数（或是返回值）不同的函数指针属于不同的类型，如果混用，编译器会警告你类型错误。</p>

<p>在 C 语言中，函数定义是可以不写参数的。比如：</p>

<p>void foo();</p>

<p>这个函数定义表示了一个返回 void 的函数，参数未定。也就是说，它是个弱类型，诸如：</p>

<p>void foo(int);</p>

<p>void foo(void *);</p>

<p>这些类型都可以无害的转换成它。正如在 C 语言中，具体的指针类型如 int * ，char * 都可以转换为 void * 一样。</p>

<p>注1：如果要严格定义一个无参数的函数，应该写成 void foo(void);</p>

<p>注2：如果有部分参数固定，而其后的参数可变，则定义看起来是这样： void foo(int , ...); 这表示第一个参数为 int ，从第 2 个参数开始可变。</p>
]]>
        <![CDATA[<p>不过，C 语言的这种语法，实际上不太使用。因为用 C 语言无法主动控制函数调用的参数压栈。我们很难根据程序的上下文来决定如何传入参数去调用某个函数。如果需要逐级传递多个函数的参数，用的更多的是 <code>va_list</code></p>

<p>比如，你很难对 printf 做封装，通常为了方便做封装，还提供了形为 vprintf 的接口。</p>

<p>C++ 解决此类问题的方案是用类去模拟一个函数，通过重载 () 操作符的方法，让函数调用看起来和普通函数一致（并美其名曰 functor/仿函数）。当然，也有撕破语法糖的伪装，用更直白的类继承的方式来定义出接口。</p>

<p>这里想说的是，C 语言里也还有一种有趣的方案来在保证类型安全的基础上解决类似问题。</p>

<p>在 X-Window 的消息定义中就可以看到这样的手法。</p>

<p>在 Windows 的接口中，Windows 的消息携带的数据通常用两个参数来表示：WPARAM 和 LPARAM ，均为 32bit 整数。我们知道，消息本质上等同于对象的方法。在更早的面向对象语言如 smalltalk 中，调用对象的方法即被看成向对象发送一个消息。Windows 如此把所有消息处理相关函数的接口都以 WPARAM 与 LPARAM 的形式传递参数，正是为了方便统一其接口形式。各种五花八门的参数都蕴涵于这 64 bit 数据中。</p>

<p>Xlib 处理类似的问题，对 C 程序员的亲合力则大的多。至少更为类型安全。</p>

<p>Xlib 定义了一个叫做 XEvent 的结构体（实际是一个 union）。然后把各种可能的消息类型放在这个 union 中。例如，我想取键盘消息，则可以用 event.xkey.keycode 。</p>

<p>一般说来，我们可以把模块的对外接口看成是接收一组输入参数并加以处理。如果需要粘合多个不同的模块，他们需要处理不同的输入参数的话，可以借鉴 XLib 的这个方法。在粘合层定义一个 union ，把所有可能的参数组，每组定义成一个 struct 然后定义在同一个 union 中。这个粘合层的统一接口则为这个 union 指针。有必要的话，所有的参数组 struct 的头部都留下 type 字段。这样比较容易分发消息。</p>

<p>这样做的本质是：把函数调用时由编译生成的、将调用参数逐个压栈的代码，改由程序员主动填写（填写参数结构体）。利用结构的类型安全，保证了函数调用时的参数类型安全。再利用 union 的语法，把不同的参数组联合到一起变成同一类型。</p>

<p>给 api 传递一个 struct 或 union 指针而不是逐个参数传递，是 C 接口设计的一种常见手法。除了 XLib 的设计，还能找到很多耳熟能详的例子。例如，我们在 socket api 上也可以看到类似的东西。例如 connect 的参数中有一 sockaddr 结构，就适用于各种不同的网络底层协议。</p>
]]>
    </content>
</entry>
<entry>
    <title>区分一个包含汉字的字符串是 UTF-8 还是 GBK</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/06/detect_utf-8_gbk.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=601" title="区分一个包含汉字的字符串是 UTF-8 还是 GBK" />
    <id>tag:blog.codingnow.com,2010://1.601</id>
    
    <published>2010-06-29T07:11:00Z</published>
    <updated>2010-06-29T07:35:25Z</updated>
    
    <summary>今天检查 svn 仓库，发现又有同学没按规定提交包含汉字的代码。我们规律，所有源文件中包含的汉字必须使用 UTF-8 编码方式，而不能使用 GBK 。 总这么人工检查也不是个事。所以我想写一个 svn 的钩子，在提交前检查。在仓库的 hooks/pre-commit.teml 加一行检查脚本应该就可以了。 我想用正则表达式匹配一下，可是想了想又觉得 UTF-8 和 GBK 的编码集有点交集，不太好做。btw, google 了一下，的确有人写过特定编码的正则表达式。 继续 google ，找到一篇跟我需求有点类似的文章UTF-8编码检测失败特例。看了正文，觉得不太靠谱，然后继续看回复，觉得这方法可行。 然后定睛一看，原来文章是孟岩写的，回复是我自己三年多前回复在他的 blog 上的。 -_-...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="技术" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天检查 svn 仓库，发现又有同学没按规定提交包含汉字的代码。我们规律，所有源文件中包含的汉字必须使用 UTF-8 编码方式，而不能使用 GBK 。</p>

<p>总这么人工检查也不是个事。所以我想写一个 svn 的钩子，在提交前检查。在仓库的 hooks/pre-commit.teml 加一行检查脚本应该就可以了。</p>

<p>我想用正则表达式匹配一下，可是想了想又觉得 UTF-8 和 GBK 的编码集有点交集，不太好做。btw, google 了一下，的确有人写过<a href="http://www.dnbcw.com/biancheng/php/hzko119744.html">特定编码的正则表达式</a>。</p>

<p>继续 google ，找到一篇跟我需求有点类似的文章<a href="http://www.kuqin.com/language/20071201/2740.html">UTF-8编码检测失败特例</a>。看了正文，觉得不太靠谱，然后继续看回复，觉得这方法可行。</p>

<p>然后定睛一看，原来文章是孟岩写的，回复是我自己三年多前回复在他的 blog 上的。 -_-</p>
]]>
        <![CDATA[<p>打算还是自己写个小程序做检查，不用现成工具了。</p>

<p>具体算法复制回这里：</p>

<p>cloudwu 发表于2007-01-05 00:49:51  IP: 218.72.15.*</p>

<p>如果想区分一个完整的字符串是 GBK 还是 UTF8 其实蛮简单的。
虽然做不到 100% 有效，但也比上面的方法强许多。</p>

<p>UTF8 是兼容 ascii 的，所以 0~127 就和 ascii 完全一致了。</p>

<p>gbk 的第一字节是高位为 1 的，第 2 字节可能高位为 0 。这种情况一定是 gbk ，因为 UTF8 对 >127 的编码一定每个字节高位为 1 。</p>

<p>另外，对于中文，UTF8 一定编码成 3 字节。（似乎亚洲文字都是，UTF8 中双字节好象只用于西方字符集）</p>

<p>所以型如 <code>110***** 10******</code> 的，我们一概看成 gbk/gb2312 编码。这就解决了“位”的问题。</p>

<p>汉字以及汉字标点（包括日文汉字等），在 UTF8 中一定被编码成：<code>1110**** 10****** 10******</code></p>

<p>连续汉字数量不是 3 的倍数的 gb2312 编码的汉字字符串一定不会被误认为 UTF8 。用了一些gbk 扩展字，或是插入了一些 ascii 符号的字符串也几乎不会被认为是 UTF8 。</p>

<p>一般说来，只要汉字稍微多几个，gbk 串被误认为 UTF8 的可能性极其低。（只需要默认不使用 UTF8 中双字节表示的字符）可能性低，这里还有另外一个原因。UTF8 中汉字编码的第一个字节是 <code>1110****</code>
，这处于汉字的 gb2312 中二级汉字（不常用汉字，区码从 11011000 开始）的编码空间。一般是一些生僻字才会碰上。</p>
]]>
    </content>
</entry>
<entry>
    <title>C 语言的前世今生</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/06/c_programming_language.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=600" title="C 语言的前世今生" />
    <id>tag:blog.codingnow.com,2010://1.600</id>
    
    <published>2010-06-28T14:46:20Z</published>
    <updated>2010-06-29T05:43:48Z</updated>
    
    <summary>本篇是应《程序员》杂志约稿所写。原本要求是写篇谈 C 语言的短文。4000 字之内 。我刚列了个提纲就去了 三千多字。 -_- 现放在这里，接受大家的批评指正。勿转载。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>本篇是应《程序员》杂志约稿所写。原本要求是写篇谈 C 语言的短文。4000 字之内 。我刚列了个提纲就去了 三千多字。 -_-</p>

<p>现放在这里，接受大家的批评指正。勿转载。</p>
]]>
        <![CDATA[<hr />

<h2>C 语言的前世今生</h2>

<p>C 语言，从 1970 年代设计并实现之初，它就注定了带有强烈工程师文化的语言，而缺乏一些学术气息。它的许多细节设计，都带有强烈的实用化痕迹。C 语言因 UNIX 操作系统而生，是 UNIX 系统的母语。这导致在这个广泛应用的操作系统上开发，必须通过 C 语言的形式和系统进行交互。这不仅影响了 UNIX 一个平台上的软件，既而也影响了后来世界上最大的桌面系统 Windows ，以及越来越多的嵌入式平台。</p>

<p>由于大部分应用软件最终都需要和操作系统打交道，所以用来开发应用软件的语言，绝大部分也需要利用 C 语言完成和操作系统的通讯。这个世界上绝大部分流行的编程语言，都选择了用 C 语言来实现其编译器或解释器，以及基础部分的运行时库。无论 C 语言设计本身有何种缺憾，在今天，它已无可取代。</p>

<p>到了今天，大部分程序员不再需要逐个时间周期的去抠程序的性能。不需要刻意追求速度最快，最节省系统资源的软件。不需要写那些和系统内核紧密联系的程序。但 C 语言在此之外，依然有其重要的应用领域。我们可以把它作为对最终机器模型的高层次的统一抽象工具，而不必考虑机器环境的差异。经过 30 多年的发展，证明了 C 语言的确是对经典机器模型的最佳表述。仅仅通过增加了一个非常薄的胶合层就得到了一个清晰简洁的设计。正是这一点，使得 C 语言在计算机硬件高速发展的几十年中，一直生机勃勃。</p>

<p>我们在讨论 C 语言时，其实不仅仅涉及了 C 语言本身那用三十几个保留字构成的精简的控制结构和简约的语言特征。还包括了一套对 # 号打头的预处理部分（尤其是基于文本替换的宏处理），以及某些惯用的源代码组织方式（例如：所有的接口定义被定义在后缀为 h 的文件中，并通过预处理方式替换进源代码），和基本的程序库。</p>

<p>这几部分语言核心之外的部分相对独立。以至于使用 C 语言开发并不一定使用标准化的那些东西。C 语言对运行时环境的依赖是非常小的。</p>

<p>而编译预处理器又使得语言富有弹性，甚至可以写出违背 C 语言哲学的代码。著名的 IOCCC 大赛展示了许多常人无法理解的 C 代码。但实际上，C 语言主张代码清晰，表里如一。开发者和维护者都能很容易的预测每一行代码背后的行为。避免存在一些阴暗的角落藏着一些罕见的用法导致程序运行时出现诡异的行为。C 语言在发展过程中一直坚持着最小意外原则。而这一点，正是 C 语言的一个著名发展分支 C++ 所偏离的东西。</p>

<p>C 语言并不是绝对意义上最快的语言。但是它的效率非常好，在切合大部分机器模型并给出统一抽象的基础上，几乎没有其它语言做的更好了。这也是 C 语言哲学的一部分：在统一硬件抽象模型的基础上，尽可能的利用所在硬件环境的一切资源。有时候，C 语言程序员会走向某种极端。追求语言细节的优化，觉得某种代码的组织方式会比另一种方式更高效。但几乎总是错的。优化取决于对具体硬件的理解，以及对编译器如何翻译这些代码的了解。但这正是设计 C 语言想避免的东西。我们不必去争论在语句级上每行代码精确开销的优劣。</p>

<p>同时，C 语言的另一设计哲学就是让每行 C 代码尽量准确的对应相当数量的目标机器码。这使得程序员可以更为容易的理解程序的运行过程。让程序员脑海里可以实时地做一个源代码到最终控制流程的映射。基于这个思想，C 语言一直没有增加对结构进行运算的操作符（而 C++ 中把类或结构模拟成原生类型的做法相当普遍）。甚至于 inline 关键字也迟迟没有被标准化（inline 出现在 C99 标准中，而这个最新的 C 语言标准并没有被广泛接受），正是因为它某种程度破坏了这一点。</p>

<p>C 语言在坚持以上几点理念时，并非突出某个方面（比如追求性能），而是同时兼顾的。</p>

<p>C 语言并不是这个世界上唯一的编程语言，可惜的是，不是所有程序员都认识到了这点。对于把 C 语言作为自己唯一开发语言的程序员来说，很有必要开拓自己的眼界，这样反过来才能更为清晰的理解 C 语言的内在精神。并不是说，某某语言本身是用 C 语言来实现，那么 C 语言就可以以同样的方式，解决那种语言解决的问题（甚至更为高效）。一些 C 语言中的概念，到了另一种语言中，很可能用完全不同的方式展现出来。正如自然语言会影响人的思维方式一样，编程语言一样会影响人对某种算法的编码形式。在 C 里，我们总以为某些写法是自然而然的，但换了种语言却很可能并不尽然。</p>

<p>无论如何 C 语言的语法和设计影响了许多其它语言。最为彻底的是 C++ 。以及大多数程序员都能叫的出名字的一些流行语言：Java ， PHP ，Javascript，Perl ，C#，D，Objective-C 等等。 这些给人造成一种错觉，新的语言取代了旧的，对老的语言做了改良和完善。最广泛传播的观点是，C++ 是 C 的一个超集，它能做所有 C 能做的所有事情，且能做的更好。持有这种观点的 C++ 程序员们甚至向把已有的各种 C 代码用 C++ 重新实现。但实际上，C 和 C++ 更应该被看成是相互平等的存在。C++ 更像是一种借用了几乎全部 C 语法（但还是有细微差异）的全新语言。它们在很多方面都有设计理念上的差异。C++ 企图完全兼容 C 的语法却不想完全继承 C 语言的理念，这使它背负了巨大的包袱。而 C 的另一个继任者：Objective-C ，抛弃了一些东西，则显得清爽一些。</p>

<p>回顾 C++ 出现的时代背景在于把面向对象当成解决复杂问题的“银弹”的年代。这使得 C++ 在发明之初，迅速的占领了大量原本是 C 语言的市场，甚至被看成是 C 语言的替代品。但 C++ 的拥趸们并没有等到这一天。历史证明，面向对象也不是“银弹”、最近十年，C++ 的粉丝们从 C++ 语言的犄角旮旯里挖掘出来的各种武器，让 C++ 语言变成了包含多种编程范式的巨无霸。却并没有让解决问题变得更容易。这并不完全是语言的问题，可能有很大程度上是面向对象等开发方法本身的问题。这也证明了 C 语言保持自身的简洁正是其生机昂然的源泉。</p>

<p>和浩如烟海的 C++ 书籍相比较。如果你已经是程序员，但还不了解 C 语言的话。学习 C 语言，只需要读一本书，而这本书没有第二选择，就是经典的《The C Programming Language》(K&amp;R)。薄薄的一本就讲透了语言的方方面面。可惜的是，C 语言过于注重对机器模型的抽象，并不适合用来程序员入门。尤其是在国内的教材市场，充斥着大量糟糕的 C 语言教材。在这些拙劣的教材中，甚至把开发工具（比如特定的 C 语言开发集成环境）和特定的硬件环境（甚至是过时的 8086 内存模型）与语言教学混为一谈。</p>

<p>对于 C 语言不是母语的程序员来说，有充分的理由去学习一下 C 语言。那是低投入，高产出的。它会使你学会在硬件层次上思考问题（这或许对你是一个新的思维角度）。而且 C 语言已经非常稳定，不会再有（它本身也不希望有）大的变化，不用担心学到的知识会过时。C 语言在 1990 年制订出一个现在通行的标准（ C90 ）以来，在 C 的主流开发社区中几乎没有变过了。虽然，从 1999 年开始，C 语言委员会几经修订 C 语言的新标准（ C99 ），但似乎并不被广泛接受。虽然有很大程度上，这是源于世界上最大的 C/C++ 商业编译器提供商微软对其不感兴趣。可在开源界，即使有 GNU C 对 C 语言新标准的不断推动，那些实际用 C 语言做开发的大佬们还是纷纷表示，新的标准还不是很成熟。新的特性也不是特别有必要。</p>

<p>笔者用 C99 开发有一些年头，但也只使用了其中一个子集，不太敢在正式项目中完全推广。至于 C 语言近年来的发展，我个人比较欣赏苹果公司对 C 语言添加的 blocks 扩展以用来实现 closure 。但并不看好这些新特性会迅速融入 C 语言社区。</p>

<p>C 语言从语言角度上讲，最大缺陷在于要求程序员自己去做内存管理。用 C 语言去处理复杂的数据结构，程序员大部分的时间都花在了这上面，并且滋生了无数 bug 。调试 C 程序变成了一项独立于编写 C 程序的技能。防止缓冲区溢出、防止数据读写越界、正确的动态回收内存、避免悬空指针，这些在大部分语言看起来不可思议的关注点，在 C 语言程序员眼里变得稀松平常。甚至是衡量 C 程序员技能经验水平的重要标志。可要知道，这些和具体问题的解决过程无关。</p>

<p>也有人试图在 C 语言层面解决这个问题，例如以库形式提供垃圾回收的机制（笔者也曾做过类似尝试）。但 C 语言本身的设计使它无法成为一个完美的解决方案。同样的问题也存在于 C++ 。现在看来，不对语言做大的改造，很难回避。可改造本身又违背了 C 语言一贯的哲学。C 语言的发明人之一的 Ken Thompson 近年来参与了新的 Go 语言的设计和实现，可以看成从另一角度对新的程序开发语言的尝试，可那已经不是 C 。</p>

<p>这个问题在一定程度上也促使了 java 的诞生。Java 采用了虚拟机和字节码的方式改造了底层的机器模型。并在底层模型的基础上加入了垃圾回收机制。并在语言层面取消了指针。在 C 语言的原生地，也有更多的动态（脚本）语言出现。先是有 awk 这样的简易语言，后有 perl ，再是 python 等的流行。在 Unix 风格下，程序员倾向于为特定领域设计特定的语言。C 和 Unix 的设计哲学是一体的。它们都鼓励清晰的模块化设计。让模块之间独立，再用薄的胶合层联系起来。脚本语言在现代类 Unix 系统上大量出现，并充当这种粘合工作就是一种发展必然。而原本的充当粘合部分的脚本语言，也逐步发展起来，远远超出脚本的用途范畴。做为程序员，尤其是 C 程序员，必须对它们有所了解并掌握其中的一些，才能适应现代的挑战。</p>

<p>我们不应该指望一门语言解决所有的问题。可至于 C 语言本身，它将在很长的一段时间，带着它的优雅和缺陷，继续扮演它在计算机世界中重要的角色。</p>

<hr />

<p>ps. 命题作文真难写啊。</p>
]]>
    </content>
</entry>
<entry>
    <title>把 vfs 实现好了</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2010/06/vfs_implemention.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=598" title="把 vfs 实现好了" />
    <id>tag:blog.codingnow.com,2010://1.598</id>
    
    <published>2010-06-13T07:16:32Z</published>
    <updated>2010-06-15T17:35:18Z</updated>
    
    <summary>极尽简洁，然过犹不及(As simple as possible, but not simpler.)——爱因斯坦 这段时间的工作是把上次提到的 VFS 系统实现了。而写这篇 Blog 的促因是 twitter 上有同学想让我谈谈对 Linus 最近的一篇老生常谈的看法。哦，看似既然是语言之争。C 好，还是 C++ 好。但这次他平和了许多。Linus 惯有风格依旧，但少了些须三年前的争论 中的刻薄。 我想说，C 的三个特质(见引用文最后一段) 哪一点都不可忽略。Linus 这次强调的大约是第三点，也是 C++ 程序员们不屑一顾的一点。可对于多人协作构建的项目，这一点实在是太重要了。这并不是人人都聪明就能回避的问题。如果程序员们都足够睿智，反而更能意识到沟通之成本。其实即使是你一个人在做整个项目，从前的你和现在的你以及将来的你，同样有沟通（记忆）的成本。人不可能两次踏进同一条河流。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="优化与技巧" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>极尽简洁，然过犹不及(As simple as possible, but not simpler.)——爱因斯坦</p>

<p>这段时间的工作是把上次提到的 <a href="http://blog.codingnow.com/2010/04/vfs.html">VFS</a> 系统实现了。而写这篇 Blog 的促因是 twitter 上有同学想让我谈谈对 <a href="http://www.realworldtech.com/forums/index.cfm?action=detail&amp;id=110618&amp;threadid=110549&amp;roomid=2">Linus 最近的一篇老生常谈</a>的看法。哦，看似既然是语言之争。C 好，还是 C++ 好。但这次他平和了许多。Linus 惯有风格依旧，但少了些须<a href="http://blog.codingnow.com/2007/09/c_vs_cplusplus.html">三年前的争论</a> 中的刻薄。</p>

<p>我想说，<a href="http://blog.codingnow.com/2009/01/the_new_c_standard.html">C 的三个特质(见引用文最后一段)</a> 哪一点都不可忽略。Linus 这次强调的大约是第三点，也是 C++ 程序员们不屑一顾的一点。可对于多人协作构建的项目，这一点实在是太重要了。这并不是人人都聪明就能回避的问题。如果程序员们都足够睿智，反而更能意识到沟通之成本。其实即使是你一个人在做整个项目，从前的你和现在的你以及将来的你，同样有沟通（记忆）的成本。人不可能两次踏进同一条河流。</p>
]]>
        <![CDATA[<p>我的观点在于，如非必要，勿增概念。这是我这次翻新资源管理系统的初衷。在项目组内，这种大的修改是反对多过赞同的。我尚无能力像说服自己那样说服每个人。虽然我及其主张项目演化中的民主，但这一次稍显独断，实在是不得已。因为我觉得这是个不明显的重大缺陷。虽然老的设计它精巧，且可以很好的工作，但它不适合长期保留。</p>

<p>理想的大项目，应该是每个人专心做自己的一块东西，它涉及的外部部分用极少的文档或易于表达的概念定义清楚：无论是程序接口、对资源的占用、适用的范围等等。尤其是弱化 framework 这种联系方方面面的巨无霸。</p>

<p>说回这次实现的 VFS 模块。实现还是比较简单的。但是设计很困难。难点在于，虽无可避免的有一些 framework 的倾向（比原来的系统要弱化许多），但怎样让后面具体的文件系统跟这个小型 framework 交流最少。架子主要解决的是内存资源管理问题和用 cache 提升索引性能的问题。</p>

<p>我定义了两个内部数据结构，借用 linux 的 vfs 中的概念表达。一种叫 dentry 描述目录项，一种叫 inode 描述文件项。但没有暴露这两个数据结构的内部布局。用户扩充的时候，需要给出各一个额外的数据指针来扩展自己的结构。<a href="http://blog.codingnow.com/2010/03/object_oriented_programming_in_c.html">这种手法，我曾经描述过</a>。实际应用的时候，没有定法。</p>

<p>所有函数都应该是可重入的，但暂不要求线程安全。以此实现文件系统的嵌套 mount 。起初，我认为实现一个 zipfs 会很容易。可以 mount 上通用的 zip 文件使用。实际实现时，发现无论是通用的 <a href="http://www.zlib.net/">zlib</a> 还是另一个使用执照稍微麻烦一点的 <a href="http://zziplib.sourceforge.net/">zziplib</a> ，都不提供高效的 seek 接口。我花了一整个晚上研究 zip 的文件格式和解码算法。发现对于这种流式数据压缩，很难做到特别高效的 seek 算法。</p>

<p>利用 zlib 的底层 api ，我想了个办法来提高 zlib 中带的 minizip 库的 seek 效率（目前必须通过假读来实现），不过也不可能达到 O ( LogN ) 的水平。所以我放弃了这个打算。没有高效的 seek 接口，坏处在于嵌套的 mount zip 文件中被打包的 zip 文件性能会很差。当然，可以选择在 zip 被打包进 zip 时不压缩，这样稍微改造一下 minizip ，就能把 seek 提高到 O(1) 的水平。我权衡了一下，还是把此类需求留到以后自己设计一个新的包格式为佳。（采用分块压缩即可）</p>

<p>最终我实现了一个基本的 rootfs ，一个 memfs 用于在内存中创建文件和目录（主要用于创建出最初的 mount 点），一个 nativefs 用于把本地文件系统上的目录树以只读方式映射给引擎，一个 zipfs 实现开发期基本的打包方案。未来可能会增加一个可写的文件系统，用于保存一些本地设置。一个自定义的包方案。</p>

<p>同时在同事的协助上，把 engine 中老的资源操作的接口迁移到新系统上。</p>

<p>btw, 我还考察了<a href="http://www.shapeshifter.se/code/vlc-unrar/">一个修改过的 unrar 的库</a>。不过由于繁杂的执照问题，暂时没有采纳。如果人力充足的话，日后倒可以找人来加上。</p>
]]>
    </content>
</entry>

</feed> 

