RDT通信

基于套接字编程实现RDT通信

RDT即可靠数据传输,此处基于套接字编程,在UDP的基础上,实现RDT通信。

相关原理:

RDT通信的意义:
由于 IP 协议为传输层提供的只是 best-effort 服务,并不能保证端到端的可靠数据传输。如果要基于 UDP 协议实现可靠数据传输,需要对 UDP 协议进行扩展。RDT 协议通过调用 UDP 协议的 seed_to 及 recv_from 函数进行数据包的发送和接收,在此基础上通过实现检验和,报文序列号,确认重传等机制为上层提供一条虚拟的可靠数据信道。
1. 停等协议
在此协议中,发送端每次只发送一个数据包,为此数据包设置超时定时器,然后进入阻塞等待该数据包的确认消息。发送端如果收到接收端的 ACK 消息,则开始发送下一个数据包;如果超时或收到 NACK 消息,则重新发送数据包。
接收端收到一个数据包后,先检查此数据包是否完整,如果完整,发送 ACK 消息;否则发送 NACK 消息。
2.Go-Back-N协议
在 GBN 协议中,发送端发送[base, base+N-1]的滑动窗口,为每个正在发送的数据包设置一个定时器。如果数据包 k 超时,所有 k 及以后的数据包重新发送。接收端只需要记住当前预期的数据包的序列号 expectedseqnum;如果收到完整的数据包,并且包序号等于 expectedseqnum,发送确认消息;否者将数据包丢
弃,并使用累积确认方式发送 ACK 数据包。
3.选择重传协议
在 GBN 协议中,如果接收方收到的数据包序列号不等于 expectedseqnum,则将其丢弃;如果一个数据包出错,则其以后的数据包都需要重传,无论此数据包是否已被正确接收过。而选择重传协议则让发送方只重传出错的数据包,接收端同样维护一个缓存窗口,如果接收的数据包的序列号在此窗口内,则将其缓存;如果接收到的数据包的序列号等于 recv_base,则将该数据包及其后连续的数据包交送到上层。
4. RDT 编程接口(API)
为方便实验,我们对原始的 UDP 发送接收程序进行了若干调整,并提供相关的文件读写及包封装的接口。

“net_exp.h”头文件
给出linux网络编程的各引用库,同时给出了进行RDT通信的相关宏定义,包括UDP地址、端口、数据包相关参数和等待时间等。

给出了功能接口:

int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag );

功能:
用于向空包rdt_pkt中写入:data_len字节长度的data_buff数据块,包序号seq_num(int是4个字节,在发送文件小于10M的情况下,足够保证包序号从头到尾都不重复,这里是简化的处理方法),以及相关的包头标识flag(这里flag只反映出RDT_CTRL_BEGN,RDT_CTRL_DATA, RDT_CTRL_ACK, RDT_CTRL_NACK ,RDT_CTRL_END这几个包属性。

int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag );

功能:
用于从总长为pkt_len字节的包中读取:data_buff数据块,包序号seq_num,以及相关的包头标识flag。
返回:包中的数据(载荷)长度。

void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len );

功能:
模拟不可靠信道的丢包现象,以百分之 RDT_PKT_LOSS_RATE 的概率(默认 5%)发送失败,并给出提示信息。
返回:无。

代码:

头文件:

包含常用的c头文件及socket编程所需的头文件。
定义RDT协议的一些常量及类型及包类型

#ifndef NETEXP_H
#define NETEXP_H

#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>


#define		RDT_SERVER_ADDRESS		"127.0.0.1"		//RDT服务器端IP
#define 	RDT_RECV_PORT			8006            //RDT接收端端口号
#define		RDT_SEND_PORT			8004            //RDT发送端端口号
#define		RDT_BEGIN_SEQ			1               //RDT数据包初始序列号,(假设数据包序列号不循环)

#define		RDT_PKT_LOSS_RATE		10				//不可靠数据传输层的丢包率
#define		RDT_TIME_OUT			5000            //数据包超时时限
#define		RDT_HEADER_LEN			(4 + 4)         //RDT头标长度
#define 	RDT_DATA_LEN			10           
 //RDT中数据域长度,此处设置较小,为方便观察实验现象
#define		RDT_PKT_LEN				( RDT_DATA_LEN + RDT_HEADER_LEN )	//RDT中数据包长度

//RDT包类型  flag
#define		RDT_CTRL_BEGN			0	//初始包
#define		RDT_CTRL_DATA			1   //数据包
#define 	RDT_CTRL_ACK			2   //ACK包
#define		RDT_CTRL_NACK			3   //NACK包
#define		RDT_CTRL_END			4	//结束包


/*
	RDT packet format: |CTRL|SEQ|...DATA...|
	将数据封装成RDT数据包,头部只包含控制域和序列号域,其中控制域用来标识RDT数据包类型,序列号域是包含此数据包的序列号。
        向空包rdt_pkt中写入:data_len字节长度的data_buf中的数据
	函数返回RDT数据包长度
*/
int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag );

/*
	RDT packet format: |CTRL|SEQ|...DATA...|
	将数据包解封装。
	函数返回RDT包中的数据长度
*/
int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag );

/*
	模拟不可靠数据传输,以一定的概率(RDT_PKT_LOSS_RATE)丢弃数据包,调用形式和recvfrom一致
*/
void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len );

#endif

rdt_pkt_util.c文件

包含一些功能函数,如将数据封装成RDT包的函数及解封装的函数,模拟不可靠数据传输的函数

#include "net_exp.h"

/*
	将数据封装成RDT数据包:即在数据前加上RDT数据包头部
*/
int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag )
{
	char *ptr = rdt_pkt;
	uint32_t ctrl_net_order = htonl( flag );
	uint32_t seq_net_order = htonl( seq_num );

	memcpy( ptr, &ctrl_net_order, sizeof(uint32_t) );
	ptr += sizeof(uint32_t);
	memcpy( ptr, &seq_net_order, sizeof(uint32_t) );
	ptr += sizeof(uint32_t);
	
	if( data_len > 0 && data_buf != NULL )
		memcpy( ptr, data_buf, data_len );
	return (RDT_HEADER_LEN+data_len);
}

/*
	将RDT数据包解封装
*/
int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag )
{
	char *ptr = rdt_pkt;
	uint32_t ctrl_net_order, seq_net_order;
	int data_len;
	
	memcpy( &ctrl_net_order, ptr, sizeof(uint32_t) );/*读第一部分存入ctrl_net_order*/
	ptr += sizeof(uint32_t);//指针偏移
	*flag = ntohl( ctrl_net_order );//赋值
	
	memcpy( &seq_net_order, ptr, sizeof(uint32_t) );//读第二部分存入seq_net_order
	ptr += sizeof(uint32_t);
	*seq_num = ntohl( seq_net_order );
	
	data_len = pkt_len - RDT_HEADER_LEN;
	if( data_buf != NULL && data_len > 0 )
		memcpy( data_buf, ptr, data_len );//读数据部分
	return data_len;
}

/*
	模拟不可靠数据传输,以一定的概率(RDT_PKT_LOSS_RATE)丢弃数据包
*/
void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len )
{
	int seed =  rand() % 100;
	if( seed >= RDT_PKT_LOSS_RATE )
		sendto( sock_fd, pkt, pkt_len, flags, recv_addr, addr_len );
	else //pkt lost
		printf( " emulate packet lost!\n" );
}

makefile文件:

make 的输入文件


PROGS = rdt_stopwait_receiver rdt_stopwait_sender

CC = cc		
CFLAGS = -g #-Wall
		
all: ${PROGS}
		
rdt_stopwait_receiver: rdt_stopwait_receiver.o rdt_pkt_util.c net_exp.h
	${CC} ${CFLAGS} -o $@ rdt_stopwait_receiver.o rdt_pkt_util.c
	
rdt_stopwait_sender: rdt_stopwait_sender.o rdt_pkt_util.c net_exp.h
	${CC} ${CFLAGS} -o $@ rdt_stopwait_sender.o rdt_pkt_util.c
	
.PHONY: clean	

clean:
	rm -f *.o *.exe ${PROGS} *.gz *.tar *.log

tar:
	tar czf exp_code_stopwait.tar.gz rdt_stopwait_receiver.c rdt_stopwait_sender.c \
	rdt_pkt_util.c net_exp.h README Makefile

rdt_stopwait_receiver.c文件:

停等协议的接收端程序,使用UDP socket接收数据并写入到文件中

#include "net_exp.h"

void usage( char **argv )
{
	printf( "wrong argument!\n" );
	printf( "usage: %s save_file\n",  argv[0] );
}

/*
	停等协议接收端接收函数
	输入参数:
		save_file_name: 保存文件名
		sock_fd:接收数据的socket
*/
int receive_file( char *save_file_name, int sock_fd )
{
	char reply_pkt_buf[RDT_PKT_LEN];//回复
	int reply_pkt_len;
	
	char rdt_pkt[RDT_PKT_LEN];//接收
	char rdt_data[RDT_DATA_LEN];
	int seq_num;
	int flag;
	int exp_seq_num; //当前接收端需要的数据包序列号
	
	int total_recv_byte = 0;
	
	struct sockaddr_in client_addr;
	int i, j, sin_len, pkt_len, data_len;
	
	int counter = 1;
	FILE *fp; //将收到的RDT数据包按顺序写到此文件中
	if( (fp = fopen( save_file_name, "w" )) == NULL )
	{
		printf( "open file : %s failed.\n",  save_file_name );
		return 1;
	}
	
	memset( &client_addr, 0, sizeof(client_addr) );
	sin_len = sizeof( client_addr );
	
	exp_seq_num = RDT_BEGIN_SEQ;//欲接收初始序列号1
	
	///TODO
	while(1) //接收RDT数据包,直到所有数据全部接收完毕
	{			
		
		//step 1. 接收RDT数据包 :	recvfrom()
                pkt_len = recvfrom( sock_fd, rdt_pkt, RDT_PKT_LEN, 0, 
		        (struct sockaddr *)&client_addr, &sin_len );
                
		//step 2. 解封装RDT数据包 : unpack_rdt_pkt(),数据存入rdt_data,返回数据长度
                data_len = unpack_rdt_pkt(rdt_data, rdt_pkt, pkt_len, &seq_num, &flag);

		//step 3. 检查此数据包是否为期待的数据包 : seq_num==exp_seq_num
                if(seq_num==exp_seq_num&&flag!=RDT_CTRL_END)
                {
                        fwrite(rdt_data, sizeof(char),data_len, fp);//把rdt_data中的数据写入文件中
                        printf( "receive len of %d data with seq_num #%d\n", data_len,seq_num);
                        total_recv_byte += data_len;
                        
			//step 4. 封装一个新的RDT数据包(ACK包) : pack_rdt_pkt()
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_ACK);
			//step 5. 调用不可靠数据传输发送新的RDT数据包(ACK包): udt_sendto()
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        printf( "send ACK for data #%d \n",seq_num);
                        exp_seq_num++;
                        continue;
                 }
                if(seq_num==exp_seq_num&&flag==RDT_CTRL_END)
                {
                        for(counter=1;counter<=10;counter++)
                        {
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_END);
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        }
                        printf( "send ACK for the last data #%d \n",seq_num);
                        break;

                }
                else 
                {
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_ACK);
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        printf( "send ACK for data #%d again \n",seq_num);
                        continue;
                }		
                
	}
	
		
	printf( "\n\nreceive file succeed. write to file %s\ntotal recv %d byte\n", save_file_name, total_recv_byte );
		
	fflush( fp );
	fclose( fp );
	return 0;
}

//主函数
int main( int argc, char **argv )
{
	struct sockaddr_in recv_addr;
	int sin_len;
	int sock_fd;
	int pkt_len;
	
	if( argc != 2 )
	{
		usage( argv );
		exit(0);
	}
        //printf("input recv_port:");
        //int recv_port;
	//scanf("%d",&recv_port);
	srand ( time(NULL) );//初始化随机数种子
	
	memset( &recv_addr, 0, sizeof(recv_addr) );
	recv_addr.sin_family = AF_INET;
	recv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	recv_addr.sin_port = htons(RDT_RECV_PORT );
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	if( bind( sock_fd, (struct sockaddr *)&recv_addr, sizeof(recv_addr) ) == -1 )
	{
		close( sock_fd );
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	if( receive_file( argv[1], sock_fd ) != 0 )
	{
		printf( "receive file %s failed.\n", argv[1] );
		close( sock_fd );
		exit(1);
	}
	
	close( sock_fd );
	return 0;
}

rdt_stopwait_sender.c文件:

停等协议的发送端程序,从文件中读取数据并封装成RDT数据包,采用停等协议发送数据包

#include "net_exp.h"

void usage( char **argv )
{
	printf( "wrong argument!\n" );
	printf( "usage: %s send_file_name. \n",  argv[0] );
}


/*
	停等协议发送端函数
	输入参数:
		send_file_name: 待发送的文件名,将此文件中的数据封装成一个个的数据包进行发送
		sock_fd:发送数据的socket (同时从该socket发送数据包和接收数据包ACK)
		recv_addr_ptr: 接收端的地址
*/
int deliver_file( char *send_file_name, int sock_fd,  struct sockaddr_in *recv_addr_ptr )
{
	char recv_pkt_buf[RDT_PKT_LEN];
	char rdt_pkt[RDT_PKT_LEN];
	int seq_num = RDT_BEGIN_SEQ;
	int flag;
	int rdt_pkt_len;
	int total_send_byte = 0;
	
	struct sockaddr_in reply_addr;
	int reply_addr_len;
	int reply_ack_seq;
	int reply_ack_flag;

	char send_window[RDT_DATA_LEN]; //发送端窗口大小为1
	
	FILE *fp;
	int i, j, read_len, pkt_len;
	int counter = 1;

	if( (fp = fopen( send_file_name, "r" )) == NULL )
	{
		printf( "open file : %s failed.\n",  send_file_name );
		return 1;
	}
	
	while(1)//直到文件全部发送完成,退出此循环
	{
		if( feof( fp ) )//文件读到结尾,包中flag设置为RDT_CTRL_END
		{
			flag = RDT_CTRL_END;
			read_len = 0;
			rdt_pkt_len = pack_rdt_pkt( NULL, rdt_pkt, 0, seq_num, flag );
		}
		else //文件未到结尾,设置包flag为RDT_CTRL_DATA
		{
			flag = RDT_CTRL_DATA;	
			read_len = fread( send_window, sizeof(char), RDT_DATA_LEN, fp );//文件内容读到窗口内
			rdt_pkt_len = pack_rdt_pkt( send_window, rdt_pkt, read_len, seq_num, flag );
		}
		//包封装完成
		while(1) //开始发送,直到成功接收到正确ack退出此循环
		{
			fd_set fds; //文件描述符集合
			struct timeval timeout;
			int sock_state;

			//不可靠的发出去
			printf( "send count #%d, rdt_pkt #%d: %d bytes.\n", counter++, seq_num, rdt_pkt_len );
			udt_sendto( sock_fd, rdt_pkt, rdt_pkt_len, 0,
					(struct sockaddr *)recv_addr_ptr, sizeof(*recv_addr_ptr) );

			timeout.tv_sec = 0;
			timeout.tv_usec = RDT_TIME_OUT ;
			FD_ZERO( &fds ); //初始化文件描述符
			FD_SET( sock_fd, &fds ); //将sock_fd加入到文件描述符集合中
			
			//一直等待到文件描述符集合中某个文件有可读数据,或者到达超时时限
			sock_state = select( sock_fd + 1, &fds, NULL, NULL, &timeout );

			if( sock_state == -1 ) //socket 错误
			{
				printf( "select failed.\n" );
				return 1;
			}
			else if( sock_state == 0 ) //数据包超时
			{
				printf( "packet #%d time out, resend....\n",  seq_num );
				continue; //重新发送
			}
			else //文件描述符集合中某个文件有可读数据
			{
				if( FD_ISSET( sock_fd, &fds ) ) //sock_fd有数据到达
				{
					memset( &reply_addr, 0, sizeof(reply_addr) );
					reply_addr_len = sizeof( reply_addr );
					pkt_len = recvfrom( sock_fd, recv_pkt_buf, RDT_PKT_LEN, 0, 
						(struct sockaddr *)&reply_addr, &reply_addr_len );
					//收包,解封		
					unpack_rdt_pkt( NULL, recv_pkt_buf, pkt_len, &reply_ack_seq, &reply_ack_flag );
					
					//发送成功,开始发送下一个数据包
					if( reply_ack_seq == seq_num && reply_ack_flag == RDT_CTRL_ACK )
					{
						printf( "receive ACK for rdt_pkt #%d\n", seq_num);
						break; //跳出当前循环
					}	
					if( reply_ack_flag == RDT_CTRL_NACK  ) //接收到NACK数据包
						continue; //重新发送
                                        if(reply_ack_flag == RDT_CTRL_END)
                                        {
						printf( "receive ACK for the last rdt_pkt #%d\n", seq_num);
						break; //跳出当前循环
					}
				}
			}
		}
		
		seq_num++;
		total_send_byte += read_len;
		if( flag == RDT_CTRL_END ) //如果所有数据包都发送完,则结束发送
			break; //跳出当前循环
	}
	printf( "\n\nsend file %s finished\ntotal send %5d bytes.\n", send_file_name, total_send_byte );
	fclose( fp );
	return 0;
}

//主函数
int main( int argc, char **argv )
{
	struct sockaddr_in recv_addr, send_addr;
	int sock_fd;

	if( argc != 2 )
	{
		usage( argv );
		exit(0);
	}
	
	srand ( time(NULL) ); //初始化随机数种子
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
        printf("input send_port:");
        int send_port;
	scanf("%d",&send_port);
	memset( &send_addr, 0, sizeof(send_addr) );
	send_addr.sin_family = AF_INET;
	send_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	send_addr.sin_port = htons( /*RDT_SEND_PORT*/send_port );
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	if( bind( sock_fd, (struct sockaddr *)&send_addr, sizeof(send_addr) ) == -1 )
	{
		close( sock_fd );
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
			
	memset( &recv_addr, 0, sizeof(recv_addr) );
	recv_addr.sin_family = AF_INET;
	recv_addr.sin_addr.s_addr = inet_addr( RDT_SERVER_ADDRESS );
	recv_addr.sin_port = htons( RDT_RECV_PORT );

	if( deliver_file( argv[1], sock_fd, &recv_addr ) != 0 )//发送
	{
		printf( "deliver file %s failed.\n", argv[1] );
		close( sock_fd );
		exit(1);
	}
	
	close( sock_fd );
	return 0;
}

rdt_gbn_sender.c

	GBN协议的发送端程序,从文件中读取数据并封装成RDT数据包,采用停等协议发送数据包
#include "net_exp.h"

void usage( char **argv )
{
	printf( "wrong argument!\n" );
	printf( "usage: %s save_file_name\n",  argv[0] );
}

/*
	Go-Back-N 协议接收端接受函数
	输入参数:
		save_file_name: 保存文件名
		sock_fd:接受数据的socket
*/
int receive_file( char *save_file_name, int sock_fd )
{
	char reply_pkt_buf[RDT_PKT_LEN];
	int reply_pkt_len;
	
	char rdt_pkt[RDT_PKT_LEN];
	char rdt_data[RDT_DATA_LEN];
	uint32_t seq_net_order;
	int seq_num;
	int flag;
	int exp_seq_num;
	
	int total_recv_byte = 0;
	
	struct sockaddr_in client_addr;
	int i, j, sin_len, pkt_len, data_len;
	
	int counter = 1;
	FILE *fp;
	
	if( (fp = fopen( save_file_name, "w" )) == NULL )
	{
		printf( "open file : %s failed.\n",  save_file_name );
		return 1;
	}
	
	memset( &client_addr, 0, sizeof(client_addr) );
	sin_len = sizeof( client_addr );

	exp_seq_num = RDT_BEGIN_SEQ;
	
	//TODO
	while(1) //接收RDT数据包,直到所有数据全部接收完毕
	{			
		/*
			step 1. 接收RDT数据包 :	recvfrom()
			step 2. 解封装RDT数据包 : unpack_rdt_pkt()
			step 3. 检查此数据包是否为期待的数据包 : seq_num==exp_seq_num
			step 4. 封装一个新的RDT数据包(ACK包) : pack_rdt_pkt()
			step 5. 调用不可靠数据传输发送新的RDT数据包(ACK包): udt_sendto()
		*/
                //step 1. 接收RDT数据包 :	recvfrom()
                pkt_len = recvfrom( sock_fd, rdt_pkt, RDT_PKT_LEN, 0, 
		        (struct sockaddr *)&client_addr, &sin_len );
                
		//step 2. 解封装RDT数据包 : unpack_rdt_pkt(),数据存入rdt_data,返回数据长度
                data_len = unpack_rdt_pkt(rdt_data, rdt_pkt, pkt_len, &seq_num, &flag);

		//step 3. 检查此数据包是否为期待的数据包 : seq_num==exp_seq_num
                if(seq_num==exp_seq_num&&flag!=RDT_CTRL_END)
                {
                        fwrite(rdt_data, sizeof(char),data_len, fp);//把rdt_data中的数据写入文件中
                        printf( "receive len of %d data with seq_num #%d\n", data_len,seq_num);
                        total_recv_byte += data_len;
                        
			//step 4. 封装一个新的RDT数据包(ACK包) : pack_rdt_pkt()
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_ACK);
			//step 5. 调用不可靠数据传输发送新的RDT数据包(ACK包): udt_sendto()
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        printf( "send ACK for data #%d \n",seq_num);
                        exp_seq_num++;
                        continue;
                 }
                if(seq_num==exp_seq_num&&flag==RDT_CTRL_END)
                {
                        for(counter=1;counter<=10;counter++)
                        {
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_END);
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        }
                        printf( "send ACK for the last data #%d \n",seq_num);
                        break;

                }
                else 
                {
                        pack_rdt_pkt(reply_pkt_buf, rdt_pkt,data_len,seq_num,RDT_CTRL_ACK);
                        udt_sendto(sock_fd, rdt_pkt, RDT_PKT_LEN, 0,
			            (struct sockaddr *)&client_addr, sin_len );
                        printf( "send ACK for data #%d again \n",seq_num);
                        continue;
                }													
	}
	printf( "\n\nreceive file succeed. write to file %s\ntotal recv %d byte\n", 
				save_file_name, total_recv_byte );
		
	fflush( fp );
	fclose( fp );
	return 0;
}

int main( int argc, char **argv )
{
	struct sockaddr_in recv_addr;
	int sin_len;
	int sock_fd;
	int pkt_len;
	
	srand ( time(NULL) );
	
	if( argc != 2 )
	{
		usage( argv );
		exit(0);
	}
	
	memset( &recv_addr, 0, sizeof(recv_addr) );
	recv_addr.sin_family = AF_INET;
	recv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	recv_addr.sin_port = htons( RDT_RECV_PORT );
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	if( bind( sock_fd, (struct sockaddr *)&recv_addr, sizeof(recv_addr) ) == -1 )
	{
		close( sock_fd );
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	if( receive_file( argv[1], sock_fd ) != 0 )
	{
		printf( "receive file %s failed.\n", argv[1] );
		close( sock_fd );
		exit(1);
	}
	
	close( sock_fd );
	return 0;
}


udp_gbn_receiver.c:

GBN协议的接收端程序,使用UDP socket接收数据并写入到文件中

#include "net_exp.h"

SLD_WIN sending_window; //滑动窗口数据结构体
int total_send_byte = 0; //记录累计发送的字节数	

void usage( char **argv )
{
	printf( "wrong argument!\n" );
	printf( "usage: %s send_file_name. \n",  argv[0] );
}


/*
	处理接收端发回的ACK,重新设置滑动窗口内数据包的状态
*/
void slide_window_ack( int ack_num )
{	
	pthread_mutex_lock( &sending_window.lock );//将互斥量上锁
	
	printf( "\t[child thread] recv ack# %-8d\n",  ack_num );
	if( ack_num >= sending_window.send_left && ack_num < sending_window.send_right )
	{
		int succ_pkt_seq;
		for( succ_pkt_seq = sending_window.send_left; succ_pkt_seq <= ack_num; succ_pkt_seq++ )
			if( sending_window.rdt_pkts[succ_pkt_seq % sending_window.win_len].state == 1 )
			{
				sending_window.rdt_pkts[succ_pkt_seq % sending_window.win_len].state = 2; //set pkt state to acked
				total_send_byte += (sending_window.rdt_pkts[succ_pkt_seq % sending_window.win_len].pkt_len - RDT_HEADER_LEN);
			}
	}
	else
	{
		if( ack_num < sending_window.send_left )
			printf( "\t[child thread] already acked #pkt %-8d\n", ack_num );
		else
		{
			printf( "\t[child thread] recv wrong acked, #ack[%d] > sending_window.send_right[%d].\n", ack_num, sending_window.send_right );
			sleep(10); //wrong ack, do something?
		}
	}
	
	pthread_mutex_unlock( &sending_window.lock ); //将互斥量上锁
}

/*
	用于监听端口并接收ACK包的线程函数
*/
void *recv_acks_thread( void *arg )
{
	int *sock_fd = ( int *) arg;
	char rdt_pkt[RDT_PKT_LEN];
	int pkt_len;
	
	struct sockaddr_in reply_addr;
	int reply_addr_len;
	int reply_ack_seq;
	int reply_ack_flag;
	memset( &reply_addr, 0, sizeof(reply_addr) );
	reply_addr_len = sizeof( reply_addr );
	
	printf( "\t[child thread] waiting acks..\n" );
	while(1)
	{		
		/*检查是否有数据包到达*/
		pkt_len = recvfrom( *sock_fd, rdt_pkt, RDT_PKT_LEN, MSG_PEEK, 
			(struct sockaddr *)&reply_addr, &reply_addr_len );
			
		if( pkt_len > 0 ) //有数据包到达
		{
			pkt_len = recvfrom( *sock_fd, rdt_pkt, RDT_PKT_LEN, 0, 
				(struct sockaddr *)&reply_addr, &reply_addr_len   );	
			
			unpack_rdt_pkt( NULL, rdt_pkt, pkt_len, &reply_ack_seq, &reply_ack_flag );
			if( reply_ack_flag == RDT_CTRL_ACK ) //有ACK包到达
				slide_window_ack( reply_ack_seq ); //重新设置滑动窗口状态
		}
	}
}



/*
	检查滑动窗口内是否有数据包需要发送,返回窗口内待发送的数据包数。
*/
int pre_sending_rdt_pkt( FILE *fp )
{
	int pkt_to_send = 0;
	
	char read_buf[RDT_DATA_LEN];
	int send_base_acked;
	
	pthread_mutex_lock( &sending_window.lock );
	
	STATE_PKT *ptr_pkt_left = &sending_window.rdt_pkts[ sending_window.send_left % sending_window.win_len];
	send_base_acked = (ptr_pkt_left->state == RDT_PKT_ST_ACKED) ? 1 : 0;
	
	
	/*
		如果滑动窗口最左端的包已经收到ACK,则将滑动窗口滑动到下一个没收到ACK的包的位置,
		同时将新数据包装入滑动窗口右端空缺位置。
	*/
	if( send_base_acked == 1 ) 
	{
		int new_pkt_seq;
		int max_acked;
		max_acked = sending_window.send_left;
		
		while( max_acked < sending_window.send_right &&
				(sending_window.rdt_pkts[max_acked % sending_window.win_len].state == RDT_PKT_ST_ACKED) )
			max_acked++;
		sending_window.send_left = max_acked;	

		for( 	new_pkt_seq = sending_window.send_right; 
				new_pkt_seq < (sending_window.send_left + sending_window.win_len); 
				new_pkt_seq++ )
		{
				if( feof(fp) ) //检查是否已到达发送文件结尾
					break;
					
				int read_len, rdt_pkt_len;
				STATE_PKT *ptr_pkt_new = &sending_window.rdt_pkts[new_pkt_seq % sending_window.win_len];
				
				read_len = fread( read_buf, sizeof(char), RDT_DATA_LEN, fp ); //读取发送文件中的数据
				rdt_pkt_len = pack_rdt_pkt( read_buf, 
											ptr_pkt_new->rdt_pkt, 
											read_len, 
											new_pkt_seq, 
											RDT_CTRL_DATA ); //封装为RDT数据包
				
				//初始化新RDT数据包状态
				ptr_pkt_new->pkt_seq = new_pkt_seq;
				ptr_pkt_new->pkt_len = rdt_pkt_len;
				ptr_pkt_new->state = RDT_PKT_ST_INIT; 
				memset( &(ptr_pkt_new->send_time), 0, sizeof(struct timeval) );
				
				pkt_to_send++;
		}
		sending_window.send_right = new_pkt_seq;		
	}
	/*
		如果滑动窗口最左端的包还没有收到数据包,则检查其是否超时,如果超时,则重新发送所有滑动窗口内的数据包。否则继续等待。
	*/
	else 
	{
		struct timeval time_now;
		gettimeofday( &time_now, NULL );
		
		/*
			如果是第一次发送数据,将滑动窗口初始化,填满待发送的数据
		*/
		if( sending_window.send_left == sending_window.send_right )
		{
			int new_pkt_seq;
			for( 	new_pkt_seq = sending_window.send_right; 
					new_pkt_seq < (sending_window.send_left + sending_window.win_len); 
					new_pkt_seq++ )
			{
					int read_len, rdt_pkt_len;
					if( feof(fp) ) //检查是否已到达发送文件结尾
						break;
					STATE_PKT *ptr_pkt_new = &sending_window.rdt_pkts[new_pkt_seq % sending_window.win_len];
					read_len = fread( read_buf, sizeof(char), RDT_DATA_LEN, fp );
					rdt_pkt_len = pack_rdt_pkt( read_buf, 
												ptr_pkt_new->rdt_pkt, 
												read_len, 
												new_pkt_seq, 
												RDT_CTRL_DATA );
					ptr_pkt_new->pkt_seq = new_pkt_seq;
					ptr_pkt_new->pkt_len = rdt_pkt_len;
					ptr_pkt_new->state = RDT_PKT_ST_INIT; //set pkt state to init state
					memset( &(ptr_pkt_new->send_time), 0, sizeof(struct timeval) );
					pkt_to_send++;
			}
			sending_window.send_right = new_pkt_seq;
			printf( "[main thread] begin sending, slide window[%d,%d).\n", 
							sending_window.send_left, sending_window.send_right );
		}		
		else if( time_out( time_now, ptr_pkt_left->send_time ) ) //第一个数据包超时
		{
			int i; 
			//重新发送窗口内所有数据包
			for( i = sending_window.send_left; i < sending_window.send_right; i++ )
			{
				STATE_PKT *ptr_pkt = &sending_window.rdt_pkts[i % sending_window.win_len];
				memset( &(ptr_pkt->send_time), 0, sizeof(struct timeval) ); 
				ptr_pkt->state = RDT_PKT_ST_TMOUT;
				pkt_to_send++;
			}
			printf( "[main thread] slide window[%d,%d), first pkt time out.\n", 
						sending_window.send_left, sending_window.send_right );
		}
		else
		{				
			pkt_to_send = 0; //没有超时,继续等待ACK
		}
	}
		
	pthread_mutex_unlock( &sending_window.lock );
	
	return pkt_to_send;
}


/*
	Go-Back-N 协议发送端函数
	输入参数:
		send_file_name: 待发送的文件名
		sock_fd:发送数据的socket (同时从该socket发送数据包和接受数据包ACK)
		recv_addr_ptr: 接收端的地址
	说明:
		创建一个子线程用于接收监听端口并接受ACK
		而主线程用于维护滑动窗口并发送数据包
*/

int deliver_file( char *send_file_name, int sock_fd,  struct sockaddr_in *recv_addr_ptr )
{
	char recv_pkt_buf[RDT_PKT_LEN];
	int seq_num = RDT_BEGIN_SEQ;
	
	struct sockaddr_in reply_addr;
	int reply_addr_len;
	int reply_ack_seq;
	int reply_ack_flag;
	pthread_t worker_thread;
	int reply_thread;
	
        int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void*),void *arg);

	FILE *fp;
	int i, j, read_len, pkt_len;
	int counter = 1;
	STATE_PKT *ptr_pkt;

	if( (fp = fopen( send_file_name, "r" )) == NULL )
	{
		printf( "open file : %s failed.\n",  send_file_name );
		return 1;
	}
	
	memset( &reply_addr, 0, sizeof(reply_addr) );
	
	//初始化互斥量,保证只有一个线程访问滑动端口
	pthread_mutex_init( &sending_window.lock, NULL );
	
	//初始化滑动窗口
	sending_window.win_len = RDT_SENDWIN_LEN;
	sending_window.send_left = RDT_BEGIN_SEQ;
	sending_window.send_right = RDT_BEGIN_SEQ;	//[slide window]= [RDT_BEGIN_SEQ, RDT_BEGIN_SEQ)
	ptr_pkt = &sending_window.rdt_pkts[sending_window.send_left % sending_window.win_len];
	ptr_pkt->pkt_seq = RDT_BEGIN_SEQ;	
	ptr_pkt->state = 0;//数据包状态为0,表示未发送

	//创建子线程用于接收ACK
	reply_thread = pthread_create( &worker_thread, NULL, recv_acks_thread, (void *)&sock_fd );
	if( reply_thread != 0 )//创建成功返回0
	{
		perror( "pthread_create failed.\n" );
		exit( 1 );
	}
	
	while(1)
	{
		int pkt_to_send;
		
		/*检查滑动窗口内是否有数据包需要发送,返回窗口内待发送的数据包数*/
		pkt_to_send = pre_sending_rdt_pkt( fp );
		
		if( pkt_to_send > 0 )
		{
			printf( "[main thread] #%d pkts to send.\n", pkt_to_send );
			for( i = sending_window.send_left; i < sending_window.send_right; i++ )
			{
				STATE_PKT *ptr_pkt;
				ptr_pkt = &sending_window.rdt_pkts[ i % sending_window.win_len];
				if( ptr_pkt->state == RDT_PKT_ST_INIT ||  ptr_pkt->state == RDT_PKT_ST_TMOUT )
				{
                                        ptr_pkt->state = RDT_PKT_ST_SENT;
					udt_sendto( sock_fd, ptr_pkt->rdt_pkt, ptr_pkt->pkt_len, 0,
							(struct sockaddr *)recv_addr_ptr, sizeof(*recv_addr_ptr) );
					
					
					gettimeofday( &ptr_pkt->send_time, NULL );
					printf( "[main thread] send count #%-8d rdt_pkt #%-8d %-10d bytes.\n", counter++, ptr_pkt->pkt_seq, ptr_pkt->pkt_len );
				}
			}
			printf( "[main thread] slide window: [%d,%d).\n", sending_window.send_left, sending_window.send_right );
		}
		else
		{
			//检查文件是否发送完
			if( sending_window.send_left == sending_window.send_right && feof(fp) ) //data transfer finished
			{
				printf( "[main thread] finished sending! slide window: [%d,%d).\n", sending_window.send_left, sending_window.send_right );
				reply_thread = pthread_cancel( worker_thread ); /*删除子线程*/
				if( reply_thread != 0 )
				{
					printf( "[main thread] pthread_cancel failed.\n" );
					exit(1);
				}
				break;			
			}
		}
	}
	
	//删除互斥量
	pthread_mutex_destroy( &sending_window.lock );
	
	//结束发送过程,给接受端发送结束数据包(包类型为RDT_CTRL_END)
	if( feof(fp) )
	{
		char rdt_pkt[RDT_PKT_LEN];
		int rdt_pkt_len;
		int new_pkt_seq = sending_window.send_left;
		rdt_pkt_len = pack_rdt_pkt( NULL, 
							rdt_pkt, 
							0, 
							new_pkt_seq, 
							RDT_CTRL_END );
		sendto( sock_fd, rdt_pkt, rdt_pkt_len, 0, (struct sockaddr *)recv_addr_ptr, sizeof(*recv_addr_ptr) );
	}
	printf( "\n\nsend file %s finished\ntotal send %d bytes.\n", send_file_name, total_send_byte );
	fclose( fp );
	return 0;
}

int main( int argc, char **argv )
{
	struct sockaddr_in recv_addr, send_addr;
	int sock_fd;
	
	if( argc != 2 )
	{
		usage( argv );
		exit(0);
	}
	
	srand ( time(NULL) );
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	memset( &send_addr, 0, sizeof(send_addr) );
	send_addr.sin_family = AF_INET;
	send_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	send_addr.sin_port = htons( RDT_SEND_PORT );
	
	if( ( sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
	
	//将socket绑定到本地的某个端口,同时从该socket发送数据包和接受ACK
	if( bind( sock_fd, (struct sockaddr *)&send_addr, sizeof(send_addr) ) == -1 )
	{
		close( sock_fd );
		printf( "error! information: %s\n", strerror(errno) );
		exit(1);	
	}
			
	//设置接收端地址		
	memset( &recv_addr, 0, sizeof(recv_addr) );
	recv_addr.sin_family = AF_INET;
	recv_addr.sin_addr.s_addr = inet_addr( RDT_SERVER_ADDRESS );
	recv_addr.sin_port = htons( RDT_RECV_PORT );

	
	//调用传输文件的函数
	if( deliver_file( argv[1], sock_fd, &recv_addr ) != 0 )
	{
		printf( "deliver file %s failed.\n", argv[1] );
		close( sock_fd );
		exit(1);
	}
	
	close( sock_fd );
	return 0;
}


可能的遇到问题与解决方法:

1、若make命令报错
可以安装一下build-essential
或者使用

gcc -o outfilename infilename

进行单个文件编译。

2、若make后出现警告:
检测到时钟错误。您的创建可能是不完整的。

make:Warning:filename has modification time 62235s in the future

这是因为文件的时间比当前系统时间还要晚。可以使用touch这个方法:

touch makefile

3、make完之后会在该目录下生成两个可执行文件:
rdt_stopwait_sender和rdt_stopwait_receiver。
此时可能同时生成两个文本文件:
savefliename和sendfilename
若未生成也可以自己创建。并在sendfilename文件中写入任意想要传输的内容。
接下来在该目录下打开两个终端,分别键入

./rdt_stopwait_sender  sendfilename
./rdt_stopwait_receiver savefilename

回车运行,就可以看到RDT通信的实现结果。打开savefilename文件,可以看到其中包含了从sendfliename文件传输而来的内容。由于本机两个窗口通信不会出现丢失,实验中send_to的成功率设置为95%。从结果也可以看到一部分数据包发送失败。RDT正是在不可靠传输上,实现了可靠通信。

4、go_back_n中需要修改
Makefile文件中“-lpthread”需改成“-pthread”
rdt_gbn_sender.c278行“ptr_pkt->state=RDT_PKT_ST_SENT;”改到275行“udt_sendto(sock_fd,ptr_pkt->rdt_pkt,ptr_pkt->pkt_len,0(structsockaddr*)recv_addr_ptr, sizeof(*recv_addr_ptr) );”之前

头文件补充:

#ifndef NETEXP_H
#define NETEXP_H

#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <ctype.h>


#define		TCP_SERVER_ADDRESS		"127.0.0.1"
#define 	TCP_SERVER_PORT			8000
#define		TCP_BUF_LENGTH			1000
#define 	CONN_NUM				10

#define		UDP_SERVER_ADDRESS		"127.0.0.1"
#define		UDP_RECV_PORT			8001 
#define		UDP_SEND_PORT			8002 
#define		UDP_BUF_LENGTH			1000

#define		RDT_SERVER_ADDRESS		"127.0.0.1"		//RDT服务器端IP
#define 	RDT_RECV_PORT			8003			//RDT接受端端口号
#define		RDT_SEND_PORT			8004			//RDT发送端端口号
#define		RDT_BEGIN_SEQ			1				//RDT数据包初始序列号,(假设数据包序列号不循环)		
#define		RDT_SENDWIN_LEN			10				//发送端窗口大小

#define		RDT_PKT_LOSS_RATE		10			//不可靠数据传输层的丢包率
#define		RDT_TIME_OUT			50000		//数据包超时时限
#define		RDT_HEADER_LEN			(4 + 4)		//RDT头标长度
#define 	RDT_DATA_LEN			1000		//RDT中数据域长度
#define		RDT_PKT_LEN				( RDT_DATA_LEN + RDT_HEADER_LEN )

//RDT包类型
#define		RDT_CTRL_BEGN			0 //初始包
#define		RDT_CTRL_DATA			1 //数据包
#define 	RDT_CTRL_ACK			2 //ACK包
#define		RDT_CTRL_END			3 //结束包

//滑动窗口中数据包的状态
#define		RDT_PKT_ST_INIT			0	//未发送
#define		RDT_PKT_ST_SENT			1	//已发送,未确认
#define		RDT_PKT_ST_ACKED		2	//已确认
#define		RDT_PKT_ST_TMOUT		3	//超时

/*
	滑动窗口中数据包的结构体
*/
typedef struct _STATE_PKT
{
	struct timeval send_time;
	int pkt_seq;
	int pkt_len;
	int state; //标识数据包的状态
	char rdt_pkt[RDT_PKT_LEN];
}STATE_PKT;

/*
	滑动窗口的结构体
*/
typedef struct _SLD_WIN
{
	int win_len;							//滑动窗口长度
	int send_left;							//滑动窗口的左端
	int send_right;							//滑动窗口的右端
	pthread_mutex_t lock; 					//滑动窗口互斥量,保证只有一个线程在访问发送窗口中的数据
	STATE_PKT rdt_pkts[RDT_SENDWIN_LEN]; 	//窗口中的数据包
}SLD_WIN;


/*
	RDT packet format: |CTRL|SEQ|...DATA...|
	将数据封装成RDT数据包,头部只包含控制域和序列号域,其中控制域用来标识RDT数据包类型,序列号域是此数据包的序列号。
	返回RDT数据包长度
*/
int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag );



/*
	RDT packet format: |CTRL|SEQ|...DATA...|
	
	将数据包解封装。
	返回RDT包中的数据长度
*/
int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag );



/*
	模拟不可靠数据传输,以一定的概率(RDT_PKT_LOSS_RATE)丢弃数据包
*/
void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len );



/*
	检查 (time_1 - time_2) 是否超过设定时限(RDT_TIME_OUT),
	如果超时返回1,否则返回0*/
int time_out( struct timeval time_1, struct timeval time_2 );

#endif


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值