Linux下基于TCP的简易FTP云盘项目

发布时间:2026/7/9 13:52:19
Linux下基于TCP的简易FTP云盘项目 本项目研发了一款Linux平台下的简易FTP云端存储工具核心基于C语言实现标准的客户端-服务端体系。主要涵盖的功能矩阵为双端文件视图展示ls/lls、层级目录切换cd/lcd及核心的资源上传put与拉取get。服务端主要通过Socket机制捕获网络通信结合多进程调度策略保障高并发访问的稳定性客户端则依靠指令驱动完成用户交互。技术实现细节上系统借助strtok、popen等POSIX标准库函数高效完成了复杂路径的剥离与跨进程通信。整体项目对齐了核心FTP传输标准提供了纯粹且轻量的终端交互体验。运行规范上要求先初始化服务端监听状态客户端输入目标IP及端口建立连接后即可触发各类文件流转。Linux下基于TCP的简易FTP云盘项目1、项目介绍系统概念本项目实现了一个遵循FTPFile Transfer Protocol规范的网络侧文件托管与访问服务。项目概述系统基于Linux网络编程开发采用经典的C/S客户端-服务端架构。主要功能不仅支持远程与本地双端的文件目录浏览还涵盖了对远端文件的删除、存储与归档管理并实现了客户端与服务端之间的文件双向传输上传与下载。运行机制在工作流程上服务端处于持续监听状态以接收网络指令具备处理多客户端并发接入及解析对应命令的能力并将执行结果回传客户端则负责接收服务端下发的处理回执将操作结果直观地呈现给终端用户从而实现完整的文件交互闭环。2、基础功能通过socket实现ls查看服务端文件lls查看客户端自己的文件cd切换服务端目录lcd切换客户端自己的目录put上传文件get下载文件g退出3、流程图4、整体思路服务端socket实例化服务端通信套接字bind绑定本地网络节点的IP与端口listen开启对客户端请求的被动监听accept捕获并建立与客户端的接入连接read提取网络链路中客户端发送的指令报文服务端开始处理从客户端接收到的消息send (write)将服务端的响应数据回写至客户端客户端socket实例化客户端通信套接字构建端到端的数据交互链路connect向目标服务端发起TCP握手请求并建立网络连接捕获终端标准输入并对指令缓冲区buf进行解析与封装send (write)将客户端的控制指令下发并推送至服务端read提取并解析服务端回传的执行响应报文5、get和put功能get功能流程图put功能流程图6、一些相关函数的补充1.popen函数机制基于匿名管道Pipe机制动态派生子进程从而构建标准化的跨进程通信IPC数据链路。函数原型FILE *popen(const char *command, const char *type);执行状态返回值句柄挂载正常返回标准的FILE*I/O 流指针允许开发者复用fread、fgets、fprintf等 POSIX 标准库接口以流式操作接管跨进程数据。句柄挂载异常返回NULL空指针并向系统底层的errno注册具体的异常状态码。入参释义command执行载荷需交由底层 Shell 执行的终端指令序列例如资源遍历ls -l或字符过滤grep hello。type流向控制定义 IPC 通道的 I/O 交互模式r(数据捕获模式)主控程序读取/劫持被唤醒子进程的标准输出流Standard Output。w(数据注入模式)主控程序向被唤醒子进程的标准输入流Standard Input推送数据负载。2.strtok函数机制实现对连续内存块中字符序列的动态切割与词法提取Tokenization根据指定的特征字符集对报文进行精准剥离。函数原型char *strtok(char *str, const char *delimiters);执行状态返回值解析命中返回指向当前提取到的词法单元Token的内存指针。解析终止当遍历耗尽或无有效字符可提取时返回NULL空指针作为越界标识。入参释义str目标载荷与状态控制初始游标绑定首次调用时需传入待解析字符串的首地址以初始化底层的静态解析游标。游标状态延续在后续的循环迭代中需传入NULL函数将依托内部维持的静态指针Static Pointer从上次截断的位置继续向后文滑动解析。delimiters截断掩码集定义用于触发字符串截断动作的字符集合字典例如空格、换行符等。3.strcmp函数机制基于 ASCII 编码规范对两个以\0Null-terminated作为截断标识的连续内存字符块执行严格的逐字节字典序比对。函数原型int strcmp(const char *str1, const char *str2);执行状态返回值 0精确命中双端字符序列完全一致验证通过。 0或 0序列偏差在遇到首个不匹配字符时发生偏移正负值直接表征str1与str2在底层 ASCII 码值上的大小差值与偏离方向。入参释义str1/str2参与校验的目标字符载荷指针通常由const修饰以保障内存只读安全。4.memset函数机制对连续的内存块执行批量的按字节覆写Byte-wise Overwriting主要用于网络缓冲区Buffer的快速零值初始化、数据结构重置或脏数据清洗。函数原型void *memset(void *str, int c, size_t n);执行状态返回值操作完成返回目标内存块的初始基地址即传入的str指针此设计在系统底层原生支持流式的链式调用Chained Calls。入参释义str目标基地址待格式化操作的连续内存段起始指针。c状态掩码预设的内存填充字。需要注意的是该参数虽以int形式压栈但在底层执行时会被系统强制截断为单字节的unsigned char掩码进行逐字节写入。n内存跨度严格界定本次物理内存覆写操作的字节长度边界是防范内存越界Buffer Overflow的核心安全参数。5.exit函数机制主动触发当前进程的生命周期终止Termination。在向操作系统内核交还控制权之前底层会自动接管并强制冲刷Flush所有标准 I/O 缓冲区确保残留数据安全落盘并执行注册的退出清理函数。函数原型void exit(int status);入参释义statusOS级状态回执向父进程或操作系统调度器递交的进程退出状态向量。标准状态掩码EXIT_SUCCESS或0正常销毁。表征业务逻辑已按预期执行完毕系统上下文与资源已安全释放。EXIT_FAILURE或 非零值异常中断。表征进程在运行期遭遇致命错误Fatal Error或触发越界访问主动终止运行栈以保护系统安全。6.bzero函数机制针对指定的连续物理内存空间执行高速的字节级零值填充Zero-fill。在经典的 Linux 网络编程范式中该接口被高频用于网络配置结构体如sockaddr_in或 I/O 缓冲区的快速脏数据擦除以保障底层通信的内存纯净度。函数原型void bzero(void *s, size_t n);入参释义s目标基地址需执行物理清零操作的内存段首地址。n清零跨度严格限定的字节格式化边界决定了底层零值覆盖的有效物理长度。7、代码实现先创建一个FTP的目录用于存放代码文件首先建立service.c的文件作为服务端#include stdio.h #include sys/types.h #include sys/socket.h #include string.h #include arpa/inet.h #include strings.h #include string.h #include unistd.h #include stdlib.h #include unistd.h #include sys/stat.h #include fcntl.h #define FIF 5 char *getbehind(char cmd[128]) { char *p (char *)malloc(128); p strtok(cmd, ); p strtok(NULL, ); return p; } void getmessage(char cmd[128],int c_fd) { char *p getbehind(cmd); char readbuf[8000] {}; int fd -1; if(access(p,F_OK) -1) { write(c_fd,service not file,strlen(service not file)); } else { fd open(p,O_RDWR,0666); read(fd,readbuf,8000); write(c_fd,readbuf,sizeof(readbuf)); close(fd); } } void putmessage(char cmd[128],int c_fd) { char *p getbehind(cmd); int fd -1; char readbuf[8000] {}; read(c_fd,readbuf,8000); fd open(p,O_RDWR | O_CREAT,0666); write(fd,readbuf,sizeof(readbuf)); printf(service receivr success!\n); memset(p,0,sizeof(p)); close(fd); } int change(char cmd[128]) { if(!strcmp(ls,cmd)) { return 1; } else if(!strcmp(ps,cmd)) { return 2; } else if(!strcmp(g,cmd)) { return 3; } else if(strstr(cmd,lcd) ! NULL) { return 4; } else if(strstr(cmd,get) ! NULL) { return 5; } else if(strstr(cmd,put) ! NULL) { return 6; } else if(strstr(cmd,cd) ! NULL) return 7; } void choosecmd(char cmd[128],int c_fd) { int ret change(cmd); FILE* fdb; char readbuf[8000] {}; char *Freadbuf (char *)malloc(128); char *p (char *)malloc(128); printf(cmd:%s,ret:%d\n,cmd,ret); switch(ret) { case 1: fdb popen(ls,r); fread(readbuf,sizeof(readbuf),1,fdb); write(c_fd,readbuf,sizeof(readbuf)); //bzero(readbuf,sizeof(readbuf)); memset(readbuf,0,sizeof(readbuf)); printf(The message was sent to the client successfully\n); break; case 2: system(ps); break; case 3: read(c_fd,Freadbuf,8000); printf(%s\n,Freadbuf); bzero(Freadbuf,128); exit(0); case 4: printf(client into path success!\n); break; case 5: getmessage(cmd,c_fd); break; case 6: putmessage(cmd,c_fd); break; case 7: p getbehind(cmd); if(chdir(p) -1) { printf(service shift error\n); } else { printf(service shift success\n); } bzero(p,128); break; } } int main(int argc,char *argv[]) { if(argc ! 3) { perror(argc); exit(0); } int s_fd -1; int c_fd -1; int nread -1; pid_t pid; char readbuf[8000] {}; s_fd socket(AF_INET,SOCK_STREAM,0); if(s_fd 0) { perror(socket); exit(0); } struct sockaddr_in s_in; bzero(s_in,sizeof(s_in)); s_in.sin_family AF_INET; s_in.sin_port htons(atoi(argv[2])); inet_aton(argv[1],s_in.sin_addr); int s_addrlen sizeof(s_in); if(bind(s_fd,(struct sockaddr*)s_in,s_addrlen) 0 ) { perror(bind); exit(0); } if(listen(s_fd,FIF) 0 ) { perror(listen); exit(0); } int ret -1; struct sockaddr_in c_in; bzero(c_in,sizeof(c_in)); socklen_t c_addrlen sizeof(c_in); printf(Wait connecting\n); while(1) { c_fd accept(s_fd,(struct sockaddr*)c_in,c_addrlen); if(c_fd 0) { perror(accept); exit(0); } printf(Client connect:%s,%d\n,inet_ntoa(c_in.sin_addr),ntohs(s_in.sin_port)); pid fork(); if(pid 0) { while(1) { nread read(c_fd,readbuf,128); choosecmd(readbuf,c_fd); bzero(readbuf,sizeof(readbuf)); } } } return 0; }接着再创建一个client.c的文件当作客户端#includestdio.h #includesys/types.h #includesys/socket.h #includearpa/inet.h #includenetinet/in.h #includestdlib.h #includeunistd.h #includestring.h #includesys/stat.h #includefcntl.h int change(char cmd[128]) { if(!strcmp(lls,cmd)) return 1; else if(!strcmp(ls,cmd)) return 2; else if(!strcmp(g,cmd)) return 3; else if(strstr(cmd,cd) ! NULL) return 4; else if(strstr(cmd,lcd) ! NULL) return 5; else if(strstr(cmd,get) ! NULL) return 6; else if(strstr(cmd,put) ! NULL) return 7; else return 0; // 补充默认返回值 } // 修正返回值类型为 char*去掉多余的 malloc char* getbehind(char cmd[128]) { char *p; p strtok(cmd, ); p strtok(NULL, ); return p; } void getmessage(char cmd[128],int c_fd) { char *p getbehind(cmd); char readbuf[8000] {0}; // 修正为一级字符数组 int n_read read(c_fd, readbuf, 8000); // 接收实际读取字节数 int fd open(p, O_RDWR|O_CREAT, 0666); write(fd, readbuf, n_read); // 用实际读取长度不用 strlen printf(receive successful!\n); close(fd); } void putmessage(char cmd[128],int c_fd) { char p[8000] {0}; char *readbuf getbehind(cmd); // 去掉多余的 malloc if(access(readbuf, F_OK) -1) { printf(no file\n); } else { int s_fd open(readbuf, O_RDWR); int n_read read(s_fd, p, 8000); write(c_fd, p, n_read); printf(the file put success!\n); close(s_fd); } } void choosecmd(char cmd[128],int c_fd) { int ret change(cmd); char p[8000] {0}; switch(ret) { case 1: system(ls); break; case 2: read(c_fd, p, 1024); printf(%s\n,p); memset(p, 0, 1024); break; case 3: printf(unconnecting\n); write(c_fd, away_host, 128); close(c_fd); exit(-1); break; case 4: printf(services cd finish\n); break; case 5: { char *path getbehind(cmd); chdir(path); break; } case 6: getmessage(cmd, c_fd); break; case 7: putmessage(cmd, c_fd); break; default: printf(unknown cmd\n); break; } } int main(int argc,char **argv) { char WriteBuf[128] {0}; char ReadBuf[1024]; int c_fd; struct sockaddr_in c_addr; socklen_t clent; if(argc ! 3) { perror(argc:); exit(1); } c_fd socket(AF_INET, SOCK_STREAM, 0); if(c_fd -1) { perror(socket:); exit(1); } memset(c_addr, 0, sizeof(struct sockaddr_in)); c_addr.sin_family AF_INET; c_addr.sin_port htons(atoi(argv[2])); inet_aton(argv[1], c_addr.sin_addr); clent sizeof(struct sockaddr_in); if(connect(c_fd, (struct sockaddr *)c_addr, clent)) { perror(connect); exit(1); } printf(--------connect--------\n); while(1) { bzero(WriteBuf, 128); // 修正变量名大小写 fgets(WriteBuf, 128, stdin); // 用安全的 fgets 替代 gets // 去掉 fgets 读入的换行符 WriteBuf[strcspn(WriteBuf, \n)] \0; printf(cmd:%s\n, WriteBuf); write(c_fd, WriteBuf, strlen(WriteBuf)); choosecmd(WriteBuf, c_fd); printf(--------cmd--------\n); memset(WriteBuf, 0, sizeof(WriteBuf)); } return 0; }然后编译两个文件gcc client.c -o client gcc service.c -o service最后再新开一个终端运行客户端程序并在命令后附加服务端的内网 IP 和端口号即可完成连接并体验该功能: