【计网实验——prj17】网络传输机制实验三
实验内容
实验步骤
- 执行create_randfile.sh,生成待传输数据文件client-input.dat
- 运行给定网络拓扑(tcp_topo.py)
- 在节点h1上执行TCP程序
- 执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
- 在h1上运行TCP协议栈的服务器模式 (./tcp_stack server 10001)
- 在节点h2上执行TCP程序
- 执行脚本(disable_tcp_rst.sh, disable_offloading.sh),禁止协议栈的相应功能
- 在h2上运行TCP协议栈的客户端模式 (./tcp_stack client 10.0.0.1 10001)
- Client发送文件client-input.dat给server,server将收到的数据存储到文件server-output.dat
- 使用md5sum比较两个文件是否完全相同
- 记录h2中每次cwnd调整的时间和相应值,呈现到二维坐标图中
实现方案
??本实验需要实现拥塞控制下的数据传输,当网络中的数据包数目小于发送窗口时,允许发送数据包。其中,拥塞控制涉及Open,Disorder,Recovery和Loss四个状态,对于拥塞窗口大小的控制使用慢启动、拥塞避免、快重传、超时重传和快恢复五种方法。
拥塞窗口大小控制
??拥塞窗口大小的控制分为窗口增大,减小和不变三种情况,由慢启动和拥塞避免来控制窗口的增大,快重传和超时重传控制窗口的减小,快恢复保持窗口大小不变。
窗口增大
- 慢启动(Slow Start)
- 对方每确认一个报文段,cwnd增加1MSS,直到cwnd超过ssthresh值
- 经过1个RTT,前一个cwnd的所有数据被确认后, cwnd大小翻倍
- 拥塞避免(Congestion Avoidance)
- 对方每确认一个报文段,cwnd增加
1
M
S
S
C
W
N
D
?
1
M
S
S
\frac{1 MSS}{CWND} ? 1MSS
CWND1MSS??1MSS
- 经过1个RTT,前一个cwnd的所有数据被确认后, cwnd增加
1
M
S
S
1 MSS
1MSS
??具体代码实现如下:
void update_cwnd(struct tcp_sock *tsk) {
if ((int)tsk->cwnd < tsk->ssthresh) {
tsk->cwnd ++;
} else {
tsk->cwnd += 1.0/tsk->cwnd;
}
}
窗口减小
- 快重传(Fast Retransmission)
- ssthresh减小为当前cwnd的一半:ssthresh <- cwnd / 2
- 新拥塞窗口值cwnd <- 新的ssthresh
- 超时重传(Retransmission Timeout)
- Ssthresh减小为当前cwnd的一半:ssthresh <- cwnd / 2
- 拥塞窗口值cwnd减为1 MSS
??在具体实现减半操作时时,每收到一个ACK,cwnd的值减少0.5 (cwnd的变量类型设为了float )。
窗口不变
- 快恢复(Fast Recovery)
- 进入:在快重传之后立即进入
- 退出:
- 当对方确认了进入FR前发送的所有数据时,进入Open状态
- 当触发重传后,进入Loss状态
- 在FR内,收到一个ACK:
- 若该ACK没有确认新数据,则说明inflight(在途数据包数目)减一,cwnd允许发送一个新数据包
- 若该ACK确认了新数据
- 如果是Partial ACK,则重传对应的数据包
- 如果是Full ACK,则退出FR阶段
??发送数据包之前,需要先判断是否允许发送数据包,也就是在途数据包数目是否小于发送窗口大小,这个过程通过函数is_allow_to_send 实现:
int is_allow_to_send (struct tcp_sock *tsk) {
int inflight = (tsk->snd_nxt - tsk->snd_una)/MSS - tsk->dupacks;
return max(tsk->snd_wnd / MSS - inflight, 0);
}
拥塞控制状态转移
??根据以上拥塞窗口大小控制的方法,可以得到四种状态间的迁移关系如图所示:
Open
??当没有丢包且没有收到重复的ACK时,处于该状态,同时也是初始状态。若此时收到了新的ACK,则增加拥塞窗口值,若收到了重复的ACK,则状态转移到Disorder。代码实现如下:
if (tsk->current_state == TCP_OPEN) {
if (isNewAck) {
update_cwnd(tsk);
} else {
tsk->dupacks ++;
update_cwnd(tsk);
tsk->current_state = TCP_DISORDER;
}
return;
}
Disorder
??收到了重复的ACK,但重复数量还不足以触发重传,则处于Disorder状态。此时,类似于Open状态,若收到了新的ACK,则增加拥塞窗口值,状态跳回Open;若收到了三个重复的ACK,则转移到下一个状态Recovery。同时,更新ssthresh的值,标记拥塞窗口大小应减小(在tcp_sock 结构体中新增变量cwnd_grow ,其为1表示拥塞窗口大小应增加,反之为0)。代码实现如下:
if (tsk->current_state == TCP_DISORDER) {
if (isNewAck) {
update_cwnd(tsk);
tsk->current_state = TCP_OPEN;
tsk->dupacks = 0;
} else {
tsk->dupacks ++;
update_cwnd(tsk);
if (tsk->dupacks == 3) {
tsk->ssthresh = max((u32)(tsk->cwnd / 2), 1);
tsk->cwnd -= 0.5;
printf("cwnd-:%f\n",tsk->cwnd);
tsk->cwnd_grow = 0;
tsk->recovery_point = tsk->snd_nxt;
retrans_send_buffer_packet(tsk);
tsk->current_state = TCP_RECOVERY;
}
}
return;
}
Recovery
??遇到网络丢包时,处于该状态。此时,进入快重传快恢复阶段,先将窗口值减半,然后恢复丢包。在恢复丢包的过程中,当收到一个ACK时,若收到的ACK小于切换到Recovery状态时的snd_nxt时,即Partial ACK,则重传对应数据包;若收到的ACK大于等于切换到Recovery状态时的snd_nxt时,即Full ACK,则退出快恢复(FR)。与此同时,状态跳转分为两种情况:如果对方确认了进入快恢复阶段前发送的所有数据,则跳转回Open状态;如果触发了超时重传,那么进入Loss状态。代码实现如下:
if (tsk->current_state == TCP_RECOVERY) {
if (tsk->cwnd > tsk->ssthresh && tsk->cwnd_grow == 0) {
tsk->cwnd -= 0.5;
printf("cwnd-:%f\n",tsk->cwnd);
} else {
tsk->cwnd_grow = 1;
}
if (isNewAck) {
if (cb->ack >= tsk->recovery_point) {
tsk->current_state = TCP_OPEN;
tsk->dupacks = 0;
} else {
retrans_send_buffer_packet(tsk);
}
} else {
tsk->dupacks ++;
}
return;
}
Loss
??当触发超时重传时,处于该状态。因此,在实现超时重传的函数中,需要添加状态转换为Loss的部分。在函数tcp_scan_retrans_timer_list 中添加以下部分:
tsk->ssthresh = max(((u32)(tsk->cwnd / 2)), 1);
tsk->cwnd = 1;
tsk->current_state = TCP_LOSS;
tsk->loss_point = tsk->snd_nxt;
??在该状态下认为所有未确认的数据都已丢失,拥塞窗口从1开始慢启动增长。超时重传ssthresh减小为当前cwnd的一半,拥塞窗口值减为1MSS,增加拥塞窗口值,当重传完成后,即收到的ACK大于切换到Loss状态时的snd_nxt时,跳转进入Open状态。
if (tsk->current_state == TCP_LOSS) {
if (isNewAck) {
update_cwnd(tsk);
if (cb->ack >= tsk->loss_point) {
tsk->current_state = TCP_OPEN;
tsk->dupacks = 0;
}
} else {
tsk->dupacks ++;
update_cwnd(tsk);
}
return;
}
运行结果
??本次实验的传输结果如下:
??上图可知,可知本次实验结果符合预期,客户端发送的文件与服务器端接受的文件一致。
??CWND的变化曲线如下: ??如上图所示,随着时间的变化,拥塞窗口值大致符合慢启动,拥塞避免,快重传以及超时重传的主要特点。
问题与感想
??第三阶段的实现全部基于TCP协议栈展开,具有连续性,因此在完成每次实验时需要更加严谨,以尽量保证下一次复用实验代码时不会暴露潜在问题,这对于我们来说是非常考验理论知识的掌握情况和思维的严谨性的。事实上,在认真理解好实验框架与各传输机制的原理之后,需要实现的代码并没有多少,但设计想法上存在漏洞的话却仍然需要花较长的时间进行调试,比如一开始在状态转换上我漏掉了超时重传函数中的部分,导致始终无法得到正确的结果。这个过程足以使我们对理论课学习的知识有更好的掌握。通过一学期的实验,我对于计算机网络相关协议和内容在亲自动手实现的过程中有了更加深刻的理解,这得益于老师在课堂上对实验相关知识耐心且清晰的讲解,非常感谢老师的辛勤付出。
|