2009年6月29日星期一

我心中的两位股市大师

价值投资,不是"傻根投资"。理想的结果与实现的过程是两码事。"价值投资"根本不是一种投资理念,而是一种投资能力。
  进入2007年,中国股市进入相对"高位",几乎每天都有大量的投资者进入市场。相对于年末沪市收盘的2675点,指数近期涨幅并不大。但据业内统计,现在盘中已经有数百只股票见到历史新高,其中最为活跃的120只股票今年以来的绝对涨幅超过100%;此外,沪深两市目前已经没有了"资产净值"以下的股票。
  与此同时,股票型基金的业绩,却在高位徘徊,几乎完全复制指数的涨幅,看来,如果今年全年市场最终只是箱体震荡,那么选择"长线持股"的投资策略就有可能跟着股指上上下下地"坐电梯",至少收益率不会很高。
  比较一下两位华尔街的名人,他们代表短线与长线的不同的投资理念。这就是巴菲特与彼得・林奇,这两人都是市场大师,都是我们的偶像。但是在一个多元化的市场,经常比较一下他们的差异,还是很有必要的。
  首先,是关于资产规模的比较。从1956年做到1965年,巴菲特的投资基金年均增长率达30%,规模做到2600万美元;而彼得・林奇的投资基金,从1976年到1989年的13年间,年均增长率29%,虽然与巴菲特管理的投资合伙公司水平相当,但资产规模的扩张要快得多,13年的时间即由2000万美元增长到130亿美元。
  其次,是关于投资难易风格的比较。彼得・林奇最为擅长的是"短线",以便能够追随市场的波动。他相信,只要资金的规模足够大,你买到哪只股票,哪只股票必涨无疑。但是如此操作也具有相当的难度。彼得・林奇是在46岁的时候就宣布退休,因为压力太大,身体受损,这就是炒短线最大的坏处。但"从来不看电脑股票行情"的巴菲特的行业寿命,却一直持续到了今天。
  不过要讲投资,集中投资与分散投资,并不意味着前者一定强于后者。彼得・林奇在其职业生涯之中,轮番持有过1400多只股票,远远超过同一时期的巴菲特。
  其三,是关于投资成本的比较。让巴菲特做短线,行还是不行?可以说"肯定不行",这是因为巴菲特所控股的核心企业就是"美国地方政府"的保险公司,该公司可以为巴菲特提供大量的低成本现金,但不允许他进行短线套利交易。正因如此,所以巴菲特被迫当了几十年的"股市傻根",事实的真相是,"傻根"没赢,他只是把所有的风险都转嫁给了"华仔",包括"奶茶"。
  其四,关于机会成本,尽管巴菲特的"机会成本"为零,但在他的投资生涯之中,同样难免失手,这就是所谓投资无必胜。譬如巴菲特曾经投资于美国航空,彼得・林奇也曾经重仓美国航空这只股票,但后者是获利出局,而巴老先生却没有他那么幸运。1989年7月,巴菲特投资3.58亿美元购入美国航空的股票,当时美国航空普通股每股股价是50美元,此前的1981年至1988年,美国航空一直是令投资者印象深刻的蓝筹股,它的股东权益报酬率平均达14%,税前纯收益率在8%―12%。这样的股票,完全符合巴菲特的选股条件。而且航空业进入门槛高,属于寡头垄断,巴菲特坚信美国航空的普通股应该会有不错的表现。然而结果却事与愿违。航空公司经营的恶化,始于廉价航空公司和破产航空公司纷纷采取自杀性的低价战略,以及相对强势的航空工人工会具备极强的议价能力,致使工资成本高居不下。美国航空居然发生巨额亏损,股价从此一年不如一年。
  该次投资应是巴菲特为数不多的失败案例之一,以此看来,"天下无贼"毕竟只是传说,关键中的关键在于,这回巴菲特当的是"华仔",而不是运气好得要命的"傻根"。
  巴菲特喜欢低市盈率,但低市盈率并不总是好事,低市盈率有时仅仅表明这只股票(或这个市场)的买家极其稀少,股价极不活跃,或者就存在着你所不知道的"系统风险"。
  其五,关于可复制的难易程度的比较。这个已经不用我说,在当今世界,每10个股民中就有9个自认为可以效法巴菲特,以为巴菲特他老人家只是攥着几只好股票,然后"熬年头",一直熬成股神的。但事实上,巴菲特并非一般人可以复制的,想复制他的成功,比复制彼得・林奇更难,因为他对于看中的股票采用绝对控股的办法,他买下的是公司,而非公司的股票。在今天的美国,至少有30万人可以这样说,"我在巴菲特先生的公司里工作"。以此观之,彼得・林奇缝的是"被窝儿",但巴菲特把自己也"缝"进去了,你根本分不出哪一块是"被窝儿",哪一块是巴菲特他自己。
  对此,我们完全可以得出一个结论:价值投资,绝不是"傻根投资",理想的结果与实现的过程是两码事,恐怕不能相提并论。很多今年新入市的投资者并不晓得,一般而言,"价值投资"根本不是一种投资理念,而是一种投资能力。
1老巴不喜欢快速的成长。认为过快的成长会伤害公司股东利益。而林奇喜欢快速的成长,因为这里10倍股较多。
2老巴不喜欢零售业,因为零售业竞争太激烈,这个行业太危险,林奇喜欢,因为他喜欢零售连锁企业的高速扩张。
3老巴不喜欢转困型的股票,因为坏学生永远会是坏学生。林奇喜欢,因为他认为,好学生遭遇临时困难,度过危机后,还是好学生,
4老巴不喜欢价值被低估的股票,虽然他年轻的时候很喜欢,后来他把这种股票投资叫吸烟屁股,只能抽一口没什么意思。有经验后改成低估85%+成长15%,必须前提是持续成长,然后低价买入,二者缺一不可。而林奇喜欢,低价买入,等股价回升到其价值的三分之一时就抛掉。
5:老巴喜欢稳定移动的大象,林奇不喜欢,主要原因两个人管理的钱不一样。投资风格不一样。
6:老巴喜欢集中投资。长期持有,林奇喜欢分散投资,短期持有。
7:老巴不喜欢周期繁荣的股票,所以一生都是追求确定性高的垄断消费品行业,而林奇喜欢周期股,喜欢那种爆涨爆跌的预期游戏。
相同点:
1:老巴和林奇都以价值投资为基础,分析企业和行业能力超一流
2:老巴和林奇都不喜欢高科技股,因为看不利润,也看不到稳定性
3:都相信,从长期来看股价和企业业绩100%相关。
4:都相信,市场的愚蠢可以让我们挣大钱
5:都相信,长线是金
最后老巴被人叫成股神,林奇被叫做股圣

上证综指和我的“进化”

1992年涉足证券投资时,上证综指刚发布不久。股票的魔力激发了我无尽的求知欲。各种投资流派都加以学习、尝试,来者不拒。
经过一段时间的学习和实践,我归纳了证券投资的致胜金字塔、四项原则和八大课题。看似系统全面,本质上就是目前颇为流行的基本面选股、技术面选时,投资和投机相结合的方法。
这一阶段,上证综指在我的投资决策中起到重要的"风向标"作用。原因有二:一是我当时的投资系统中,在基本面选股的基础上,注重把握涨跌趋势,以技术指标的低、高档作为买卖股票的主要依据。二是当时绝大多数股票出现齐涨齐跌的局面,一切行动都听上证综指的"指挥"。
正当我自以为已经找到投资秘籍、准备大干一场的时候,"十月革命一声炮响送来了社会主义"。1995年底,我在在上海证券报《财经闲话》专栏阅读了孙涤教授介绍格雷厄姆、巴菲特思想的系列文章,"安全边际"、"买卖股票如同经营企业"的核心思想如露入心、醍醐灌顶,使我投资方式和人生轨迹都发生了重大转折。上证综指在我投资系统的作用也相应地发生了变化。
接受巴菲特思想后,我投资系统的进化过程,实际上就是不断的"去投机化"的过程,从脚踏投资、投机两条船,逐步过渡到两只脚都踏进价值投资一条船上。因为我认识到,以市场价格波动趋势为主要操作依据的买卖行为,应归类为"投机"。投机者A,长期试图判断 B、C、D,而B、C、D反过来也做着同样判断,这种博弈行为必然导致"囚徒困境",在不自觉中形成难以摆脱的"投机陷阱"。而且就投机者的整体而言,因为交易磨损成本的存在,其失败是先天的、必然的。
格雷厄姆、巴菲特的投资方法有两大基础优势:一是安全边际使投资者有犯错的空间,避免损失;二是这种方法深深地植根于对心理学的了解,具有巨大的心理优势,不会因为情绪波动而导致决策失误。相反,技术分析等投机的模式只看重价格趋势,忽视公司质地、价值评估,经常如"高空走钢丝"或一直在悬崖边上行走,缺乏安全边际的保护,唯一的救命方式是停损,但碰到"黑天鹅"等突发事件,停损根本无法执行,存在 "一失足成千古恨"而前功尽弃的致命风险。而且技术分析等投机方法,缺乏心理学的支持,心理因素对投机者的成功构成强烈的威胁。如果把技术分析等投机方法引进价值投资,将使价值投资的心理优势荡然无存,这无疑是一种引狼入室的行为。
我进化的过程并非一帆风顺,中间伴随着困惑、摇摆和试错。经过对巴菲特思想的长期学习、思考和实践,直到2002年,我的投资系统才基本定型,变得纯粹、专注起来。
在这个进化过程中,我对宏观经济和股市趋势的分析不断淡化,逐步专注于公司长期竞争优势的分析。因为我认识到,宏观经济和股票市场是庞大的复杂适应性系统,没有任何模型能准确预测。格雷厄姆在《证券分析》中指出:"人们不可能对处在人类自身控制之下的经济事件作出科学预测。这种预测的"可靠性"将使人们采取相应的行动,而这种行动反过来推翻当初的预测"
所以,上证综指的短期趋势是不可知的,也不重要。对上证综指趋势的正确态度是判断、调整、适应,重在应对而不是预测。只有在少数极端的情形下,分析家才能对上证综指的中长期走向发出权威的声音,其他大部分时间的预言都是苍白无力的。
而且股票市场和宏观经济之间的关系也相当复杂,并非像教科书上所说的"股市是宏观经济的晴雨表"那么简单。
2001年11月10日,美国《财富》杂志发表了《巴菲特谈股市》一文。文中巴菲特重申了股市整体表现长期来说与美国经济整体增长性相关。过度高估或过度低估的股价长期而言肯定会回归其内在价值。但是,巴菲特还以翔实的历史数据说明了1899年―1998年的100年间,美国股市整体走势与GNP走势相背离的现象。指出影响股市走向有三个关键因素:利率、人们对未来投资报酬率的预期和心理因素。
有"德国巴菲特"之称的安德烈・科斯托兰尼曾讲述了一个"男人和狗"的寓言:有一个男人带着狗在街上散步,像所有的狗一样,这狗先跑到前面,再回到主人身边。接着,又跑到前面,看到自己跑得太远,又再折回来。整个过程里,狗就这样反反复复。最后,他俩同时抵达终点,男子悠闲地走了1公里,而狗跑来跑去,走了4公里――在这个寓言里,男子就是经济,狗则是股市。"长远看来,经济和证券市场的发展方向相同,但在过程中,却有可能选择完全相反的方向。"
居于以上认识,上证综指在我投资决策中的作用也相应从"风向标"逐步演变成了"温度计",从"老师"变成了"朋友"。通过评估上证综指所处区域的估值水平,判断市场是否处于过热或过冷阶段,有助于对股市的长期趋势作出判断,对投资决策和资产配置会起到一定的参考作用。此外,我还把上证综指作为长期投资业绩的评价标准,每五年拿自己投资业绩和上证综指相比较,检讨得与失。
本杰明・富兰克林说过:"回忆生命并把它记录下来,无异于重获新生。这样,它就能够长时间流传下来,历久弥新。"回顾我近18年的投资生涯,上证综指先是我的"严师",后是我的"益友",一直伴随着我的成长。

新人入行指导

每个项目都有遇到新人进入团队后的指导工作,公司只是在大的层面上指导,更多的还是靠新人自己去摸索。这里转贴,有很好的参考价值,希望对大家有用。
从2003年加入现在的公司,已经有了快6个年头,自己也从一个计算机软件开发方面的新兵变成了老人。在公司里也做过几个不同的项目,有一些新的同事加
入项目组,会有这样那样的疑惑和问题。在这里想简单说说,一个新人如何能快速的融入一个新的开发组,让其他同事能够接受自己。
首先是读文档。计算机方面有个著名的黑话叫做RTFM,什么意思呢?按字面翻译就去"去读他妈的文档",这是在新闻组或者论坛里可能常见的回复,一些人
娇滴滴的说"我是妹妹,能指导一下这个问题么"或者贱兮兮的"跪求某某问题答案"。当然,同事之间不可能搞这些,不过也经常碰到有人问一些感觉非常简单
问题,这些问题实际上都在项目文档或者软件规范里就明白写着。
一个新人加入某个项目,或者转换到一个新项目,都会感觉到手足无措,就像老虎吃刺猬不知如何下嘴。领导不会让这样的新人去完成一些复杂模块的开发或者修改一些相对困难的bug,基本上都是先分一些界面上的小改动,或者是让他开发耦合程度低一些、相对独立一些的功能作为练手,这个时候作为一个新人就要尽快的掌握整个项目的大概,然后泛读一下项目的重要文档。如果要开发某个业内标准的实现软件,那这个标准的相关文档至少要通读一遍,不需要投入很长时间,可以快速掌握一下大概,做做简单的笔记,不懂的地方先记下来以后有空再说。读文档不要作为一个整体任务完成,可以用一些零碎时间来读,以免很长时间没有
什么进展,领导看了还以为在磨洋工呢。
**注意事项一,尽管有这样那样的软件支持,一支笔一个本子仍然是最方便最快速最实用的学习工具,我几乎每年都要写掉八九个大笔记本,里面写着项目开发
的心得、文档书籍的感受、领导指示的一些开发问题等等,不需要有什么文章格式,先写下来就可以了。
如果是第一次进入公司,项目组长会分一个任务作为对新人水平的考察,就好比网游中的新手任务。我们公司大多数的新手任务都是半个月一个月左右的时间,注意要尽量赶在期限结束前完成所有的编码和单元测试,而且最好完成代码清理和代码注释工作,注意命名规则,这样看起来比较专业一些。
**注意事项二,接到一个项目第一个要问的就是这个项目结束期限(dead
line)是什么,这样心里比较容易对进度有个估计,免得最后无法完成任务。领导可能会反过来咨询老程序员对分到的项目难度估计。一般来说,估计一个大概的编码时间,然后把估计时间乘二,留出一定余量比较好。
读文档不要作为一个整体任务完成,可以用一些零碎时间来读。实际上接手一个新任务,必须要做的就是理解需求。一个开发人员如何不理解他想做什么,基本上这个任务一定会失败。我们高考时候都会写一篇大作文,对题目的理解非常重要,偏题跑题就没法拿到高分,开发也是如此。对需求的理解需要反复的进行,定期和项目组领导或者客户进行沟通,以免自己做了无用功。但是沟通之前一定要注意,自己先掌握一定的背景知识,比如前面提到的规范文档,或者是读一读已经有的代码,跑一跑成型的产品,免得问的都是不必要的问题。
搜索一下可以找到一篇题为"提问的智慧"的文章,里面介绍如何在网上问问题。其实项目组内沟通也是如此,要注意问有意义的问题。打个比方说,有时候自己会有一种感觉,跑到别人面前,把问题说了一遍,还没等人回答,突然拍着脑袋说"啊我明白了",也许是反应挺快,可是还是耽误了别人的功夫,像这样的问题,自己组织组织语言或者写出来,答案就很容易发现。另外也有的人,总是这是怎么回事那是怎么回事,其实到搜索引擎一搜或者是把动手做做就知道结果,但是非要张嘴问,这样的人说得难听点就是问题不经过大脑,其实一思考就能得到答案了。问问题之前也可以试着理清一下思路,看看前因后果,简化一下问题模型,也许经过这些方式,自己就能找到答案。有同组的同事问问题,很多时间我反问几句,把思路理清,他自己就知道答案是什么了。忘记在哪有看到一个轶事,在某个著名软件公司里,开发组的桌上会放着一只小熊,大家互相问问题之前,先对着小熊把问题说一遍,看能不能把问题描述的清晰,基本上说的比较有条理以后,答案也就随之而来了,大家可以试试这个办法。
**注意事项三,提问之前,自己先试图看看能不能找到答案,我建议的寻找顺序是文档、google,最后才是张嘴问,问之前最好已经积累了一些材料,比如关于这个问题自己做了什么研究,搜索了什么关键字等等,这样问的有诚意,回答才能有诚意。
另外,对于一个新人,需要多跟老同事沟通,了解项目的关键点是什么,比如开发一个通讯程序,用了什么协议,哪个网站比较有用,哪个文档需要精读,都是非常有意义的问题,这些问题可以少走一些弯路。早上到了公司,第一件事是接收业务邮件,然后记下一些需要回复或者要做的工作。邮件阅读以后,可以跟项目组长做个简单的沟通,了解一下哪个任务或者功能需要快一点完成,交流一下自己的想法,时间花费不多但是可以把一天的任务明确下来。
如果比较努力而且有一定的开发能力,两三个月以后应该开始接触到项目比较核心的东西了,这个时候需要做的工作就是读代码。一个项目最重要的东西就是代
码,至于文档、注释、测试,其实都是保证代码质量以及代码可维护型的一种辅助手段,作为一个开发人员,不熟悉代码就是致命的错误。
读代码我这里有一些简单的体会。
读代码要注意的第一条是从界面开始,深入到功能。打个比方说,一个桌面软件,其中有个格式转换功能,可以从菜单选择"格式转换"进行操作(注意,所谓格式转换就是一个例子,没有实际意义),那么就可以搜索菜单里的"格式转换",找到相对应的界面函数,这就是一个相对独立的功能入口点了。然后从这个入口点顺藤摸瓜,就可以搞清楚格式转换这个功能需要的一系列界面函数以及逻辑实现函数。
读代码的时候注意要随时做笔记,可以用word或者是powerpoint这样的软件做记录,搭配一个抓屏软件抓取界面变化、程序运行栈或者是一些关键
数据就更好了。
一个相对独立的功能基本上是由几个数据类或者数据结构,加上几个比较重要的逻辑函数实现的,抓住了这些关键就抓住了这个功能的核心。比如说一个通讯软
件,重要的就是通讯数据格式和通讯协议实现。经过我的体验,这个小窍门还是很有用的。一般经常出现问题的也就是这些相对来说复杂一些的函数。
阅读代码的同时还要经常问自己一些问题,比如这个地方为何这样实现,有没有其他的方案,哪个方案更好一些等等。这些问题可以让自己更好的理解当时开发人员的一些想法思路,另外也是将来代码进行重构的一个铺垫。
**注意事项四,作为一个开发人员,多思考是非常必要的一个特质。
一个新人,经过这些阶段,基本上可以成为项目组的中坚力量了,希望每个入行的新人都能成功晋级成为老手。

使用TCP协议的NAT穿透技术-原理及实现

本文来自:http://blog.csdn.net/ssihc0/archive/2008/10/10/3053395.aspx
其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之。
一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基本上没有人会告诉你如何使用TCP协议去穿透(甚至有的人会直接告诉你TCP协议
是无法实现穿透的)。但是,众所周知的是,UDP是一个无连接的数据报协议,使用它就必须自己维护收发数据包的完整性,这常常会大大增加程序的复杂度,而
且一些程序由于某些原因,必须使用TCP协议,这样就常常令一些开发TCP网络程序的人员"谈穿透色变"。那么,使用TCP协议是不是就不能实现穿透呢?
答案当然是否定的:TCP协议不仅能实现NAT穿透,而且实现起来比UDP穿透甚至还简单一些。
要了解如何使用TCP穿透NAT,就要首先看看如何使用UDP穿透NAT。
我们假设在两个不同的局域网后面分别有2台客户机A和
B,AB所在的局域网都分别通过一个路由器接入互联网。互联网上有一台服务器S。
现在AB是无法直接和对方发送信息的,AB都不知道对方在互联网上真正的IP和端口,
AB所在的局域网的路由器只允许内部向外主动发送的信息通过。对于B直接发送给A的路由器的消息,路由会认为其"不被信任"而直接丢弃。
要实现
AB直接的通讯,就必须进行以下3步:A首先连接互联网上的服务器S并发送一条消息(对于UDP这种无连接的协议其实直接初始会话发送消息即可),这样S
就获取了A在互联网上的实际终端(发送消息的IP和端口号)。接着
B也进行同样的步骤,S就知道了AB在互联网上的终端(这就是"打洞")。接着S分别告诉A和B对方客户端在互联网上的实际终端,也即S告诉A客户B的会
话终端,S告诉B客户A的会话终端。这样,在AB都知道了对方的实际终端之后,就可以直接通过实际终端发送消息了(因为先前双方都向外发送过消息,路由上
已经有允许数据进出的消息通道)。
用UDP来实现以上3步不存在什么理论上的问题,因为UDP是无连接的协议,它允许socket进行"多对一"的通讯(即几个具有不同IP和端口号的
socket向一个接收socket发送消息)。但是使用TCP就出现了问题:在一般情况下,TCP
socket不允许在已经建立连接的端口上再进行监听和使用该本地端口。换句话说,当AB连接上服务器S后,S将AB的实际终端告诉对方,下一步本该是
AB利用对方的实际终端进行直连,但这时你会发现对方的实际终端已经被占用了(就是各自连接到服务器S的会话占用了终端),无法同时listen和
connect。于是很多人得出结论:TCP无法实现NAT穿透。
于是问题的关键变成了如何复用一个TCP连接的本地终端,这其实不是协议的问题,而是一个API的问题。幸运的是,所有主流操作系统都支持一个特定的
TCP套接字选项——SO_REUSEADDR。这个选项允许将多个socket绑定到同一个本地终端。我们建立socket的时候只要加上这么一行:
setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &flag, len) ; //C++就这么做
_Client.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReuseAddress, True) '这是vb.net 更加简单
知道上面的知识就很好办了,下面我来说说TCP协议的穿透流程:
机器布局还是和上面使用UDP的一样。现在假设客户A想和客户B建立TCP连接。
首 先还是
AB分别和服务器S分别建立连接,S记录AB的互联网实际终端。然后S分别向AB发送对方的实际终端。接着,从A和B向S连接时使用的端口,AB都异步调
用connect函数连接对方的实际终端(就是S告诉的终端),同时,AB双方都在同一个本地端口监听到来的连接(也可以先监听,再connect更
好)。由于双方都向对方发送了connect请求(假设各自的SYN封包已经穿过了自己的NAT),因此在对方connect请求到达本地的监听端口时,
路由器会认为这个请求是刚刚那个connect会话的一部分,是已经被许可的,本地监听端口就会用SYN-ACK响应,同意连接。这样,TCP穿透NAT
的点对点连接就成功了。
下面是示例代码下载,VB.NET代码,演示如何用TCP协议穿透NAT实现文件传送,请用vs2005打开解决方案
http://dl2.csdn.net/down4/20070724/24133943521.rar
代码中有一个我自己封装的模仿vb6
winsock的控件ZXMSocket,这个socket可以让你设置是否使用SO_REUSEADDR参数,socket是事件驱动的。
如果你要测试代码,需要使用一个bat来启动发送和接收程序(文件格式请参照bin/Debug文件夹
下的run.bat文件),这个bat的功能是以命令行的方式告诉程序登录服务器缩使用的用户名,对于服务器来说,这个用户名必须是唯一的,当然,这可能
有点不科学,但是这毕竟只是一个demo。

P2P之UDP穿透NAT的原理与实现

本文来自:http://hi.baidu.com/wangzhe1945/blog/item/3e72fffe47fc2f365d60080c.html
源码下载: http://www.ppcn.net/upload/2005_08/05080112299104.rar
参考: http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
文章说明:
关于UDP穿透NAT的中文资料在网络上是很少的,仅有<<P2P之UDP穿透NAT的原理与实现(shootingstars)>>这篇文章有实际的参考价值。本人近两年来也一直从事P2P方面的开发工作,比较有代表性的是个人开发的BitTorrent下载软件
- FlashBT(变态快车).
对P2P下载或者P2P的开发感兴趣的朋友可以访问软件的官方主页:
http://www.hwysoft.com/chs/
下载看看,说不定有收获。写这篇文章的主要目的是懒的再每次单独回答一些网友的提问,
一次性写下来,
即节省了自己的时间,也方便了对于P2P的UDP穿透感兴趣的网友阅读和理解。对此有兴趣和经验的朋友可以给我发邮件或者访问我的个人Blog留言:
http://hwycheng.blogchina.com. 您可以自由转载此篇文章,但是请保留此说明。
再次感谢shootingstars网友的早期贡献. 表示谢意。
NAT(The IP Network Address Translator) 的概念和意义是什么?
NAT, 中文翻译为网络地址转换。具体的详细信息可以访问RFC 1631 -
http://www.faqs.org/rfcs/rfc1631.html,
这是对于NAT的定义和解释的最权威的描述。网络术语都是很抽象和艰涩的,除非是专业人士,否则很难从字面中来准确理解NAT的含义。
要想完全明白NAT
的作用,我们必须理解IP地址的两大分类,一类是私有IP地址,在这里我们称作内网IP地址。一类是非私有的IP地址,在这里我们称作公网IP地址。关于IP地址的概念和作用的介绍参见我的另一篇文章:
http://hwycheng.blogchina.com/2402121.html
内网IP地址: 是指使用A/B/C类中的私有地址,
分配的IP地址在全球不惧有唯一性,也因此无法被其它外网主机直接访问。公网IP地址:
是指具有全球唯一的IP地址,能够直接被其它主机访问的。
NAT
最初的目的是为使用内网IP地址的计算机提供通过少数几台具有公网的IP地址的计算机访问外部网络的功能。NAT
负责将某些内网IP地址的计算机向外部网络发出的IP数据包的源IP地址转换为NAT自己的公网的IP地址,目的IP地址不变,
并将IP数据包转发给路由器,最终到达外部的计算机。同时负责将外部的计算机返回的IP数据包的目的IP地址转换为内网的IP地址,源IP地址不变,并最终送达到内网中的计算机。
图一: NAT 实现了私有IP的计算机分享几个公网IP地址访问Internet的功能。
随着网络的普及,IPv4的局限性暴露出来。公网IP地址成为一种稀缺的资源,此时NAT
的功能局限也暴露出来,同一个公网的IP地址,某个时间只能由一台私有IP地址的计算机使用。于是NAPT(The
IP Network Address/Port
Translator)应运而生,NAPT实现了多台私有IP地址的计算机可以同时通过一个公网IP地址来访问Internet的功能。这在很大程度上暂时缓解了IPv4地址资源的紧张。
NAPT
负责将某些内网IP地址的计算机向外部网络发出的TCP/UDP数据包的源IP地址转换为NAPT自己的公网的IP地址,源端口转为NAPT自己的一个端口。目的IP地址和端口不变,
并将IP数据包发给路由器,最终到达外部的计算机。同时负责将外部的计算机返回的IP数据包的目的IP地址转换内网的IP地址,目的端口转为内网计算机的端口,源IP地址和源端口不变,并最终送达到内网中的计算机。
图二: NAPT 实现了私有IP的计算机分享一个公网IP地址访问Internet的功能。
在我们的工作和生活中,
NAPT的作用随处可见,绝大部分公司的网络架构,都是通过1至N台支持NAPT的路由器来实现公司的所有计算机连接外部的Internet网络的。包括本人在写这篇文章的时候,也是在家中使用一台IBM笔记本通过一台宽带连接的台式机来访问Internet的。我们本篇文章主要讨论的NAPT的问题。
NAPT(The IP Network Address/Port Translator) 为何阻碍了P2P软件的应用?
通过NAPT
上网的特点决定了只能由NAPT内的计算机主动向NAPT外部的主机发起连接,外部的主机想直接和NAPT内的计算机直接建立连接是不被允许的。IM(即时通讯)而言,这意味着由于NAPT内的计算机和NAPT外的计算机只能通过服务器中转数据来进行通讯。对于P2P方式的下载程序而言,意味着NAPT内的计算机不能接收到NAPT外部的连接,导致连接数用过少,下载速度很难上去。因此P2P软件必须要解决的一个问题就是要能够在一定的程度上解决NAPT内的计算机不能被外部连接的问题。
NAT(The IP Network Address Translator) 进行UDP穿透的原理是什么?
TCP/IP传输时主要用到TCP和UDP协议。TCP协议是可靠的,面向连接的传输协议。UDP是不可靠的,无连接的协议。根据TCP和UDP协议的实现原理,对于NAPT来进行穿透,主要是指的UDP协议。TCP协议也有可能,但是可行性非常小,要求更高,我们此处不作讨论,如果感兴趣可以到Google上搜索,有些文章对这个问题做了探讨性的描述。下面我们来看看利用UDP协议来穿透NAPT的原理是什么:
图三: NAPT 是如何将私有IP地址的UDP数据包与公网主机进行透明传输的。
UDP协议包经NAPT透明传输的说明:
NAPT为每一个Session分配一个NAPT自己的端口号,依据此端口号来判断将收到的公网IP主机返回的TCP/IP数据包转发给那台内网IP地址的计算机。在这里Session是虚拟的,UDP通讯并不需要建立连接,但是对于NAPT而言,的确要有一个Session的概念存在。NAPT对于UDP协议
包的透明传输面临的一个重要的问题就是如何处理这个虚拟的Session。我们都知道TCP连接的Session以SYN包开始,以FIN包结束,NAPT可以很容易的获取到TCP
Session的生命周期,并进行处理。但是对于UDP而言,就麻烦了,NAPT并不知道转发出去的UDP协议包是否到达了目的主机,也没有办法知道。而且鉴于UDP协议的特点,可靠很差,因此NAPT必须强制维持Session的存在,以便等待将外部送回来的数据并转发给曾经发起请求的内网IP地址的计算机。NAPT具体如何处理UDP
Session的超时呢?不同的厂商提供的设备对于NAPT的实现不近相同,也许几分钟,也许几个小时,些NAPT的实现还会根据设备的忙碌状态进行智能计算超时时间的长短。
图四: NAPT 将内部发出的UDP协议包的源地址和源端口改变传输给公网IP主机。
图五: NAPT
将收到的公网IP主机返回的UDP协议包的目的地址和目的端口改变传输给内网IP计算机现在我们大概明白了NAPT如何实现内网计算机和外网主机间的透明通讯。现在来看一下我们最关心的问题,就是NAPT是依据什么策略来判断是否要为一个请求发出的UDP数据包建立Session的呢?主要有一下几个策略:
A. 源地址(内网IP地址)不同,忽略其它因素, 在NAPT上肯定对应不同的Session B.
源地址(内网IP地址)相同,源端口不同,忽略其它的因素,则在NAPT上也肯定对应不同的Session
C.
源地址(内网IP地址)相同,源端口相同,目的地址(公网IP地址)相同,目的端口不同,则在NAPT上肯定对应同一个Session
D.
源地址(内网IP地址)相同,源端口相同,目的地址(公网IP地址)不同,忽略目的端口,则在NAPT上是如何处理Session的呢?
D的情况正式我们关心和要讨论的问题。依据目的地址(公网IP地址)对于Session的建立的决定方式我们将NAPT设备划分为两大类:
Symmetric NAPT: 对于到同一个IP地址,任意端口的连接分配使用同一个Session;
对于到不同的IP地址, 任意端口的连接使用不同的Session. 我们称此种NAPT为
Symmetric NAPT. 也就是只要本地绑定的UDP端口相同,
发出的目的IP地址不同,则会建立不同的Session.
图六: Symmetric 的英文意思是对称。多个端口对应多个主机,平行的,对称的!
Cone NAPT: 对于到同一个IP地址,任意端口的连接分配使用同一个Session;
对于到不同的IP地址,任意端口的连接也使用同一个Session. 我们称此种NAPT为
Cone NAPT. 也就是只要本地绑定的UDP端口相同, 发出的目的地址不管是否相同,
都使用同一个Session.
图七: Cone 的英文意思是锥。一个端口对应多个主机,是不是像个锥子?
现在绝大多数的NAPT属于后者,即Cone
NAT。本人在测试的过程中,只好使用了一台日本的Symmetric
NAT。还好不是自己的买的,我从不买日货,
希望看这篇文章的朋友也自觉的不要购买日本的东西。Win9x/2K/XP/2003系统自带的NAPT也是属于
Cone NAT的。这是值的庆幸的,因为我们要做的UDP穿透只能在Cone
NAT间进行,只要有一台不是Cone
NAT,对不起,UDP穿透没有希望了,服务器转发吧。后面会做详细分析!
下面我们再来分析一下NAPT
工作时的一些数据结构,在这里我们将真正说明UDP可以穿透Cone
NAT的依据。这里描述的数据结构只是为了说明原理,不具有实际参考价值,真正感兴趣可以阅读Linux的中关于NAT实现部分的源码。真正的NAT实现也没有利用数据库的,呵呵,为了速度!
Symmetric NAPT 工作时的端口映射数据结构如下:
内网信息表:
[NAPT 分配端口] [ 内网IP地址 ] [ 内网端口 ] [ 外网IP地址 ] [ SessionTime
开始时间 ]
PRIMARY KEY( [NAPT 分配端口] ) -> 表示依据[NAPT
分配端口]建立主键,必须唯一且建立索引,加快查找. UNIQUE( [ 内网IP地址 ], [
内网端口 ] ) -> 表示这两个字段联合起来不能重复. UNIQUE( [ 内网IP地址 ], [
内网端口 ], [ 外网IP地址 ] ) -> 表示这三个字段联合起来不能重复.
映射表:
[NAPT 分配端口] [ 外网端口 ]
UNIQUE( [NAPT 分配端口], [ 外网端口 ] ) -> 表示这两个字段联合起来不能重复.
Cone NAPT 工作时的端口映射数据结构如下:
内网信息表:
[NAPT 分配端口] [ 内网IP地址 ] [ 内网端口 ] [ SessionTime 开始时间 ]
PRIMARY KEY( [NAPT 分配端口] ) -> 表示依据[NAPT
分配端口]建立主键,必须唯一且建立索引,加快查找. UNIQUE( [ 内网IP地址 ], [
内网端口 ] ) -> 表示这两个字段联合起来不能重复.
外网信息表:
[ wid 主键标识 ] [ 外网IP地址 ] [ 外网端口 ]
PRIMARY KEY( [ wid 主键标识 ] ) -> 表示依据[ wid 主键标识
]建立主键,必须唯一且建立索引,加快查找. UNIQUE( [ 外网IP地址 ], [
外网端口 ] ) -> 表示这两个字段联合起来不能重复.
映射表: 实现一对多,的
[NAPT 分配端口] [ wid 主键标识 ]
UNIQUE( [NAPT 分配端口], [ wid 主键标识 ] ) ->
表示这两个字段联合起来不能重复. UNIQUE( [ wid 主键标识 ] ) ->
标识此字段不能重复.
看完了上面的数据结构是更明白了还是更晕了? 呵呵!
多想一会儿就会明白了。通过NAT,内网计算机计算机向外连结是很容易的,NAPT会自动处理,我们的应用程序根本不必关心它是如何处理的。那么外部的计算机想访问内网中的计算机如何实现呢?我们来看一下下面的流程:
c 是一台在NAPT后面的内网计算机,s是一台有外网IP地址的计算机。c 主动向 s
发起连接请求,NAPT依据上面描述的规则在自己的数据结构中记录下来,建立一个Session.
然后 c 和 s 之间就可以实现双向的透明的数据传输了。如下面所示:
c[192.168.0.6:1827] <-> [priv ip:
192.168.0.1]NAPT[pub ip: 61.51.99.86:9881] <-> s[61.51.76.102:8098]
由此可见,一台外网IP地址的计算机想和NAPT后面的内网计算机通讯的条件就是要求NAPT后面的内网计算机主动向外网IP地址的计算机发起一个UDP数据包。外网IP地址的计算机利用收到的UDP数据包获取到NAPT的外网IP地址和映射的端口,以后就可以和内网IP的计算机透明的进行通讯了。
现在我们再来分析一下我们最关心的两个NAPT后面的内网计算机如何实现直接通讯呢?
两者都无法主动发出连接请求,谁也不知道对方的NAPT的公网IP地址和NAPT上面映射的端口号。所以我们要靠一个公网IP地址的服务器帮助两者来建立连接。当两个NAPT后面的内网计算机分别连接了公网IP地址的服务器后,服务器可以从收到的UDP数据包中获取到这两个NAPT设备的公网IP地址和这两个连接建立的Session的映射端口。两个内网计算机可以从服务器上获取到对方的NAPT设备公网IP地址和映射的端口了。
我们假设两个内网计算机分别为A和B,对应的NAPT分别为AN和 BN,
如果A在获取到B对应的BN的IP地址和映射的端口后,迫不急待的向这个IP
地址和映射的端口发送了个UDP数据包,会有什么情况发生呢?依据上面的原理和数据结构我们会知道,AN会在自己的数据结构中生成一条记录,标识一个新Session的存在。BN在收到数据包后,从自己的数据结构中查询,没有找到相关记录,因此将包丢弃。B是个慢性子,此时才慢吞吞的向着AN的IP地址和映射的端口发送了一个UDP数据包,结果如何呢?当然是我们期望的结构了,AN在收到数据包后,从自己的数据结构中查找到了记录,所以将数据包进行处理发送给了A。A
再次向B发送数据包时,一切都时畅通无阻了。OK, 大工告成!且慢,这时对于Cone
NAPT而言,对于Symmetric NAPT呢?呵呵,自己分析一下吧...
NAPT(The IP Network Address/Port Translator) 进行UDP穿透的具体情况分析!
首先明确的将NAPT设备按照上面的说明分为: Symmetric NAPT 和 Cone NAPT, Cone
NAPT 是我们需要的。Win9x/2K/XP/2003 自带的NAPT也为Cone NAPT。
第一种情况, 双方都是Symmetric NAPT:
此情况应给不存在什么问题,肯定是不支持UDP穿透。
第二种情况, 双方都是Cone NAPT:
此情况是我们需要的,可以进行UDP穿透。
第三种情况, 一个是Symmetric NAPT, 一个是Cone NAPT:
此情况比较复杂,但我们按照上面的描述和数据机构进行一下分析也很容易就会明白了,
分析如下,
假设: A -> Symmetric NAT, B -> Cone NAT
1. A 想连接 B, A 从服务器那儿获取到 B 的NAT地址和映射端口, A
通知服务器,服务器告知 B A的NAT地址和映射端口, B 向 A 发起连接,A
肯定无法接收到。此时 A 向 B 发起连接, A
对应的NAT建立了一个新的Session,分配了一个新的映射端口, B 的 NAT
接收到UDP包后,在自己的映射表中查询,无法找到映射项,因此将包丢弃了。
2. B 想连接 A, B 从服务器那儿获取到 A 的NAT地址和映射端口, B 通知服务器,
服务器告知 A B的NAT地址和映射端口,A 向 B 发起连接, A
对应的NAT建立了一个新的Session,分配了一个新的映射端口B肯定无法接收到。此时
B 向 A 发起连接, 由于 B 无法获取 A
建立的新的Session的映射端口,仍是使用服务器上获取的映射端口进行连接, 因此
A 的NAT在接收到UDP包后,在自己的映射表中查询,无法找到映射项,
因此将包丢弃了。
根据以上分析,只有当连接的两端的NAT都为Cone
NAT的情况下,才能进行UDP的内网穿透互联。
NAPT(The IP Network Address/Port Translator)
进行UDP穿透如何进行现实的验证和分析!
需要的网络结构如下:
三个NAT后面的内网机器,两个外网服务器。其中两台Cone NAPT,一台 Symmetric
NAPT。
验证方法:
可以使用本程序提供的源码,编译,然后分别运行服务器程序和客户端。修改过后的源码增加了客户端之间直接通过IP地址和端口发送消息的命令,利用此命令,你可以手动的验证NAPT的穿透情况。为了方便操作,推荐你使用一个远程登陆软件,可以直接在一台机器上操作所有的相关的计算机,这样很方便,一个人就可以完成所有的工作了。呵呵,本人就是这么完成的。欢迎有兴趣和经验的朋友来信批评指正,共同进步。