2008年4月16日星期三
飞到美国了
过安检门的时候,首先要看登机牌,我们是自助打印登机牌的,一开始安检人员又看不懂这是否是合法通行证,又要确认一下。因为我们是去美国的,特意要求我们要脱鞋,也就是去其它国家可能不用脱鞋检查啦?那就是要根据机票来决定安检严谨程度啦?这样效率不低才怪呢。现在在登机之前,还有一个液体物品的检查,也就相当于两次安检了。我们到得很早,见到第二次检查的人由队长带着来打登机口前,然后一轮训话,说今天开始严查,所以大家不能随便开包看看问问就行,一定要认真查。结果呢?开包之后,还是很随便看看就行了。
在美国入境时,又是长蛇阵的排队,接着是入境面谈,问了一下来干什么的和要停留多久。开头我在海关申报单选了非business,结果入境官员说,别人付钱让你来的就是business,我说那就business吧。我可从来没想过,作为一个学生,有人出钱让我来开会,这也叫做business。在领到托运行李后,又被人“随机”扔去了过X光检查,真是不幸,不过X光检查的黑人倒好人,看到我是到Microsoft的,就很欢迎的样子,可能最近几天到Microsoft的人太多了,而Microsoft又是Seattle的支柱吧。我的箱子有北京托运来的标记,他就问我北京是不是在紧张准备奥运。
之后就出去了,就这么简单。当然,也有朋友被拦下来的,估计因为他在日本买了个新相机,然后未出关就在那里狂拍照,哈哈。
2008年4月12日星期六
黄金周
明天就飞西雅图了,非常向往,但也非常留恋在北京的不到5天。为什么呢?因为这5天很早就计划好了,从第1天开始你就是倒数着过,每天很清楚记得自己干过什么,因此印象特别深刻。举个例子,同样是在北京,我在百度实习3个月,印象就要浅一些,大概只记得第几周做过什么。这不是因为上班的问题,而是因为我站在第1天向前看就是12周,按周来倒数。如果我在百度的实习是5天,那么我也会清晰记住每天干了什么。
因此,黄金周在于日子倒数着过,而最好的方式就是去旅行。在北京这5天没有怎么出去玩,明天飞到西雅图一定要抓住唯一大家都有空外出的一天,去爽一下!另外期待暑假的德国之旅,如果能去成的话。
2008年4月11日星期五
广州地铁三号线的算术陷阱
这种设计,鼓励的是加独立的列车,而不是加车厢。然而,列车加得越多,加一节车厢的成本也就越高,因为同时要加一节车厢的列车数多了。这就变成了,列车越多,越不鼓励加车厢,越倾向于再加独立的列车。最终,整条线路已经无法再加新的列车时,列出数肯定已经相当多了,这时候加一节车厢的成本就会很大,因此加车厢的可能性反而很低。
我觉得唯一的可能就是,在列车很多的时候选择把短列车重新分组为长列车,多余的机车没所谓,反正机车肯定有冗余的,这样子才能避开这个算术陷阱。
2008年4月2日星期三
救救 Web Developers ,拒绝 IE6 !
为了支持这个campaign,我特意为他们做了一个中文翻译。我本来是想和aw一起做的,不过他们把内容发给我的那个晚上找不到aw,于是有翻译问题就直接找Junyu问了,最后在Junyu的帮忙下把翻译搞定了。我在想是不是应该多做一个粤语翻译,这样会比较好玩,反正也就一个晚上的事情而已。
2008年3月24日星期一
深入理解 ASP.NET 动态控件 (Part 6 - 模板控件)
在之前的文章中,我极力推荐大家使用Repeater和MultiView这类TemplateControl,为什么呢?因为只有这样做,才算是符合MVP或MVC模式。(到底是MVP还是MVC,这视乎你选用什么呈现引擎了。)
虽然我们要动态创建控件,但实际上这部分控件仍然属于View的部分,我们应该尽量采用ASPX的声明性名义来描述这些控件,避免用C#代码来创建控件、设置属性并添加为子控件。就拿最简单的例子来说,创建一个LinkButton,通常我们都需要设置它的ID、Text、OnClick属性/事件,甚至还要设置OnCommand、CommandName、CommandArgument等属性/事件,那就是大概3到5个属性了,用ASPX来声明只需要1~2行代码,而用C#代码则需要写至少5行(把new和Add()也算上的话),由此可见在定义控件这类声明上ASPX比C#代码的可读性要高。
接下来,我们来研究一下TemplateControl是如何工作的,这自然要从如何编写一个TemplateControl讲起。
编写模板控件
在这里,我们假设要编写一个SimpleRepeater控件,自身不支持数据绑定,只有唯一一个名为ItemTemplate的模板,并且就按照Count属性指定的次数重复出现该模板。首先,让我们来定义这两个属性:
public ITemplate ItemTemplate { get; set; }
public int Count { get; set; }
因为ItemTemplate属性不是以键值对的形式在SimpleRepeater的声明中给出的,而是以内嵌一对标签的方式定义的,因此我们需要让解释器去把<ItemTemplate>...</ItemTemplate>中间的内容读取出来,并把解释结果作为ItemTemplate属性的值处理。这时候,我们就需要为SimpleRepeater类加上ParseChildrenAttribute,也就是这样子:
[ParseChildren(true)] public class SimpleRepeater {...}
最后,我们需要重载一下CreateChildControl()方法,把ItemTemplate的内容作为子控件添加到SimpleRepeater之内:
protected override void CreateChildControls()
{
if (ItemTemplate != null)
{
Controls.Clear();
for (int i = 0; i < Count; i++)
{
Control control = new Control();
ItemTemplate.InstantiateIn(control);
Controls.Add(control);
}
}
}
这段代码的用意应该是相当清晰的了,就是循环Count指定那么多次,每次循环创建一个空白的Control,用ItemTemplate.InstantiateIn()方法填充它,最后把它添加到SimpleRepeater.Controls里面,就那么简单。那么这个神秘的InstantiateIn()方法到底是干什么的呢?后面来解释。
编译模板控件
在之前的《深入理解 ASP.NET 动态控件 (Part 2 - 编译过程)》里面,我详细地解释了ASP.NET 2.0的编译模型。在《深入理解 ASP.NET 动态控件 (Part 5 - 编译实验)》中,我们又做了一个动手实验,亲眼看到了ASPX和C#代码是如何编译到一起的。现在让我们来看看当碰上模板控件时,代码会被如何编译吧。我们把上面编写的SimpleRepeater注册到页面上,前缀为ctrl,并且编写如下一段代码:
<ctrl:SimpleRepeater ID="SimpleRepeater1" Count="10" runat="server">
<ItemTemplate>
<asp:Button ID="Button1" Text="Button" runat="server" />
</ItemTemplate>
</ctrl:SimpleRepeater>
然后还是用aspnet_compiler编译一下,并且用Reflector打开编译出来的dll看看。我们可以看到构造SimpleRepeater实例是通过这样一个语句完成的:
return new SimpleRepeater { ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control1)), Count = 10 };
这个语句其实就是一个普通的new语句,并且给ItemTemplate和Count两个属性赋值了,唯一值得关注的就是ItemTemplate的值。ItemTemplate的类型是ITemplate,因此任何实现了ITemplate接口的类都可以复制给它,然而我们却从来没指定过到底要赋值什么类型的实例给它,因为这已经由ASP.NET帮我们想好了,假若我们不指定的话,那就是CompiledTemplateBuilder类型。还是熟悉的Builder模式,它只需要已委托的形式接受一个实例化ITemplate的函数,然后就能返回实例化好的ITemplate控件子树。可能你会问,既然我已经有实例化ITemplate的函数,干什么要先传给你CompiledTemplateBuilder,让你来调用一下,再把实例化好的给我?我自己实例化不好吗?在此,ASP.NET引擎的做法只是为了保持Builder模式的一致性,处处用Builder模式来分离逻辑而已。
那么这个用于实例化ITemplate的函数从哪来呢?在解释器进入到<ItemTemplate>...</ItemTemplate>内部时,它会继续层层构建Builder模式,就如同在整个页面内执行的一样。因此,整个<ItemTemplate>...</ItemTemplate>会被解释器转化为一个函数,它也是通过层层调用内部函数完成自身的控件子树的构建,传递给BuilderTemplateBuilder构造函数的委托正是指向此函数。
因此,模板控件里面的内容将如同模板外的内容一样,被无缝地解释和构建到一起来。
INamingContainer
如果你查看SimpleRepeater输出的HTML代码,你会发现里面有10个<input id="Button1" name="Button1" type="button" value="Button" />。我们都知道,重复的id是不符合标准的,因此我们需要通过INamingContainer把这个问题解决掉。因为重复的控件是ITemplate,所以应该对它加上INamingContainer,然而它的实例编译时自动使用了CompiledTemplateBuilder,我们如何把INamingContainer加上去呢?我们就只能把INamingContainer加到它的父控件上面去。此时,我们需要一个实现了INamingContainer的简单控件:
public class SimpleRepeaterItem : System.Web.UI.Control, System.Web.UI.INamingContainer {}
然后我们把SimpleRepeater.CreateChildControls()方法的这个语句:Control control = new Control(),替换为:SimpleRepeaterItem control = new SimpleRepeaterItem()。这样,ITemplate的容器就变成了一个具有INamingContainer接口的控件,这时候各个Button的客户端id就会自动加上其容器的id作为前缀,因为容器的服务器端ID是自动变好的,所以必然是各不相同的,这样就解决了Button客户端id相同的问题。
通过这个例子,我们了解到了编写模板控件时必须为模板的容器加上INamingContainer,因为模板内的控件ID命名是可能重复的,加上INamingContainer就可以避免它们的客户端id重复。
小结
这次的文章解释了为什么我们应该尽量使用模板控件来实现动态控件,并且也说明了如何编写自己的模板控件,以及模板控件最终是被如何编译为Builder模式的代码的。
2008年3月21日星期五
每个 blog 都会有一堆 draft posts
这估计是因为,如果只是有一点灵感,这是不全面的,很难自圆其说。当几个灵感合并在一起,就能看到概貌了,前因后果清晰了,正方反方都出场了,自然写出来的文章就会显得内容丰富一些。
深入理解 ASP.NET 动态控件 (Part 5 - 编译实验)
这次的文章是一个小小的动手实验,你需要准备好Visual Studio 2005或者Visual Studio 2008,以及最新版本的Reflector。通过这次的实验,你将对ASPX与C#代码如何合并编译为一个dll代码有所理解。
在实验开始之前,首先来一个小问题:如果不允许你使用ASPX,要你完全使用C#代码写一个具备复杂控件树的页面你会怎么写?把声明控件的代码都放在Page_Load里面吗?或者有更好的代码编写方法?先想想这个问题,然后继续往下看。
实验的第一步,也就是在Visual Studio里面创建一个ASP.NET项目,并编写一个简单的ASPX页面。例如下面这个例子:(以下代码仅包括HtmlForm内的主体部分)
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="View1" runat="server">
<div>Please choose either of the followings:</div>
<asp:RadioButton ID="RadioButton1" runat="server" />
<asp:RadioButton ID="RadioButton2" runat="server" />
</asp:View>
<asp:View ID="View2" runat="server">
<div>Please choose any of the following:</div>
<asp:CheckBox ID="CheckBox1" runat="server" />
<asp:CheckBox ID="CheckBox2" runat="server" />
</asp:View>
</asp:MultiView>
在这个例子中,我们构建了一个简单的控件树,同时又不至于过于复杂,确保了编译出来的代码相对简单一些。接下来我们就需要将它编译了,最简单的手动编译方法就是用ASP.NET 2.0自带的aspnet_compiler.exe,这个文件默认会在这个目录中:C:\Windows\Microsoft.NET\Framework\v2.0.50727。你可以使用aspnet_compiler -h来查看完整的帮助,例如编译一个IIS默认站点中的ASP.NET子站点可以使用这样的代码:
aspnet_compiler -v / -p C:\inetpub\wwwroot\site C:\output\site
接下来,我们到输出目录的bin子目录里把dll抓到Reflector里面看看吧。你会看到这个dll里面有三个namespace,分别是-(在Reflector中代表没有namespace)、__ASP、ASP。假设你编译的站点有一个Default.aspx,那么在无namespace的类当中就会有一个_Default的类,对应的就是Default.aspx.cs编译出来的类。大家应该还记得《深入理解 ASP.NET 动态控件 (Part 2 - 编译过程)》里面提到的,直接继承自Page的类是用后台代码编译出来的,_Default类就是这样一个具体例子了。我们打开_Default类来看看,就会发现MultiView1已经是其成员了,为什么呢?MultiView1仅仅在ASPX中声明,没有在C#中声明啊。回头看看《深入理解 ASP.NET 动态控件 (Part 2 - 编译过程)》就能解释了这种现象——Default.aspx.cs是标记为partial的,而在你手动编辑的文件中,这是唯一一个partial,另外一个partial由编译器根据Default.aspx自动生成,编译器解释完Default.aspx后在自动生成的partial中定义了MultiView1,因此两个partial合并编译后,_Default类自然就有了MultiView1这个成员了。
接下来,我们再看看ASP这个namespace下的default_aspx类,这个是ASPX文件继承自上述_Default类后编译的结果,它完整表述了ASPX文件中整个控件树的逻辑,而不仅仅是一个包含一堆成员控件定义的Page派生类。这个类的执行入口是FrameworkInitialize()方法,它通过调用__BuildControlTree()方法来构建控件树。在这个方法里面,你可以看到<!DOCTYPE ...>这样的字符串是被解释为LiteralControl的,LiteralControl在Render()时就会把这段文本原样输出。同时你还可以看到,它调用了另外两个方法,分别用来构建HtmlHead和HtmlForm,这两个方法通过类似的形式继续调用其他方法来构建更深层次的控件。
通过阅读default_aspx类的代码,你已经能够理解ASPX的控件树是如何转化为C#代码的了——采用的正是Builder模式。了解到这一点,这次动手试验的目的也就达到了。如果你看到文章开头的那个问题时,你已经想到了使用Builder模式,那么此时也就验证了你的想法是完全正确的。
下一次的文章将是与TemplateControl相关的,我们将继续动手做一些小实验,敬请期待。欢迎订阅我的blog:
2008年3月14日星期五
在校学生找实习、找工作、了解企业情况等等等等
因为时不时就有低年级的同学跑来问我这类问题,所以我干脆写篇文章好了。
信息获取
最先想到的,也是最重要的,是你想干什么,而不是你父母想你去干什么,或者哪个赚钱之类的。在计算机系里面,你总能碰到一些对这个行业没什么感觉的人,他们会说当年填报志愿的时候根本没什么喜欢不喜欢可言,于是在父母驱使下或者金钱诱惑下就报了计算机系。显然你不想犯这类错误,因此第一步是弄清楚你想要什么,或者说,有什么是你可以选择不要的。世界上没有十全十美的事情,所以一切都是权衡取舍(trade off),这个思想贯穿着计算机体系设计的方方面面,缺乏这种思想肯定会导致你在这个行业里发展受到限制,所以首先你要想好的就是你将来的职业发展取什么而舍什么。
在你先清晰了解自己之后,才有如何了解企业实际情况的问题。如果说第一个问题严重依赖于你个人悟性的话,那么第二个问题就依赖于你的人脉了,至少是你的外向程度和活跃程度。
一个企业,其自身对外的公关肯定只会说好话,就算没有任何夸张成分,也不会让你看到这个企业内部任何的不足。因此,如果你需要知道一些细节,看看这企业是否如你想象中那么好,你就必须认识在这家企业里面的员工,通过他们好好了解一下。如果你是一个在学校里已经很活跃的学生,整天在BBS上板聊,或者参加各种学生社团的活动,那么你肯定有机会认识到不少比你高一两个年级的同学,看看他们里面有没有一些正好在你想去的企业工作的,联系他们了解一下企业的情况,例如工资和各种福利怎么算啊,工作与学习的氛围如何啊,以及他们是否也认为你适合这个企业。
如果有机会多问几个人的话,最好都问问,并且以人生经历和价值取向与你相近的人的说法为主要参考。这是因为,在同一家企业里面,不同的员工看到它不同的方面,并且都带有自己的主观想法,因此得出的评价可能截然不同。这就好像假如你问我北京是否好住,我肯定说不好住,因为我看重的是商业环境,我觉得北京的服务业经常让我失望,因此就这样说。但肯定也有人很喜欢住北京的,他看重其他一些因素,并且觉得我看重的那些因素可以忽略不计。如果你确实很想去一家企业,就应该多找几个员工了解情况,特别是头脑清晰能看清楚自己所在企业优劣势的人。如果你碰到一个已经被洗脑的员工,那么问什么也没用,反正他就只能帮你洗脑……
联系途径
根据往年的经验,现在正是2009年毕业的学生找实习的时候,甚至有2010年毕业的学生也提前开始找实习了。积极的人肯定早已开始四处打听消息,包括各大公司招聘什么实习职位,往年难度如何,转正比例多少,等等。在这里,我就要“曝光”我身边的一些朋友了,大家不要怪我“出卖”朋友了哦,因为谁都知道成功把有才华的人推荐给自己公司的意义所在。
在这里,我主要说的是BGM(Baidu、Google、Microsoft的意思,不是background music),如果你对其他IT企业有兴趣的话,也可以找我帮忙,只要我认识该企业的人,我会充当一下“路由”的角色为你“尽力服务”的。
Baidu
如果分别把BGM比作人的话,Baidu是一个典型的中国年轻人,一点都不张扬。就如同在中国随手抓一个ASP.NET MVP问他,“你是不是很熟悉页面生命周期”,他可能回答道,“懂一点吧,有什么问题你先说说看。”虽然从资源(resources)的级别来说,Baidu是很难和Google、Microsoft去比的,但是开放程度(openness)还是是相当高的,你可以做自己喜欢做的项目,可以获取到项目所需的资源。
想去Baidu的可以找我,或者布丁,先了解一下。当然,如果你来找我的话,最好你也能说服我为什么值得推荐你。我主要做Web前端开发的,而布丁则是做后端开发的,所以如果你想申请这两个方面的职位,可以直接找我们了解Baidu现在所采用的技术或者流程。如果是其他职位,也可以帮你联系其他同事问问。完整的实习职位列表,请看这里。
Google是一个典型的美国年轻人,你不问他也会很主动地把他的优势展示给你看。在我看来,Google最大的诱惑不在于公司内的“饮食娱乐”项目(虽然这些也很吸引),而是作为一般的中国员工也可以随便跑到美国总部去,顺道参加美国举行的一些会议或者讲座。看看Junyu同学就跑到Austin去开SXSW了。
如果想去Google的话,去找Junyu问问吧。别告诉我找不到他的联系方式,我通常只链接别人的blog,因为该URL就是个人的标识(即使抛开OpenID不谈),拿着这个URL你手上就已经掌握了搜索他联系方式的一切资料。Junyu喜欢把人拐卖进Google里,想了解招聘职位什么的,找他就好了。
Microsoft
Microsoft是个稳重的美国中年男人,Google所晒的东西,Microsoft会暗自想着“我当年又不是没晒过,现在我已经不屑于晒了”。很成熟的流程,健全的管理体制,都很好地说明了这一点。
貌似我没有很熟的朋友在Microsoft,不算太熟那些又不敢晒出来,所以想找人推荐的话还是联系我,然后我再帮你联系吧。想了解的话,可以找Jeffrey或者Dflying这两位老员工加现任MVP,他们现在不再是员工了,可能观点会更加客观一些。
其他事情
据我所知,很多人也是准备找实习了,才想到要写简历的。这是一项需要创意的工作,在此我提供我的简历和Junyu的简历给大家作为参考。虽然我们的网站下方都写着Creative Commons License,不过不建议你直接使用别人设计好的模板。特别是,假如你想申请Web Developer/Designer类别的职位的话,你可不能够错过这个机会好好展示你的设计风格和编码艺术。
最后,祝大家在08年里都能找到自己喜欢的工作学习环境,好好享受生活。
2008年3月8日星期六
使用 .NET 实现 Ajax 长连接 (Part 2 - Mutex Wait & Signal)
在上一次的文章中,我们说到了如何设计一个ASP.NET Web Service来处理长连接请求。很多人对此就提出了问题,如何hold住请求让它30秒不断开了?这其实很简单,只需要Sleep()一下就可以了:
Thread.Sleep(30 * 1000);
然而问题是,我们不是要等30秒然后看看是否有事件需要返回,而是在这30秒内随时有事件随时返回。因此,我们需要一套机制来在等待的过程中检查是否有事件发生了。
Monitor模型
在.NET里面,大家最熟悉的线程同步模型应该就是Monitor模型了。没听说过?就是C#的那个lock关键字,实际上它编译出来就是一对Monitor.Enter()和Monitor.Exit()。
通过lock命令,我们可以针对一个对象创建一个临界区,代码执行到临界区入口时必须获取到该对象的锁才能执行下去,并且在临界区的出口释放该锁。然而这种模型不太适用于解决我们的问题,因为我们需要等待一个事件,如果使用lock来等待的话,那就是说要先在Web Service外部把对象锁上,然后等事件触发了就解锁,这时候Web Service才顺利进入临界区域。
事实上,要进行这类型的阻塞,还有一个更好的选择,那就是Mutex。
Mutex模型
Mutex,也就是mutual exclusive的缩写,“互斥”的意思。Mutex是如何运作的?这有点像是银行的排队叫号系统,所有等待服务的人都坐在大厅里等候(wait)被叫,当一个服务窗口空闲时它就会发出一个信号(signal)来通知下一位等候服务的人。总之,所有执行wait指令的线程都在等候,而每一个signal能够让一个线程结束等候继续执行。
在.NET里面,wait和signal这两个操作分别对应Mutex.WaitOne()和Mutex.ReleaseMutex()这两个方法。我们可以让Web Service的线程使用Mutex.WaitOne()进入等候状态,而在事件发生时使用Mutex.ReleaseMutex()来通知Web Service线程。因为必须在Mutex.ReleaseMutex()发生后Mutex.WaitOne()才可能继续执行下去,因此能够执行下去就证明必然有事件发生了并且调用了Mutex.ReleaseMutext(),这时候就可以放心地去读取事件消息了。
简单示例
在选定使用Mutex模型后,我们来编写一个简单的示例。首先,我们要在WebService派生类内定义一个Mutex,还有一个代表消息的字符串。
Mutex mutex = new Mutex();
string message;
然后,我们定义两个WebMethod。为了把问题简单化,我们选用上一篇文章中开头所说的两个函数签名,也就说只能在一个Web Service内自己发自己收,没有发送目标的概念,也没有超时的概念,还没有可靠性设计。同时,我们将Message类型替换为普通字符串,以便于我们测试。
我们先编写发送消息的函数:
public void Send(string message) {
this.message = message;
this.mutex.ReleaseMutex();
}
在这个发送函数里,首先我们把消息放进了类内全局的变量中,然后让全局的Mutex类释放一个signal。这时候,如果有线程在等待,它可以马上执行下去。如果此时没有线程在等待,那么下一个wait的线程执行到该阻塞的地方就能够不受阻塞继续执行下去。
现在我们来编写接收消息的函数:
public string Wait() {
this.mutex.WaitOne();
return this.message;
}
接收函数一开始就进入wait状态。在得到signal后,需要做的事情就是把全局的消息返回给客户端。
亲身体验
最后,我们可以通过ASP.NET Web Service本身支持的Web测试界面来测试一下我们的代码。我们开两个浏览器窗口,一个进入Send()调用,一个进入Wait()调用。然后我们按照如下方法来测试:
- 首先执行Send("Hello"),然后执行Wait()。这时候你可以马上看到"Hello"。
- 首先执行Wait(),让它等待返回,这时候执行Send("Hello")。随后你可以看到Wait()那段返回"Hello"了。
- 按如下顺序执行:Send("Hello");Wait();Send("World");Wait();
- 按如下顺序执行:Send("Hello");Send("World");Wait();Wait();
- 按如下顺序执行:Wait();Wait();Send("Hello");Send("World");
- 按如下顺序执行:Wait();Send("Hello");Wait();Send("World");
你会发现这样一些奇怪的结果:第3个测试返回的是"World"和"World"。第5个测试先返回"Hello"的并不一定是先执行的那个Wait()线程。后者在某些情况下不是什么问题,特别是长连接中一般之后一个Wait()线程在等待中,所以我们可以不管。而前者,则是因为没有消息队列所造成的,我们只有长度为1的消息窗口,所以只能缓存最后一个消息。这个问题我们将在下一篇文章中解决。
小结
在本文中,我们看到了不同的线程同步模型的差异。Monitor模型的lock本质上是一个Semaphore,也就是一个不能连续signal的Mutex,一个signal发出去后必须被一个wait接收了才能进行下一次的signal。同时,Semaphore也限制了signal和wait必须在同一个线程内成对执行,而Mutex则没有此限制。虽然.NET是针对Monitor模型优化的,但在我们的需求当中,只能通过Mutex模型来解决。
接着,我们便写了一个小小的消协发送与接收函数,实现了我们想要的阻塞式Web Service。同时我们也看到了没有消息队列造成的问题,因此确定接下来我们要做一个消息队列。如果你想知道消息队列如何编写,欢迎订阅我的blog:
2008年3月4日星期二
拿到了美国签证
最有趣的地方是,原来驻广州的是美国总领馆,而驻北京北京的只是大使馆,移民业务都在广州处理。整个广州大使馆都是说广东话的,除非看到你的签证签发地是非广东话地区,那就会说普通话或者英语。在移民大厅内,新移民在广东话的带读下进行宣誓,非常好玩。
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--)
首先,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
为什么如此之多英中字典对同一词条的解释与英英字典不同
随便找一个懂一些英语中国人,或者就说一个英语学习中规中矩的高中生,问问他affair是什么意思,他多半会告诉你“暧昧关系”,或者直接一点,说是“婚外情”。然而实际上,affair通常就用作一般的“事务”,例如“外交事务”之类的,在英英字典中“婚外情”的解释被排到很后的位置。
这里有一个问题,就是英中字典多数不是解释单词,而是翻译单词。通常,把英语单词放在一个常见的上下文中,然后整句翻译为中文,再把上下文剥离,这就得到了与该英文单词对应的中文短语,而这正是英中字典中所给出的结果。然而无论该上下文如何常见,这样得到的都不叫做解释,因为它没有解释原来英文单词的含义,只是把它翻译过来了。
由于大多数中国学生都习惯了这样的字典翻译,习惯了一个英文单词记忆一个对应的中文短语作为解释,从来不知道单词的真实含义,才造成了开头所说的问题。如果大家不要仅限于背一个解释,而真正理解单词的含义,那么问题也就解决了。
2008年2月10日星期日
需要针对平台编译的 Web App
我现在使用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 终于能当电话用啦!
今年过年确实比较爽,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 发布了
我现在比较关注的是,它是否能在安装初始化时成功加载RAID驱动。因为Windows 2003最开始的版本是没有RAID驱动的,如果有RAID设备就必须在安装开始时把驱动软盘插进去,让安装程序从软盘加载驱动。这年头还有软盘?没错!而且是依赖于软盘。到SP出来之后,貌似是补上了RAID驱动,才省去了软盘这一步。如果Windows 2008又变成要软盘的,我就晕倒了……希望不是吧。
2008年2月7日星期四
原来 Vista 对 FAT32 的支持比 XP 要差
之前我已经试过几次了,硬盘经过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抓取到的XFN与FOAF数据。
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关系。