预约系统版本2(基于第一版改良)

预约系统版本2

基于 C++ + epoll + pthread + MySQL 的多线程并发预约系统

包含连接池、线程池、epoll I/O 多路复用、JSON 协议、SQL 事务


一、项目概述

1.1 项目定位

一个支持多客户端同时在线的票务预约系统,客户端通过 TCP 长连接与服务端交互,服务端使用 epoll 监听 I/O 事件,并通过线程池把任务交给 worker 线程处理,worker 线程通过连接池借用 MySQL 连接执行 SQL。

1.2 核心特性

特性 实现方式
并发模型 epoll + pthread 线程池
数据库复用 Connect_Pool 连接池(5 个连接复用)
协议格式 JSON(基于 jsoncpp)
事务支持 预约/取消操作使用 MySQL 事务
容错 连接池满时返回 "MYSQL CLIENT BUSSY",客户端提示重试

1.3 业务功能

  • 用户注册(手机号 + 用户名 + 密码)
  • 用户登录
  • 查看可预约票列表
  • 预约票(带事务,防止超卖)
  • 查看我的预约
  • 取消预约(带事务,保持 count 一致)

二、项目结构

复制代码
csproject2/
├── service.h          # 服务端头文件(类声明)
├── service.cpp        # 服务端实现(主流程 + 业务逻辑)
├── client.h           # 客户端头文件
├── client.cpp         # 客户端实现
├── service.conf       # 服务端配置(IP/Port/lismax)
├── mysql.conf         # 数据库配置(IP/Port/db_name)
├── Makefile           # 构建脚本
├── ser_start.sh      # 启动脚本
├── service            # 编译产物(服务端可执行)
└── client             # 编译产物(客户端可执行)

编译 & 启动

bash 复制代码
make clean && make all
./service        # 启动服务端
./client         # 启动客户端

三、改进建议

3.1 性能改进

3.1.1 连接池加条件变量(当前最大短板)

现状Connect_Mysql_Server 借不到连接直接 return false,构造失败 → 客户端收到 BUSSY。

问题 :并发请求超过 5 个时,第 6 个直接被拒,用户体验差。

改进 :在 Connect_Poolpthread_cond_t,借不到时 pthread_cond_wait,等他人归还后唤醒。

cpp 复制代码
// 借连接
while (C_P_array[i].status == false) pthread_cond_wait(&cond, &mutex);
// 归还时
pthread_cond_signal(&cond);
3.1.2 worker 线程绑定固定连接(彻底消除锁)

现状 :5 个 worker 共享 5 个连接,借/还都要加锁。

改进 :worker 启动时绑死一个连接(线程局部存储 __threadpthread_key),完全无锁。

3.1.3 send/recv 改非阻塞 + 边沿触发

现状 :epoll 用 Level Triggered + EPOLLONESHOT,每次要重新 MOD。

改进:改 Edge Triggered + 非阻塞 fd,循环 recv 到 EAGAIN,减少 epoll_ctl 调用。

3.1.4 SQL 预处理语句

现状 :每次 mysql_query 拼字符串,服务端每次都要 parse SQL。

改进 :用 mysql_stmt_prepare + mysql_stmt_bind_param,复用执行计划。


3.2 安全改进

3.2.1 SQL 注入(必改)

现状Db_User_RegisterDb_User_Login 等直接拼字符串:

cpp 复制代码
string sql = "select Name,Passwd from user_info where Tel='" + tel + "'";

风险 :用户 tel 输入 ' OR '1'='1 直接绕过密码校验。

改进 :用 mysql_real_escape_string 转义,或改 prepared statement。

3.2.2 密码明文存储

现状Db_User_Register values(0,'tel','name','passwd',...) 直接存明文。

风险 :数据库被拖库后密码全暴露。

改进 :用 crypt()/SHA256/bcrypt 存哈希,登录时比哈希。

3.2.3 客户端不验证服务端身份

现状 :客户端直接 connect,无法识别中间人。

改进:用 TLS(OpenSSL)加密通道。


3.3 健壮性改进

3.3.1 signal 处理

现状Ctrl+Cepoll_wait 会被 EINTR 中断,但 L895-L900 直接 exit(1),不释放资源。

改进 :注册 SIGINT/SIGTERM handler,设 Status = false,让主循环优雅退出,析构 Tcp_Service → 析构 Thread_Pool → join 所有 worker。

3.3.2 客户端断连感知

现状 :客户端 Ctrl+C 退出,服务端 Do_client 收到 recv_num == 0delete this,OK。但如果客户端网络异常(没发 FIN),服务端不知道。

改进 :用 TCP keepalive(setsockopt SO_KEEPALIVE)或应用层心跳。

3.3.3 EPOLLONESHOT 处理一致

现状 :listen socket 没用 EPOLLONESHOT,client socket 用了。

风险 :多线程同时 accept 同一个 listen fd,可能竞争。

改进:listen fd 也用 EPOLLONESHOT,accept 后重新 MOD。

3.3.4 配置文件硬编码路径

现状 :<service.h#L22> #define Conf_path "/home/xiaoc/mycode/csproject2/service.conf",换机器就废。

改进:从 argv1 读,或读环境变量。

3.3.5 mysql_real_connect 密码硬编码

现状Connect_Pool::Run "root","111111" 写死在代码里。

改进 :从 mysql.conf 读用户名/密码,或用 MySQL 配置文件 [client] 段。


3.4 可维护性改进

3.4.1 头文件依赖混乱

现状 :<service.h> 把 Service_ConfigSocketAccSocketRecvSocketThread_PoolMysql_configMysql_ClientMysql_cliConnect_PoolTcp_Service 10 个类型全堆在一个文件。

改进 :拆分成 socket.hthread_pool.hmysql_pool.hmysql_client.htcp_service.hservice_config.h,每个文件单一职责。

3.4.2 全局变量

现状service.cpp#L15-L16 Mysql_config file;Connect_Pool* g_pool; 是全局变量。

问题 :难追踪修改点,难单元测试。

改进g_pool 改成 Connect_Pool::GetInstance() 单例;file 放到 Tcp_Service 里作成员。

3.4.3 裸指针 + 手动 new/delete

现状Socket* taskMysql_cli* C_P_array 等全是裸指针,靠 delete 手动管理。

风险 :异常路径漏 delete 会泄漏。

改进 :用 std::unique_ptr / std::shared_ptr,析构自动释放。

3.4.4 错误日志不分级

现状 :所有错误都 cout << 打到 stdout。

改进:引入简单日志库(如 spdlog),分 INFO/WARN/ERROR/FATAL,支持文件分卷。

3.4.5 magic number

现状 :<service.cpp#L787> "root","111111"、<client.h#L18> PORTADRESS 8888MAXLINE 256d_size 1024 等硬编码。

改进:全部移到配置文件或 const 常量。


3.5 功能扩展

3.5.1 缺少客户端超时

客户端 recv 是阻塞的,服务端挂了客户端会一直等。

改进 :用 setsockopt SO_RCVTIMEO 设 5 秒超时,超时提示。

3.5.2 缺少管理员后台

现在无法后台管理票务(增删改查票、查用户、统计)。

改进:加一个管理端协议 type=Admin_Login + type=Admin_Add_Ticket 等。

3.5.3 缺少并发数统计

无法查看当前在线用户、池使用率。

改进:服务端加一个 stats 接口(可通过 SIGUSR1 信号 dump 状态到日志)。


四、各文件详解

4.1 <service.h> --- 服务端类声明

完整代码
cpp 复制代码
#include <stdlib.h>

#include <string>
#include <cstring>


#include <netinet/in.h>
#include <arpa/inet.h>

#include <jsoncpp/json/json.h>
#include <mysql/mysql.h>

#include <sys/socket.h>
#include <sys/epoll.h>
#include <pthread.h>

#include <unistd.h>


#define MAXLINE 256
#define START -1
#define Conf_path "/home/xiaoc/mycode/csproject2/service.conf" 
#define Sql_Conf_path "/home/xiaoc/mycode/csproject2/mysql.conf"

#define d_size  1024

#define thread_num 5

#define o "ok"

class Connect_Pool;
extern Connect_Pool* g_pool;

struct Service_Config{
     const char* config_path=Conf_path;
     std::string IP;
     short Port;
     short Lismax;
     

     void ReadConfig(const char* config_path);
     void PrintConfig() const;
};




class Socket{
public:
      int fd;
      int epfd;

      Socket(int fd,int epfd);
     virtual ~Socket();
     virtual void Do_client() = 0;
};

class AccSocket : public Socket{
public:
      AccSocket(int fd,int epfd);
      ~AccSocket();
     void Do_client() override;

private:
     static unsigned int count;
};

class RecvSocket : public Socket{
public:
      RecvSocket(int fd,int epfd);
      ~RecvSocket();
      void Do_client() override;
      void send_Ok();
      void send_false();

};

typedef struct{
    Socket* task;
}task_t;

class Thread_Pool{
private:
    task_t * dequeue;
    int dequeue_size;
    int front;
    int rear;

    int task_Num;
    int thread_Num;

    bool status;

    pthread_mutex_t mutex;
    pthread_cond_t cond;

    pthread_t Thread_array[thread_num];
public:
     Thread_Pool();
     ~Thread_Pool();
     bool Init_Thread_pool();
     void add_Task(Socket* csocket);
     void Create_Thread();
     void Work();
};

void* General_Thread(void* arg);


struct Mysql_config{
     short db_port;
     std::string db_ip;
     std::string db_name;
     const char* Sql_config_path=Sql_Conf_path;

     void Init_Mysql_config(const char* Sql_config_path);
     void print_Mysql_config () const;
};

class Mysql_Client{
private:
      struct Mysql_config* file;
      MYSQL* mysql_con;//mysql的连接句柄
      int who;
      bool connected;   // 是否成功从池里借到连接
public:
     Mysql_Client(struct Mysql_config * Sql_config,int who);
     void ResetSTatus();
     ~Mysql_Client();
     bool IsConnected() const { return connected; }   // 供外部检查是否借到连接

     bool Connect_Mysql_Server();
     bool Db_User_Register(const std::string &user_tel,const std::string &user_name,const std::string &user_passwd);
     bool Db_User_Login(const std::string& tel,std::string& name,const std::string& passwd);
     bool Db_Show_Ticket(Json::Value &res);
     bool Db_Yd_Ticket(std::string usertel,std::string tk_id);
     bool Db_Show_My_Ticket(const std::string&user_tel,Json::Value &res);
     bool Db_Cancel_Ticket(const std::string &user_tel,const std::string &yd_id);

     void Send_full();
     void Mysql_Begin();
     void Mysql_RollBack();
     void Mysql_Commit();

};

//连接池中的每一个与数据库进行连接的客户端入口结构体
struct Mysql_cli{
     MYSQL* mysql_con;
     bool status;
};



class Connect_Pool{
private:
  
   bool status;
   int MAX_USER;
   Mysql_config* Sql_config;
public: 
   Mysql_cli* C_P_array;   
   int user;
   pthread_mutex_t mutex;


   Connect_Pool(Mysql_config* Sql_config); 
   ~Connect_Pool();

   bool Run();
   void Reset();
};

class Tcp_Service{
private:
     const Service_Config* config; 
     Thread_Pool* pool;

     int lis_fd = START;
     int ep_fd = START;
     bool Status;

     struct epoll_event ready_events[MAXLINE]{0};

    
public:
     Tcp_Service(const Service_Config* config);
     ~Tcp_Service();
     
     void Init(const Service_Config* config);
};
文件作用

集中声明服务端所有类型:配置类、Socket 体系、线程池、MySQL 客户端、连接池、Tcp_Service 主控。

关键类型一览
类型 行号 职责
Service_Config L34-L43 读 service.conf,存 IP/Port/Lismax
Socket(抽象基类) L48-L56 持有 fd + epfd,纯虚 Do_client()
AccSocket L58-L66 处理 listen fd 上的 accept 事件
RecvSocket L68-L76 处理 client fd 上的 recv 事件
task_t L78-L80 任务结构体(包 Socket* 指针)
Thread_Pool L82-L105 任务队列 + 5 个 worker 线程
Mysql_config L110-L118 读 mysql.conf,存 db_ip/port/name
Mysql_Client L120-L145 业务层,封装 SQL 操作
Mysql_cli L148-L151 连接池中单个 slot(MYSQL* + status)
Connect_Pool L155-L172 5 个 Mysql_cli + mutex
Tcp_Service L174-L191 主控:listen + epoll 循环
设计亮点
  • 前置声明 class Connect_Pool;(L31)让 g_pool 全局指针可用,定义放在后面
  • Socket 抽象基类 + Do_client() 纯虚函数,多态分发 accept/recv 任务
  • Mysql_Client::IsConnected()(L130)提供连接状态查询,避免外部访问私有成员
不足
  • 10 个类型挤一个文件,违反单一职责
  • Mysql_cli 是 struct 但成员公开,封装不严

4.2 <service.cpp> --- 服务端实现

完整代码
cpp 复制代码
#include "service.h"
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>
#include <mysql/mysql.h>
#include <netinet/in.h>
#include <pthread.h>
#include <string>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;

Mysql_config file;
Connect_Pool* g_pool = nullptr;

enum OP_TYPE {
  Login = 1,
  Register,
  CheckApoint,
  Appointment,
  AppoinMessage,
  CancelAppoin,
  Exit
};

void Service_Config::ReadConfig(const char *config_path) {
  if (config_path == nullptr) {
    cout << "config_path is nullptr!" << endl;
    return;
  }

  FILE *fp = fopen(config_path, "r");
  if (fp == nullptr) {
    perror("fopen config file failed");
    cout << "fopen config file failed! errno=" << errno << endl;
    return;
  }

  char line[MAXLINE]{0};
  int num = 0;

  while (fgets(line, MAXLINE - 2, fp) != nullptr) {
    num++;
    if (line[0] == '#' || line[0] == '\n') {
      continue;
    }

    char *Before = strtok(line, "=");
    char *After = strtok(nullptr, "=");

    if (Before == nullptr || After == nullptr) {
      continue;
    }

    After[strlen(After) - 1] = '\0';

    if (strcmp("ip", Before) == 0) {
      IP = After; // 禁止共享内存,line是一次性使用,After会被覆盖
    } else if (strcmp("port", Before) == 0) {
      Port = atoi(After);
    } else if (strcmp("lismax", Before) == 0) {
      Lismax = atoi(After);
    } else if (strcmp("service_conf_path", Before) == 0) {
      config_path = After;
    } else {
      cout << "unknown config item: " << Before << " at line " << num << endl;
    }
    memset(line, 0, sizeof(line));
  }
  fclose(fp);
}

void Service_Config::PrintConfig() const {
  cout << "IP: " << IP << endl;
  cout << "Port: " << Port << endl;
  cout << "Lismax: " << Lismax << endl;
  cout << "config_path: " << config_path << endl;
}

Tcp_Service::Tcp_Service(const Service_Config *config) { Init(config); }

Tcp_Service::~Tcp_Service() {
  cout << "~Tcp_Service()" << endl;
  close(lis_fd);
  close(ep_fd);
  if (pool != nullptr)
    delete pool;
}

Socket::Socket(int fd, int epfd) : fd(fd), epfd(epfd) {}

Socket::~Socket() {}

AccSocket::AccSocket(int fd, int epfd) : Socket(fd, epfd) {}

unsigned int AccSocket::count = 0;

AccSocket::~AccSocket() {}

void AccSocket::Do_client() {
  struct sockaddr_in client_addr;
  socklen_t client_addr_len = sizeof(client_addr);
  memset(&client_addr, 0, sizeof(client_addr));

  // lis_fd对accept的影响,在默认情况下,lis_fd在队列为空时,会默认让accept在获取这个空队列时阻塞,线程会直接阻塞卡死在这
  // 所以我们要用非阻塞模式的accept,当队列为空时,accept会返回EAGAIN或EWOULDBLOCK,而不是阻塞线程
  int conn_fd = accept(fd, (struct sockaddr *)&client_addr, &client_addr_len);

  if (conn_fd < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      // listen 队列空了,别的 worker 已经把连接取走了,正常退出
      delete this;
      return;
    }

    perror("accept failed");
    cout << "accept failed! errno=" << errno << endl;
    delete this;
    return;
  }

  cout << "client:" << conn_fd << " connect success!" << endl;
  cout << "Service accept client numb now: " << ++count << endl;
  cout << "-------------------------------------" << endl;

  struct epoll_event ev_client_accpet;
  ev_client_accpet.events = EPOLLIN | EPOLLONESHOT;
  auto *recvSock = new RecvSocket(conn_fd, epfd);
  ev_client_accpet.data.ptr = recvSock;

  if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev_client_accpet) == -1) {
    perror("epoll_ctl add client fd failed");
    cout << "epoll_client_ctl add false! errno=" << errno << endl;
    close(conn_fd);  // 关闭连接,不受监控的不要
    delete recvSock; // 删除对象
  }

  delete this;
}

RecvSocket::RecvSocket(int fd, int pdfd) : Socket(fd, pdfd) {}

RecvSocket::~RecvSocket() { close(fd); }

void RecvSocket::send_Ok() {
  Json::Value value;
  string a = o;
  value["status"] = a;
  send(fd, value.toStyledString().c_str(),
       strlen(value.toStyledString().c_str()), 0);
}

void RecvSocket::send_false() {
  Json::Value value;
  string a = "false";
  value["status"] = a;
  send(fd, value.toStyledString().c_str(),
       strlen(value.toStyledString().c_str()), 0);
}


void RecvSocket::Do_client() {
  char buff[MAXLINE]{0};
  ssize_t recv_num = recv(fd, buff, sizeof(buff) - 1, 0);

  bool tag = false;

  if (recv_num < 0) {
    perror("recv failed");
    cout << "[fd=" << fd << "] recv 出错 errno=" << errno << endl;
    tag = true;
  } else if (recv_num == 0) {
    cout << "[fd=" << fd << "] 客户端正常关闭连接" << endl;
    tag = true;
  } else {
    buff[recv_num] = '\0';
    cout << "[fd=" << fd << "] 收到 " << recv_num << " 字节" << endl;
    cout << "---------------------------------------------" << endl;

    Json::Value val;
    Json::Reader read;
    if (!read.parse(buff, val)) {
      cout << "[fd=" << fd << "] Json 解析失败! 收到内容: " << buff << endl;
      tag = true;
    } else {

      Mysql_Client sql(&file,fd);
      if (sql.IsConnected()) {
      int i = val["type"].asInt();
      if (i == Login) {
        string user_tel = val["user_tel"].asString();
        string passwd = val["passwd"].asString();
        string user_name = val["user_name"].asString();
        if (sql.Db_User_Login(user_tel, user_name, passwd)) {
          send_Ok();
        } else {
          send_false();
        }
      } else if (i == Register) {
        string tel = val["user_tel"].asString();
        string name = val["user_name"].asString();
        string passwd = val["passwd"].asString();
        if (sql.Db_User_Register(tel, name, passwd)) {
          send_Ok();
        } else {
          send_false();
        }

      } else if (i == CheckApoint) {
        Json::Value Reader;
        if (!sql.Db_Show_Ticket(Reader)) {
          send_false();
        } else
          send(fd, Reader.toStyledString().c_str(),
               strlen(Reader.toStyledString().c_str()), 0);
      } else if (i == Appointment) {
        string user_tel = val["user_tel"].asString();
        string ticket_id = val["ticket_id"].asString();
        if (!sql.Db_Yd_Ticket(user_tel, ticket_id)) {
          send_false();
        } else {
          send_Ok();
        }
      } else if (i == AppoinMessage) {
        string user_tel = val["user_tel"].asString();
        Json::Value res;
        if (!sql.Db_Show_My_Ticket(user_tel, res)) {
          send_false();
        } else {
          send(fd, res.toStyledString().c_str(),
               strlen(res.toStyledString().c_str()), 0);
        }
      } else if (i == CancelAppoin) {
        string user_tel = val["user_tel"].asString();
        string yd_id = val["yd_id"].asString();
        if (!sql.Db_Cancel_Ticket(user_tel, yd_id)) {
          send_false();
        } else {
          send_Ok();
        }
      } else {
        tag = true;
      }
      }
    }
  }


  if (tag) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    delete this;
  } else {
    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLONESHOT;
    ev.data.ptr = this;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
  }
}

Thread_Pool::Thread_Pool() {
  if (!Init_Thread_pool()) {
  exit(1);
  }

  Create_Thread();
}

Thread_Pool::~Thread_Pool() {
  pthread_mutex_lock(&mutex);
  status = false;
  pthread_cond_broadcast(&cond); // 唤醒所有在 cond_wait 的线程
  pthread_mutex_unlock(&mutex);

  for (int i = 0; i < thread_num; i++) {
    pthread_join(Thread_array[i], NULL);
  }
  free(dequeue);
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);
}

bool Thread_Pool::Init_Thread_pool() {

  dequeue = (task_t *)malloc(sizeof(task_t) * d_size);

  if (dequeue == nullptr) {
    cout << "dequeue malloc failure" << endl;
    return false;
  }

  dequeue_size = d_size;
  front = 0;
  rear = front;
  task_Num = 0;
  thread_Num = thread_num;

  pthread_cond_init(&cond, NULL);
  pthread_mutex_init(&mutex, NULL);

  status = true;
  cout << "ThreadPool init success!" << endl;
  cout << "---------------------------------------" << endl;

  return true;
}

void Thread_Pool::add_Task(Socket *csocket) {
  pthread_mutex_lock(&mutex);
  if (task_Num >= dequeue_size) {
    delete csocket;
    cout << "dequeue_size is full!add_Task false!" << endl;
    pthread_mutex_unlock(&mutex);
    return;
  }

  dequeue[rear].task = csocket;
  rear = (rear + 1) % dequeue_size;
  task_Num++;

  pthread_cond_signal(&cond);
  pthread_mutex_unlock(&mutex);
}

void *General_Thread(void *arg) {
  Thread_Pool *poo = (Thread_Pool *)arg;
  poo->Work();
  return NULL;
}

void Thread_Pool::Create_Thread() {
  for (int i = 0; i < thread_num; i++) {
    pthread_create(&Thread_array[i], NULL, General_Thread, this);
  }
}

void Thread_Pool::Work() {
  while (true) {
    pthread_mutex_lock(&mutex);

    while (task_Num == 0 && status == true) {
      pthread_cond_wait(&cond, &mutex);
      // 这是条件休眠,进入到条件变量的休眠队列,进入条件变量的休眠队列是会自动释放锁,这与sleep是不相同的。
    } // 不能用if,要用while,以防虚假唤醒。

    if (status == false) {
      cout << "ThreadPool not run....." << endl;
      break;
    } // 处理线程池未启用的状态。

    Socket *tmp = dequeue[front].task;
    if (tmp == nullptr) {
      cout << "tmp is nullptr" << endl;
      break;
    }
    front = (front + 1) % dequeue_size;
    task_Num--;
    // cout<<"Thread id: "<<pthread_self()<<endl;
    pthread_mutex_unlock(&mutex);

    tmp->Do_client();
  }
  // 发生意外情况也要及时释放锁
  pthread_mutex_unlock(&mutex);
}

void Mysql_config::Init_Mysql_config(const char *Sql_config_path) {
  if (Sql_config_path == nullptr) {
    cout << "config_path is nullptr!" << endl;
    return;
  }

  FILE *fp = fopen(Sql_config_path, "r");
  if (fp == nullptr) {
    perror("fopen config file failed");
    cout << "fopen config file failed! errno=" << errno << endl;
    return;
  }

  char line[MAXLINE]{0};
  int num = 0;

  while (fgets(line, MAXLINE - 2, fp) != nullptr) {
    num++;
    if (line[0] == '#' || line[0] == '\n') {
      continue;
    }

    char *Before = strtok(line, "=");
    char *After = strtok(nullptr, "=");

    if (Before == nullptr || After == nullptr) {
      continue;
    }

    After[strlen(After) - 1] = '\0';

    if (strcmp("dp_ip", Before) == 0) {
      db_ip = After; // 禁止共享内存,line是一次性使用,After会被覆盖
    } else if (strcmp("dp_port", Before) == 0) {
      db_port = atoi(After);
    } else if (strcmp("dp_name", Before) == 0) {
      db_name = After;
    } else if (strcmp("dp_mysql_path", Before) == 0) {
      Sql_config_path = After;
    } else {
      cout << "unknown config item: " << Before << " at line " << num << endl;
    }
    memset(line, 0, sizeof(line));
  }
  fclose(fp);
  print_Mysql_config();
}

void Mysql_config::print_Mysql_config() const {
  cout << "db_ip: " << db_ip << endl;
  cout << "db_port: " << db_port << endl;
  cout << "db_name: " << db_name << endl;
  cout << "config_path: " << Sql_config_path << endl;
}

 void Mysql_Client::Send_full(){
  string a = "MYSQL CLIENT BUSSY";
  Json::Value value;
  value["status"] = a;
  send(who, value.toStyledString().c_str(),
       strlen(value.toStyledString().c_str()), 0);
 }

Mysql_Client::
Mysql_Client(struct Mysql_config *Sql_config,int who):who(who) {
  connected = false;
  mysql_con = nullptr;
  if (Sql_config == nullptr) {
    cout << "Mysql_config_path nullptr!" << endl;
    Send_full();
    return;                       // 不 exit, 让客户端收到 busy 后自己重试
  }
  file = (struct Mysql_config *)Sql_config;
  if (!Connect_Mysql_Server()) {
    cout << "---------------------------------------------" << endl;
    cout << "connect mysql false (pool busy)" << endl;
    cout << "---------------------------------------------" << endl;
    Send_full();                  // 给客户端发 "MYSQL CLIENT BUSSY"
    return;                       // 不再继续, 后续 Db_* 也不会被调用
  }
  connected = true;
  cout << "connect mysql succeed" << endl;
  cout << "---------------------------------------------" << endl;
}


void Mysql_Client::Mysql_Begin() {
  if (!connected) return;
  if (mysql_query(mysql_con, "begin") != 0) {
    cout << "事物启动失败!" << endl;
  }
}

void Mysql_Client::Mysql_RollBack() {
  if (!connected) return;
  if (mysql_query(mysql_con, "rollback") != 0) {
    cout << "事物回滚失败!" << endl;
  }
}

void Mysql_Client::Mysql_Commit() {
  if (!connected) return;
  if (mysql_query(mysql_con, "commit") != 0) {
    cout << "事物提交失败!" << endl;
  }
}

bool Mysql_Client::Connect_Mysql_Server() {
  pthread_mutex_lock(&g_pool->mutex);
  

  int i=0;
  while(i<thread_num){
    if(g_pool->C_P_array[i].status==true)
    {
  mysql_con = g_pool->C_P_array[i].mysql_con;
  g_pool->C_P_array[i].status=false;
  break;
    }
    i++;
  }


  if(i>=thread_num){
  pthread_mutex_unlock(&g_pool->mutex);
  return false; 
  }
  else{
  g_pool->user++;
  pthread_mutex_unlock(&g_pool->mutex);  
  return true;
  }
}

bool Mysql_Client::Db_User_Register(const std::string &user_tel,
                                    const std::string &user_name,
                                    const std::string &user_passwd) {
  if (!connected) return false;

  string sql = string("insert into user_info values(0,'") + user_tel +
               string("','") + user_name + string("','") + user_passwd +
               string("',1,CURDATE())");

  if (mysql_query(mysql_con, sql.c_str()) != 0) {
    return false;
  }

  return true;
}

bool Mysql_Client::Db_User_Login(const std::string &tel, std::string &name,
                                 const std::string &passwd) {
  if (!connected) return false;
  string sql = string("select Name,Passwd from user_info where Tel='") + tel +
               string("'");
  if (mysql_query(mysql_con, sql.c_str()) != 0) {
    return false;
  }

  MYSQL_RES *r = mysql_store_result(mysql_con);
  if (r == nullptr) {
    return false;
  }

  int num = mysql_num_rows(r);
  if (num == 0) {
    return false;
  }

  MYSQL_ROW row = mysql_fetch_row(r);
  if (row == nullptr) {
    return false;
  }

  string username = row[0];
  string userpasswd = row[1];

  mysql_free_result(r);
  if (userpasswd != passwd || name != username) {
    return false;
  }

  return true;
}

bool Mysql_Client::Db_Show_Ticket(Json::Value &res) {
  if (!connected) return false;
  string sql = "select * from ticket_table where status=1";
  if (mysql_query(mysql_con, sql.c_str()) != 0) {
    return false;
  }

  MYSQL_RES *r = mysql_store_result(mysql_con);
  if (r == nullptr) {
    return false;
  }

  int num = mysql_num_rows(r);
  if (num == 0) {
    return false;
  }
  res["status"] = o;
  res["num"] = num;
  for (int i = 0; i < num; i++) {
    MYSQL_ROW row = mysql_fetch_row(r);
    Json::Value val;
    val["ticket_id"] = atoi(row[0]);
    val["ticket_name"] = row[1];
    val["ticket_max"] = row[2];
    val["ticket_count"] = row[3];
    val["day_time"] = row[4];
    res["ticket_arr"].append(val); // Json对象也可以添加进去
  }

  mysql_free_result(r);
  return true;
}

bool Mysql_Client::Db_Yd_Ticket(std::string usertel, std::string tk_id) {
  if (!connected) return false;
  string sql_max_count =
      string("select tk_max,count from ticket_table where Tk_id=") + tk_id;
  if (mysql_query(mysql_con, sql_max_count.c_str()) != 0) {
    cout << "[预约] 查询票信息失败: " << mysql_error(mysql_con)
         << " | SQL: " << sql_max_count << endl;
    return false;
  }

  MYSQL_RES *r = mysql_store_result(mysql_con);
  if (r == NULL) {
    cout << "[预约] 取结果集失败: " << mysql_error(mysql_con) << endl;
    return false;
  }

  int num = mysql_num_rows(r);
  if (num == 0) {
    cout << "[预约] 未找到 Tk_id=" << tk_id << " 的票记录" << endl;
    mysql_free_result(r);
    return false;
  }

  MYSQL_ROW row = mysql_fetch_row(r);
  int max = atoi(row[0]);
  int count = atoi(row[1]);
  mysql_free_result(r);

  if (count >= max) {
    cout << "[预约] 票数不足 max=" << max << " count=" << count << endl;
    return false;
  }

  count++;

  Mysql_Begin();
  // 一旦开启事物,这两条sql语句都成功执行的情况下,才会去进行提交事物的操作,
  //  否则一旦有其中一个未成功,就算其中一个成功的操作也会撤销,二者立刻回滚,所有改动立即撤销,
  string sql_setcount = string("update ticket_table set count=") +
                        to_string(count) + string(" where Tk_id=") + tk_id;
  if (mysql_query(mysql_con, sql_setcount.c_str()) != 0) {
    cout << "[预约] 更新票数失败: " << mysql_error(mysql_con)
         << " | SQL: " << sql_setcount << endl;
    Mysql_RollBack();
    return false;
  }

  string sql_yd = string("insert into yd_table values(0,'") + usertel +
                  string("','") + tk_id + string("',now())");
  if (mysql_query(mysql_con, sql_yd.c_str()) != 0) {
    cout << "[预约] 写入预约表失败: " << mysql_error(mysql_con)
         << " | SQL: " << sql_yd << endl;
    Mysql_RollBack();
    return false;
  }

  Mysql_Commit();
  cout << "[预约] 成功: user_tel=" << usertel << " Tk_id=" << tk_id
       << " 剩余=" << (max - count) << endl;
  return true;
}

bool Mysql_Client::Db_Show_My_Ticket(const std::string &user_tel,
                                     Json::Value &res) {
  if (!connected) return false;
  string sql =
      string("select y.yd_id, t.tk_name, t.tk_max, t.`count`, t.DateTime ") +
      string("from yd_table y, ticket_table t ") +
      string("where y.user_tel='") + user_tel + string("' and y.tk_id=t.Tk_id");
  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 == nullptr) {
    res["status"] = o;
    res["num"] = 0;
    return true;
  }

  int num = mysql_num_rows(r);
  res["status"] = o;
  res["num"] = num;
  for (int i = 0; i < num; i++) {
    MYSQL_ROW row = mysql_fetch_row(r);
    Json::Value val;
    val["yd_id"] = row[0];
    val["ticket_name"] = row[1];
    val["ticket_max"] = row[2];
    val["ticket_count"] = row[3];
    val["day_time"] = row[4];
    res["my_ticket_arr"].append(val);
  }

  mysql_free_result(r);
  return true;
}

bool Mysql_Client::Db_Cancel_Ticket(const std::string &user_tel,
                                    const std::string &yd_id) {
  if (!connected) return false;
  string sql_select = string("select tk_id from yd_table where yd_id=") +
                      yd_id + string(" and user_tel='") + user_tel +
                      string("'");

  // 因为我们删除的时候是删除我们自己的预约表的最前面的那个编号,而不是票的票号,所以我们要额外的去存储这个票的票号,对这个票的数据进行减一的操作。
  if (mysql_query(mysql_con, sql_select.c_str()) != 0) {
    cout << "查询预约记录失败: " << mysql_error(mysql_con) << endl;
    return false;
  }

  MYSQL_RES *r = mysql_store_result(mysql_con);
  if (r == nullptr) {
    return false;
  }

  int num = mysql_num_rows(r);
  if (num == 0) {
    mysql_free_result(r);
    return false; // 预约记录不存在,无法取消
  }

  MYSQL_ROW row = mysql_fetch_row(r);
  string tk_id = row[0];
  mysql_free_result(r);

  Mysql_Begin();

  string sql_del = string("delete from yd_table where yd_id=") + yd_id;
  if (mysql_query(mysql_con, sql_del.c_str()) != 0) {
    cout << mysql_error(mysql_con) << endl;
    Mysql_RollBack();
    return false;
  }

  string sql_update =
      string("update ticket_table set `count`=`count`-1 where Tk_id=") + tk_id;
  if (mysql_query(mysql_con, sql_update.c_str()) != 0) {
    cout << mysql_error(mysql_con) << endl;
    Mysql_RollBack();
    return false;
  }

  Mysql_Commit();

  string sql_reset = "ALTER TABLE yd_table AUTO_INCREMENT = 1";
  if (mysql_query(mysql_con, sql_reset.c_str()) != 0) {
    cout << mysql_error(mysql_con) << endl;
  }
  return true;
}

Mysql_Client::~Mysql_Client() {
  pthread_mutex_lock(&g_pool->mutex);
  for(int i=0;i<thread_num;i++){
    if(mysql_con==g_pool->C_P_array[i].mysql_con){
    g_pool->C_P_array[i].status=true;
    g_pool->user--;
    }
   }
   pthread_mutex_unlock(&g_pool->mutex);
}

Connect_Pool::Connect_Pool(Mysql_config *Sql_config) {
  if (Sql_config == nullptr) {
    cout << "Connect Sqlconfig == nullptr!" << endl;
    return;
  }

  user = 0;
  status = true;
  MAX_USER = thread_num;
  this->Sql_config = Sql_config;
  C_P_array = new Mysql_cli[thread_num];

  Reset();
  pthread_mutex_init(&mutex,NULL);

  if(!Run()){
    cout<<"Connect_Pool Run failed!"<<endl;
    Reset();
    exit(1);
  }
  cout<<"-------------------------"<<endl;
  cout<<"Connect_Pool Run success!"<<endl;
  cout<<"-------------------------"<<endl;
}

bool Connect_Pool::Run() {
  if (status == false) {
    return false;
  }
  for (int i = 0; i < thread_num; i++) {

    MYSQL *mysql = mysql_init(C_P_array[i].mysql_con);
    if (mysql == nullptr) {
      return false;
    }

    mysql = mysql_real_connect(mysql, Sql_config->db_ip.c_str(), "root",
                               "111111", Sql_config->db_name.c_str(),
                               Sql_config->db_port, NULL, 0);

    if (mysql == nullptr) {
      return false;
    }
    
    C_P_array[i].mysql_con = mysql;
    C_P_array[i].status = true;
  }   
  return true;
}


void Connect_Pool::Reset(){
for(int i=0;i<thread_num;i++){
  if(C_P_array[i].mysql_con!=nullptr){
    mysql_close( C_P_array[i].mysql_con);
  }
  C_P_array[i].mysql_con= nullptr;
  C_P_array[i].status = false;
}
}

Connect_Pool::~Connect_Pool(){
  for(int i = 0;i<thread_num;i++){
    mysql_close(C_P_array[i].mysql_con);
  }
  delete C_P_array;
  pthread_mutex_destroy(&mutex);
}

void Tcp_Service::Init(const Service_Config *config) {
  if (config == nullptr) {
    cout << "config is nullptr!" << endl;
    return;
  }
  this->config = config;
  Status = true;

  pool = new Thread_Pool;

  Mysql_config file;
  file.Init_Mysql_config(Sql_Conf_path);

  lis_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (lis_fd == START) {
    perror("socket failed");
    cout << "socket failed! errno=" << errno << endl;
    exit(1);
  }

  // listen socket 设为非阻塞,accept 队列空时返回 EAGAIN 而不是阻塞
  int flags = fcntl(lis_fd, F_GETFL, 0);
  fcntl(lis_fd, F_SETFL, flags | O_NONBLOCK);
  // 因为是lis_fd为空导致的阻塞,所以我们这个将模式设为非阻塞,在此之后,accept会直接返回EAGAIN或EWOULDBLOCK,而不是阻塞线程
  // 不会阻塞

  int opt = 1;
  setsockopt(lis_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = inet_addr(config->IP.c_str());
  server_addr.sin_port = htons(config->Port);

  config->PrintConfig();

  if (bind(lis_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("bind failed");
    cout << "bind failed! errno=" << errno << endl;
    exit(1);
  }

  if (listen(lis_fd, config->Lismax) < 0) {
    perror("listen failed");
    cout << "listen init error! errno=" << errno << endl;
    exit(1);
  }

  int epfd =
      epoll_create1(EPOLL_CLOEXEC); // 防止fork版本公用一个文件描述符的情况
  if (-1 == epfd) {
    perror("epoll_create1 failed");
    cout << "create epoll error! errno=" << errno << endl;
    close(lis_fd);
    exit(1);
  }

  ep_fd = epfd;
  cout << "epoll success!" << endl;

  struct epoll_event ev_listen;
  ev_listen.events = EPOLLIN;
  ev_listen.data.fd = lis_fd;

  if (epoll_ctl(epfd, EPOLL_CTL_ADD, lis_fd, &ev_listen) == -1) {
    perror("epoll_ctl add listen fd failed");
    cout << "epoll_ctl add false! errno=" << errno << endl;
    close(lis_fd);
    exit(1);
  }

  cout << "Tcp service success!" << endl;
  cout << "------------------------------------------------------" << endl;

  while (Status) {

    int ready_count = epoll_wait(epfd, ready_events, MAXLINE, -1);
    //- 内核 :负责把发生事件的 fd 放入就绪双链表(这是中断/回调驱动的,随时发生)
    // epoll_wait :负责从就绪双链表取出事件,拷贝给用户态的 ready_events
    if (ready_count == -1) {
      perror("epoll_wait failed");
      cout << "epoll_wait error! errno=" << errno << endl;
      close(lis_fd);
      exit(1);
    }

    for (int i = 0; i < ready_count; i++) {
      int tmfd = ready_events[i].data.fd;
      if (tmfd == lis_fd) {
        auto csocket = new AccSocket(lis_fd, epfd);
        pool->add_Task(csocket);
      } else {
        auto csocket = ready_events[i].data.ptr;
        if (csocket == nullptr) {
          cout << "csocket is nullptr" << endl;
          continue;
        }
        pool->add_Task((Socket *)csocket);
      }
    }
  }
}


int main() {
  
  file.Init_Mysql_config(Sql_Conf_path); 
  file.print_Mysql_config();  
  cout << "-----------------------------------------" << endl;
  g_pool = new Connect_Pool(&file);


  Service_Config a;
  a.ReadConfig(a.config_path);
  cout << "-----------------------------------------" << endl;

  Tcp_Service tcp_service(&a);
  delete g_pool;
  return 0;
}
文件作用

实现 service.h 里所有类的方法 + main 函数(约 935 行)。

核心流程
复制代码
main()
  ├─ file.Init_Mysql_config()           // 读 mysql.conf
  ├─ g_pool = new Connect_Pool(&file)    // 建连接池(5 个连接)
  ├─ a.ReadConfig()                     // 读 service.conf
  └─ Tcp_Service tcp_service(&a)        // 进入主循环
       ├─ new Thread_Pool                // 起 5 个 worker
       ├─ listen + bind + socket         // 起 TCP 服务
       └─ while (Status) {               // epoll 主循环
            epoll_wait()
            ├─ if listen fd → new AccSocket → add_Task
            └─ else → 取 RecvSocket* → add_Task
          }
关键方法详解
Tcp_Service::Init (L815-L917)

主控函数:建线程池 → 建 listen fd(设非阻塞 + SO_REUSEADDR)→ bind → listen → epoll_create1 → epoll_ctl ADD listen fd → while(Status) 循环 epoll_wait。

Thread_Pool::Work (L338-L366)

worker 线程主循环:pthread_mutex_lockwhile(task_Num==0) cond_wait → 取出 dequeue[front].task → unlock → tmp->Do_client() 多态分发。

RecvSocket::Do_client (L164-L260)
  • recv 数据 → JSON 解析 → 构造 Mysql_Client sql(&file, fd)
  • 检查 sql.IsConnected()
    • false(池满)→ 跳过 Db_* 调用,构造里已 Send_full() 发了 BUSSY
    • true → 按 val["type"] 走 6 个业务分支
  • 结束后 epoll_ctl MOD 重新监听(EPOLLONESHOT)
Mysql_Client::Connect_Mysql_Server (L474-L499)

加锁遍历 C_P_array,找到 status==true 的 slot → mysql_con = C_P_array[i].mysql_constatus=falseuser++ → 返回 true。借不到返回 false。

Mysql_Client::~Mysql_Client (L740-L749)

加锁遍历 C_P_array,找到 mysql_con 相等的 slot → status=trueuser-- 归还。

Connect_Pool::Run (L776-L798)

循环 5 次:mysql_initmysql_real_connect → 存进 C_P_array[i].mysql_con + status=true

Connect_Pool::Reset (L801-L809)

循环 5 次:若 mysql_con != nullptrmysql_close,然后置 nullptr + status=false。

业务方法
  • Db_User_Register (L501) --- 插入 user_info
  • Db_User_Login (L517) --- select Name,Passwd 比对
  • Db_Show_Ticket (L552) --- select * 拼成 JSON
  • Db_Yd_Ticket (L585) --- 事务:查 max/count → 更新 count → 插 yd_table
  • Db_Show_My_Ticket (L647) --- JOIN yd_table + ticket_table
  • Db_Cancel_Ticket (L684) --- 事务:查 tk_id → 删 yd → count-1
设计亮点
  • Mysql_Client::IsConnected() 让 Do_client 可以无侵入处理"池满"
  • Send_full() 直接发 BUSSY 给客户端 fd,避免在 Do_client 多发一次 false
  • 事务在 Db_Yd_Ticket / Db_Cancel_Ticket 中正确 begin/commit/rollback
  • listen fd 设非阻塞 + accept 返回 EAGAIN 时不报错静默退出(多 worker 竞争保护)
不足
  • Mysql_Client::mysql_con 之前是野指针(已加 mysql_con = nullptr; 初始化修好)
  • day-time vs day_time 字段名不一致(已修)
  • Connect_Pool::Run 失败时局部 mysql 指针丢失(OS 兜底)
  • SQL 全字符串拼接,存在注入风险

4.3 <client.h> --- 客户端类声明

完整代码
cpp 复制代码
#include <stdlib.h>
#include <string>
#include <cstring>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <jsoncpp/json/json.h>
#include <mysql/mysql.h>

#include <sys/socket.h>
#include <sys/epoll.h>
#include <pthread.h>

#include <unistd.h>

#define IPADRESS "127.0.0.1"
#define PORTADRESS 8888
#define START -1
#define Length 1024
#define k "ok"
#define BUSY "MYSQL CLIENT BUSSY"   // 服务器连接池满时返回的标记

class Client{
   private:
     
     short Port;

     int conn_fd;
     int User_Login_way;
     
     bool Running;
     bool Dl_Status;
     

     std::string IP;
     std::string user_name;
     std::string user_tel;

    public:
     Client();
     ~Client();
     void Client_Init();
     void Print_Config();

     void Run();
     void Show_Menu();
     void User_Register();
     void User_login();
   
     void Show_Ticket();  
     void Yd_Ticket();
     void Show_My_Ticket();
     void Qx_Ticket();
};
文件作用

声明 Client 类,定义客户端常量(IP/PORT/Buff 大小)。

关键成员
cpp 复制代码
#define IPADRESS "127.0.0.1"
#define PORTADRESS 8888
#define Length 1024
#define k "ok"
#define BUSY "MYSQL CLIENT BUSSY"   // 服务器连接池满时返回的标记
Client 类
成员 作用
conn_fd TCP 连接 fd
Dl_Status 登录状态
Running 主循环开关
user_name / user_tel 当前用户信息
方法
  • Client_Init() --- socket + connect
  • Show_Menu() --- 显示菜单(登录前后不同)
  • User_Register / User_login / Show_Ticket / Yd_Ticket / Show_My_Ticket / Qx_Ticket --- 6 个业务
  • Run() --- 主循环 switch 分发

4.4 <client.cpp> --- 客户端实现

完整代码
cpp 复制代码
#include "client.h"
#include <jsoncpp/json/value.h>
#include <ostream>
#include <sys/socket.h>
#include <iostream>
using namespace std;

enum OP_TYPE{
  Login = 1,
  Register,
  CheckApoint,
  Appointment,
  AppoinMessage,
  CancelAppoin,
  Exit
};



Client::Client()
{
    IP=IPADRESS;
    Port = PORTADRESS;
    conn_fd = START;
    Dl_Status = false;
    Running = true;
    Client_Init();
}


Client::~Client()
{
    if(conn_fd != START){
        close(conn_fd);
    }
    cout<<"Client close"<<endl;
}

void Client::Client_Init(){

   int sock_fd = socket(AF_INET,SOCK_STREAM,0);
   if(sock_fd<=0){
    cout<<"Client socket Error!"<<endl;
    exit(1);
   }

   conn_fd = sock_fd;
   Print_Config();
 

   struct sockaddr_in Client_sock_ad;
   memset(&Client_sock_ad,0,sizeof(Client_sock_ad));
   Client_sock_ad.sin_family = AF_INET;
   Client_sock_ad.sin_addr.s_addr=inet_addr(IPADRESS);
   Client_sock_ad.sin_port = htons(PORTADRESS);

   if(connect(sock_fd,(const struct sockaddr* )&Client_sock_ad,sizeof(Client_sock_ad))==START){
    cout<<"connect failed!"<<endl;
    close(sock_fd);
    exit(1);
   }

   cout<<"connect succed!"<<endl;
   cout<<"               client start begin                     "<<endl;
   cout<<"------------------------------------------------------"<<endl;

   Run();
}

void Client::Print_Config(){
    cout<<"IP: "<<IP<<endl;
    cout<<"Port: "<<Port<<endl;
    cout<<"conn_fd: "<<conn_fd<<endl;
    cout<<"this: "<<this<<endl;
}

void Client::Show_Menu(){
    if(!Dl_Status){
        cout <<"---用户名:游客---------状态:未登录-----"<<endl;
        cout << "1 登陆   2  注册   3  退出" << endl;
        cout << "---------------------------------" << endl;
        cout << "请输入选项编号:" << endl;
    }
    else{
        cout << "---用户名:" << user_name << "--------状态:已登陆------" << endl;
        cout << "1 查看预约      2  预定 " << endl;
        cout << "3 查看我的预约   4 取消预定 " << endl;
        cout << " 5 退出" << endl;
        cout << "---------------------------------" << endl;
        cout << "请输入选项编号:" << endl;
        cout << "---------------------------------" << endl;
    }
}

void Client::User_Register(){
    cout<<"请输入手机号码:"<<endl;
    cin>>user_tel;
    cout<<"请输入用户名:"<<endl;
    cin>>user_name;
    cout<<"请输入密码"<<endl;
    string passwd;
    cin>>passwd;

    Json::Value mess;
    mess["type"] = Register;
    mess["user_tel"] = user_tel;

    mess["user_name"] = user_name;
    mess["passwd"] = passwd;
    send(conn_fd,mess.toStyledString().c_str(),strlen(mess.toStyledString().c_str()),0);

    char buff_status[Length] = {0};
    int n = recv(conn_fd,buff_status,Length-1,0);
    if(n<0){
        cout<<"ser err"<<endl;
        return;
    }

    Json::Value Acmess;
    Json::Reader Read;
    if(!Read.parse(buff_status,Acmess)){
        cout<<"Json Read error!"<<endl;
        return;
    }

    string st = Acmess["status"].asString();
    if(st == BUSY){
        cout<<"服务器繁忙,请稍后重试"<<endl;
        return ;
    }
    if(st.compare(k)!=0){
        cout<<"Register false!"<<endl;
        return ;
    }

    cout<<"Register success!"<<endl;
    Dl_Status = true;
}

void Client::User_login(){
    cout<<"请输入手机号码"<<endl;
    cin>>user_tel;
    cout<<"请输入用户名"<<endl;
    cin>>user_name;
    cout<<"请输入密码"<<endl;
    string passwd;
    cin>>passwd;

    Json::Value val;
    val["type"] = Login;
    val["user_tel"] = user_tel;
    val["passwd"] = passwd;
    val["user_name"] = user_name;
    send(conn_fd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0);

   char buff_status[Length] = {0};
    int n = recv(conn_fd,buff_status,Length-1,0);
    if(n<0){
        cout<<"ser err"<<endl;
        return;
    }

    Json::Value Acmess;
    Json::Reader Read;
    if(!Read.parse(buff_status,Acmess)){
        cout<<"Json Read error!"<<endl;
        return;
    }

    string st = Acmess["status"].asString();
    if(st == BUSY){
        cout<<"服务器繁忙,请稍后重试"<<endl;
        return ;
    }
    if(st.compare(k)!=0){
        cout<<"Login false!"<<endl;
        return ;
    }

    cout<<"Login success!"<<endl;
    Dl_Status = true;
}

void Client::Show_Ticket(){
   Json::Value val;
   val["type"] = CheckApoint;
   send(conn_fd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0);

   char buff[Length]={0};
   int n = recv(conn_fd,buff,Length-1,0);
   if(n<=0){
    cout<<"ser close"<<endl;
    return;
   }

   Json::Value res_val;
   Json::Reader Read;
   if(!Read.parse(buff,res_val)){
     cout<<"Json 解析失败"<<endl;
     return;
   }

   string s = res_val["status"].asString();
   if(s == BUSY){
      cout<<"服务器繁忙,请稍后重试"<<endl;
      return;
   }
   if(s.compare(k)!=0){
      cout<<"查看预约信息失败"<<endl;
      return;
   }

   int num = res_val["num"].asInt();
   if(num<0){
    cout<<"暂时没有可以预约的信息"<<endl;
    return ;
   }

   cout<<"|----------|--------------|--------|--------|------------|"<<endl;
   cout<<"|   序号   |     名称     | 总票数 | 已预定 |    时间    |"<<endl;
   cout<<"|----------|--------------|--------|--------|------------|"<<endl;

   for(int i=0;i<num;i++){
    printf("| %8s | %12s | %6s | %6s | %10s |\n",
           res_val["ticket_arr"][i]["ticket_id"].asString().c_str(),
           res_val["ticket_arr"][i]["ticket_name"].asString().c_str(),
           res_val["ticket_arr"][i]["ticket_max"].asString().c_str(),
           res_val["ticket_arr"][i]["ticket_count"].asString().c_str(),
           res_val["ticket_arr"][i]["day_time"].asString().c_str());
   }
   cout<<"|----------|--------------|--------|--------|------------|"<<endl;

}

void Client::Yd_Ticket(){
    Show_Ticket();
    cout<<"请输入需要预订的序号"<<endl;
    int index;
    cin>>index;
    Json::Value val;
    val["type"] = Appointment;
    val["user_tel"] = user_tel;
    val["ticket_id"] = to_string(index);

    send(conn_fd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0);

    char buff[Length] = {0};
    int n = recv(conn_fd,buff,Length-1,0);
    if(n<0){
        cout<<"ser close"<<endl;
        return ;
    }

    Json::Value res_val;
    Json::Reader Read;
    if(!Read.parse(buff,res_val)){
        cout<<"Json 解析失败"<<endl;
        return;
    }

    string s = res_val["status"].asString();
    if(s == BUSY){
        cout<<"服务器繁忙,请稍后重试"<<endl;
        return;
    }
    if(s.compare(k)!=0){
        cout<<"预订失败"<<endl;
        return;
    }

    cout<<"预订成功!"<<endl;

}

void Client::Show_My_Ticket(){
  Json::Value val;
  val["type"] = AppoinMessage;
  val["user_tel"] = user_tel;
  send(conn_fd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0);

  char buff[Length]={0};
  int n = recv(conn_fd,buff,Length,0);
  if(n<=0){
    cout<<"ser close"<<endl;
    return;
  }

  Json::Value res_val;
  Json::Reader Read;
  if(!Read.parse(buff,res_val)){
      cout<<"Json 解析失败"<<endl;
     return;
  }

  string s = res_val["status"].asString();
  if(s == BUSY){
    cout<<"服务器繁忙,请稍后重试"<<endl;
    return;
  }
  if(s.compare(k)!=0){
    cout<<"查看我的预约失败"<<endl;
    return;
  }

  int num = res_val["num"].asInt();
 if(num == 0){
    cout<<"您还没有预约任何信息"<<endl;
    return ;
}

   cout<<"|----------|--------------|--------|--------|------------|"<<endl;
   cout<<"|  预约ID  |     名称     | 总票数 | 已预定 |    时间    |"<<endl;
   cout<<"|----------|--------------|--------|--------|------------|"<<endl;

   for(int i=0;i<num;i++){
    printf("| %8s | %12s | %6s | %6s | %10s |\n",
           res_val["my_ticket_arr"][i]["yd_id"].asString().c_str(),
           res_val["my_ticket_arr"][i]["ticket_name"].asString().c_str(),
           res_val["my_ticket_arr"][i]["ticket_max"].asString().c_str(),
           res_val["my_ticket_arr"][i]["ticket_count"].asString().c_str(),
           res_val["my_ticket_arr"][i]["day_time"].asString().c_str());
   }
   cout<<"|----------|--------------|--------|--------|------------|"<<endl;
}

void Client::Qx_Ticket(){
    Show_My_Ticket();
    cout<<"请输入需要取消的预约ID:"<<endl;
    string yd_id;
    cin>>yd_id;

    Json::Value val;
    val["type"] = CancelAppoin;
    val["user_tel"] = user_tel;
   val["yd_id"] = yd_id;
   send(conn_fd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0);

   char buff[Length] = {0};
   int n = recv(conn_fd,buff,Length-1,0);
   if(n <= 0){
    cout<<"ser close"<<endl;
    return ;
   }

   Json::Value res_val;
   Json::Reader Read;
   if(!Read.parse(buff,res_val)){
    cout<<"Json解析失败"<<endl;
    return ;
   }

   string s = res_val["status"].asString();
   if(s == BUSY){
    cout<<"服务器繁忙,请稍后重试"<<endl;
    return ;
   }
   if(s.compare(k)!=0){
    cout<<"取消预约失败"<<endl;
    return ;
   }

   cout<<"取消预约成功"<<endl;
}

void Client::Run(){
    while(Running){
        Show_Menu();
        int tmp;
        cin>>tmp;
        if(tmp < Login || tmp > Exit){
            cout<<"输入错误"<<endl;
            continue;
        }
        if(Dl_Status){
            tmp = tmp+2;
        }
        else if(tmp==3){
            tmp = Exit;
        }
        switch(tmp){
            case Login:
                User_login();
                break;
            case Register:
                User_Register();
                break;
            case CheckApoint:
                Show_Ticket();
                break;
            case Appointment:
                Yd_Ticket();
                break;
            case AppoinMessage:
                Show_My_Ticket();
                break;
            case CancelAppoin:
                Qx_Ticket();
                break;
            case Exit:
                Running = false;
                break;
        }
    }
}


int main(){
    Client client;
    return 0;
}
文件作用

实现 Client 类所有方法 + main(约 410 行)。

通信协议(JSON)

客户端发送:

json 复制代码
{ "type": 1, "user_tel": "...", "passwd": "...", "user_name": "..." }

服务端返回(成功):

json 复制代码
{ "status": "ok", "num": 3, "ticket_arr": [...] }

服务端返回(失败):

json 复制代码
{ "status": "false" }

服务端返回(池满):

json 复制代码
{ "status": "MYSQL CLIENT BUSSY" }
6 个业务方法
方法 type 流程
User_Register 2 输入 tel/name/passwd → send → recv → BUSY?/ok?
User_login 1 同上
Show_Ticket 3 不带参数 → recv 列表 → 表格打印
Yd_Ticket 4 先 Show_Ticket → 输入序号 → send → recv
Show_My_Ticket 5 带 user_tel → recv → 表格
Qx_Ticket 6 先 Show_My_Ticket → 输入 yd_id → send → recv
菜单逻辑
cpp 复制代码
switch(tmp) {
  case Login: User_login(); break;
  case Register: User_Register(); break;
  ...
  case Exit: Running = false; break;
}
设计亮点
  • 统一 BUSY 检查模板:6 个方法都先判断 s == BUSY 提示"服务器繁忙",再判断 s != k 提示具体失败
  • 业务方法可组合(Yd_Ticket 复用 Show_Ticket,Qx_Ticket 复用 Show_My_Ticket)
不足
  • recv 阻塞,无超时(服务端挂起客户端会一直等)
  • cin >> tmp 没检查失败状态(输入非数字会陷入死循环)
  • 表格用 printf 硬编码列宽,长名会错位
  • 全局 enum OP_TYPE 与服务端重复定义(应放共享头)

4.5 --- 构建脚本

完整代码
makefile 复制代码
all:service client

service:service.cpp
	g++ -g -O0 -o service service.cpp -lpthread -lmysqlclient -ljsoncpp

client:client.cpp
	g++ -g -O0 -o client client.cpp -lpthread -lmysqlclient -ljsoncpp

clean:
	rm -f *.o service client
依赖说明
用途
-lpthread pthread_t / mutex / cond
-lmysqlclient MySQL C API
-ljsoncpp JSON 解析
改进建议
  • -Wall -Wextra 开启警告
  • -std=c++11(用了 autonullptr
  • 拆分头文件后改用 .o 中间产物 + 自动依赖

4.6 <ser_start.sh> --- 启动脚本

完整代码
bash 复制代码
#!/bin/bash
if [ -f Makefile ]
then
    make clean
    make all
fi
if [ -f service ]
then
    ./service service.conf
fi
不足
  • 没加 set -e,make 失败仍会启动旧 service
  • 没传 service.conf 路径给程序读(代码里也硬编码了)
  • 没用 nohup 后台运行 + 日志重定向

4.7 <service.conf> --- 服务端配置

完整内容
conf 复制代码
ip=127.0.0.1

port=8888

lismax=1024

service_conf_path=/home/xiaoc/mycode/csproject/service.conf
注意
  • service_conf_path 字段在代码里赋值给局部参数,无实际作用
  • 修改后必须 Ctrl+S 保存磁盘才生效(之前 IDE 缓冲区与磁盘不一致问题)

4.8 <mysql.conf> --- 数据库配置

完整内容
conf 复制代码
dp_ip=127.0.0.1

dp_port=3306

dp_name=c2411db

dp_mysql_path=/home/xiaoc/mycode/csproject/mysql.conf
注意
  • dp_mysql_path 路径写错(少了 "2"),但代码里赋值给局部参数,无实际作用
  • 缺少 dp_user / dp_password 字段,密码硬编码在 <service.cpp#L787>

五、Bug 修复记录(本次版本)

Bug 描述 状态
1 Connect_Mysql_Serveri 不递归导致死循环 ✅ 已修
2 Run() 没把 mysql 保存到 C_P_arrayi.mysql_con ✅ 已修
3 status 语义反(true 应表示可用) ✅ 已修
4 借出后没设 status=false ✅ 已修
5 Mysql_Client 构造失败仍执行 SQL ✅ 已修
6 Thread_Pool 构造 delete this ✅ 已修
7 借/还不加锁 ✅ 已修
A Mysql_Client::mysql_con 构造时未初始化为 nullptr ✅ 已修
B day-time vs day_time 字段名不匹配 ✅ 已修
C Connect_Pool::Run 失败时局部 mysql 指针丢失 ⚠️ 不修(OS 兜底)
D Tcp_Service::Init 局部 file 遮蔽全局 ✅ 已修

六、改进路线图(按优先级)

优先级 改进项 影响
P0 SQL 注入修复(prepared statement) 安全
P0 密码哈希存储 安全
P1 连接池加条件变量 性能/体验
P1 signal handler 优雅退出 健壮性
P1 配置文件路径可配置 可移植
P2 头文件拆分 可维护性
P2 全局变量改单例 可维护性
P2 日志分级 运维
P3 客户端 recv 超时 健壮性
P3 管理员后台 功能扩展
P3 send/recv 改 ET 模式 性能
相关推荐
卓怡学长1 小时前
w192基于springboot教务管理系统
java·数据库·spring boot·spring·maven·intellij-idea
码农颜1 小时前
6.4.1 监听连接
java·数据库·mysql
被怪兽吃掉了1 小时前
5.2.1一维组数定义方式
开发语言·c++·算法
桐桐桐1 小时前
Python 实战:批量生成带来源参数的 WhatsApp 短链 + 二维码
服务器·数据库·python·前端框架·ip·跨境电商·独立站
旖旎夜光1 小时前
LeetCode 69:x 的平方根(二分查找) —— 题解
数据结构·c++·算法·leetcode·二分查找
j7~1 小时前
【C++】《C++二叉搜索树(BST)从入门到精通:概念、实现与Key/Value模型全解析》
开发语言·c++·学习·二叉搜索树
qq_349447951 小时前
在ollama下部署DeepSeek-r1模型(linux机器)
linux·运维·服务器
hehelm1 小时前
仿muduo库实现高并发服务器—Channel类
linux·服务器·开发语言·网络·c++
末代iOS程序员华仔1 小时前
Cursor 编辑器 + Figma‑MCP 服务器(真正让 Codex 直接读取 Figma 链接,高级方案)
服务器·编辑器·figma