2022-7-17 FTP客户端项目实现 - 总结

一.简介

FTP客户端(Linux C++, https://gitee.com/tracker647/linftp)

项目描述:该项目模拟FTP客户端与服务端的通信流程,具有shell,网络,文件系统等模块,实现了对服务端的ls,cd,put和get功能,未来考虑添加登录验证,多线程和mkdir和rmdir等更多指令支持。

个人收获:通过该项目对于Linux的文件和网络相关API更为熟悉,了解了网络协议的基本实现方式,同时加深了对TCP传输特点和网络传输原理的理解。

二.项目设计与实现

计划实现功能

指令功能实现情况
ls显示ftp服务器当前目录文件,默认为根目录
cd切换远程目录
help指令一览
open连接指定IP
mkdir创建远程目录
rmdir删除远程目录
get下载文件
put上传文件
quit退出系统
status显示客户端状态
close断开连接

尚未考虑指令的其他功能特性:

​ 登录验证(为方便调试就没做)

​ 多客户端支持(目前试过多进程-> 借此了解vscode调试多进程的方法,以后尝试多线程)

​ 方向键弹出历史指令(一度写了个历史指令队列,但Linux C并没有各按键中断的处理库,除了Ctrl+C,这部分有一个nurse库实现了,但是懒得再引入外部库,放弃了)

功能实现思路

ls功能实现:
  • 客户端发送ls信号等待服务端响应

  • 如服务端正常响应,则客户端进入数据接收状态

  • 服务端将当前路径传入文件系统的对应函数,该函数根据当前路径返回当前目录的所有文件名的字符串

  • 将当前目录信息,文件名集合整合到服务端写缓冲上,发送给客户端

  • 客户端读取后显示服务端当前目录的信息

cd功能实现:
  • 接收 ”./” , “…/”, 相对路径三种输出

  • 对于 “./” 直接返回,让客户端显示当前目录

  • 对于 “…/” 若已在根目录,告知客户端后返回,否则获取上一级目录的路径并将服务端当前目录改为上一级目录,并告知客户端

  • 对于相对路径,与当前路径组合后通过open检查其路径有效性,若路径有效,则将当前路径拷贝为当前路径与相对路径组合后的新路径,并告知客户端

put功能实现:
  • 传输开始前,客户端进入准备状态并告知服务器

- 客户端打开路径对应文件,若打开失败,返回异常信息并告知服务器中断传输

,否则告知服务端输入流打开成功,令服务端打开输出流

- 服务端若打开输出流失败,需告知客户端中断传输,否则告知客户端输入流打开

- 双方写流和读流都开启后,客户端告知服务端数据各字段长度(包括文件名,文件大小),并按各字段长度开始传输

- 服务端逐字节接收,若接收过程中出现异常,需告知客户端传输失败并中断传输

- 告知的数据长度传递完毕后两段即正常完成传输

image-20220717221701664
get功能实现:

- 客户端向服务端传送路径,询问服务端路径有效性,若路径有效,则服务端通知客户端可正常打开输入流,否则告知路径非法,中断传输。

- 客户端打开输入流若成功告知服务端开始数据传输,否则告知打开失败,中断传输。

- 服务端告知客户端数据各字段长度(包括文件名,文件大小),并按各字段长度开始传输

- 客户端逐字节接收,若接收过程中出现异常,需告知服务端传输失败并中断传输

- 告知的数据长度传递完毕后两段即正常完成传输

三.项目总体布局(2022717)

图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wkRVXgKU-1658071604140)(https://s2.loli.net/2022/07/17/b4RT6mklJAVD75t.png)]

目录结构
linftp/

├── ftp_client

├── ftp_server

├── makefile

├── obj/

│ ├── client_shell.o

│ ├── error.o

│ ├── filesys.o

│ ├── ftp_client.o

│ ├── ftp_server.o

│ ├── network.o

│ └── server_backend.o

├── serverRootDir/

└── src/

├── client_shell.c

├── client_shell.h

├── cmd_code.h

├── filesys.c

├── filesys.h

├── ftp_client.c

├── ftp_server.c

├── network.c

├── network.h

├── server_backend.c

└── server_backend.h
主要文件介绍

serverRootDir/ ftp服务器文件根目录

ftp_client 客户端前端,主要指令功能由client_shell实现

cmd_code shell用于shell指令的宏定义

client_shell 客户端上指令的具体实现,包括本地指令和连接服务端后才能用的指令

network 负责联网部分功能的封装和实现,包括客户端连接和服务端socket初始化

filesys 服务端的文件系统,宏定义和功能实现

ftp_server 服务端前端,主要功能由server_backend实现

server_backend 服务器后端,用于承载服务端主要功能,接收客户端远程指令

四.系统运行和测试

服务端和客户端总界面

img

客户端调用ls, cd功能

img

img

img

客户端调用put功能

img

img

img

客户端调用get功能

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8eICzBV7-1658071604152)(https://s2.loli.net/2022/07/17/eh43vOWwtuLZVED.jpg)]

img

五.实现时遇到的困难和坑点记录

直接使用字符串传指令(用宏定义各种状态):

导致各种问题,包括字符不定长增大编程难度,野字符导致比较失效,最后决定用指令码取代字符串指令,在头文件定义了各类指令宏,后来文件系统出现的各种状态也使用了这种定义方式。

用memset初始化输出数组:

实现ls指令时,发现ls根目录后,cd后更下一级目录再ls,ls会把当前目录的文件和根目录的文件一起输出去,原因是函数退出后并不一定会将内存都销毁,有些内存会被操作系统留下来供以后复用,最后通过memset在每次运行ls时将输出数组初始化解决了这个问题。

知识点:free 释放内存,会归还给操作系统吗?

字符串传输中出现字符串尾部莫名出现/377/177问题:

定义好字符数组后用memset初始化一下。

write的写溢出和read的读溢出:

回声测试时直接让双方以定义的缓存宏为单位通信数据,结果带来了很多问题,具体举例如下:

	char *mes1 = "Hello World"; 
    char *mes2 = "SINCE WHEN YOU ARE THE ONE IN CONTROL?" ;
    char *mes3 = "LOOK A DEER";
    int filefd = open("sockfd",O_RDWR, O_TRUNC);
    write(filefd,mes1,500);

以上的write不仅会读入mes1,还将把mes2和mes3以及各种垃圾字符共500字读入文件中。

而以下代码

//客户端
int datalen = statbuf.st_size;
write(servfd,filename,strlen(filename)); //5
write(servfd,filedata,datalen);	//50

//服务端
#define READ_BUFFER_SIZE 500
read(servfd,filename,READ_BUFFER_SIZE)

将导致服务端把文件名和文件数据全部读入filename数组中,导致后继的逻辑异常。

以上原因全是因为TCP的传输方式是无边界的,意味着数据的读写不受范围限制,一次write不一定对应一个read,可能分几次read走(比如传入的字符串发生了拆包),而N个属性各符一个write共N次输出也可能一次就被read走(造成粘包问题)。

因此网络通信中对每种信息,最好都通知对方自己将发送多少数据的字节(比如上次文件,客户端依次发送文件名长,文件名,数据长,最后数据传输),保证read和write能够一一对应,避免拆包和粘包,才能确保网络程序的正常运行。

另一个方法是在每种信息之间都加入自定义的分界符,不过我没试过。

open的mode位引发的权限问题:

实现put和get时发现一个怪现象,凡是接收端对应路径已经存在同名文件就会导致异常,由于之前是在云服务器编程,全程root,并没有发现这个问题,后来切换到虚拟机编程时才发现是自己的权限问题,文件浏览器一看新建的文件都是加叉加锁的,最后定位到open函数上,一查,发现mode位对权限的计算方式是mode & ~umask,而不是像chmod那样直接修改, 根据参考修改了mode位后,问题解决。

多进程服务端的调试问题:

之前尝试过用多进程的方式实现服务端,原理是有新连接时父进程将另外fork出一个子进程用于会话,而调试器默认只能追踪父进程,无法查知子进程的执行情况,解决方式预先将gdb设置指令为set follow-fork-mode child (在vscode上则是设置launch.json的对应配置的setupCommands属性,详见项目源码),这样调试器将在fork时自动切换到子进程。

六.个人收获心得

  • 不要自信自己在无提前git的情况就能升级函数代码,一旦写崩了有你好受的,我就因此自信自己能把lcd写出来去修改成型的cd函数,结果导致自己花了一整晚才把cd的功能重新恢复。

  • 接第一条,升级代码更好的方法是出芽生殖,比如要修改xxx_fun函数,应该另写个个xxx_fun2去测试,有多种思路想测试还可以写xxx_fun3, xxx_fun4。。。这样万一写崩了直接改回原本的函数就行。

  • 别想着一开始就把代码写的最好,动不动就想要封装或者简化,在有限的知识下的实践带来的产品往往是漏洞百出,再简化的功能,随着代码量的增多也会变成一坨屎,你会感觉注释反而更加重要。此外不管你是什么代码,工作N年后回头去看都会觉得是一坨垃圾。

  • 在自作聪明地为自己已知的文件异常定义状态宏时不如请教下errno。

  • 写put和get有感:不要放过流程出现的每一个可能异常,打上日志!

  • printf日志可能提供比GDB还出其不意的效果。

  • 直接用纸笔记录自己项目的实现情况更直观也更助于思考,我在实现这个项目时常把思路手写到纸质本上,时常就能看到,电子化笔记应该用于以后总结。

七.附录

开发环境

系统:Ubuntu 20.04 LTS

IDE:vscode

参考

https://blog.csdn.net/qq_24889575/article/details/81566164

《UNIX环境编程》

《UNIX网络编程》

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux服务器配置与管理项目8 FTP服务器配置与管理 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第1页。 【项目描述】 公司为方便内部信息的交流,需要一台FTP服务器实现公司内部文件的上传下载功能。同时,公司的Web服务器也要借助FTP服务来实现网站资源的更新。 本项目中我们来完成FTP服务器的配置与管理任务。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第2页。 【学习目标】 (1)了解FTP服务器网络中的作用。 (2)掌握FTP服务器的安装过程。 (3)掌握匿名访问FTP服务器的配置方法。 (4)掌握具名访问FTP服务器的配置方法。 (5)掌握FTP服务器的安全管理方法。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第3页。 预备知识 认识FTP服务器 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第4页。 FTP的工作原理 以HTTP为基础的WWW服务功能虽然强大,但对于文件传输来说却略显不足。一种专门用于文件传输的FTP服务应运而生。 FTP(File Transfer Protocol)即文件传输协议,FTP服务是用于文件传输的服务,相对于WWW服务,具有更高的可靠性和效率。 FTP极大简化了文件传输的复杂性,能够使文件通过网络从一台主机传送到另一台主机却不受计算机和操作系统类型的限制。无论是PC、服务器、大型机,还是Linux、Windows操作系统,只要双方都支持FTP协议,就可以方便、可靠地进行文件的传送。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第5页。 FTP的工作原理 FTP服务的具体工作过程如图所示。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第6页。 FTP的传输模式 1)主动传输模式 在主动传输模式下,FTP客户端随机开启一个大于1024的端口(1024+X)向服务器的21号端口发起连接,然后开放(1024+X+1)号端口进行监听,并向服务器发出"PORT 1024+X+1"命令。服务器接收到命令后,会用其本地的FTP数据端口(通常是20)来连接客户端指定的端口(1024+X+1),进行数据传输。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第7页。 FTP的传输模式 2)被动传输模式 在被动传输模式下,FTP客户端随机开启一个大于1024的端口(1024+X)向服务器的21号端口发起连接,同时会开启(1024+X+1)号端口,然后向服务器发送PASV命令,通知服务器自己处于被动模式。服务器收到命令后,会开放一个大于1024的端口(1024+Y)进行监听,然后用"PORT 1024+Y"命令通知客户端,自己的数据端口是1024+Y。客户端接收到命令后,会通过(1024+X+1)号端口连接服务器的1024+Y端口,然后在两个端口之间进行数据传输。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第8页。 任务一 安装FTP服务器 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第9页。 任务提出 要想使FTP服务器顺利运行,首先需要安装好所需要的软件包。本次任务主要安装FTP服务器所需要的软件包vsftpd。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第10页。 任务分析 FTP可以通过很多软件实现linux中最常用的FTP服务器软件是vsftpd。 vsftpd 是一个基于GPL发布的FTP服务器软件。其中的vs是"Very Secure"的缩写,由此名称缩写可以看出,该服务器的初衷就是服务的安全性。 RHEL7.3中的vsftpd主程序软件包是vsftpd-3.0.2-21.el7.x86_64.rpm,我们需要安装此软件包。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第11页。 任务实施——查看系统中是否已经安装vsftpd软件包 如果主程序包没有安装,则需要安装主程序包。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第12页。 任务实施——安装主程序包 步骤1 挂载光盘。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第13页。 任务实施——安装主程序包 步骤2 使用YUM安装软件包。 Linux服务器配置与管理项目8--FTP服务器配置与管理全文共50页,当前为第14页。 任务总结 本次任务主要完成了FTP服务器软件的安装。由于使用了YUM工具,在安
ftp客户端ftpclient纯C语言winsock实现socket编程 /* *本程序是2009年计算机网络课程设计作品。 *本程序参考rfc959标准。能准确与遵守此标准的服务器进行信息交互。 *本人不保留任何版权。 *本程序仅供学习研究测试使用。因使用本程序所有或部分代码所产生的任何后果,本人均不负任何法律责任。 *2009年7月13日 */ #include #include #include /*system()*/ #include #pragma comment(lib,"ws2_32.lib") #define ONUM 512 #define MNUM 512 #define FNUM 512 #define pt struct host { char ip[20]; unsigned short port; }; SOCKET ts; fd_set readfds; struct timeval timeval; struct host host; char renum[4]; char ordertemp[ONUM]; char order[ONUM]; char ordercp[ONUM]; char mess[MNUM]; char file[FNUM]; char setpath[FNUM]; char setpathf[FNUM]; int i,door,r,sys,seti;/*i for;door switch;r receive num;sys system state;set set state*/ char *p;/*strtok*/ int printmess(); void input(char ordertemp[]); int ftp(); int user(); int pass(); int command(); int list(); SOCKET createDataSocket(); int set(); int retr(); int stor(); int stor() { char filename[256]; SOCKET ds; int wi; int r2,r3,r4,bsnum,brnum; FILE *fp=NULL; set(); memset(filename,'\0',256); memset(order,'\0',ONUM); for(i=5;ibsnum); }/*while*/ printf("\n"); switch(printmess()) { case 425: case 426: case 451: case 551: case 552:closesocket(ds);fclose(fp);return -1; case 250: case 226:fclose(fp);return 0; default:return 0; } }/*stor*/ int retr() { char filename[256]; unsigned long fsize,wfsize; int r2,wi; SOCKET ds; FILE *fp=NULL; memset(filename,'\0',256); memset(order,'\0',ONUM); for(i=5;i<=200&&ordercp[i]!='\0';i++)order[i-5]=ordercp[i]; strcpy(filename,setpathf); strcat(filename,"\\\\"); strcat(filename,order); ds=createDataSocket(); if(ds==-1)return -1; memset(order,'\0',ONUM); strcpy(order,"type i\r\n");/*type i 二进制 type a ASCII*/ send(ts,order,strlen(order),0); switch(printmess()) { case 421:closesocket(ds);closesocket(ts);sys=0;return 421; case 530:closesocket(ds);sys=1;return -1; case 500: case 501: case 504: case 226:closesocket(ds);return -1; case 200:break; default:closesocket(ds);return -1; } strcat(ordercp,"\r\n"); send(ts,ordercp,strlen(ordercp),0); switch(printmess()) { case 421:closesocket(ds);closesocket(ts);sys=0;return 421; case 530:closesocket(ds);sys=1;return -1; case 450: case 500: case 501: case 550:closesocket(ds);return -1; case 125: case 150:break; default:closesocket(ds);return -1; } set(); system(setpath); fp=fopen(filename,"wb"); if(!fp){printf("create file fail!\n");closesocket(ds);printmess();return -1;} wi=1;fsize=0;wfsize=0; while(wi) { memset(file,'\0',FNUM); r=recv(ds,file,FNUM,0); if(r==SOCKET_ERROR) { printf("file recv error!\n"); closesocket(ds); fclose(fp); return -1; } fsize=fsize+r; if(r==0){wi=0;break;} printf("receive %8d bytes! have received %16d bytes!\r",r,fsize); r2=fwrite(file,sizeof(char),r,fp); fflush(fp);/*这里是关键*/ wfsize=wfsize+r2; printf("write %8d bytes!have written %20d bytes!\r",r2,wfsize); }/*while*/ printf("\n"); switch(printmess()) { case 425: case 426: case 451:closesocket(ds);fclose(fp);return -1; case 250: case 226: closesocket(ds); wi=1; while(wi)if(fclose(fp)==0)wi=0;return 0; default:return 0; } }/*retr()*/ 以下代码请下载本程序。VC++6.0编译通过
### 回答1: Python可以使用内置的ftplib模块来实现FTP客户端的设计和实现。 首先,我们需要导入ftplib模块: ```python import ftplib ``` 接下来,我们需要连接到FTP服务器。我们可以使用ftplib.FTP类的connect方法来建立连接: ```python ftp = ftplib.FTP() ftp.connect("ftp服务器地址", "ftp服务器端口") ``` 连接成功后,我们需要登录到FTP服务器。我们可以使用ftplib.FTP类的login方法来登录: ```python ftp.login("用户名", "密码") ``` 登录成功后,我们可以执行各种FTP操作。例如,我们可以列出FTP服务器上的文件列表: ```python files = ftp.nlst() for file in files: print(file) ``` 我们还可以下载文件: ```python ftp.retrbinary("RETR 文件名", open("本地保存路径", "wb").write) ``` 同时,我们还可以上传文件: ```python ftp.storbinary("STOR 文件名", open("本地文件路径", "rb")) ``` 最后,我们可以关闭与FTP服务器的连接: ```python ftp.quit() ``` 通过以上步骤,我们可以实现一个简单的FTP客户端。当然,我们还可以根据具体需求来进一步增加其他功能,例如创建文件夹、删除文件、重命名文件等操作。 需要注意的是,FTP服务器的地址、端口、用户名和密码等参数都需要根据实际情况进行设置,并且在处理错误和异常时需要进行适当的处理。 ### 回答2: Python可以使用ftplib库来实现FTP客户端的设计与实现。以下是一个基本的示例代码: ```python # 导入ftplib库 from ftplib import FTP # 创建FTP客户端对象 ftp = FTP() # 连接FTP服务器 ftp.connect('ftp.example.com', 21) # 登录FTP服务器 ftp.login('username', 'password') # 切换到指定目录 ftp.cwd('path/to/directory') # 列出当前目录下的文件和文件夹 file_list = ftp.nlst() for file in file_list: print(file) # 下载文件 ftp.retrbinary('RETR filename', open('local_filename', 'wb').write) # 上传文件 file = open('local_filename', 'rb') ftp.storbinary('STOR remote_filename', file) # 删除文件 ftp.delete('filename') # 关闭FTP连接 ftp.quit() ``` 以上代码演示了连接到FTP服务器、登录、切换目录、列出文件、下载、上传和删除文件的基本操作。你可以根据具体需求来进行修改和拓展。使用Python实现FTP客户端可以方便地与FTP服务器进行交互,并进行文件传输和管理操作。 ### 回答3: Python实现FTP客户端可以通过使用Python的内置模块`ftplib`来完成。`ftplib`模块提供了一些方法和函数来方便地连接FTP服务器、上传和下载文件。 首先,我们需要通过`ftplib.FTP`类来建立与FTP服务器的连接。我们可以使用`ftplib.FTP`类的`connect(host, port)`方法来指定FTP服务器的地址和端口。例如,我们可以使用以下代码连接到FTP服务器: ```python import ftplib ftp = ftplib.FTP() ftp.connect('ftp.example.com', 21) ``` 接下来,我们需要使用`login(user, passwd)`方法进行登录。我们需要提供用户名和密码。例如,我们可以使用以下代码登录FTP服务器: ```python ftp.login('username', 'password') ``` 登录成功后,我们可以使用`ftp.cwd(directory)`方法切换到目标目录。例如,我们可以使用以下代码进入目标目录: ```python ftp.cwd('directory') ``` 此时,我们可以使用`ftp.retrbinary(command, callback)`方法来下载文件。`retrbinary`方法需要提供FTP服务器的命令和一个回调函数来接收文件数据。例如,我们可以使用以下代码下载文件到本地: ```python def receive_data(data): # 处理接收到的数据,例如将数据写入本地文件 pass ftp.retrbinary('RETR filename', receive_data) ``` 类似地,我们可以使用`ftp.storbinary(command, file)`方法来上传文件。`storbinary`方法需要提供FTP服务器的命令和一个本地文件对象。例如,我们可以使用以下代码将本地文件上传到FTP服务器: ```python file = open('localfile', 'rb') ftp.storbinary('STOR remotefilename', file) file.close() ``` 最后,我们可以使用`ftp.quit()`方法来结束与FTP服务器的连接: ```python ftp.quit() ``` 综上所述,以上是使用Python实现FTP客户端的主要步骤和方法。通过使用`ftplib`模块的相关函数和方法,我们可以方便地连接FTP服务器、登录、切换目录、上传和下载文件等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值