miniFTP项目实战六
项目简介:
在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项目实战六相关推荐
- SpringBoot+MyBatis+MYSQL项目实战六(新增收货地址)
SpringBoot+MyBatis+MYSQL项目实战六(新增收货地址) 项目源码地址:电脑商城实战 点击新增收货地址 一:新增收货地址--数据表的创建 CREATE TABLE t_address ...
- Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码
文章目录 一.简介 二.Vue2和Vue3区别 三.Vue知识点学习 四.TypeScript知识点 一.JavaScript和TypeScript 二.TypeScript的安装和使用 1.Type ...
- Java Web项目源代码|CRM客户关系管理系统项目实战(Struts2+Spring+Hibernate)解析+源代码+教程
客户关系管理 (CRM) CRM一般指客户关系管理 客户关系管理是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新 ...
- Android零基础开发到项目实战
Android零基础开发到项目实战(目录) 前言:本教程适合零基础学习安卓开发的伙伴,下面是目录,本博主会每天定时更新每一章节的教程,未完..... 一.Java基础阶段 day01_Java语言概述 ...
- flutter 项目实战二 网络请求
本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...
- flutter 项目实战四 列表数据展示
本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...
- Vue3+TypeScript从入门到进阶(八)——项目打包和自动化部署——附沿途学习案例及项目实战代码
文章目录 一.简介 二.Vue2和Vue3区别 三.Vue知识点学习 四.TypeScript知识点 五.项目实战 六.项目打包和自动化部署 一. 项目部署和DevOps 1.1. 传统的开发模式 1 ...
- 【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权
上篇文章[.NET Core项目实战-统一认证平台]第五章 网关篇-自定义缓存Redis 我们介绍了网关使用Redis进行缓存,并介绍了如何进行缓存实现,缓存信息清理接口的使用.本篇我们将介绍如何实现 ...
- 数据挖掘机器学习[六]---项目实战金融风控之贷款违约预测
相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据挖掘机器学习---汽车交易价格预测详细版本[二]{EDA-数据探索性分析} 数据挖掘机器学习 ...
最新文章
- 目标检测算法综述 | 基于候选区域的目标检测器 | CV | 机器视觉
- [android] 百度地图开发 (二).定位城市位置和城市POI搜索
- 欢乐纪中A组赛【2019.8.20】
- 华强北耳机为啥老是有人翻车?
- iOS 开发之几个 Demo 分享网站
- 终于把所有的Python库,都整理出来啦!
- Excel用公式计算年龄
- dubbo调用原理,过程(知其然,知其所以然)
- 单例模式如何确保线程安全
- 封装了一个加单的php验证码功能类,超级详细,麻雀虽小五脏俱全
- 文件比较命令(comp)
- Linux虚拟机下WWW(HTTP)服务器的搭建与使用(详细)
- start with connect by prior的使用方法
- python技巧:将文件夹下的文件遍历,保留特定文件,删除其他文件
- PHP+ajaxfileupload 实现用户头像上传
- Oracle数据库常用SQL语句查询
- 云服务商将占据 80% CDN 市场份额,传统CDN或将终结
- libc_database 库文件下载
- 使用spm预处理fMRI数据
- 淘宝/天猫如何获取sku API接口,item_sku - 获取sku详细信息
热门文章
- mysql leader/followers_领导者-追随者(Leader/Followers)模型的比喻
- ZOOM H1n录音笔怎么连接电脑做音频输入麦克风【教程】
- CSDN会员页面办卡人员信息刷屏写死代码
- ese如何实现支付 nfc_基于nfc的支付方法、基于nfc的支付系统和终端的制作方法...
- 星轨体育,让体育成为全民新风尚
- row_number()函数
- Windows使用公网IPv6建站过程
- vue element Transfer 穿梭框 自定义数
- Microsoft store怎么重新安装
- PHP实用图片压缩方法