当前位置:   article > 正文

linux模仿ftp服务器代码大全,Linux下FTP服务器的实现(仿vsftpd)

模仿utxpftp %jgyyyyuu

继上一篇博文实现Linux下的shell后,我们进一步利用网络编程和系统编程的知识实现Linux下的FTP服务器。我们以vsftpd为原型并实现了其大部分的功能。由于篇幅和时间的关系,这里不再一一赘述具体的实现过程,而是简要概述功能实现思想和部分核心代码。

(一)基本框架和流程

bbae8663e92fa56120bbef0f7253c026.png

a6187c0a0673acfa251c3a21356a97d4.png

先解决两个疑问:

(1)为什么要使用nobody进程和服务进程两个进程?

在PORT模式下,服务器会主动建立数据通道连接客户端,服务器可能就没有权限做这种事情,就需要nobody进程来帮忙。 Nobody进程会通过unix域协议(本机通信效率高) 将套接字传递给服务进程。普通用户没有权限绑定20端口,需要nobody进程的协助,所以需要nobody进程作为控制进程。

(2)为什么使用多进程而不是多线程?

原因是在多线程或IO复用的情况下,当前目录是共享的,无法根据每一个连接来拥有自己的当前目录,也就是说当前用户目录的切换会影响到其他的用户。

(二)主被动模式的实现

主被动是相对于服务器来说的:

主动模式:服务器向客户端敲门,然后客户端开门

被动模式:客户端向服务器敲门,然后服务器开门

被动模式的出现主要是为了解决 防火墙或者NAT造成的问题。当通过NAT转换之后,服务器只能得知NAT的地址而不能得知客户端的IP地址,因此服务器以20端口主动向NAT的PORT端口发动请求,但是NAT并没有启用PORT端口,所以连接会被拒绝。

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;

// 如果是主动模式

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)

{

// 重新安装SIGALRM信号,并启动闹钟

start_data_alarm();

}

return ret;

}

(三)基本命令的实现

参照RFC规范和vsftpd的演示结果,依次仿真实现以下命令:

static void do_user(session_t *sess);

static void do_pass(session_t *sess);

static void do_cwd(session_t *sess);

static void do_cdup(session_t *sess);

static void do_quit(session_t *sess);

static void do_port(session_t *sess);

static void do_pasv(session_t *sess);

static void do_type(session_t *sess);

static void do_retr(session_t *sess);

static void do_stor(session_t *sess);

static void do_appe(session_t *sess);

static void do_list(session_t *sess);

static void do_nlst(session_t *sess);

static void do_rest(session_t *sess);

static void do_abor(session_t *sess);

static void do_pwd(session_t *sess);

static void do_mkd(session_t *sess);

static void do_rmd(session_t *sess);

static void do_dele(session_t *sess);

static void do_rnfr(session_t *sess);

static void do_rnto(session_t *sess);

static void do_site(session_t *sess);

static void do_syst(session_t *sess);

static void do_feat(session_t *sess);

static void do_size(session_t *sess);

static void do_stat(session_t *sess);

static void do_noop(session_t *sess);

static void do_help(session_t *sess);

注:使用static是为了只在一个模块中应用。

(四)上传/下载中断点续传的实现

断点续传的思想非常简单,只需要使用一个全局变量记录文件中的偏移量即可。下次从偏移量继续上传/下载。

static void do_retr(session_t *sess)

{

// 下载文件

// 断点续载

// 创建数据连接

if (get_transfer_fd(sess) == 0)

{

return;

}

long long offset = sess->restart_pos;

sess->restart_pos = 0;

// 打开文件

int fd = open(sess->arg, O_RDONLY);

if (fd == -1)

{

ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");

return;

}

int ret;

// 加读锁

ret = lock_file_read(fd);

if (ret == -1)

{

ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");

return;

}

// 判断是否是普通文件

struct stat sbuf;

ret = fstat(fd, &sbuf);

if (!S_ISREG(sbuf.st_mode))

{

ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");

return;

}

if (offset != 0)

{

ret = lseek(fd, offset, SEEK_SET);

if (ret == -1)

{

ftp_reply(sess, FTP_FILEFAIL, "Failed to open file.");

return;

}

}

//150 Opening BINARY mode data connection for /home/jjl/tmp/echocli.c (1085 bytes).

// 150

char text[1024] = {0};

if (sess->is_ascii)

{

sprintf(text, "Opening ASCII mode data connection for %s (%lld bytes).",

sess->arg, (long long)sbuf.st_size);

}

else

{

sprintf(text, "Opening BINARY mode data connection for %s (%lld bytes).",

sess->arg, (long long)sbuf.st_size);

}

ftp_reply(sess, FTP_DATACONN, text);

int flag = 0;

// ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

long long bytes_to_send = sbuf.st_size;

if (offset > bytes_to_send)

{

bytes_to_send = 0;

}

else

{

bytes_to_send -= offset;

}

sess->bw_transfer_start_sec = get_time_sec();

sess->bw_transfer_start_usec = get_time_usec();

while (bytes_to_send)

{

int num_this_time = bytes_to_send > 4096 ? 4096 : bytes_to_send;

ret = sendfile(sess->data_fd, fd, NULL, num_this_time);

if (ret == -1)

{

flag = 2;

break;

}

limit_rate(sess, ret, 0);

if (sess->abor_received)

{

flag = 2;

break;

}

bytes_to_send -= ret;

}

if (bytes_to_send == 0)

{

flag = 0;

}

// 关闭数据套接字

close(sess->data_fd);

sess->data_fd = -1;

close(fd);

if (flag == 0 && !sess->abor_received)

{

// 226

ftp_reply(sess, FTP_TRANSFEROK, "Transfer complete.");

}

else if (flag == 1)

{

// 451

ftp_reply(sess, FTP_BADSENDFILE, "Failure reading from local file.");

}

else if (flag == 2)

{

// 426

ftp_reply(sess, FTP_BADSENDNET, "Failure writting to network stream.");

}

check_abor(sess);

// 重新开启控制连接通道闹钟

start_cmdio_alarm();

}

(五)限速的实现

限速是通过使进程睡眠实现的,设置一个定时器计算当前的速度,如果发现大于限定的速度,那么就通过 睡眠时间 = (当前传输速度 / 最大传输速度 – 1) * 当前传输时间来计算。

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 = (double)(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);

double rate_ratio;

if (is_upload)

{

if (bw_rate <= sess->bw_upload_rate_max)

{

// 不需要限速

sess->bw_transfer_start_sec = curr_sec;

sess->bw_transfer_start_usec = curr_usec;

return;

}

rate_ratio = bw_rate / sess->bw_upload_rate_max;

}

else

{

if (bw_rate <= sess->bw_download_rate_max)

{

// 不需要限速

sess->bw_transfer_start_sec = curr_sec;

sess->bw_transfer_start_usec = curr_usec;

return;

}

rate_ratio = bw_rate / sess->bw_download_rate_max;

}

// 睡眠时间 = (当前传输速度 / 最大传输速度 – 1) * 当前传输时间;

double pause_time;

pause_time = (rate_ratio - (double)1) * elapsed;

nano_sleep(pause_time);

sess->bw_transfer_start_sec = get_time_sec();

sess->bw_transfer_start_usec = get_time_usec();

}

(六)单IP最大连接数的限制

使用哈希表实现。映射之后如果发现某个IP的连接数超过规定的数字,不允许连接即可。这里需要注意的是要建立两个哈希表,分别记录 IP&进程之间的映射和 IP&连接数之间的映射。因为当用户断开连接时我们必须知道进程和IP之间的关系。

请参考我的 博客中介绍哈希表的博文:http://blog.csdn.net/nk_test/article/details/50526184

s_ip_count_hash = hash_alloc(256, hash_func);

s_pid_ip_hash = hash_alloc(256, hash_func);

void check_limits(session_t *sess)

{

if (tunable_max_clients > 0 && sess->num_clients > tunable_max_clients)

{

ftp_reply(sess, FTP_TOO_MANY_USERS,

"There are too many connected users, please try later.");

exit(EXIT_FAILURE);

}

if (tunable_max_per_ip > 0 && sess->num_this_ip > tunable_max_per_ip)

{

ftp_reply(sess, FTP_IP_LIMIT,

"There are too many connections from your internet address.");

exit(EXIT_FAILURE);

}

}

关于项目的详细实现 请到我的 Github 下载源码。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/199938
推荐阅读
相关标签
  

闽ICP备14008679号