2008年2月26日星期二

Imagine Cup 2008 - IT Challenge 晋级 Round 2

今年的Imagine Cup已经为没有Web Development了,估计因为往年Software Design太多人做Web了,所以干脆合并这两项了。今年新增项目为Game Development,用的工具是XNA Game Studio,看起来容易,但要做一个完整的游戏并不容易。总之,我们去年的团队今年没有再团队参赛,所以我自己挑了两个个人项目来玩玩。

第一个是Algorithm,因为我从来不知道Algorithm是怎么玩的,本年也仅仅参加了最后一场Round 1的比赛,所以在我知道怎么比赛时,已经在比赛了。我只能想尽办法拿分,没有任何经验和技巧而言,然后就挂了。

第二个是IT Challenge,同样是等到Round 1最后一场才参赛。这是考察MSIT技术知识的吗?还是考察Google的使用能力?不管了,反正30道选择题,我一看就懂做的大概只有两题,所以整个比赛就是靠Google。一个小时里面,每道题可以分配两分钟的查找时间,并行处理的话,打开页面的事件可以忽略不算。

比赛完,看我答对的题数,觉得自己不怎么样,结果竟然进入Round 2了。有趣的是,Round 2的资料一直没发出,甚至没有邮件通知,我是主动到ImagineCup.com看才知道自己进入Round 2了的。不过因为Round 2至今为止还没有发布任何资料,我也没办法做什么,只有等邮件通知了,或者天天上去刷新。

使用 .NET 实现 Ajax 长连接 (Part 1 - Comet Web Service)

Ajax的长连接,或者有些人所说的Comet,就是指以XMLHttpRequest的方式连接服务器,连接后服务器并非即时写入相应并返回。服务器会保持连接并等待一个需要通知客户端的事件,该事件发生后马上将数据写入响应,这时候客户端就以相当“实时”的方式接收到事件通知。具体的通信模型,请参考这篇文章:《Comet:基于 HTTP 长连接的“服务器推”技术》,里面已经说得非常详细了,我就不再复述了。

我们接着开始讨论如何使用.NET实现这个模型。首先我们能想到的是,我们需要一个Web Service,可以是ASP.NET Web Service,也可以是WCF Web Service,ASP.NET AJAX Library两者都支持。在这里,为了简单起见,就选择大家更熟悉的ASP.NET Web Service举例。然后,我们写下以下两个函数签名:

public void Send(Message message);
public Message Wait();

其中,Send函数用来发送一个Message对象,而Wait函数用来等待一个Message对象。然后,让我们来讨论一些细节问题。

无事件导致超时

首先,长期保持连接时不行的。对于服务器和客户端来说,这不是个问题,但我们永远都要记住中间可能存在各式各样配置怪异的网关和代理,它们上面可能有各式各样的超时规则,因此Comet最好设计为定期重连。一般情况下,如果30秒没有任何事件发生,服务器端就应该通知客户端确实没有事件发生,结束掉本次请求,然后重新开始一次新的请求以便继续等待。

那么上述函数签名可否用来返回一个无事件的消息呢?这是显然可以的,我们可以选择返回null表示无事件,或者返回一个EmptyMessage常量,这视乎我们使用class还是struct来定义Message。(甚至,我们还可以做一个名为NoMessageMessage的Message派生类来做这个事情。)

定义发送目标

上述函数签名确实能用来收发消息,但是没指名发给谁。可能有人会说,发送给谁可以在Message类里面通过一个属性来定义啊。但是Wait()方法没有说明接受方是谁,服务器端依然不知道哪些消息应该让你接收。

因此,我们引入Channel的概念,Channel使用其名称来标识,相同名称的就必然是同一个Channel。在发送与接受时,通过名称指定要发送到哪个Channel,这样问题就解决了。此时,函数签名修改如下:

public void Send(string channelName, Message message);
public Message Wait(string channelName);

可靠的消息队列

想象一个可能发生的情况,服务器端向你发送一个消息,你没有成功接收,但是服务器端认为发送了就成功了,消息从队列删除了,然后这个消息就永久丢失掉了。可能有人会强调TCP多么可靠,服务器端发送的消息如果在TCP的层面发生问题了,肯定会引发Socket级别的Exception,这个Exception冒泡上来,服务器端就能截获,从而得知发送失败,然后先不删除队首消息。可是别忘了,中间是可能存在代理的,如果代理成功把消息收回去了,可是代理发送到客户端这一步失败了,服务器端就不一定会发生异常了。

因此,我们需要制定一种策略,来确保下行消息总能发送到客户端。在这里,我们选择了引入逐个ACK的机制,来确认消息的接收。也就是说,服务器端发送给客户端的消息带有一个序号,在客户端收到消息后就将该序号发回给服务器端,已确认它受到了该消息。这时候,函数签名更改如下:

public int Send(string channelName, Message message);
public Message Wait(string channelName, int sequence);

我们使用Wait()接收到的Message中,应该有一个Sequence的属性,标记它的序号。然后,再我们执行下一次Wait()时就将该序号加1的值通过sequence参数传递回去,让服务器知道我们期望下一条消息的编号是这个。例如我们收到Message,其Sequence属性为836,那么下一次调用Wait()的时候就传给服务器837。服务器端此时应该保留了编号为836的Message在对首,如果客户端继续请求836号消息,证明它上次没收到,这次仍然发送836号消息给它;如果客户端请求837号消息,证明它成功收到836号消息的,这次就发送837号消息给它。

如果都不是,那该怎么办?那意味着,这是一个错误的请求,甚至可能是攻击请求,因为正常情况下不应该出现这样的请求的,服务器端可以考虑抛个无关紧要的Exception(不要告诉攻击者你知道他在攻击了),甚至直接给个400 (bad request)的响应代号。

与Wait()类似的,Send()也可以加入ACK机制,只需要将返回类型从void改为int就可以了,这个值就专门用于传递消息编号,实现方式和Wait()是一样的,不过Send()是由客户端保存待发送消息的队列。

小结

到此为止。我们的Web Service就写好了。这就写好了?只有签名没有函数体?是的,复杂的工作留给model去做,Web Service在这里只是相当于一个view,用于将model的接口暴露出来。

在下一次的文章中,我们将开始讨论如何实现服务器端的消息传递机制。如果你对本文章系列感兴趣的话,欢迎订阅我的blog:

2008年2月20日星期三

(Twitter++ == Blogger--)

不知道这个推论是否成立,Junyu曾经说过,“忙碌导致没时间把文字整理为blog,所以把思维碎片发不到Twitter”,现在我也差不多这个样子了。

首先,Twitter上的圈子不错,不用说我发了文章后,还要等各位订阅的来看,然后我写得有深度,大家也要想想如何有深度地回答;我写得没深度,大家想也不想就不留言了。Twitter上不用想,凭直觉,你觉得可以回的tweet可以直接回,重度Twitter用户都如此。在我的Twitter上,有Flypig这样的flooding狂人,有和他一唱一和的Junyu,最近我也开始和他们一起flooding别人了,还把awflasher也拉上来了。至于Yuancheng,他是饭否的人,我拉不动。

其次,如果一些思维的碎片,你在Twitter上发泄出来了,你就不想再整理成文了,因此也有人选择了自动把近期的tweet自动发布到blog的做法,例如hidecloud。不过我暂时还不想让tweet打乱我维护那么久的有深度的Blogger,所以还是不要这样做了。不过无论如何,发到Twitter,自然导致Blogger的减产。但是Twitter一旦变成重度用户就难以回头,所以这对我来说已经很难改变,除非好像Kevin Mitnick那样被判下半辈子不得接触互联网吧。

2008年2月19日星期二

数不尽的 blog 与 podcast

我发现未看的blog和podcast都太多了,已经到了无法处理的阶段了,该怎么办呢?最近大家都在自己的Twitter写到,“我终于把Google Reader给清了”,我不知道自己的什么时候能清掉,哈哈。

为什么如此之多英中字典对同一词条的解释与英英字典不同

随便找一个懂一些英语中国人,或者就说一个英语学习中规中矩的高中生,问问他affair是什么意思,他多半会告诉你“暧昧关系”,或者直接一点,说是“婚外情”。然而实际上,affair通常就用作一般的“事务”,例如“外交事务”之类的,在英英字典中“婚外情”的解释被排到很后的位置。

这里有一个问题,就是英中字典多数不是解释单词,而是翻译单词。通常,把英语单词放在一个常见的上下文中,然后整句翻译为中文,再把上下文剥离,这就得到了与该英文单词对应的中文短语,而这正是英中字典中所给出的结果。然而无论该上下文如何常见,这样得到的都不叫做解释,因为它没有解释原来英文单词的含义,只是把它翻译过来了。

由于大多数中国学生都习惯了这样的字典翻译,习惯了一个英文单词记忆一个对应的中文短语作为解释,从来不知道单词的真实含义,才造成了开头所说的问题。如果大家不要仅限于背一个解释,而真正理解单词的含义,那么问题也就解决了。

2008年2月10日星期日

需要针对平台编译的 Web App

很久之前,我提到过了CSS需要常量的问题。最简单的,定义若干调色板常量,整个CSS就用这些常量去标记颜色,以后要更换调色板也就容易了,只需要更改常量,无需更改逐个CSS属性更新。现在看来,可编程的CSS已经不足够了,在HTML + CSS + JavaScript中,我们都大量地存在信息冗余,而这些冗余的信息不符合DRY原则,手动维护的成本高,所以我们需要一种更高级的语言来自动生成这些冗余信息。

我现在使用Ruby on Rails,不过不是很熟悉。我有同事比较熟悉Ruby on Rails,非Rails的Web布局,他也有自己一套HTML + CSS模板,定制一下然后执行一段Ruby脚本,就build好一个output了。我觉得这是很好的一种做法,因为如果定制的工作是修改HTML与CSS的话,这就涉及大量的手工改动了,那就有可能改漏了或者改错了。

既然已经做到这一步了,不妨想象一下将来自动化操作是否能够更进一步,就是自动生成针对平台的HTML代码。我们用一种真正浏览器无关的语言编写行为与样式,然后服务器端自动根据user-agent来生成对应的JavaScript与CSS。这些JavaScript与CSS都是针对特定的user-agent生成的,所以不会有用于兼容浏览器的冗余信息。同时,因为它们是自动生成的,也就不需要人手维护这些冗余信息,生成它们的源代码也就能够保持DRY原则了。

2008年2月9日星期六

我的 iPhone 终于能当电话用啦!

花了一整晚的时间,从晚上9点开始,搞到凌晨5点,终于把iPhone软破了。

今年过年确实比较爽,Windows Server 2008发布了,然后iPhone原生1.1.2的纯软件破解方式也有人研究出来了。天天就在研究这些,过一个很geek的年,哈哈……

详细说一下破解的过程吧。我的iPhone买回来后,我自己懒得研究激活方法,所以让同事教我jailbreak,jailbreak之后就是1.1.1,我也懒得升级到1.1.2再jailbreak一次了,所以就当一个1.1.1的Touch先用了两个月。不过因为同事是用PC的,所以降级并进入恢复模式后,他不知道Mac上用什么软件把机器踢回正常模式,于是就在PC上用iBrickr完成了此操作。这次我亲自操作,开头在Mac上用jailbreak.jar总是不能成功,后来换到PC上用AppTappInstaller.exe才成功了。

我第一次升级1.1.2,按照iphone.unlock.no的教程,它没有写要装OktoPrep,我也就忘记装了,结果升级到1.1.2才发现不能jailbreak。这时候就降级再来。第二次升级到1.1.2,成功jailbreak,但是机器停留在恢复模式,我以为是jailbreak时没有关掉iTunes的缘故,于是再次降级……其实后来想一下,因为jailbreak.jar重来不能成过把我的iPhone从恢复模式踢出来,这次应该也一样吧。第三次升到1.1.2,jailbreak,然后换到PC把它从恢复模式踢出来,终于成功了,能用而且有信号!

做过一次后,才发现也不是那么难。开头想着破解后就在1.1.1用,懒得升级到1.1.2,结果发现破解后必须升级到1.1.2,否则没有信号,换上移动的SIM卡后还是相当于一只Touch。无奈之下升级1.1.2,结果一晚就做了三次升级,也终于知道升级怎么做。

2008年2月8日星期五

Windows Server 2008 发布了

过年之前就看到x64的版本出现在MSDN Subscription上面了,可惜我准备拿一台x86的机器来试试,所以就选择了继续等。今天终于看到了x86的版本了,现在看时下载,之后就装到服务器上看看效果如何。

我现在比较关注的是,它是否能在安装初始化时成功加载RAID驱动。因为Windows 2003最开始的版本是没有RAID驱动的,如果有RAID设备就必须在安装开始时把驱动软盘插进去,让安装程序从软盘加载驱动。这年头还有软盘?没错!而且是依赖于软盘。到SP出来之后,貌似是补上了RAID驱动,才省去了软盘这一步。如果Windows 2008又变成要软盘的,我就晕倒了……希望不是吧。

2008年2月7日星期四

原来 Vista 对 FAT32 的支持比 XP 要差

自从我开始使用MacBook,我的移动硬盘就从NTFS改为了FAT32。说实在的,对于FAT32这种这么老的格式,我实在没什么信心,但是在PC与Mac之间搬数据也没有第二个选择了。况且,FAT32还不支持4G以上的文件,要把一个DVD镜像搬到另外一台机器就必须分割。

之前我已经试过几次了,硬盘经过OSX写入数据后,在Windows就无法再识别出来。打开磁盘管理器一看,竟然格式成为RAW,连修复的机会也不给我了。这时候我能怎样做?只能在OSX中把几十G数据转存,然后把移动硬盘重新格式化一遍,然后再把数据传存回去。这时候,Windows中又能读了。

于是,我在网上搜索OSX和Windows经移动硬盘交换数据的方案,结果大家都说FAT32就行了,而且兼容得很好。我就很郁闷了,为什么就我遇到问题,从此我也尽量避免硬盘在两个系统之间转来转去,特别是避免从OSX写入数据,因为我觉得是OSX写入了一些数据,导致Windows不能识别该硬盘。

直到最近,我才发现问题的来源。我将移动硬盘从XP的机器上拔下来后插入到Vista的机器中,这次Vista又将它识别为RAW格式了。这时候我就明白了,不是OSX写入的数据有问题,而是Vista对FAT32的支持有问题,竟然XP写入后Vista也会读不出来。

这就真是没办法了,看来保留一个XP还是很有必要的,Vista的兼容性不错,但对一些老设备的支持还是有问题(FAT32算是老设备格式了吧),况且我也不知道对FAT32的支持是否能在SP1修正。

2008年2月6日星期三

Google Social Graph API 体验

Google推出了Social Graph API,允许大家更加便捷地搜索Google抓取到的XFNFOAF数据。

XFN是一种基于XHTML的Microformat,在<a />标签的rel属性中加上各种各样的值来指示目标URL(的拥有者)与本URL(的拥有者)的关系。例如me是指代自己,friend是指代朋友,met是指代见过面,等等。

FOAF则是基于RDF的一个扩展,原本RDF用于描述资源,FOAF则把侧重点放到了描述资源拥有者上来。

Google对Social Graph API的使用方法范例是这样的:假如你新加入到Twitter,没有任何好友给你follow,那么Twitter就可以考虑引入Social Graph API来为你寻找已有的好友。因为你在Twitter注册时输入了自己的URL,Twitter仅需要将该URL题交给Social Graph API查询就好了。或许你提供的URL正是你的blog,上面链接到你好友的blog了,并且也用XFN标记了,那么Social Graph API就能将这些好友的blog返回给Twitter,Twitter发现原来这正是另外一个用户的URL,那就是说该用户就是你blog上链接到的那位朋友了。

这到底是否真的那么有效呢?我马上用自己的Twitter地址测试了一下:

http://socialgraph.apis.google.com/lookup?q=twitter.com/catchen&pretty=true&fme=true&edi=true&edo=true

Social Graph API在我的Twitter页面上,找到了我设置的URL属性,知道那是指向我的另一个页面。那个页面就是我的claimID页了,上面当然不乏XFN标记了的链接,于是它又找到了我的一堆blog。可惜的是,新的Blogger模板中的链接widget无法假如XFN格式,除非你放弃widget改回用全手动编写的HTML,因此Social Graph API无法通过我的那堆blog找到到我的好友。

Update: Twitter把following加上XFN信息了,所以你的following和follower都会被Social Graph API认为是contact关系。不过这又引入了另一个问题,就是Social Graph API总是认为关系是自动双向的,但其实我的一部分follower我并不认识,我也不认为我和他们存在contact关系。

步出感觉安全的区域

这是The Game里面提到的,意思是人总会选择在一个自己觉得安全的区域内徘徊,而拒绝离开这个区域。举个例子,内向的人,或者说不擅长与女性打交道的男人,可能就觉得只有和同类打交道才是安全的,出了这个范围就不知道怎样打交道,关键在于怕被别人拒绝,或者更严重的是遭受羞辱。

事实上,不仅仅社交方面如此,很多方面都会由于这种现象的存在,而限制了个人发展。技术上的依赖,会导致个人的技术跟不上潮流。我是在ASP.NET 2.0发布后才算是真正开始使用ASP.NET的,所以没有什么ASP.NET 1.1的包袱,然而有不少人却因为习惯了ASP.NET 1.1而不太热衷于升级到ASP.NET 2.0来,因为他们已经熟悉了ASP.NET 1.1,那是个感觉相对安稳的环境。现在,ASP.NET MVC出来了,而我手头又没有多少ASP.NET项目要做,我也变得不急于迁移到ASP.NET MVC了,顶多看看别人写的文章,自己没时间动手去实践。

生活上,也差不多,习惯了和谁住,习惯了住在某个城市某个城区,也就觉得只有这样的环境才叫做安稳。而如果真的要获得更多的机会,必须敢于放弃这些让你感觉安全的区域,大胆地走出去,面对可能迎面而来的总总风险甚至是危机,之后才可能获得安全区域内得不到的收获。

2008年2月5日星期二

MacBook 突然充不进电了!

昨晚回到家,把充电器插到拖板上,打开拖板的开关,就开始用电脑了,后来才发现一直处于使用电池的状态。尝试插拔了充电器几次,也没有解决问题,MagSafe上面的灯是不亮的,电池的硬件指示等也没显示在充电,我怀疑充电器坏了,然后感到非常郁闷──怎么早不坏迟不坏,偏要过年前坏!过年啊,有保修的话也没有人帮你修,如果充电器不在保修范围内,想买或许也难买。上网查了一下充电器的价格,80USD,或是600HK$,吓死了,大陆的价格肯定还要贵。

因为已经很晚了,所以就郁闷地睡觉去了。第二天跟布丁抱怨这件事时,他说可以把电池拆下来再装回去试试,他的HP曾经因为电池冻到不能识别而出现类似情况。我就试着拆一下电池,发现果然能充了!原来在北方还有那么多的保养问题需要注意的,不知道MacBook拿到南极是否还能用……

2008年2月4日星期一

退订 blogbus 所有 feed

身边有部分高中同学,就是从高中开始写blog的那种,当时国内最大的BSP是blogbus,所以用的就是blogbus。然而blogbus最近“出了点问题”,也不是最近了,持续了很多个月了,我今天忍无可忍,决定无论是多么熟的朋友,只要是blogbus的,一律退订,在我得知blogbus修正此问题之前也不再订阅任何blogbus的blog。

首先说说这个问题给我造成的麻烦,我在FeedDemon或NetNewsWire中对一篇blogbus的文章标记为已读,然而第二天这篇文章又变成未读,每天我都要手动将所有blogbus的feed手动标记为全部已读。一开始我认识只是blogbus升级过程中造成的小问题,就好像之前Live Space也会时不时这样,所以手动标记一下就好了,结果这样的事情一直持续到今天。最终我确认了一下问题的来源,因为blogbus把文章输出到feed时要在最后追加“相关文章”,而且这些“相关文章”还不停变化,因此Newsgator只能把变更后的文章当作新文章来处理。

在其他的blog平台中,作者有时候也会修改文章,为什么不会导致Newsgator把这当作一篇新的文章?这关键之处就在于blogbus不对文章输出guid,因此变更后的文章无法确认它是不是由一篇已经抓取过的文章更新而来的,就只能当作新文章处理。如果输出了guid,Newsgator一看这个guid已经被抓去过,也就不管了。另外一些feed reader,处理方式可能略有不同,而将同guid不同内容的文章重新标记为未读或标记为已更新,但至少不会把这当作一篇全新的文章。

不标准的feed总是让人很头痛,虽然现在feed的使用越来越普遍了,但是还有很多feed提供者自己没有完全理解feed相关的标准就发布feed了,导致使用该信息源时需要加入各种兼容性设计。

2008年2月1日星期五

怪异的国内 iPhone App 市场

环视国内最大的iPhone社区weiphone,你会发现没有多少人讨论Web App,大多数人关注的都是破解以及客户端应用,然而实际上Apple官方就收录了一大堆第三方Web App,按分类整理好了,其中有些热门的非常好用,另外一些也不错。

在这样一个环境里,国内开发Web App没有任何优势可言,还不如去研究那个还不完整的API然后开发客户端应用呢。况且,Apple这个月就要发布客户端SDK了,API文档就能够得到补全,原本的客户端应用总能修改一下就变得符合官方指引。不过,听说只有官方私钥签名后的应用程序,才能够从iTunes下载安装,估计这还是少数,因此Installer将依然有用,老的应用开发模型也不会消亡。

回到Web App的话题上来,如果要在国内做一个Web App并获得成功,必须很注意iPhone用户的构成。现在有一些iPhone用户,还没有把iPhone破解掉,而把iPhone当作iPod Touch用,那么这些用户就只能当作iPod Touch用户算了。有什么区别?iPod Touch只能在wifi覆盖区域内享用网络服务,而iPhone则能够使用EDGE(或者说至少是GPRS吧),享受网络服务的连续性要更好,总计时长也多得多。开发一个Web App,就必须很注重可访问性,目标用户虽然有设备,可惜根本没有网络覆盖,访问不了,那有什么意义。

我现在在公司和在家里都有wifi覆盖,迟一些回广州肯定也要马上买一个无线路由回家以确保我的设备都能无线使用。然而,有多少人能够有这么好的网络覆盖条件呢,根据我从weiphone观察而来的,其实还有很多人是只有有线网络连接的,要安装软件到iPhone就只能通过iBrickr从数据线传。例如Jim,他就觉得要wifi安装的东西是很不方便的,因为学校的wifi环境不好,家里也没有无线路由。然而我正好相反,我没有装iBrickr,因为我用Mac,又懒得在Mac上装类似解决方案,所以我就严重依赖于wifi。

总之,因为Apple的门槛设置,以及破解的流行,导致了iPhone用户数量虽多,可是他们所处的环境异常复杂,一设计一个Web App成功的捕获不同环境中的用户是非常困难的。