TCP/IP协议族(五)

TCP滑动窗口与回退N帧协议以及拥塞控制

滑动窗口
我们知道在数据链路层中也有一个滑动窗口,其实滑动窗口指的就是发送方和接收方所关心的序列号范围。在发送滑动窗口中,滑动窗口包含已经发送出去的序列号和可以发送的序列号。在接收滑动窗口中,滑动窗口包含了将要接收的序列号。

回退N帧
数据链路层的接收滑动窗口大小始终为1,也就是说在当前允许到达的序列号到达之前窗口不会发生滑动。如果接收方所期待的序列号为2,发送方已经将2,3,4号发出,但是由于传输原因2号丢失,那么发送窗口就需要从2号开始重新发送。

选择性重复ARQ

选择性重复ARQ的接收窗口大小是大于等于1的,他可以接收多个序列号,当其中有一个序列号丢失的时候,不需要从丢失的序列号开始全部重新发送,只需要重新发送丢失的序列号即可。

介绍完这三个基本概念我们来看看TCP的滑动窗口和回退N帧协议。
TCP的滑动窗口与数据链路层不同的一点是,数据链路层的滑动窗口是面向帧的,TCP的滑动窗口是面向字节的,而且数据链路层的窗口大小是固定的,TCP的窗口大小是可变的。
那什么是可变的呢?窗口是张开的,合拢的,收缩的。窗口张开是指右边沿向右移动,也就是允许缓存更新符合条件的字节。窗口合拢是指左边沿向右移动,也就是有些字节已经被确认。窗口收缩指的是右边沿向左移动,也就是废除了某些满足条件的发送(因为这样的操作有可能造成已经发送的字节失效,所以现实中不建议这样使用)。

TCP的回退N帧和数据链路层的有所不同,TCP中的回退N帧是数据链路层中回退N帧和选择性重复ARQ的结合体。
因为他没有NAK所以看起来像回退N帧,但是接收方会保存失序的段直到丢失的段到达,所以看起来也像选择重发。

TCP拥塞控制

TCP的拥塞控制大致分为三个阶段:慢速启动,拥塞避免,拥塞检测。

慢速启动:指数增长。在最开始时将窗口设置为一个最大段长度,随着接受到的确认个数的增加,窗口大小按照指数增长。直到达到窗口的阈值。

拥塞避免:加性增加。为了避免拥塞,当窗口到达阈值之后,随着确认个数的增加,窗口不再沿着指数增长,而是每次加1。

拥塞检测:乘性减少。发送方推测出发送拥塞现象的唯一方法就是通过重传的要求。也就是在发送方接收到三个ACK之后或者计时器到时。

如果是计时器到时,那么意味着存在严重拥塞的可能性,在这种情况下TCP会做出强烈的反应:
1、将阈值设为当前窗口的额一半
2、将拥塞窗口设置为一个最大段的长度
3、启动慢速启动阶段

如果是接收到三个ACK,那么意味着存在轻度拥塞的可能性,这种情况下TCP会做出轻度的反应:
1、将阈值设为当前窗口的一半
2、将拥塞窗口设置为阈值
3、启动拥塞避免阶段。

(像我们平时在下载东西的时候,可能下着下着下载速度就变慢了,如果我们暂停之后重新开始就会发现速度又重新变快了,那么大概就是TCP的拥塞控制的结果了。)

由于连续重传滑动窗口协议的实现可能有很多细节,因此这里只提供一个简单的示例代码作为参考。 发送方: ```python from socket import * # 创建套接字和连接服务器 client_socket = socket(AF_INET, SOCK_STREAM) client_socket.connect(('127.0.0.1', 8888)) # 定义窗口大小和起始序号 WINDOW_SIZE = 3 start_seq_num = 0 # 发送方发送数据的次数 send_times = 10 # 创建数据包 packets = [f"{i}".encode() for i in range(send_times)] # 记录已发送但未收到确认的数据包 unacknowledged_packets = packets[:WINDOW_SIZE] # 循环发送数据包 while packets: # 发送未确认的数据包 for packet in unacknowledged_packets: client_socket.send(packet) print(f"send:{packet.decode()}") # 接收确认,如果有超时则重传 for i in range(WINDOW_SIZE): try: # 设置接收超时时间 client_socket.settimeout(1) data = client_socket.recv(1024) except timeout: # 超时了,重传未确认的数据包 print(f"timeout, resend:{unacknowledged_packets[0].decode()}") client_socket.send(unacknowledged_packets[0]) continue # 收到确认,从未确认列表中删除 ack = int(data.decode()) print(f"ack:{ack}") unacknowledged_packets.pop(0) # 发送下一个数据包 if packets: packet = packets.pop(0) unacknowledged_packets.append(packet) # 关闭套接字 client_socket.close() ``` 接收方: ```python from socket import * import time # 创建套接字和绑定端口 server_socket = socket(AF_INET, SOCK_STREAM) server_socket.bind(('127.0.0.1', 8888)) server_socket.listen(1) print('The server is ready to receive') # 接收方接收数据的次数 receive_times = 10 # 定义窗口大小和期望接收的下一个序号 WINDOW_SIZE = 3 expected_seq_num = 0 # 记录已收到但未确认的数据包 unacknowledged_packets = [] # 循环接收数据包并发送确认 while receive_times > 0: # 接收数据包 connection_socket, address = server_socket.accept() data = connection_socket.recv(1024) print(f"receive:{data.decode()}") # 如果接收到的是期望的数据包,则发送确认 seq_num = int(data.decode()) if seq_num == expected_seq_num: expected_seq_num += 1 ack = str(seq_num).encode() connection_socket.send(ack) print(f"send ack:{ack.decode()}") # 将已收到但未确认的数据包添加到列表中 unacknowledged_packets.append(data) # 如果列表中的数据包个数达到窗口大小,则移动窗口并清空列表 if len(unacknowledged_packets) == WINDOW_SIZE: unacknowledged_packets.clear() receive_times -= WINDOW_SIZE print(f"move window, receive_times:{receive_times}") # 如果接收到的不是期望的数据包,则丢弃并重新发送确认 else: ack = str(expected_seq_num - 1).encode() connection_socket.send(ack) print(f"send ack:{ack.decode()}") # 延迟一段时间再关闭套接字,模拟网络延迟 time.sleep(0.5) connection_socket.close() # 关闭套接字 server_socket.close() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值