服务器预约系统linux小项目-第一节课

一、项目介绍

我们现在做的项目是一个预约系统 。系统主要由 服务器端(server)管理员端(admin)客户端(cli)MySQL 数据库 组成。

其中:

  • server 是系统核心,负责处理客户端请求、连接数据库、验证预约信息。

  • admin 主要负责设置预约信息,包括可预约人数、预约详细内容,也可以暂停预约和查看预约情况。

  • cli 是客户端,用户可以通过命令行完成登录、查询、查看已有预约、取消预约和退出系统等操作。

  • MySQL 用来保存预约系统中的各种数据,比如预约信息、用户信息和预约记录。

需要注意的是:

管理员和服务器不是直接耦合在一起的,它们之间没有直接关系,而是通过数据库进行关联。

整个系统可以理解为:

管理员设置数据 → 数据库存储 → 服务器读取并处理 → 客户端使用系统。

我们测试时可以先封装一个服务器端,然后做一个客户端

①让服务器端和客户端先连上用json来通信,只要能通讯不管是登录还是查询无非就是换内容就行。

②服务器收到数据后如何和数据库进行交互

二、数据库表设计

1. user_info(用户信息表)

存放用户的基本信息。

字段含义:

  • id_tel:用户账号或手机号

  • name:用户名

  • 密码:登录密码

  • 身份证:用户身份信息

  • 状态:用户当前是否可正常使用系统,比如正常、禁用

2. ticket_table(预约信息表)

存放管理员设置的预约项目。

字段含义:

  • res_id:预约项目编号

  • res_name:预约项目名称

  • 总数目:可预约总人数

  • 已使用:已经预约的人数

  • 是否开启预约:当前项目是否开放预约

3. query_res(预约记录表)

存放用户的预约结果。

字段含义:

  • id:用户编号

  • res_id:预约项目编号

  • 是否使用过:该预约是否已经使用

要先将表设计好,定好需求。

三、现阶段任务以及系统整体结构

3.1目前的目标主要有两点:

  1. 服务器端使用 libevent 启动起来

  2. 客户端能够连接服务器,并通过 JSON 把数据发送给服务器

也就是说,现阶段先完成:

  • 连接建立

  • 事件处理

  • 数据收发

等这一步跑通之后,再继续做数据库和具体业务。

四、服务器端

这里我们封装一个 libevent 类,用来实现服务器和客户端之间的连接与通信。

1. 为什么要封装 Libevent 类

服务器端不能只处理一个连接,而是要同时监听多个事件,所以这里使用 libevent 做事件驱动。

为了方便管理,我们把它封装成一个 Libevent 类。

1.1这个类主要提供三个核心功能:

  1. 初始化

    启动 libevent,完成事件机制的基础准备。

  2. 注册事件

    提供 add(fd, fun) 方法,给某个文件描述符 fd 绑定对应的回调函数 fun

    也就是告诉系统:

    • 监听哪个描述符

    • 当这个描述符上有事件发生时,调用哪个函数处理

  3. 事件循环

    事件循环启动后,libevent 会不断检测:①哪个描述符有事件②然后调用对应回调函数处理

一句话理解

libevent 启动后主要做两件事:监听描述符、为描述符绑定回调函数;然后进入事件循环,谁有事件就处理谁。

2.监听套接字和连接套接字

在服务器里,有两类很重要的套接字。


2.1 监听套接字 sock_listen

监听套接字的作用是:

专门负责等待客户端连接

它本身不负责和客户端真正通信,只负责监听"有没有新的客户端来连接服务器"。

所以服务器启动时,首先要创建监听套接字。


2.2 为什么监听套接字也要注册到 Libevent

因为 libevent 是事件驱动模型,谁要被监听,谁就要注册进去。

因此:

  • sock_listen 也要注册到 Libevent

  • 当有新的连接到来时,就会触发它对应的回调函数


2.3 新连接套接字的产生

当客户端连接服务器时:

  • 监听套接字检测到连接事件

  • 回调函数中调用 accept()

  • accept() 会返回一个新的套接字

这个新的套接字就是 连接套接字


2.4 连接套接字的作用

连接套接字不是负责监听新连接,而是负责:

  • 接收客户端数据

  • 给客户端返回数据

可以简单理解为:

  • sock_listen:负责"接人"

  • 新的连接套接字:负责"和这个人通信"


2.5 为什么新的连接套接字也要注册

因为客户端后续发消息,都是通过这个新连接套接字完成的。

所以:

  • 每产生一个新的连接套接字

  • 都要继续注册到 Libevent

  • 这样 libevent 才能继续监听它的读写事件

3.回调基类设计

3.1为什么要设计回调基类

服务器中会出现不同类型的事件,比如:

  • 有新的客户端连接

  • 某个客户端发送了数据

这两类事件处理逻辑不同,但本质上都属于:

事件发生后,需要执行一个回调函数

所以这里可以抽象出一个统一的基类。


3.2call_back 基类

定义一个回调基类:

  • call_back

在这个类中定义统一接口:

  • call_back_fun()

它的作用是:
规定所有回调类都必须实现这个函数。

基类只定义接口,不写具体处理逻辑。


3.3accept_call_back

这个类继承 call_back,主要负责处理:

监听套接字上的新连接事件

当监听套接字有事件时:

  • 调用 accept()

  • 生成新的连接套接字

  • 再把新的连接套接字注册到事件系统中

所以它负责的是:
连接建立


3.4recv_call_back

这个类也继承 call_back,主要负责处理:

连接套接字上的数据接收事件

当客户端发送数据时:

  • 调用 recv()

  • 接收客户端发来的数据

  • 后续再交给业务逻辑处理

所以它负责的是:
数据收发


3.5这样设计的意义

这种设计体现了 继承和多态 思想。

好处有:

  • 统一回调接口

  • 不同事件分开处理

  • 结构更清晰

  • 后续更容易扩展

也就是说:

  • 基类负责定义规范

  • 子类负责具体实现

  • libevent 根据不同事件调用不同对象的回调函数

四、代码展示

1.server.h

cpp 复制代码
#include <iostream>
#include <string>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

// 监听队列的最大长度
const int Max_listen = 10;

// 监听套接字类
// 作用:封装服务器监听 socket,完成 socket 创建、绑定和监听
class Lis_Socket
{
private:
    int sockfd;     // 监听套接字描述符
    string ip;      // 监听的 IP 地址
    short port;     // 监听的端口号

public:
    // 默认构造函数
    // 这里只做成员变量初始化,不做容易失败的系统调用
    Lis_Socket()
    {
        sockfd = -1;                 // -1 表示当前还没有创建 socket
        ip = "127.0.0.1";            // 默认监听本机地址
        port = 6000;                 // 默认监听端口
    }

    // 带参构造函数
    // 可以自定义监听的 IP 和端口
    Lis_Socket(const char* ips, short p)
    {
        sockfd = -1;
        ip = string(ips);
        port = p;
    }

    // 初始化监听套接字
    // 完成三件事:
    // 1. 创建 socket
    // 2. 绑定 IP 和端口
    // 3. 进入监听状态
    bool bind()
    {
        // 1. 创建 TCP 套接字
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            cout << "socket err" << endl;
            return false;
        }

        // 2. 准备服务器地址信息
        struct sockaddr_in saddr;
        memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family = AF_INET;                     // IPv4
        saddr.sin_port = htons(port);                  // 端口转网络字节序
        saddr.sin_addr.s_addr = inet_addr(ip.c_str()); // 字符串 IP 转网络地址

        // 3. 将 socket 绑定到指定 IP 和端口
        int res = ::bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
        if (res == -1)
        {
            cout << "bind err" << endl;
            return false;
        }

        // 4. 进入监听状态
        res = listen(sockfd, Max_listen);
        if (res == -1)
        {
            cout << "listen err" << endl;
            return false;
        }

        return true;
    }

    // 获取监听套接字描述符
    int Get_Sockfd()
    {
        return sockfd;
    }

    // 析构函数
    // 对象销毁时关闭 socket,防止资源泄漏
    ~Lis_Socket()
    {
        if (sockfd != -1)
        {
            close(sockfd);
            sockfd = -1;
        }
    }
};

2.server.cpp

cpp 复制代码
#include "server.h"
#include <stdlib.h>

int main()
{
    // 创建服务器监听对象
    Lis_Socket ser;

    // 初始化监听套接字
    if (!ser.bind())
    {
        exit(1);
    }

    cout << "server start success..." << endl;

    return 0;
}

五、本节课总结

这节课的核心目标,是先把 预约系统的客户端和服务器端通信框架 搭起来,而不是马上做数据库和完整业务。

整个项目后面会包括:

  • 客户端

  • 服务器端

  • 管理员端

  • 数据库

但当前阶段重点是先完成:客户端能连上服务器,服务器能接收客户端发来的数据。


1. 服务器端学了什么

服务器端这节课先封装了 监听套接字类 Lis_Socket,作用是完成服务器启动最基础的三步:

  1. socket() 创建 TCP 套接字

  2. bind() 绑定 IP 和端口

  3. listen() 开始监听客户端连接

也就是说,这一步先让服务器具备"在门口等客户端连接"的能力。

其中类里保存了:

  • sockfd:套接字描述符

  • ip:监听地址

  • port:监听端口

构造函数只负责初始化这些成员,真正可能失败的操作,比如创建 socket、绑定、监听,都放在普通成员函数里处理,这体现了封装思想。


2. 这节课还明确了两类套接字

监听套接字

专门负责等待新的客户端连接。

连接套接字

当客户端连进来后,通过 accept() 产生新的连接套接字,后续真正的数据收发是在这个套接字上完成的。

所以可以记成:

  • 监听套接字负责"接人"

  • 连接套接字负责"通信"


3. 为什么要用 Libevent

因为服务器后面不只处理一个客户端,还会处理多种事件,所以要用 libevent 做事件驱动。

这节课明确了 Libevent 类的三个核心功能:

  1. 初始化

  2. 注册事件 add(fd, fun)

  3. 事件循环 dispatch()

它的作用就是统一管理"哪个描述符发生了什么事,并调用哪个回调函数处理"。


4. 回调类设计学了什么

为了处理不同类型的事件,这节课引入了 回调基类 call_back ,并定义统一接口 call_back_fun()

然后再派生出不同子类:

  • accept_call_back:处理新连接事件

  • recv_call_back:处理接收数据事件

这样做的意义是:

  • 把不同事件的处理逻辑分开

  • 通过继承和多态统一管理回调

也就是:

虽然外部统一调用 call_back_fun(),但不同对象会执行不同逻辑。


5. 客户端这节课的目标

客户端当前阶段不用做复杂业务,重点是先完成:

  1. 连接服务器

  2. 从键盘读取输入

  3. 把数据封装成 JSON

  4. 发送给服务器

所以这一阶段客户端的重点不是业务功能,而是先把数据发过去。


6. 这节课整条主线

这节课完整流程可以概括为:

  • 服务器先创建监听套接字并注册到 Libevent

  • 客户端发起连接

  • 服务器通过回调函数处理连接事件,并生成新的连接套接字

  • 新的连接套接字继续注册到事件系统

  • 客户端把键盘输入封装成 JSON 发给服务器

  • 服务器再通过接收回调函数把数据接住

到这里,客户端和服务器端最基础的通信框架就搭起来了。


7. 这节课的意义

这节课虽然还没有接数据库,也没有完成登录、查询、预约这些业务,但已经把预约系统最底层的通信骨架搭好了。

也就是说,后面数据库、用户功能、管理员功能,都是在这套 "客户端发送数据 + 服务器事件驱动接收数据" 的框架上继续往下做。


一段背诵版总结

这节课主要完成了预约系统第一阶段的通信框架搭建。服务器端通过封装监听套接字类完成了 socketbindlisten 三个基础步骤,并明确了监听套接字和连接套接字的区别;同时引入 Libevent 管理事件,并通过回调基类及其子类实现不同事件的多态处理。客户端则负责连接服务器、读取键盘输入,并将数据封装成 JSON 发送给服务器。这样,整个系统最基础的客户端---服务器通信主线就建立起来了。

相关推荐
搬山境KL攻城狮3 小时前
ssh密钥对使用
运维·ssh
道亦无名3 小时前
Linux下是STM32的编译修改配置文件tensorflow
linux·运维
Azure DevOps4 小时前
Azure DevOps Server:2026年3月份补丁
运维·microsoft·azure·devops
User_芊芊君子10 小时前
影音自由新玩法:Plex+cpolar 解锁异地访问,告别网盘限速烦恼
服务器·nginx·测评
wanhengidc10 小时前
云手机的运行环境如何
运维·服务器·游戏·智能手机·生活
cyber_两只龙宝10 小时前
【Haproxy】Haproxy的算法详解及配置
linux·运维·服务器·云原生·负载均衡·haproxy·调度算法
阿常呓语10 小时前
Linux命令 jq详解
linux·运维·shell·jq
hy____12311 小时前
Linux_网络基础3
linux·服务器·网络
Mr. Cao code12 小时前
MySQL服务器配置与socket连接详解
服务器·数据库·mysql