前言我现在也感觉自己学的差不多了现在来复习之前自己写的文件传输系统唉感觉从正式学编程到现在已经差不多一年多了感觉自己学了好多学了忘忘了学。我对这个项目的定义是它的功能很玩具 但是它的可优化点很高 似乎基于c/s架构的东西优化点就非常多 所以实现它就似乎是在给洋娃娃套上机甲一样 对于现在的我来说是这样的。以前学习的时候感觉让我写一个这玩意完全没有一点思路老感觉很吃力这也不是什么比较开源的项目像学长说的就是烂大街的项目我也想试试工业级的开源项目可是对于码龄这么小的我感觉好难很多东西也都还不理解所以我想先找一份实习实习过后试一下开源项目那是我觉得就应该差不都了我当时问过学长什么时候才能把自己的项目放到简历上他们建议我能手搓出来且各项功能都还能完善所以现在我决定重新稳固一下这个项目尽可能优化一下起码对我而言不那么玩具感觉还是有点东西在里面的也还是只有做出来才有答案file_manager/ ├── client/ # 客户端代码 ├── server/ # 服务器代码 ├── common/ # 公共代码数据库操作 └── Makefile # 编译脚本数据库操作common/mysql_con.h#ifndef MYSQL_CON_H #define MYSQL_CON_H #include iostream #include string #include mysql/mysql.h using namespace std; class mysqlclient { private: string dbusername; string dbpasswd; string dbip; string dbname; short dbport; MYSQL mysql_con; public: // 初始化数据库连接信息 mysqlclient(); // 关闭数据库连接 ~mysqlclient(); bool connectserver(); bool db_register(const string usertel, const string username, const string passwd); bool db_login(const string usertel, string username, const string passwd); }; #endif数据库操作实现common/mysql_con.cpp#include mysql_con.h mysqlclient::mysqlclient() { dbusername root; dbpasswd 123456; dbip 127.0.0.1; dbname project_db; dbport 3306; } mysqlclient::~mysqlclient() { mysql_close(mysql_con); } bool mysqlclient::connectserver() { if (mysql_init(mysql_con) NULL) { cout MySQL初始化失败 endl; return false; } // 连接到MySQL服务器 if (mysql_real_connect(mysql_con, dbip.c_str(), dbusername.c_str(), dbpasswd.c_str(), dbname.c_str(), dbport, NULL, 0) NULL) { cout 数据库连接失败 mysql_error(mysql_con) endl; return false; } return true; } // 用户注册 bool mysqlclient::db_register(const string usertel, const string username, const string passwd) { if (usertel.empty() || username.empty() || passwd.empty()) { return false; } //初始化sql语句 string sql insert into User_Info values(0, usertel , username , passwd ,1,now()); // 执行SQL语句进行插入 if (mysql_query(mysql_con, sql.c_str()) ! 0) { cout 注册失败 mysql_error(mysql_con) endl; return false; } return true; } //登录 bool mysqlclient::db_login(const string usertel, string username, const string passwd) { string sql select name,passwd from User_Info where tel usertel; if (mysql_query(mysql_con, sql.c_str()) ! 0) { cout 查询失败 mysql_error(mysql_con) endl; return false; } // 获得结果集 MYSQL_RES* r mysql_store_result(mysql_con); if (r NULL) { cout 获取结果集失败 endl; return false; } //检查是否有数据 if (mysql_num_rows(r) 0) { cout 手机号不存在 endl; mysql_free_result(r); // 释放结果集防止内存泄漏 return false; } // 获取结果集中的第一行数据 MYSQL_ROW row mysql_fetch_row(r); string db_passwd row[1]; // 数据库中存储的密码 // 验证密码是否正确 if (passwd ! db_passwd) { cout 密码错误 endl; mysql_free_result(r); return false; } // 密码正确返回用户名 username row[0]; mysql_free_result(r); // 一定要释放结果集 return true; }mysql_query()执行 SQL 语句的核心函数成功返回 0失败返回非 0mysql_store_result()将查询结果全部读取到客户端内存中mysql_num_rows(r)返回结果集中的行数mysql_fetch_row(r)获取结果集中的下一行返回一个字符串数组重要所有mysql_store_result()获取的结果集使用完后必须调用mysql_free_result()释放否则会造成内存泄漏客户端头文件client/client.h#ifndef CLIENT_H #define CLIENT_H #include iostream #include stdlib.h #include unistd.h #include string.h #include string #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include jsoncpp/json/json.h #include fcntl.h using namespace std; // 枚举定义所有用户操作的编号 // 客户端和服务器必须使用完全相同的枚举值否则无法通信 enum OPS_TYPE { USEREXIT0, // 退出 REGISTER, // 注册 LOGIN, // 登录 SHOWFILES, // 查看文件列表 GET, // 下载文件 POST, // 上传文件 MKDIR, // 新建目录 RMFILE, // 删除文件 MVNAME, // 重命名 CHDIR, // 进入目录 RET, // 返回上级目录 MVFILE // 拷贝文件 }; // 客户端类 class client { private: void print(); // 菜单栏 void Register(); // 注册 void Login(); // 登录 void showfiles(); // 查看文件列表 void get_file(); // 下载文件 void Mkdir(); // 新建目录 void Rmfile(); // 删除文件 void Rename(); //重命名 void Chdir(); // 进入目录 void Ret(); // 返回上级目录 private: // 客户端状态信息 string ip; // IP地址 short port; // 服务器端口号 int sockfd; // 套接字 bool flg; // 登录状态true已登录false未登录 int op; // 操作编号 string usertel; // 用户手机号 string username; // 用户名 public: //waibudiao用接口 client(); client(string ips, short port); ~client(); bool connect_ser(); // 连接服务器 void run(); // 客户端主循环 }; #endif4.4 客户端实现client/client.cpp先写前半部分构造函数、连接服务器、菜单打印#include client.h client::client() { ip 127.0.0.1; // 本地 port 6000; // 端口 sockfd -1; flg false; // 未登录 op -1; // 编号为-1 } // 带参数的构造函数指定服务器地址和端口 client::client(string ips, short port) { ip ips; this-port port; sockfd -1; flg false; op -1; } // 析构函数关闭套接字 client::~client() { if (sockfd ! -1) { close(sockfd); } } bool client::connect_ser() { sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(socket create failed); //错误输出错误信息 return false; } // 填充服务器地址结构 struct sockaddr_in saddr; memset(saddr, 0, sizeof(saddr)); saddr.sin_family AF_INET; //采用ipv4协议 saddr.sin_port htons(port); // 主机字节序转网络字节序 // IP地址点分十进制字符串转网络字节序整数 saddr.sin_addr.s_addr inet_addr(ip.c_str()); int res connect(sockfd, (struct sockaddr*)saddr, sizeof(saddr)); if (res -1) { perror(connect failed); close(sockfd); // 连接失败关闭套接字 sockfd -1; return false; } cout 连接服务器成功 endl; return true; } void client::print() { if (!flg) { cout \n 欢迎使用文件传输系统 endl; cout 1. 注册 endl; cout 2. 登录 endl; cout 3. 退出 endl; cout 请输入选择; while (!(std::cin op)) { std::cin.clear(); std::cin.ignore(10000, \n); std::cerr 输入无效请输入一个整数; } if (3 op) { op USEREXIT; } } else { cout \n 用户 username endl; cout 1. 查看服务器文件 2. 下载 3. 上传 4. 新建目录 endl; cout 5. 删除文件 6. 重命名 7. 进入目录 8. 返回上级 endl; cout 9. 拷贝文件 10. 退出 endl; cout 请输入要执行的操作编号; while (!(std::cin op)) { std::cin.clear(); std::cin.ignore(10000, \n); std::cout 输入无效请输入一个整数endl; } if (10 op) { op USEREXIT; return; } //加2进行转化因为枚举值不匹配 op 2; } }重点代码讲解socket()创建套接字返回一个文件描述符所有网络操作都通过这个描述符进行htons()将主机字节序的短整数转换为网络字节序大端序inet_addr()将点分十进制的 IP 地址字符串转换为网络字节序的整数connect()向服务器发起 TCP 连接请求输入验证非常重要如果用户输入非数字cin会进入错误状态必须用clear()和ignore()恢复4.5 服务器头文件server/server.h#ifndef SERVER_H #define SERVER_H #include iostream #include cstring #include jsoncpp/json/json.h #include event2/event.h #include unistd.h #include sys/stat.h #include dirent.h #include fcntl.h #include arpa/inet.h #include mysql/mysql.h #include ../common/mysql_con.h using namespace std; const int LIS_MAX 20; // 监听队列最大长度 // 修改成自己的实际路径 const string PATH /home/mzz/file_server/download/; enum OPS_TYPE { USEREXIT0, REGISTER, LOGIN, SHOWFILES, GET, POST, MKDIR, RMFILE, MVNAME, CHDIR, RET, MVFILE }; class Con_Client; class Server { public: Server(); Server(string ip, int port); ~Server(); bool Ser_Init(); bool Accept_Client(); void Run(); private: bool Create_Socket(); bool Libevent_Init(); private: string ip; short port; int sockfd; struct event_base* base; // Libevent事件对象 }; class Con_Client { public: Con_Client(int c, struct event_base* b); ~Con_Client(); void Set_event(struct event* e); // 设置该客户端对应的事件对象 void Recv_Data(); // 接收并处理客户端数据 private: bool is_json(const char buff[]); // 判断数据是否是JSON格式 void send_ok(); void send_err(); void do_run(int op); // 根据操作类型分发请求 void Register(); void Login(); void send_Json(Json::Value v); void showfiles(); void get_file(char* ptr); void Mkdir(); void Rmfile(); void Rename(); void Chdir(); void Ret(); private: Json::Value val; // 存储解析后的JSON数据 int c; // 客户端套接字 struct event_base* base; struct event* ev; // 该客户端对应的读事件对象 string mypath; string userpath; int fd; }; // 全局回调函数声明Libevent要求回调函数必须是全局或静态函数 extern C { void Accept_CallBack(int, short, void*); void Read_CallBack(int, short, void*); #endif行了今天就写这些我快燃尽了明后天进行下一阶段的重构系统博客对了这是系统整体流程1. 启动服务器 → 等着客户端来连接2. 启动客户端 → 连接服务器3. 客户端显示菜单4. 你输入操作注册/登录/退出5. 客户端把你的操作发给服务器6. 服务器处理查数据库/建目录/返回结果7. 服务器把结果发给客户端8. 客户端显示成功/失败
项目——基于C/S架构的文件传输系统平台 (1)——重构
发布时间:2026/5/17 7:32:26
前言我现在也感觉自己学的差不多了现在来复习之前自己写的文件传输系统唉感觉从正式学编程到现在已经差不多一年多了感觉自己学了好多学了忘忘了学。我对这个项目的定义是它的功能很玩具 但是它的可优化点很高 似乎基于c/s架构的东西优化点就非常多 所以实现它就似乎是在给洋娃娃套上机甲一样 对于现在的我来说是这样的。以前学习的时候感觉让我写一个这玩意完全没有一点思路老感觉很吃力这也不是什么比较开源的项目像学长说的就是烂大街的项目我也想试试工业级的开源项目可是对于码龄这么小的我感觉好难很多东西也都还不理解所以我想先找一份实习实习过后试一下开源项目那是我觉得就应该差不都了我当时问过学长什么时候才能把自己的项目放到简历上他们建议我能手搓出来且各项功能都还能完善所以现在我决定重新稳固一下这个项目尽可能优化一下起码对我而言不那么玩具感觉还是有点东西在里面的也还是只有做出来才有答案file_manager/ ├── client/ # 客户端代码 ├── server/ # 服务器代码 ├── common/ # 公共代码数据库操作 └── Makefile # 编译脚本数据库操作common/mysql_con.h#ifndef MYSQL_CON_H #define MYSQL_CON_H #include iostream #include string #include mysql/mysql.h using namespace std; class mysqlclient { private: string dbusername; string dbpasswd; string dbip; string dbname; short dbport; MYSQL mysql_con; public: // 初始化数据库连接信息 mysqlclient(); // 关闭数据库连接 ~mysqlclient(); bool connectserver(); bool db_register(const string usertel, const string username, const string passwd); bool db_login(const string usertel, string username, const string passwd); }; #endif数据库操作实现common/mysql_con.cpp#include mysql_con.h mysqlclient::mysqlclient() { dbusername root; dbpasswd 123456; dbip 127.0.0.1; dbname project_db; dbport 3306; } mysqlclient::~mysqlclient() { mysql_close(mysql_con); } bool mysqlclient::connectserver() { if (mysql_init(mysql_con) NULL) { cout MySQL初始化失败 endl; return false; } // 连接到MySQL服务器 if (mysql_real_connect(mysql_con, dbip.c_str(), dbusername.c_str(), dbpasswd.c_str(), dbname.c_str(), dbport, NULL, 0) NULL) { cout 数据库连接失败 mysql_error(mysql_con) endl; return false; } return true; } // 用户注册 bool mysqlclient::db_register(const string usertel, const string username, const string passwd) { if (usertel.empty() || username.empty() || passwd.empty()) { return false; } //初始化sql语句 string sql insert into User_Info values(0, usertel , username , passwd ,1,now()); // 执行SQL语句进行插入 if (mysql_query(mysql_con, sql.c_str()) ! 0) { cout 注册失败 mysql_error(mysql_con) endl; return false; } return true; } //登录 bool mysqlclient::db_login(const string usertel, string username, const string passwd) { string sql select name,passwd from User_Info where tel usertel; if (mysql_query(mysql_con, sql.c_str()) ! 0) { cout 查询失败 mysql_error(mysql_con) endl; return false; } // 获得结果集 MYSQL_RES* r mysql_store_result(mysql_con); if (r NULL) { cout 获取结果集失败 endl; return false; } //检查是否有数据 if (mysql_num_rows(r) 0) { cout 手机号不存在 endl; mysql_free_result(r); // 释放结果集防止内存泄漏 return false; } // 获取结果集中的第一行数据 MYSQL_ROW row mysql_fetch_row(r); string db_passwd row[1]; // 数据库中存储的密码 // 验证密码是否正确 if (passwd ! db_passwd) { cout 密码错误 endl; mysql_free_result(r); return false; } // 密码正确返回用户名 username row[0]; mysql_free_result(r); // 一定要释放结果集 return true; }mysql_query()执行 SQL 语句的核心函数成功返回 0失败返回非 0mysql_store_result()将查询结果全部读取到客户端内存中mysql_num_rows(r)返回结果集中的行数mysql_fetch_row(r)获取结果集中的下一行返回一个字符串数组重要所有mysql_store_result()获取的结果集使用完后必须调用mysql_free_result()释放否则会造成内存泄漏客户端头文件client/client.h#ifndef CLIENT_H #define CLIENT_H #include iostream #include stdlib.h #include unistd.h #include string.h #include string #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include jsoncpp/json/json.h #include fcntl.h using namespace std; // 枚举定义所有用户操作的编号 // 客户端和服务器必须使用完全相同的枚举值否则无法通信 enum OPS_TYPE { USEREXIT0, // 退出 REGISTER, // 注册 LOGIN, // 登录 SHOWFILES, // 查看文件列表 GET, // 下载文件 POST, // 上传文件 MKDIR, // 新建目录 RMFILE, // 删除文件 MVNAME, // 重命名 CHDIR, // 进入目录 RET, // 返回上级目录 MVFILE // 拷贝文件 }; // 客户端类 class client { private: void print(); // 菜单栏 void Register(); // 注册 void Login(); // 登录 void showfiles(); // 查看文件列表 void get_file(); // 下载文件 void Mkdir(); // 新建目录 void Rmfile(); // 删除文件 void Rename(); //重命名 void Chdir(); // 进入目录 void Ret(); // 返回上级目录 private: // 客户端状态信息 string ip; // IP地址 short port; // 服务器端口号 int sockfd; // 套接字 bool flg; // 登录状态true已登录false未登录 int op; // 操作编号 string usertel; // 用户手机号 string username; // 用户名 public: //waibudiao用接口 client(); client(string ips, short port); ~client(); bool connect_ser(); // 连接服务器 void run(); // 客户端主循环 }; #endif4.4 客户端实现client/client.cpp先写前半部分构造函数、连接服务器、菜单打印#include client.h client::client() { ip 127.0.0.1; // 本地 port 6000; // 端口 sockfd -1; flg false; // 未登录 op -1; // 编号为-1 } // 带参数的构造函数指定服务器地址和端口 client::client(string ips, short port) { ip ips; this-port port; sockfd -1; flg false; op -1; } // 析构函数关闭套接字 client::~client() { if (sockfd ! -1) { close(sockfd); } } bool client::connect_ser() { sockfd socket(AF_INET, SOCK_STREAM, 0); if (-1 sockfd) { perror(socket create failed); //错误输出错误信息 return false; } // 填充服务器地址结构 struct sockaddr_in saddr; memset(saddr, 0, sizeof(saddr)); saddr.sin_family AF_INET; //采用ipv4协议 saddr.sin_port htons(port); // 主机字节序转网络字节序 // IP地址点分十进制字符串转网络字节序整数 saddr.sin_addr.s_addr inet_addr(ip.c_str()); int res connect(sockfd, (struct sockaddr*)saddr, sizeof(saddr)); if (res -1) { perror(connect failed); close(sockfd); // 连接失败关闭套接字 sockfd -1; return false; } cout 连接服务器成功 endl; return true; } void client::print() { if (!flg) { cout \n 欢迎使用文件传输系统 endl; cout 1. 注册 endl; cout 2. 登录 endl; cout 3. 退出 endl; cout 请输入选择; while (!(std::cin op)) { std::cin.clear(); std::cin.ignore(10000, \n); std::cerr 输入无效请输入一个整数; } if (3 op) { op USEREXIT; } } else { cout \n 用户 username endl; cout 1. 查看服务器文件 2. 下载 3. 上传 4. 新建目录 endl; cout 5. 删除文件 6. 重命名 7. 进入目录 8. 返回上级 endl; cout 9. 拷贝文件 10. 退出 endl; cout 请输入要执行的操作编号; while (!(std::cin op)) { std::cin.clear(); std::cin.ignore(10000, \n); std::cout 输入无效请输入一个整数endl; } if (10 op) { op USEREXIT; return; } //加2进行转化因为枚举值不匹配 op 2; } }重点代码讲解socket()创建套接字返回一个文件描述符所有网络操作都通过这个描述符进行htons()将主机字节序的短整数转换为网络字节序大端序inet_addr()将点分十进制的 IP 地址字符串转换为网络字节序的整数connect()向服务器发起 TCP 连接请求输入验证非常重要如果用户输入非数字cin会进入错误状态必须用clear()和ignore()恢复4.5 服务器头文件server/server.h#ifndef SERVER_H #define SERVER_H #include iostream #include cstring #include jsoncpp/json/json.h #include event2/event.h #include unistd.h #include sys/stat.h #include dirent.h #include fcntl.h #include arpa/inet.h #include mysql/mysql.h #include ../common/mysql_con.h using namespace std; const int LIS_MAX 20; // 监听队列最大长度 // 修改成自己的实际路径 const string PATH /home/mzz/file_server/download/; enum OPS_TYPE { USEREXIT0, REGISTER, LOGIN, SHOWFILES, GET, POST, MKDIR, RMFILE, MVNAME, CHDIR, RET, MVFILE }; class Con_Client; class Server { public: Server(); Server(string ip, int port); ~Server(); bool Ser_Init(); bool Accept_Client(); void Run(); private: bool Create_Socket(); bool Libevent_Init(); private: string ip; short port; int sockfd; struct event_base* base; // Libevent事件对象 }; class Con_Client { public: Con_Client(int c, struct event_base* b); ~Con_Client(); void Set_event(struct event* e); // 设置该客户端对应的事件对象 void Recv_Data(); // 接收并处理客户端数据 private: bool is_json(const char buff[]); // 判断数据是否是JSON格式 void send_ok(); void send_err(); void do_run(int op); // 根据操作类型分发请求 void Register(); void Login(); void send_Json(Json::Value v); void showfiles(); void get_file(char* ptr); void Mkdir(); void Rmfile(); void Rename(); void Chdir(); void Ret(); private: Json::Value val; // 存储解析后的JSON数据 int c; // 客户端套接字 struct event_base* base; struct event* ev; // 该客户端对应的读事件对象 string mypath; string userpath; int fd; }; // 全局回调函数声明Libevent要求回调函数必须是全局或静态函数 extern C { void Accept_CallBack(int, short, void*); void Read_CallBack(int, short, void*); #endif行了今天就写这些我快燃尽了明后天进行下一阶段的重构系统博客对了这是系统整体流程1. 启动服务器 → 等着客户端来连接2. 启动客户端 → 连接服务器3. 客户端显示菜单4. 你输入操作注册/登录/退出5. 客户端把你的操作发给服务器6. 服务器处理查数据库/建目录/返回结果7. 服务器把结果发给客户端8. 客户端显示成功/失败