版权声明:本文为博主原创文章,未经博主允许不得转载。 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.上传完成,关闭数据连接套接字,关闭文件描述符,给客户端应答
代码同下载过程