FTP服务器的实现(包括一些重要函数的介绍和实现逻辑)

一些重要函数:

1.system()

函数原型:int system(char* command);

函数功能:发出一个DOS命令。
system执行过程:
(1)fork一个子进程;
(2)在子进程中调用exec函数去执行command;
(3)在父进程中调用wait等待子进程结束。

返回值:
fork失败,返回-1;
exec成功,即命令执行成功,返回command通过exit或return返回的值;exec失败,返回127。
如果command为NULL,返回非0值,一般为1。

2.stat()

函数原型:int stat(const char *file_name, struct stat *buf);

函数功能:通过文件名filename获取文件信息,并保存在buf所指的结构体stat中。

返回值:
执行成功返回0,失败返回-1,错误代码存于errno。
ENOENT 参数file_name指定的文件不存在
ENOTDIR 路径中的目录存在但却非真正的目录
ELOOP 欲打开的文件有过多符号连接问题,上限为16符号连接
EFAULT 参数buf为无效指针,指向无法存在的内存空间
EACCESS 存取文件时被拒绝
ENOMEM 核心内存不足
ENAMETOOLONG 参数file_name的路径名称太长

3.sendfile()

函数原型:ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);

函数功能:在两个文件描述符之间直接传递数据,完全在内核中进行,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率高,称为零拷贝。

参数:从in_fd中读出数据写入到out_fd中,offset是指从文件流的哪个位置读,若为空,则从in_fd文件描述符所指的文件的文件偏移位置开始读,并且会更新in_fd所指的文件偏;若不为空,则指定从读入文件的哪个位置开始读,不更新。
count是指传递数据的字节数。

返回值:成功返回传输的字节数,失败返回-1并设置errno。

4.recv()

函数原型:ssize_t recv(int sockfd, void* buf, size_t len, int flags);

扫描二维码关注公众号,回复: 2833039 查看本文章

函数功能:从TCP连接的另一端接收数据。

参数:
sockfd指定接收端套接字描述符。
buf指定一个缓冲区,该缓冲区用来存放recv函数接收到的数据。
len指明缓冲区的长度。
flags一般置为0。

返回值:recv函数返回实际拷贝的字节数。
如果拷贝时出错,返回SOCKET_ERROR;如果在等待协议接收数据时网络中断,返回0.

默认recv的socket是阻塞的。

5.write()

函数原型:ssize_t write(int fd, const void *buf, size_t count);

函数功能:把buf中所指的内存写入count个字节到fd中。

返回值:写入成功,返回实际写入的字节数,写入失败返回-1并设置errno,文件位置会随之移动。

6.read()

函数原型:ssize_t read(int fd, void *buf, size_t count);

函数功能:将fd的内容传送读出count字节到buf指针所指的内存中。

返回值:读取成功,返回实际读取的字节数,读取失败返回-1并设置errno。如果返回为0,表示已经读到文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

7.sprintf()

函数原型:int sprintf(char buf, const char format…);

函数功能:格式化字符串,将格式化的数据写入字符串中。

参数:
buf:指向写入的字符串指针。
format:格式化字符串,在程序中想要的格式。

返回值:buf指向的字符串长度。

8.getpeername

函数原型:int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);

函数功能:用于获取预sockfd连接的对端的地址信息,结果存在peeraddr所指向的空间中。

参数:
addrlen:初始化addr所指向的空间的大小。

返回值:
成功返回0,失败返回-1,并将errno设置为对应的错误。

9.strtok函数

函数原型:char* strtok(char* str, const char* delimiters);

函数功能:切割字符串,将str切成一个个子串。

参数:
str:第一次调用时str是传入需要被切割的字符串的首地址,在后面调用时传入NULL。
delimiters:切割字符串的分隔符。

返回值:
当str中的字符查找到末尾时,返回NULL;
如果查不到delimiters所标识的字符,则返回当前strtok的字符串指针。

程序框架:

1.comm.h

创建监听套接字
接受连接
连接远端主机
从文件描述符中读取数据
发送响应码到文件描述符
从标准输入读取一行
去除字符串中的空白和换行

2.server.h

处理一个ftp事件
从控制连接中接收命令
用户登录
检测用户名和密码是否正确
创建一个数据连接
处理客户端发来的ls命令
处理文件上传
处理文件下载

3.client.h

读取服务器的回复
打印回复消息
读取客户端输入命令
下载文件
打开数据连接
处理list命令
发送命令
用户登录
上传文件

公共部分(comm.c)

部分函数实现可参考:简单的网络套接字编程

一、创建监听套接字

1.创建socket
2.bind,绑定端口号
3.listen,监听socket
特点:在socket_create中调用setsockopt函数,解决TIME_WAIT等的问题。端口复用,服务器立即进入重启。
int op=1;
setsockopt(sock ,SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));
函数声明:
int socket_create(const char* ip,const int port) ;//创建一个监听套接字

二、接受连接

accept,接收请求
函数声明:
int socket_accept(int sock);//接受连接

三、连接远端主机

1.创建一个socket
2.connect,建立连接
函数声明:
int socket_connect(const char* ip,const int port);//连接远端主机

四、从文件描述符读取数据

1.初始化,memset
2.recv
函数声明:
int recv_data(int sock,char* buf,int bufsize);//从文件描述符sock中读数据

五、发送响应码到文件描述符

1.htonl转换为网络序列
2.send,将响应码发送过去
函数声明:
int send_response(int sock,int code);//发送响应码到文件描述符

六、从标准输入读取一行

1.初始化buf
2.用fgets一行一行读取
函数声明:
void read_input(char *buf,int buffsize);//从标准输入读取一行

七、去除字符串中的空白和换行

遍历字符串,遇到空白或者是’\0’直接省去。
函数声明:
void trimstr(char* str,int n);//去除字符串中的空白和换行

将TCP常用操作和一些重复使用的操作封装起来,实现代码复用,方便我们管理。

服务器实现(server.c)

一、处理ftp事件

1.服务就绪,发送220响应码到文件描述符;
2.登录;成功,发送230到文件描述符。失败,发送430到文件描述符,并结束本次会话。
3.循环接受命令,获取命令和参数。当响应码为200时,创建一个数据连接,通过命令的不同处理不同的事件,最后关闭文件描述符。
函数声明:
void ftpserver_process(int sock_ctl);//处理一个ftp事件

二、从控制连接中接收命令

1.从文件描述符中读取数据;
2.将缓存区的数据拷贝到cmd和arg中;
3.根据不同的cmd,设置状态码。
4.向文件描述符中发送状态,返回响应码。
函数声明:
int ftps_recv_cmd(int sock_ctl,char* cmd,char* arg);//从控制连接中接受命令

三、用户登录

1.从文件描述符中读取数据;
2.通过自定义的用户名格式,将用户名保存起来;
3.通知用户输入密码;
4.通过自定义的密码格式,将密码保存起来;
5.验证用户名和密码是否正确,返回登录状态。
函数声明:
int ftpserver_login(int sock_ctl);//用户登录

四、检查用户名和密码是否正确

1.定义一个文件,里边保存用户名和密码;
2.用strtok函数分割用户名和密码,并对文件进行处理(去掉字符串中空格和换行符,方便用户不同格式的输入);
3.用strcmp检查用户名和密码是否正确。
函数声明:
int ftpserver_check_user(const char* user,const char* pass);//检查用户名和密码是否正确

五、创建一个数据连接

1.从另一端接收数据;
2.建立数据连接。
函数声明:
int ftpserver_start_data_conn(int sock_ctl);//创建一个数据连接

六、处理客户端发来的ls命令

1.将ftp目录下的文件列表重定向到.txt文件中;
2.打开.txt文件,准备发送数据;
3.将文件中的信息保存在stat结构体中;
4.在内核中将文件描述符信息拷贝到另一个文件描述符中,并关闭该文件描述符;
5.发送应答码226到文件描述符中。
函数声明:
int ftpserver_list(int sock_data,int sock_ctl);//处理客户端发来的ls命令

七、处理下载事件

1.打开文件成功后,发送150;失败,发送550.
2.打开成功后,获取文件信息,将该文件描述符的内容拷贝到另一个文件描述符,文件传输完成发送226。
函数声明:
void ftpserver_retr(int sock_data,int sock_ctl,char *filename);//处理文件下载

八、处理文件上传

1.从另一端接收数据,接收失败,发送502;
2.上传失败,发送553;
3.打开要上传的文件,打开失败,发送502;
4.打开成功,接收数据,保存到data中;
5.接收失败,发送502;接收成功,发送226;
6.将data里的数据写入文件描述符中,作为输出型参数。
函数原型:
void ftpserver_push(int sock_data,int sock_ctl,char* filename);//处理文件上传

FTP是用多线程实现的,创建新线程,然后让主线程等。为了不让主线程一直等子线程执行完成,采用了线程分离。

客户端实现(client.c)

一、读取服务器的回复

1.从服务器文件描述符中读取信息;
2.将网络序列转换为主机序列;
函数声明:
int read_reply(int sock_ctl);

二、打印回复信息

根据响应码打印提示信息;
这里实现了部分的响应码,严格按照ftp服务器的规定,响应码后空一个接对应的提示消息。
函数声明:
void print_reply(int status);

响应码 提示消息
220 服务器就绪。
221 服务器关闭数据连接,可以退出登录。
226 关闭数据连接,请求的文件操作成功。
502 命令未执行。
550 未执行请求操作。
553 文件名不合法,文件找不到。
其他 未知错误。

三、读取客户端输入的命令

1.读取客户端输入的命令;
2.将读取的命令以空格分割,arg指向输入的命令所带的参数;
3.判断输入的命令为正确的命令,将其存入code中;
4.最后将命令和参数以空格隔开再放入buf中。
函数声明:
int ftpclient_read_cmd(char* buf, size_t size, struct command* cmd);

四、上传文件

1.以只读方式打开文件;
2.将文件的信息保存在st结构中;
3.将fd的内容写入到输出文件描述符中;
4.关闭打开文件的描述符。
函数原型:
int ftpclient_put(int sock_data, char* filename);

五、下载文件

1.以只写方式打开文件;
2.循环读取文件描述符中的数据,并写入文件中。
函数原型:
int ftpclient_get(int sock_data, char* filename);

六、打开数据连接

1.创建一个数据连接;
2.给服务器发送一个确认,告诉服务器客户端创建好了一条数据链路;
3.接收连接,关系监听文件描述符。
函数原型:
int ftpclient_open_conn(int sock_ctl);

七、处理list命令

1.服务器连接成功;
2.接收服务器发来的数据,循环读取;
3.服务器返送完成。
函数原型:
int ftpclient_list(int sock_ctl, int sock_data);

八、发送命令

1.将cmd中的命令(code)和参数(arg)格式化写入buf中;
2.将buf中的数据发送到文件描述符中。
函数原型:
int ftpclient_send_cmd(int sock_ctl, struct command* cmd);

九、用户登录

1.输入用户名和密码,并设置code和arg;
2.发送用户名、密码到服务器;
3.接收应答码;
4.通过不同的应答码,对于登录的状态进行提示。
函数原型:
int ftpclient_login(int sock_ctl);

猜你喜欢

转载自blog.csdn.net/ZWE7616175/article/details/81415421
今日推荐