一、先讲整体第六节课的预约功能到底在做什么这节课你们不是一下子就把“预约成功”全部做完而是先把预约功能拆成了两步。第一步客户端先告诉服务器我要看现在有哪些票可以预约。服务器收到后把数据库里的票务信息查出来整理后发给客户端显示。这个阶段对应的函数就是Show_ticket()第二步客户端看到列表以后再从中选一个具体的票比如某个ticket_id再发给服务器服务器才真正处理预约。这个阶段对应的是YD_ticket()所以你一定要先记住一句话第六节课真正完成的重点是预约功能的第一步先把可预约列表显示出来。二、先讲完整流程预约第一步是怎么跑起来的你现在先不要急着看代码先把流程背熟。预约第一步完整流程客户端发请求→ 告诉服务器“我要查看可预约信息”→ 服务器收到请求→ 根据type判断这是预约查看业务→ 服务器查询数据库里的票务表→ 把多条票务信息封装成 JSON 数组→ 服务器把 JSON 发回客户端→ 客户端解析 JSON→ 把票务列表一条一条打印出来这一整条链路就是第六节课的核心。三、先讲客户端Show_ticket()是怎么发起请求的客户端这一步不是“提交预约”而是“请求查看可预约列表”。1先看代码void Client::Show_ticket() { // 定义一个 JSON 对象 a // 这个对象用来表示客户端要发送给服务器的请求包 Json::Value a; // type YUYUE // 表示这次请求的业务类型是“预约相关业务” // 这里先表示“我要查看当前可预约的信息” a[type] YUYUE; // 把 JSON 对象序列化成字符串 string send_str a.toStyledString(); // 通过 socket 把字符串发送给服务器 send(sockfd, send_str.c_str(), strlen(send_str.c_str()), 0); }这就是客户端发起“查看预约列表”的核心代码。2这段代码在做什么第一部分创建 JSON 对象Json::Value a;这里的a就是一个 JSON 对象。你可以把它理解成一个“快递箱子”客户端要往里面装数据然后发给服务器。第二部分设置业务类型a[type] YUYUE;这一步非常关键。因为服务器不是自动知道你这次想干什么。客户端必须通过type字段告诉服务器我这次不是登录不是注册而是预约相关请求。所以这里写type YUYUE就是在给服务器打标签。第三部分序列化并发送string send_str a.toStyledString(); send(sockfd, send_str.c_str(), strlen(send_str.c_str()), 0);这里做了两件事第一toStyledString()把 JSON 对象变成字符串。第二send()通过 socket 发送给服务器。为什么不能直接发a因为网络传输只能发字节流不能直接发一个 C 里的 JSON 对象。所以必须先变成字符串再发出去。3这段代码在项目流程中的位置这段代码在整个预约流程里属于起点。也就是说这是客户端第一次发起预约业务它不是选票它只是先问服务器“能预约的票有哪些”所以这一步的本质不是“预约成功”而是先申请拿到一份可预约列表。四、服务端收到请求后怎么判断该进预约业务服务端还是沿用你前面登录、注册时的老套路先recv()收包再parse()反序列化再看type再根据type分发到不同函数1先看业务映射表代码Recv_CallBack::Recv_CallBack(int fd) { c fd; // 注册业务 cho_table.insert(make_pair(ZC, ZC)); // 登录业务 cho_table.insert(make_pair(DL, DL)); // 预约业务 cho_table.insert(make_pair(YUYUE, YUYUE)); // 查看业务 cho_table.insert(make_pair(CK, CK)); // 取消预约业务 cho_table.insert(make_pair(QXYY, QXYY)); }2这段代码在做什么这里本质上是在建立一张“翻译表”。比如客户端发ZC服务器知道你要注册客户端发DL服务器知道你要登录客户端发YUYUE服务器就知道你现在是预约业务所以这里的作用可以概括成一句话把 JSON 里的业务类型字符串映射成程序内部要执行的业务逻辑。3再看业务分发代码switch (cho_type) { case ZC: Zc_User(); break; case DL: Dl_User(); break; case YUYUE: Show_ticket(); break; case CK: Ck_User(); break; case QXYY: Qxyy_User(); break; default: Send_err(); break; }4这段代码在做什么这一步就是服务端的“总调度中心”。当服务器发现type YUYUE就会进入Show_ticket();这说明客户端发起预约查看请求以后服务端会进入Show_ticket()来处理。所以Show_ticket()是服务端预约第一步的核心函数。五、服务端Show_ticket()到底在做什么这是这一节最关键的函数之一。你可以先用一句话记住它的作用Show_ticket()的职责就是把数据库里的票务列表查出来封装成 JSON再发给客户端。1先看完整代码void Recv_CallBack::Show_ticket() { // 第一步创建数据库对象 // 后面所有数据库操作都通过这个对象完成 Mysql_Client mysqlcli; // 第二步初始化 MySQL if (!mysqlcli.Init()) { Send_err(); return; } // 第三步连接数据库服务器 if (!mysqlcli.Connect_mysql_ser()) { Send_err(); return; } // 第四步执行查询 SQL // 从票务表 ticket_table 中查出所有票务信息 string sql string(select * from ticket_table); if (!mysqlcli.Query_sql(sql)) { Send_err(); mysqlcli.Close(); return; } // 第五步定义一个 JSON 对象 res_val // 用来保存最终要返回给客户端的预约列表 Json::Value res_val; // 第六步把数据库结果集封装进 JSON if (!mysqlcli.Read_YuYue(res_val)) { Send_err(); mysqlcli.Close(); return; } // 第七步数据库任务完成后关闭连接 mysqlcli.Close(); // 第八步把 JSON 对象序列化成字符串 string res_str res_val.toStyledString(); // 第九步通过 socket 发给客户端 send(c, res_str.c_str(), strlen(res_str.c_str()), 0); }这段代码就是预约第一步的服务端核心逻辑。2逐段讲解这段代码第一部分创建数据库对象Mysql_Client mysqlcli;这一步说明服务端并没有直接把所有数据库操作都写在Show_ticket()里面而是先创建一个数据库类对象通过它去做初始化连接查表读取结果关闭连接这就是“封装”的思想。你可以把Mysql_Client理解成一个专门帮你和数据库打交道的“中间人”。第二部分初始化和连接数据库if (!mysqlcli.Init()) { Send_err(); return; } if (!mysqlcli.Connect_mysql_ser()) { Send_err(); return; }这两步和前面登录时一模一样Init()先把 MySQL 客户端对象准备好Connect_mysql_ser()真正连到数据库服务器也就是说这节课虽然业务从“登录”切到了“预约”但数据库操作的大框架没有变。第三部分执行 SQL 查询票务表string sql string(select * from ticket_table); if (!mysqlcli.Query_sql(sql)) { Send_err(); mysqlcli.Close(); return; }这条 SQL 非常重要。它的意思是从ticket_table表里把所有票务记录都查出来。这里你要特别注意前面登录查的是用户表user_info现在预约查的是票务表ticket_table。说明项目已经开始从“用户系统”走向“具体业务系统”了。第四部分为什么要定义res_valJson::Value res_val;这里的res_val是一个 JSON 对象用来装最终发给客户端的数据。也就是说数据源来自数据库但不能把数据库结果集原样发给客户端必须先整理成客户端和服务器约定好的 JSON 协议格式所以这一步的本质是先准备一个“响应包”。第五部分调用Read_YuYue(res_val)if (!mysqlcli.Read_YuYue(res_val)) { Send_err(); mysqlcli.Close(); return; }这是今天最关键的一步之一。因为 SQL 虽然执行了但结果还放在 MySQL 的结果集里。你还没有真正把这些票务信息“读出来”。Read_YuYue()干的事就是把数据库里的多条票务记录一行一行读出来并封装成 JSON 数组放进res_val。这一步是今天最核心的协议转换过程。第六部分发给客户端string res_str res_val.toStyledString(); send(c, res_str.c_str(), strlen(res_str.c_str()), 0);和前面的登录、注册一样先把 JSON 变成字符串再通过 socket 发送给客户端也就是说预约第一步到这里正式闭环客户端发查看请求→ 服务器查票务表→ 服务器返回票务列表。六、今天最重要的知识点Read_YuYue()为什么这么写如果说Show_ticket()是预约第一步的“主流程函数”那Read_YuYue()就是这节课最有技术含量的“封装函数”。因为它真正完成了把数据库结果集转成 JSON 数组。1先看完整代码bool Mysql_Client::Read_YuYue(Json::Value res_val) { // 第一步从 mysql_con 中取出 SQL 执行后的结果集 MYSQL_RES *r mysql_store_result(mysql_con); if (r NULL) { return false; } // 第二步统计结果集中有多少条票务记录 int num mysql_num_rows(r); // 把状态和条数先写入返回 JSON res_val[status] OK; res_val[num] num; // 如果数据库里没有任何票务记录 // 也返回一个合法 JSON只是 num 0 if (num 0) { mysql_free_result(r); return true; } // 第三步定义 row 用来一行一行读取数据库记录 MYSQL_ROW row; // i 用来表示 JSON 数组下标 int i 0; // 第四步循环读取结果集中的每一行 while ((row mysql_fetch_row(r)) ! NULL) { // 定义一个 JSON 对象 one_ticket // 表示一张票的信息 Json::Value one_ticket; // 把数据库表中的每一列按字段名放进 JSON 对象 one_ticket[ticket_id] row[0]; one_ticket[ticket_name] row[1]; one_ticket[ticket_max] row[2]; one_ticket[count] row[3]; one_ticket[day_time] row[4]; one_ticket[status] row[5]; // 把这一张票对象放进 ticket_arr 数组里 res_val[ticket_arr][i] one_ticket; i; } // 第五步释放结果集内存 mysql_free_result(r); return true; }这段代码就是把“数据库行”变成“JSON 数组”的关键函数。2逐段讲解这段代码第一部分先把结果集拿出来MYSQL_RES *r mysql_store_result(mysql_con);前面的Query_sql(sql)只是让数据库执行了查询。但查出来的数据还在数据库内部。mysql_store_result()的作用就是把 SQL 查询后的结果集真正拿到程序里。你可以把它理解成Query_sql()去仓库里找货mysql_store_result()把货从仓库里搬出来第二部分先看有几条票int num mysql_num_rows(r); res_val[status] OK; res_val[num] num;这里先数结果集里一共有多少条记录。然后先把两个字段写进 JSONstatusnum这说明最终返回给客户端的 JSON 外层至少有这两个成员{ status: OK, num: 2 }所以客户端后面可以先看返回状态对不对一共有几条票务信息第三部分为什么num 0也不算报错if (num 0) { mysql_free_result(r); return true; }这一步很容易被忽略但特别重要。如果数据库里当前没有票务信息程序并不是返回false也不是直接报错。而是返回一个合法的 JSON只不过num 0这样客户端就可以根据这个结果提示当前没有可预约信息。这是一种很好的“协议设计”思路没有数据不等于程序出错。第四部分为什么要 while 循环读取while ((row mysql_fetch_row(r)) ! NULL)因为你查询出来的不是一条票而是很多条票。所以这里不能像登录那样只读一行而是要一行一行读直到结果集读完为止。这一步说明预约列表是多条记录不是单条记录。这也正是这节课为什么要引入 JSON 数组的根本原因。第五部分一行数据库记录怎么变成一个 JSON 对象Json::Value one_ticket; one_ticket[ticket_id] row[0]; one_ticket[ticket_name] row[1]; one_ticket[ticket_max] row[2]; one_ticket[count] row[3]; one_ticket[day_time] row[4]; one_ticket[status] row[5];这里就是“数据库字段”到“JSON 字段”的映射过程。一行数据库记录本来只是第 0 列第 1 列第 2 列第 3 列第 4 列第 5 列但客户端如果收到的只是row[0] row[1] row[2]这种形式是看不懂的。所以必须重新加上清晰的字段名。于是就变成ticket_idticket_nameticket_maxcountday_timestatus这一步本质上是在做把数据库里的“表格行”转成网络传输里的“结构化对象”。第六部分为什么要放进ticket_arr[i]res_val[ticket_arr][i] one_ticket; i;这句就是今天最关键的 JSON 数组操作。含义是ticket_arr是一个数组数组的第i个元素是一张票的对象也就是说ticket_arr[0] 第一张票 ticket_arr[1] 第二张票 ticket_arr[2] 第三张票这就是为什么说一张票是一个 JSON 对象很多张票组成一个 JSON 数组。3最后形成的 JSON 长什么样最后服务端发给客户端的大概会长这样{ status: OK, num: 2, ticket_arr: [ { ticket_id: 1, ticket_name: 历史博物馆, ticket_max: 100, count: 20, day_time: 2026-04-01, status: 1 }, { ticket_id: 2, ticket_name: 图书馆, ticket_max: 50, count: 10, day_time: 2026-04-02, status: 1 } ] }这个结构你不用死记每个括号但你一定要记住三个关键成员statusnumticket_arr这三个字段就是客户端后面解析列表时的核心。七、客户端收到以后怎么解析并显示这些票务信息这一步也很重要因为客户端已经开始不只是“收包”而是要把结果真正展示出来。1先看代码string status res_val[status].asString(); if (status.compare(OK) ! 0) { cout 获取可预约信息失败 endl; return; } int ticket_num res_val[num].asInt(); if (ticket_num 0) { cout 当前没有可预约信息 endl; return; } cout 当前可预约信息如下 endl; for (int i 0; i ticket_num; i) { Json::Value one res_val[ticket_arr][i]; cout 票id: one[ticket_id].asString() ; cout 名称: one[ticket_name].asString() ; cout 最大票数: one[ticket_max].asString() ; cout 已预约: one[count].asString() ; cout 日期: one[day_time].asString() endl; }这段代码说明客户端已经形成了完整的“接收—校验—解析—显示”流程。2逐段讲解第一步先看状态码string status res_val[status].asString(); if (status.compare(OK) ! 0) { cout 获取可预约信息失败 endl; return; }这说明客户端不会一收到包就直接开始打印。它会先判断协议是不是成功响应。这就是一种“防御式编程”先确认包是不是正常再继续往下处理。第二步再看一共有几条票int ticket_num res_val[num].asInt(); if (ticket_num 0) { cout 当前没有可预约信息 endl; return; }这一步说明如果服务器虽然成功返回了但数据库里确实没有票客户端就应该提示“没有可预约信息”而不是盲目进入for循环。第三步循环遍历数组for (int i 0; i ticket_num; i) { Json::Value one res_val[ticket_arr][i]; ... }这里说明客户端已经知道ticket_arr是数组数组里每个元素都是一张票的 JSON 对象所以它一条一条取出来再逐字段打印。第四步为什么不是直接打印整包 JSON因为整包 JSON 虽然机器容易处理但人看起来不够直观。而你这里把它打印成票 id名称最大票数已预约日期这样更适合终端用户去看。也就是说这一步其实已经开始在做“界面展示层”的工作了。八、这一节课真正学到的知识点我帮你串起来1. 预约功能要拆成两步第六节课不是一步做到预约成功而是先看列表再正式预约。今天完成的是第一步。2. 多条同类数据必须用 JSON 数组前面登录注册是单条对象这里票务列表是多条对象所以要用数组。3. 一条数据库记录要转换成一个 JSON 对象数据库里是一行一行的表格网络协议里则是一张票一个 JSON 对象。4.Read_YuYue()是数据库封装升级后的体现前面数据库类主要负责“查”现在数据库类开始负责“查完之后还要封装成 JSON 数组”。5. 客户端解析层也开始变复杂客户端不再只看一个status而是要解析statusnumticket_arr说明协议复杂度上升了。九、最适合你复习的版本详细复习版第六节课主要实现了预约功能的第一步也就是“查看可预约信息”。客户端通过Show_ticket()发送一个typeYUYUE的 JSON 请求给服务器表示自己要查看当前可预约的票务列表。服务器收到请求后通过业务分发进入Show_ticket()连接数据库并执行select * from ticket_table从票务表中查询所有票务记录。由于返回结果不再是一条数据而是多条票务信息所以这节课引入了 JSON 数组。服务器通过Read_YuYue()把数据库结果集逐行读取出来并把每一行封装成一个 JSON 对象再统一放入ticket_arr数组中最后把包含status、num和ticket_arr的 JSON 返回给客户端。客户端收到后会先检查状态码再检查票务数量最后遍历数组并把票务信息打印出来。这样预约功能的第一步就形成了完整闭环客户端发请求服务器查库并封装 JSON 数组客户端解析并展示预约列表。简短背诵版第六节课先做预约功能的第一步查看可预约列表。客户端发送typeYUYUE给服务器服务器查询ticket_table再通过Read_YuYue()把数据库结果集封装成包含status、num和ticket_arr的 JSON 返回。ticket_arr是 JSON 数组里面每个元素都是一张票的对象。客户端收到后再解析并打印列表。十、最后给你一个“死记版”预约功能不是一步完成而是先看列表再正式预约。今天完成的是第一步Show_ticket()。多条票务信息要用 JSON 数组传。ticket_arr里的每个元素本质上都是一个 JSON 对象。Read_YuYue()的作用是把数据库结果集封装成 JSON 协议。客户端已经能完整接收、校验、解析并显示预约列表。你要是愿意我下一条可以继续把这一节再整理成你最喜欢的那种格式预约 分两步 第一步 先预约 将能预约的信息打印出来第二步 选择预约哪一个信息 对应的函数第一步:客户端要给服务器端发一个信息告诉它要预约。 然后服务器 会给客户端 回一个可以预约的信息。这算一次交互查看可预约的信息 Show_ticket()第二部根据服务器反馈的信息选择一条我们要预约的给服务器端服务器给我们回复状态YD_ticket()服务器 创建数据库的表用来记录所有票据信息。