【计网实验——prj16】网络传输机制实验三

【计网实验——prj16】网络传输机制实验三

实验内容

  支持TCP可靠数据传输

  • 网络丢包
  • 超时重传机制
  • 有丢包场景下的连接建立和断开
  • 发送队列和接收队列
  • 超时定时器实现

实验步骤

  • 修改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可靠数据传输。

超时重传机制

  超时重传机制基于定时器实现,为每一个连接维护一个超时重传定时器。

定时器管理方法:
  • 发送数据/FIN/SYN包时,若定时器没有开启,则启动定时器,并设置时间为200ms
  • 当ACK确认了部分数据,重启定时器,重设时间为200ms
  • 当ACK确认了全部数据(包括FIN和SYN包),则关闭定时器
定时器维护:
  • 在tcp_sock中维护定时器struct tcp_timer retrans_timer,其中,type变量用于记录是否为重传连接
struct tcp_timer {
	int type;	// time-wait: 0		retrans: 1
	int timeout;	// in micro second
	int retrans_time;
	struct list_head list;
	int enable;
};
  • 当开启定时器时,将retrans_timer放到timer_list
  • 关闭定时器时,将retrans_timertimer_list中移除
  • 定时器扫描,每10ms扫描一次定时器队列,重传定时器的值为200ms * 2^N
触发定时器后:

  触发定时器等待超时后,重传机制设计如下:

  • 重传发送队列 snd_buffer 中的第一个数据包
  • 将定时器等待时间增大一倍
  • 如果已经重传了三次仍没有得到应答,则发送 TCP_RST 包,关闭连接

  以上过程通过函数tcp_scan_timer_list实现,具体代码如下:

// scan the timer_list, find the tcp sock which stays for at 2*MSL, release it
void tcp_scan_timer_list()
{
	struct tcp_sock *sock;
	struct tcp_timer *time;
	int i = 0;
	list_for_each_entry(time, &timer_list, list){
		if(time->type == 0){
			time->timeout -= TCP_TIMER_SCAN_INTERVAL;
			if(time->timeout <= 0){
				list_delete_entry(&time->list);
				sock = timewait_to_tcp_sock(time);
				if(sock->parent == NULL){
					tcp_bind_unhash(sock);
				}
				tcp_set_state(sock, TCP_CLOSED);
				free_tcp_sock(sock);
			}
		}else if(time->type == 1){
			sock = retranstimer_to_tcp_sock(time);
			pthread_mutex_lock(&sock->time_lock);
			time->timeout -= TCP_TIMER_SCAN_INTERVAL;
			if(time->timeout <= 0){
				pthread_mutex_lock(&sock->sb_lock);
				struct send_buffer* temp;
				list_for_each_entry(temp, &(sock->send_buf), list){
					if(temp->number >= 3){
						//printf("have send three times\n");
						tcp_send_control_packet(sock, TCP_RST);
						exit(1);
					}else{
						char *packet_temp = (char *)malloc(temp->total_len);
						memcpy(packet_temp, temp->packet, temp->total_len);
						ip_send_packet(packet_temp, temp->total_len);
						
						time->timeout = TCP_RETRANS_INTERVAL_INITIAL;
						temp->number += 1;
						for(i=0; i<temp->number; i++){
							time->timeout = 2*time->timeout;
						}
					}
					break;
				}
				pthread_mutex_unlock(&sock->sb_lock);
			}
			pthread_mutex_unlock(&sock->time_lock);
		}
	}
}

发送队列维护

将数据包插入发送队列
  • 所有未确认的数据/SYN/FIN包,在收到其对应的ACK之前,都要放在发送队列snd_buffer中,以备后面可能的重传
  • 发送新的数据时,放到snd_buffer队尾,打开定时器

  以上过程根据将要发送数据包的序列号 seq、数据长度 data_len、总长度total_len等信息创建发送队列中的新表项,打开定时器,并将该表项加入到发送队列中。通过函数insert_send_buf实现,其主要在 tcp_send_packettcp_send_control_packet 函数中被调用,具体代码如下:

void insert_send_buf(struct tcp_sock* tsk, char *packet, u32 seq, int data_len, int total_len){
	struct send_buffer *temp = (struct send_buffer*)malloc(sizeof(struct send_buffer));
	char *packet_temp = (char *)malloc(total_len);
	memcpy(packet_temp, packet, total_len);
	
	temp->packet = packet_temp;
	temp->seq = seq;
	temp->data_len = data_len;
	
	pthread_mutex_lock(&tsk->sb_lock);
	pthread_mutex_lock(&tsk->time_lock);
	if((tsk->retrans_timer).type == 0){
		(tsk->retrans_timer).type = 1;
		(tsk->retrans_timer).timeout = TCP_RETRANS_INTERVAL_INITIAL;
		add_timer_list(tsk);
		tsk->ref_cnt += 1;
	}
	list_add_tail(&temp->list, &tsk->send_buf);
	pthread_mutex_unlock(&tsk->time_lock);
	pthread_mutex_unlock(&tsk->sb_lock);
}

  其中,自定义send_buffer表项的数据结构如下:

struct send_buffer {
	struct list_head list;
	char * packet;
	u32 seq;
	int data_len;
};
从发送队列中删除包
  • 收到新的ACK时, 如果发送队列表项的 seq_end(即 seq+data_len) <= ack,将该表项移除,并更新定时器
void delete_send_buf(struct tcp_sock *tsk, u32 ack){
	int length = 0;
	struct send_buffer *temp;
	pthread_mutex_lock(&tsk->sb_lock);
	list_for_each_entry(temp, &tsk->send_buf, list){
		if(less_or_equal_32b(temp->seq+temp->datalen, ack)){
			length += temp->datalen;
			list_delete_entry(&temp->list);
			pthread_mutex_lock(&tsk->time_lock);
			(tsk->retrans_timer).timeout = TCP_RETRANS_INTERVAL_INITIAL;
			pthread_mutex_unlock(&tsk->time_lock);
		}
	}
	pthread_mutex_unlock(&tsk->sb_lock);
	tsk->snd_wnd += length;
	if(tsk->wait_send->sleep == 1){
		wake_up(tsk->wait_send);
	}
}

接收队列维护

  • 数据接收方需要维护两个队列
    • 已经连续收到的数据,放在rcv_ring_buffer中供app读取
    • 收到不连续的数据,放到rcv_ofo_buffer队列中
  • TCP属于发送方驱动传输机制
    • 接收方只负责在收到数据包时回复相应ACK
  • 收到不连续的数据包时
    • 放在rcv_ofo_buffer队列,如果队列中包含了连续数据,则将其移到rcv_ring_buffer中

  以上过程通过函数handle_rcv_data实现,具体代码如下:

void handle_rcv_data(struct tcp_sock *tsk, struct tcp_cb *cb, char *packet){
	if(cb->seq == tsk->rcv_nxt){
		pthread_mutex_lock(&(tsk->rcv_buf->rb_lock));
		write_ring_buffer(tsk->rcv_buf, cb->payload, cb->pl_len);
		pthread_mutex_unlock(&(tsk->rcv_buf->rb_lock));	
		
		tsk->rcv_nxt += cb->pl_len;
		u32 record = tsk->rcv_nxt;
		while(1){
			int flag = 0;
			struct recv_buffer *temp;
			list_for_each_entry(temp, &tsk->rcv_ofo_buf, list){
				if(temp->seq == record){
					struct tcphdr *tcphdr_t = packet_to_tcp_hdr(temp->packet);
					char *data_t = (char *)tcphdr_t + tcphdr_t->off * 4;
					pthread_mutex_lock(&(tsk->rcv_buf->rb_lock));
					write_ring_buffer(tsk->rcv_buf, data_t, temp->datalen);
					pthread_mutex_unlock(&(tsk->rcv_buf->rb_lock));
					tsk->rcv_nxt += temp->datalen;
					record = tsk->rcv_nxt;
					flag = 1;
					list_delete_entry(&temp->list);
					break;
				}
			}
			if(flag == 0){
				break;
			}
		}
		tcp_send_control_packet(tsk, TCP_ACK);
		if(tsk->wait_recv->sleep == 1){
			wake_up(tsk->wait_recv);
		}
	}else if(less_than_32b(tsk->rcv_nxt, cb->seq)){
		struct iphdr *iphdr_t = packet_to_ip_hdr(packet);
		int all_len = ntohs(iphdr_t->tot_len) + ETHER_HDR_SIZE;
		struct recv_buffer *temp = (struct recv_buffer*)malloc(sizeof(struct recv_buffer));
		
		char *packet_temp = (char *)malloc(all_len);
		memcpy(packet_temp, packet, all_len);
		
		temp->packet = packet_temp;
		temp->total_len = all_len;
		temp->seq = cb->seq;
		temp->datalen = cb->pl_len;
		
		list_add_tail(&temp->list, &tsk->rcv_ofo_buf);
	}else{
		tcp_send_control_packet(tsk, TCP_ACK);
	}
	tsk->snd_una = cb->ack - 1;
	delete_send_buf(tsk, cb->ack);
}

运行结果

h2运行python tcp_stack.py测试h1

  实验结果如下:

在这里插入图片描述

h1运行python tcp_stack.py测试h2

  实验结果如下:

在这里插入图片描述

h1和h2同时运行本实验中实现的tcp_stack程序

  实验结果如下:

在这里插入图片描述

  通过比对可知本次实验结果符合预期,客户端发送的文件与服务器端接受的文件一致。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值