第十九章 APO连接与网络v节点

                  第十九章   APO连接与网络v节点

 

    在编写网络底层实现前,需要做许多的准备工作;除了修改前面的章节外,可能还需写3章;本章、文件号管理类的实现、本地内存管理类的实现。我很希望最终的网络编程能给我惊喜;IP/TCP/UDP/ICMP的实现、包括所有的网络服务器的实现(HTTP、DNS、FTP等等),能到达300行的代码量;多于300行代码量那就更好了、我会很高兴。网络编程第一层简为socket层,第二层简称为TCP层,第三层简称为IP层;以后,不再说明。

     这章会写得慢,因为要仔细的研究那个:复杂的、巨大的、认真的、严肃的、搞笑的、牛角尖的、牛逼的TCP协议。

一、APO的连接

    什么是连接?这简单的问题要耗费我不少时间,简单其实意味着复杂、是很多内容浓缩而形成。Socket、插座、电话号码、端口,就算我们通过类比来描述一个通信连接过程;但能完全描述一个连接吗?对于一个socket客户端口,确实对应着一个客户端连接。但对于同一个socket服务端口,可以有许多连接啊;显然不行。使用双方的IP地址、和socket端口来定义一个连接,那显然可以;但那需要32字节啊,想快速定位一个连接是不行的。嗯、查了一下网上资料,天哟、真的是用什么5元组、7元组来确定一个连接!五元组是: 源IP地址、目的IP地址、协议号、源端口、目的端口。七元组是: 源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型及接口索引。

1、连接的表示

    对于客户端,可以用16位socket客户端口来表示;那么、如何知道一个连接是客户端还是服务端?我们需要建立一个端口表,并不大、因为一个服务端口就对应很多连接;APO的表大小只是8K项。那么、表项的内容是什么?应该是跟操作系统有关,我不清楚,或许它们不是使用端口表来表示。不管怎样,接收了一个数据包,就应该判断其属于哪一个连接;想法与文件号sockfd关联。APO的端口表项就是一个字,如果是客户端口:高8位是状态标志、低24位是文件号sockfd;对于服务端口,高8位是状态标志、低24位是0;该端口项不用、全0。实际上,还需管理端口的位图(用于端口的分配、释放),及端口查询表(表项内容就是端口值)。提取IP数据包的目标socket端口值,先在端口查询表找出端口值在第i项(通常需要耗费约30ns);之后、再用i去端口表获得文件号,如是0、则提取IP数据包的24位的服务端文件号字段。如果主机的MAC端口速度是1GBPS,包之间的最小间隔160ns;APO的最小包是512位、等效512ns;所以、在IP层处理一个IP数据包的最短时间只有672ns。IP层处理需要包含内存管理、IP数据包组装成IP数据报、错误包处理、正确提交到TCP层(该层最复杂,连接、文件号管理、端口管理等,处理的最短时间只有672ns。)、ICMP报文处理等。APO的处理速度也就每秒1百多万个数据包,支持千万个连接应该问题不大。

    使用五元组,目的方不是本机吗?三元组不行?嗯,它们不像APO主机是唯一的IP地址,是可以有多个IP地址。好了,五元组的字节数太多、直接比较耗费太大(千个连接就会耗尽inte类主机资源),想法每元抽取8位、形成32位哈希值(哈希算法简单、只抽取4元、够啰嗦的)。那不一定是唯一哈希值啊,嗯、增加冲突处理;我要晕了!也许千个连接还行,对付10万个连接时,主机要歇菜。它们应该是用链表去关联到文件号sockfd,对于复杂脑袋型的操作系统、我无法进行仔细探讨,所以、只能是对它们推测。

    既然我们将socket看作文件,那么为何不直接使用文件号sockfd来代表一个连接呢?可以,APO就是这样的。客户端就不用说了;在服务端,我们用不同的文件号来代表不同的客户端到同一个服务端的连接;而描述连接的是文件号相对应的内存v节点。但客户端是不知道它们到服务端的连接、在服务端的相应文件号啊;所以,需要在IP包中使用到24位的服务端文件号字段了。服务端文件号字段是服务端设置,而客户端发送数据报时、也将服务端文件号字段一起转发就行了。这样,客户端的数据报到达服务端主机的网络底层时,就可提取24位的服务端文件号字段、快速定位到相应的内存v节点,从而得到该连接的描述。网络底层通常是先提取数据包的目标socket端口、查socket端口表,如是客户端口,那就可以立即得到相应的文件号;如果是0(服务端口),那就需提取IP包头的24位服务端文件号字段了;当然、进入相应的v节点后,还需进一步比较确认,那只是不到10ns的一次性比较吧。


2、建立连接


     信令必须可靠,TCP是使用三次握手连接,为何?要保证“信令可靠”,就需要对一个信令数据包进行ack确认表示接收端收到。如果收不到ack,就要做超时重发、APO最多2次超时重发。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要63s,TCP才会把断开这个连接。我们知道一个512的信令包只是不到1us,就算路径有100个中间路由交换机的存储转发会产生一定的时延,从A到达B也不会到1ms、来回2ms;为何需要重试时间间隔为多于 1s呢?这不浪费时间吗? 给黑客利用的机会?有点像脱裤子放屁哟?你可能说,如果server端接到了clien发的SYN后回了SYN-ACK后client就突然掉线了(或故意不发ACK、这概率更大),server端没有收到client回来的ACK,那么,应该给client端1分钟、和重发5次的机会。要知道主机每秒处理1百万个数据包,那么1分钟就6千万个数据包啊;如果这些数据包都是请求连接信令包、主机的资源就给耗尽。一些恶意的人就为此制造了SYNFlood攻击——给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接;这样,攻击者就可以把服务器的syn连接的队列耗尽,让正常的连接请求不能处理。于是,为了纠正一个小错误,就要付出更为复杂和耗时的代价。Linux下给了一个叫tcp_syncookies的 参数来应对这个事——当SYN队列满了后,TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去(又叫cookie),如果是攻击者则不会有响应,如果是正常连接,则会把这个 SYN Cookie发回来,然后服务端可以通过cookie建连接(即使你不在SYN队列中)。代价就是,本可以每秒处理1百万个数据包,可能变成每秒处理1万个数据包。关于这些,linux还有不少的复杂处理方法;不再讨论。我就想不通,为何复杂脑袋的产物总是九曲十八弯的;就不能简明些;协议也不应该是专为垃圾主机制定的。

     MSS是最大传输大小的缩写,就是TCP数据包每次能够传输的最大数据报分段。一个数据报被分段成多个小于MTU的数据包(也叫数据报分段)。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去MAC头18字节、IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1518 – 58 = 1460B。通常MTU是1536B,而传输一个TCP信令只是64B,浪费啊;3次握手、还要探查MTU和协商MSS值、测试往返时间,你说要建立一个连接需要多少次信令报往返?浪费多少资源?我以前远看TCP协议、感觉严谨、伟大,就像充满迷一样的大石山;为何、现在看上去就像垃圾山?漏洞百出、被黑客攻击得遍体鳞伤,好搞笑啊!小学生水平都不如,却复杂无比、扁扁我还看几天才略懂;诶、矛盾的世界。

     在APO中,TCP是双工连接、UDP是简单型半双工连接。APO的连接并非是通常的TCP三次握手连接,而是使用ICMP探查数据包来进行连接的;只是2次握手,请求、确认。当客户端的请求连接ICMP探查数据包到达服务端时,探查数据包已经包含了沿途路由交换机的MTU、时间戳、IP地址标记等(共16字节);那么、服务端就可知道客户端的地球位置、路径信息、IP地址、单向耗时,并做过滤分析:源IP地址、路由交换机的链路地址是否上了黑名单;如果安全,那么服务端建立描述该连接的文件号、保存连接描述到内存v节点,转发ICMP探查数据包,并在上面打上服务端文件号字段、分段大小MSS、允许的数据报大小、单向耗时、ACK确认等印记。APO网络是无法假冒源地址的,除非你劫持了路由交换机的子网地址。但服务端规定了子网每秒发出到服务端的连接请求数不能超过1000个、主机每秒发出到服务端的连接请求数不能超过10个;黑客想SYN Flood攻击是不可能的,要知道服务端可是允许千万个连接啊;APO取消了广播,你用组播服务器、还是相当于从一台机器发包,没用。那么,劫持路由交换机的子网地址,每秒连接请求数1000个,连续n秒;也没用,APO的重发时间是1s、上一批的1000个连接,如果没发数据包、就被取消了连接。黑客啊,千万别上黑名单,否则来多少数据包、就被丢掉还是小事;事后、追究、报警才大件事。那么,使用招数:占着茅坑不拉屎;一台主机正常连接、每10分钟发一个垃圾GET请求来保持连接。这也不行,服务端规定每台主机只给50个连接;黑客需要劫持30多万台主机。如果主机用APO操作系统,你想劫持那么多主机的root、只是做梦。APO信令报文只是最多发3次,重发时间间隔是,200ms、600ms,延迟到1s时、如果没收到ACK(客户端)或数据包(服务端)、就释放了该连接。APO的探查ICMP数据包最多45E,只能最多90个路由交换机的印记;如果往返超过90个,那么会有部分印记被消除。使用汇聚路由交换机,这种情形不会出现,再远、往返也不会超过50个路由交换机。

     客户端发送SYN连接请求ICMP探查数据包后、进入SYN_SENT状态。服务端收到连接请求ICMP探查数据包后、转发打上印记ACK+SYN的ICMP探查数据包、进入SYN_RECEIVED状态;如果在1s内、再次收到相应客户端的IP数据包,则进入ESTABLISHED连接建立状态。否则、在1s内重发2次后,没收到数据包;释放该连接!客户端收到确认ICMP探查数据包后,才确认该连接、进入ESTABLISHED状态,并计算往返时间、确定MSS、初始化发送数据报的头;发送第一个IP数据包。如果收到如目标不可到达等的ICMP差错报文,释放连接、返回错误消息给应用层。如果、超时则重发最多2次的请求连接ICMP探查数据包;如果在1s内没收到服务端的确认ICMP探查数据包,则释放连接、返回错误消息给应用层。

3、连接建立后的TCP通信

    TCP要保证所有的数据包都可以到达,所以,必需要有重传机制。假如客户端只是请求下载一个大小32GB的文件,TCP先是分割成512K个64KB的报文、再将每个报文分段成、比如1280B的52个IP数据包吧。我们假设一个IP数据包的往返时间是2ms,如果每发送一个IP数据包、都要来一个确认后,才发下一个数据包,那要晕倒!不说,ACK信令包浪费带宽,可是要多花2倍的时间啊,学院派的大神们、终于脸红了,羞愧了。还有啊,如果对方没有ACK回应、那还要等待超时重发,还要惨。TCP每发送一个报文段,就对此报文段设置一个超时重传计时器。此计时器设置的超时重传时间RTO(RetransmissionTime-Out)应当略大于TCP报文段的平均往返时延RTT,一般可取RTO = 2RTT;4ms? 但是,也可以根据具体情况人为调整RTO的值,例如可以设置此超时重传时间RTO = 90秒。当超过了规定的超时重传时间还未收到对此TCP报文段的预期确认信息,则必须重新传输此TCP报文段。

重传计时器(retransmission timer):当TCP发送报文段时,就创建该特定报文段的重传计时器。可能发生两种情况:
A: 若在计时器截止时间到(通常是60秒)之前收到了对此特定报文段的确认,则撤销此计时器。
B: 若在收到了对此特定报文段的确认之前计时器截止期到,则重传此报文段,并将计时器复位。

     TCP在重发的时候不是等间隔的,第一次和第二次发送之间是N秒,第二次和第三次之间是2N秒,一般N的初值是6秒吧,75秒内收不到回应TCP层就认为连接失败(不同的系统可能这个值不同),这些值可能都是无法控制的,TCP自己有一套复杂的RTT测量算法,重发间隔就是基于这个算法;忽快忽慢应该和重发的不等间隔性质有关。

    以上的几段是网上摘抄,我到现在都不明白具体的重传时间!即使是6秒啊,那也可以发送好几百万个数据包了;学院派啊、真是不惜成本。下面是网上摘抄TCP的RTT算法。

  从前面的TCP的重传机制我们知道Timeout的设置对于重传非常重要,设长了,重发就慢,没有效率,性能差;设短了,重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。


  而且,这个超时时间在不同的网络的情况下,有不同的时间,根本没有办法设置一个死的。只能动态地设置。为了动态地设置,TCP引入了RTT——Round Trip Time,也就是一个数据包从发出去到回来的时间。这样发送端就大约知道需要多少的时间,从而可以方便地设置Timeout—— RTO(Retransmission TimeOut),以让我们的重传机制更高效。听起来似乎很简单,好像就是在发送端发包时记下t0,然后接收端再把这个ack回来时再记一个t1,于是RTT = t1 – t0。没那么简单,这只是一个采样,不能代表普遍情况。

经典算法: 1)首先,先采样RTT,记下最近好几次的RTT值。 2)然后做平滑计算SRTT – Smoothed RTT。公式为:(其中的 α 取值在0.8 到 0.9之间,这个算法英文叫Exponential weighted movingaverage,中文叫:加权移动平均)SRTT =( α * SRTT ) + ((1- α) * RTT)3)开始计算RTO。公式如下:RTO = min [ UBOUND, max [LBOUND, (β * SRTT) ] ]其中:

UBOUND是最大的timeout时间,上限值

LBOUND是最小的timeout时间,下限值

β 值一般在1.3到2.0之间。

  
Karn / Partridge 算法:

  但是上面的这个算法在重传的时候会出有一个终极问题——你是用第一次的时间和ack回来的时候做RTT样本,还是用重传的时间和ACK的时间做RTT样本?这个问题无论你先那头都是按下葫芦起了瓢。情况(a)是ack没回来,所发重传。如果你计算第一次发送和ACK的时间,那么,明显算大了。情况(b)是ack回来慢了,重传不一会,之前ACK就回来了。如果你是算重传的时间和ACK回来的时间,就会短了。


Karn / Partridge Algorithm:这个算法的最大特点是——忽略重传,不把重传的RTT做采样(你看,你不需要去解决不存在的问题)。但是,这样一来,又会引发一个大BUG——如果在某一时间,网络闪动,突然变慢了,产生了比较大的延时,这个延时导致要重转所有的包(因为之前的RTO很小),于是,因为重转的不算,所以,RTO就不会被更新,这是一个灾难。 于是Karn算法用了一个取巧的方式——只要一发生重传,就对现有的RTO值翻倍(这就是所谓的Exponential backoff)。

Jacobson / Karels 算法:

  前面两种算法用的都是“加权移动平均”,这种方法最大的毛病就是如果RTT有一个大的波动的话,很难被发现,因为被平滑掉了。所以,1988年,又有人推出来了一个新的算法,这个算法叫Jacobson / Karels Algorithm。这个算法引入了最新的RTT的采样和平滑过的SRTT的差距做因子来计算。 公式如下:(其中的DevRTT是Deviation RTT的意思)SRTT= SRTT+ α(RTT– SRTT)DevRTT= (1-β)*DevRTT+β*(|RTT-SRTT|)RTO= µ *SRTT + ∂ *DevRTT(其中:在Linux下,α = 0.125,β = 0.25, μ = 1,∂= 4 ——这就是算法中的“调得一手好参数”,nobody knows why, it justworks…) 最后的这个算法在被用在今天的TCP协议中。我真的晕倒了,那要耗去多少CPU资源啊?要为之编写多少代码啊?能支持到1万个连接吗?你们的脑袋何必那样复杂啊?要知道过了、就成脑残啊!


    嗯,重传机制、羞愧的学院派大神们、开始改进了;我们往下看,他们如何做。接收端给发送端的Ack确认只会确认最后一个连续的包,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。

  一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3。一旦接收方收到3后,会ack 回 4——意味着3和4都收到了。

  但是,这种方式会有比较严重的问题,那就是因为要死等3,所以会导致4和5即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传。

  对此有两种选择:

一种是仅重传timeout的包,也就是第3份数据。

另一种是重传timeout后所有的数据,也就是第3,4,5这三份数据。

  这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长。

    于是,TCP引入了一种叫Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。

  比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回 2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack = 2的确认,知道了2还没有到,于是就马上重传2。然后, 接收端收到了2,此时因为3,4,5都收到了,于是ack回6。

    Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是重传之前的一个还是重装所有的问题。对于上面的示例来说,是重传#2呢?还是重传#2,#3,#4,#5呢?因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现);可见,这是一把双刃剑。

 

   另外一种更好的方式叫:Selective Acknowledgment(SACK),这种方式需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版。这样,在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有到。于是就优化了Fast Retransmit的算法。当然,这个协议需要两边都支持。在 Linux下,可以通过tcp_sack参数打开这个功能。
  
  这里还需要注意一个问题——接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么做可能会有些极端情况,比如要把内存给别的更重要的东西。所以,发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack。


  注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。

Duplicate SACK又称D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了。D-SACK使用了SACK的第一个段来做标志,如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK。如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK。引入了D-SACK,有这么几个好处:

  1)可以让发送方知道,是发出去的包丢了,还是回来的ACK包丢了。

  2)是不是自己的timeout太小了,导致重传。

  3)网络上出现了先发的包后到的情况(又称reordering)

  4)网络上是不是把我的数据包给复制了。

  知道这些东西可以很好得帮助TCP了解网络情况,从而可以更好的做网络上的流控;Linux下的tcp_dsack参数用于开启这个功能。

 

    晕了吧!如果为了掩盖一个小小的谎言,可以使用更复杂的、更大的谎言;为了纠正一个小小错误,可以犯更大的错误、使用更为复杂的犯错方式的话。如果这成为真理、成为聪明人之常情的话;我无语、佩服得五体投地。有更为简洁的!要在IP层的实现那章才介绍。大神们,你们就不能简单的去思考?

    据说,如果你不了解TCP的滑动窗口这个事,你等于不了解TCP协议。那为何不将其想象成一堆臃肿垃圾?我正是这样,所以也不想深入的去理解!有关滑动窗口的事情、可以写成一本书了。当红脸叫兽得意非凡的吹嘘“交通规则、糊涂窗口、小包传输”时,我看到的是巨大的代码量、和低能的效率、少量的连接!苍白搞笑的吹嘘,我体起疙瘩、浑身无力;是那样的无奈。大神们,别搞了;求你们了,我真的受不了。我不生气、我不生气! “牢骚太盛防肠断,风物长宜放眼量。”我阿Q还不行?


4、连接的断开

    在APO中,连接的断开是简单的、也没有那么多种状态,也没有什么4次握手的概念。主动关闭(active close)端应用程序调用close,于是其TCP层发出FIN请求主动关闭连接,之后进入FIN_WAIT状态;等待远程TCP的ACK确认,收到ACK则释放连接。如果1s内、并重发2次FIN请求,还是没有收到ACK;也释放连接。被动关闭(passive close)端TCP也是类似的;当被动关闭端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入TIME-WAIT:等待足够的时间(1s内、如果再次收到FIN,会重发ACK;最多2次),以确保远程TCP接收到连接断开请求的确认ACK。不管怎样,在1s后,都会自动释放被动关闭端的连接。TIME_WAIT等待状态,又叫做2MSL状态。APO的服务端的多个客户连接、是TCP层实现的,每秒可实现上百万个连接;跟应用层无关,不需要那些listen()、accept()、connect()等垃圾。无须像流行TCP那样,应用程序在2MSL时间内是无法再次使用同一个插口的,启动httpd就会出现错误(插口被使用),也没有服务器平静时间的概念。

    在关闭连接后,也许该连接还有一些数据包在网络中传输;但1s内应该都被IP层判断无此连接而丢弃了,如果刚好新连接参数(客户端口、IP地址)与旧连接的一样、并收到这些遗留数据包,那会怎样?我们是否能用流标签去判断、从而丢弃?还是其它方法?

    我们还应考虑下极端情形:假如一个客户端A,和服务端B建立了一个连接。如果A关闭了连接,或许根本就不发FIN,或许发送了FIN、而B喝醉酒不响应,或许FIN丢失,或许B发了ACK、而A没收到超时关闭;总之是A关闭了、B可能还是在ESTABLISHED状态。如果客户端A重复的上演这种情况,那么服务端B将会出现大量的虚假的ESTABLISHED连接。如何解决?我们是否应该在协议上增加一个由服务器定义的随机的32位唯一标识字段?或者这是否就可以用服务端sockfd文件号字段表示呢?这样一来,客户端的连接,肯定是对应着唯一的服务端sockfd文件号字段;如果,服务端向客户端发出查询一个连接是否真实存在时,客户端就可拿端口对应的v节点内的服务端sockfd文件号字段与数据包的做比对;不一样,那就返回虚假连接ACK应答了;服务端就可以释放虚假连接。那么,我们对于遗留数据包也同样处理、从而丢弃。

    对于黑客主机的客户端,是没招的;但我们可以限制一台主机对应一个服务端口,只给它50个连接。虚就虚着吧,又能飞天?我服务端可以有1千多万个连接,怕谁?黑客搞搞震,不就浪费自己的电费、资源。黑客们,千万不要让你们的肉鸡进入监视名单,那将是你们的噩梦。


二、网络v节点


    什么是内存v节点?内存v节点是大小为4E = 128B的一个内存结构变量,APO中最多可以有16M个v节点。V节点与在内存中打开的文件是一一对应的,网络sockfd文件号实际上也就是v节点号。我们将网络socket看作是一个文件,所以、与网络socket文件必定对应有一个网络v节点。一定要牢记v节点就是代表在内存中打开的文件,每个v节点号、对应一个文件。v节点的内容就是对一个文件的属性描述,对于网络socket文件就是对一个连接的描述。


1、v节点号与普通文件号fd的区别


    我们必须搞清这2个概念的区别,对于网络socket文件、与进程或线程是唯一对应的;所以,网络socket文件号sockfd与v节点号是一致的。但是,对于在内存中打开的文件、对应一个唯一的v节点号;但可能对应着许多进程或线程。所以,为了区分不同进程或线程在内存中打开同一个文件;我们使用了文件描述符、即文件号fd,和文件打开表项。文件号fd与进程的文件打开表项是一一对应的,或许2个进程有相同的文件号,但不一定对应相同的v节点、这取决于它们的文件打开表项。所以,我们只能说文件号fd代表的是进程的文件打开表项,在进程的文件打开表项有对v节点的引用,用户定义的流容器的引用等等。对于普通文件的v节点就是对应文件的i节点属性和其所属父目录中的目录项属性;请参看前面的章节。注意:网络socket文件是没有目录项和i节点的,只有v节点;网络v节点描述的是一个连接,是本章需要论述的。


2、描述一个连接的网络v节点

    socket文件是在内存建立的、和其它类型的文件不一样,无须i节点、目录项,也不需要文件打开表项;只有不一样的v节点,描述了一个连接。网络v节点就是描述网络通信双方的标识和协议,包含通信协议头及建立、或释放连接的一些参数。

   
   APO中,带连接的UDP协议和TCP的合一;所以,除了无连接的ICMP外,APO就只有一个TCP协议了。APO的TCP协议也想将应用层协议(HTTP、FTP、TFTP、TELNET、PRC等)的一部分柔和进来;前面的章节关于协议部分需要修改。APO的TCP协议与现有的不一样,做了简化及性能的加强;要快速多了,跟无连接的DUP差不了多少。取消了麻烦、低效的窗口、序号、确认号;使用了独特、高效、高速的重传机制。APO的最小数据包是64B + 4B(CRC)+ 8B(同步头)= 76B;最大普通数据包是:23*64B + 12B = 1484B,最大ICMP的数据包是:47*32B + 12B = 1516B。路由交换机的MTU,如果小于1516B,不分段、不丢包,只是截断。


BU4E socket_vnode{ // socket文件的网络v节点。

  BU1E IPAPO{ // APO的IP头。

   BU48  MDA; // 以太网目标地址

   BU48  MSA; // 以太网源地址

   BU16  TYPE; // 类型高5位和低11位的长度。

   BU8   TTL;  // 跳数限制。

   BU8   TOS;  // 8位传输优先级、流量类型。

   BU64  SLADD; // 源链路地址。SLADD + MSA = 源IP地址

   BU64  DLADD; // 目标链路地址。DLADD + MDA = 目标IP地址

  }

union{ // APO的TCP协议头、1E;支持4GB的文件流传输。

   BU32  length; // 文件流总字节长度,不含头部64字节(2E)。

   BU32  sheetoff;// 低26位片偏移、分段单位1片 = 64字节。

// sheetoff.31-26  request; 上层协议的请求方法标识。Read、write、GET、

// POST、PUT、DELETE、TRACE、CONNECT、PATCH、ABOR、ACCT等等;或自定义。

   BU8   trflag; // 传输控制标志。

// trflag.7  MF; 更多分段标志(more fragment),0、最后一个分段

// trflag.6  DF; 分段标志:1、不分段,0、可以分段

// trflag.5  MULTICAST; 组播标志,1、有效。

// trflag.4  SIGLL; 信令标志:1、信令报文段,0、正常的DATA报文段传输。

// trflag.3  RQ; 请求标志(SIGLL=1有效):1、请求报文段,0、响应报文段。

// trflag.2  SIGI; 1、连接信令,0、应用层协议的信令。

// trflag.1-0 SIGC; 关于连接信令的标识,0、丢失的数据包信息,1-3备用。

   BU24  flowlabel; // 24位流标签,标识不同的数据报。

   BU8   conflag; // 连接控制标志。

// conflag.7  CWR; 用来表明它接收到了设置ECE标志的TCP包。

// conflag.6  ECE; 网络拥挤标志。

// conflag.5  URG; 1、紧急指针字段有效。

// conflag.4  ACK; 1、确认应答。

// conflag.3  PSR; 1、推送,不等组装、直接提交报文段到应用层。

// conflag.2  RST; 1、复位连接。

// conflag.1  SYN; 1、请求建立连接。

// conflag.0  FIN; 1、释放连接。

   BU24  servesockfd; // 24位服务端的socket文件号。  

   BU16  SPORT; // 源端口

   BU16  DPORT; // 目的端口

   BU16  cheksum; // 校验和。

   BU16  urgentp; // 紧急指针。

   BU16  tranmode; // 高层协议的传输模式。

   BU16  staterp; // 高层协议的状态响应码,或错误码。

   BU32  usedata; // 用户自定义、或高层协议使用、或组播域定义。

  }

union{ // APO的ICMPAPO协议头、1E,无连接。

   BU8   imcptype; // 报文类型:宣告、邻居请求、探查。

   BU8   segnum; // 路由交换机附带数据项印记指针(项数、每项16字节)。

   BU16  devtype;// 源设备类型(主机,2-3-4层交换机等)、网络中的级数。 

   BU32  PTID;  // 源设备的进程号(或端口号)、线程号(或标识)。

   BU32  times; // 报文发送时的时间戳us。

   BU16  MTU;   // 源设备的MTU。

   BU16  cheksum;// 校验和。

   BU16B icmpdata; // 第0项的附带数据,后面接着是更多的附带数据。

   }

  BU2E{ // 2行的连接参数描述。

   BU32  recvstream_p; // 接收的数据报流容器本地内存空间指针。

   BU32  recvstream_len;// 流容器对象的大小,单位E

   BU16  FLAG; // 协议、状态标志。

   BU16  BACKLOG;// 最大连接数、单位为256个连接。

   BU32  RECVLEN;// 低24位,允许的最大报文长度单位E,高8位附加状态信息。

   BU32  PTID; // 高16位关联的进程号、低16位关联的线程号。

   BU32  SRCOUNT; // 发送、接收数据报的计数。

   BU32  crtime; // 连接创建时间标识,单位mS。

// 36B,定时器等参数,等待设计、定义。   

   }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章