【计网实验——prj15】网络传输机制实验二
实验内容
实验一
- 运行给定网络拓扑(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协议栈的客户端模式,连接h1并正确收发数据 (./tcp_stack client 10.0.0.1 10001)
- client向server发送数据,server将数据echo给client
- 使用tcp_stack.py替换其中任意一端,对端都能正确收发数据
实验二
- 修改tcp_apps.c(以及tcp_stack.py),使之能够收发文件
- 执行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比较两个文件是否完全相同
- 使用tcp_stack.py替换其中任意一端,对端都能正确收发数据
实现方案
数据接收和缓存
TCP协议栈收到数据包后,将数据进行缓存,方便应用程序的读取。其中,缓存区域使用的是环形缓存(ring buffer),允许接收的数据缓存大小为recv_window。
tcp_sock_read
函数
该函数负责读取环形缓存区的内容,同时通过判断缓存区的状态控制数据的接收。当缓存区为空时,阻塞接收数据的线程,进入睡眠状态,等待唤醒;当缓存区不为空时,读取缓存区的内容,并唤醒接收线程(使其能够对读取的数据进行处理),最后将读取数据的长度最为返回值。
int tcp_sock_read(struct tcp_sock *tsk, char *buf, int len) {
while (ring_buffer_empty(tsk->rcv_buf)) {
sleep_on(tsk->wait_recv);
}
int rlen = read_ring_buffer(tsk->rcv_buf, buf, len);
wake_up(tsk->wait_recv);
return rlen;
}
更新tcp_process
函数
相较于上周的实验,本周的实验需要对该函数进行补充,以支持接收TCP数据包。更新部分的代码如下:
if (tsk->state == TCP_ESTABLISHED) {
if (tcp->flags & TCP_FIN) {
tcp_set_state(tsk, TCP_CLOSE_WAIT);
tsk->rcv_nxt = cb->seq + 1;
tcp_send_control_packet(tsk, TCP_ACK);
} else if (tcp->flags & TCP_ACK) {
if (cb->pl_len == 0) {
tsk->snd_una = cb->ack;
tsk->rcv_nxt = cb->seq + 1;
tcp_update_window_safe(tsk, cb);
} else {
while (ring_buffer_full(tsk->rcv_buf)) {
sleep_on(tsk->wait_recv);
}
write_ring_buffer(tsk->rcv_buf, cb->payload, cb->pl_len);
wake_up(tsk->wait_recv);
tsk->rcv_nxt = cb->seq + cb->pl_len;
tsk->snd_una = cb->ack;
tcp_send_control_packet(tsk, TCP_ACK);
}
}
}
其中,在TCP_ESTABLISHED
状态添加实现了处理接收数据的过程,即先确认环形缓存区是否为满,若满,则暂停接收(阻塞接收数据的线程,进入睡眠状态,直到缓存区不再为空时被唤醒);若还未满,则将收到了的数据写入缓存区,将接收线程唤醒。
数据发送
数据发送的流程如下:
- 待发送数据全部存储于上层应用buffer中
- 如果对端recv_window允许,则发送数据
- 每次读取1个数据包大小的数据
min(data_len, 1514 - ETHER_HDR_SIZE - IP_HDR_SIZE - TCP_HDR_SIZE)
- 封装数据包,通过IP层发送函数,将数据包发出去
该过程主要通过tcp_sock_write
函数实现。
tcp_sock_write
函数
int tcp_sock_write(struct tcp_sock *tsk, char *buf, int len) {
int single_len = 0;
int init_seq = tsk->snd_una;
int init_len = len;
while (len > 1514 - ETHER_HDR_SIZE - IP_BASE_HDR_SIZE - TCP_BASE_HDR_SIZE) {
single_len = min(len, 1514 - ETHER_HDR_SIZE - IP_BASE_HDR_SIZE - TCP_BASE_HDR_SIZE);
int send_packet_len = ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE + len;
char * packet = (char *)malloc(send_packet_len);
memcpy(packet + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE, buf + (tsk->snd_una - init_seq), len);
tsk->snd_wnd = len;
tcp_send_packet(tsk, packet, send_packet_len);
if (tsk->snd_wnd == 0) {
sleep_on(tsk->wait_send);
}
len -= single_len;
}
int send_packet_len = ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE + len;
char * packet = (char *)malloc(send_packet_len);
memcpy(packet + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + TCP_BASE_HDR_SIZE, buf + (tsk->snd_una - init_seq), len);
tsk->snd_wnd = len;
tcp_send_packet(tsk, packet, send_packet_len);
return init_len;
}
运行结果
结点h1作为服务器,结点h2作为客户端,分别运行TCP程序:
实验一
h2运行python tcp_stack.py测试h1
实验结果如下:
h1运行python tcp_stack.py测试h2
实验结果如下:
h1和h2同时运行本实验中实现的tcp_stack程序
实验结果如下:
实验二
h2运行python tcp_stack.py测试h1
实验结果如下:
h1运行python tcp_stack.py测试h2
实验结果如下:
h1和h2同时运行本实验中实现的tcp_stack程序
实验结果如下:
通过比对可知本次实验结果符合预期,客户端发送的文件与服务器端接受的文件一致。