可靠传输协议

所谓可靠传输协议,通俗一点说,就是发送方给接收方发送数据,接收方保证能正确接收到数据。一个可靠传输协议一般需要有三个性质:ACK、超时重传、序列号(数据包的序列号与ACK的序列号)。

ACK肯定不用说必须有,否则如果没有ACK, A 给 B 发送数据包,A都不知道B到底有没有收到。

再说超时重传,先看最简单的情况,A每次只能发送一个数据包给B,而且要收到ACK后才能发送下一个数据包。假设A发送数据包P1给B,A收不到ACK有可能是P1丢失了,也有可能是ACK丢失了。由于A无法判断是哪一种情况,所以只要没收到ACK,必须重传。但关键是,发送一个数据包后,A需要等多久,才开始重传呢。如果等待时间过短,说不定重传后就接收到了”迟到“的ACK。那么换句话说,A发送一个包P1后,等待多长时间,能够保证之后不会再收到P1的ACK。我们假设一个包在网络中的最大生存时间为MST(也就是说,经过MST时间后,如果该包还在网络中,就应该把它丢弃),思考一下,发现A的等待时间应该设为 2*MST。等待这个时间后,A是肯定收不到ACK的。

通过上面的分析发现,每次发送一个包时,即使没有序列号也能保证数据包之间传输的可靠性。没错,情况就是这样的。

接下来我们分析如果每次能发送多个包的情况,为了更直观分析,就两个吧。假设A给B发送两个数据包P1、P2(假设如果没有丢包,那么先发送的包先到达),如果B收到P2后,P1丢失了,那下次收到重传的P1时,岂不是重组数据时把P2放到了P1的前面(因为如果包没有序列号,只能是谁先到,重组的时候谁放在前面),所以数据包一定要有序列号。假设ACK没有序列号,当A发送了两个数据包,丢失了一个包,A只收到了一个没有序列号的ACK,那么A该如何判断是哪个包丢失了呢;所以呢,ACK的序列号也是必需的。

通过以上的分析发现,在A可以同时传多个数据包时(实际上就是滑动窗口大于1),数据包与ACK都必须要有序列号。

接下来我们要分析,当接收方收到不按顺序到达的数据包时该如何处理。假设P0已经被正确接收,P1还没有收到,当收到P2时,接收方该如何处理这个包呢?丢失它?还是先把它缓存起来然后等P1正确接收后再交给上层协议?所以这时就有两种选择:

(1)数据包不按顺序到达时,接收方同样正确接收,然后把它们缓存起来。当然,接收方不可能缓存无数多个包,所以要指定一个大小,也就是所谓的接收窗口大小。假设接收窗口的大小为N(即最多能缓存N个数据包)且最后一个按顺序接收的包为P(k)(按顺序接收到的包会立即交给上层协议,所以P(0)到P(k)已经交给了上层协议),那么如果此时收到P(k+1)就把P(k+1)交给上层协议,如果此时收到的不是P(k+1)而是P(k+2)到P(k+N)中的若干个,不妨假设为P(k+1)和P(k+N)两个,则把它们都缓存起来,若之后收到P(k+1)就把P(k+1)和P(k+2)交给上层协议,然后又可以开始缓存P(k+3)到P(k+N+2)的数据包。

(2)数据包不按顺序到达时,就把包丢掉。这种情况我们说,接收窗口的大小为1。

以上可以看出,发送窗口大小为N的实质就是当P(k)被确认后,P(k+1)到P(k+N)可以按顺序发送出去,P(k+N)不需要等到P(k+1)到P(k+N-1)被确认。接收窗口大小为N的实质就是每次能缓存N个数据包。

Selective Repeat

选择重传协议就是上面对应的情况(1),它的发送窗口大小和接收窗口大小都为N。SR协议中,由于不按顺序到达的包也能够被正常接收,所以接收方收到包P(k)时,应该回复ACK(k)。超时重传体现在哪里呢?超时重传体现在,每个包都有自己独立的计时器,当一个包P(k)发送出去后,经过2MST后如果没有收到ACK(k),就重新发送这个包。

这个网站上有很多协议的动画模拟,非常棒

http://wps.pearsoned.com/ecs_kurose_compnetw_6/216/55463/14198700.cw/index.html

以下是一个模拟

在当前时刻,P0已经被确认,那么可以顺序发送P1到P5

接收P1后,接收方的滑动窗口向后滑动一格

当发送方收到ACK1后,滑动窗口向后滑一格,此时P6可以发送

若ACK2丢失,ACK3、4、6照样可以确认

当P2的计时器超时时,重传P2(没截图,因为模拟动画的等待时间太长了)

Go-Back-N

GBN对应的是上面的情况(2),发送窗口大小为N,接收窗口大小为1,每次只能按顺序接收包。现在我们分析其该如何回复ACK。假设P(k)已经被正确接收,交给了上层协议,此时等待接收P(k+1),如果P(k+1)到达,则回复ACK(k+1),然后把P(k+1)交给上层协议,继续等待接收P(k+2);但如果P(k+1)没有接收到,收到了P(k+2),怎么办?由于P(k+2)不能交给上层协议,那我们可以做的选择是缓存它,或者丢弃它;如果我们把P(k+2)缓存起来,那么接收窗口就不能缓存其他数据包了,当P(k+1)到达时,还是要把P(k+2)丢弃掉,用来缓存P(k+1)(每个数据包都要通过缓存交给上层协议);综上分析,我们只有一个选择,就是把P(k+2)丢弃掉。既然把P(k+2)丢弃了,我们就不能回复ACK(k+2),也不能回复ACK(k+1),所以只能回复ACK(k),表示最后一个正确接收的包为P(k)。

接下来我们看发送方收到ACK时如何处理。有两种情况:

(a)收到重复的ACK -- ACK(k)已经收到,不久后又收到了ACK(k)

(b)ACK序号不连续 -- ACK(k)已经收到,ACK(k+1)没有收到,却收到了ACK(k+2)

对于情况(a),GBN的做法是,不理会它。

对于情况(b),当收到ACK(k+2)时怎么办呢?丢掉还是用来确认数据包P(k+2)还是其他?我们来分析一下收到ACK(k+2)可能的情况,当收到ACK(k+2)时,表示P(k+2)已经被接收方正确接收了,所以P(k+1)也肯定已经被正确接收了,那么ACK(k+2)能同时确认P(k+1)和P(k+2)。

接下来讨论超时重传的问题,如何计时呢?假设发送方依次发送了P(k+1)到P(k+N),发送P(k+1)的时间点为t(k+1),发送P(k+N)的时间点为t(k+N),在t(k+1+2MST)时刻,如果还没有收到ACK(k+1),说明永远收不到ACK(k+1)了,但是,有可能收到ACK(k+2)到ACK(k+N),所以,理论上来说,我们应该把计时器设给最后一个发送的包。But,However,该协议并没有这么做,它把计时器设给了第一个发送的数据包。为什么要这样呢?原因是当发送第一个包时,协议不知道应用层什么时候会给它第二个数据包的数据,所以当发送第一个数据包时,就要开始计时。下面是发送方的代码,该代码说的就是对第一个包计时:

 

rdt_send(data)
{
	if (nextseqnum < base + N) // base = 1, nextseqnum = 1, base is the first package to send
	{
		sndpkt[nextseqnum] = make_pkt(nextseqnum, data, checksum);
		udt_send(sndpkt[nextseqnum]);
		if (nextseqnum == base)
			start_timer;
		nextseqnum++;
	}
	else
		refuse_data(data);
}

但是问题又来了,当第一个包的ACK收到了之后,计时器应该如何调整呢?在动画模拟的log输出中个人分析出,当第一个包的ACK收到后,重置计时器,对第二个包开始计时,如此向下类推。

 

下面是动画模拟图

发送P1到P5,但是P1丢失了

那么当接收方接收到P2时,回复的是ACK0

此时,当我们收到ACK0时,知道P1包丢失了,但是如果收到四个ACK0,说明P2到P5被接收了啊,重传P1不行么?答案是 不行。因为当接收到第一个ACK0时,我们不知道后面还有几个ACK0,而且当接收到第一个ACK0时,发送方就要反应,所以只能把其丢弃掉。

后续探索

上面讨论的SR是N-N,GBN是N-1模型,当发送窗口与接收窗口的大小不一样,比如发送窗口为5,接收窗口为3时呢?比如说发送方发送了P1到P5,但是接收方只能接收P1到P3,假如P1到P3刚好丢失,收到了P4和P5,如何处理?一种解决方案是采用GBN的模式,丢弃P4和P5,回复ACK0,表示P0是最后一个正确接收的包。但是,如果P1丢失,P2和P3收到了呢?还是把它们丢弃,回复ACK0?如果这样子的话,它和GBN协议完全没区别了,浪费了接收窗口的两个缓存。在这种情况下,其实可以回复ACK2和ACK3,把P2和P3缓存起来,发送方收到ACK2和ACK3后知道这两个被正确接收,选择重传P1即可,如果这样的话那么还是每个数据包一个单独的计时器。个人把这种模型暂时称为N-M模型

TCP传输协议

TCP的发送窗口是会变化的,所以它不是单纯的SR协议或者GBN协议,其实我们用上面的N-M模型就可以了(没错,本文最后有提到这种模型,而且还是后面提出的对TCP的一个修改)。但是,刚开始TCP并没有使用这种模型。

我们先来说TCP的一点改进。我们知道,在现实网络中,应用层给下层协议的数据一般时间间隔一般会很短。比如T时刻应用层给一部分数据给下层协议,发送方立即发送一个数据包P1,0.1秒后应用层又给了一部分数据给下层协议,发送方立即发送一个数据包P2,理想状态下,接收方收到P1,P2的时间间隔很短。接收方接收到一个包就立即回复一个ACK,会使得ACK的数量过多,加重网络的负担。可不可以接收到P1后稍微的等待一会儿呢,就一会儿,看还有没有其他的包,等待时间结束后,如果收到了P1和P2,只回复一个ACK2,表示ACK2及其以前的包都正确接收了?哈哈,没错,TCP就是这么干的。

接下来用简单的例子来说明TCP的工作过程。

假设A为发送方,B为接收方,开始时,A先后发送了0-999和1000-1999两个包给B,B接收到0-999后等待0.5秒,发现这0.5秒内还收到了1000-1999,于是回复一个累积ACK,ACK的序号为2000。注意,这里与GBN和SR不同,ACK2000表示字节2000以前的包都已经正确接收,下一个期望接收的包为2000字节开始的。

A接收到ACK2000该怎么处理呢?哦,它知道接收方是累积确认,也就知道虽然没有收到ACK1000,但是0-999和1000-1999都已经被正确接收了。

刚过不久,应用层又来新的数据了,这时A继续发送,假设发送了2000-2999,3000-3999这两个包,但是不幸的是,2000-2999丢失了,TCP只收到了3000-3999,这个时候B怎么办呢?丢弃这个包,然后回复ACK2000,表示我想要的包是2000字节开始的?由于TCP是累积ACK,回复ACK2000是没错的,但是如果把3000-3999丢弃了,岂不是浪费了自己接收窗口的缓存。于是,TCP就把它缓存起来,回复一个ACK2000。当发送方接收到ACK2000,发现它已经接收过了一次。

那么TCP如何对待重复的ACK呢?两种做法:

(a)不管它

(b)立即重传

在最初的TCP标准中,采用方案a,等待超时。

假设采取方案a,等待一段时间后,就会发生超时。那么问题来了,给哪个包计时呢?还是给整个窗口计时呢?如果整个窗口计时,那么3000-3999也要重传一次,然而接收方已经缓存了这个数据,所以根本没必要。于是,TCP的做法是,给第一个未被确认的包计时,这里也就是2000-2999,当超时后,TCP的发送方只重传这一个包。当接收方收到了2000-2999后,由于缓存中有3000-3999,于是就回复一个ACK4000,表示字节4000以前的数据包都正确接收了。

以下是TCP发送方的伪代码(对于Duplicated ACK,采用方案a)

 

loop (forever){
	switch (event)
		event : data received from application above
			create TCP segment with sequence number NextSeqNum
			if(timer currently not running)
				start timer
			pass segment to IP
			NextSeqNum += length(data)
			break;
		event : timer timeout
			retransmit not-yet-acknowledged segment with samllest sequence number
			start timer
			break;
		event : ACK received, with ACK field value of y
			if(y > SendBase)
			{
				SendBase = y
				if(there are currently any not-yet-acknowledged segments)
					start timer
			}
			break;
}

现在我们来总结下TCP的工作机制

 

ACK         ---  累积确认
超时重传 ---  给未被确认的发送时间最早的那个包计时

TCP看起来有点像GBN,回复最后一个按序接收到的包,但又与SR有点像,因为它会缓存无序的包,所以,它是一个混血儿。

TCP的一点修改(N-M模型)

在RFC2018中,提出了对TCP的一点修改,就是所谓的selective acknowledgement。就是说,接收方在收到无序数据包的时候,对这些数据包分别回复对应的ACK,当然,这种情况下TCP使用的就不是累积ACK了。惟一的一个缺点就是不能等待0.5秒发送累积ACK。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 很抱歉,作为AI语言模型,我无法进行编程实现。但是,基于UDP服务设计可靠传输协议的一般思路是通过添加重传机制、确认机制、超时机制等来保证数据的可靠传输。具体实现可以参考TCP协议的思路,但需要注意UDP协议的无连接特性。 ### 回答2: UDP是一种无连接的、不可靠传输协议,因此在UDP协议上实现可靠传输需要特殊的设计方法。可靠传输协议的设计思路一般分为两种: 1. 基于应用层协议的可靠传输设计: 基于应用层协议的可靠传输,主要是在应用层上增加一些控制信息,来保证数据的可靠传输。比如,在发送的每个数据包中,增加序列号,确认号,校验和等信息。 UDP协议中,没有序列号和确认号的机制,因此在应用层上我们可以自己实现这些机制。在发送数据包时,可以给数据包设定一个序列号,接收方收到数据包后发送一个确认包,来确认收到的数据包序列号。如果发送方没有收到确认包,则超时重传。 2. 基于定时器和缓存的可靠传输设计: 另外一种设计思路是通过定时器和缓存来解决可靠传输问题。在发送数据包时,会启动一个定时器,如果在规定时间内没有收到确认包,则超时重传。 同时,为了防止数据包丢失,可以在发送方和接收方分别维护一个缓存,用来存储已发送的数据包和已接收的数据包。接收方会根据序列号来判断数据包是否已经接收,并向发送方发送确认包。 基于第二种设计思路,我们可以将数据包的状态分为三种:未确认、已确认、已超时。 未确认的数据包需要等待接收方确认,已确认的数据包则可以从缓存中清除,已超时的数据包需要重新发送。 针对这两种设计思路,我们可以使用 Python 语言在 UDP 协议上进行编程实现。需要注意一些细节问题,如超时时间的设置、缓存的长度、序列号的范围等。同时,需要进行多次测试来验证可靠传输的性能和效果。 ### 回答3: UDP是一种无连接、不可靠传输协议,尽管它比TCP更简单,但在一些需要可靠传输的应用中存在一些问题,比如文件传输、流媒体传输等。因此,我们需要设计一种基于UDP的可靠传输协议来保证数据的可靠传输。 对于UDP的可靠传输协议,可以采取以下几种方法实现: 1. 消息序列号和确认机制:发送方给每个消息分配一个序列号,并且期望收到确认消息。当发送方发送完一个消息后,会在规定时间内等待接收到确认消息,如果没有收到,就重新发送该消息。接收方处理收到的消息,若消息序号与期望接收的序号不一致,则发送NACK给发送方,要求重新发送消息。 2. 数据包检验和和重传机制:发送方将数据包拆成小的块,并给每个块加上检验和。接收方接收到数据包后,计算检验和,如果检验和不对就要求发送方重新发送该数据包。 3. 超时重传机制:在传输数据时,发送方需要设置一个超时时间,在这个时间内如果没有接收到确认消息,则认为数据包丢失,需要重新发送。如果反复重传多次仍然没有收到确认消息,就认为连接已经断开,需要重新建立连接。 实现可靠传输协议需要编程实现,下面是一个基于UDP的简单例子: 发送方: ```python import socket import pickle import hashlib # 设置传输数据的参数 ip = 'localhost' port = 8000 bufsize = 1024 timeout = 3 # 建立Socket并绑定端口 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((ip,port)) # 数据序列号参数 seq_num = 0 # 传输数据 while True: # 要发送的数据 data = input("Enter data to send:") message = (data, seq_num) #计算校验和 chksum = hashlib.md5(pickle.dumps(message)).hexdigest() message_packet = [seq_num,chksum,message] # 发送数据并计时 sock.sendto(pickle.dumps(message_packet), (ip,port)) sock.settimeout(timeout) # 等待接收确认消息 try: ack, addr = sock.recvfrom(bufsize) except socket.timeout: print("Packet timed out, resending...") continue # 解析返回的确认消息 ack_data = pickle.loads(ack) ack_num = ack_data[0] if ack_num == seq_num: seq_num += 1 else: print("ACK not received correctly, resending...") #重置超时计时器 sock.settimeout(None) # 关闭socket连接 sock.close() ``` 接收方: ```python import socket import pickle import hashlib # 设置传输数据的参数 ip = 'localhost' port = 8000 bufsize = 1024 # 建立Socket并绑定端口 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((ip,port)) # 用于确认序号 expectedseqnum = 0 # 接收数据 while True: # 接收数据并计算校验和 data, addr = sock.recvfrom(bufsize) data_packet = pickle.loads(data) seq_num = data_packet[0] chksum = data_packet[1] message = data_packet[2] verify_checksum = hashlib.md5(pickle.dumps(message)).hexdigest() if chksum == verify_checksum: # 如果数据有效,发送ACK if seq_num == expectedseqnum: print(message) ack_data = expectedseqnum sock.sendto(pickle.dumps(ack_data), addr) expectedseqnum += 1 else: # 数据校验失败,需要重传 print('Checksum verification failed, packet dropped') # 重置超时计时器 sock.settimeout(None) # 关闭socket连接 sock.close() ``` 在上述代码中,我们实现了一个基于UDP的简单可靠传输协议。发送方在发送数据时通过计算数据的MD5校验和来判断数据是否正确,在发送完数据后等待接收到确认消息。接收方在接收到数据后计算校验和并发送确认消息给发送方,如果接收到的数据有误,接收方将丢弃这个数据包。通过这样的机制,我们就可以在基于UDP的数据传输中实现可靠性的保证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值