在所里碰到刚从美国读完博士回来的同事,因为他们刚离开博士生的阶段,比较有一些自己较独特的想法,我就问他:「如果你讲这个问题,准备要贡献什么?」……
一、研究生与大学生的区别
首先跟大家说明一下研究生和大学生的区别。大学生基本上是来接受学问、接受知识的,然而不管是对于硕士时期或是博士时期的研究而言,都应该准备要开始制造新的知识,我们在美国得到博士学位时都会领到看不懂的毕业证书,在一个偶然的机会下,我问了一位懂拉丁文的人,上面的内容为何?他告诉我:「里头写的是恭喜你对人类的知识有所创新,因此授予你这个学位。」在中国原本并没有博硕士的学历,但是在西方他们原来的用意是,恭贺你已经对人类普遍的知识有所创新,这个创新或大或小,都是对于普遍的知识有所贡献。这个创新不会因为你做本土与否而有所不同,所以第一个我们必须要很用心、很深刻的思考,大学生和研究生是不同的。
(一)选择自己的问题取向,学会创新
你一旦是研究生,你就已经进入另一个阶段,不只是要完全乐在其中,更要从而接受各种有趣的知识,进入制造知识的阶段,也就是说你的论文应该有所创新。由接受知识到创造知识,是身为一个研究生最大的特色,不仅如此,还要体认自己不再是个容器,等着老师把某些东西倒在茶杯里,而是要开始逐步发展和开发自己。做为研究生不再是对于各种新奇的课照单全收,而是要重视问题取向的安排,就是在硕士或博士的阶段里面,所有的精力、所有修课以及读的书里面都应该要有一个关注的焦点,而不能像大学那般漫无目标。大学生时代是因为你要尽量开创自己接受任何东西,但是到了硕士生和博士生,有一个最终的目的,就是要完成论文,那篇论文是你个人所有武功的总集合,所以这时候必须要有个问题取向的学习。
(二)尝试跨领域研究,主动学习
提出一个重要的问题,跨越一个重要的领域,将决定你未来的成败。我也在台大和清华教了十几年的课,我常常跟学生讲,选对一个领域和选对一个问题是成败的关键,而你自己本身必须是带着问题来探究无限的学问世界,因为你不再像大学时代一样泛滥无所归。所以这段时间内,必须选定一个有兴趣与关注的主题为出发点,来探究这些知识,产生有机的循环。由于你是自发性的对这个问题产生好奇和兴趣,所以你的态度和大学部的学生是截然不同的,你慢慢从被动的接受者变成是一个主动的探索者,并学会悠游在这学术的领域。
我举一个例子,我们的中央研究院院长李远哲先生,得了诺贝尔奖。他曾经在中研院的周报写过几篇文章,在他的言论集里面,或许各位也可以看到,他反复提到他的故事。他是因为读了一个叫做马亨教授的教科书而去美国柏克莱大学念书,去了以后才发现,这个老师只给他一张支票,跟他说你要花钱你尽量用,但是从来不教他任何东西。可是隔壁那个教授,老师教很多,而且每天学生都是跟着老师学习。他有一次就跟那个老师抱怨:「那你为什么不教我点东西呢?」那个老师就说:「如果我知道结果,那我要你来这边念书做什么?我就是因为不知道,所以要我们共同探索一个问题、一个未知的领域。」他说其实这两种教法都有用处,但是他自己从这个什么都不教他,永远碰到他只问他「有没有什么新发现」的老师身上,得到很大的成长。所以这两方面都各自蕴含深层的道理,没有所谓的好坏,但是最好的方式就是将这两个方式结合起来。我为什么讲这个故事呢?就是强调在这个阶段,学习是一种「 self-help 」,并且是在老师的引导下学习「 self-help 」,而不能再像大学时代般,都是纯粹用听的,这个阶段的学习要基于对研究问题的好奇和兴趣,要带着一颗热忱的心来探索这个领域。
然而研究生另外一个重要的阶段就是 Learn how to learn ,不只是学习而已,而是学习如何学习,不再是要去买一件很漂亮的衣服,而是要学习拿起那一根针,学会绣出一件漂亮的衣服,慢慢学习把目标放在一个标准上,而这一个标准就是你将来要完成硕士或博士论文。如果你到西方一流的大学去读书,你会觉得我这一篇论文可能要和全世界做同一件问题的人相比较。我想即使在***也应该要有这样的心情,你的标准不能单单只是放在旁边几个人而已,而应该是要放在领域的普遍人里面。你这篇文章要有新的东西,才算达到的标准,也才符合到我们刚刚讲到那张拉丁文的博士证书上面所讲的,有所贡献与创新。
二、一个老师怎么训练研究生
第二个,身为老师你要怎么训练研究生。我认为人文科学和社会科学的训练,哪怕是自然科学的训练,到研究生阶段应该更像师徒制,所以来自个人和老师、个人和同侪间密切的互动和学习是非常重要的,跟大学部坐在那边单纯听课,听完就走人是不一样的,相较之下你的生活应该要和你所追求的知识与解答相结合,并且你往后的生活应该或多或少都和这个探索有相关。
(一)善用与老师的伙伴关系,不断 Research
我常说英文 research 这个字非常有意义, search 是寻找,而 research 是再寻找,所以每个人都要 research ,不断的一遍一遍再寻找,并进而使你的生活和学习成为一体。中国近代兵学大师蒋百里在他的兵学书中曾说:「生活条件要跟战斗条件一致,近代欧洲凡生活与战斗条件一致者强,凡生活与战斗条件不一致者弱。」我就是藉由这个来说明研究生的生活,你的生活条件与你的战斗条件要一致,你的生活是跟着老师与同学共同成长的,当中你所听到的每一句话,都可能带给你无限的启发。
回想当时我在美国念书的研究生生活,只要随便在楼梯口碰到任何一个人,他都有办法帮忙解答你语言上的困难,不管是英文、拉丁文、德文、希腊文 …… 等。所以能帮助解决问题的不单只是你的老师,还包括所有同学以及学习团体。你的学习是跟生活合在一起的。当我看到有学生呈现被动或是懈怠的时候,我就会用毛泽东的「革命不是请客吃饭!」来跟他讲:「作研究生不是请客吃饭。」
(二)藉由大量阅读和老师提点,进入研究领域
怎样进入一个领域最好,我个人觉得只有两条路,其中一条就是让他不停的念书、不停的报告,这是进入一个陌生的领域最快,又最方便的方法,到最后不知不觉学生就会知道这个领域有些什么,我们在不停念书的时候常常可能会沉溺在细节里不能自拔,进而失去全景,导致见树不见林,或是被那几句英文困住,而忘记全局在讲什么。藉由学生的报告,老师可以讲述或是厘清其中的精华内容,经由老师几句提点,就会慢慢打通任督二脉,逐渐发展一种自发学习的能力,同时也知道碰到问题可以看哪些东西。就像是我在美国念书的时候,我修过一些我完全没有背景知识的国家的历史,所以我就不停的念书、不停***着自己吸收,而老师也只是不停的开书目,运用这样的方式慢慢训练,有一天我不再研究它时,我发现自己仍然有自我生产及蓄发的能力,因为我知道这个学问大概是什么样的轮廓,碰到问题也有能力可以去查询相关的资料。所以努力让自己的学习产生自发的延展性是很重要的。
(三)循序渐进地练习论文写作
到了硕士或博士最重要的一件事,是完成一篇学位论文,而不管是硕士或博士论文,其规模都远比你从小学以来所受的教育、所要写的东西都还要长得多,虽然我不知道教育方面的论文情况是如何,但是史学的论文都要写二、三十万字,不然就是十几二十万字。写这么大的一个篇幅,如何才能有条不紊、条理清楚,并把整体架构组织得通畅可读?首先,必须要从一千字、五千字、一万字循序渐进的训练,先从少的慢慢写成多的,而且要在很短的时间内训练到可以从一万字写到十万字。这么大规模的论文谁都写得出来,问题是写得好不好,因为这么大规模的写作,有这么许多的脚注,还要注意首尾相映,使论述一体成型,而不是散落一地的铜钱;是一间大礼堂,而不是一间小小分割的阁楼。为了完成一个大的、完整的、有机的架构模型,必须要从小规模的篇幅慢慢练习,这是一个最有效的办法。
因为受计算机的影响,我发现很多学生写文章能力都大幅下降。写论文时很重要的一点是,文笔一定要清楚,不要花俏、不必漂亮,「清楚」是最高指导原则,经过慢慢练习会使你的文笔跟思考产生一致的连贯性。我常跟学生讲不必写的花俏,不必展现你散文的才能,因为这是学术论文,所以关键在于要写得非常清楚,如果有好的文笔当然更棒,但那是可遇不可求的,文彩像个人的生命一样,英文叫 style , style 本身就像个人一样带有一点点天生。因此最重要的还是把内容陈述清楚,从一万字到最后十万字的东西,都要架构井然、论述清楚、文笔清晰。
我在念书的时候,有一位欧洲史、英国史的大师 Lawrence Stone ,他目前已经过世了,曾经有一本书访问十位最了不起的史学家,我记得他在访问中说了一句非常吸引人注意的话,他说他英文文笔相当好,所以他一辈子没有被退过稿。因此文笔清楚或是文笔好,对于将来文章可被接受的程度有举足轻重的地位。内容非常重要,有好的表达工具更是具有加分的作用,但是这里不是讲究漂亮的 style ,而是论述清楚。
三、研究生如何训练自己
(一)尝试接受挑战,勇于克服
研究生如何训练自己?就是每天、每周或每个月给自己一个挑战,要每隔一段时间就给自己一个挑战,挑战一个你做不到的东西,你不一定要求自己每次都能顺利克服那个挑战,但是要努力去尝试。我在我求学的生涯中,碰到太多聪明但却一无所成的人,因为他们很容易困在自己的障碍里面,举例来说,我在普林斯顿大学碰到一个很聪明的人,他就是没办法克服他给自己的挑战,他就总是东看西看,虽然我也有这个毛病,可是我会定期给我自己一个挑战,例如:我会告诉自己,在某一个期限内,无论如何一定要把这三行字改掉,或是这个礼拜一定要把这篇草稿写完,虽然我仍然常常写不完,但是有这个挑战跟没这个挑战是不一样的,因为我挑战三次总会完成一次,完成一次就够了,就足以表示克服了自己,如果觉得每一个礼拜的挑战,可行性太低,可以把时间延长为一个月的挑战,去挑战原来的你,不一定能做到的事情。不过也要切记,硕士生是刚开始进入这一个领域的新手,如果一开始问题太小,或是问题大到不能控制,都会造成以后研究的困难。
(二)论文的写作是个训练过程,不能苛求完成精典之作
各位要记得我以前的老师所说的一句话:「硕士跟博士是一个训练的过程,硕士跟博士不是写经典之作的过程。」我看过很多人,包括我的亲戚朋友们,他之所以没有办法好好的完成硕士论文,或是博士论文,就是因为他把它当成在写经典之作的过程,虽然事实上,很多人一生最好的作品就是硕士论文或博士论文,因为之后的时间很难再有三年或六年的时间,沉浸在一个主题里反复的耕耘,当你做教授的时候,像我今天被行政缠身,你不再有充裕的时间好好探究一个问题,尤其做教授还要指导学生、上课,因此非常的忙碌,所以他一生最集中又精华的时间,当然就是他写博士、或是硕士论文的时候,而那一本成为他一生中最重要的著作也就一点都不奇怪了。
不一定要刻意强求,要有这是一个训练过程的信念,应该清楚知道从哪里开始,也要知道从哪里放手,不要无限的追下去。当然我不是否认这个过程的重要性,只是要调整自己的心态,把论文的完成当成一个目标,不要成为是一种的心理障碍或是心理负担。这方面有太多的例子了,我在普林斯顿大学念书的时候,那边旧书摊有一位非常博学多文的旧书店老板,我常常赞叹的对他说:「你为什么不要在大学做教授。」他说:「因为那篇博士论文没有写完。」原因在于他把那个博士论文当成要写一本经典,那当然永远写不完。如果真能写成经典那是最好,就像美丽新境界那部电影的男主角 John Nash 一样,一生最大的贡献就是博士那二十几页的论文,不过切记不要把那个当作是目标,因为那是自然而然形成的,应该要坚定的告诉自己,所要完成的是一份结构严谨、论述清楚与言之有物的论文,不要一开始就期待它是经典之作。如果你期待它是经典之作,你可能会变成我所看到的那位旧书摊的老板,至于我为什么知道他有那么多学问,是因为那时候我在找一本书,但它并没有在旧书店里面,不过他告诉我:「还有很多本都跟他不相上下。」后来我对那个领域稍稍懂了之后,证明确实如他所建议的那般。一个旧书店的老板精熟每一本书,可是他就是永远无法完成,他梦幻般的学位论文,因为他不知道要在哪里放手,这一切都只成为空谈。
(三)论文的正式写作
1. 学习有所取舍
到了写论文的时候,要能取也要能舍,因为现在信息爆炸,可以看的书太多,所以一定要建构一个属于自己的知识树,首先,要有一棵自己的知识树,才能在那棵树挂相关的东西,但千万不要不断的挂不相关的东西,而且要慢慢的舍掉一些挂不上去的东西,再随着你的问题跟关心的领域,让这棵知识树有主干和枝叶。然而这棵知识树要如何形成?第一步你必须对所关心的领域中,有用的书籍或是数据非常熟悉。
2. 形成你的知识树
我昨天还请教林毓生院士,他今年已经七十几岁了,我告诉他我今天要来作演讲,就问他:「你如果讲这个题目你要怎么讲?」他说:「只有一点,就是那重要的五、六本书要读好几遍。」因为林毓生先生是海耶克,还有几位近代思想大师在芝加哥大学的学生,他们受的训练中很重要的一部份是精读原典。这句话很有道理,虽然你不可能只读那几本重要的书,但是那五、六本书将逐渐形成你知识树的主干,此后的东西要挂在上面,都可以参照这一个架构,然后把不相干的东西暂放一边。生也有涯,知也无涯,你不可能读遍天下所有的好书,所以要学习取舍,了解自己无法看遍所有有兴趣的书,而且一但看遍所有有兴趣的书,很可能就会落得普林斯顿街上的那位旧书店的老板一般,因为阅读太多不是自己所关心的领域的知识,它对于你来说只是一地的散钱。
3. 掌握工具
在这个阶段一定要掌握语文与合适的工具。要有一个外语可以非常流畅的阅读,要有另外一个语文至少可以看得懂文章的标题,能学更多当然更好,但是至少要有一个语文,不管是英文、日文、法文 …… 等,一定要有一个语文能够非常流畅的阅读相关书籍,这是起码的前提。一旦这个工具没有了,你的视野就会因此大受限制,因为语文就如同是一扇天窗,没有这个天窗你这房间就封闭住了。为什么你要看得懂标题?因为这样才不会有重要的文章而你不知道,如果你连标题都看不懂,你就不知道如何找人来帮你或是自己查相关的数据。其它的工具,不管是统计或是其它的任何工具,你也一定要多掌握,因为你将来没有时间再把这样的工具学会。
4. 突破学科间的界线
应该要把跨学科的学习当作是一件很重要的事,但是跨学科涉及到的东西必须要对你这棵知识树有帮助,要学会到别的领域稍微偷打几枪,到别的领域去摄取一些概念,对于本身关心的问题产生另一种不同的启发,可是不要泛滥无所归。为什么要去偷打那几枪?近几十年来,人们发现不管是科学或人文,最有创新的部份是发生在学科交会的地方。为什么会如此?因为我们现在的所有学科大部分都在西方十九世纪形成的,而中国再把它转借过来。十九世纪形成这些知识学科的划分的时候,很多都带有那个时代的思想跟学术背景,比如说,中研院的李院长的专长就是物理化学,他之所以得诺贝尔奖就是他在物理和化学的交界处做工作。像诺贝尔经济奖,这二十年来所颁的奖,如果在传统的经济学奖来看就是旁门走道,古典经济学岂会有这些东西,甚至心理学家也得诺贝尔经济奖,连 John Nash 这位数学家也得诺贝尔经济奖,为什么?因为他们都在学科的交界上,学科跟学科、平台跟平台的交界之处有所突破。在平台本身、在学科原本最核心的地方已经 search 太多次了,因此不一定能有很大的创新,所以为什么跨领域学习是一件很重要的事情。
常常一篇硕士论文或博士论文最重要、最关键的,是那一个统摄性的重要概念,而通常你在本学科里面抓不到,是因为你已经泡在这个学科里面太久了,你已经拿着手电筒在这个小仓库里面照来照去照太久了,而忘了还有别的东西可以更好解释你这些材料的现象,不过这些东西可遇而不可求。 John Nash 这一位数学家为什么会得诺贝尔数学奖?为什么他在赛局理论的博士论文,会在数十年之后得诺贝尔经济奖?因为他在大学时代上经济学导论的课,所以他认为数学可以用在经济方面来思考,而这个东西在一开始,他也没有想到会有这么大的用处。他是在数学和经济学的知识交界之处做突破。有时候在经济学这一个部分没有大关系,在数学的这一个部分也没有大关系,不过两个加在一起,火花就会蹦出来。
5. 论文题目要有延展性
对一个硕士生或博士生来说,如果选错了题目,就是失败,题目选对了,还有百分之七十胜利的机会。这个问题值得研一、博一的学生好好思考。你的第一年其实就是要花在这上面,你要不断的跟老师商量寻找一个有意义、有延展性的问题,而且不要太难。我在国科会当过人文处长,当我离开的时候,每次就有七千件申请案,就有一万四千个袋子,就要送给一万四千个教授审查。我当然不可能看那么多,可是我有个重要的任务,就是要看申诉。有些申诉者认为:「我的研究计划很好,我的著作很好,所以我来申诉。」申诉通过的大概只有百分之十,那么我的责任就是在百分之九十未通过的案子正式判决前,再拿来看一看。有几个印象最深常常被拿出来讨论的,就是这个题目不必再做了、这个题目本身没有发展性,所以使我更加确认选对一个有意义、有延展性、可控制、可以经营的题目是非常重要的。
我的学生常常选非常难的题目,我说你千万不要这样,因为没有人会仔细去看你研究的困难度,对于难的题目你要花更多的时间阅读史料,才能得到一点点东西;要挤很多东西,才能筛选出一点点内容,所以你最好选择一个难易适中的题目。
我写过好几本书,我认为我对每一本书的花的心力都是一样,虽然我写任何东西我都不满意,但是在过程中我都绞尽脑汁希望把他写好。目前为止很多人认为我最好的书,是我二十几岁刚到史语所那一年所写的那本书。我在那本书花的时间并不长,那本书的大部分的稿子,是我和许添明老师同时在当兵的军营里面写的,而且还是用我以前旧的笔记写的。大陆这些年有许多出版社,反复要求出版我以前的书,尤其是这一本,我说:「不行。」因为我用的是我以前的读书笔记,我怕引文有错字,因为在军队营区里面随时都要出操、随时就要集合,手边又没有书,怎么可能好好的去核对呢?而如果要我重新校正一遍,又因为引用太多书,实在没有力气校正。
为什么举这个例子呢?我后来想一想,那本书之所以比较好,可能是因为那个题目可延展性大,那个题目波澜起伏的可能性大。很多人都认为,我最好的书应该是剑桥大学出的那一本,不过我认为我最好的书一定是用中文写的,因为这个语文我能掌握,英文我没办法掌握得出神入化。读、写任何语文一定要练习到你能带着三分随意,那时候你才可以说对于这一个语文完全理解与精熟,如果你还无法达到三分的随意,就表示你还在摸索。
回到我刚刚讲的,其实每一本书、每一篇论文我都很想把它写好。但是有些东西没办法写好,为什么?因为一开始选择的题目不够好。因此唯有选定题目以后,你的所有训练跟努力才有价值。我在这里建议大家,选题的工作要尽早做,所选的题目所要处理的材料最好要集中,不要太分散,因为硕士生可能只有三年、博士生可能只有五年,如果你的材料太不集中,读书或看数据可能就要花掉你大部分的时间,让你没有余力思考。而且这个题目要适合你的性向,如果你不会统计学或讨厌数字,但却选了一个全都要靠统计的论文,那是不可能做得好。
6. 养成遵照学术格式的写作习惯
另一个最基本的训练,就是平时不管你写一万字、三万字、五万字都要养成遵照学术规范的习惯,要让他自然天成,就是说你论文的脚注、格式,在一开始进入研究生的阶段就要培养成为你生命中的一个部份,如果这个习惯没有养成,人家就会觉得这个论文不严谨,之后修改也要花很多时间,因为你的论文规模很大,可能几百页,如果一开始弄错了,后来再重头改到尾,一定很耗时费力,因此要在一开始就养成习惯,因为我们是在写论文而不是在写散文,哪一个逗点应该在哪里、哪一个书名号该在哪里、哪一个地方要用引号、哪一个要什么标点符号,都有一定的规定,用中文写还好,用英文有一大堆简称。在 1960 年代***知识还很封闭的时候,有一个人从美国回来就说:「美国有个不得了的情形,因为有一个人非常不得了。」有人问他为什么不得了,他说:「因为这个人的作品到处被引用。」他的名字就叫 ibid 。所谓 ibid 就是同前作者,这个字是从拉丁文发展出来的,拉丁文有一大堆简称,像 et. al. 就是两人共同编的。英文有一本 The Chicago Manual of Style 就是专门说明这一些写作规范。各位要尽早学会中英文的写作规范,慢慢练习,最后随性下笔,就能写出符合规范的文章。
7. 善用图书馆
图书馆应该是研究生阶段最重要的地方,不必读每一本书,可是要知道有哪些书。我记得我做学生时,新进的书都会放在图书馆的墙上,而身为学生最重要的事情,就是要把书名看一看。在某些程度上知道书皮就够了,但是这仍和打计算机是不一样的,你要实际上熟悉一下那本书,摸一下,看一眼目录。我知道现在从计算机就可以查到书名,可是我还是非常珍惜这种定期去 browse 新到的书的感觉,或去看看相关领域的书长成什么样子。中研院有一位院士是哈佛大学信息教授,他告诉我他在创造力最高峰的时候,每个礼拜都到他们信息系图书室里,翻阅重要的信息期刊。所以图书馆应该是身为研究生的人们,最熟悉的地方。不过切记不重要的不要花时间去看,你们生活在信息泛滥的时代,跟我生长在信息贫乏的时代是不同的,所以生长在这一个时代的你,要能有所取舍。我常常看我的学生引用一些三流的论文,却引得津津有味,我都替他感到难过,因为我强调要读有用、有价值的东西。
8. 留下时间,精致思考
还要记得给自己保留一些思考的时间。一篇论文能不能出神入化、能不能引人入胜,很重要的是在现象之上作概念性的思考,但我不是说一定要走理论的路线,而是提醒大家要在一般的层次再提升两三步, conceptualize 你所看到的东西。真切去了解,你所看到的东西是什么?整体意义是什么?整体的轮廓是什么?千万不要被枝节淹没,虽然枝节是你最重要的开始,但是你一天总也要留一些时间好好思考、慢慢沉淀。 conceptualize 是一种非常难教的东西,我记得我念书时,有位老师信誓旦旦说要开一门课,教学生如何 conceptualize ,可是从来都没开成,因为这非常难教。我要提醒的是,在被很多材料和枝节淹没的时候,要适时跳出来想一想,所看到的东西有哪些意义?这个意义有没有广泛连结到更大层面的知识价值。
傅斯年先生来到***以后,同时担任中央研究院历史语言研究所的所长及台大的校长。台大有个傅钟每小时钟声有二十一响、敲二十一次。以前有一个人,写了一本书叫《钟声二十一响》,当时很轰动。他当时对这二十一响解释是说:因为台大的学生都很好,所以二十一响是欢迎国家元首二十一响的礼炮。不久前我发现台大在每一个重要的古迹下面竖一个铜牌,我仔细看看傅钟下的解释,才知道原来是因为傅斯年当台大校长的时候,曾经说过一句话:「人一天只有二十一个小时,另外三小时是要思考的。」所以才叫二十一响。我觉得这句话大有道理,可是我觉得三小时可能太多,因为研究生是非常忙的,但至少每天要留个三十分钟、一小时思考,想一想你看到了什么?学习跳到比你所看到的东西更高一点的层次去思考。
9. 找到学习的楷模
我刚到美国念书的时候,每次写报告头皮就重的不得了,因为我们的英文报告三、四十页,一个学期有四门课的话就有一百六十页,可是你连脚注都要从头学习。后来我找到一个好办法,就是我每次要写的时候,把一篇我最喜欢的论文放在旁边,虽然他写的题目跟我写的都没关系,不过我每次都看他如何写,看看他的注脚、读几行,然后我就开始写。就像最有名的男高音 Pavarotti 唱歌剧的时候都会捏着一条手帕,因为他说:「上舞台就像下地狱,太紧张了。」他为了克服紧张,他有习惯性的动作,就是捏着白手帕。我想当年那一篇论文抽印本就像是我的白手帕一样,能让我开始好好写这篇报告,我学习它里面如何思考、如何构思、如何照顾全体、如何用英文作脚注。好好的把一位大师的作品读完,开始模仿和学习他,是入门最好的方法,逐步的,你也开始写出自己的东西。我也常常鼓励我的学生,出国半年或是一年到国外看看。像现在国科会有各式各样的机会,可以增长眼界,可以知道现在的餐馆正在卖些什么菜,回来后自己要作菜也才知道要如何着手。
四、用两条腿走路,练习培养自己的兴趣
最后还有一点很重要的,就是我们的人生是两只脚,我们不是靠一只脚走路。做研究生的时代,固然应该把所有的心思都放在学业上,探索你所要探索的那些问题,可是那只是你的一只脚,另外还有一只脚是要学习培养一、两种兴趣。很多人后来会发现他的右脚特别肥重(包括我自己在内),也就是因为忘了培养左脚。很多很有名的大学者最后都陷入极度的精神困扰之中,就是因为他只是培养他的右脚,他忘了培养他的左脚,他忘了人生用两只脚走路,他少了一个小小的兴趣或嗜好,用来好好的调解或是排遣自己。
去年夏天,香港《亚洲周刊》要访问我,我说:「我不想接受访问,我不是重要的人。」可是后来他们还是把一个简单的对话刊出来了,里面我只记得讲了一段话:做一个研究生或一个学者,有两个感觉最重要 -- 责任感与罪恶感。你一定要有很大的责任感,去写出好的东西,如果责任感还不够强,还要有一个罪恶感,你会觉得如果今天没有好好做几个小时的工作的话,会有很大的罪恶感。除非是了不得的天才,不然即使爱因斯坦也是需要很努力的。很多很了不得的人,他只是把所有的努力集中在一百页里面,他花了一千小时和另外一个人只花了十个小时,相对于来说,当然是那花一千个小时所写出来的文章较好。所以为什么说要赶快选定题目?因为如果太晚选定一个题目,只有一年的时间可以好好耕耘那个题目,早点选定可以有二、三年耕耘那个题目,是三年做出的东西好,还是一年的东西好?如果我们的才智都一样的话,将三年的努力与思考都灌在上面,当然比一年还要好。
五、营造卓越的大学,分享学术的氛围
现在很多人都在讨论,何谓卓越的大学?我认为一个好的大学,学校生活的一大部份,以及校园的许多活动,直接或间接都与学问有关,同学在咖啡厅里面谈论的,直接或间接也都会是学术相关的议题。教授们在餐厅里面吃饭,谈的是「有没有新的发现」?或是哪个人那天演讲到底讲了什么重要的想法?一定是沉浸在这种氛围中的大学,才有可能成为卓越大学。那种交换思想学识、那种互相教育的气氛不是花钱就有办法获得的。我知道钱固然重要,但不是唯一的东西。一个卓越的大学、一个好的大学、一个好的学习环境,表示里面有一个共同关心的焦点,如果没有的话,这个学校就不可能成为好的大学。
2009年4月10日星期五
读博士的经验!读后,令人毛塞顿开的科研经典之作zz(转载)
读博士的经验,感觉还不错,大家过来看看
1. 先看综述,后看论著
看综述搞清概念,看论著掌握方法
2. 早动手
在师兄师姐离开之前学会关键技术
3. 多数文章看摘要,少数文章看全文
掌握了一点查全文的技巧,往往会以搞到全文为乐,以至于没有时间看文章的内容,更不屑于看摘要。真正有用的全文并不多,过分追求全文是浪费,不可走极端。当然只看摘要也是不对的。
4. 集中时间看文献
看过总会遗忘。看文献的时间越分散,浪费时间越多。集中时间看更容易联系起来,形成整体印象。
5. 做好记录和标记
复印或打印的文献,直接用笔标记或批注。pdf 或html 格式的文献,可以用编辑器标亮或改变文字颜色。这是避免时间浪费的又一重要手段。否则等于没看。
6. 准备引用的文章要亲自看过。
转引造成的以讹传讹不胜枚举。
7. 注意文章的参考价值。
刊物的影响因子、文章的被引次数能反映文章的参考价值。但要注意引用这篇文章的其它文章是如何评价这篇文章的:支持还是反对,补充还是纠错。
8. 交流是最好的老师
做 实验遇到困难是家常便饭。你的第一反应是什么?反复尝试?放弃?看书?这些做法都有道理,但首先应该想到的是交流。对有身份的人,私下的请教体现你对他的 尊重;对同年资的人,公开的讨论可以使大家畅所欲言,而且出言谨慎。千万不能闭门造车。一个实验折腾半年,后来别人告诉你那是死路,岂不冤大头?
9. 最高层次的能力是表达能力
再 好的工作最终都要靠别人认可。表达能力,体现为写和说的能力,是需要长期培养的素质。比如发现一个罕见病例,写好了发一篇论著;写不好只能发一个病例报 道。比如做一个课题,写好了发一篇或数篇论著;写不好只能发一个论著摘要或被枪毙。一张图,一张表,无不是表达能力的体现。寥寥几百上千字的标书,可以赢 得大笔基金;虽然关系很重要,但写得太差也不行。有人说,我不学PCR,不学spss,只要学会ppt(powerpoint)就可以了。此话有一点道 理,实验室的boss 们表面上就是靠一串串ppt 行走江湖的。经常有研究生因思维敏捷条例清楚而令人肃然起敬。也经常有研究生不理解"为什么我做了大部分工作而老板却让另一个没怎么干活的人写了文章?让 他去大会发言?"你没有看到人家有张口就来的本事吗?
10. 学好英语,不学二外。
如今不论去日本还是欧洲,学术交流早已是英语的天下。你不必为看不懂一篇法语的文章而遗憾,写那篇文章的人正在为没学好英语而犯愁。如果英文尚未精通,暂且不要去学二外。
英文文章写作
1. 阅读10 篇文献,总结100 个常用句型和常用短语。经常复习。注意,文献作者必须是以英文为母语者,文献内容要与你的专业有关。这属于平时看文献的副产品。
2. 找3-5 篇技术路线和统计方法与你的课题接近的文章,精读。
写出论文的草稿。要按照标题、作者、摘要、背景、目的、材料、方法、结果、讨论、致谢、参考文献、图例、图、表、照片和说明的统一格式来写。这样做的好处是从它可以方便地改成任何杂志的格式。
3. 针对论文的每一部分,尤其是某种具体方法、要讨论的某一具体方面,各找5-8 篇文献阅读,充实完善。 这里讨论的只涉及英文表达,也只推荐给缺乏英文写作经验的人。
4. 找到你想投的杂志的稿约,再找2-3 篇该杂志的article,按它的格式改写。注
意,每次改写都要先另存为不同的文件名,以免出了问题不能恢复。
5. 找英文高手改。找不到合适的人,就去找提供英语论文编辑服务(English corr
ection and improvement,not translation)的公司 ,在此向有钱没时间的人强烈推荐。
文献管理
1. 下载电子版文献时(caj,pdf,html),把文章题目粘贴为文件名。
注意,文件名不能有特殊符号,要把 \ / : * ? < > | 以及 换行符 删掉。 每次
按照同样的习惯设置文件名,可以防止重复下载。
2. 不同主题存入不同文件夹。文件夹的题目要简短,如:PD,LTP,PKC,NO。
3. 看过的文献归入子文件夹,最起码要把有用的和没用的分开。
4. 重要文献根据重要程度在文件名前加001,002,003 编号,然后按名称排列图标,最重要的文献就排在最前了。
5. 复印或打印的文献,用打孔器(¥10-15)打孔,装入硬质文件夹(¥10-20/个)。
我们经常会在参考文献的引用上耍一些小聪明,殊不知这些都会降低论文质量。
1. 知而不引
明明借鉴了同行的类似工作,却故意不引用同行的类似工作,使自己工作看上去"新颖""领先"。实际上审稿的可能就是同行。
2. 断章取义
故意截取作者试图否定的部分来烘托自己的观点。
3. 引而不确
没有认真看原文,引文错漏。
4. 来源不实
某些字句来源不可靠(比如非正式的或非学术的出版物),且不注明来源。常见于一些统计数字。
5. 盲目自引
不是为了说明自己的工作与前期工作之间的关系,而是单纯为提高自己文章被引用次数而自引。
国内文章水平不高的几个原因:
1. 审稿人知识陈旧
年纪大的审稿人查文献和和上网的能力相当有限,无法核实该研究是否有意义,创新点在那里,方法是否可靠,结果是否可信。但匪夷所思的是他们经常提的审稿意见是"参考文献不够新"。
2. 选错审稿人
虽然一般指定两名审稿人,但编辑部经常让不懂分子生物学的人审分子生物学的文章,让不懂统计的人审统计处理比较复杂的文章。出于爱面子,很少有人提出"我不适合审这篇文章"。
3. 关系文章
有了关系,什么都简单了。
4. 不承认阴性结果
诚实的阴性结果被认为无意义。怪不得有人大声疾呼"我要办一本阴性杂志"。
5. 造假
任何人都不愿意成为制度的牺牲品。出不来预期结果就没法交差。为生存计,为按期毕业计,造吧。
动态的科研
1. 科研靠积累。
象 伦琴发现X 射线那样凭借一次简单观察就得诺贝尔奖的机会越来越少。更多的科研成果来自于实验室长期积累。最终实至名归。做科研不要指望一步登天。设计课题不要好高骛 远。基金评审也是这样。没有前期积累,获得资助的可能性小。选导师要想好:你是要白手起家,还是要为人作嫁?
2. 文献要追踪。
开题时通过查文献了解的情况,到结题的时候可能有很大不同。实验过程中要注意追踪。运气好,你可以得到更多的线索;运气不好,发现别人抢先了。据此修正你的实验。写论文之前一定要重新查一遍文献。
3. 记录要复习。
前面的实验记录要经常复习。随着经验的增加和认识的提高,你会发现最初的判断未必正确。
我曾经向一些比我有经验的人请教"什么是科研",他们没有正面回答我,只是给我打了五个比方。
1. 科研是流行歌曲
什么流行用什么,什么流行做什么。张口生物芯片,闭口纳米技术。老板是追星一族,流行的就是最好的。
2. 科研是移花接木
设计课题?课题怎么是设计出来的呢?是拼出来的。A 的材料,B 的方法,C 的指标,D 的意义。
3. 科研是傻瓜相机
原理搞不懂?恕我老朽,没时间看原理了。我能折腾,多折腾几次就出来了。为什么要做这一步?老板心里明白就行了!他每周安排的活儿我还干不完呢。
4. 科研是照葫芦画瓢
综述不会写?抄啊。论文不会写?套啊。反正不会有人追究。无知者无畏!
5. 科研是垃圾
实验完成了,论文发表了,答辩通过了。老板语重心长地说:"你们走后,这些都是垃圾"。
晕!倒!挣扎!再倒!
他们没有骗我,实用主义自有它的道理。但我从此不再随便批判国内的科研水平了,因为在某些时候我也重复着同样的故事。
写毕业论文
1. 先列提纲
不列提纲,上来就写,是坏习惯。几百字没问题,几千字勉强,几万字就难了。必须列出写作提纲,再充实完善,以保证思路的连贯和字数的均衡。
2. 平时多写
及时总结阶段性的工作,多写文章多投稿。到最后阶段,把这些文字有机地组合起来,就是一篇很好的毕业论文。
3. 不要罗列所有数据
为了保证毕业论文的分量,研究生往往会观测较多的指标。但毕业论文并非数据越多越好。一定要舍弃那些与主旨关系不大的数据。否则,要么显得累赘松散,要么成为破绽。
4. 打印修改
在电脑上直接修改,会遗漏很多错误。要尽可能地减少任何错误,一定要打印出来修改。
5. 让别人指出错误
自己修改,仍然受个人习惯的局限。错误摆在那里,却熟视无睹。让别人给你指出错误吧 ,不管他与你是不是同一专业。
怎样读文献
1. 目标:
漫 无目的则毫无效率,抓不住重点才效率低下。选题之前可能会有一段时间处于迷茫状态,不知从哪入手。胡乱看了大量文献,却不知所以然。在导师的指导下,在同 行的启发下,有些人可以迅速明确目标,有的放矢,入门就从这里开始。即使导师不导,没有定题,自己也要先设定一个具体的问题看文献。不管你将来做不做这些 东西,总比没有目标好得多,保证有收获。科研的一般法则是共通的。
2. 层次:
对于一个具体的课题来说,相关文献分属于 三个层次:研究方向、研究领域、研究课题。例如有人研究干细胞定向分化治疗帕金森病,对他来说,研究方向就是帕金森病,研究领域是帕金森病的干细胞治疗, 研究课题是某种物质诱导干细胞定向分化为分泌多巴胺的神经细胞。看文献时要分清手上的文献是属于那个层次,这决定你对它要掌握到什么程度。
研究方向层次的文献:一般涉及,基础知识,学科水准,了解当前重大进展与趋势,达到专业人员水平;
研究领域层次的文献:了解焦点与热点,已/正/将进行的课题,达到专家水平;
研究课题层次的文献:要全面,了解历史、现状、展望、主要方法、手段,达到No1专家水平。
正确分辨文章的层次,才能把精力用到点子上。
3. 形式:
广 义的文献包括可以阅读的所有出版形式。教科书、专著、会议摘要汇编、期刊、网页、甚至ppt文件。比如要了解免疫应答的基本形式,最好是看教科书;要参考 大鼠脑立体定位图谱,最好是看专著;要知道最新进展,最好是查阅期刊;要了解别人的研究动向,最好是参会或看会议论文汇编。不要找错信息源。
4. 程度:
对 文献的熟悉程度不同,阅读文献的方式大不相同。新手学习式阅读,逐字逐句,搞清细节,掌握最基本的知识点。最初的十几、几十篇要精读,精华的几篇甚至要背 诵。老手搜索式阅读,已熟悉各种研究的常见模式和一般套路,能够迅速提取关键信息,把握思路,经常不按常规顺序阅读。有人看图说话,有人辨数识字。高手批 判式阅读,一针见血,直指问题所在。实际上没有一篇论文是无懈可击的。新手要稳,老手要准,高手要狠。新手、老手、高手的代表人物分别是研究生、导师和审 稿人,但认真钻研的研究生完全可以在3年中实现从新手到高手的嬗变。对自己有清醒的定位,才能选择正确的阅读方式。
5. 矛盾:
文 献读的多了,脑子里塞满了信息。公说公有理,婆说婆有理,反而无所适从?为了解决这个问题,循证医学划分临床试验证据的等级;同理,我们看文献也要重视实 验证据的强度。发现矛盾,是第一步;找出异同,是第二步;思考解决,是第三步。从相互矛盾的结论推导中发现矛盾的根源,此时如能跳出圈外,不走思维定势, 从原始的科学问题出发,"无招胜有招",真正是到达另外一种境界了。何必翻译外国人的综述谎称自己的综述?何必重复别人做过的实验谎称自己的思路?
(注:转自此博客,未找到原始出处)
1. 先看综述,后看论著
看综述搞清概念,看论著掌握方法
2. 早动手
在师兄师姐离开之前学会关键技术
3. 多数文章看摘要,少数文章看全文
掌握了一点查全文的技巧,往往会以搞到全文为乐,以至于没有时间看文章的内容,更不屑于看摘要。真正有用的全文并不多,过分追求全文是浪费,不可走极端。当然只看摘要也是不对的。
4. 集中时间看文献
看过总会遗忘。看文献的时间越分散,浪费时间越多。集中时间看更容易联系起来,形成整体印象。
5. 做好记录和标记
复印或打印的文献,直接用笔标记或批注。pdf 或html 格式的文献,可以用编辑器标亮或改变文字颜色。这是避免时间浪费的又一重要手段。否则等于没看。
6. 准备引用的文章要亲自看过。
转引造成的以讹传讹不胜枚举。
7. 注意文章的参考价值。
刊物的影响因子、文章的被引次数能反映文章的参考价值。但要注意引用这篇文章的其它文章是如何评价这篇文章的:支持还是反对,补充还是纠错。
8. 交流是最好的老师
做 实验遇到困难是家常便饭。你的第一反应是什么?反复尝试?放弃?看书?这些做法都有道理,但首先应该想到的是交流。对有身份的人,私下的请教体现你对他的 尊重;对同年资的人,公开的讨论可以使大家畅所欲言,而且出言谨慎。千万不能闭门造车。一个实验折腾半年,后来别人告诉你那是死路,岂不冤大头?
9. 最高层次的能力是表达能力
再 好的工作最终都要靠别人认可。表达能力,体现为写和说的能力,是需要长期培养的素质。比如发现一个罕见病例,写好了发一篇论著;写不好只能发一个病例报 道。比如做一个课题,写好了发一篇或数篇论著;写不好只能发一个论著摘要或被枪毙。一张图,一张表,无不是表达能力的体现。寥寥几百上千字的标书,可以赢 得大笔基金;虽然关系很重要,但写得太差也不行。有人说,我不学PCR,不学spss,只要学会ppt(powerpoint)就可以了。此话有一点道 理,实验室的boss 们表面上就是靠一串串ppt 行走江湖的。经常有研究生因思维敏捷条例清楚而令人肃然起敬。也经常有研究生不理解"为什么我做了大部分工作而老板却让另一个没怎么干活的人写了文章?让 他去大会发言?"你没有看到人家有张口就来的本事吗?
10. 学好英语,不学二外。
如今不论去日本还是欧洲,学术交流早已是英语的天下。你不必为看不懂一篇法语的文章而遗憾,写那篇文章的人正在为没学好英语而犯愁。如果英文尚未精通,暂且不要去学二外。
英文文章写作
1. 阅读10 篇文献,总结100 个常用句型和常用短语。经常复习。注意,文献作者必须是以英文为母语者,文献内容要与你的专业有关。这属于平时看文献的副产品。
2. 找3-5 篇技术路线和统计方法与你的课题接近的文章,精读。
写出论文的草稿。要按照标题、作者、摘要、背景、目的、材料、方法、结果、讨论、致谢、参考文献、图例、图、表、照片和说明的统一格式来写。这样做的好处是从它可以方便地改成任何杂志的格式。
3. 针对论文的每一部分,尤其是某种具体方法、要讨论的某一具体方面,各找5-8 篇文献阅读,充实完善。 这里讨论的只涉及英文表达,也只推荐给缺乏英文写作经验的人。
4. 找到你想投的杂志的稿约,再找2-3 篇该杂志的article,按它的格式改写。注
意,每次改写都要先另存为不同的文件名,以免出了问题不能恢复。
5. 找英文高手改。找不到合适的人,就去找提供英语论文编辑服务(English corr
ection and improvement,not translation)的公司 ,在此向有钱没时间的人强烈推荐。
文献管理
1. 下载电子版文献时(caj,pdf,html),把文章题目粘贴为文件名。
注意,文件名不能有特殊符号,要把 \ / : * ? < > | 以及 换行符 删掉。 每次
按照同样的习惯设置文件名,可以防止重复下载。
2. 不同主题存入不同文件夹。文件夹的题目要简短,如:PD,LTP,PKC,NO。
3. 看过的文献归入子文件夹,最起码要把有用的和没用的分开。
4. 重要文献根据重要程度在文件名前加001,002,003 编号,然后按名称排列图标,最重要的文献就排在最前了。
5. 复印或打印的文献,用打孔器(¥10-15)打孔,装入硬质文件夹(¥10-20/个)。
我们经常会在参考文献的引用上耍一些小聪明,殊不知这些都会降低论文质量。
1. 知而不引
明明借鉴了同行的类似工作,却故意不引用同行的类似工作,使自己工作看上去"新颖""领先"。实际上审稿的可能就是同行。
2. 断章取义
故意截取作者试图否定的部分来烘托自己的观点。
3. 引而不确
没有认真看原文,引文错漏。
4. 来源不实
某些字句来源不可靠(比如非正式的或非学术的出版物),且不注明来源。常见于一些统计数字。
5. 盲目自引
不是为了说明自己的工作与前期工作之间的关系,而是单纯为提高自己文章被引用次数而自引。
国内文章水平不高的几个原因:
1. 审稿人知识陈旧
年纪大的审稿人查文献和和上网的能力相当有限,无法核实该研究是否有意义,创新点在那里,方法是否可靠,结果是否可信。但匪夷所思的是他们经常提的审稿意见是"参考文献不够新"。
2. 选错审稿人
虽然一般指定两名审稿人,但编辑部经常让不懂分子生物学的人审分子生物学的文章,让不懂统计的人审统计处理比较复杂的文章。出于爱面子,很少有人提出"我不适合审这篇文章"。
3. 关系文章
有了关系,什么都简单了。
4. 不承认阴性结果
诚实的阴性结果被认为无意义。怪不得有人大声疾呼"我要办一本阴性杂志"。
5. 造假
任何人都不愿意成为制度的牺牲品。出不来预期结果就没法交差。为生存计,为按期毕业计,造吧。
动态的科研
1. 科研靠积累。
象 伦琴发现X 射线那样凭借一次简单观察就得诺贝尔奖的机会越来越少。更多的科研成果来自于实验室长期积累。最终实至名归。做科研不要指望一步登天。设计课题不要好高骛 远。基金评审也是这样。没有前期积累,获得资助的可能性小。选导师要想好:你是要白手起家,还是要为人作嫁?
2. 文献要追踪。
开题时通过查文献了解的情况,到结题的时候可能有很大不同。实验过程中要注意追踪。运气好,你可以得到更多的线索;运气不好,发现别人抢先了。据此修正你的实验。写论文之前一定要重新查一遍文献。
3. 记录要复习。
前面的实验记录要经常复习。随着经验的增加和认识的提高,你会发现最初的判断未必正确。
我曾经向一些比我有经验的人请教"什么是科研",他们没有正面回答我,只是给我打了五个比方。
1. 科研是流行歌曲
什么流行用什么,什么流行做什么。张口生物芯片,闭口纳米技术。老板是追星一族,流行的就是最好的。
2. 科研是移花接木
设计课题?课题怎么是设计出来的呢?是拼出来的。A 的材料,B 的方法,C 的指标,D 的意义。
3. 科研是傻瓜相机
原理搞不懂?恕我老朽,没时间看原理了。我能折腾,多折腾几次就出来了。为什么要做这一步?老板心里明白就行了!他每周安排的活儿我还干不完呢。
4. 科研是照葫芦画瓢
综述不会写?抄啊。论文不会写?套啊。反正不会有人追究。无知者无畏!
5. 科研是垃圾
实验完成了,论文发表了,答辩通过了。老板语重心长地说:"你们走后,这些都是垃圾"。
晕!倒!挣扎!再倒!
他们没有骗我,实用主义自有它的道理。但我从此不再随便批判国内的科研水平了,因为在某些时候我也重复着同样的故事。
写毕业论文
1. 先列提纲
不列提纲,上来就写,是坏习惯。几百字没问题,几千字勉强,几万字就难了。必须列出写作提纲,再充实完善,以保证思路的连贯和字数的均衡。
2. 平时多写
及时总结阶段性的工作,多写文章多投稿。到最后阶段,把这些文字有机地组合起来,就是一篇很好的毕业论文。
3. 不要罗列所有数据
为了保证毕业论文的分量,研究生往往会观测较多的指标。但毕业论文并非数据越多越好。一定要舍弃那些与主旨关系不大的数据。否则,要么显得累赘松散,要么成为破绽。
4. 打印修改
在电脑上直接修改,会遗漏很多错误。要尽可能地减少任何错误,一定要打印出来修改。
5. 让别人指出错误
自己修改,仍然受个人习惯的局限。错误摆在那里,却熟视无睹。让别人给你指出错误吧 ,不管他与你是不是同一专业。
怎样读文献
1. 目标:
漫 无目的则毫无效率,抓不住重点才效率低下。选题之前可能会有一段时间处于迷茫状态,不知从哪入手。胡乱看了大量文献,却不知所以然。在导师的指导下,在同 行的启发下,有些人可以迅速明确目标,有的放矢,入门就从这里开始。即使导师不导,没有定题,自己也要先设定一个具体的问题看文献。不管你将来做不做这些 东西,总比没有目标好得多,保证有收获。科研的一般法则是共通的。
2. 层次:
对于一个具体的课题来说,相关文献分属于 三个层次:研究方向、研究领域、研究课题。例如有人研究干细胞定向分化治疗帕金森病,对他来说,研究方向就是帕金森病,研究领域是帕金森病的干细胞治疗, 研究课题是某种物质诱导干细胞定向分化为分泌多巴胺的神经细胞。看文献时要分清手上的文献是属于那个层次,这决定你对它要掌握到什么程度。
研究方向层次的文献:一般涉及,基础知识,学科水准,了解当前重大进展与趋势,达到专业人员水平;
研究领域层次的文献:了解焦点与热点,已/正/将进行的课题,达到专家水平;
研究课题层次的文献:要全面,了解历史、现状、展望、主要方法、手段,达到No1专家水平。
正确分辨文章的层次,才能把精力用到点子上。
3. 形式:
广 义的文献包括可以阅读的所有出版形式。教科书、专著、会议摘要汇编、期刊、网页、甚至ppt文件。比如要了解免疫应答的基本形式,最好是看教科书;要参考 大鼠脑立体定位图谱,最好是看专著;要知道最新进展,最好是查阅期刊;要了解别人的研究动向,最好是参会或看会议论文汇编。不要找错信息源。
4. 程度:
对 文献的熟悉程度不同,阅读文献的方式大不相同。新手学习式阅读,逐字逐句,搞清细节,掌握最基本的知识点。最初的十几、几十篇要精读,精华的几篇甚至要背 诵。老手搜索式阅读,已熟悉各种研究的常见模式和一般套路,能够迅速提取关键信息,把握思路,经常不按常规顺序阅读。有人看图说话,有人辨数识字。高手批 判式阅读,一针见血,直指问题所在。实际上没有一篇论文是无懈可击的。新手要稳,老手要准,高手要狠。新手、老手、高手的代表人物分别是研究生、导师和审 稿人,但认真钻研的研究生完全可以在3年中实现从新手到高手的嬗变。对自己有清醒的定位,才能选择正确的阅读方式。
5. 矛盾:
文 献读的多了,脑子里塞满了信息。公说公有理,婆说婆有理,反而无所适从?为了解决这个问题,循证医学划分临床试验证据的等级;同理,我们看文献也要重视实 验证据的强度。发现矛盾,是第一步;找出异同,是第二步;思考解决,是第三步。从相互矛盾的结论推导中发现矛盾的根源,此时如能跳出圈外,不走思维定势, 从原始的科学问题出发,"无招胜有招",真正是到达另外一种境界了。何必翻译外国人的综述谎称自己的综述?何必重复别人做过的实验谎称自己的思路?
(注:转自此博客,未找到原始出处)
Mapping out a Research Agenda
1.Why do research?
• To satisfy intellectual curiosity
• To better understand things
• To be at the forefront of an exciting, technical field
• To always be learning new things
• Because that’s what professors do!
2.Helpful Personal Qualities in Pursuing Research
• Creativity
• Curiosity
• Independence of thought
• Good communication skills
• Perseverance
• Self-discipline
• Interaction skills
3.Choosing a Research Problem
• Problem should be important
• Problem should hold your personal interest
• Problem should have depth, in terms of aspects possibly available for investigation
• Problem might come from questioning existing literature
4.How to proceed?
• Set aside uninterruptible blocks of 'research thinking time' in your weekly schedule
• Familiarize yourself with previous work from the literature
• Critically examine previous approaches, questioning generality, practicality, validation
• Frame long-term questions to be answered
• Use short-term objectives to subdivide research into manageable pieces
– Divide work into investigations that ‘fit’ into a coherent whole
– Make progress one paper at a time
• Know what it means to ‘solve a problem’ or validate a technique
• Write papers and give talks about your work
– Intuition, intuition, intuition
– Exercise: do an in-the-elevator summary
• Develop a personal style
– One at a time vs juggling several projects
• Allow your graduate students to suggest explorations
• Re-examine your research achievements at regular intervals, to ensure progress towards answering long-term questions
5.SE Research
• What practical SW problems are you addressing?
• How will you validate your approach?
• How can you ‘keep up’ with this broad area of CS&E?
– Attend conferences and network
– Pick favorite journals and other research groups and periodically visit their websites
6.Specific Techniques
• Establish a reading group with your students
• Summarize attended conferences to others, to discuss key research issues
encountered
– 2-3 sentence summaries of each presentation
• Keep a research notebook where you can jot down ideas for later consideration
– Go back and look at your entries!
• Teach a graduate seminar in your area of interest
– Teaching is a learning experience
• Attend workshops, especially those with work-in-progress presentations
• Participate in grant evaluation panels and program committees
• Leverage your efforts with graduate students
• Use senior faculty mentor(s)
– e.g., Obtain examples of funded proposals
7.Possible Pitfalls
• Switch of research areas during junior faculty years
– Requires large time investment up front
• Controversial/risky research areas
• Obtaining negative results
• Interdisciplinary work
8.Collaboration
• Con: Need for junior faculty to establish a personal research identity
• Con: May be time-consuming
• Pro: Projects can be more complex and more realistic
• Pro: Allows groups to tap into personal strengths of participants
9.Biggest Challenge
How to develop a coherent research agenda with limited time to do so, while juggling the responsibilities of a junior faculty?
(by Barbara G. Ryder,Rutgers University)
• To satisfy intellectual curiosity
• To better understand things
• To be at the forefront of an exciting, technical field
• To always be learning new things
• Because that’s what professors do!
2.Helpful Personal Qualities in Pursuing Research
• Creativity
• Curiosity
• Independence of thought
• Good communication skills
• Perseverance
• Self-discipline
• Interaction skills
3.Choosing a Research Problem
• Problem should be important
• Problem should hold your personal interest
• Problem should have depth, in terms of aspects possibly available for investigation
• Problem might come from questioning existing literature
4.How to proceed?
• Set aside uninterruptible blocks of 'research thinking time' in your weekly schedule
• Familiarize yourself with previous work from the literature
• Critically examine previous approaches, questioning generality, practicality, validation
• Frame long-term questions to be answered
• Use short-term objectives to subdivide research into manageable pieces
– Divide work into investigations that ‘fit’ into a coherent whole
– Make progress one paper at a time
• Know what it means to ‘solve a problem’ or validate a technique
• Write papers and give talks about your work
– Intuition, intuition, intuition
– Exercise: do an in-the-elevator summary
• Develop a personal style
– One at a time vs juggling several projects
• Allow your graduate students to suggest explorations
• Re-examine your research achievements at regular intervals, to ensure progress towards answering long-term questions
5.SE Research
• What practical SW problems are you addressing?
• How will you validate your approach?
• How can you ‘keep up’ with this broad area of CS&E?
– Attend conferences and network
– Pick favorite journals and other research groups and periodically visit their websites
6.Specific Techniques
• Establish a reading group with your students
• Summarize attended conferences to others, to discuss key research issues
encountered
– 2-3 sentence summaries of each presentation
• Keep a research notebook where you can jot down ideas for later consideration
– Go back and look at your entries!
• Teach a graduate seminar in your area of interest
– Teaching is a learning experience
• Attend workshops, especially those with work-in-progress presentations
• Participate in grant evaluation panels and program committees
• Leverage your efforts with graduate students
• Use senior faculty mentor(s)
– e.g., Obtain examples of funded proposals
7.Possible Pitfalls
• Switch of research areas during junior faculty years
– Requires large time investment up front
• Controversial/risky research areas
• Obtaining negative results
• Interdisciplinary work
8.Collaboration
• Con: Need for junior faculty to establish a personal research identity
• Con: May be time-consuming
• Pro: Projects can be more complex and more realistic
• Pro: Allows groups to tap into personal strengths of participants
9.Biggest Challenge
How to develop a coherent research agenda with limited time to do so, while juggling the responsibilities of a junior faculty?
(by Barbara G. Ryder,Rutgers University)
2009年3月2日星期一
Ad-Hoc Routing Protocols (Characteristics)
Ad-Hoc Routing Protocols (Characteristics)
Classification of the Characteristics of Ad-Hoc Protocols and Routing-StrategiesExtension of Lang2003 and Murthy2004.
Classification of the Characteristics of Ad-Hoc Protocols and Routing-StrategiesExtension of Lang2003 and Murthy2004.
In general, routing can be divided into two strategies:Adaptive Routing vs. Not-Adaptive Routing
Adaptive: Changes of the network-topology are adapted by the routing-strategy
Not-Adaptive: The routing is done using fixed tables
For Ad-Hoc-Netorks, only adaptiv strategies are usefull.Reactive Routing/On-Demand Routing vs. Proactive Routing/Table-Driven Routing vs. Hybrid Routing
Reactive/On-Demand
A route is only calculated, when it is needed
Does not try to keep routing-information everytime to all node
Proactive/Table-Driven
Routes are calculated before one is needed
Tries to keep routing-information to all nodes everytime up-to-date
Update of the tables:
Event-driven: only if a change is recoginzed
Periodically
Hybrid
Reactive and Proactive at the same time
E.g.: Intra-Zone: Proactiv, Inter-Zone: Reactiv
Distance-Vector Routing vs. Link-State Routing
Distance-Vector
Calculates the distance to all nodes
Exchange of these information only with the neighbours
Link-State
Mesuare the distance to the neighbours
Exchange of these information with all nodes
Flat Routing vs. Hierarchical Routing / Clustered Routing
Hierarchical Routing / Clustered Routing:
Trying to structure/cluster the network
Clusterhead:
Responsible for the creation and extension of a cluster
Builds up a hierarchie of clusters
Manages the communication inside a cluster
Gateway-Node:
Responsible for the communication between clusters
Maybe bottleneck
Flat Routing:
Network has no hierarchy
Geographical Routing / Positionbased Routing / Direction-Based Routing
No routing-tables
Information is send in any way in the direction of the destination
No overhead to find or update routes But:
Position required
Determination of the position via
internal search-process
external service
Uniform vs. Non-Uniform
Uniform: All nodes are equal
Non-Uniform: Some nodes have special roles, e.g. Clusterhead, Gateway-Node
Full vs. Reduced Topology Information
Full: All topology-information will be distributed
Reduced: Only a fraction of the known topology-information will be distributed
Past History vs. Prediction
Past History: Information of past statuses is used to make a decision
Prediction: Expectation of future statuses are used to make routing decisions
Broadcast
Local Multicast: Only to some nodes in transmission-distance
Local Broadcast: Only to the nodes in transmission-distance
Networkwide Broadcast: Flooding
Restricted Networkwide Broadcast: Flooding with a time-to-live
Recovery Strategy
Mechansims to keep or to restore routes
Link-Reversal Routing
Does not try to find an (somehow) optimal way
Only tries to find any way
Source-Routing
The sender specifies the way to go
Under the circumstances, a node inbetween can decide to redefine the way
Route-Selection-Strategies
Power-Aware Routing
Signal Strength
Link Stability
Shortest Path
Link-State Routing / Distance-Vector Routing
Direction-Based Routing / Positionbased Routing / Geographical Routing
Link-Reversal Routing
Multipath Routing "Ad-Hoc Protocols (Characteristics)" wird erwähnt auf: Ad-Hoc Protocols
理解NS2中的OTcl/tclCL
from:http://stevieliu.bokee.com/3247914.html
题记
真正触动我写这篇短文的原因是试图理解NS2的基本原理. 在"the NS2 manual"中, 解释了为什么采用了两种语言来建构整个系统, 然后在第三章描述了tclcl的六个类. 这个手册中的对各个类描述性文字让我如坠雾里, 不明所以. 我查找了一些NS2的文章和站点, 有一些ppt倒是很形象, 但我的认识上总有些模糊. 后来, 我逐渐明白到OTcl/Tcl的嵌入特性. --- 这才是理解NS2框架的关键.
Abstract
本文的主要目的是理解NS2的architecture, 了解NS2的基本原理. NS2采用了Tcl/C++分裂的模型, 在这种模型中OTcl是处于比较关键的位置, NS2采用了Tcl的编程模式. 使用C++来编写应用实例, 使用OTcl来操纵这些实例. 理解了OTcl就理解了NS2的框架. 本文先简述Tcl语言的嵌入特性, 然后描述了NS2的应用场景, 进而分析NS2的架构, 以及实现该架构采用的技术.
Introduction
NS2是MIT的一个作品, 它是一个面向对象的网络仿真工具. 使用NS2可以完整的仿真整个网络环境, 只要你的机器足够快 :-) NS2使用一整套C++类库实现了大多数常见的网络协议以及链路层的模型, 使用这些类的实例我们就可以搭建起整个网络的模型, 而且包括了各个细节. --- 这简直就是一种梦想的实现, 试想如果手头能有这样一个工具, 我们就可以在单机环境中模拟网络的各个元素, 加深对网络的了解和认识; 同时, 加快我们开发新协议的速度.
与NS2类似的软件有OPNET, 这是一个商用的网络仿真软件, 据说它能够针对各款交换机和路由器来搭建网络, 够牛x. 与之相比, NS2是一个免费的软件, 它可以在Windows/Unix上运行, 我们可以看到NS2的所有源代码, 另外在学术界更多的是采用NS2来做仿真.
NS2采用了Tcl/C++分裂的模型来建构它的应用, 这样做的好处是显而易见的. 使用Tcl语言我们可以灵活的配置网络环境, 定制系统; 而采用C++来编程满足了我们对仿真效率的需要. 缺点也是明了的, 要同时维护两套代码, 对使用者要求较高.
NS2的Tcl/C++架构与Windows下的COM/VBScript编程模式有些类似, 使用VC来编写和实现COM对象, 然后使用VB来操纵COM对象. Windows提供了COM接口, 这就在系统范围内保证了这种机制的有效性. --- 这就是Windows的高明之处. 与之相比, NS2则能够使Tcl脚本解到它的C++类库结构, 同时按照它的类分级来创建对象. --- 这也很了不起.
要使用NS2来仿真开发一个新协议, 就必须对NS2的类库进行某些扩展. 撇开各个协议或链路的细节不谈, 对NS2的实现机制的了解是一个关键, 否则难免会疏漏百出. 如果不了解NS2的机制, 在刚开始开发协议时, 你看着NS2的代码可能会感觉到无处下手.
NS2的手册中对它的机制和原理主要在"the NS manual"一书的第三章, 但是这一章的内容写来就象tclcl类的简单介绍, 读来非常费解. 它对NS2的整体设计思路并没有交待的很清楚, 本文就打算解析这第三章背后的话.
以下的内容安排如下, 首先简单介绍Tcl语言的嵌入特性, 然后描述NS2的应用场景, 分析NS2的框架, 然后考察实现这个NS2框架所遇到的问题. 最后以一个新协议的添加作为例子, 感受NS2的仿真开发过程.
嵌入的Tcl
OTcl称为Objected Tcl, 它是在Tcl基础上的一个面向对象的封装. Tcl语言本身比较简单, 在NS2的主页上有一些相关的链接, 可以快速了解它的基本语法, 这里对Tcl的语法并不感兴趣.
Tcl是一种脚本语言, Tool Cammand Language. Tcl语言的吸引人之处在于它简单同时支持嵌入式应用. 大家都用过Ms Word或Emacs, 这两种编辑器之所以如此强大, 很大的原因是因为它们内嵌了VB或Lisp语言. 这些内嵌的脚本能够定制编译器的环境, 使应用变的非常灵活.
你可能还听说过Windows下的脚本引擎, 把一个脚本引擎嵌入到Windows应用中, 就可以使这个应用具有类似Word的这种能力. 在Unix下类似的脚本语言嵌入很早就有, 上面提到的Emacs就是一个例子. Tcl语言也支持内嵌, 具体做法是把Tcl的库链接到应用程序中去, 从而使应用具有解释Tcl语言的能力. 当然仅仅这么做是不够的, 你还要为这个应用定制开发一些Tcl命令.
(NS2为什么不使用Lisp作为内嵌? 这可能与OTcl语言本身也是MIT的作品有关, 而且是MIT的近期作品. 但是如果采用Lisp的话, 就可以在Emacs做这个仿真了, 呵呵, 想想都要偷笑了, 可惜呀. 毕竟我是个Emacs的拥护者.)
Figure 1是Tcl C与应用程序集成的原理图. Figure 1中黑线表示C的调用, 红线表示Tcl脚本的调用. 为了使应用能够解释Tcl语言, 必须在应用程序的源代码中嵌入Tcl C库, 这个库的主要目的是实现一个Tcl语言的Parser, 并且实现了Tcl语言的一些关键命令, 如set, while, proc等. 应用程序必须还要编写一些针对应用扩展的Tcl命令, 然后注册进Tcl C库, 同时, 应用程序可以使用Tcl_LinkVar使某些C变量与Tcl库中的环境变量绑定起来.
Tcl C库的一些函数接口如下:
Tcl_CreateInterp 创建Tcl的Parser;
Tcl_CreateCommand/Tcl_CreateObjCommand 注册应用相关的命令;
Tcl_Eval 执行一条Tcl命令;
Tcl_LinkVar 将应用程序中的变量与Tcl库环境中的变量绑定;
Tcl_GetVar/Tcl_SetVar 设定/获取Tcl库环境中的变量;
下面是一个取自文献2的例子: /*
* Example 47-1
* The initialization procedure for a loadable package.
*/
/*
* random.c
*/
#include
/*
* Declarations for application-specific command procedures
*/
int RandomCmd(ClientData clientData,
Tcl_Interp *interp,
int argc, char *argv[]);
int RandomObjCmd(ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
/*
* Random_Init is called when the package is loaded.
*/
int Random_Init(Tcl_Interp *interp) {
/*
* Initialize the stub table interface, which is
* described in Chapter 46.
*/
if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
return TCL_ERROR;
}
/*
* Register two variations of random.
* The orandom command uses the object interface.
*/
Tcl_CreateCommand(interp, "random", RandomCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "orandom", RandomObjCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
/*
* Declare that we implement the random package
* so scripts that do "package require random"
* can load the library automatically.
*/
Tcl_PkgProvide(interp, "random", "1.1");
return TCL_OK;
}
这个例子完整的体现了如何初始化Tcl C库, 如何向Tcl库中注册命令. 这里不打算继续讨论Tcl C库的详细问题了. 毕竟, 我们的目的是为了理解Tcl的嵌入能力, 上面的原理图已经足够.
NS2采用的是OTcl来实现它的脚本语言内嵌, 原因是NS2还有一套C++类库. 这个C++类库实现了网络仿真的各个元素, 仅仅是Tcl来操纵这套类库有些困难. 这关系到NS2的实现, 我们在后面谈.
OTcl的语法设计
这一部分的内容主要取自文献1, 放在这里的目的有两个: 一是为了了解OTcl的面向对象扩展, 从而能够方便的理解一些NS2的代码; 二是理解OTcl的实现原理.
文献1中主要介绍的是对Tcl语言本身的面向对象扩展, 它没有讲述实现如何操纵一个C++的对象. 从代码上来看OTcl本身好象并没有实现对C++对象的操纵.
OTcl和tclCL的相关文献都比较少, 这也许就是对NS2诟病较多的原因. OTcl是MIT的一个流媒体项目VuSystem的辅产品, 它并不是最早提出的Object概念的Tcl, 文献1认为OTcl的特点是Dynamic, 能够动态地创建一个对象.
OTcl的语法扩展
对OTcl的语法描述, 更详细的见网页45678.
OTcl的语言设计采用了称为"Object Command"的方法. 每个命令可能被解释成对象, 而子命令被解释成传递给对象的消息. 文献1中认为这样做可以比较方便的实现Tk的消息机制达到某种一致, 同时对象作为Tcl语言中array, list, proc等要素的补充, 这种扩展显得比较自然.
下面是OTcl一个一段示例代码, 我们可以看到对象astack的子命令set, proc等作为消息传递给对象. 而且要注意它确实是动态的, 在代码解释过程中动态的添加属性和方法. Object astack
astack set things {}
astack proc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
astack proc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrang $things 1 end]
return $top
}
astack put toast ==> toast
astack get ==> toast
astack destroy ==> {}
注意上面的instvar方法, 代码的前面用set定义了一个things的变量, 在方法内要操纵它必须使用instvar来声明, 有些怪, 是不是? 否则things将是一个方法内的局部变量. 下表是OTcl对象的方法:
name
description
class
创建一个对象
destroy
销毁一个对象
proc
定义Tcl对象方法
set
定义Tcl对象变量
instvar
绑定实例变量
info procs
列出Tcl对象的所有方法
info args
列出Tcl对象方法的参数格式
info body
列出Tcl对象方法的函数主体
info commands
列出Tcl/C的命令
info vars
列出Tcl变量
inof class
获取class名
在文献1中提到上面方法的语境(context)如下表. 这个语境感觉好象是专门用来显示的指出对象方法的作用域. --- 如果是这样的话, OTcl的名字空间管理好象有些问题.
name
description
type
self
对象名
变量
proc
方法名
变量
class
定义的类型方法
变量
next
下一个影子方法
方法
$self类似于C++类中的this指针, $proc给出方法名, $next是指父类的同名方法, 就是C++中的函数重载, 这关系到OTcl对象的多继承机制.
OTcl的语法的进一步解释45678
在OTcl中, 类(Class)和对象(objects)是区分开来的. 类表示一种类型, 对象是类的实例. 在OTcl中, 类可以看成是一种特殊的对象. 类标志对象的类名, 类中可以放置所有对象共享的变量, 类似于C++中的静态变量.
OTcl的基类称为Object, 注意它可不是前面所说的对象. 所有的类都是从Object派生而来.
OTcl的属性都是C++意义上的public的.
instproc用来定义类的方法, 而proc用来定义对象方法. 后者定义的方法只能用于该对象.
unset用来undefine一个变量.
OTcl中, 类的instproc函数init相当于C++中的构造函数.
superclass用于继承.
OTcl的继承机制
文献1对OTcl的继承机制描述的很清晰. OTcl支持类的多继承, 唯一的要求是继承关系满足DAG(有向无环图). OTcl的类继承可以简单地通过下面这个例子来理解.
Class Safety
Safety instproc init {} {
$self next
$self set count 0
}
Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
Safety instproc get {} {
$self instvar count
if {$count == 0} then { return {empty!} }
incr count -1
$self next
}
Class Stack
Stack instproc init {} {
$self next
$self set things {}
}
Stack instproc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
Stack instproc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrange $things 1 end]
return $top
}
Class SafeStack -superclass {Safety Stack}
SafeStack s
s put toast ==> toast
s get ==> toast
s get ==> empty!
s destroy ==> {}
上面的例子中, SafeStack从两个类派生而来Safety, Stack. OTcl使用"方法分发(Method Dispatch)"来描述如何从子类访问父类的重载方法. 如Figure 3所示.
从Figure 3可以了解到类是如何通过next方法来访问父类的重载方法的.
OTcl的C Api
OTcl给出了一个简洁的C Api接口, 通过这个接口我们可以在应用中访问OTcl中的对象. 主要接口描述见7和otcl.h文件. 从这些接口我们可以了解到OTcl本身并没有提供操纵C++类的方法, 这留给了tclCL来完成这一工作.
检查OTcl的Makefile, 可以看到它有三个目标: libotcl.a, owish, otclsh. 后两个都是shell程序, libotcl.a是我们关心的, 它就是OTcl的库, 当它被链接到应用程序中后, 应用程序就有了OTcl脚本内嵌的功能. libotcl.a: otcl.c
rm -f libotcl.a otcl.o
$(CC) -c $(CFLAGS) $(DEFINES) $(INCLUDES) otcl.c
ar cq libotcl.a otcl.o
$(RANLIB) libotcl.a
可以看到libotcl.a只由一个文件生成otcl.c, 这个c文件有两千多行, 好在otcl.h头文件很清晰. otcl.h一开始就声明了两个结构, 然后开始声明一些函数, 这些函数的名字很self meaning.
这里不打算继续分析otcl.c的源码了, 可以从otcl.h和OTcl的主页上了解一下C api的相关内容. 下面开始在tclcl中寻找我们感兴趣的内容.
NS2的应用场景与设计
有了上面对Tcl/OTcl的了解, 我们开始探询NS2仿真系统的设计原理. NS2的目标是仿真实际网络的各个元素, 对这些网络元素的描述有相关的资源可以获取. 如TCP协议的实现, 就可以从FreeBSD上获取, 由于TCP协议的实现版本不同, 就有了Reno发布等. 对链路的仿真, 主要是通过队列, 延迟等.
如何仿真这些网络元素是一回事, 对于NS2的系统架构是另一回事. 如此多的网络元素, 它们有可能功能重叠, 特定的仿真对象有不同的要求. 要满足这种灵活性, 一个可行的方案就是采用脚本定制仿真环境. 从而导致NS2有内嵌脚本语言的要求.
另一方面, 这些网络元素是分门别类的, 再有从实现效率上考虑, 使NS2采用了C++来对这些网络仿真对象建模. 这样就对应的要求脚本语言能够有与C++对象交互的能力. --- 这个要求可不低. 而MIT正好又有一种新开发的面向对象的脚本语言OTcl, 这些都促成了NS2采用OTcl.
NS2的应用场景是这样的: 用户在一个Tcl脚本中给出对仿真环境的描述, 键入ns xx.tcl启动NS2, NS2先完成一些初始化工作, 然后按照脚本的描述实例化各个仿真元素, 并把这些仿真元素连接起来, 在启动事件发生器, 触发网络元素动作, 中间有一些记录工作, 把这些仿真信息保存在磁盘上留待继续分析.
现在我们考虑这样一个系统如何设计. OTcl本身有对象的机制, Tcl脚本可以描述我们要仿真的对象. 但是我们遇到的问题是如何从OTcl来操纵NS2的C++对象, 这包括:
动态创建一个新的C++对象,
访问这个C++对象的属性,
调用该C++对象的方法.
C++对象的创建与删除
首先, 我们首先需要一种机制, 能够从一个描述类的字符串来动态的创建一个C++对象. 如果退到Tcl的方式, 我们就必须为每一个类写这样一种Tcl命令接口, 这不是一个好的解决方案, 它最大的问题是丧失了C++的继承关系. NS2的解决方案称为"Split Model", 它在OTcl上建立Tcl下的类, 与C++类分级保持一致, 每个C++类都对应一个OTcl的类, 但是问题还没有完全解决. 你在OTcl上初始化了一个对象, 必须要同时初始化一个对应的C++的对象.
为了解决这个问题, NS2在C++上使用了TclClass类来封装注册机制. 每个C++类都有一个对应的从TclClass派生而来的对象, 注意这里是对象, 是一个实例, NS2一启动就会实例化它. 该对象的主要目的是封装注册C++类的动态创建函数, 注册信息维护在一个hash表中, 该hash表是tclCL包的一部分, hash键是描述类的一个字符串计算而来.
接下来的问题是, 如何调用这个C++类的创建函数. NS2的方法很技巧, 前面所说的OTcl继承关系是一个关键, OTcl的对象的初始化函数都是init, 一个派生的OTcl对象首先是调用它的父类的init函数. 如前面的代码: Stack instproc init {} {
$self next
$self set things {}
}
这样, 一个OTcl的初始化肯定要调用到OTcl的Object类的init, 如果这个Object能够在此时初始化这个C++对象将是再理想不过. 这样一来, C++对象的初始化就对用户来说不可见了, 他只看到的是一个OTcl对象被初始化. NS2使用了TclObject而不是Object来派生所有的OTcl对象, 对应的, C++仿真对象也从一个C++的TclObject类派生. 由OTcl的根类TclObject来搜索C++类名hash表, 完成C++对象的创建工作. 还有一个小问题, init必须带入C++类名的字符串作为参数, 否则就没法查hash表了.
OTcl对象/C++对象的删除也是类似的道理. 至此, 第1个问题解决.
访问C++对象的属性
下一个问题是要从OTcl上操纵C++对象的属性. OTcl对象的属性并不一定要完全照抄C++对象的属性, OTcl对象属性的设计原则是能够方便的完成对C++属性的设置即可. 所以一般来说, OTcl对象属性集合要小于C++对象属性集合.
在分析如何访问C++对象属性之前, 先澄清一些OTcl名字空间的概念. OTcl是一个脚本程序, 它传统的继承了Unix下环境变量的概念, 变量的名字空间是扁平的. 而引入了对象机制后, 名字空间就有些复杂了. 显然一个OTcl对象的属性的名字与环境变量下的名字有可能重叠. 在OTcl中, 这称为名字的"context"语境.
对比C++对象的名字, 它是确定的, 这是因为有编译器的帮助, 编译器在编译一个C++源代码的时候它可以根据上下文来判断这里变量指的是什么. 而在OTcl的环境中, 也需要类似的机制, 由OTcl的Parser动态的确定一个名字的含义.
显然要确定一个名字的含义, 可以通过对象名来帮助作到这一点. 在OTcl中还有一个对象的hash表, 对象创建后要注册到这个hash表中. 对一个名字解释, 首先是要搜索对象hash表, 再搜索该对象的class及其父类, 参照前面的next指针.
在下面的代码中, $self instvar count这条语句就是切换context的. 如果不切换context, 我们将不知道是环境变量名还是其他的对象的属性. Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
对一个C++对象属性的访问, 有读/写两种操作. 由于存在OTcl/C++两个对象, 有可能它们的属性并不一致. 但是要注意到OTcl的对象属性是给用户看的, 只要保证在读/写OTcl对象属性的时候能够作到与C++对象一致就行了, 完全保持二者的一致性是没有必要的. 有了这样的要求, 下面的方式才是可行的.
NS2采用了一种trap机制来捕获对OTcl对象属性的访问. 具体来说, 在tclCL中是以InstVar类对象来封装这种Trap机制. Trap的位置安装在语境切换的时刻, 因为只有在语境切换后, 才有可能对该
OTcl对象的属性进行访问.
对C++对象非static属性的访问
现在来考虑一下实现trap或者说C++对象属性绑定的细节问题. 首先, 绑定的是一个C++对象的属性(对绑定一个C++类的static属性问题在后面谈), 这意味着要知道该C++对象属性的位置, 所以C++对象属性的位置是绑定的一个必要条件.
其次, 如何设计这个Trap? 假设我们要对某个OTcl的变量进行写操作, 一般的操作是利用Tcl的Parser直接写, 但是这里我们要保持与某个位置上的信息同步, 就还需要向这个位置写相应的信息. 要解决问题, 可以修改Tcl的Parser, 让它写同步位置即可. --- 这就是InstVar的思路, 我们可以在语境切换的时刻安装一个定制的Parser, 让这个定制的Parser来向C++对象属性的位置写, 问题就解决了.
剩下的问题是, 何时安装这样一个定制Parser? 原则上任何时候都是可以的, 只要你知道这个C++对象属性的位置. 但是一个方便的做法是在该C++对象构造函数内做. 当调用这个binding函数的时候, 它会在OTcl对象属性位置上做一个标记, 表示有绑定的属性. 当进行语境切换的时候, 如果在该OTcl对象的属性位置上有绑定标志, 则OTcl动态安装定制的Parser, 这个定制的Parser就是tclCL的InstVar的一个成员函数.
由于OTcl是脚本语言, 是若类型的语言, 它用字符串表示所有的变量, 只有当它在eval的时候才会知道它具体是char/int/real等. 所有InstVar有几个派生类, 原因是这个定制的Parser要向C++属性写不同类型的信息.
给个例子 ASRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_); \* real variable */
bind("requestor_", &requestor_); \* integer variable */
bind_time("lastSent_", &lastSessSent_); \* time variable */
bind_bw("ctrlLimit_", &ctrlBWLimit_); \* bandwidth variable */
bind_bool("running_", &running_); \* boolean variable */
}
对C++对象static属性的访问
C++对象的静态属性是放在局部堆上的, 而且是该类的所有对象共享的. 如果在C++对象的构造函数中绑定这个静态属性, 显然有效率上的问题, 在同时存在多个该类的C++对象时, 就会多次绑定. 更严重的是, 它绑定到的对应的是OTcl对象上, 这样在OTcl对象上该static属性就不同一了.
注意到NS2启动时, C++类对应的TclClass把C++类要注册到OTcl的类hash表上, 这个时刻是做对C++类工作的绝好机会.
分析OTcl的类/对象机制, 可以看到OTcl无法象C++对象一样有静态变量, 这算是OTcl设计的一个缺陷? 用OTcl类初始化一个OTcl对象, 意味着完整拷贝OTcl的信息. 对次, NS2采用了一种变通的方法. 它首先在OTcl类上添加了一个属性, 以后用该类初始化OTcl对象时, 所有对象都有这样一个属性. 然后在该属性上注册一个定制的Parser到这个属性上, 这个Parser直接访问了C++对象静态变量. ---这样就在OTcl对象上作出了一个静态变量. 见下面的从文献3中摘录的代码.
假设C++类的static变量为 class Packet {
......
static int hdrlen_;
};
Packet的TclClass中定义如下: class PacketHeaderClass : public TclClass {
protected:
PacketHeaderClass(const char* classname, int hdrsize);
TclObject* create(int argc, const char*const* argv);
\* These two implements OTcl class access methods */
virtual void bind();
virtual int method(int argc, const char*const* argv);
};
void PacketHeaderClass::bind()
{
\* Call to base class bind() must precede add_method() */
TclClass::bind();
add_method("hdrlen");
}
int PacketHeaderClass::method(int ac, const char*const* av)
{
Tcl& tcl = Tcl::instance();
\* Notice this argument translation; we can then handle them as if in TclObject::command() */
int argc = ac - 2;
const char*const* argv = av + 2;
if (argc == 2) {
if (strcmp(argv[1], "hdrlen") == 0) {
tcl.resultf("%d", Packet::hdrlen_);
return (TCL_OK);
}
} else if (argc == 3) {
if (strcmp(argv[1], "hdrlen") == 0) {
Packet::hdrlen_ = atoi(argv[2]);
return (TCL_OK);
}
}
return TclClass::method(ac, av);
}
OTcl脚本如下, 这个脚本模拟了读写两种操作. PacketHeader hdrlen 120
set i [PacketHeader hdrlen]
几点感想
定制Parser在上面的代码中体现无疑.
上面在对C++对象非static属性访问的代码中bind函数值得回味. 它把一个private/protected的属性给暴露出去了, 而C++编译器却照样编译通过, 有意思.
调用C++对象的方法
在NS2中, 很少有OTcl本身再实现一个对象的方法的, 因为从效率的角度考虑, 这样做会得不偿失. 一般的情况都是直接调用C++对象的方法来处理. 从实现上来看, 要调用一个对象的方法并不困难, 只要在合适的语境中, 给出参数直接调用就可以了. 所以NS2中实现对C++对象的方法的引用至多也就是定制Tcl的Parser.
注册顶级命令
OTcl继承了Tcl的某些特性, 可以通过TclCommand类定制一个顶级命令注册到OTcl的Parser中, 不过这种方法不值得推荐. 下面的例子3给出了实现方法:
要在OTcl中注册的顶级命令是hi: % hi this is ns [ns-version]
hello world, this is ns 2.0a12
下面是实现代码, 构造函数直接以TclCommand的构造函数注册"hi"命令, command()函数是命令的实现部分. class say_hello : public TclCommand {
public:
say_hello();
int command(int argc, const char*const* argv);
};
say_hello() : TclCommand("hi") {}
#include\* because we are using stream I/O */
int say_hello::command(int argc, const char*const* argv) {
cout << "hello world:"; for (int i = 1; i <> method?*/
virtual int command(int argc, const char*const* argv);
virtual void trace(TracedVar*);
...
可以看到, command()是一个虚函数, 这么做的目的是为了保证所有的command()函数都一样. 再看看3中给的一个例子: int ASRMAgent::command(int argc, const char*const*argv) {
Tcl& tcl = Tcl::instance();
if (argc == 3) {
if (strcmp(argv[1], "distance?") == 0) {
int sender = atoi(argv[2]);
SRMinfo* sp = get_state(sender);
tcl.tesultf("%f", sp->distance_);
return TCL_OK;
}
}
return (SRMAgent::command(argc, argv));
}
注意到最后一行return (SRMAgent::command(argc, argv)); --- 问题清楚了, command()函数是在C++对象一侧上溯到它的父类的, 这样做显然效率要高一些. 因此, ccommand()应该是在C++对象初始化的时候, 被注册到OTcl对象的cmd{}上.
最后交待一下OTcl如何调用cmd{}. 有两种方式, 一种是显示的调用cmd{} $srmObject cmd distance?
另一种是隐式的调用: $srmObject distance?
在隐式调用方式下, Tcl的Parser先检查有OTcl对象没有distance?这样的命令, 这里显然没有. 然后Parser会把解释权传递给OTcl对象的unknown{}函数, unknown{}函数会使用上面的显示调用的方式来调用C++对象的command()函数.
在OTcl对象中实现命令的例子
虽然在OTcl对象中实现方法的例子不常见, 文献3还是给出了一个例子: Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
这个例子说明了在OTcl对象中中重载C++对象的命令.
到现在为止, 我们已经大致了解了NS2的基本架构和设计原理. 还有一些细节问题留到下一节对tclCL模块的类说明.
tclcl
tclCL是在OTcl基础上的封装, 它们与Tcl之间的关系如图Figure 2. tclCL实际上搭建了NS2的框架, NS2的类库都是建立在tclCL基础上的. 在文献3第三章简单介绍了tclCL的六个类: Tcl, TclObject, TclClass, TclCommand, EmbeddedTcl, InstVar.
其中, Tcl类可以看成是一个Tcl的C++接口类, 它提供C++访问Tcl库的接口. TclObject是Tcl/C++两个面向对象语言的类库的基类, 在最新的tclcl中, 采用了SplitObject的术语. TclClass注册编译分级, 保持了编译分级的层次结构, 同时给OTcl对象提供了创建C++对象的方法. TclCommand用于定义简单的全局解释命令. EmbeddedTcl是定制的Tcl命令. InstVar类包含了从Tcl访问C++类成员变量的方法.
这里不打算对tclcl的六个类再详细介绍了, 这篇文章到此为止已经基本达到它的目的. 下面的内容只挑选我认为是容易遗漏的地方. 文献3作为开发NS2手头必备的工具应该详细研读.
Class Tcl
Tcl类提供如下的方法
获取Tcl实例句柄
Tcl类只有一个实例, 该实例在NS2启动时初始化. 获取Tcl类的实例方法是: Tcl& tcl = Tcl::instance();
解析执行Tcl脚本命令
返回结果给Parser, 设置Tcl的环境变量
Tcl类的结果是指tcl_->result, 与脚本执行的退出码不同. 如下的代码 if (strcmp(argv[1], "now") == 0) {
tcl.resultf("%.17g", clock());
return TCL_OK;
}
tcl.result("Invalid operation specified");
return TCL_ERROR;
报告错误, 以一种统一的方式退出执行脚本
有两种错误退出方式, 一种是返回TCL_ERROR的退出码, 另一种是以tcl.error()函数退出, 两者稍有区别. 前者可以在解释器中trap这个错误, 然后打出出错的调用栈桢; 后者则不行.
存储并搜索TclObject对象
Tcl类中有一个C++对象的hash表, hash键是对象名. 这里值得注意的是C++对象的hash表而不是OTcl对象的hash表. 有些奇怪, 难道OTcl对象的hash表在OTcl模块中?
插入定制的Parser
tcl.interp(void)函数是tcl的Parser句柄, 可以修改它, 加入定制的Parser.
Class TclObject
在文献3中, OTcl对象称为解释分级对象, C++对象称为编译分级对象. TclObject并不包括Simulator, Node, Link, rtObject等对象. 对用户来说, 如果只使用Tcl配置脚本的话, 一般只看得到OTcl对象的创建, 而C++对象的创建正如我们前面分析的, 对用户是不可见的.
对象的创建和销毁
OTcl对象包括TclObject, Simulator等都使用在~/tclcl/tcl-object.tcl文件中定义的new{}和delete{}函数来初始化和销毁. 这一部分的内容前面已经分析的比较清楚.
每个OTcl对象在创建的时候会获取一个id返回给用户. OTcl的基类TclObject使用create-shadow{}函数来创建C++对象. 在C++对象的构造函数中一般都会调用属性绑定函数, 进行C++/OTcl属性绑定. C++对象创建后, 被插入到Tcl类对象的hash表中. 然后用C++对象的command()函数在OTcl对象中注册cmd{}函数.
下面的这张图比较清楚的反映了对象创建的过程.
OTcl中TclObject对象的函数create-shadow{}是TclClass注册的一个命令. 虚线表示C++编译器自动的调用父类的初始化函数.
变量绑定
OTcl/C++对象的属性初始化在~ns/tcl/lib/ns-default.tcl中做.
变量跟踪
命令方法
Class TclClass
Class TclCommand
Class EmbeddedTcl
Class InstVar
两个例子
本来打算写一两完整的例子, 展示如何向NS2中添加一个新的模块, 但我发现网上有两个比较好的资源可以借用910, 这里就免了.
结束语
这篇文章的后面两节实在没时间写下去了, 就省略, 省略, 再省略, ...
等我以后有时间在续吧.
References
[1] Extending Tcl for Dynamic Object-Oriented Programming
[2] Practical Programming in Tcl and Tk
[3] The ns Manual
[4] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/tutorial.html
[5] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/object.html
[6] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/class.html
[7] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/capi.html
[8] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/autoload.html
[9] http://nile.wpi.edu/NS/linkage.html
[10] http://140.116.72.80/~smallko/ns2/module.htm
题记
真正触动我写这篇短文的原因是试图理解NS2的基本原理. 在"the NS2 manual"中, 解释了为什么采用了两种语言来建构整个系统, 然后在第三章描述了tclcl的六个类. 这个手册中的对各个类描述性文字让我如坠雾里, 不明所以. 我查找了一些NS2的文章和站点, 有一些ppt倒是很形象, 但我的认识上总有些模糊. 后来, 我逐渐明白到OTcl/Tcl的嵌入特性. --- 这才是理解NS2框架的关键.
Abstract
本文的主要目的是理解NS2的architecture, 了解NS2的基本原理. NS2采用了Tcl/C++分裂的模型, 在这种模型中OTcl是处于比较关键的位置, NS2采用了Tcl的编程模式. 使用C++来编写应用实例, 使用OTcl来操纵这些实例. 理解了OTcl就理解了NS2的框架. 本文先简述Tcl语言的嵌入特性, 然后描述了NS2的应用场景, 进而分析NS2的架构, 以及实现该架构采用的技术.
Introduction
NS2是MIT的一个作品, 它是一个面向对象的网络仿真工具. 使用NS2可以完整的仿真整个网络环境, 只要你的机器足够快 :-) NS2使用一整套C++类库实现了大多数常见的网络协议以及链路层的模型, 使用这些类的实例我们就可以搭建起整个网络的模型, 而且包括了各个细节. --- 这简直就是一种梦想的实现, 试想如果手头能有这样一个工具, 我们就可以在单机环境中模拟网络的各个元素, 加深对网络的了解和认识; 同时, 加快我们开发新协议的速度.
与NS2类似的软件有OPNET, 这是一个商用的网络仿真软件, 据说它能够针对各款交换机和路由器来搭建网络, 够牛x. 与之相比, NS2是一个免费的软件, 它可以在Windows/Unix上运行, 我们可以看到NS2的所有源代码, 另外在学术界更多的是采用NS2来做仿真.
NS2采用了Tcl/C++分裂的模型来建构它的应用, 这样做的好处是显而易见的. 使用Tcl语言我们可以灵活的配置网络环境, 定制系统; 而采用C++来编程满足了我们对仿真效率的需要. 缺点也是明了的, 要同时维护两套代码, 对使用者要求较高.
NS2的Tcl/C++架构与Windows下的COM/VBScript编程模式有些类似, 使用VC来编写和实现COM对象, 然后使用VB来操纵COM对象. Windows提供了COM接口, 这就在系统范围内保证了这种机制的有效性. --- 这就是Windows的高明之处. 与之相比, NS2则能够使Tcl脚本解到它的C++类库结构, 同时按照它的类分级来创建对象. --- 这也很了不起.
要使用NS2来仿真开发一个新协议, 就必须对NS2的类库进行某些扩展. 撇开各个协议或链路的细节不谈, 对NS2的实现机制的了解是一个关键, 否则难免会疏漏百出. 如果不了解NS2的机制, 在刚开始开发协议时, 你看着NS2的代码可能会感觉到无处下手.
NS2的手册中对它的机制和原理主要在"the NS manual"一书的第三章, 但是这一章的内容写来就象tclcl类的简单介绍, 读来非常费解. 它对NS2的整体设计思路并没有交待的很清楚, 本文就打算解析这第三章背后的话.
以下的内容安排如下, 首先简单介绍Tcl语言的嵌入特性, 然后描述NS2的应用场景, 分析NS2的框架, 然后考察实现这个NS2框架所遇到的问题. 最后以一个新协议的添加作为例子, 感受NS2的仿真开发过程.
嵌入的Tcl
OTcl称为Objected Tcl, 它是在Tcl基础上的一个面向对象的封装. Tcl语言本身比较简单, 在NS2的主页上有一些相关的链接, 可以快速了解它的基本语法, 这里对Tcl的语法并不感兴趣.
Tcl是一种脚本语言, Tool Cammand Language. Tcl语言的吸引人之处在于它简单同时支持嵌入式应用. 大家都用过Ms Word或Emacs, 这两种编辑器之所以如此强大, 很大的原因是因为它们内嵌了VB或Lisp语言. 这些内嵌的脚本能够定制编译器的环境, 使应用变的非常灵活.
你可能还听说过Windows下的脚本引擎, 把一个脚本引擎嵌入到Windows应用中, 就可以使这个应用具有类似Word的这种能力. 在Unix下类似的脚本语言嵌入很早就有, 上面提到的Emacs就是一个例子. Tcl语言也支持内嵌, 具体做法是把Tcl的库链接到应用程序中去, 从而使应用具有解释Tcl语言的能力. 当然仅仅这么做是不够的, 你还要为这个应用定制开发一些Tcl命令.
(NS2为什么不使用Lisp作为内嵌? 这可能与OTcl语言本身也是MIT的作品有关, 而且是MIT的近期作品. 但是如果采用Lisp的话, 就可以在Emacs做这个仿真了, 呵呵, 想想都要偷笑了, 可惜呀. 毕竟我是个Emacs的拥护者.)
Figure 1是Tcl C与应用程序集成的原理图. Figure 1中黑线表示C的调用, 红线表示Tcl脚本的调用. 为了使应用能够解释Tcl语言, 必须在应用程序的源代码中嵌入Tcl C库, 这个库的主要目的是实现一个Tcl语言的Parser, 并且实现了Tcl语言的一些关键命令, 如set, while, proc等. 应用程序必须还要编写一些针对应用扩展的Tcl命令, 然后注册进Tcl C库, 同时, 应用程序可以使用Tcl_LinkVar使某些C变量与Tcl库中的环境变量绑定起来.
Tcl C库的一些函数接口如下:
Tcl_CreateInterp 创建Tcl的Parser;
Tcl_CreateCommand/Tcl_CreateObjCommand 注册应用相关的命令;
Tcl_Eval 执行一条Tcl命令;
Tcl_LinkVar 将应用程序中的变量与Tcl库环境中的变量绑定;
Tcl_GetVar/Tcl_SetVar 设定/获取Tcl库环境中的变量;
下面是一个取自文献2的例子: /*
* Example 47-1
* The initialization procedure for a loadable package.
*/
/*
* random.c
*/
#include
/*
* Declarations for application-specific command procedures
*/
int RandomCmd(ClientData clientData,
Tcl_Interp *interp,
int argc, char *argv[]);
int RandomObjCmd(ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
/*
* Random_Init is called when the package is loaded.
*/
int Random_Init(Tcl_Interp *interp) {
/*
* Initialize the stub table interface, which is
* described in Chapter 46.
*/
if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
return TCL_ERROR;
}
/*
* Register two variations of random.
* The orandom command uses the object interface.
*/
Tcl_CreateCommand(interp, "random", RandomCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "orandom", RandomObjCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
/*
* Declare that we implement the random package
* so scripts that do "package require random"
* can load the library automatically.
*/
Tcl_PkgProvide(interp, "random", "1.1");
return TCL_OK;
}
这个例子完整的体现了如何初始化Tcl C库, 如何向Tcl库中注册命令. 这里不打算继续讨论Tcl C库的详细问题了. 毕竟, 我们的目的是为了理解Tcl的嵌入能力, 上面的原理图已经足够.
NS2采用的是OTcl来实现它的脚本语言内嵌, 原因是NS2还有一套C++类库. 这个C++类库实现了网络仿真的各个元素, 仅仅是Tcl来操纵这套类库有些困难. 这关系到NS2的实现, 我们在后面谈.
OTcl的语法设计
这一部分的内容主要取自文献1, 放在这里的目的有两个: 一是为了了解OTcl的面向对象扩展, 从而能够方便的理解一些NS2的代码; 二是理解OTcl的实现原理.
文献1中主要介绍的是对Tcl语言本身的面向对象扩展, 它没有讲述实现如何操纵一个C++的对象. 从代码上来看OTcl本身好象并没有实现对C++对象的操纵.
OTcl和tclCL的相关文献都比较少, 这也许就是对NS2诟病较多的原因. OTcl是MIT的一个流媒体项目VuSystem的辅产品, 它并不是最早提出的Object概念的Tcl, 文献1认为OTcl的特点是Dynamic, 能够动态地创建一个对象.
OTcl的语法扩展
对OTcl的语法描述, 更详细的见网页45678.
OTcl的语言设计采用了称为"Object Command"的方法. 每个命令可能被解释成对象, 而子命令被解释成传递给对象的消息. 文献1中认为这样做可以比较方便的实现Tk的消息机制达到某种一致, 同时对象作为Tcl语言中array, list, proc等要素的补充, 这种扩展显得比较自然.
下面是OTcl一个一段示例代码, 我们可以看到对象astack的子命令set, proc等作为消息传递给对象. 而且要注意它确实是动态的, 在代码解释过程中动态的添加属性和方法. Object astack
astack set things {}
astack proc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
astack proc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrang $things 1 end]
return $top
}
astack put toast ==> toast
astack get ==> toast
astack destroy ==> {}
注意上面的instvar方法, 代码的前面用set定义了一个things的变量, 在方法内要操纵它必须使用instvar来声明, 有些怪, 是不是? 否则things将是一个方法内的局部变量. 下表是OTcl对象的方法:
name
description
class
创建一个对象
destroy
销毁一个对象
proc
定义Tcl对象方法
set
定义Tcl对象变量
instvar
绑定实例变量
info procs
列出Tcl对象的所有方法
info args
列出Tcl对象方法的参数格式
info body
列出Tcl对象方法的函数主体
info commands
列出Tcl/C的命令
info vars
列出Tcl变量
inof class
获取class名
在文献1中提到上面方法的语境(context)如下表. 这个语境感觉好象是专门用来显示的指出对象方法的作用域. --- 如果是这样的话, OTcl的名字空间管理好象有些问题.
name
description
type
self
对象名
变量
proc
方法名
变量
class
定义的类型方法
变量
next
下一个影子方法
方法
$self类似于C++类中的this指针, $proc给出方法名, $next是指父类的同名方法, 就是C++中的函数重载, 这关系到OTcl对象的多继承机制.
OTcl的语法的进一步解释45678
在OTcl中, 类(Class)和对象(objects)是区分开来的. 类表示一种类型, 对象是类的实例. 在OTcl中, 类可以看成是一种特殊的对象. 类标志对象的类名, 类中可以放置所有对象共享的变量, 类似于C++中的静态变量.
OTcl的基类称为Object, 注意它可不是前面所说的对象. 所有的类都是从Object派生而来.
OTcl的属性都是C++意义上的public的.
instproc用来定义类的方法, 而proc用来定义对象方法. 后者定义的方法只能用于该对象.
unset用来undefine一个变量.
OTcl中, 类的instproc函数init相当于C++中的构造函数.
superclass用于继承.
OTcl的继承机制
文献1对OTcl的继承机制描述的很清晰. OTcl支持类的多继承, 唯一的要求是继承关系满足DAG(有向无环图). OTcl的类继承可以简单地通过下面这个例子来理解.
Class Safety
Safety instproc init {} {
$self next
$self set count 0
}
Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
Safety instproc get {} {
$self instvar count
if {$count == 0} then { return {empty!} }
incr count -1
$self next
}
Class Stack
Stack instproc init {} {
$self next
$self set things {}
}
Stack instproc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
Stack instproc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrange $things 1 end]
return $top
}
Class SafeStack -superclass {Safety Stack}
SafeStack s
s put toast ==> toast
s get ==> toast
s get ==> empty!
s destroy ==> {}
上面的例子中, SafeStack从两个类派生而来Safety, Stack. OTcl使用"方法分发(Method Dispatch)"来描述如何从子类访问父类的重载方法. 如Figure 3所示.
从Figure 3可以了解到类是如何通过next方法来访问父类的重载方法的.
OTcl的C Api
OTcl给出了一个简洁的C Api接口, 通过这个接口我们可以在应用中访问OTcl中的对象. 主要接口描述见7和otcl.h文件. 从这些接口我们可以了解到OTcl本身并没有提供操纵C++类的方法, 这留给了tclCL来完成这一工作.
检查OTcl的Makefile, 可以看到它有三个目标: libotcl.a, owish, otclsh. 后两个都是shell程序, libotcl.a是我们关心的, 它就是OTcl的库, 当它被链接到应用程序中后, 应用程序就有了OTcl脚本内嵌的功能. libotcl.a: otcl.c
rm -f libotcl.a otcl.o
$(CC) -c $(CFLAGS) $(DEFINES) $(INCLUDES) otcl.c
ar cq libotcl.a otcl.o
$(RANLIB) libotcl.a
可以看到libotcl.a只由一个文件生成otcl.c, 这个c文件有两千多行, 好在otcl.h头文件很清晰. otcl.h一开始就声明了两个结构, 然后开始声明一些函数, 这些函数的名字很self meaning.
这里不打算继续分析otcl.c的源码了, 可以从otcl.h和OTcl的主页上了解一下C api的相关内容. 下面开始在tclcl中寻找我们感兴趣的内容.
NS2的应用场景与设计
有了上面对Tcl/OTcl的了解, 我们开始探询NS2仿真系统的设计原理. NS2的目标是仿真实际网络的各个元素, 对这些网络元素的描述有相关的资源可以获取. 如TCP协议的实现, 就可以从FreeBSD上获取, 由于TCP协议的实现版本不同, 就有了Reno发布等. 对链路的仿真, 主要是通过队列, 延迟等.
如何仿真这些网络元素是一回事, 对于NS2的系统架构是另一回事. 如此多的网络元素, 它们有可能功能重叠, 特定的仿真对象有不同的要求. 要满足这种灵活性, 一个可行的方案就是采用脚本定制仿真环境. 从而导致NS2有内嵌脚本语言的要求.
另一方面, 这些网络元素是分门别类的, 再有从实现效率上考虑, 使NS2采用了C++来对这些网络仿真对象建模. 这样就对应的要求脚本语言能够有与C++对象交互的能力. --- 这个要求可不低. 而MIT正好又有一种新开发的面向对象的脚本语言OTcl, 这些都促成了NS2采用OTcl.
NS2的应用场景是这样的: 用户在一个Tcl脚本中给出对仿真环境的描述, 键入ns xx.tcl启动NS2, NS2先完成一些初始化工作, 然后按照脚本的描述实例化各个仿真元素, 并把这些仿真元素连接起来, 在启动事件发生器, 触发网络元素动作, 中间有一些记录工作, 把这些仿真信息保存在磁盘上留待继续分析.
现在我们考虑这样一个系统如何设计. OTcl本身有对象的机制, Tcl脚本可以描述我们要仿真的对象. 但是我们遇到的问题是如何从OTcl来操纵NS2的C++对象, 这包括:
动态创建一个新的C++对象,
访问这个C++对象的属性,
调用该C++对象的方法.
C++对象的创建与删除
首先, 我们首先需要一种机制, 能够从一个描述类的字符串来动态的创建一个C++对象. 如果退到Tcl的方式, 我们就必须为每一个类写这样一种Tcl命令接口, 这不是一个好的解决方案, 它最大的问题是丧失了C++的继承关系. NS2的解决方案称为"Split Model", 它在OTcl上建立Tcl下的类, 与C++类分级保持一致, 每个C++类都对应一个OTcl的类, 但是问题还没有完全解决. 你在OTcl上初始化了一个对象, 必须要同时初始化一个对应的C++的对象.
为了解决这个问题, NS2在C++上使用了TclClass类来封装注册机制. 每个C++类都有一个对应的从TclClass派生而来的对象, 注意这里是对象, 是一个实例, NS2一启动就会实例化它. 该对象的主要目的是封装注册C++类的动态创建函数, 注册信息维护在一个hash表中, 该hash表是tclCL包的一部分, hash键是描述类的一个字符串计算而来.
接下来的问题是, 如何调用这个C++类的创建函数. NS2的方法很技巧, 前面所说的OTcl继承关系是一个关键, OTcl的对象的初始化函数都是init, 一个派生的OTcl对象首先是调用它的父类的init函数. 如前面的代码: Stack instproc init {} {
$self next
$self set things {}
}
这样, 一个OTcl的初始化肯定要调用到OTcl的Object类的init, 如果这个Object能够在此时初始化这个C++对象将是再理想不过. 这样一来, C++对象的初始化就对用户来说不可见了, 他只看到的是一个OTcl对象被初始化. NS2使用了TclObject而不是Object来派生所有的OTcl对象, 对应的, C++仿真对象也从一个C++的TclObject类派生. 由OTcl的根类TclObject来搜索C++类名hash表, 完成C++对象的创建工作. 还有一个小问题, init必须带入C++类名的字符串作为参数, 否则就没法查hash表了.
OTcl对象/C++对象的删除也是类似的道理. 至此, 第1个问题解决.
访问C++对象的属性
下一个问题是要从OTcl上操纵C++对象的属性. OTcl对象的属性并不一定要完全照抄C++对象的属性, OTcl对象属性的设计原则是能够方便的完成对C++属性的设置即可. 所以一般来说, OTcl对象属性集合要小于C++对象属性集合.
在分析如何访问C++对象属性之前, 先澄清一些OTcl名字空间的概念. OTcl是一个脚本程序, 它传统的继承了Unix下环境变量的概念, 变量的名字空间是扁平的. 而引入了对象机制后, 名字空间就有些复杂了. 显然一个OTcl对象的属性的名字与环境变量下的名字有可能重叠. 在OTcl中, 这称为名字的"context"语境.
对比C++对象的名字, 它是确定的, 这是因为有编译器的帮助, 编译器在编译一个C++源代码的时候它可以根据上下文来判断这里变量指的是什么. 而在OTcl的环境中, 也需要类似的机制, 由OTcl的Parser动态的确定一个名字的含义.
显然要确定一个名字的含义, 可以通过对象名来帮助作到这一点. 在OTcl中还有一个对象的hash表, 对象创建后要注册到这个hash表中. 对一个名字解释, 首先是要搜索对象hash表, 再搜索该对象的class及其父类, 参照前面的next指针.
在下面的代码中, $self instvar count这条语句就是切换context的. 如果不切换context, 我们将不知道是环境变量名还是其他的对象的属性. Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
对一个C++对象属性的访问, 有读/写两种操作. 由于存在OTcl/C++两个对象, 有可能它们的属性并不一致. 但是要注意到OTcl的对象属性是给用户看的, 只要保证在读/写OTcl对象属性的时候能够作到与C++对象一致就行了, 完全保持二者的一致性是没有必要的. 有了这样的要求, 下面的方式才是可行的.
NS2采用了一种trap机制来捕获对OTcl对象属性的访问. 具体来说, 在tclCL中是以InstVar类对象来封装这种Trap机制. Trap的位置安装在语境切换的时刻, 因为只有在语境切换后, 才有可能对该
OTcl对象的属性进行访问.
对C++对象非static属性的访问
现在来考虑一下实现trap或者说C++对象属性绑定的细节问题. 首先, 绑定的是一个C++对象的属性(对绑定一个C++类的static属性问题在后面谈), 这意味着要知道该C++对象属性的位置, 所以C++对象属性的位置是绑定的一个必要条件.
其次, 如何设计这个Trap? 假设我们要对某个OTcl的变量进行写操作, 一般的操作是利用Tcl的Parser直接写, 但是这里我们要保持与某个位置上的信息同步, 就还需要向这个位置写相应的信息. 要解决问题, 可以修改Tcl的Parser, 让它写同步位置即可. --- 这就是InstVar的思路, 我们可以在语境切换的时刻安装一个定制的Parser, 让这个定制的Parser来向C++对象属性的位置写, 问题就解决了.
剩下的问题是, 何时安装这样一个定制Parser? 原则上任何时候都是可以的, 只要你知道这个C++对象属性的位置. 但是一个方便的做法是在该C++对象构造函数内做. 当调用这个binding函数的时候, 它会在OTcl对象属性位置上做一个标记, 表示有绑定的属性. 当进行语境切换的时候, 如果在该OTcl对象的属性位置上有绑定标志, 则OTcl动态安装定制的Parser, 这个定制的Parser就是tclCL的InstVar的一个成员函数.
由于OTcl是脚本语言, 是若类型的语言, 它用字符串表示所有的变量, 只有当它在eval的时候才会知道它具体是char/int/real等. 所有InstVar有几个派生类, 原因是这个定制的Parser要向C++属性写不同类型的信息.
给个例子 ASRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_); \* real variable */
bind("requestor_", &requestor_); \* integer variable */
bind_time("lastSent_", &lastSessSent_); \* time variable */
bind_bw("ctrlLimit_", &ctrlBWLimit_); \* bandwidth variable */
bind_bool("running_", &running_); \* boolean variable */
}
对C++对象static属性的访问
C++对象的静态属性是放在局部堆上的, 而且是该类的所有对象共享的. 如果在C++对象的构造函数中绑定这个静态属性, 显然有效率上的问题, 在同时存在多个该类的C++对象时, 就会多次绑定. 更严重的是, 它绑定到的对应的是OTcl对象上, 这样在OTcl对象上该static属性就不同一了.
注意到NS2启动时, C++类对应的TclClass把C++类要注册到OTcl的类hash表上, 这个时刻是做对C++类工作的绝好机会.
分析OTcl的类/对象机制, 可以看到OTcl无法象C++对象一样有静态变量, 这算是OTcl设计的一个缺陷? 用OTcl类初始化一个OTcl对象, 意味着完整拷贝OTcl的信息. 对次, NS2采用了一种变通的方法. 它首先在OTcl类上添加了一个属性, 以后用该类初始化OTcl对象时, 所有对象都有这样一个属性. 然后在该属性上注册一个定制的Parser到这个属性上, 这个Parser直接访问了C++对象静态变量. ---这样就在OTcl对象上作出了一个静态变量. 见下面的从文献3中摘录的代码.
假设C++类的static变量为 class Packet {
......
static int hdrlen_;
};
Packet的TclClass中定义如下: class PacketHeaderClass : public TclClass {
protected:
PacketHeaderClass(const char* classname, int hdrsize);
TclObject* create(int argc, const char*const* argv);
\* These two implements OTcl class access methods */
virtual void bind();
virtual int method(int argc, const char*const* argv);
};
void PacketHeaderClass::bind()
{
\* Call to base class bind() must precede add_method() */
TclClass::bind();
add_method("hdrlen");
}
int PacketHeaderClass::method(int ac, const char*const* av)
{
Tcl& tcl = Tcl::instance();
\* Notice this argument translation; we can then handle them as if in TclObject::command() */
int argc = ac - 2;
const char*const* argv = av + 2;
if (argc == 2) {
if (strcmp(argv[1], "hdrlen") == 0) {
tcl.resultf("%d", Packet::hdrlen_);
return (TCL_OK);
}
} else if (argc == 3) {
if (strcmp(argv[1], "hdrlen") == 0) {
Packet::hdrlen_ = atoi(argv[2]);
return (TCL_OK);
}
}
return TclClass::method(ac, av);
}
OTcl脚本如下, 这个脚本模拟了读写两种操作. PacketHeader hdrlen 120
set i [PacketHeader hdrlen]
几点感想
定制Parser在上面的代码中体现无疑.
上面在对C++对象非static属性访问的代码中bind函数值得回味. 它把一个private/protected的属性给暴露出去了, 而C++编译器却照样编译通过, 有意思.
调用C++对象的方法
在NS2中, 很少有OTcl本身再实现一个对象的方法的, 因为从效率的角度考虑, 这样做会得不偿失. 一般的情况都是直接调用C++对象的方法来处理. 从实现上来看, 要调用一个对象的方法并不困难, 只要在合适的语境中, 给出参数直接调用就可以了. 所以NS2中实现对C++对象的方法的引用至多也就是定制Tcl的Parser.
注册顶级命令
OTcl继承了Tcl的某些特性, 可以通过TclCommand类定制一个顶级命令注册到OTcl的Parser中, 不过这种方法不值得推荐. 下面的例子3给出了实现方法:
要在OTcl中注册的顶级命令是hi: % hi this is ns [ns-version]
hello world, this is ns 2.0a12
下面是实现代码, 构造函数直接以TclCommand的构造函数注册"hi"命令, command()函数是命令的实现部分. class say_hello : public TclCommand {
public:
say_hello();
int command(int argc, const char*const* argv);
};
say_hello() : TclCommand("hi") {}
#include
int say_hello::command(int argc, const char*const* argv) {
cout << "hello world:"; for (int i = 1; i <> method?*/
virtual int command(int argc, const char*const* argv);
virtual void trace(TracedVar*);
...
可以看到, command()是一个虚函数, 这么做的目的是为了保证所有的command()函数都一样. 再看看3中给的一个例子: int ASRMAgent::command(int argc, const char*const*argv) {
Tcl& tcl = Tcl::instance();
if (argc == 3) {
if (strcmp(argv[1], "distance?") == 0) {
int sender = atoi(argv[2]);
SRMinfo* sp = get_state(sender);
tcl.tesultf("%f", sp->distance_);
return TCL_OK;
}
}
return (SRMAgent::command(argc, argv));
}
注意到最后一行return (SRMAgent::command(argc, argv)); --- 问题清楚了, command()函数是在C++对象一侧上溯到它的父类的, 这样做显然效率要高一些. 因此, ccommand()应该是在C++对象初始化的时候, 被注册到OTcl对象的cmd{}上.
最后交待一下OTcl如何调用cmd{}. 有两种方式, 一种是显示的调用cmd{} $srmObject cmd distance?
另一种是隐式的调用: $srmObject distance?
在隐式调用方式下, Tcl的Parser先检查有OTcl对象没有distance?这样的命令, 这里显然没有. 然后Parser会把解释权传递给OTcl对象的unknown{}函数, unknown{}函数会使用上面的显示调用的方式来调用C++对象的command()函数.
在OTcl对象中实现命令的例子
虽然在OTcl对象中实现方法的例子不常见, 文献3还是给出了一个例子: Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
这个例子说明了在OTcl对象中中重载C++对象的命令.
到现在为止, 我们已经大致了解了NS2的基本架构和设计原理. 还有一些细节问题留到下一节对tclCL模块的类说明.
tclcl
tclCL是在OTcl基础上的封装, 它们与Tcl之间的关系如图Figure 2. tclCL实际上搭建了NS2的框架, NS2的类库都是建立在tclCL基础上的. 在文献3第三章简单介绍了tclCL的六个类: Tcl, TclObject, TclClass, TclCommand, EmbeddedTcl, InstVar.
其中, Tcl类可以看成是一个Tcl的C++接口类, 它提供C++访问Tcl库的接口. TclObject是Tcl/C++两个面向对象语言的类库的基类, 在最新的tclcl中, 采用了SplitObject的术语. TclClass注册编译分级, 保持了编译分级的层次结构, 同时给OTcl对象提供了创建C++对象的方法. TclCommand用于定义简单的全局解释命令. EmbeddedTcl是定制的Tcl命令. InstVar类包含了从Tcl访问C++类成员变量的方法.
这里不打算对tclcl的六个类再详细介绍了, 这篇文章到此为止已经基本达到它的目的. 下面的内容只挑选我认为是容易遗漏的地方. 文献3作为开发NS2手头必备的工具应该详细研读.
Class Tcl
Tcl类提供如下的方法
获取Tcl实例句柄
Tcl类只有一个实例, 该实例在NS2启动时初始化. 获取Tcl类的实例方法是: Tcl& tcl = Tcl::instance();
解析执行Tcl脚本命令
返回结果给Parser, 设置Tcl的环境变量
Tcl类的结果是指tcl_->result, 与脚本执行的退出码不同. 如下的代码 if (strcmp(argv[1], "now") == 0) {
tcl.resultf("%.17g", clock());
return TCL_OK;
}
tcl.result("Invalid operation specified");
return TCL_ERROR;
报告错误, 以一种统一的方式退出执行脚本
有两种错误退出方式, 一种是返回TCL_ERROR的退出码, 另一种是以tcl.error()函数退出, 两者稍有区别. 前者可以在解释器中trap这个错误, 然后打出出错的调用栈桢; 后者则不行.
存储并搜索TclObject对象
Tcl类中有一个C++对象的hash表, hash键是对象名. 这里值得注意的是C++对象的hash表而不是OTcl对象的hash表. 有些奇怪, 难道OTcl对象的hash表在OTcl模块中?
插入定制的Parser
tcl.interp(void)函数是tcl的Parser句柄, 可以修改它, 加入定制的Parser.
Class TclObject
在文献3中, OTcl对象称为解释分级对象, C++对象称为编译分级对象. TclObject并不包括Simulator, Node, Link, rtObject等对象. 对用户来说, 如果只使用Tcl配置脚本的话, 一般只看得到OTcl对象的创建, 而C++对象的创建正如我们前面分析的, 对用户是不可见的.
对象的创建和销毁
OTcl对象包括TclObject, Simulator等都使用在~/tclcl/tcl-object.tcl文件中定义的new{}和delete{}函数来初始化和销毁. 这一部分的内容前面已经分析的比较清楚.
每个OTcl对象在创建的时候会获取一个id返回给用户. OTcl的基类TclObject使用create-shadow{}函数来创建C++对象. 在C++对象的构造函数中一般都会调用属性绑定函数, 进行C++/OTcl属性绑定. C++对象创建后, 被插入到Tcl类对象的hash表中. 然后用C++对象的command()函数在OTcl对象中注册cmd{}函数.
下面的这张图比较清楚的反映了对象创建的过程.
OTcl中TclObject对象的函数create-shadow{}是TclClass注册的一个命令. 虚线表示C++编译器自动的调用父类的初始化函数.
变量绑定
OTcl/C++对象的属性初始化在~ns/tcl/lib/ns-default.tcl中做.
变量跟踪
命令方法
Class TclClass
Class TclCommand
Class EmbeddedTcl
Class InstVar
两个例子
本来打算写一两完整的例子, 展示如何向NS2中添加一个新的模块, 但我发现网上有两个比较好的资源可以借用910, 这里就免了.
结束语
这篇文章的后面两节实在没时间写下去了, 就省略, 省略, 再省略, ...
等我以后有时间在续吧.
References
[1] Extending Tcl for Dynamic Object-Oriented Programming
[2] Practical Programming in Tcl and Tk
[3] The ns Manual
[4] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/tutorial.html
[5] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/object.html
[6] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/class.html
[7] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/capi.html
[8] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/autoload.html
[9] http://nile.wpi.edu/NS/linkage.html
[10] http://140.116.72.80/~smallko/ns2/module.htm
2008年11月19日星期三
NS相关学习资源(转)
[新手导航]NS相关学习资源(强烈推荐!!!)
(also see my blog at baidu.com)
(一). NS常用基本网站
1. Maillist个人觉得是寻求问题答案最好的地方。
http://mailman.isi.edu/pipermail/ns-users/
2. 柯老师的网站,包含很多非常实用资源:安装,trace文件分析,源代码分析,添加新协议的途径等
http://140.116.72.80/~smallko/ns2/ns2.htm
3. NS本身的一些bug等信息,如果你的问题怎么都解决不了,可以来这里看看,或许会使你豁然开朗:)
http://www.isi.edu/nsnam/ns/ns-problems.html
4. NS by example-入门必读,强烈推荐!!!
http://nile.wpi.edu/NS/
5. ns_tutorial-入门的经典资料
http://www.isi.edu/nsnam/ns/tutorial/
6. ns-FAQ(极其经典,强烈推荐!!!包括:trace分析,Threshold,多场景模拟等)
7. NS2的trace格式分析,包括各个字段的含义
http://www.k-lug.org/~griswold/NS2/ns2-trace-formats.html
8. NS@WPI FAQ一些常见问题
http://nile.wpi.edu/NS/FAQ.html
9. 这里有很多有用的资源,推荐!
http://web.syr.edu/~dchen02/resources.htm
10.网络技术方面(有TCP 方面的研究,有拥塞控制,新的TCP协议开发)
http://www.icir.org/floyd/
http://www-nrg.ee.lbl.gov/floyd/floyd.html
11.组播方面
http://www.cse.nd.edu/~striegel/research/REU/04-Fall-Leimkuhler/index.htm stealth multicast. 还有http://www.cse.nd.edu/research/GenMCast/index.html 是做GenMCast-- generic Mulitcast方面的!http://www.cisco.com/univercd/cc/td/doc/cisintwk/ito_doc/ipmulti.htm IP multicast 方面的介绍!
12. 卸载NS
http://mailman.isi.edu/pipermail/ns-users/2004-December/046262.html
(二). 常用结果分析工具
1. Gnuplot使用介绍
1) Gnuplot导读:http://learn.tsinghua.edu.cn/homepage/2001214728/Manual/95006/index.htm
2) Gnuplot详细说明:http://gnuplot.sourceforge.net/docs_4.1/node1.html
3) GNUPlot介绍: http://yinjilong.blogbus.com/logs/2004/11/509772.html
4) Gnuplot FAQ: http://www.gnuplot.info/faq/faq.html
2.Gawk使用介绍
awk详解:http://linux.chinaunix.net/doc/program/2005-02-03/1085.shtml
3.gcc使用说明
gcc使用指南:http://www.61ic.com/embed/kfhj/qt/200505/1330.html
4.Xgraph使用说明
XGRAPH:http://www.atl.external.lmco.com/projects/csim/xgraph/xgraph.html#anch1
(三). 怎样添加新协议
1.Step by step教你怎样添加新协议,包括怎样修改trace格式,非常经典! http://ants.dif.um.es/masimum/nsrt-howto/html/nsrt-howto.html
(四). 已经实现但尚未添加到NS的协议
1. 这个网站拥有大量的资源,包括各层协议源码,可以直接下载安装。 http://www.isi.edu/nsnam/ns/ns-contributed.html
2. MAC 802.15.4 协议实现 http://ees2cy.engr.ccny.cuny.edu/zheng/pub/
3. 多播协议扩展 http://www.monarch.cs.cmu.edu/multicast_extensions.html
4. AODV-UU 协议下载 http://www.docs.uu.se/docs/research/projects/scanet/aodv/aodvuu.shtml
5.Leach协议
http://www.internetworkflow.com/downloads/ns2leach/mit.tar.gz
6.Cluster Based Routing Protocol
http://www.comp.nus.edu.sg/~tayyc/cbrp/
(also see my blog at baidu.com)
(一). NS常用基本网站
1. Maillist个人觉得是寻求问题答案最好的地方。
http://mailman.isi.edu/pipermail/ns-users/
2. 柯老师的网站,包含很多非常实用资源:安装,trace文件分析,源代码分析,添加新协议的途径等
http://140.116.72.80/~smallko/ns2/ns2.htm
3. NS本身的一些bug等信息,如果你的问题怎么都解决不了,可以来这里看看,或许会使你豁然开朗:)
http://www.isi.edu/nsnam/ns/ns-problems.html
4. NS by example-入门必读,强烈推荐!!!
http://nile.wpi.edu/NS/
5. ns_tutorial-入门的经典资料
http://www.isi.edu/nsnam/ns/tutorial/
6. ns-FAQ(极其经典,强烈推荐!!!包括:trace分析,Threshold,多场景模拟等)
7. NS2的trace格式分析,包括各个字段的含义
http://www.k-lug.org/~griswold/NS2/ns2-trace-formats.html
8. NS@WPI FAQ一些常见问题
http://nile.wpi.edu/NS/FAQ.html
9. 这里有很多有用的资源,推荐!
http://web.syr.edu/~dchen02/resources.htm
10.网络技术方面(有TCP 方面的研究,有拥塞控制,新的TCP协议开发)
http://www.icir.org/floyd/
http://www-nrg.ee.lbl.gov/floyd/floyd.html
11.组播方面
http://www.cse.nd.edu/~striegel/research/REU/04-Fall-Leimkuhler/index.htm stealth multicast. 还有http://www.cse.nd.edu/research/GenMCast/index.html 是做GenMCast-- generic Mulitcast方面的!http://www.cisco.com/univercd/cc/td/doc/cisintwk/ito_doc/ipmulti.htm IP multicast 方面的介绍!
12. 卸载NS
http://mailman.isi.edu/pipermail/ns-users/2004-December/046262.html
(二). 常用结果分析工具
1. Gnuplot使用介绍
1) Gnuplot导读:http://learn.tsinghua.edu.cn/homepage/2001214728/Manual/95006/index.htm
2) Gnuplot详细说明:http://gnuplot.sourceforge.net/docs_4.1/node1.html
3) GNUPlot介绍: http://yinjilong.blogbus.com/logs/2004/11/509772.html
4) Gnuplot FAQ: http://www.gnuplot.info/faq/faq.html
2.Gawk使用介绍
awk详解:http://linux.chinaunix.net/doc/program/2005-02-03/1085.shtml
3.gcc使用说明
gcc使用指南:http://www.61ic.com/embed/kfhj/qt/200505/1330.html
4.Xgraph使用说明
XGRAPH:http://www.atl.external.lmco.com/projects/csim/xgraph/xgraph.html#anch1
(三). 怎样添加新协议
1.Step by step教你怎样添加新协议,包括怎样修改trace格式,非常经典! http://ants.dif.um.es/masimum/nsrt-howto/html/nsrt-howto.html
(四). 已经实现但尚未添加到NS的协议
1. 这个网站拥有大量的资源,包括各层协议源码,可以直接下载安装。 http://www.isi.edu/nsnam/ns/ns-contributed.html
2. MAC 802.15.4 协议实现 http://ees2cy.engr.ccny.cuny.edu/zheng/pub/
3. 多播协议扩展 http://www.monarch.cs.cmu.edu/multicast_extensions.html
4. AODV-UU 协议下载 http://www.docs.uu.se/docs/research/projects/scanet/aodv/aodvuu.shtml
5.Leach协议
http://www.internetworkflow.com/downloads/ns2leach/mit.tar.gz
6.Cluster Based Routing Protocol
http://www.comp.nus.edu.sg/~tayyc/cbrp/
NS简介(转)
NS是一种针对网络技术的源代码公开的、免费的软件模拟平台,研究人员使用它可以很容易的进行网络技术的开发,而且发展到今天,它所包含的模块已经非常丰富,几乎涉及到了网络技术的所有方面。所以,NS成了目前学术界广泛使用的一种网络模拟软件。在每年国内外发表的有关网络技术的学术论文中,利用NS给出模拟结果的文章最多,通过这种方法得出的研究结果也是被学术界所普遍认可的,此外,NS也可作为一种辅助教学的工具,已被广泛应用在了网络技术的教学方面。因此,目前在学术界和教育界,有大量的人正在使用或试图使用NS。
然而,对初学者来说,NS是非常难于掌握的,一般人从学习NS到上手至少需要半年多时间。原因是多方面的:一方面,NS内容庞杂,随软件所提供的手册更新不够快,初学者阅读起来非常困难;另一方面,使用NS还要掌握其它很多必备的相关知识以及相关工具,这会使初学者感到无从入手;有的使用者可能还不了解网络模拟的过程或是对NS软件的机制缺乏理解,这也影响了对NS的掌握。另外,不论在国外还是国内,还没有一本书能集中回答和解决这些问题,这也是NS难于被掌握的一个重要原因。
1、NS2简介
NS2(Network Simulator, version 2)是一种面向对象的网络仿真器,本质上是一个离散事件模拟器。由UC Berkeley开发而成。它本身有一个虚拟时钟,所有的仿真都由离散事件驱动的。目前NS2可以用于仿真各种不同的IP网,已经实现的一些仿真有:网络传输协议,比如TCP和UDP;业务源流量产生器,比如FTP, Telnet, Web CBR和VBR;路由队列管理机制,比如Droptail , RED和CBQ;路由算法,比如Dijkstra等。NS2也为进行局域网的仿真而实现了多播以及一些MAC 子层协议。
NS2使用C++和Otcl作为开发语言。NS可以说是Otcl的脚本解释器,它包含仿真事件调度器、网络组件对象库以及网络构建模型库等。事件调度器计算仿真时间,并且激活事件队列中的当前事件,执行一些相关的事件,网络组件通过传递分组来相互通信,但这并不耗费仿真时间。所有需要花费仿真时间来处理分组的网络组件都必须要使用事件调度器。它先为这个分组发出一个事件,然后等待这个事件被调度回来之后,才能做下一步的处理工作。事件调度器的另一个用处就是计时。NS是用Otcl和C++编写的。由于效率的原因,NS将数据通道和控制通道的实现相分离。为了减少分组和事件的处理时间,事件调度器和数据通道上的基本网络组件对象都使用C++写出并编译的,这些对象通过映射对Otcl解释器可见。
当仿真完成以后,NS将会产生一个或多个基于文本的跟踪文件。只要在Tcl脚本中加入一些简单的语句,这些文件中就会包含详细的跟踪信息。这些数据可以用于下一步的分析处理,也可以使用NAM将整个仿真过程展示出来。
2、使用NS进行网络仿真的方法和一般过程。
进行网络仿真前,首先分析仿真涉及哪个层次,NS仿真分两个层次:一个是基于OTcl编程的层次。利用NS已有的网络元素实现仿真,无需修改NS本身,只需编写OTcl脚本。另一个是基于C++和OTcl编程的层次。如果NS中没有所需的网络元素,则需要对NS进行扩展,添加所需网络元素,即添加新的C++和OTcl类,编写新的OTcl脚本。假设用户已经完成了对NS的扩展,或者NS所包含的构件已经满足了要求,那么进行一次仿真的步骤大致如下:
(1)开始编写OTcl脚本。首先配置模拟网络拓扑结构,此时可以确定链路的基本特性,如延迟、带宽和丢失策略等。
(2)建立协议代理,包括端设备的协议绑定和通信业务量模型的建立。
(3)配置业务量模型的参数,从而确定网络上的业务量分布。
(4)设置Trace对象。NS通过Trace文件来保存整个模拟过程。仿真完后,用户可以对Trace文件进行分析研究。
(5)编写其他的辅助过程,设定模拟结束时间,至此OTcl脚本编写完成。
(6)用NS解释执行刚才编写的OTcl脚本。
(7)对Trace文件进行分析,得出有用的数据。
(8)调整配置拓扑结构和业务量模型,重新进行上述模拟过程。
NS2采用两级体系结构,为了提高代码的执行效率,NS2 将数据操作与控制部分的实现相分离,事件调度器和大部分基本的网络组件对象后台使用C++实现和编译,称为编译层,主要功能是实现对数据包的处理;NS2的前端是一个OTcl 解释器,称为解释层,主要功能是对模拟环境的配置、建立。从用户角度看,NS2 是一个具有仿真事件驱动、网络构件对象库和网络配置模块库的OTcl脚本解释器。NS2中编译类对象通过OTcl连接建立了与之对应的解释类对象,这样用户间能够方便地对C++对象的函数进行修改与配置,充分体现了仿真器的一致性和灵活性。
3、NS2的功能模块NS2仿真器封装了许多功能模块,最基本的是节点、链路、代理、数据包格式等等,下面分别来介绍一下各个模块。
(1)事件调度器:目前NS2提供了四种具有不同数据结构的调度器,分别是链表、堆、日历表和实时调度器。
(2)节点(node):是由TclObject对象组成的复合组件,在NS2中可以表示端节点和路由器。
(3)链路(link):由多个组件复合而成,用来连接网络节点。所有的链路都是以队列的形式来管理分组的到达、离开和丢弃。
(4)代理(agent):负责网络层分组的产生和接收,也可以用在各个层次的协议实现中。每个agent连接到一个网络节点上,由该节点给它分配一个端口号。
(5)包(packet):由头部和数据两部分组成。一般情况下,packet只有头部、没有数据部分。
4、NS2的软件构成
NS2包含Tcl/Tk, OTcl, NS,Tclcl。其中Tcl是一个开放脚本语言,用来对NS2进行编程;Tk是Tcl的图形界面开发工具,可帮助用户在图形环境下开发图形界面;OTcl是基于Tcl/Tk的面向对象扩展,有自己的类层次结构;NS2为本软件包的核心,是面向对象的仿真器,用C++编写,以OTcl解释器作为前端;Tclcl则提供NS2和OTcl的接口,使对象和变量出现在两种语言中。为了直观的观察和分析仿真结果,NS2 提供了可选的Xgraphy、可选件Nam。
5、NS现有的仿真元素
从网络拓扑仿真、协议仿真和通信量仿真等方面介绍NS的相应元素:
(1)网络拓扑主要包括节点、链路。NS的节点由一系列的分类器(Classifier,如地址分类器等)组成,而链路由一系列的连接器(Connector)组成。
(2)在节点上,配置不同的代理可以实现相应的协议或其它模型仿真。如NS的TCP代理,发送代理有:TCP,TCP/Reno,TCP/Vegas,TCP/Sack1,TCP/FACK,TCP/FULLTCP等,接收代理有:TCPSINK,TCPSINK/DELACK。TCPSINK/SACK1,TCPSINK/SACK1/DELACK等。此外,还提供有UDP代理及接收代理Null(负责通信量接收)、Loss Monitor(通信量接收并维护一些接收数据的统计)。
(3)网络的路由配置通过对节点附加路由协议而实现。NS中有三种单播路由策略:静态、会话、动态。
(4)在链路上,可以配置带宽、时延和丢弃模型。NS支持:Drop-tail(FIFO)队列、RED缓冲管、CBO(包括优先权和Round-robin 调度)。各种公平队列包括:FQ,SFQ,DRR等。
(5)通信量仿真方面,NS提供了许多通信应用,如FTP,它产生较大的峰值数据传输;Telnet则根据相应文件随机选取传输数据的大小。此外,NS提供了四种类型的通信量产生器:EXPOO,根据指数分布(On/Off)产生通信量,在On阶段分组以固定速率发送,Off阶段不发送分组,On/Off的分布符合指数分布,分组尺寸固定;POO,根据Pareto分布(On/Off)产生通信量,它能用来产生长范围相关的急剧通信量;CBR,以确定的速率产生通信量,分组尺寸固定,可在分组间隔之间产生随机抖动;Traffic Trace,根据追踪文件产生通信量。
然而,对初学者来说,NS是非常难于掌握的,一般人从学习NS到上手至少需要半年多时间。原因是多方面的:一方面,NS内容庞杂,随软件所提供的手册更新不够快,初学者阅读起来非常困难;另一方面,使用NS还要掌握其它很多必备的相关知识以及相关工具,这会使初学者感到无从入手;有的使用者可能还不了解网络模拟的过程或是对NS软件的机制缺乏理解,这也影响了对NS的掌握。另外,不论在国外还是国内,还没有一本书能集中回答和解决这些问题,这也是NS难于被掌握的一个重要原因。
1、NS2简介
NS2(Network Simulator, version 2)是一种面向对象的网络仿真器,本质上是一个离散事件模拟器。由UC Berkeley开发而成。它本身有一个虚拟时钟,所有的仿真都由离散事件驱动的。目前NS2可以用于仿真各种不同的IP网,已经实现的一些仿真有:网络传输协议,比如TCP和UDP;业务源流量产生器,比如FTP, Telnet, Web CBR和VBR;路由队列管理机制,比如Droptail , RED和CBQ;路由算法,比如Dijkstra等。NS2也为进行局域网的仿真而实现了多播以及一些MAC 子层协议。
NS2使用C++和Otcl作为开发语言。NS可以说是Otcl的脚本解释器,它包含仿真事件调度器、网络组件对象库以及网络构建模型库等。事件调度器计算仿真时间,并且激活事件队列中的当前事件,执行一些相关的事件,网络组件通过传递分组来相互通信,但这并不耗费仿真时间。所有需要花费仿真时间来处理分组的网络组件都必须要使用事件调度器。它先为这个分组发出一个事件,然后等待这个事件被调度回来之后,才能做下一步的处理工作。事件调度器的另一个用处就是计时。NS是用Otcl和C++编写的。由于效率的原因,NS将数据通道和控制通道的实现相分离。为了减少分组和事件的处理时间,事件调度器和数据通道上的基本网络组件对象都使用C++写出并编译的,这些对象通过映射对Otcl解释器可见。
当仿真完成以后,NS将会产生一个或多个基于文本的跟踪文件。只要在Tcl脚本中加入一些简单的语句,这些文件中就会包含详细的跟踪信息。这些数据可以用于下一步的分析处理,也可以使用NAM将整个仿真过程展示出来。
2、使用NS进行网络仿真的方法和一般过程。
进行网络仿真前,首先分析仿真涉及哪个层次,NS仿真分两个层次:一个是基于OTcl编程的层次。利用NS已有的网络元素实现仿真,无需修改NS本身,只需编写OTcl脚本。另一个是基于C++和OTcl编程的层次。如果NS中没有所需的网络元素,则需要对NS进行扩展,添加所需网络元素,即添加新的C++和OTcl类,编写新的OTcl脚本。假设用户已经完成了对NS的扩展,或者NS所包含的构件已经满足了要求,那么进行一次仿真的步骤大致如下:
(1)开始编写OTcl脚本。首先配置模拟网络拓扑结构,此时可以确定链路的基本特性,如延迟、带宽和丢失策略等。
(2)建立协议代理,包括端设备的协议绑定和通信业务量模型的建立。
(3)配置业务量模型的参数,从而确定网络上的业务量分布。
(4)设置Trace对象。NS通过Trace文件来保存整个模拟过程。仿真完后,用户可以对Trace文件进行分析研究。
(5)编写其他的辅助过程,设定模拟结束时间,至此OTcl脚本编写完成。
(6)用NS解释执行刚才编写的OTcl脚本。
(7)对Trace文件进行分析,得出有用的数据。
(8)调整配置拓扑结构和业务量模型,重新进行上述模拟过程。
NS2采用两级体系结构,为了提高代码的执行效率,NS2 将数据操作与控制部分的实现相分离,事件调度器和大部分基本的网络组件对象后台使用C++实现和编译,称为编译层,主要功能是实现对数据包的处理;NS2的前端是一个OTcl 解释器,称为解释层,主要功能是对模拟环境的配置、建立。从用户角度看,NS2 是一个具有仿真事件驱动、网络构件对象库和网络配置模块库的OTcl脚本解释器。NS2中编译类对象通过OTcl连接建立了与之对应的解释类对象,这样用户间能够方便地对C++对象的函数进行修改与配置,充分体现了仿真器的一致性和灵活性。
3、NS2的功能模块NS2仿真器封装了许多功能模块,最基本的是节点、链路、代理、数据包格式等等,下面分别来介绍一下各个模块。
(1)事件调度器:目前NS2提供了四种具有不同数据结构的调度器,分别是链表、堆、日历表和实时调度器。
(2)节点(node):是由TclObject对象组成的复合组件,在NS2中可以表示端节点和路由器。
(3)链路(link):由多个组件复合而成,用来连接网络节点。所有的链路都是以队列的形式来管理分组的到达、离开和丢弃。
(4)代理(agent):负责网络层分组的产生和接收,也可以用在各个层次的协议实现中。每个agent连接到一个网络节点上,由该节点给它分配一个端口号。
(5)包(packet):由头部和数据两部分组成。一般情况下,packet只有头部、没有数据部分。
4、NS2的软件构成
NS2包含Tcl/Tk, OTcl, NS,Tclcl。其中Tcl是一个开放脚本语言,用来对NS2进行编程;Tk是Tcl的图形界面开发工具,可帮助用户在图形环境下开发图形界面;OTcl是基于Tcl/Tk的面向对象扩展,有自己的类层次结构;NS2为本软件包的核心,是面向对象的仿真器,用C++编写,以OTcl解释器作为前端;Tclcl则提供NS2和OTcl的接口,使对象和变量出现在两种语言中。为了直观的观察和分析仿真结果,NS2 提供了可选的Xgraphy、可选件Nam。
5、NS现有的仿真元素
从网络拓扑仿真、协议仿真和通信量仿真等方面介绍NS的相应元素:
(1)网络拓扑主要包括节点、链路。NS的节点由一系列的分类器(Classifier,如地址分类器等)组成,而链路由一系列的连接器(Connector)组成。
(2)在节点上,配置不同的代理可以实现相应的协议或其它模型仿真。如NS的TCP代理,发送代理有:TCP,TCP/Reno,TCP/Vegas,TCP/Sack1,TCP/FACK,TCP/FULLTCP等,接收代理有:TCPSINK,TCPSINK/DELACK。TCPSINK/SACK1,TCPSINK/SACK1/DELACK等。此外,还提供有UDP代理及接收代理Null(负责通信量接收)、Loss Monitor(通信量接收并维护一些接收数据的统计)。
(3)网络的路由配置通过对节点附加路由协议而实现。NS中有三种单播路由策略:静态、会话、动态。
(4)在链路上,可以配置带宽、时延和丢弃模型。NS支持:Drop-tail(FIFO)队列、RED缓冲管、CBO(包括优先权和Round-robin 调度)。各种公平队列包括:FQ,SFQ,DRR等。
(5)通信量仿真方面,NS提供了许多通信应用,如FTP,它产生较大的峰值数据传输;Telnet则根据相应文件随机选取传输数据的大小。此外,NS提供了四种类型的通信量产生器:EXPOO,根据指数分布(On/Off)产生通信量,在On阶段分组以固定速率发送,Off阶段不发送分组,On/Off的分布符合指数分布,分组尺寸固定;POO,根据Pareto分布(On/Off)产生通信量,它能用来产生长范围相关的急剧通信量;CBR,以确定的速率产生通信量,分组尺寸固定,可在分组间隔之间产生随机抖动;Traffic Trace,根据追踪文件产生通信量。
订阅:
博文 (Atom)