2007年7月30日星期一

在维基百科上发表了我的第一篇条目——瓦尔哈

源起我前两天在听瓦尔哈演奏的巴赫管风琴全集。应为是暑假回家来第一次听,确实很受感动,突然想去看看维基上关于瓦尔哈的条目是怎么描述他的。谁知中文维基上居然没有瓦尔哈的条目,英文的也写得很简略(维基上演奏家们的条目都相对较少,虽然他们可写的东西并不少)。

我想,听了瓦尔哈那么多的演奏,都没有为他做点什么,正好可以借这个机会报答一下。虽然我手上关于瓦尔哈的资料并不多,但网络本身就可以提供大量信息。于是花了两三天时间,把网上搜集到的资料收集整理后翻译成中文,加上图片,就成为了一个条目了。

维基是自由的网络百科全书,希望任何爱好音乐的朋友都来参与。如果我写的有什么不对的地方,请大家修改。

条目地址为:http://zh.wikipedia.org/wiki/%E8%B5%AB%E5%B0%94%E7%A9%86%E7%89%B9%C2%B7%E7%93%A6%E5%B0%94%E5%93%88

在国内由GFW无法直接连接维基百科。但因为Blogspot也是被封站点,所以既然您能看到这篇文章,那您可以使用相同的办法办法访问维基百科(条目应该没有敏感字词)。

2007年7月26日星期四

写了一个Firefox的扩展

实在受不了GFW的横行霸道。凭什么要把Wikipedia、Google Cache这些站点封了呢?现在就连上这个Blog都得用代理了,哎……于是,我在ErrorZilla Mod的基础上加上了对失败地址使用Web代理进行访问的功能。

插件名叫ErrorZilla Plus。下载安装后,每次遇到无法打开的地址,一个新的失败页面就显示出来。和ErrorZilla Mod不同的是,现在多了一个"Proxify"的按钮,其下增加了一个列表框。列表框里保存了几个Web代理的名字。用户可以在这几个代理中选择一个,用它对失败的页面进行访问。这样,每次打开维基中文出错后,只需要点一下Proxify就可以访问了。

目前初版是0.3版。在这个版本里,我是把代理列表直接保存在netError.xhtml里的,因为我实在找不到如何在Firefox里读取外部文件的方法,即使是XML也不行。还要请高手指点一下。

如果要修改代理的列表,请打开ErrorZilla Plus安装的目录(<当前profile目录>/extensions/{03651b2d-eb7d-4be7-af1b-dc0cd162dd54}),找到Content文件夹下的netError.xhtml。在该文件中部有一段标签的内容。代理列表在这里以XML的格式保存。name节点保存代理站的名字,website节点保存站点的地址,address保存查询页面的相对地址。更改了列表后请重新启动Firefox。

ErrorZilla Plus的下载地址是https://addons.mozilla.org/en-US/firefox/addon/5398,目前还在沙盒(Sandbox)中。

--------------------

又查了一些资料。Mozilla在扩展的Javascript里有执行权限的限制。向about:这样的地址的权限较低,因此无法调用外部文件。我试了一下XPCOM组件,直接打开about:neterror时,执行Components.class会提示“Uncaught exception: Permission denied to get property UnnamedClass.class”,而通过Chrome(打开chrome://errorzillaplus/content/neterror.xhtml)则没有问题。我想这个问题暂时没法解决,除非Mozilla修改它的权限策略。

2007年7月19日星期四

Singleton (单件) 设计模式

[2007.04.01]

最近在苏州一家计算机公司工作。因为大量用到了Singleton模式,而原来自己实现的Singleton模式存在内存泄漏的问题,所以花了点时间研究如何更好地实现Singleton模式。

我原来实现的Singleton模式是这样的:

(Singleton1.h)

class Singleton
{
public:
static Singleton* Instance()
{
if (_instance == 0)
_instance = new Singleton;

return _instance;
}

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton* _instance;

int* _testPtr;
};


(Singleton1.cpp)

Singleton* Singleton::_instance = 0;


很明显,Singleton::_instance 只是一个指针,在程序结束的时候并不会被自动析构,因此Singleton::~Singleton() 也不会被调用,完全是一个空壳。为了使Singleton类被自动析构,一个最直接的办法就是把Singleton::_instance改成类的实例而非指针,这样静态变量_instance就会在程序结束的时候自动被析构,再设法让_instance中的析构函数调用Singleton类的析构函数,就可以解决内存泄漏的问题了。

这里有三种解决方案:
1. _instance是一个和Singleton类不相关的类(假设为SingletonDestroyer)的实例;2. _instance是Singleton类的父类的实例;3. _instance是Singleton类自己的实例。

第一种解决方案首先被排除,因为如何让SingletonDestroyer访问Singleton类的析构函数是一个问题。Singleton类可以是任何不同的类,有不同的接口,无法统一地被SingletonDestroyer处理。当然,可以让所有的Singleton类继承于一个基类,使它们具有相同的接口,再在SingletonDestroyer中保留一个此基类的指针,在SingletonDestroyer::~SingletonDestroyer()中调用基类指针的析构函数( _singleton->~Singleton(); ),但这样其实已经退化成第二种解决方案了,所以第一种解决方案被排除。

让我们来看看第二种解决方案的实现。

(Singleton2.h)

class Singleton
{
public:
Singleton() { _singleton = 0; }
~Singleton()
{
if (_singleton != 0)
{
_singleton->Destroy();
delete _singleton;
}
}

Singleton* _singleton;

protected:
virtual void Destroy() {}
};

class Sub : public Singleton
{
public:
static Sub* Instance()
{
if (_instance._singleton == 0)
_instance._singleton = new Sub;

return (Sub*)_instance._singleton;
}

private:
Sub() { _testPtr = new int; }
void Destroy() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(Singleton2.cpp)

Singleton Sub::_instance = Singleton();


Sub是实际的单件类,所有Sub类都继承于Singleton类。Sub::_instance是一个Singleton类的对象,在程序结束时会被自动析构,调用Singleton::~Singleton()。Singleton类的Destroy()是提供给子类销毁自己的成员数据的,会在Singleton::~Singleton()中调用。如果子类不覆盖Destroy(),则不执行任何程序。

这个解决方案在非MFC的单线程程序中可以正常工作,但是在多线程的MFC程序中有问题(运行到afxmem.cpp的某行会出错),其他情况我没有测试。在我这里这个解决方案也被否决了。

第三种解决方案:

(singleton3.h)

class Singleton
{
public:
static Singleton* Instance() { return &_instance; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(singleton3.cpp)

Singleton Singleton::_instance = Singleton();


还是第三种方案最简单。Singleton::_instance是自己类的实例,由于是静态成员,所以可以存在。程序结束时,自动调用自己类的析构函数。在MFC和非MFC、单线程和多线程中都没有问题,可以参考。
4.23更新:如果Singleton类是继承于一个父类BaseClass,那么它的_instance变量的类型和实例化都应该不变,而不是像指针那样,BaseClass* _instance; _instance = new Singleton;

另外还有一个第三种方法的变种,就是使用智能指针std::auto_ptr,代码如下:

(Singleton4.h)

#include <memory>

class Singleton
{
public:
static Singleton* Instance() { return _instance.get(); }
~Singleton() { delete _testPtr; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
static auto_ptr<singleton> _instance;

int* _testPtr;
};

(Singleton4.cpp)

auto_ptr<singleton> Singleton::_instance(new Singleton);


[2007.07.19 更新]

其实既然Singleton的生存周期贯穿整个程序,那么必然只有在程序结束的时候才会析构Singleton类。既然程序都结束了,操作系统也会自动回收所有相关内存,那么Singleton类的析构就显得多余了。

因此,又写了一个Singleton的模板类,更方便一点了:

(Singleton5.h)

template <typename T>
class Singleton
{
static T* _instance;

Singleton() {};

public:
static T* Instance()
{
if (_instance == NULL)
_instance = new T;

return _instance;
}
};

(Singleton5.cpp)

template <typename T>
T* Singleton<T>::_instance = NULL;

注意,Singleton的相关实现也要放到头文件里。使用时,所有类继承于Singleton,并传入类自己作为模板参数。如果子类希望有private的构造函数,则还需要让自己和Singleton类成为友元,因为Singleton里有new T,会访问子类的构造函数,而并没有什么修饰符可以指定只允许父类访问,而不许其他对象访问。

如:

class Sub : public Singleton<Sub>
{
friend class Singleton<Sub>;
Sub();

public:
int testFunc() { return 0; }
};

main()
{
Sub::Instance()->testFunc();
}

总结一下。某些类一个程序执行期间只需要存在一个副本。通常这种类仅仅提供某些功能,不存储任何数据,或者仅存储程序的全局数据。对于前者,只需要将类的所有函数设为静态函数即可。对于后者,则使用单件模式。一个类由普通类转换成单件类不需要做任何修改,只需要添加_instance成员变量和Instance()成员函数。

2007年7月18日星期三

解决 Ubuntu 中的循环依赖 (Cycle Dependency)

最近在VMware中安装了一个Ubuntu系统。由于创建系统时没有要求安装虚拟网卡,因此需要安装Ubuntu的软件包时,需要自己下载deb文件,拖进系统,然后双击调用gdebi进行安装。可是,当我尝试安装G++时却出现了问题:g++-4.1这个包依赖libstdc++6-4.1-dev这个包(也就是C++库),而libstdc++6-4.1-dev又依赖g++-4.1。结果两个包都装不上。





上网搜索,在Ubuntu官方论坛找到了解决方法:

在命令行下执行以下语句
sudo dpkg -i --ignore-depends=libstdc++6-4.1-dev g++-4.1_4.1.2-0ubuntu4_i386.deb
sudo dpkg -i --ignore-depends=g++-4.1 libstdc++6-4.1-dev_4.1.2-0ubuntu4_i386.deb
即可。
(g++-4.1默认安装包名为g++-4.1_4.1.2-0ubuntu4_i386.deb;libstdc++6-4.1-dev默认安装包名为libstdc++6-4.1-dev_4.1.2-0ubuntu4_i386.deb)

也就是说,强行让两个安装包忽略依赖项。

使用apt-get自动安装应该没有这种问题。

2007年5月22日星期二

一点关于宗教和信仰的讨论

昨天朋友过来,就和他讨论了一下最近在重新看的《卡拉马佐夫兄弟》。仍然还是从宗教大法官开始。为什么阿廖沙要叫道“你的长诗是对耶稣的赞颂,而不是诋毁……”呢?大法官难道不是在告诉耶稣,也告诉所有的读者,耶稣所做的其实并没有起到他希望的效果,他是高估了普通人类吗?难道他没有指出,与其跟随耶稣,整日在旷野里嚼草根、禁欲,魔鬼的食物、奇迹更能吸引他们吗?而宗教大法官剥夺了人民的自由,却给了他们食物,他充当的是魔鬼的,而非基督的代言人,虽然他告诉人民他是暂时代替基督来统治他们的。问题就在这儿:基督教之所以可以成为最大的宗教教派之一,离不开它对政治统治提供的无可取代的帮助。不可否认,如果基督教仅仅是一门宗教,它不可能得到如此庞大,如此久远的发展的。统治者们利用基督教实施他们的统治,即使他们自己本来毫无宗教信仰,他们而得在民众面前装得很虔诚。相反,基督教在民众心中是很纯洁的。从它被创造之初到现在,宗教本身没有,也不可能有很大的改变。在基督教传播的过程中,变化的只是谁利用它,怎样利用它,它的本质则始终如一。

当然,时代变化了,民众也会变化。从中世纪的神权统治,到重视精神思想的文艺复兴,再到现在的重视物质的社会,民众对宗教的态度也会有很大的变化。基督教在现在之所以还会有这么大范围的影响,应该要归功于它在这么多世纪以来的积累。设想一下现在有一门全新的宗教被创造出来(注意,是宗教,而不是法轮功那种迷信),有可能产生像基督教那样的影响吗?基督教强调灵魂不死,强调忏悔和救赎,这在一千年前完全可以成为人们寄托希望的容器,但在现在,金钱往往是人们最首要考虑的因素。

期待下一个精神时代的到来。

毕业设计——BitTorrent客户端(二)

最近把程序的极原始的雏形写出来了。现在的进度是:
1) 连接Tracker。获得正确回应后记录返回的IP列表,等待回应中指定的时长后重新连接。返回错误回应则记录错误信息。连接错误和回应错误则等待预先设置的时长再重新连接。使用一个线程作为定时器,检查每个Socket是否到了应该重连的时间。

2) 连接Peer。主动连接或者被动接受连接。Tracker获得IP列表后,会把列表存放到一个IP池里。由另一线程去定时尝试连接那些IP。任务一开始会开一个Socket来监听某个指定端口,当有人尝试连接本客户端,则接受连接。

3) 消息流程还是按照BT规范里约定的。主动发起者先发送握手消息,被动接受者收到握手消息后,检查无误,发送回应握手消息,连接建立。然后双方发送Bitfield消息,然后Unchoke消息等等。不同客户端对Bitfield消息的产生方式不一样。像BitComet就是严格按照规范里描述的,把所有Piece的消息写到一个Bitfield里去,而uTorrent对大任务是把Bitfield消息置空,而用Have消息通知Peer它有哪些Piece。

我在自己计算机上测试的,下载可以达到2MB/s的速度。瓶颈是在消息处理流程过长,以及主动请求频率的问题。

4) 保存和恢复任务进度。我是参考uTorrent的方法,将任务信息用bencodin写到文件里去。

5) 文件读写缓冲。所有写文件的操作先是写到内存里去。当缓冲区达到指定大小时,将缓冲区数据写入文件。读操作时,先到缓冲区里去找,若没有则读实际文件。当然要解决的问题是,如果程序意外终止,缓冲区里的数据来不及写到文件里去,那下载的数据就白费了。还是要研究一下Windows关于文件缓冲的技术。

6) 为了简化代码,目前暂时是把支持多任务给去掉了的,不过以后是肯定要加入的。因为每个回收站文件也是一个另一种类型的任务,纯上传任务。

以前使用的是同步Socket,但是问题很多:每个Peer连接都要开启一个线程,要有地方来管理这些线程。另外要控制某个Peer连接去主动请求Piece也很难,因为是要由一个线程去控制另一个线程。线程与线程的同步也是要解决的问题。总之,同步Socket是不能采用的方案。

现在所有连接使用CAsyncSocket,MFC框架。异步Socket + 消息驱动可以保证很少的线程,很低的CPU占用,很高的速度。当然,异步Socket也要解决一些“异步”相关的问题,比如消息发送的先后问题。

关于回收站模式,我会在下一篇文章中介绍。

2007年5月15日星期二

找到 Skype 的官方代理

前段时间用 Skype 3.1 版的时候发现在 Connection 选项卡里面多出来一个“CERNET Beta”,应该是TOM通过某种技术或者手段让教育网用户也能连上美国的服务器。这两天换了3.2版,发现CERNET Beta没有了,但代理设置里面是有值的,用密码显示器得到用户名和密码后一试,竟然可以直接在浏览器里面使用,看来是得到了一个极品的适合教育网使用的代理了。当然也不知道能坚持多久。需要的朋友可以试试:

IP:59.64.114.29
端口:2000
用户名:skype
密码:tomskype

2007年5月5日星期六

大学生在小学

一天做梦,梦见我和中学的几位同学不知道什么原因被要求去小学重读一年的书。可以选择任何一所小学,任何一个年级,任何一个班。当然,在现实中这样的事就太匪夷所思了,要求一个拥有大学生知识和经历的人像一个小学生那样生活一年。不过假如用小说来写出一部这个题材的作品,估计会是挺有趣的吧:

想想,首先,选哪一个年级?六年级会比较接近一点,不会像一年级那样无聊吧。平时上课干什么呢?总不能老打瞌睡吧,那不就白白浪费了一年的时间了。做自己的事,看自己的书?那要假设老师和学校强制要求认真听课。怎么和那些小学生交流呢?总不能一年时间都一个人太孤僻吧。既然那些老师都能和这些小孩打成一片,那我们也能做得到的。作业和考试?当然是轻松完成了。作息安排上,当然是不能迟到、早退的。星期一升国旗的时候肯定会比较醒目吧,这么一个个儿……

当然还有很多情况了,不过小说的结尾是什么呢?一句疑问:“不过,我干嘛要到这儿来上一年小学?”可能最大的好处就是能够有一年时间拥有良好的作息规律,对身体好吧:)

重读《穷人》

五一这几天又在重读陀氏的《穷人》,虽然是他的第一部小说,当也写得确实很真切。除了好奇为什么那个时代的俄罗斯,富人和穷人的差异有如此之大外,我想起了我妈妈。

和马卡尔·杰武什金一样,我妈妈也是一个在岗位上辛辛苦苦、勤勤恳恳了一辈子的人,虽然在机关里是出了名的好人,好心肠,品德高尚,但也从来没有过工作上的升迁。同马卡尔一样,每个月都还是按时拿到虽然不算微薄,但还能过得去的薪水。虽然不多,但她也从来不抱怨什么,总还是认为单位对她已经很不错了。当然,她的这种满足对她来说是好事,事实上,在这个竞争激烈的时代,她在工作方面拥有的能力也确实只配拿到那么多薪水。从我的角度来说,我一直纳闷,干嘛就不发发奋,好好学点技术含量高的知识,争取到金字塔的更高层去呢?依她的人缘,这并不是很难办得到的啊?

现在我逐渐想到,当一个人还年轻的时候,他总是可以对未来充满希望,认为即使现在的状况再差,他也有时间、有力量去改变。而当一个人在一个环境里面呆上了10年、20年以后,他就很难再有改变现状的希望和勇气了。应该改变的东西早就应该已经改变了,没有变化过的东西也基本上不可能在现在再来改变了。即使现状再怎么差,看来都是命运决定了的,自己的力量已不足以撼动它了。不管这种思想如何如何,在那种境况下的人应该是很容易产生这种想法的。君不见这位马卡尔·杰武什金,在他“年纪轻轻”的时候也是那么富有热情,发疯似的去追求女演员,现在老了,每天都用同样的衣装,走同样的路线,到同样的地方,做同样的工作,和同样的人打交道,得到同样的回应。我不清楚我妈妈年轻时具体是怎么想的,不过现在,她是肯定不会再去想怎么让自己升迁,获得更多的报酬。

当然,支撑杰武什金活下去的,是瓦尔瓦拉,支撑我妈妈活下去的,是我。正如瓦尔瓦拉写到的,“您倒想想看,您吃尽了苦头,直到现在,您还只是为我而活着,为我的欢乐而欢乐,悲伤而悲伤,为我的情意而活着。”因为瓦尔瓦拉还年轻,还有希望,所以瓦尔瓦拉很有可能改变她的现状,变得好起来,比他能够设想到的更好。既然他以她的欢乐而欢乐,那么瓦尔瓦拉的改变也就意味着他自己的改变,这是他活下去、极端无私地帮助瓦尔瓦拉的根本原因。同样,我妈妈也只能把希望寄托在我身上(这是她的一句口头禅),希望我的未来会比他的好,这样她也就能心满意足了。

当然,我们的处境比杰武什金他们好,我也比瓦尔瓦拉更有力量去改变自己的命运。

2007年4月24日星期二

毕业设计——BitTorrent客户端(一)

从苏州回来以后还是一直在写自己的毕业设计。题目是我自己选的,做一个BitTorrent客户端,并且在这个基本的客户端上加入我自己设想的独特功能,使这个客户端更加强大。从研究的角度讲,我认为这个题目还是比较合适的,对我个人来说,实现BitTorrent客户端绝对是一个合适的挑战;对计算机领域来说,如果我设想的独特功能被证明是有意义的,那也是有价值的。

当然,要实现“独特功能”,首先得把那个基本的、能够和现有别的客户端通讯的底层做出来,而这个才是难点所在。想一想,虽然一篇BitTorrent协议不算长(和IA-32、FAT这些比的话),但需要实现的东西还是挺多的:

按照协议的顺序,首先要实现bendcoding的解析器,这个在解析torrent文件和解析tracker服务器响应时都会用到。除了解析,还要提供接口,让外部访问者能获得结构化了的数据。

其次,要能和tracker服务器通讯。要充分考虑到tracker可能发生的各种情况,如连接失败,需要重定向,返回无效数据,返回错误,以及返回理想信息等。做到后面还要考虑多线程向多个tracker服务器发送请求,维持请求间隔时间,使用代理服务器访问等等。

再次,要能和各个peer通讯,传输数据。这部分其实就是在博弈,在你我都不知道对方规则的情况下各自制定一套规则,规则本身是由协议里面定义的消息元素来构造的,但是具体细节完全是由你我自己控制的。然后大家在一个对等的平台上,通过规则进行通讯,双方的目的都是在不违反对方规则的同时使自己获得最大的收益。比如,我可以一次向某个peer发送很多请求,但如果这个peer很忙,他就有可能choke我,一个可能更好的策略是,分散请求面,首先请求最稀少的数据块。自己获得最大利益并不意味着要损害对方,最好的结局是双赢,所有人都最终得到完整的数据。当然除了博弈这个算法的问题外,还有很多基础的网络问题要解决。

最后,还要把各个模块整合起来。比如在向tracker服务器发送请求时就要提供当前任务已经下载和上传了多少数据量。而这个数据量显然是要在和peer通讯时统计得到的。类与类之间肯定需要一个中间层来联系。像这种耦合在程序里实在不少,因此架构设计也是需要非常谨慎的。

原本打算还要把图形界面加上,做成一个完整的可发布软件,现在看起来一是时间不够,我实在是找不到能在短时间内实现像uTorrent那样界面的GUI解决方案,二是觉得作为毕业设计,界面确实不是最重要的,不能喧宾夺主嘛。所以界面的事可能得先缓一缓了。

下一篇里我会介绍一些我客户端的框架和一些实现细节,也算是为论文先打个草稿啦。