下载和上传

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/86507936

一、预备知识

1.lseek函数

在程序中作用是断点续载或断电上传时偏移文件指针lseek函数介绍

2.fcntl函数

在此程序中的作用是对文件加读写锁fcntl函数介绍

二、下载的实现

1.不论是上传还是下载,首先都要进行数据连接字的创建

将创建好的数据连接套接字保存到data_fd中

//数据连接通道的创建
//创建数据连接,获取数据连接所对应的套接字,有可能是主动模式也可能是被动模式
int get_transfer_fd(session_t *sess)
{
	// 检测是否收到PORT或者PASV命令,也就是检测是主动模式还是被动模式
	if (!port_active(sess) && !pasv_active(sess)) {
		//若上述两者命令都未收到,给客户端一个应答
		ftp_reply(sess, FTP_BADSENDCONN, "Use PORT or PASV first.");
		return 0;
	}

	int ret = 1;
	// 如果是主动模式PORT,则服务器端创建数据套接字(bind20端口),调用conect
	//主动连接客户端IP和端口,从而建立数据连接
	if (port_active(sess)) {
		//获得了数据套接字
		if (get_port_fd(sess) == 0) {
			ret = 0;
		}
	}

	//被动模式 
	if (pasv_active(sess)) {
		//获取被动模式的数据套接字
		if (get_pasv_fd(sess) == 0) {
			ret = 0;
		}

	}

	if (sess->port_addr) {
		free(sess->port_addr);
		sess->port_addr = NULL;
	}

	if (ret) {
		// 不管是哪种方式创建数据通道,一旦创建完毕,就启动数据连接闹钟
		start_data_alarm();
	}

	return ret;
}

2.获取待下载的文件描述符fd

	// 只读方式打开文件
	//将文件里面的内容读取出来,然后写入数据套接字即可
	int fd = open(sess->arg, O_RDONLY);
	if (fd == -1) {
		ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");
		return;
	}

3.判断是否存在断点,存过存在,重新定位文件偏移位置

	//保存断点位置
	long long offset = sess->restart_pos;
	//然后将断点位置清零
	sess->restart_pos = 0;
	//说明有断点
	if (offset != 0) {
		//lseek函数的作用是用来重新定位文件读写的位移
		//SEEK_SET,从文件头部开始偏移offset个字节
		ret = lseek(fd, offset, SEEK_SET);
		if (ret == -1) {
			ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");
			return;
		}
	}

4.下载之前加读锁

	int ret;
	// 加读锁,希望在下载的时候其他进程不能更改该文件
	ret = lock_file_read(fd);
	if (ret == -1) {
		ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");
		return;
	}
static int lock_internal(int fd, int lock_type)
{
	int ret;
	//锁的结构体
	struct flock the_lock;
	memset(&the_lock, 0, sizeof(the_lock));
	the_lock.l_type = lock_type;//锁的类型
	the_lock.l_whence = SEEK_SET;//加锁的位置
	the_lock.l_start = 0;//头部的偏移位置开始加锁
	the_lock.l_len = 0;//加锁的字节数,0表示整个文件
	do {
		ret = fcntl(fd, F_SETLKW, &the_lock);
	}
	while (ret < 0 && errno == EINTR);//被信号中断了,继续加锁,直到成功为止

	return ret;
}

5.计算传送文件的大小

struct stat sbuf;
//计算传送的文件大小  
long long bytes_to_send = sbuf.st_size;
if (offset > bytes_to_send) {
	bytes_to_send = 0;  
}
else {
	//断点到文件结尾的大小,如果不是断点续传,那么offset=0
	bytes_to_send -= offset;
}

6.进行文件传送

利用的是sendfile,高效sendfile详解

while (bytes_to_send) {
	    //发送的字节数
		int num_this_time = bytes_to_send > 4096 ? 4096 : bytes_to_send;
		//当前的传输量,返回值ret是发送的字节数
		ret = sendfile(sess->data_fd, fd, NULL, num_this_time);
		if (ret == -1) {
			flag = 2;
			break;
		}

		limit_rate(sess, ret, 0);
		//如果处于限速状态,恰好接收到abor命令,此刻无法返回,所以需要下面的判断
		if (sess->abor_received) {
			flag = 2;
			break;
		}

		bytes_to_send -= ret;
}

7.传送完成,关闭数据连接套接字,关闭文件描述符,给客户端应答

	//下载完之后,关闭数据连接
	close(sess->data_fd);
	sess->data_fd = -1;

	close(fd);

三、上传的实现

1.同下载过程,要进行数据连接字的创建

代码同下载过程

2.以创建的方式打开一个文件,获得文件描述符fd

	// 打开一个文件,接收上传的内容,0表示8进制
	int fd = open(sess->arg, O_CREAT | O_WRONLY, 0666);
	if (fd == -1) {
		ftp_reply(sess, FTP_UPLOADFAIL, "Could not create file.");
		return;
	}

3.判断是否存在断点,存过存在,重新定位文件偏移位置

代码同下载过程

4.上传之前加写锁

代码同下载过程

5.将内容从数据连接字读到buf中,然后再将buf中的内容写到fd中,循环此过程,直到数据套接字中没有数据。

	while (1) {
		//从数据套接字接收数据
		ret = read(sess->data_fd, buf, sizeof(buf));
		if (ret == -1) {
			if (errno == EINTR) {
				continue;
			} else {
				flag = 2;
				break;
			}
		}
		else if (ret == 0) {
			flag = 0;
			break;
		}

		//读取了一定数据之后需要判断是否限速
		limit_rate(sess, ret, 1);
		//睡醒之后判断是否收到了abor,如果是直接break,其实没有也可以,返回去
		//的时候read会返回-1,因为已经关闭了数据连接套接字了
		if (sess->abor_received) {
			flag = 2;
			break;
		}

		//写入文件中
		if (writen(fd, buf, ret) != ret) {
			flag = 1;
			break;
		}
	}

7.上传完成,关闭数据连接套接字,关闭文件描述符,给客户端应答

代码同下载过程

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/86507936