服务器预约系统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. 这节课的意义

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

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


一段背诵版总结

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

相关推荐
@兽血沸腾22 分钟前
Windows系统安装Jenkins部署应用至远程Windows服务器操作指南
运维·服务器·jenkins
讯捷蓝达6 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
数字新视界8 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落8 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
lisanmengmeng9 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮9 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
上火的金鱼妹10 小时前
k8s生产实际问题排查(网关,数据库读写等)
运维·服务器
孙克旭_10 小时前
k8s基础组件与创建资源
运维·docker·容器·kubernetes
孙克旭_10 小时前
云原生基础之Containerd容器运行时
运维·云原生
分布式存储与RustFS10 小时前
RustFS 自建对象存储实战目录:110 篇按场景分类(持续更新)
运维·云原生·开源·对象存储·分布式存储·s3·性能基准