项目——基于C/S架构的文件传输系统平台 (1)——重构

前言:

我现在也感觉自己学的差不多了,现在来复习之前自己写的文件传输系统,唉,感觉,从正式学编程到现在已经差不多一年多了,感觉自己学了好多,学了忘,忘了学。

我对这个项目的定义是:它的功能很玩具 但是它的可优化点很高 似乎基于c/s架构的东西优化点就非常多 所以实现它就似乎是在给洋娃娃套上机甲一样 对于现在的我来说是这样的。

以前学习的时候感觉让我写一个这玩意完全没有一点思路,老感觉很吃力,这也不是什么比较开源的项目,像学长说的,就是烂大街的项目,我也想试试工业级的开源项目,可是,对于码龄这么小的我感觉好难,很多东西也都还不理解,所以,我想先找一份实习,实习过后试一下开源项目,那是我觉得就应该差不都了,我当时问过学长,什么时候才能把自己的项目放到简历上,他们建议我能手搓出来,且各项功能都还能完善,所以现在,我决定重新稳固一下这个项目,尽可能优化一下,起码对我而言不那么玩具,感觉还是有点东西在里面的

也还是,只有做出来才有答案

bash 复制代码
file_manager/
├── client/    # 客户端代码
├── server/    # 服务器代码
├── common/    # 公共代码(数据库操作)
└── Makefile   # 编译脚本

数据库操作(common/mysql_con.h)

cpp 复制代码
#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)

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,失败返回非 0

mysql_store_result():将查询结果全部读取到客户端内存中

mysql_num_rows(r):返回结果集中的行数

mysql_fetch_row(r):获取结果集中的下一行,返回一个字符串数组
重要:所有mysql_store_result()获取的结果集,使用完后必须调用mysql_free_result()释放,否则会造成内存泄漏

客户端头文件(client/client.h)

cpp 复制代码
#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 {
    USEREXIT=0,  // 退出
    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:
 //wai'bu'diao用接口
    client();           
    client(string ips, short port); 
    ~client();          
    bool connect_ser();  // 连接服务器
    void run();          // 客户端主循环
};

#endif  

4.4 客户端实现(client/client.cpp)

先写前半部分(构造函数、连接服务器、菜单打印)

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)

cpp 复制代码
#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 {
    USEREXIT=0, 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. 客户端显示成功/失败

相关推荐
福大大架构师每日一题2 小时前
ollama v0.24.0 更新:Codex App 正式接入、内置浏览器、评审模式与 MLX 采样器重构,带来哪些变化?
重构·golang
wuxinyan1233 小时前
工业级大模型学习之路015:RAG零基础入门教程(第十一篇):系统重构与代码规范化
人工智能·python·学习·重构·rag
元宵大师3 小时前
[升级V2.1.5]回测模块重构:参数确认+异步进度+日志持久化!本地Web版多因子轮动系统
前端·重构
小程故事多_803 小时前
AI重构DevOps,智能增强而非替代,人始终是最终决策者
人工智能·重构·devops
智流学社4 小时前
AI 重构产研线:我怎么把角色交接的 40% 信息损耗压到0
人工智能·深度学习·自然语言处理·重构
曦夜日长5 小时前
Linux系统篇,开发工具(三):文件翻译的思路重构、库的深入理解、文件链接时区别与细节
linux·数据库·重构
小尘要自信5 小时前
见证范式跃迁:在高德开放平台AI发布会,看空间智能如何重构产业基座
人工智能·重构
Black_mario6 小时前
Berachain 上的 BrownFi ,如何通过 v3 重构链上做市逻辑?
重构
带娃的IT创业者6 小时前
Rewrite Bun in Rust:一次前端工具链的底层重构实践入门指南
前端·重构·rust·bun·运行时·前端工具链