项目简介:
在Linux环境下用C语言开发的Vsftpd的简化版本,拥有部分Vsftpd功能和相同的FTP协议,系统的主要架构采用多进程模型,每当有一个新的客户连接到达,主进程就会派生出一个ftp服务进程来为客户提供服务。同时每个ftp服务进程配套了nobody进程(内部私有进程),主要是为了做权限提升和控制。
实现功能:
除了基本的文件上传和下载功能,还实现模式选择、断点续传、限制连接数、空闲断开、限速等功能。
用到的技术:
socket、I/O复用、进程间通信、HashTable
欢迎技术交流q:2723808286
项目开源!!!

miniFTP项目实战一
miniFTP项目实战二
miniFTP项目实战三
miniFTP项目实战四
miniFTP项目实战五
miniFTP项目实战六

文章目录

  • 6.1 限速
  • 6.2 空闲断开
    • 控制连接
    • 数据连接
  • 6.3 连接数限制
    • 最大连接数限制
    • 每个IP最大连接数

6.1 限速

限速的关键在于睡眠时间,当前传输速度大于限速的时候,就进行睡眠,可以这样想:
传输速度 = 传输Byte / 传输时间 。 传输Byte是定值,只有改变传输时间才可以改变传输速度。

睡眠的时间 = 最大限速传输所需时间 - 当前速度传输所需时间 = (当前传输速度 / 限速传输速度 - 1)* 当前传输时间

所以问题的关键就是求出传输速度,核心在于求出传输x字节数据的时间差,所以要在传输数据之前计时,在数据传输完一轮之后,立即计算速度!!!

每一轮的read之后进行限速计算,在限速计算中判断是否超速,并进行睡眠操作。

在开始传输前,获取当前时间,并存在sess中:

sess->bw_transfer_start_sec = get_time_sec();
sess->bw_transfer_start_usec = get_time_usec();

限速计算中,获取当前时间与上一次时间做差,获得时间差:

void limit_rate(session_t *sess, int bytes_transfered, int is_upload)
{sess->data_process = 1;  //数据传输状态//睡眠时间 = (当前传输速度/最大传输速度 - 1)*当前传输时间long curr_sec = get_time_sec();long curr_usec = get_time_usec();double elapsed;  //当前传输时间elapsed = curr_sec - sess->bw_transfer_start_sec;elapsed += (double)(curr_usec - sess->bw_transfer_start_usec) / (double)1000000;if (elapsed <= (double)0) elapsed = (double)0.01;  //浮点数unsigned int bw_rate = (unsigned int)((double)bytes_transfered / elapsed);   //计算当前传输速度unsigned int max_rate;  //最大传输速度max_rate = (is_upload == 1) ? sess->bw_upload_rate_max : sess->bw_download_rate_max;double rate_ratio;if (bw_rate > max_rate) {rate_ratio = bw_rate / max_rate;} else {//即使不需要限速 也要更新sess->bw_transfer_start_sec = get_time_sec();sess->bw_transfer_start_usec = get_time_usec();return ;}double pause_time = (rate_ratio - 1) * elapsed; //睡眠时间//通过封装 nanosleep函数进行睡眠nano_sleep(pause_time);//为什么不用sleep?sleep内部可能用时钟信号机制来实现,所以尽量不将sleep和alarm函数一起使用//alarm用来实现空闲断开//更新时间sess->bw_transfer_start_sec = get_time_sec();sess->bw_transfer_start_usec = get_time_usec();}

睡眠操作如下:

void nano_sleep(double seconds)
{time_t secs = (time_t) seconds;double fractional = seconds - (double) secs;struct timespec ts;ts.tv_sec = secs;ts.tv_nsec = (long) (fractional * (double) 1000000000);int ret;do {ret = nanosleep(&ts, &ts);} while (ret == -1 && errno == EINTR);  //防止中断打断
}

注意这里使用nanosleep进行睡眠操作,不使用sleep函数进行睡眠操作的原因是:Linux中并没有提供系统调用sleep(),sleep()是在库函数中实现的,它是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号。

后面要用alarm来实现空闲断开。

6.2 空闲断开

包括控制连接的空闲断开和数据链接的空闲断开。

控制连接

安装信号SIGALRM,启动定时闹钟,如果再闹钟来临之前没有收到任何命令,在信号处理程序中关闭闹钟,并给客户端421 timeout的响应,退出会话。

在服务进程的处理逻辑中启动ALARM闹钟,在whil循环中,每一次收到命令后都重新启动闹钟,闹钟启动函数与处理函数如下:

session_t *p_sess;   //全局变量,在main中extern
//定时处理函数
void handle_alarm_timeout(int sig)
{shutdown(p_sess->ctl_fd, SHUT_RD);  //关闭读取ftp_relply(p_sess, FTP_IDLE_TIMEOUT, "Timeout");shutdown(p_sess->ctl_fd, SHUT_WR);  //关闭写入exit(EXIT_FAILURE);//服务进程的退出会导致nobody退出,因为nobody会在内部通信是读取到0,代表服务进程退出,随之我们退出nobody进程
}//启动ALARM闹钟  超时断开用到
void start_cmdio_alarm(void)
{if (tunable_idle_session_timeout > 0) {signal(SIGALRM, handle_alarm_timeout);  //注册处理函数alarm(tunable_idle_session_timeout);    //启动闹钟}
}

可是这种情况忽略了数据传输的情况。

如果只是以上这种情况,在进行数据传输的时候,如果超时,也是会断开的。

所以!!!当目前处理数据传输的状态时,即使控制连接超时了,也不能退出会话。可以在数据传输前关闭控制连接的闹钟。

数据连接

如果数据连接通道建立了,一定时间没有传输数据,也要将会话断开。

实现方法:在传输数据之前安装SIGALRM信号,启动闹钟。传输数据的过程中如果收到信号,要进行判断,即如果不处于数据传输的时候退出,否则重新安装信号。所以在sess中新建一个变量data_process用于表示当前是否处于数据传输。

注意!!!在数据通道建立之前重启闹钟,另外创建一个信号处理函数,于控制通道的处理函数不同。与此同时还得关闭控制连接通道的闹钟

void handle_siglarm(int sig)
{if (p_sess->data_process == 0) {  //如果不进行数据传输  退出ftp_relply(p_sess, FTP_DATA_TIMEOUT, "Data timeout. Reconnect.");exit(EXIT_FAILURE);}//当前处于数据传输状态 收到超时信号,将状态置为数据传输状态并重新启动闹钟p_sess->data_process = 0;start_data_alarm();
}void start_data_alarm(void)
{if (tunable_data_connection_timeout > 0) {  //开启数据连接通道 闹钟signal(SIGALRM, handle_siglarm);alarm(tunable_data_connection_timeout);} else if (tunable_idle_session_timeout > 0) {  //关闭控制连接通道 闹钟alarm(0);  //关闭先前安装的闹钟}
}

数据传输状态的确定放在限速函数中去实现!!!上传下载都要用到限速函数。

6.3 连接数限制

参考:github

最大连接数限制

超过最大连接数之后,回复客户端421提示,如下:

将当前连接数保存在全局变量中,当有新连接请求时与配置文件中的配置项进行判断。在创建新会话的时候判断最大连接数,通过check_limits来判断:

void check_limits(session_t *sess)
{//开启连接数限制 并且当前连接数大于最大连接数 421响应if (tunable_max_clients > 0 && sess->num_clients > tunable_max_clients) {ftp_relply(sess, FTP_TOO_MANY_USERS, "There are too many connected users, please try later.");exit(EXIT_FAILURE);}//检查每IP最大连接数if (tunable_max_per_ip > 0 && sess->num_this_ip > tunable_max_per_ip) {ftp_relply(sess, FTP_IP_LIMIT,"There are too many connections from your internet address.");exit(EXIT_FAILURE);}
}

每当有断开连接,nobody进程都会向主进程发出SIGCILD信号,我们就在这个信号的处理函数中将连接数-1:

void signal_handler(int argc)
{//等待任意,没等到立刻返回0unsigned int pid;while (pid = waitpid(-1, NULL, WNOHANG) > 0) {--s_children; //统计的进程数-1  改变的是父进程的变量unsigned int *ip = hash_lookup_entry(s_pid_ip_hash, &pid, sizeof(pid));if (ip == NULL) {continue;}drop_ip_count(ip);  //pid->ip->count--hash_free_entry(s_pid_ip_hash, &pid, sizeof(pid));  //释放空间}return ;
}

要注意,在新会话中也创建了一个服务进程,当服务进程结束的时候,服务进程也会向新会话进程发送SIGCILD信号,但是我们之前以及建立了服务进程退出导致nobody进程退出的操作(read返回0),所以这里要在创建会话之前再声明一次信号处理操作。

在创建新会话之前进行如下操作,避免nobody中对服务进程的退出做响应:

signal(SIGCHLD, SIG_IGN);  //忽略信号

每个IP最大连接数

比如设置每个IP最大连接数是3,当同一IP第四个连接到达时,有如下回应:

需要维护两个哈希表:

  • IP与连接数的关系 :每IP的当前连接数,主要用于记录连接
  • 进程与IP的关系:在进程退出的时候,根据这个哈希表找到对应的IP,然后根据另外一个哈希表将计数减一

在最大连接数的限制的时候,进程退出时发出的信号,在信号处理函数中将连接数减一。对于每个IP连接数在进程退出时的变化,根据waitpid返回的pid,再通过pid与IP的哈希表找出对应IP的连接数,然后减一。

//根据ip,将连接数+1
unsigned int handle_ip_count(void *ip)
{unsigned int count;//查看当前的连接数unsigned int *p_count = (unsigned int *) hash_lookup_entry(s_ip_count_hash,ip, sizeof(unsigned int));if (p_count == NULL) {  //新增表项count = 1;hash_add_entry(s_ip_count_hash, ip, sizeof(unsigned int),&count, sizeof(unsigned int));} else {count = *p_count;++count;*p_count = count;}return count;
}//根据ip,将连接数-1
void drop_ip_count(void *ip)
{unsigned int count;unsigned int *p_count = (unsigned int *) hash_lookup_entry(s_ip_count_hash,ip, sizeof(unsigned int));if (p_count == NULL) {return;}count = *p_count;if (count <= 0) {return;}--count;*p_count = count;if (count == 0) {hash_free_entry(s_ip_count_hash, ip, sizeof(unsigned int));}
}

miniFTP项目实战六相关推荐

  1. SpringBoot+MyBatis+MYSQL项目实战六(新增收货地址)

    SpringBoot+MyBatis+MYSQL项目实战六(新增收货地址) 项目源码地址:电脑商城实战 点击新增收货地址 一:新增收货地址--数据表的创建 CREATE TABLE t_address ...

  2. Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码

    文章目录 一.简介 二.Vue2和Vue3区别 三.Vue知识点学习 四.TypeScript知识点 一.JavaScript和TypeScript 二.TypeScript的安装和使用 1.Type ...

  3. Java Web项目源代码|CRM客户关系管理系统项目实战(Struts2+Spring+Hibernate)解析+源代码+教程

    客户关系管理 (CRM) CRM一般指客户关系管理 客户关系管理是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新 ...

  4. Android零基础开发到项目实战

    Android零基础开发到项目实战(目录) 前言:本教程适合零基础学习安卓开发的伙伴,下面是目录,本博主会每天定时更新每一章节的教程,未完..... 一.Java基础阶段 day01_Java语言概述 ...

  5. flutter 项目实战二 网络请求

    本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...

  6. flutter 项目实战四 列表数据展示

    本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...

  7. Vue3+TypeScript从入门到进阶(八)——项目打包和自动化部署——附沿途学习案例及项目实战代码

    文章目录 一.简介 二.Vue2和Vue3区别 三.Vue知识点学习 四.TypeScript知识点 五.项目实战 六.项目打包和自动化部署 一. 项目部署和DevOps 1.1. 传统的开发模式 1 ...

  8. 【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权

    上篇文章[.NET Core项目实战-统一认证平台]第五章 网关篇-自定义缓存Redis 我们介绍了网关使用Redis进行缓存,并介绍了如何进行缓存实现,缓存信息清理接口的使用.本篇我们将介绍如何实现 ...

  9. 数据挖掘机器学习[六]---项目实战金融风控之贷款违约预测

    相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据挖掘机器学习---汽车交易价格预测详细版本[二]{EDA-数据探索性分析} 数据挖掘机器学习 ...

最新文章

  1. 目标检测算法综述 | 基于候选区域的目标检测器 | CV | 机器视觉
  2. [android] 百度地图开发 (二).定位城市位置和城市POI搜索
  3. 欢乐纪中A组赛【2019.8.20】
  4. 华强北耳机为啥老是有人翻车?
  5. iOS 开发之几个 Demo 分享网站
  6. 终于把所有的Python库,都整理出来啦!
  7. Excel用公式计算年龄
  8. dubbo调用原理,过程(知其然,知其所以然)
  9. 单例模式如何确保线程安全
  10. 封装了一个加单的php验证码功能类,超级详细,麻雀虽小五脏俱全
  11. 文件比较命令(comp)
  12. Linux虚拟机下WWW(HTTP)服务器的搭建与使用(详细)
  13. start with connect by prior的使用方法
  14. python技巧:将文件夹下的文件遍历,保留特定文件,删除其他文件
  15. PHP+ajaxfileupload 实现用户头像上传
  16. Oracle数据库常用SQL语句查询
  17. 云服务商将占据 80% CDN 市场份额,传统CDN或将终结
  18. libc_database 库文件下载
  19. 使用spm预处理fMRI数据
  20. 淘宝/天猫如何获取sku API接口,item_sku - 获取sku详细信息

热门文章

  1. mysql leader/followers_领导者-追随者(Leader/Followers)模型的比喻
  2. ZOOM H1n录音笔怎么连接电脑做音频输入麦克风【教程】
  3. CSDN会员页面办卡人员信息刷屏写死代码
  4. ese如何实现支付 nfc_基于nfc的支付方法、基于nfc的支付系统和终端的制作方法...
  5. 星轨体育,让体育成为全民新风尚
  6. row_number()函数
  7. Windows使用公网IPv6建站过程
  8. vue element Transfer 穿梭框 自定义数
  9. Microsoft store怎么重新安装
  10. PHP实用图片压缩方法