Thursday, November 4, 2010

使用MinGW编译FFmpeg

友情提示: 编译FFmpeg本来就是一件体力活, 使用MinGW来编更是相当蛋疼, 因此, 如果你没有刚刚作出一个非常艰难的决定, 请立即停止这种自虐行为.

FFmpeg的人估计恨死Window$了, 如果你曾经有过使用VS编译的想法, 现在可以放心抛弃这种打算了. 这时如果你依然很不幸需要用到MinGW来编译, 那么下面的一些提示应该对你的编译过程有所帮助.

  1. configure的参数会稍微特别点:
    $ ./configure --enable-memalign-hack --disable-debug
    
    --disable-debug建议加上, 否则编译出的archive会很大很大...
  2. 现在先不忙着敲make, 我们需要patch一下MinGW的库文件, 否则就会出现诸如"implicit declaration of function 'strncasecmp'"这样的错误. 打开这个链接: http://fate.arrozcru.org/mingw32/patches/, 依照着里面的diff文件挨个patch吧, 完成就可以顺利编译了, 别忘了还要安装Yasm哦.

  3. 编译完成之后你应该可以在各个子目录中找到archive文件, 拿着这些文件做爱做的事吧.

不过小a娘也不好调教, 如果你需要再拿去链接, 可以参考FFmpeg这篇官方指南: http://www.ffmpeg.org/general.html#SEC23. 你可能还会遇到下面一些链接错误.

  • "unresolved external symbol _strcasecmp", 这个不知是不是MinGW的bug, 居然没有提供strcasecmp()函数, 解决方法是自己实现一个相同的函数, 可以参考GNU C Library
  • "unresolved external symbol __imp____lc_codepage", 这是一个更加诡异的错误, 解决方法我在这篇文章中找到, 主要就是把"Runtime Library"从/MD改为/MT
  • "error LNK2005: XXX already defined in LIBCMT.lib MSVCRT.lib", 最诡异错误出现, 操蛋的VS库啊, 在这篇文章的帮助下, 找到了解决办法, 在"Ignore Specific Library"里添加"MSVCRT.lib"即可.

Friday, October 15, 2010

Wish

April Story

淡淡的灯光, 轻轻的音乐, 飘扬的樱花, 已经许久没有看岩井俊二的电影, 对于《四月物语》, 一看就能知道这是他的电影. 电影结束得让我有点意犹未尽, 榆野卯月一个人打着红色的雨伞站在那里, 默念着心中的话语. 看着雨伞上滴落的雨水, 绽放出一丝笑容, 曾几何时, 我也会这样傻傻地干着傻傻的事. 不是因为雨水多么可爱, 只因心中有那么一股暖流在那时流过, 因为某个人, 因为某件事. 在那个阳光明媚的午后, 我也傻傻地望着不远的转角, 期待着某个身影的出现.

喜欢电影从头至尾的清新感觉, 日本干净的街道, 温馨的房间, 以及空旷草地上那抹夕阳. 我跟珍妮说我以后也想要一个很温馨, 很舒适的房子, 不需要多大, 我也不在乎它是租来的, 还是买来的. 但是它会有那种米色的窗帘在卧室, 当窗外阳光透进来时屋内会格外温馨. 有一张很大很舒服的床, 让人看到的第一眼就想要跳上去. 客厅会有布料沙发, 上面或许还有几个柔软的抱枕和玩具. 窗帘是白色的, 会给人一种清新的感觉. 还要一盏小台灯, 夜幕降临时它能为我照耀出轻柔的灯光. 也许, 也许我还能抱着你静静地倚在那里.

中午和同事在附近尝了广东的早茶, 餐厅是一个有着幽暗灯光的地方, 倒很适合情侣. 虽然叫做早茶, 不过不是去品茶, 广东的早茶有着各种小吃和点心, 再配上几碗恬淡的粥, 油而不腻. 也许我也该慢慢学着懂得生活, 从小到大的教育是教我怎样学习, 怎样考上好学校, 却没有教我怎样生活, 生活的本质又是什么. 当集体的信仰缺失, 就会造成一个民族的精神滞后.

我跟大猫说我们还年轻, 有那么多的事等着我们去做, 不管我们的未来如何, 总要知道希望就在前方, 就像很久之前一位同学告诉我说: 希望一定会实现的. 这句话我一直记着.

Sunday, September 19, 2010

那些与彩蛋有关的事情

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1006

前几天@liancheng同学在Twitter上传说中GCC有一个关于#pragma的彩蛋, 大致是说Stallman同学对#pragma很有意见, 因此在GCC中埋了一个彩蛋, 只要发现#pragma就会蹦出俄罗斯方块. 这个很有意思的八卦让我想要一探究竟, 经过一番查找, 这个彩蛋的确是存在的, 不过不是蹦出俄罗斯方块, 根据Wikipedia上一篇文章的介绍, GCC会试图依次启动Hack, Rogue, 以及Emacs中的Towers of Hanoi (汉诺塔), Hack和Rogue都曾经在「桃源」的另一篇文章[经典游戏推荐: NetHack]中提及. 不过这个彩蛋现在已经不存在了, Stallman同学曾经在接受采访时谈到 (请在采访稿中搜索"Easter eggs"), 这个彩蛋其实是针对C标准开的一个玩笑, 后来由于某种原因被移除. 幸运的是, GCC的古老代码依然存在, 下面是从GCC 1.21中提取出来的"遗体":
/* This was a fun hack, but #pragma seems to start to be useful.
   By failing to recognize it, we pass it through unchanged to cc1.  */

/*
 * the behavior of the #pragma directive is implementation defined.
 * this implementation defines it as follows.
 */
do_pragma ()
{
  close (0);
  if (open ("/dev/tty", O_RDONLY) != 0)
    goto nope;
  close (1);
  if (open ("/dev/tty", O_WRONLY) != 1)
    goto nope;
  execl ("/usr/games/hack", "#pragma", 0);
  execl ("/usr/games/rogue", "#pragma", 0);
  execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
  execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
nope:
  fatal ("You are in a maze of twisty compiler features, all different");
}

关于#pragma彩蛋的故事结束了, 但彩蛋本身就像一个充满魔力的物体, 一直是Geek间津津乐道的话题. Linux中的彩蛋不胜枚举, LinuxTOY有一篇文章大概介绍了其中的一小部分. 还有著名的Google Reader中的Konami Code彩蛋. xkcd的命令行版unixkcd更是隐藏了大量彩蛋, 其中就包括Konami Code. 还有一个颇有意思的彩蛋, 在Emacs中按下"M-x butterfly C-M-c"即可, 这个彩蛋的由来是因为xkcd上曾经有一篇漫画 (这篇漫画很有趣, 推荐大家看看, Linux程序员定会泪牛满面, lol) 虚构了Emacs中有这么一个彩蛋, 于是开发Emacs的那群家伙就真的在Emacs 23.1中加入了这个彩蛋...

彩蛋是程序员创造的独一无二的标志, 也许是为了好玩, 也许是为了吐槽, 也许是为了方便调试, 总之彩蛋给这个世界带来了一个值得纪念的回忆. 如果你还知道什么有趣或者有意义的彩蛋, 欢迎分享, :)

For LaN

Thursday, August 19, 2010

小工具介绍: XBindKeys, Global Menu

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=987

小高子一直都是一个近乎狂热的键盘控, 寻找各种可以通过键盘实现的操作. 自从结识了GNOME Do这个神器一般的软件, 启动各种程序再也没有用到鼠标. 不过有一点是GNOME Do办不到的, 当Firefox有多个账户时, 这时通过GNOME Do启动Firefox就有点混乱, 每次必须通过冗长的命令行在多个Firefox账户间切换. 可不可以把这些命令绑定到键盘上呢? 那就是今天的第一位主角了: XBindKeys.

安装好XBindKeys之后, 先生成默认的配置文件:
$ xbindkeys --defaults > ~/.xbindkeysrc
配置文件的语法规则也很简单, 第一行为命令, 第二行为快捷键, 比如:
"firefox -P default -no-remote"
  control + shift + f
这样就可以把任何命令绑定到键盘上了.

Global Menu最初是大猫同学在Twitter上推荐的, 听名字很容易知道这个软件是用来干嘛的, 效果图如下:

Global Menu

怎么安装呢? 这里有一个官方安装指引, Ubuntu用户可以通过PPA源:
$ sudo add-apt-repository ppa:globalmenu-team/ppa
$ sudo apt-get update
$ sudo apt-get install gnome-globalmenu
将menu bar放在屏幕最顶端的设计源于Mac OS, 不过这可不仅仅是单纯的创新或者为了好看, vgod同学的这篇文章详细分析了这种GUI设计背后的数学背景.

Sunday, June 20, 2010

Linux下各种Buffer的比较

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=919

类型 默认大小[1] 存储位置 操作函数 备注
标准I/O流[2] File BUFSIZ[3] (8192) 或者 st_blksize[4] (4096) User Space stat(2), setvbuf(), fflush() 每一个标准I/O流都有一个buffer
stdin stdin->_IO_buf_end[5] - stdin->_IO_buf_base (1024)
stdout stdout->_IO_buf_end - stdout->_IO_buf_base (1024)
stderr 1
TCP Receive Buffer SO_RCVBUF[6] (87380) Kernel Space getsockopt(), setsockopt() 每一个socket都有两个buffer
Send Buffer SO_SNDBUF (16384)
UDP[7] Receive Buffer SO_RCVBUF (114688)
Send Buffer SO_SNDBUF (114688)

脚注:

  1. 括号中的数字为我电脑上的实际大小, 单位为字节. 测试环境: Ubuntu 9.10, 内核版本2.6.31, GNU C library版本2.10.1, 文件系统ext4.
  2. 标准I/O流buffer的默认大小是由具体的C函数库实现决定的, 比如GNU C library就使用st_blksize作为默认大小. 每个流的buffer是在创建好流之后, 第一次调用标准I/O库函数对流进行操作时通过malloc()函数分配的. (参见《APUE》5.4节与5.12节)
  3. BUFSIZ是定义在<stdio.h>头文件中的宏.
  4. st_blksize是"struct stat"中的成员, 通过stat(2)函数获得.
  5. _IO_buf_end以及_IO_buf_base是"struct _IO_FILE"中的成员, "struct _IO_FILE"的定义通常在<libio.h>中. 其实在<stdio.h>中可以看到"typedef struct _IO_FILE FILE;", 也就是我们经常使用的FILE指针指向的结构体了.
  6. SO_RCVBUF是socket的选项名, 可以通过getsockopt()函数获得大小, 以及setsockopt()设置大小. 后同. (参见《UNP》2.11节与7.5节)
  7. UDP类型的socket实际上是不存在buffer的, 这里的大小只是用来约束数据报的最大长度. (参见《UNP》2.11节)

参考资料:

  1. W. Richard Stevens and Stephen A. Rago. Advanced Programming in the UNIX Environment, 2/e. Addison-Wesley Professional, June 17, 2005, ISBN 0201433079.
  2. W. Richard Stevens, Bill Fenner, and Andrew M. Rudoff. UNIX Network Programming, Volume 1: The Sockets Networking API, 3/e. Addison-Wesley Professional, November 21, 2003, ISBN 0131411551.
  3. Helali Bhuiyan, Mark McGinley, Tao Li and Malathi Veeraraghavan. TCP Implementation in Linux: A Brief Tutorial. Available online from http://www.ece.virginia.edu/mv/research/DOE09/publications/TCPlinux.pdf

P.S. 如有不当之处, 还望指教.

Monday, May 24, 2010

最后的战役

John Basilone

献给John Basilone & Lena Mae Riggi

《The Pacific》第8集是属于John Basilone的, 前几集的大肆渲染让我感到很是困惑, 如此一个言过其实的角色凭什么占有这么多的戏份. 直到第8集, 我看到了他身上的正气, 看到了他心中不甘沉沦的理想. 当周围的人们因为他的英勇事迹欢呼雀跃, 因为他的荣耀与光环相敬如宾, Basilone想的不是怎样利用自己的影响力去多捞点好处, 不是每天在办公室喝喝咖啡, 玩玩打字机. 可能你也猜到了, Basilone是一个有抱负的人. 作为二战中唯一一个同时获得荣誉勋章与海军十字勋章的海军陆战队队员, 他选择了放弃舒适的生活, 回到那个能够让他的血液沸腾的地方. 他属于战场.

在新兵训练营遇见Lena是Basilone生命的转折, 前面几集大家可能都会把他当作是一个花花公子, 因为每次在他身边出现的女人都不一样. 在酒店就餐那段Lena演得真好, 那语气, 那神情, "Ooh. Ah. There goes John Basilone. The hero of Guadalcanal." Basilone真是尴尬得无地自容, 他需要向Lena证明自己的心是可以依靠的. 让我们跳过谈情说爱的剧情, 反正最后帅哥肯定是赢得了美女的芳心. 在Basilone即将出征奔赴战场之前, Lena嫁给了他. 在我看来, 这是全集最温情的时刻, Basilone躺在床上, 深情地望着Lena美丽的背影, Lena笑笑, 问到"What?", Basilone什么也没有说, 眼神中已经掺杂了些许泪光, 把目光转向了窗外, 那是一片平静的大海. 奔赴战场, 命运未卜, 怎能忍心留下Lena独自一人. Lena说她可以等, 而我只希望导演能够让Basilone平安回家.

1945年2月19日, 硫磺岛登陆战. 在长达10分钟的镜头里, 导演几乎把每一帧都给了Basilone, 这也许就意味着他最终的命运. 即使是在一年后重返战场, Basilone依旧骁勇善战. 在他倒下的那一刻, 世界静止了, 时间也静止了. Basilone微睁的双眼望着远方, 他心中所想, 只有Lena.

Tuesday, May 11, 2010

split()函数的C、C++标准库实现

2010.5.11 22:52更新: 在大猫同学的强烈要求下, 增加了测试代码中动态分配空间的销毁.

2010.5.14 22:46再更新: 上次更新代码之后, 大猫同学指出还是存在内存泄漏问题, 各位同学一定要注意malloc()free()必须成对使用哦~

最近有这么一个需求, split()函数大家都用过, 将字符串按特定的分隔符 (delimiter) 分割成几个部分, 这在脚本语言中早已司空见惯. 但C/C++中是没有的, 于是就给了我这样闲得蛋疼的人消磨时间的机会, 下面分别是用C和C++的标准库实现的split()函数.

首先是C语言的实现, 这个是我自己写的, 函数返回一个指向char*数组 (也就是字符串数组) 的指针.
#include <string.h>
#include <stdlib.h>

/*
 * split - Break a string @s into an array of strings.
 * @s: The string to be split.
 * @delim: The delimiter.
 * @n: The length of the array of strings, created by splitting @s.
 */
char** split(char* s, char delim, int* n)
{
    char* p;
    char* q;
    *n = 1;
    p = q = s;
    /*
     * Calculate the value of @n.
     */
    while ((p = strchr(q, delim)) != NULL)
    {
        ++*n;
        q = p + 1;  // skip the delimiter
    }
    p = q = s;

    char** sep = (char**) malloc(sizeof(char*) * (*n));  // the array of strings
    int i;
    for (i = 0; i != *n; ++i, q = p + 1)
    {
        /*
         * When there isn't delimiter, put `p' point to the end of @s.
         * Same like behavior of strchrnul() library function.
         */
        if ((p = strchr(q, delim)) == NULL)
            p = s + strlen(s);

        int len = p - q;
        sep[i] = (char*) malloc(sizeof(char) * (len + 1));
        strncpy(sep[i], q, len);
        sep[i][len] = '\0';
    }

    return sep;
}
可以使用下面的代码进行测试:
#include <stdio.h>
#include <stdlib.h>

char** split(char*, char, int*);

int main(int argc, char* argv[])
{
    char str[] = ":GFW::is:evil:";
    int i, n;
    char** sep = split(str, ':', &n);
    for (i = 0; i != n; ++i)
        printf("%s\n", sep[i]);

    for (i = 0; i != n; ++i)
    {
        free(sep[i]);
        sep[i] = NULL;
    }
    free(sep);
    sep = NULL;

    return 0;
}

C++实现来自Stack Overflow的一篇讨论, 我稍微进行了一些修改.
#include <sstream>
#include <vector>

using namespace std;

vector<string> split(const string s, char delim = ' ')
{
    vector<string> elems;
    istringstream ss(s);
    string item;
    while (getline(ss, item, delim))
    {
        elems.push_back(item);
    }
    if (item == "")
        elems.push_back(item);
    return elems;
}
代码很简洁, 测试代码就不写了, 大家应该都能看懂. 其实大名鼎鼎的Boost也实现了split(), 但毕竟不是任何时候都能使用Boost.

Sunday, May 9, 2010

转换多个JPG文件到PDF文件

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=908

最近需要将大量JPG格式的图片全部转换到一个PDF文件里, Google之后发现Window$下清一色的收费软件, 即使有试用版, 也会带有很多限制. 而Linux下则从来都不缺乏这种实用的小工具, 在bitPrison.net的一篇文章中介绍了通过ImageMagick中的convert组件进行转换的方法, 经过尝试, 发现这种方法效率很低, 并且会占用很高的CPU. 该篇文章后面的评论提供了一种更优的方法, 使用PDFjam包中的pdfjoin命令完成, 具体步骤如下:
$ rename 's/\.jpg$/\.pdf/' *.jpg
$ pdfjoin --outfile alist.pdf *.pdf
这里先将所有JPG文件重命名为PDF文件, 接着便可使用pdfjoin命令进行合并.

其实GNOME或者KDE下的程序都可以直接将图片打印为PDF文件, 比如使用Eye of GNOME打开图片之后, 选择打印就会出现“Print to File”选项, 这种方法适用于少量的或者临时性的转换需求.

Monday, April 26, 2010

小技巧: 在C++中实现在main函数之前及之后执行代码

对于C++程序而言, 程序执行的入口总是main函数. 怎样才能在执行main函数之前执行一段代码呢? 答案是全局对象 (global object). 程序在执行时因为会先初始化全局变量, 当这个全局变量是一个对象时, 自然会调用相应的构造函数, 这时构造函数中的代码就得以执行.

那怎样在main函数退出之后再执行一段代码? 答案依然是全局对象. 当程序退出时, 全局变量必须销毁, 自然会调用全局对象的析构函数, 剩下的就同构造函数一样了.

下面是一段测试代码:
#include <iostream>

using namespace std;

class A
{
public:
    A()
    {
        cout << "1" << endl;
    }

    ~A()
    {
        cout << "4" << endl;
    }
};

A a;

int main(int argc, char* argv[])
{
    cout << "2" << endl;

    cout << "3" << endl;

    return 0;
}

P.S. 本来想通过汇编进一步分析的, 无奈功力还不够深厚, 希望以后能够搞懂. 若有高人了解, 还望指点.

Tuesday, March 30, 2010

OAuth协议及开发简介

这是昨天三叶草小组内部交流我的主题, 因为考虑到多数人对Twitter不熟悉, 所以用人人网做的例子, 但其实人人网是不支持OAuth的.

Saturday, March 20, 2010

照相

cute girl

深夜, 可恶的Google Reader一直刷不开, 于是点开QQ邮箱开始看空间, 看到好友又更新了相册, 一张正面无码单人照, 有点带着淡淡忧伤的表情. 突然有点感怀神伤, 天使爱美丽中说人们之所以会不断地照相, 其实是担心被这个世界遗忘. 我们的青春是短暂的, 美丽女孩的青春更是短暂的. 多少年后, 可以留下些什么吧. 曾经看过的采访中说, 那些为领导、老板服务的姑娘们平时最大的爱好就是把自己打扮得漂漂亮亮, 然后用五彩的像素凝固下自己那转瞬即逝的青春.

她们为这个世界带来了美丽, 也许她们想要的仅仅是一个可以依靠的臭男人. 而一个真真懂得疼爱的男人却是比处女还要稀少的动物.

说到照相就不得不说说兰姐, 人送外号"柳照". 什么意思? 讲个故事你就明白了. 话说当年和兰姐一起去西岭雪山, 就是杜甫同学写的"窗寒西岭千秋雪"中的西岭. 当我们来到一座小木屋前时, 兰姐说: 给我在这里照一张吧. 当我们进入旅店的大厅时, 兰姐说: 这里挺不错, 照一张吧. 当我们去滑雪场滑雪时, 兰姐说: 这么难得的机会, 一定要摆一个帅点的pose. 当我们从滑雪场回来, 再次经过那个小木屋时, 兰姐说: 我太喜欢这座房子了, 多拍几张. 这里省略后续台词无数. 从刚才的故事我们懂得了, 兰姐是一个非常喜欢照相的人. 对于这点, 兰姐也是直言不讳, 如果你要让她在照相和男朋友之间选择的话, 我估计她会选前者. "柳"字是四川话的谐音字, 意思是说频繁地, 持续不断地做某件事. "柳照"绝对是形容兰姐最贴切的词语.

今天屁颠屁颠地跟着兰姐班上出去春游, 到了一个满是鲜花盛开的地方, 真是春天到啦, MM们也越穿越少啦. 那真是"阅黑丝无数, 行人无不侧目." "柳照"同学自然不会放过这样一个大好时机, 即便被热得满脸通红, 也要坚持摆出各种pose拍照, 这种专业的精神, 也只有在看《喜剧之王》的时候被星爷感染过.

今天真是有意义的一天.

Thursday, March 11, 2010

"西厢计划"Ubuntu安装配置指南

"西厢计划"是今天听到的最振奋人心的消息, 穿越GFW这么多年, 基本上各种类型的方法都用过, 每每穿墙都有一种莫名的快感, 想着GFW同学被无数人一次又一次地虐待. 作为新世纪的有志青年, 翻墙已经成了一种必须掌握的技能. 俗话说得好, "不会翻墙的网民不是好公民." 看到这里你应该已经知道"西厢计划"到底是个什么东西了, 但它不同于以往的所有翻墙技术, 算是翻墙界的最新研究成果吧. "西厢计划"的优势在于可以用最直接的方式访问网站, 比如平时用的代理、VPN都是先到另一个地方绕一圈, 而按照作者的说法, "西厢计划"直接注入GFW系统, 这样当你访问敏感词网站时, GFW就变得像个傻子一样, 根本不会进行重置操作, 从而以最快的速度访问网站.

目前还只能在Linux系统中进行使用, 且要求内核版本大于等于2.6.17, 下面是我在Ubuntu中安装配置的全过程.

安装

这里下载安装文件, 解压进入目录. 先安装一些依赖软件:
$ sudo apt-get install autoconf automake libtool aptitude xtables-addons-common iptables-dev
接下来开始编译加安装:
$ ./autogen.sh
$ CFLAGS="" ./configure --prefix=/usr --libexecdir=/lib
$ make
$ sudo make install

配置

在安装文件的"examples"目录中有一些已经设定好的ipset规则, 这些规则用来过滤那些特定的网络访问, 先导入所有规则:
$ sudo ipset -R < CHINA
$ sudo ipset -R < GOOGLE
$ sudo ipset -R < YOUTUBE
$ sudo ipset -R < NOCLIP
再使用iptables设定具体的过滤规则:
$ sudo iptables -A INPUT -p tcp --sport 80 --tcp-flags FIN,SYN,RST,ACK SYN,ACK -m state --state ESTABLISHED -m set --match-set NOCLIP src -j ZHANG -m comment --comment "client-side connection obfuscation"
$ sudo iptables -A INPUT -p tcp --dport 80 --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j CUI -m set --match-set CHINA src -m comment --comment "server-side connection obfuscation"
$ sudo iptables -A INPUT -p tcp --sport 80 -m state --state ESTABLISHED -m gfw -j LOG --log-level info --log-prefix "gfw: " -m comment --comment "log gfw tcp resets"
$ sudo iptables -A INPUT -p udp --sport 53 -m state --state ESTABLISHED -m gfw -j DROP -m comment --comment "drop gfw dns hijacks"
最后修改主DNS, 在"/etc/dhcp3/dhclient.conf"文件中找到"prepend domain-name-servers", 将后面的IP替换为没有被感染的DNS地址, 比如"8.8.8.8", 重启网卡:
$ sudo ifconfig eth0 down
$ sudo ifconfig eth0 up

大功告成! 打开浏览器试试吧, YouTube、Blogger、Picasa、Google Groups、Google Docs的Spreadsheet这些都已是畅通无阻, 不过目前Twitter、Facebook还无法访问, 但相信"西厢计划"以后将会越来越强大.

2010.3.13更新:

以上配置在重启之后都会消失, 这就麻烦了, 总不能每次开机都先输这么一长串命令吧. 在参考了Ubuntu官方Wiki之后, 有了一个很好的解决办法.

首先确定你已经设定好了上面的所有规则, 接着将当前规则导出并放到"/etc"目录中:
$ sudo ipset -S > ipset.rules
$ sudo iptables-save > iptables.rules
$ sudo mv ipset.rules iptables.rules /etc
其实我们需要做的就是让系统在每次开机的时候自动读取这些规则, 这可以有很多种方法, 比如写成脚本, 放到rc目录中, 但我觉得下面的方法更加灵活一点. 这里又要分成两种方法, 如果你使用了NetworkManager, 请看第一种, 否则看第二种. 之所以要这样分开讨论, 是因为第二种方法会导致NetworkManager不能正常工作.
  1. 这里下载NetworkManager的配置脚本, 加上可执行权限, 最后放到"/etc/NetworkManager/dispatcher.d"目录中.

  2. 在"/etc/network/interfaces"文件中添加如下几行:
    auto eth0
    iface eth0 inet dhcp
        pre-up ipset -R < /etc/ipset.rules
        pre-up iptables-restore < /etc/iptables.rules
        post-down ipset -S > /etc/ipset.rules
        post-down iptables-save > /etc/iptables.rules
    

P.S. 我终于又可以直接在Blogger里写博客了, 泪牛满面啊~~~

Monday, February 15, 2010

让Android手机通过USB数据线共享电脑网络 (Linux版)

中国移不动的流量费向来是奇贵无比 (还有极大的地域差别, 浙江移动10元是200M, 而四川只有70M), Android手机更是用起流量来刷刷刷的, 要是一直保持网络连接, 一会儿就同步点联系人、Calendar、Gmail, 一会儿又检查下有没有软件更新, 我也曾经有过因此而超支到100多元的惨痛经历. 虽说现在每个月有100M的流量可以使用, 但还是有点不够用, 我觉得最理想的是能够借助电脑的网络上网. 也许有人会觉得这样有点多余, 既然有电脑, 干嘛还非得用手机上网, 其实这也因人而异, 有些时候确实会很有帮助. 试想你一边使用电脑, 再一边用手机听着豆瓣电台是怎样的感觉.

现在手机借助有线网络上网一般有以下几种方法:

  • 通过无线路由器+Wi-Fi上网, 这是最方便的一种方法, 但也是经济成本最高的一种.
  • 通过笔记本电脑的无线网卡建立ad hoc网络, 再使用Wi-Fi连接上网. 这种方法我曾经在Window$ XP里试过, 有点复杂, 且最终以失败告终... 不过据@coolwdp同学说在Win7下很方便.
  • 通过USB数据线将手机与电脑相连, 再分别在电脑和手机上虚拟出一个网络接口用于网络通信, 这很类似于VPN与虚拟机上网的原理. 今天将要介绍的就是这种方法, 好处是不论台式还是笔记本都适用, 只要有USB接口. 机锋网上有一篇针对Window$系统的介绍帖, 使用Window$的同学可以参考参考, 这里的介绍仅仅面向使用Linux的同学.

下面是详细步骤, 初看会有点复杂, 但只要理解了原理还是很简单的.

  1. 按照我以前的文章[在Ubuntu中配置可供Windows主机共享上网的VPN服务器]中介绍的方法设置电脑的IP转发及NAT.

  2. 用USB数据线连接手机与电脑, 上一篇文章介绍的CM ROM提供了"Internet tethering" (tethering是使电脑通过手机上网的意思, 和我们今天介绍的目的刚刚相反) 功能, 在"Settings->Wireless controls"中. 如果你的手机提供有"Internet tethering"功能, 将它开启. 为什么需要这么一步呢? 我们并不是想要借助手机网络来让电脑上网, 仅仅是为了利用它来建立虚拟的网络接口. 当开启"Internet tethering"后, 分别在电脑和手机上输入"ifconfig"与"busybox ifconfig", 你会发现两边都有一个叫做"usb0"的网络接口, 且IP地址都是一个网段, 如"192.168.77.X", 两边也可以互相ping通. 这就是"Internet tethering"虚拟出来的网络接口, 我们后面全部需要靠这个接口来与电脑通信.

  3. 虽然现在手机和电脑已经可以互相ping通, 但手机还无法访问外网, 这就需要设置一下手机上的路由表. 方法很简单, 只需要将默认路由的网关设置为电脑上"usb0"接口的IP地址就可以了, 比如这个IP地址为"192.168.77.100", 则在手机终端中输入以下命令:
    # busybox route add default gw 192.168.77.100 dev usb0
    
    现在再试试ping一下"g.cn"的IP地址: 203.208.39.104, 通了的话就证明已经可以访问外网.
  4. 光能通过IP地址访问外网还不够, 需要再将DNS设置好, 同样在手机终端中输入命令:
    # setprop net.dns1 8.8.8.8
    
    感谢万能的Google大神提供了这个万能地址, 现在应该可以直接ping通"g.cn"这个网址.
  5. 其实到现在为止, 基本的配置已经完成, 你已经能够享受电脑网络而丝毫不用考虑流量这回事了. 但当你关闭"Internet tetering"之后, 刚才所有的配置又会还原, 也就是说下次还需要输入以上两行命令, 这样就太麻烦了一点. 这里推荐使用GScript软件将以上命令保存为脚本文件, 当需要设置的时候就直接运行脚本, 脚本配置如下图:

    GScript

    这样每次的步骤就变成了开启"Internet tetering", 然后运行脚本. 可能是GScript本身软件的问题, 有时脚本需要多运行几次才行.

  6. 最后需要注意的一点就是某些Google服务 (如Google Talk) 必须在打开手机网络的情况下才能使用, 即使你已经可以通过电脑访问外网. 并不用担心打开手机网络会消耗流量, 那只是一个假象, 真正的网络通信全部都是通过电脑网络来实现的, 你可以仔细观察状态栏上的手机网络图标的箭头并不会闪动.

参考资料: 【更新】让G1通过usb共享电脑的网络,更新简单详细教程, [android-freerunner] Share internet connection through USB.

Sunday, February 14, 2010

HTC Magic (G2) 简明刷机指南

刷机可以理解为重装系统, 如果一个智能手机买来都没有刷过一次机, 在我看来那是极大的浪费. 作为一个智能手机的初级用户, 在经历了足够长的适应期与心理准备期之后, 我决定尝试着弄一次. 刚开始还一直担心会不会刷完后机子就直接变砖块了, 后来才知道, 如果不刷SPL (bootloader的一部分) 的话, 普通刷机是没有风险的.

刷机最重要的就是选择合适的ROM, Android系统从版本来分有1.5、1.6、2.0、2.1, 每个版本又有官方与非官方数种ROM. 就中文用户来说, 适用的有国内两大阵营: 机锋网 (这是@coolwdp同学推荐给我的) 和安卓网制作的各种ROM. 我选择的是机锋网基于CyanogenMod (CM) 编译的ROM, 最新的稳定版为4.2.14.1, 是Android 1.6的ROM.

下面是在G2上刷机的详细步骤:

  1. 从上面的论坛地址中下载相应的ROM, 重命名为"update.zip", 复制到SD卡的根目录.

  2. 关闭手机, 同时按住Home (小房子) 和Power键一段时间, 直到进入recovery mode. 这时需要按"Alt+W"键来清空内存, 但由于G2没有硬键盘, 也就没法直接完成. 但有人对recovery mode进行了修改, 加入了几个可以用轨迹球选择的菜单, 如果你的手机的recovery mode里没有这样的菜单, 就需要安装修改版的recovery image, 如果已经有了, 请跳到下一步.

    一开始我使用的是CyanogenMod官方制作的"cm-recovery-1.4.img", 结果进入recovery mode的时候一直卡在HTC的画面上, 只能把电池取下来, 强行重启. 后来才发现G2不能使用1.4的image, 于是在xda-developers上下载了专门针对G2 32A制作的1.4 image (也有32B的image). 下载相应的recovery image到SD卡的根目录, 然后在手机的终端里执行以下命令即可安装:
    # flash_image recovery /sdcard/cm-recovery-1.4-32A.img
    
  3. 现在已经进入了recovery mode, 选择"Alt+W"开始清空内存, 这里强烈建议在清空之前使用AppManager将所有的软件都备份到SD卡上, 以免重新下载 (这年头流量可不便宜).

  4. 清空完毕之后选择"Alt+S"安装ROM, 这步时间会稍微长点, 成功之后即可重启手机开始享受全新的ROM吧.

Wednesday, January 27, 2010

有趣的数学: 小浣熊干脆面与伯努利试验

小浣熊水浒卡: 吴用

记得小学的时候特别喜欢吃一种叫做"小浣熊"的干脆面, 并不是说有多么好吃, 主要是为了收集其中附赠的卡片, 吃面倒成了其次. 当时流行的是水浒108将, 那些卡片外观精美, 正面是每一个水浒好汉的生动造型, 背面则是人物介绍, 我最喜欢的是那些关于武器、必杀技、攻击力之类的, 总之很迷恋, 对于那些名词很是敏感. 那时候每一个买这种干脆面的小孩子的最大愿望肯定都是把108张卡片全部集齐, 我几乎是每天一包的买, 下课就会冲向小卖部, 呵呵~ 不过在我集齐之前就厌倦了吃干脆面的日子, 小孩子嘛, 都比较喜新厌旧, 印象比较深的是我有排名考前的几张以及第108张"鼓上蚤——时迁". 现在淘宝上已经有人在卖这个系列的卡片, 都很便宜. 这里也有对于其中部分卡片的精彩点评.

这和伯努利试验 (Bernoulli trial) 又有什么关系? 主要是我今天在看CLRS的时候突然想用概率的知识来算算到底我需要买多少袋干脆面才能集齐108张水浒卡片, 这个应该不算很蛋疼吧, 囧. OK, 闲话少说, 现在我们的目标是收集108张各不相同的卡片, 收集到一张就算成功一次. 那种结果或者成功, 或者失败的试验就叫做伯努利试验, 因此如果我们要集齐的话, 就需要进行n次伯努利试验, 这个n也就是一共需要购买干脆面的期望袋数. 把这n次试验分成108个阶段, 每一个阶段代表上一次收集成功到这一次收集成功间的过程, 比如第108阶段就是在成功收集到107张之后至收集到108张这之间需要经历的过程. 每一个阶段又是一系列小的伯努利试验, 把所有阶段的试验次数加起来也就得到了n的值.

在搞清楚基本原理之后, 来计算一下每一个阶段成功的概率, 也就是我在买了无数袋干脆面之后终于拿到一张不是重复卡片的概率. 假设现在是第i阶段, 证明我已经成功收集了i - 1张卡片, 还剩下108 - i + 1张没有被拿到. 每一张卡片被拿到的概率是1/108, 108 - i + 1张卡片被拿到的概率就是(108 - i + 1)/108, 即是每一个阶段成功的概率. 而第i阶段所进行的一系列伯努利试验的次数ni是服从几何分布的随机变量, 根据几何分布的性质:

上面的期望值就是每一个阶段成功时进行的试验次数, 正如前面所说, 把每一个阶段的试验次数都加起来也就得到了总的试验次数, 也就是我需要买的干脆面袋数, 计算过程如下:

算下来大约需要购买506袋干脆面才能集齐卡片, 也许当时我再坚持那么一下下就可以了, :)

这个问题的变种还有将球投入n个盒子中, 计算需要投多少个球才能让每一个盒子中都至少有一个球.

Thursday, January 14, 2010

在Ubuntu中配置可供Windows主机共享上网的VPN服务器

VPN (Virtual Private Network, 虚拟专用网) 是一种在现有网络基础上建立的虚拟网络, 主要用于帮助两个网络通过VPN隧道 (tunnel) 进行通信. VPN的好处在于网络A中的电脑A1通过隧道与网络B中的电脑连接上后, A1将能够使用网络B的网络环境. VPN分为加密与不加密两种, 通常我们使用的都是加密VPN. 加密VPN常用的协议有SSL、PPTP等, 其中PPTP是Windows系统内置的协议, 因此如果想要搭建一个支持Windows电脑接入的VPN服务器, 最好是使用PPTP服务器软件. 当前VPN的主要用途有在异地接入一个内部网络, 以及翻越功夫网.

注意: VPN服务器必须是具有外网IP的电脑, 如果IP地址属于10.0.0.0~10.255.255.255, 172.16.0.0~172.31.255.255, 192.168.0.0~192.168.255.255这三个IP地址段, 则不具备搭建VPN服务器的条件.

Poptop是一款Linux下的PPTP服务器软件, 今天我们就主要借助它来完成一个VPN服务器的配置. Ubuntu系统使用如下命令安装Poptop:
$ sudo apt-get install pptpd
如果你的Linux的内核版本低于2.6.15, 那么需要先检查一下是否支持MPPE:
$ sudo modprobe ppp-compress-18 && echo "success"
若是没有输出"success"则证明内核不支持, 可以跟随这里的步骤进行内核的配置.

Poptop安装完毕之后需要简单配置一下, 打开"/etc/pptpd.conf"文件, 添加下面两行, 或者这个文件已经有了一些示例, 只需要去掉注释符号.
localip 192.168.0.1
remoteip 192.168.0.234-238,192.168.0.245
"localip"表示VPN隧道中服务器 (server) 的IP地址, "remoteip"表示VPN隧道中客户端 (client) 可以分配的IP地址. 关于"pptdp.conf"文件的更多选项, 可以阅读它的man page.
然后设置用于登录的用户名和密码, 打开"/etc/ppp/chap-secrets"文件, 添加下面一行, 中括号部分代表需要配置的地方:
[username] pptpd [password] *
最后重启Poptop:
$ sudo /etc/init.d/pptpd restart
现在试试用其它电脑是否可以成功连上, 注意客户端填写的IP地址是VPN服务器的外网IP, 而不是刚才配置的"localip".

虽然可以成功建立VPN连接, 但通常情况下还不能通过VPN服务器连接到Internet. 原因有多种, 先来看看客户端通过VPN服务器与Internet上的服务器通信的全过程:
client <--> client ppp0 <--> VPN server ppp0 <--> VPN server <--> VPN server eth0 <--> Internet server eth0 <--> Internet server
"ppp0"其实是VPN虚拟的一个网络接口 (可以想象成这是一个虚拟的网卡), VPN隧道就是通过客户端与服务器的这两个网络接口建立的. 而"eth0"则代表服务器上真实存在的物理网卡, VPN服务器与外网通信就需要通过它. 具体流程是: 客户端通过"ppp0"向VPN服务器发出请求, VPN服务器侦测到之后, 再将请求通过"eth0"转发出去, 当请求到达目的地之后, Internet服务器就根据请求做出相应的回复, 这个回复再按照刚才来的路径返回到客户端, 这样客户端就成功与Internet服务器完成一次通信.

上面图示中的箭头部分 ("<-->") 就是可能造成无法连接Internet的关键, 因此需要针对每个部分一一排查. 这里只对可能性最大的两个地方进行介绍, 想要了解每一个关键点的检测方法的同学可以阅读"Diagnosing Forwarding on pptpd"这篇文章.

  1. 是否已经打开IP转发?

  2. 查看"/proc/sys/net/ipv4/ip_forward"文件中的值是否为"1", 如果不是, 则需要在"/etc/sysctl.conf"文件中添加"net.ipv4.ip_forward=1", 然后执行以下命令:
    $ sudo /etc/init.d/procps restart
    
  3. 是否在VPN服务器上设置了对于客户端IP地址的NAT?

  4. 执行下面的命令查看表中是否有相应的表项:
    $ sudo iptables --table nat -L POSTROUTING
    
    如果没有则执行以下命令:
    $ sudo iptables --table nat --append POSTROUTING --out-interface eth0 --jump MASQUERADE
    

在完成上面两个检查之后, 应该就可以成功通过VPN服务器与Internet进行通信, 一个VPN服务器也基本配置完毕.

参考资料: Debian pptpd HOWTO, #13, Diagnosing Forwarding on pptpd.

Tuesday, January 12, 2010

Android系统在发送短信后报错的解决

今天在发送短信的时候, 每发完一条就提示"The application Messaging (process com.android.mms) has stopped unexpectedly. Please try again.", 很是烦人, 还以为是系统出问题了, 然后在这里找到了解决方法, 说来也简单, 把所有短信删了就行了... 难道是存储的短信太多造成的? 太诡异了, 我手机里总共也没多少, 不过反正有一个叫做"SMS Backup"的软件, 重要的以后就备份到Gmail去算了.

Monday, January 11, 2010

流言终结者: 使用位运算交换变量的值

在C语言中怎样交换两个变量的值? 我们通常的做法是:
tmp = a;
a = b;
b = tmp;
那怎样不使用中间变量交换两个变量的值? 也许你看过这样的方法:
a ^= b;
b ^= a;
a ^= b;
这是一个借助于位运算的小伎俩, 但一般在实际编程中是不推荐使用的, 原因在大多数人看来应该是降低了代码的可读性, 其实还有一个被忽视的地方. 在多数人的直觉中位运算绝对比普通的运算符要快, 不过刚才的例子是一个反例.

现在让我们来看一下上面两段代码对应的汇编代码, 这里使用gcc作为编译器. 首先是使用了中间变量的那几句:
movl    -4(%ebp), %eax
movl    %eax, -12(%ebp)
movl    -8(%ebp), %eax
movl    %eax, -4(%ebp)
movl    -12(%ebp), %eax
movl    %eax, -8(%ebp)
"-4(%ebp)"、"-8(%ebp)"、"-12(%ebp)"分别是变量abtmp在栈中的地址, 同时使用了寄存器"eax". 可以看出, 上面的代码一共对内存进行了3次读操作和3次写操作.
再来看看使用位运算的汇编代码:
movl    -8(%ebp), %eax
xorl    %eax, -4(%ebp)
movl    -4(%ebp), %eax
xorl    %eax, -8(%ebp)
movl    -8(%ebp), %eax
xorl    %eax, -4(%ebp)
一次"xorl"异或操作包含一次读和一次写, 因此一共是6次读操作和3次写操作.
通过比较可以看出位运算在I/O速度上已经不占优势, 再比较一下两种代码所需的内存空间, 下面这段汇编是公共的代码.
pushl   %ebp
movl    %esp, %ebp
subl    $16, %esp
两种方法都在栈中分配了16个字节的空间, 因此在空间上的比较是一样的. 这样看来, 使用位运算交换两个变量的值真是装B者必备的好东西.