1. 项目实现的功能
1.1. 在客户端对服务器的操作:
- 获取服务器的文件(get)
- 查看服务器当前路径下的所有文件(ls)
- 进入服务器的某个文件夹(cd)
- 上传文件到服务器(put)
- 查看当前服务器路径(pwd)
- 退出(quit)
1.2. 在客户端本地的功能实现:
- 查看客户端当前路径下有哪些文件(lls)
- 进入客户端的某个文件夹(lcd)
- 查看当前客户端路径(lpwd)
- 退出连接(quit)
2. 项目实现思路
2.1. socket服务器和客户端开发步骤:
- 服务器server:
- 创建套接字(socket)
- 将socket与IP地址和端口绑定(bind)
- 监听被绑定的端口(listen)
- 接收连接请求(accept)
- 当有客户端接入时创建子进程对接并处理客户端发来的请求
- 客户端client:
- 创建套接字(socket)
- 连接指定计算机的端口(connect)
- 接收输入向socket中写入信息(write)
- 获取客户端发送的内容像ls,pwd,get指令,需要进行(read)
2.2. 指令的实现思路:
- ls与pwd:调用popen函数,执行命令,并获取执行命令后的输出内容,将内容发回到客户端
- cd:将cd指令发送到服务器,在服务器端调用chdir实现路径的切换
- get:客户端获取服务器的某个文件,服务器首先通过access函数判断文件是否存在,存在则将文件的内容发送到客户端,客户端创建文件,并将服务器发送的内容保存至文件中,实现get指令。
- put:客户端向服务器发送文件,客户端首先通过access函数判断文件是否存在,存在则将文件的内容发送到服务器,服务器创建文件,并将客户端发送的内容保存至文件中,实现put指令。
- lls,lpwd:调用system函数即可。
- lcd:在客户端直接调用chdir函数即可
3. 项目用到的函数
3.1. 判断文件是否存在函数access()原型和头文件:
/*
Linux下 man 2 access查看手册
*/
#include <unistd.h>
int access(const char *pathname, int mode);
int 函数返回值,返回值如果成功(所有请求的权限都被授予,或者mode为F_OK并且文件存在),返回0。如果出现错误(mode中至少有一个 位请求的权限被拒绝,或者mode为F_OK而文件不存在,或者发生了其他错误),则返回-1,并适当设置errno。
char *pathname 需要检测的文件路径名
int mode 需要测试的操作模式
1. R_OK 测试读许可权
2. W_OK 测试写许可权
3. X_OK 测试执行许可权
4. F_OK 测试文件是否存在
/*函数说明:判断参数中的文件名是否存在*/
3.2. 字符串输入函数fgets()原型和头文件:
/*
Linux下 man fgets查看手册
*/
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
char * 函数返回值,fgets()成功时返回s(数组首地址),错误时返回NULL,或者在没有读取任何字符的情况下出现文件结束。
char *s 它是一个指向字符数组的指针,用于存储读取的文本行。
int size 表示要读取的最大字符数(包括空字符)
FILE *stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取
/*函数说明:
虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句 话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()
*/
3.3. 字符串分割函数strtok()原型和头文件:
/*
Linux下 man strtok查看手册
*/
#include <string.h>
char *strtok(char *str, const char *delim);
char * 会返回一个指向被分割出的第一个子字符串(即第一个空格之前的部分)的指针。如果没有更多的分割部分,则返回 NULL。
char *str 指向要被分割的字符串的指针
最初调用时,为需要分割的字符串。后续调用时,为 NULL 以获取下一部分标记。
char *delim 指定用来分割字符串的分隔符,比如说空格" "
一个包含分隔符的字符串,这些字符用于分割字符串中的标记。
/*函数说明:strtok 函数用于将字符串 str 分割成一系列标记 (token),这些标记由参数 delim 中的字符分隔开来。*/
使用方式:
strtok 首次调用时传入需要分割的字符串,并传入分隔符字符串。后续调用时,将 str 参数传递为 NULL,以继续检索同一字符串中的下一部分内容
char str[] = "hello world";
char delim[] = " ";
char *token = strtok(str, delim);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, delim);
}
3.4. 当前工作目录更改为指定的工作目录函数chdir()原型和头文件:
/*
Linux下 man 2 chdir查看手册
*/
#include <unistd.h>
int chdir(const char *path);
int 函数返回值,通常,当成功时返回 0,失败时返回 -1,并设置 errno 来指示出错原因。
char *path 参数是一个字符串指针 path,表示要改变到的目录路径。该路径可以是绝对路径或相对路径。
/*函数说明:
是一个标准库函数,它的主要功能是改变当前工作目录。它接收一个路径参数并尝试将当前工作目录更改为指定的路径,在成功时返回 0,失败时返回 -1 并设置错误代码。这个功能对于实现像 FTP 服务器这样的程序非常重要,因为它允许服务器根据客户端请求动态更改当前工作目录。
*/
4. FTP项目实现
- 客户端
#include <stdio.h> // 包含标准输入输出头文件
#include <stdlib.h> // 包含标准库函数头文件
#include <sys/types.h> // 包含数据类型头文件
#include <sys/socket.h> // 包含Socket通信头文件
#include <netinet/in.h> // 包含Internet地址族头文件
#include <arpa/inet.h> // 包含IP地址转换功能头文件
#include <string.h> // 包含字符串操作函数头文件
#include <unistd.h> // 包含unistd.h头文件
#include <sys/stat.h> // 包含文件状态头文件
#include <fcntl.h> // 包含文件控制头文件
#define LS 0 // 定义一个宏 LS,其值为 0。
#define PWD 1 // 定义一个宏 PWD,其值为 1。
#define GET 2 // 定义一个宏 GET,其值为 2。
#define IFGO 3 // 定义一个宏 IFGO,其值为 3。
#define CD 4 // 定义一个宏 CD,其值为 4。
#define PUT 5 // 定义一个宏 PUT,其值为 5。
#define LLS 6 // 定义一个宏 LLS,其值为 6。
#define LCD 7 // 定义一个宏 LCD,其值为 7。
#define LPWD 8 // 定义一个宏 LPWD,其值为 8。
#define QUIT 9 // 定义一个宏 QUIT,其值为 9。
#define DOFILE 10 // 定义一个宏 DOFILE,其值为 10。
struct MSG{
// 定义了一个名为MSG的结构体,其中包含三个成员:
int type; // 一个整型变量,用于表示消息类型。
char cmd[1024]; // 一个长度为 1024 的字符数组,用于存储命令数据。
char secondBuf[1024]; // 一个长度为 1024 的字符数组,用于存储额外的数据。
};
char* get_cmd_dir(char *cmd) { // 从命令中提取目录名的函数
char *p = NULL;
p = strtok(cmd, " "); // 分割命令字符串
/*使用 strtok 函数分割 cmd 字符串,以空格 " " 作为分隔符。
strtok 函数的第一个调用将返回字符串中第一个分隔符之前的部分,
之后每次调用 strtok(带有 NULL 作为第一个参数)将返回下一个分割后的子串,直到字符串尾部。
*/。‘
p = strtok(NULL, " "); // 获取目录名
return p; // 返回目录名
}
int cmd_type(char *cmd) { // 判断命令类型的函数
// ‘LS‘命令
if(!strcmp("ls", cmd)) return LS; // 如果相等 == 0,就返回1(!=) [ls] 因为 strcmp函数,两者作比较 相等返回0 ,不相等返回 -1或 整数
// 条件为真;返回 LS
// 比较 'pwd' 命令
if(!strcmp("pwd", cmd)) return PWD;
// 比较 'quit' 命令
if(!strcmp("quit", cmd)) return QUIT; //用于结束客户端与服务器之间的会话
// 查找 'CD'命令
if(strstr(cmd, "cd")) return CD; // strstr 查找函数 如果 cmd 中包含cd字符,就返回 CD命令
// 查找 'get' 命令
if(strstr(cmd, "get")) return GET; // strstr 查找函数 如果 cmd 中包含get字符,就返回get命令;从服务器获取文件并将其传输到客户端
// 查找 'put' 命令
if(strstr(cmd, "put")) return PUT; // strstr 查找函数 如果 cmd 中包含put字符,就返回put命令;从客户端传输到服务器
return -1; // 未知命令;返回-1
}
int msg_handler(int c_fd,struct MSG msg)
{
int ret; // 命令类型
int fdfile;
int n_fread;
int n_write;
// 文件描述符
char *dir = NULL; // 目录名
char *file = NULL; // 文件名
char dataBuf[1024] = {0}; // 数据缓冲区
printf("客户端发送消息:%s\n", msg.cmd); // 打印客户端消息
ret = cmd_type(msg.cmd); // 将命令类型转为整型
switch(ret) {
// 根据命令类型处理
case LS:
case PWD:
msg.type = 0; // 设置消息类型为 0,表示成功
FILE *fp = popen(msg.cmd, "r"); // 打开命令管道
/*FILE* 作为指针类型,用于操作文件流。通过这个指针,
你可以使用标准 I/O 函数(如 fopen、fclose、fread、fwrite、fprintf、fscanf 等)来执行文件操作。*/
n_fread = fread(msg.cmd, sizeof(msg.cmd), 1, fp); // 读取命令输出
if(n_fread == -1){
printf("读取失败\n");
}
n_write = write(c_fd, &msg, sizeof(msg)); // 发送命令执行结果
if(n_write == -1){
printf("发送失败\n");
}
pclose(fp); // 关闭命令管道
break;
case CD:
msg.type = 1; // 设置消息类型为 1
dir = get_cmd_dir(msg.cmd); // 获取目录名
if (chdir(dir) != 0) { // 切换到的目标目录的路径。
printf("切换目录失败\n");
perror("chdir failed:");
}
break;
case GET:
file = get_cmd_dir(msg.cmd); // 获取文件名
if(access(file, F_OK) == -1) { // 检查文件是否存在
//access 是一个 POSIX 标准的 C 函数,用于检查调用进程是否可以访问指定的文件。
int access(const char *pathname, int mode);
/*:
pathname: 指向包含文件路径的字符串。
mode: 指定访问方式,可以是以下标志的组合:
F_OK:仅仅检查文件是否存在。
R_OK:检查文件是否可读。
W_OK:检查文件是否可写。
X_OK:检查文件是否可执行。
返回值:
如果函数成功,并且指定的文件可以被访问,则返回 0。
如果函数成功,但文件不可访问,则返回 -1,并设置 errno 以指示错误类型
*/
strcpy(msg.cmd, "文件不存在!"); // 设置错误信息 strcpy拷贝函数
n_write = write(c_fd, &msg, sizeof(msg)); // 发送错误信息
if(n_write == -1){
printf("get发送失败\n");
}
} else {
msg.type = DOFILE; // 设置消息类型为 DOFILE == 10
fdfile = open(file, O_RDWR); // 打开文件
n_fread = read(fdfile, dataBuf, sizeof(dataBuf)); // 读取文件内容
if(n_fread == -1){
printf("get读取失败\n");
}
close(fdfile); // 关闭文件
strcpy(msg.secondBuf, dataBuf); // 将内容存入消息结构体
n_write = write(c_fd, &msg, sizeof(msg)); // 发送文件内容
if(n_write == -1){
printf("get发送失败\n");
}
}
break;
case PUT:
fdfile = open(get_cmd_dir(msg.cmd), O_RDWR | O_CREAT, 0666); // 创建并打开文件
n_write = write(fdfile, msg.secondBuf, sizeof(msg.secondBuf)); // 将内容写入新文件
if(n_write == -1){
printf("put写入失败\n");
}
close(fdfile); // 关闭文件
break;
case QUIT:
printf("客户端断开连接!\n"); // 打印断开连接消息
exit(-1); // 退出程序
}
}
int main(int argc ,char **argv)
{
int c_fd;
int s_fd;
int n_read; // 读取字节数
struct MSG msg; // 消息结构体
pid_t pid; // 子进程 ID
struct sockaddr_in s_addr; // 服务器地址结构体
struct sockaddr_in c_addr; // 客户端地址结构体
memset(&s_addr, 0, sizeof(s_addr)); // 清零服务器地址
memset(&c_addr, 0, sizeof(c_addr)); // 清零客户端地址
//检测ip+ 端口号
if(argc != 3) { // 检查参数个数
printf("参数错误!请按照格式输入:./serverFTP IP地址 端口号");
exit(-1); // 参数错误,程序退出
}
// 创建套接字
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1) {
printf("创建socket失败!");
exit(-1);
}
//设置IP
s_addr.sin_family = AF_INET; // 设置地址族为 AF_INET(IPv4)
s_addr.sin_port = htons(atoi(argv[2])); // 设置端口号 将端口号转换成网络字节序
inet_aton(argv[1], &s_addr.sin_addr); // 设置 IP 地址 IP地址转换成网络格式函数
// 绑定ip
int ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)); // 绑定套接字
if(ret == -1) {
printf("绑定socket失败!");
exit(-1);
}
// 开始监听连接
ret = listen(s_fd, 10);
if(ret == -1) {
printf("监听失败!\n");
}
printf("服务器启动成功!\n");
// 客户端地址长度
int c_addr_len = sizeof(c_addr);
while(1) {
// 接受连接请求
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_addr_len);
if(c_fd == -1) {
printf("接受客户端连接请求失败!");
exit(-1);
}
printf("客户端连接成功!\n");
printf("客户端地址:%s:%d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port)); // 打印客户端地址 网络格式的IP地址转换成字符串 将网络格式的端口号转换成字符串
pid = fork(); // 创建子进程
if(pid == -1) {
printf("创建子进程失败!");
exit(-1);
} else if(pid == 0) { // 子进程
while(1) {
memset(&msg, 0, sizeof(msg)); // 清零消息结构体
n_read = read(c_fd, &msg, sizeof(msg)); // 读取客户端消息
if(n_read == 0) {
printf("客户端断开连接!\n");
exit(0); // 退出子进程
} else if(n_read > 0) {
printf("客户端发送消息:%s\n", msg.cmd); // 打印客户端消息
msg_handler(c_fd, msg);// 处理客户端消息
}
}
}
}
return 0;
}
- 代码分析
-
- 头文件包含部分
这些头文件提供了网络通信、文件操作、字符串操作等功能所需的函数和类型定义。
-
- 宏定义部分
定义了一系列宏,用于表示不同的命令类型,如 LS
、PWD
、GET
、PUT
等。
-
struct MSG
定义
定义了一个结构体 MSG
,用于存储消息类型、命令字符串和额外的数据。
-
get_cmd_dir
函数
从命令字符串中提取文件路径。
-
cmd_type
函数
根据输入的命令字符串判断命令类型。
-
msg_handler
函数
根据命令类型执行相应的操作,如列出目录内容、获取文件、切换目录等。
-
- 主函数
main
- 主函数
- 参数检查:检查命令行参数是否正确。
- 创建套接字:创建一个 TCP 服务器端套接字。
- 绑定套接字:将套接字绑定到服务器的 IP 地址和端口号。
- 监听连接:监听客户端的连接请求。
- 接受连接:接受客户端的连接请求,并创建子进程处理每个连接。
- 读取和处理消息:子进程循环读取客户端发送的消息,并调用
msg_handler
函数进行处理。
- 实现了一个简单的 FTP 服务器,主要功能包括:
-
- 命令处理:
-
-
- 支持基本的 FTP 命令,如
ls
(列出目录)、pwd
(当前工作目录)、cd
(改变目录)、get
(下载文件)和put
(上传文件)。
- 支持基本的 FTP 命令,如
-
-
- 客户端-服务器通信:
-
-
- 通过 TCP 套接字与客户端建立连接,接受客户端发送的命令,并根据命令执行相应的操作。
-
-
- 多进程处理:
-
-
- 使用
fork
创建子进程,以处理多个客户端连接,实现并发。
- 使用
-
-
- 错误处理:
-
-
- 包括命令检查、文件存在性验证和基本的错误反馈。
-
-
- 工作流程
-
-
- 服务器启动并监听指定的 IP 和端口。
- 接受客户端连接请求。
- 在子进程中循环读取客户端发送的消息,识别命令类型,并调用相应的处理函数。
- 根据命令执行操作并返回结果或反馈信息。
-
- 客户端
#include <stdio.h> // 包含标准输入输出头文件
#include <stdlib.h> // 包含标准库函数头文件
#include <sys/types.h> // 包含数据类型头文件
#include <sys/socket.h> // 包含Socket通信头文件
#include <netinet/in.h> // 包含Internet地址族头文件
#include <arpa/inet.h> // 包含IP地址转换功能头文件
#include <string.h> // 包含字符串操作函数头文件
#include <unistd.h> // 包含unistd.h头文件
#include <sys/stat.h> // 包含文件状态头文件
#include <fcntl.h> // 包含文件控制头文件
#define LS 0 // 定义一个宏 LS,其值为 0。
#define PWD 1 // 定义一个宏 PWD,其值为 1。
#define GET 2 // 定义一个宏 GET,其值为 2。
#define IFGO 3 // 定义一个宏 IFGO,其值为 3。
#define CD 4 // 定义一个宏 CD,其值为 4。
#define PUT 5 // 定义一个宏 PUT,其值为 5。
#define LLS 6 // 定义一个宏 LLS,其值为 6。
#define LCD 7 // 定义一个宏 LCD,其值为 7。
#define LPWD 8 // 定义一个宏 LPWD,其值为 8。
#define QUIT 9 // 定义一个宏 QUIT,其值为 9。
#define DOFILE 10 // 定义一个宏 DOFILE,其值为 10。
struct MSG{
// 定义了一个名为MSG的结构体,其中包含三个成员:
int type; // 一个整型变量,用于表示消息类型。
char cmd[1024]; // 一个长度为 1024 的字符数组,用于存储命令数据。
char secondBuf[1024]; // 一个长度为 1024 的字符数组,用于存储额外的数据。
};
char* getDir(char *cmd) //获取文件路径函数
{
char *p = NULL; //文件路径指针
p = strtok(cmd, " "); //分割命令字
p = strtok(NULL," "); //获取文件路径
return p;
}
int cmd_type(char *cmd) //命令类型判断函数
{
if(!strcmp("ls", cmd)) return LS; //ls命令
if(!strcmp("pwd", cmd)) return PWD; //pwd命令
if(!strcmp("quit",cmd)) return QUIT; //quit命令
if(strstr(cmd,"cd")) return CD; //cd命令
if(strstr(cmd,"get")) return GET; //get命令
if(strstr(cmd,"put")) return PUT; //put命令
return -1; //未知命令
}
int cmd_handler(int c_fd, struct MSG msg) //命令处理函数
{
int ret; //返回值
char *p = NULL; //命令字指针
char buf[128] = {0}; //缓冲区
int file_fd; //文件描述符
char *dir = NULL;
char* getDir(char *cmd); //文件路径指针
ret = cmd_type(msg.cmd); //获取命令类型
switch(ret){
case LS:
case PWD:
msg.type = 0; //设置消息类型为0
write(c_fd, &msg, sizeof(msg)); //发送消息
break;
case CD:
msg.type = 1; //设置消息类型为1
write(c_fd, &msg, sizeof(msg)); //发送消息
break;
case GET:
msg.type = 2; //设置消息类型为2
write(c_fd, &msg, sizeof(msg)); //发送消息
break;
case PUT:
strcpy(buf, msg.cmd); //拷贝将 msg.cmd 指向的命令到 buf 指向的缓冲区中、
// 假设 msg.cmd 包含一个命令,例如 "get example" // 将命令复制到 buf 中
// 获取当前目录example 之后返回给p
p = getDir(buf);
//获取文件路径
if(access(p, F_OK) == -1){ //判断文件是否存在
printf("文件不存在\n");
}else{
file_fd = open(p, O_RDWR); //可读可写方式打开文件
read(file_fd, &msg.secondBuf, sizeof(msg.secondBuf)); //读文件内容到消息结构体的第二个缓冲区
close(file_fd); //关闭文件
write(c_fd, &msg, sizeof(msg)); //发送消息
}
break;
case LCD:
dir = getDir(msg.cmd); //获取文件路径
if(chdir(dir) == -1){ //切换目录
printf("目录不存在\n");
}
break;
case LLS:
system("ls"); //执行系统命令
break;
case LPWD:
system("pwd"); //执行系统命令
break;
case QUIT:
strcpy(msg.cmd, "quit"); //拷贝命令字到消息结构体
write(c_fd, &msg, sizeof(msg)); //发送消息
close(c_fd); //关闭套接字
exit(-1); //退出程序
break;
}
return ret; //返回命令类型
}
void handler_sever_message(int c_fd, struct MSG msg) //处理服务器消息函数
{
int n_read; //读取字节数
struct MSG getmsg; //接收消息结构体
char *filename; //文件名指针
int newfile_fd; //新文件描述符
n_read = read(c_fd, &getmsg, sizeof(getmsg)); //接收消息
if(n_read == 0){
printf("服务器关闭连接\n");
exit(-1);
}
if(getmsg.type == DOFILE){ //当客户端输入的指令是GET时,需要在客户端创建需要获取的文件
filename = getDir(msg.cmd); //获取文件名
newfile_fd = open(filename, O_RDWR|O_CREAT, 0666); //创建新文件
write(newfile_fd, getmsg.secondBuf, sizeof(getmsg.secondBuf)); //写入文件内容
close(newfile_fd); //关闭文件
printf("文件%s上传成功\n", filename); //打印提示信息
fflush(stdout); //刷新标准输出缓冲区
}else{
printf("------------------------------\n");
printf("服务器消息: %s\n", getmsg.cmd); //打印服务器消息
printf("------------------------------\n");
}
}
int main(int argc, char **argv)
{
int c_fd; //客户端套接字文件描述符
int ret; //返回值
struct sockaddr_in c_addr; //客户端地址结构体
struct MSG msg; //消息结构体
memset(&c_addr, 0, sizeof(c_addr)); //清零客户端地址结构体
if(argc != 3){ //判断参数是否正确
printf("参数错误,请按照格式输入: clientFTP IP 端口号\n");
exit(-1);
}
//int socket(int domain, int type, int protocol);
c_fd = socket(AF_INET, SOCK_STREAM, 0); //创建客户端套接字
if(c_fd == -1){
printf("创建客户端套接字失败\n");
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET; //设置客户端地址结构体的地址族为IPv4
c_addr.sin_port = htons(atoi(argv[2])); //设置客户端地址结构体的端口号
inet_aton(argv[1], &c_addr.sin_addr); //设置客户端IP地址
//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(c_addr)); //连接服务器
if(ret == -1){
printf("连接服务器失败\n");
perror("connect");
exit(-1);
}
printf("连接服务器成功\n");
while(1){
memset(&msg.cmd, 0, sizeof(msg.cmd)); //清零消息结构体的命令字
printf("请输入命令字: \n");
// fgets(msg.cmd);
fgets(msg.cmd, sizeof(msg.cmd), stdin); //从标准输入获取命令字
/*从用户输入中读取一行文本,最多读取 msg.cmd 的大小减去 1 个字符(以留出结束符),
并在末尾自动加上一个 \0 字符来结束字符串。*/
msg.cmd[strcspn(msg.cmd, "\n")] = 0; // 去掉换行符
printf("------------------------------\n");
printf("客户端消息: %s\n", msg.cmd); //打印客户端消息
printf("------------------------------\n");
ret = cmd_handler(c_fd, msg); //处理命令字
if(ret > IFGO){
fflush(stdout); //刷新标准输出缓冲区
continue; //继续循环
}
if(ret == -1){
printf("未知命令类型\n");
fflush(stdout); //刷新标准输出缓冲区
continue; //继续循环
}
handler_sever_message(c_fd, msg); //处理服务器消息
}
return 0;
}
- 代码分析:
-
- 头文件包含部分
这些头文件提供了网络通信、文件操作、字符串操作等功能所需的函数和类型定义。
-
- 宏定义部分
定义了一系列宏,用于表示不同的命令类型,如 LS
、PWD
、GET
、PUT
等。
-
struct MSG
定义
定义了一个结构体 MSG
,用于存储消息类型、命令字符串和额外的数据。
-
getDir
函数
从命令字符串中提取文件路径。
-
cmd_type
函数
根据输入的命令字符串判断命令类型。
-
cmd_handler
函数
根据命令类型执行相应的操作,如列出目录内容、获取文件、切换目录等。
-
handler_sever_message
函数
处理从服务器接收到的消息,如保存文件内容。
- 主函数
main
-
- 参数检查:检查命令行参数是否正确。
- 创建套接字:创建一个 TCP 客户端套接字。
- 连接服务器:使用服务器的 IP 地址和端口号连接到服务器。
- 命令循环:循环读取用户输入的命令,调用
cmd_handler
函数处理命令,然后调用handler_sever_message
函数处理服务器的响应。
- 实现了一个简单的 FTP 客户端,主要功能包括:
-
- 命令输入与处理:
-
-
- 从用户获取输入命令,如
ls
、pwd
、cd
、get
、put
等,判断命令类型并进行相应处理。
- 从用户获取输入命令,如
-
-
- 与服务器通信:
-
-
- 通过 TCP 套接字连接到服务器,发送命令并接收响应。
-
-
- 文件操作:
-
-
- 支持上传和下载文件,处理文件路径和文件描述符。
-
-
- 结构体定义:
-
-
struct MSG
:用于存储消息类型、命令和额外数据的结构体。
-
-
- 命令类型判断:
-
-
cmd_type
函数根据输入命令返回相应的命令类型。
-
-
- 命令处理:
-
-
cmd_handler
函数根据命令类型执行不同操作,比如改变目录、获取文件等。
-
-
- 服务器消息处理:
-
-
handler_sever_message
函数处理从服务器接收到的消息,执行相应的操作,如创建新文件。
-
-
- 主函数:
-
-
- 创建套接字,连接服务器,并进入命令输入循环,不断获取用户输入并处理。
-
4.1. 代码功能
- FTP 服务器代码:
-
- 实现一个基础的 FTP 服务器,支持命令如
ls
(列出目录)、pwd
(当前工作目录)、cd
(切换目录)、get
(下载文件)和put
(上传文件)。 - 处理客户端的请求,使用
fork
处理多个客户端连接。 - 通过 TCP 套接字与客户端进行通信,接收和响应命令。
- 实现一个基础的 FTP 服务器,支持命令如
- FTP 客户端代码:
-
- 实现一个基础的 FTP 客户端,能够连接到 FTP 服务器并发送命令。
- 支持输入命令并与服务器交互,处理文件的上传和下载。
- 接收服务器返回的消息并执行相应操作。
4.2. 运行思路
- 服务器:
-
- 启动时创建套接字,绑定到指定的 IP 和端口,开始监听客户端连接。
- 接受客户端连接请求,创建子进程处理各个客户端的命令。
- 解析命令并执行相应操作,反馈结果给客户端。
- 客户端:
-
- 启动时创建套接字,连接到指定的服务器 IP 和端口。
- 进入命令输入循环,从用户输入命令,发送给服务器并等待响应。
- 根据服务器返回的消息,处理文件的创建、下载或其他操作。
点个赞吧!
标签:FTP,文件,int,网盘,cmd,char,msg,客户端 From: https://blog.csdn.net/2301_79405674/article/details/143369559