TCP提供了一种面向连接的、可靠的字节流服务,在连接双方在通信前需要预先建立一条连接。
TCP特点如下(提供了协议规则来保障通信链路的可靠性):
1)数据分割
应用数据分割成TCP协议认为最适合发送的数据块。最大报文段长度MSS选项是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。
MSS只能出现在SYN报文段中,若没有指定这个选项意味着本终端能够接受任何长度的报文段;若一方不接收来自另一方的MSS值,则MSS就定为536字节。一般来讲,在不出现分段的情况下,MSS值还是越大越好,这样可以提高网络的利用率。
2)重传机制
设置定时器,等待确认包。
3)数据校验
对首部和数据进行校验。
4)数据排序
对收到的数据进行排序,然后交给应用层。
5)丢弃重复
接收端丢弃重复的数据。
6)流量控制
通过每一端声明的窗口大小来提供的
TCP包的首部字段如下:
1) URG标志(紧急字段)
TCP提供解决方式是为了使一端告诉另外一端某些“紧急数据”已经放置在普通的数据流中,让接收端对紧急数据做特别处理。
此时,URG位被置为1,并且16位的紧急数据被置为一个正的偏移量,通过此偏移量与TCP首部中的序号字段相加,可以得出紧急数据的最后一个字节的序号,常见的应用有传输中断键(在通过telnet连接过程中)。
2) RST标志( 复位字段)
当一个报文发送到某个socket接口而出现错误:TCP则会发出复位报文段。常见出现的情况有以下几种:
发送到不存在的端口的连接请求:此时对方的目的端口并没有侦听,对于UDP,将会发出ICMP不可达的 错误信息,而对于TCP,将会发出设置RST复位标志位的数据报。
异常终止一个连接:正常情况下,通过发送FIN去正常关闭一个TCP连接,但也有可能通过发送一个复位 报文段去中途释放掉一个连接。
三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号字段里。
第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号设置为客户端报文的序列号加1以.即X+1。
第三次握手.
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1放在确定字段中发送给对方.并且在数据段写ISN(TCP初始化序列号ISN)的+1
要释放一个TCP连接,需要通过四次握手过程。由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
3) 服务器关闭客户端的连接,发送一个FIN给客户端。
4) 客户段发回ACK报文确认,并将确认序号设置为收到的发送序号加1。
TCP的连接与终止过程,连接状态转换:
建立连接协议是三次握手,而关闭连接却是四次握手:
连接连接,服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。因此是三次握手。
关闭连接,当连接的一端数据发送完毕,并主动要求关闭连接时,就发送FIN到对端,而对端应答ACK回来。但是对端可能还有需要发送的数据,等数据发送完毕后,才会发送FIN,表示同意关闭连接。所以ACK和FIN是分开发送的。因此是四次握手。
TIME_WAIT状态需要等2MSL后才能返回到CLOSED状态:
虽然握手的4个报文都发送完毕,但是为了保证最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文。如果接受到对端的FIN,就需要重发ACK包括。
所以这个TIME_WAIT状态下需要等待2MSL来判断是否需要重发丢失的ACK报文。(MSL是估计数据到对端的最长时间)
wireshark抓包演示。
tcp状态变化如下图:
1)CLOSED:
表示初始状态,也是关闭状态。
2)LISTEN:
表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
3)SYN_RCVD:
表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
4)SYN_SEND:
与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SEND 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SEND 状态表示客户端已发送SYN报文。
5)ESTABLISHED:
表示连接已经建立了。
6)FIN_WAIT_1:
FIN_WAIT_1 和 FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
7)FIN_WAIT_2:
上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
8)CLOSING:
这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
9)CLOSE_WAIT:
这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
10)LAST_ACK:
是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
11)TIME_WAIT:
表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
客户端状态:
1)FIN_WAIT_1状态
当连接中的一方主动发送FIN后,则进入FIN_WAIT_1状态。
2)FIN_WAIT_2状态
当对方对己方主动发送的FIN进行了确认,己方接到确认包后将进入FIN_WAIT_2状态。
3)TIME_WAIT状态服务器状态:
1) CLOSE_WAIT状态
当连接中的一方收到对方主动发过来的FIN时,则进入CLOSE_WAIT状态。
CLOSE_WAIT是被动关闭连接是形成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。
如果服务器出了异常,较常出现情况:
1.服务器保持了大量TIME_WAIT状态的socket
2.服务器保持了大量CLOSE_WAIT状态一般是由于被动关闭连接处理不当导致的。
3.服务器保持大量SYN_RECV状态,多是Syn攻击导致
因为linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的文件句柄就一直被占着,一旦达到句柄数上限,新的请求就无法被处理了,接着就是大量Too Many Open Files异常
处理方式:
(1)处理大量TIME_WAIT:
查看现在time_wait的数量
netstat -an | grep TIME_WAIT | wc -l
发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决,在 /etc/sysctl.conf中加入
net.ipv4.tcp_timestamps = 1 (开启时间戳才能使tcp_tw_recycle开启有效)
net.ipv4.tcp_tw_recycle = 1 (表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭)
net.ipv4.tcp_tw_reuse=1 (表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;该文件表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连接。这个对快速重启动某些服务,而启动后提示端口已经被使用的情形非常有帮助)
net.ipv4.tcp_fin_timeout=30 (设置在FIN_WAIT_2状态的超时时间,默认60s)
然后执行 /sbin/sysctl -p 让参数生效。修改生效后,time_wait数会明显下降。
开启net.ipv4.tcp_tw_recycle这个开关,可以快速回收处于TIME_WAIT状态的socket(针对主动关闭连接的Server端而言,如Http服务器)。
当tcp_tw_recycle开启时,对于位于NAT设备后面的Client来说,会导到NAT设备后面的Client连接Server不稳定。
在关闭 tcp_tw_recycle 的时候,kernal 是不会检查对端机器的包的时间戳的;打开了 tcp_tw_recycle 了,就会检查时间戳。
移动的cmwap(包括手机端在wifi 下)发来的包的时间戳是乱跳的,所以服务器的就把小于服务器的时间戳的包当作是已回收的连接的重传数据,而不是新的请求,于是丢掉不回包,造成丢包,则出现新连接被拒绝。
如果连接被重用了,那么延迟收到的包就有可能会跟新连接混在一起
原因是TIME_WAIT 的作用是:
1.防止上一次连接中的包,路由后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
2.可靠的关闭TCP连接。在主动关 闭方发送的最后一个 ack,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
tcp_tw_recycle开启的适用情况是,不存在NAT的情况的内部网络,而不是公网;适合PC端,不适合使用使用wifi或者移动网络的手机端。
tcp_tw_recycle来解决TIME_WAIT的问题是危险的,因为这两个参数违反了TCP协议,需要很清楚。
TIME_WAIT表示的是你主动断连接,所以一般是让客户端主动断开,如果你的服务器是于HTTP服务器,那么设置一个HTTP的KeepAlive就很有必要(浏览器会重用一个TCP连接来处理多个HTTP请求),然后让客户端去断链接。
(2)处理大量CLOSE_WAIT
如果是服务器本身在被动关闭连接后没有适时关闭连接,则从程序本身来处理。
(3)处理大量SYN_RECV
如果是Syn攻击,有如下特征:
在三次握手过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接.此时服务器处于Syn_RECV状态.当收到ACK后,服务器转入ESTABLISHED状态.
Syn攻击就是 攻击客户端 在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直 至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
Syn攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被Syn攻击
netstat -n -p TCP | grep SYN_RECV
一般较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等.
应付Syn攻击的基本方法如下:
1)SYN cookie(开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭)
在linux下以root权限执行:
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
这个方法打开了syncookie功能,但实际效果几乎感觉不到。
2)增大backlog( 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数)
通过增加backlog的数值,可以一定程度减缓大量SYN请求导致TCP连接阻塞的状况,一般这个数值系统默认是1024,实验增加到1280~2048:
echo "2048" > /proc/sys/net/ipv4/tcp_max_syn_backlog
这样在强度不是很高的攻击下,系统响应能力提高了一点。
3)缩短retries次数 (设定 Linux 核心在回应 SYN 要求时会尝试多少次重新发送初始 SYN,ACK 封包后才决定放弃)
Linux系统默认的tcp_synack_retries是5次,将这个数值减少可以提高系统响应能力,实验改为2次:
echo "2" > /proc/sys/net/ipv4/tcp_synack_retries
修改后,SYN_RECV的数量有了少量减少,系统响应也快了一些。
4)限制SYN频率(iptables 防火墙)
上述的几个方法实际效果并不理想,尤其是DDOS攻击基本无效,目前比较有效的是对SYN的频率和次数进行限制,这样最大限度的控制了单个IP地址发动攻击的能力。
例如将SYN请求的次数限制在30次每分钟,系统默认是5次/秒,显然太高,同时将burst从默认的5个降低到2个(syn状态连接总数达到该数量时启动限制)。
/sbin/iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST SYN -m limit --limit 30/m --limit-burst 2 -j ACCEPT
注意: 该命令在shell下输入,'\'符号表示续行。
进行此操作后,对正常的用户而言无任何感觉上的差异,而并发的SYN请求量下降了不少,服务响应基本正常了。
当完成三次握手的双方,其中有一端发出FIN,此时它将进入半关闭状态,此时它关闭了自身的发送功能,但是它依然可以接收到对方的数据,如对方发过来的ACK消息。
(1)shutdown和close函数
shutdown函数原型
int shutdown(int s, int how) <sys/socket.h>1)close是推送数据并关闭连接。 而shutdown只是关闭发送或者接收或者两者,而并不是关闭连接。
2)shutdown(SD_SEND)先把socket缓冲区中的数据发送出去,然后发送FIN数据包,并没有释放本地资源(文件描述符资源)。socket在内核分配的资源没有释放,如File对象、Inode节点,dentry,file_struct等一个文件所对应的资源。
3) 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。而在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法使用该socket进行通信.
(2)SO_LINGER选项
当调用close关闭套接字时,SO_LINGER将决定系统如何处理残存在套接字发送队列中的数据。处理方式无非两种:丢弃或者将数据继续发送至对端,优雅关闭连接。事实上,SO_LINGER并不被推荐使用,大多数情况下我们推荐使用默认的关闭方式(即下方表格中的第一种情况)。
下方代码段显示linger结构语法,表格为不同参数情况下的套接字行为。
typedef struct linger { u_short l_onoff; //开关,零或者非零 u_short l_linger; //优雅关闭最长时限 } linger;
l_onoff | l_linger | closesocket行为 | 发送队列 | 底层行为 |
零 | 忽略 | 立即返回。 | 保持直至发送完成。 | 系统接管套接字并保证将数据发送至对端。 |
非零 | 零 | 立即返回。 | 立即放弃。 | 直接发送RST包,自身立即复位,不用经过2MSL状态。对端收到复位错误号。 |
非零 | 非零 | 阻塞直到l_linger时间超时或数据发送完成。(套接字必须设置为阻塞zhuan) | 在超时时间段内保持尝试发送,若超时则立即放弃。 | 超时则同第二种情况,若发送完成则正常流程关闭。 |
TCP/IP中关于定时器的介绍,TCP为每条连接建立七个定时器,分别为:
1.连接建立定时器
在发送SYN报文段建立一条新连接时启动。如果没有在75S内收到响应,连接建立将终止。
2.重传定时器
在TCP发送数据时设定。如果定时器已超时而对端的确认还未到达,TCP将重传数据。重传定时器的值(即TCP等待对端确认的时间)是动态计算的,取决于TCP为该连接测量的往返时间和该报文段已被重传的次数。
3.延迟ACK定时器
在TCP收到必须被确认但无需马上发出确认的数据设定。TCP等待200ms后发送确认响应。如果在这200ms内,有数据要在该连接上发送,延迟的ACK响应就可随着数据一起发送回对端,称为捎带确认。
4.持续定时器
坚持定时器主要是解决零窗口大小通知可能导致的死锁问题。
在连接对端通告接收窗口为0,阻止TCP继续发送数据时设定。由于连接对端发送的窗口通告不可靠(只有数据才会被确认,ACK不会被确认),允许TCP继续发送数据的后续窗口更新有可能丢失。因此,如果TCP有数据要发送,单对端通告接收窗口为0,则启动持续定时器,超时后向对端发送1字节的数据的探测报文。探测报文段提醒接收端,确认已丢失,必须重传。
持续计时器的到期时间设置为重传时间的值,但若超时没有收到来自接收端的响应,则将坚持计时器的值加倍和并复位,发送端继续发送探测报文段,直到这个值增大到阈值为止(通常为 60 秒),以后发送端每隔 60s 就发送一个报文段,直到通告窗口不为0的报文,否则重置定时器继续发送。
5.保活定时器
保活定时器是为了应对 TCP 连接双方出现长时间的没有数据传输的情况。如果客户端与服务器建立了 TCP 连接之后,客户端由于某种原因导致主机故障,则服务器就不能收到来自客户端的数据,而服务器不可能一直处于等待状态,保活定时器就是用来解决这个问题的。服务器每收到一次客户端的数据,就重新设置保活定时器,通常为 2 小时,如果 2 小时没有收到客户端的数据,服务端就发送一个探测报文,以后每隔75秒发送一次,如果连续发送10次探测报文段后仍没有收到客户端的响应,服务器就认为客户端出现了故障(主机故障,例如系统崩溃而尚未重启;如果是系统崩溃重启,会响应复位报文;连接故障,如中间的路由器发送故障或电话线断了),就可以终止这个连接。应用进程在选取了SO_KEEPALIVE选项时保活定时器生效。。
6.FIN_WAIT_2定时器
当某个连接从FIN_WAIT_1状态变迁到FIN_WAIT_2状态,并且能再接收任何新数据时(意味着应用进程调用了close,而非shutdown,没有利用TCP的半关闭功能),FIN_WAIT_2定时器启动,设为10分钟。定时器超时后,重新设为75S,第二次超时后连接被关闭。加入这个定时器的目的是为了避免如果对端一直不发送FIN,某个连接会永远滞留在FIN_WAIT_2状态。
7.TIME_WAIT定时器
一般也称为2MSL定时器。MSL指最大报文段生存时间。当连接转移到TIME_WAIT状态(连接为主动关闭),定时器启动。连接进入TIME_WAIT状态时,定时器时间设定为2MSL(在RFC793指出MSL为2分钟,然而,实现中的常用值是30秒,1分钟或2分钟),超时后,TCP控制块和InternetPCB被删除,端口号可重新使用。
TCP定时器的实现使用两个定时器函数
一个函数为200ms调用一次(快速定时器);另一个函数每500ms调用一次(慢速定时器)。延迟ACK定时器与其它6个定时器有所不同,如果某个连接上设定了延迟ACK定时器,那么下一个200ms定时器超时后,延迟的ACK必须被发送(ACK的延时时间必须在0-200ms之间)。其它的定时器每500ms递减一次,计时器减为0时,就触发相应的动作。
受到诸多因素的影响,如硬件(双方网卡吞吐量差异)、网络环境,网络极易出现各种各样的拥塞,目前所采取的措施主要有以下两种:改进拥塞算法,,以及控制发送端和接收端的流量。
TCP中流量控制机制有滑动窗口机制和停止等待算法.
滑动窗口机制实现在接收端和发送端进行流量控制。
接收端,存在一个接收缓存区,用来接收来自于发送方的数据,只有当应用进程从接收缓存区中取出数据(可能只是部分)并发出其ACK后,才算作这部分数据已经接收,然后调节此时的滑动窗口大小。
发送端,根据返回的窗口大小,计算出所能发送的数据大小。滑动窗口算法是接收端作为主动方根据自身的缓存以及处理能力主动去调节对方的发送流量的一种调节算法。
网络拥塞出现的原因:
1)收端缓存和处理能力,发端的缓存和发送速度
2)转发路由器的缓存和转发能力
滑动窗口是基于所收到的确认序列号的。当发送方根据所收到的确认序列号以及窗口大小,不断向后移动,并相应更新数据状态。
若它仅仅通过接收端所返回的滑动窗口大小,因为与发送端相连的路由器因为自身的缓冲空间的限制,难以存储并转发这么多数据而出现丢包现象。
处理方式是中间的路由器也要反馈给发送端拥塞窗口的大小。
发送端会收到两个窗口大小,分别是来自于接收端和中间路由器,前者在每次的数据报之后都会发送,而后者只是在中间路由器检查网络中出现拥塞时发送。
发送方此时取接收端的窗口大小与路由拥塞窗口大小中的最小值作为实际发送窗口。
在接收端实施流量控制所提出了的经典算法。接收端在收到一个数据报之后,停止接收新的数据报,直到发出ACK(对收到的数据报的确认)之后进行恢复。但是遇到一个效率的问题,特别是随着网络设备的数据处理能力得到了很大的提高,效率低下显得尤其明显。
停止等待协议的算法流程如下:
确认帧带有序号,ACKn 表示“第 n – 1 号帧已经收到,现在期望接收第 n 号帧”。
ACK1 表示“0 号帧已收到,现在期望接收的下一帧是 1 号帧”;
ACK0 表示“1 号帧已收到,现在期望接收的下一帧是 0 号帧”。
在发送结点
(1) 从主机取一个数据帧,送交发送缓存。
(2) V(S)←0。 /* 发送状态变量(帧序号)初始化 */
(3) N(S)←V(S)。 /* 将发送状态变量的数值写入发送序号 */
(4) 将发送缓存中的数据帧发送出去。
(5) 设置超时计时器。 /* 选择适当的超时重传时间 */
(6) 等待。 {等待以下(7)、(8)、(9)这3个事件中最先出现的一个}
(7) 收到确认帧 ACKn,
若 n = 1 – V(s),则:
从主机取一个新的数据帧,放入发送缓存;
V(S)←[1 -V(S)],转到 (3)。
否则,丢弃这个确认帧,转到(6)。
(8)若收到否认帧(NAK),则转到(4)。 /*重传数据帧 */
(9)若超时计时器时间到,则转到(4)。 /*重传数据帧 */
在接收结点
(1) V(R)←0。
(2) 等待。
(3) 收到一个数据帧;
若检测出帧出错,转到(8)。
(4) 若 N(S) = V(R),则执行(5); /* 确认本数据包,请求下一个*/
否则丢弃此数据帧,然后转到(7)。 /*确认上一个数据包,请求本数据包*/
(5) 将收到的数据帧中的数据部分送交上层软件
(也就是数据链路层模型中的主机)。
(6) V(R)←[1 - V(R)]。
(7) n←V(R);
发送确认帧 ACKn,转到(2)
(8)发送否认帧(NCK),并转到(2)。 /* 请求重传该数据帧 */