电脑技术学习

简单的 NCP 协议

dn001

本RFC定义了一则新的NCP协议,该协议十分简单,因此能够在小型计算机上实现。但是该协议又可以被扩展而高效的运行在大型的分时机器上。因为在最坏的情况下,存储需求是可以被预见到,因此该协议的一种保守的实现方法是不考虑复杂的资源分配和存储控制过程。协议定义了通用的的错误恢复过程。

概要和综述
该协议有一个很重要的假设,那就是坚持所有的用户到用户的连接是双向的。对于熟悉通信理论的人来说,这显得最合理不过了。所有的通信需要不断循环的信息流。将消息和其相应的应答信息分离开来,那会使协议不必要的复杂起来,并且简单的流控制机制也会由此而变得异常复杂。
人们建议用一个套接口数对来区分一个双向连接或叫一个双工链路,其中每个数代表一个端。这是目前需要的数目的一半。与连接有关的是一些“箱子”或叫消息容器。这些箱子携带着网络消息在链路中从一端传送到另一端,并且不断反复往来。链路的每一端都分配有缓冲区,用来保存箱子及其携带的消息。在最坏的情况下,需要的缓冲区数与网络中所流通着的箱子数相等,也就是和链路的“容量”一致。

细节
一个消息缓冲有四种相互循环转换的状态,他们是:
1)空状态,
2)布满等待卸载的满载着消息的箱子
3)布满着空箱子,以及
4)布满等待发送的满载着消息的箱子
状态的相互转换对应着消息的到达,消息的删除,消息的插入以及消息的传送。
一个NCP必须满足以下条件:
1)能通过控制链路与外部主机做初始的联系,而且在必要的时候删除以前系统建立起的链路。
2)能够创建用户到用户的链路。
3)能够通过这些链路与用户交互。
4)能够删除用户到用户的链路。
上述四个功能中的第一个不应该在这里进行讨论,但是必须指出,假如不作最大的消息传送延迟的假设,那么就不能解决该功能的要害问题。因为在ARPA网络中不存在着消息中转时间,因此选择的方法就不具有很高的可靠性。从实现一个最小化的NCP角度出发,下面先讨论其它的三个功能。假如用于更大型的机器,则需要进一步的扩充和改进。
任何的NCP必须能在当地用户进程和远程进程间建立起一条双工链路。目前的协议通过将一个任意的RFC数插入队列,等待用户检查队列来决定他希望和谁通信来实现的。这样保证不了用户何时会察看队列,也没有办法限制队列的大小。溢出错误消息在这种情况下就会失效,因为它承认RFC将只能被在发送一次。然而情况也不是那么的糟糕。下面的网络会话会告诉我们如何不使用队列和不依靠用户进程来实现连接。
假设一个当地进程和一个远程进程希望建立一个新的连接。远程进程查询它的NCP来侦听连接请求并且将它的套接口号告诉其主机。它能随意的给出两个套接口号。当地进程则会要求其NCP发送一个双工链路请求(RFDL)。其中确定了该链路的两个套接口号。
当地NCP通过控制链路按照以下格式发送一个RFDL:
RFDL<mysocket><yoursocket><maxnumberbuffers><spare>
第三个参数通常由当地NCP给定,它表明了NCP将为这个双工链路所可能分配的最大缓冲区数。假如缓冲区位于用户存储器中,则该数可以通过用户的NCP调用给出。
RFDL在远程主机被接收,远程NCP将<mysocket>和<yoursocket>与由没被匹配的侦听提交给它的套接口号相比较。对于那些只给出一个套接口号的侦听来说,只需要匹配<yoursocket>就可以了。假如给出了两个套接口号,那么这两个套接口号都必须被匹配。假如产生了一个匹配,那么一个如下格式的确认消息就会被NCP发送回去:
ACDL<yoursocket><mysocket><numberbuffers><spare>
参数<numberbuffers>的取值为前面提到的RFDL中的<maxnumberbuffers>和远程NCP的消息缓冲区数这两个中的较小值。假如套接口号不能被匹配,那么就返回一个ACDL的错误消息,其中<numberbuffers>值为零。需要注重的是,RFDL机制与RFC机制类似,只是在前者中,队列大小为1并且是否同意建立连接完全由NCP来决定。
两个侦听方法的变种对应着两种通道操作模式。单参数的变种将用于“与任何在线主机通讯”的程序,典型的如LOGIN进程。至于和谁通信则由用户进程决定。双参数变种则用于用户知道自己将和谁通信并且不希望被其它主机的随机RFDL所打断的情况。假如套接口名字空间分成几个部分,那么只有从特定的进程中才能获取匹配的RFDL。
远程主机在发送ACDL前分配用于连接的消息缓冲区,而本地主机则在收到ACDL时分配缓冲区。每端的缓冲区数与ACDL中的参数<numberbuffers>相等。所有的远程缓冲区的状态是“空”,所有的本地缓冲区的状态是“布满着空箱子”。消息缓冲区分配完毕后,本地的用户进程将会被告知,然后它就能开始发送消息。
NCP所涉及到的用户进程和新建双工链路间的接口类型由相应主机所决定。一个简单而完整的接口能提供两个NCP调用。GETMESSAGE将会从链路中返回下一个消息,消息包括标记,文本和填充字节。PUTMESSAGE将会取一个只包括标记和文本的消息,并且将其缓冲起来预备发送。假如有明显的逻辑错误将会报告。
我们建议将消息对齐的责任留给用户。在大部分机器上这是一个简单但耗时的操作。假如在NCP中实现,也不能保证用户不用再重新对其调整。光靠经验是不大可能知道文本部分究竟应该是向左,或向右来调整而使得字对齐,又或者与最后一条消息末对齐,又或者用非凡的方式来将消息分片。
在本协议中消息边界将用于提供存储分配信息。假如用户没用到该信息,那么可以忽略它,用户接口就能看作是一个位流。虽然这种策略受到纯粹主义者的欢迎,然则在试图同步链路两端的时候,这种策略就会使事情变得复杂。
通过从链路中删除空箱子并且回收分配给这些空箱子的缓冲,就可以删除一条链路。只有那些含有箱子的缓冲才能被回收,空的缓冲必须被保留来接收随时而来的消息。当所有的箱子都被删除后,缓冲也不再存在了,并且套接口号可以被忽略。当删除空箱子的时候,一个精简的消息将会发送给外部NCP,让它减少分配给缓冲区的存储量。这个消息的格式如下:
DEC<mysocket><yoursocket><numberofbuffersdropped>
然后外部NCP会被要求发送一个应答来确定删除操作的正确执行或者告知错误的发生。错误可能是“没有这个链路”或者“删除的缓冲区数不可能”。
有两个系统调用可供用户进程选择来关闭一条链路。NOMOREOUTPUT会声称本地用户进程不会再发送消息。相应的拥有空箱子的链路的所有本地缓冲被NCP回收。DEC消息将会被发送给外部NCP。在箱子为空的情况下,通过GETMESSAGE调用也把它们的缓冲回收。另外一个系统调用是KILLMESSAG。这个调用可用来代替PUTMESSAGE.。KILLMESSAG将回收空箱子和发送一个DEC控制消息,而不是用将要发送的消息来填充空箱子。
在用户进程僵死或者其它不能关闭链路的情况下,必须采取应急措施。在这些情况下,我们定义了ABEND控制消息:
ABEND<mysocket><yoursocket>
在发送了一个ABEND后,发送这个消息的NCP就开始关闭链路。所有包括输入信息的缓冲将会被关闭。针对这些缓冲以及以前的空缓冲,将会给外部NCP发送一个DEC消息。假如链路中消息到达了,这些消息被销毁并且会发送一个DEC。从该链路中收到的任何ABEND消息会被忽略。
当远程NCP收到ABEND消息的时候,它就会停止向链路发送消息,并且拒绝接受来自其用户进程的新消息。空缓冲被回收。等待被接受的输出消息被销毁,相应的缓冲也被回收。只要用户进程同意,那么输入信息就会注入用户进程中。当输入被同意接收后,其缓冲也会被要求归还。提交的DEC消息会将回收的缓冲删除。当用户进程不再输入时,输入消息会被销毁,其缓冲也会被回收。实际上链路中每端的所有缓冲最终都会被回收。这是该连接就能被关闭,同时相应的套接口号也可以被重新分配而不会造成混乱。
在这个提议的协议构造下,上述四个功能包括了一个网络NCP必须具有的所有功能。假如只在一个缓冲空间被释放后才又进行缓冲的分配工作,那么就不会有“溢出”的错误发生,也不需要为消息流加上更多的限制。对每个用户来说,肯定有缓冲空间来接受到达的消息。对任何用户来说,肯定有缓冲来接收到达的消息。所有的控制消息的处理都不需要额外的存储空间。当地的治理程序能够防止一个用户进程提交过多的侦听。
当很多未完成的连接存在时,就会导致存储空间利用十分低效。编码简易性的一个代价是50%的缓冲空间的浪费。在大型主机上,可以证实实现下列的NCP扩充是有用的。假如使用更复杂的流控制过程,一个NCP就可能分配比实际中存在的更多的缓冲空间,而且也不会产生问题。其它的扩充实现了消息的压缩、吞吐量的改善以及透明的错误恢复机制。
因为某些扩充需要外部主机的协作,并且假设了这些主机不仅仅实现了最小化的NCP,因此有人主张使用一个查询控制消息来察看外部主机实现了何种扩充。对一个INQ的应答是一个控制消息,该消息定义了主机的规范。假如返回了一个“未定义错误”,外部主机就会被默认为只实现了一个最小化的NCP。
一个简单的扩充是定义一个控制消息来代替用户RFNM消息。一个用户RFNM是一个空文本消息,比如当文件通过双工链路传输是作为应答消息。它们是低效的,因为它们将一个项与IMP链路分配表绑定起来,同时减少了网络的吞吐量。一个更有效的解决方案是通过控制链路发送一个非凡的消息。使用这种方式,一个短的消息就能代替几个用户消息。
URFNM<mysocket><yoursocket><numberofuserRFNM's>
因为控制链路与用户链路的返回端同时存在,所以当应答链路有其它消息要发送的时候,URFNM不能被用户RFNM代替。否则消息会变得无序,用户透明性也会失去。
通过在双工链路上增加额外的箱子数量这种机制,可以使吞吐量增加。这或者是由用户手工来完成,或者由NCP负责。
INC<mysocket><yoursocket><numberbuffersdesired>
外部主机对这种请求通过INCR来应答。
INCR<mysocket><yoursocket><numberbufferstobeadded>
假如外部NCP不能满足额外的缓冲需求,那么<numberbufferstobeadded>将会比<numberbuffersdesired>小,并且可能为零。所有的当地缓冲的初始状态是“布满空箱子”,而所有的外部缓冲的初始状态是“空”。
RFDL和ACDL中的<spare>参数可以被用来定义实际在发送方向上所容许的信息的最大长度。一个智能的NCP能获知这个信息,从而分配较少的缓冲。而一个智能程度不高的NCP可能会忽略这个信息,并且总是假定是最长的信息。举个例子,假如这个参数值为零,那么只会发送用户RFNM消息。而一个智能的NCP将不会分配任何缓冲。
假如NCP保留在网络上发送的用户消息的副本,直到收到应答消息为止的话,那么就可以实现一个错误自动恢复过程。因为链路带宽总是已知的,所以NCP可以知道此刻链路中是否有消息在传送。这是通过首次向外部NCP发送一个STOP消息来实现的:
STOP<mysocket><yoursocket>
STOP消息告诉外部NCP在特定的链路上暂时停止发送消息。不像CEASE-ON-LINK,在STOP消息生效前发送多少消息是没有保证的。当地NCP收到该消息后就会发送一个查询消息:
LINQ<mysocket><yoursocket>
应答消息会给出链路外部端机中的箱子数。LINQ消息不断被重复发送,直到外部端机中的箱子数与当地箱子数之和和链路带宽相等为止。此刻链路中没有传送消息,链路的两端已经被同步了。参照这个同步点,就能识别出每个消息。例如,当地NCP能发送一个控制消息来要求发送第三个消息以后的所有消息。外部NCP能识别出具体的消息,并且重新发送。一旦所有的错误被恢复,一个格式与STOP类似的START控制消息就会发送给外部NCP,正常的操作就会被恢复。整个错误恢复过程能够对接受、发送用户进程透明。
人们希望较大的主机受最坏情况下的存储分配要求影响不大。于是它们宁愿分配比自己实际拥有的缓冲数量大的缓冲,并且根据概率作应答来保证大部分时间能正常工作。只要对外部主机透明,那么这种方法是很好的。只要NCP本身没有被捕捉,协议是答应NCP谎报其存储分配的。在侦查显得很紧迫的情况下,将需要实现以下的控制机制。这些机制以力量的大小顺序列出,其中力量越大者越靠后。
a)不发送任何用户RFNM消息或者其它的短消息。使用更长的消息减少缓冲需要是一种很好的尝试。
b)尽量不从IMP接收任何的新消息。将企图发送消息的当地进程阻塞。
c)发送DEC消息来释放缓冲空间。分配给RFDL的缓冲不要超过一个,还要拒收INC消息。
d)假如消息等待用户处理,就在消息中伪造错误。只有在发送消息的主机上实现了错误恢复机制的情况下才能采用这种方法。这种方法将会释放缓冲区的空间,答应用户以后恢复。最后的这种措施被公认为最后的拯救方式,但是它应该有足够的能力去控制任何紧急情况。
作者希望以上的协议能成为RFC54及其附件的一种有吸引力的实现。虽然该协议提出比较晚,但是它的实现不需要很多的功夫。它足够简单,实现起来会很快。假如被采用,在夏天结束前当前的大部分站点就能实现智能式的通讯。