前言:
我现在也感觉自己学的差不多了,现在来复习之前自己写的文件传输系统,唉,感觉,从正式学编程到现在已经差不多一年多了,感觉自己学了好多,学了忘,忘了学。
我对这个项目的定义是:它的功能很玩具 但是它的可优化点很高 似乎基于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
行了,今天就写这些,我快燃尽了,明后天进行下一阶段的重构系统博客
对了,这是系统整体流程
-
启动服务器 → 等着客户端来连接
-
启动客户端 → 连接服务器
-
客户端显示菜单
-
你输入操作(注册/登录/退出)
-
客户端把你的操作发给服务器
-
服务器处理(查数据库/建目录/返回结果)
-
服务器把结果发给客户端
-
客户端显示成功/失败