C++ 登录状态机项目知识笔记

发布于:2025-09-01 ⋅ 阅读:(24) ⋅ 点赞:(0)

C++ 登录状态机项目知识笔记

1. 项目源码

1.1 login_state_machine.h

#pragma once

#include <string>

// 登录状态枚举
enum class LoginState { IDLE, AUTHENTICATING, SUCCESS, FAILURE, LOCKED };

// 登录事件枚举
enum class LoginEvent { REQUEST, SUCCESS, FAILURE, RETRY, TIMEOUT, LOGOUT };

// 登录数据结构体
struct LoginData {
    std::string username;
    std::string password;
    int attempt_count;
};

// 登录状态机类
class LoginStateMachine {
private:
    LoginState current_state;
    LoginData login_data;
    
    bool validateCredentials(const LoginData& data);
    void grantAccess();
    void showError();
    void lockAccount();
    
public:
    LoginStateMachine();
    void handleEvent(LoginEvent event, const LoginData* data);
    LoginState getCurrentState() const;
    void setCurrentState(LoginState state);
};

1.2 login_state_machine.cpp

#include "login_state_machine.h"
#include <iostream>
#include <string.h>

// 构造函数,初始化状态和数据
LoginStateMachine::LoginStateMachine() : current_state(LoginState::IDLE) {
    login_data.attempt_count = 0;
}

// 处理事件的核心方法
void LoginStateMachine::handleEvent(LoginEvent event, const LoginData* data) 
{
    static int count = 0;
    if (data != nullptr) 
    {
        const char* login_username = login_data.username.c_str();
        if(strlen(login_username) == strspn(login_username," /t"))
        {
            login_data = *data;
        }
        else if(!strcasecmp(data->username.c_str(),login_data.username.c_str()))
        {
            login_data.password = data->password;
        }
        else
        {
            login_data = *data;
        }
    }

    
    switch (current_state) 
    {
        case LoginState::IDLE:
            std::cout << "LoginState::IDLE" << std::endl;
            std::cout << login_data.username << "    " << login_data.password << std::endl;
            if (event == LoginEvent::REQUEST) 
            {
                current_state = LoginState::AUTHENTICATING;
                if (validateCredentials(login_data)) 
                {
                    handleEvent(LoginEvent::SUCCESS, nullptr);
                } 
                else 
                {
                    handleEvent(LoginEvent::FAILURE, nullptr);
                }
            }
            break;

        case LoginState::AUTHENTICATING:
            std::cout << "LoginState::AUTHENTICATING" << std::endl;
            std::cout << login_data.username << "    " << login_data.password << std::endl;
            if (event == LoginEvent::SUCCESS) 
            {
                grantAccess();
                login_data.attempt_count = 0; // 重置尝试次数
                current_state = LoginState::SUCCESS;
            } 
            else if (event == LoginEvent::FAILURE) 
            {
                login_data.attempt_count++;
                if (login_data.attempt_count >= 3) 
                {
                    lockAccount();
                    current_state = LoginState::LOCKED;
                } 
                else 
                {
                    showError();
                    current_state = LoginState::FAILURE;
                }
            } 
            else if (event == LoginEvent::TIMEOUT) 
            {
                showError();
                current_state = LoginState::FAILURE;
            }
            break;

        case LoginState::SUCCESS:
            std::cout << "LoginState::SUCCESS" << std::endl;
            std::cout << login_data.username <<"    "  << login_data.password << std::endl;
            if (event == LoginEvent::LOGOUT) 
            {
                current_state = LoginState::IDLE;
            }
            break;

        case LoginState::FAILURE:
            std::cout << "LoginState::FAILURE" << std::endl;
            std::cout << login_data.username<< "    "  << login_data.password << std::endl;
            if (event == LoginEvent::RETRY) 
            {
                current_state = LoginState::IDLE;
                event = LoginEvent::REQUEST;
                handleEvent(LoginEvent::REQUEST, &login_data);
            } 
            else if (event == LoginEvent::LOGOUT) 
            {
                current_state = LoginState::IDLE;
                std::cout << "Logout!!!" << std::endl;
            }
            else
            {
                count++;
                std::cout << "FAILURE count:" << count << std::endl;
            }
            break;

        case LoginState::LOCKED:
            std::cout << "LoginState::LOCKED" << std::endl;
            std::cout << login_data.username << "    "  << login_data.password << std::endl;
            // 锁定状态下不处理任何事件
            break;
    }
}

// 验证凭据的方法
bool LoginStateMachine::validateCredentials(const LoginData& data) 
{
    // 简单的验证逻辑:用户名和密码都是 "admin"
    return data.username == "admin" && data.password == "admin";
}

// 授权访问的方法
void LoginStateMachine::grantAccess() 
{
    std::cout << "Access granted! Welcome." << std::endl;
}

// 显示错误信息的方法
void LoginStateMachine::showError() 
{
    std::cout << "Authentication failed. Attempts: " 
              << login_data.attempt_count << std::endl;
}

// 锁定账户的方法
void LoginStateMachine::lockAccount() 
{
    std::cout << "Account locked due to too many failed attempts." << std::endl;
}

// 获取当前状态
LoginState LoginStateMachine::getCurrentState() const 
{
    return current_state;
}

// 设置当前状态
void LoginStateMachine::setCurrentState(LoginState state) 
{
    current_state = state;
}

1.3 main.cpp

#include "login_state_machine.h"
#include <cassert>
#include <iostream>

// 测试登录状态机
int main() 
{
    LoginStateMachine sm;
    LoginData data{"admin", "123", 0};
    
    // 第一次尝试
    sm.handleEvent(LoginEvent::REQUEST, &data);
    sm.handleEvent(LoginEvent::FAILURE, nullptr);
    std::cout << "-------------------------------------" << std::endl;
    
    // 第二次尝试
    data.password = "wrong";
    sm.handleEvent(LoginEvent::RETRY, &data);
    sm.handleEvent(LoginEvent::FAILURE, nullptr);
    std::cout << "-------------------------------------" << std::endl;
    
    // 第三次尝试 - 账户锁定
    data.password = "stillwrong";
    sm.handleEvent(LoginEvent::RETRY, &data);
    sm.handleEvent(LoginEvent::FAILURE, nullptr);
    std::cout << "-------------------------------------" << std::endl;
    
    // 验证状态为LOCKED
    assert(sm.getCurrentState() == LoginState::LOCKED);
    return 0;
}

1.4 Makefile

# 添加目标
TGT := app

CUR_DIR := $(shell pwd)

# 自动发现源文件
SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp,%.o,$(SRC))

# 自动发现头文件目录
HEADER_DIRS := $(shell find . -name "*.h" -exec dirname {} \; | sort | uniq)
INCLUDE_FLAGS := $(addprefix -I,$(HEADER_DIRS))

# cppflags 设置
CPPFLAGS := -pthread $(INCLUDE_FLAGS)

# cxxflags 设置 - 添加 -g 并移除 -O2 以支持调试
CXXFLAGS := -Wall -g -std=c++11

# 添加调试版本和发布版本的不同配置
ifdef DEBUG
CXXFLAGS += -O0
else
CXXFLAGS += -O2
endif

# 默认目标
all: $(TGT)
	@echo "构建成功"

# 链接目标
$(TGT): $(OBJ)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) $^ -o $@

# 编译规则
%.o: %.cpp
	$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< -o $@

# 清理目标
clean:
ifneq ($(wildcard $(OBJ)),)
	@rm $(OBJ)
else
	@echo "无需清理对象文件"
endif
ifneq ($(wildcard $(TGT)),)
	@rm $(TGT)
else
	@echo "无需清理可执行文件"
endif

# 仅清理对象文件
obj_clean:
ifneq ($(wildcard $(OBJ)),)
	@rm $(OBJ)
else
	@echo "无需清理对象文件"
endif

.PHONY: obj_clean clean all

1.5 tasks.json

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build with Makefile",
            "type": "shell",
            "command": "make",
            "args": [
                "DEBUG=1"
            ],
            "group": "build",
            "problemMatcher": [
                "$gcc"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "detail": "使用Makefile构建项目(调试模式)"
        },
        {
            "label": "Build Release with Makefile",
            "type": "shell",
            "command": "make",
            "args": [],
            "group": "build",
            "problemMatcher": [
                "$gcc"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "detail": "使用Makefile构建项目(发布模式)"
        },
        {
            "label": "Clean with Makefile",
            "type": "shell",
            "command": "make",
            "args": [
                "clean"
            ],
            "group": "build",
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "detail": "清理构建文件"
        }
    ]
}

1.6 launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug C++ Application",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/app",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set breakpoint at main",
                    "text": "break main",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build with Makefile",
            "miDebuggerPath": "/usr/bin/gdb"
        },
        {
            "name": "Run C++ Application",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/app",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build Release with Makefile",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}

2. 构建手顺和说明

2.1 环境准备

  1. 确保远程Ubuntu系统已安装以下工具:

    • g++ (GNU C++编译器)
    • gdb (GNU调试器)
    • make (构建工具)
    • VSCode Remote-SSH扩展
  2. 使用以下命令安装所需工具:

sudo apt update
sudo apt install g++ gdb make

2.2 项目设置

  1. 在远程Ubuntu上创建项目目录:
mkdir login_state_machine
cd login_state_machine
  1. 将上述源码文件保存到项目目录中

  2. 使用VSCode Remote-SSH连接到远程Ubuntu,打开项目目录

2.3 构建和运行

  1. 使用Makefile手动构建

    # 调试版本
    make DEBUG=1
    
    # 发布版本
    make
    
    # 清理构建文件
    make clean
    
  2. 使用VSCode任务构建

    • 按下Ctrl+Shift+P,输入"Tasks: Run Task"
    • 选择相应的构建任务(调试/发布/清理)
  3. 使用VSCode调试

    • 按下F5启动调试(使用调试版本)
    • 在调试侧边栏选择"Run C++ Application"运行发布版本

2.4 测试程序

运行编译后的程序:

./app

预期输出:

LoginState::IDLE
admin    123
LoginState::AUTHENTICATING
admin    123
Authentication failed. Attempts: 1
LoginState::FAILURE
admin    123
-------------------------------------
LoginState::IDLE
admin    wrong
LoginState::AUTHENTICATING
admin    wrong
Authentication failed. Attempts: 2
LoginState::FAILURE
admin    wrong
-------------------------------------
LoginState::IDLE
admin    stillwrong
LoginState::AUTHENTICATING
admin    stillwrong
Authentication failed. Attempts: 3
Account locked due to too many failed attempts.
LoginState::LOCKED
admin    stillwrong
-------------------------------------

3. 关键部分解释和说明

3.1 状态机设计模式

状态机是一种行为设计模式,允许对象在其内部状态改变时改变其行为。在这个项目中:

  1. 状态(State):定义了对象在不同情况下的行为
  2. 事件(Event):触发状态转换的外部输入
  3. 转换(Transition):状态之间根据事件发生的迁移
REQUEST事件
SUCCESS事件
FAILURE事件(尝试<3次)
FAILURE事件(尝试≥3次)
RETRY事件
LOGOUT事件
任何事件(锁定状态)
IDLE
AUTHENTICATING
SUCCESS
FAILURE
LOCKED

3.2 Makefile 关键概念

  1. 变量定义:使用变量简化和维护构建规则
  2. 自动发现:使用wildcardfind自动发现源文件和头文件
  3. 模式规则:使用%.o: %.cpp定义通用编译规则
  4. 条件编译:使用ifdef区分调试和发布版本

3.3 VSCode 调试配置

  1. preLaunchTask:调试前自动执行构建任务
  2. problemMatcher:解析编译器输出,在IDE中显示错误
  3. setupCommands:配置GDB初始化命令
  4. 变量替换:使用${workspaceFolder}等变量使配置更通用

3.4 数据管理策略

状态机中使用了智能数据更新策略:

if (data != nullptr) 
{
    const char* login_username = login_data.username.c_str();
    if(strlen(login_username) == strspn(login_username," /t"))
    {
        login_data = *data; // 初始数据或不同用户
    }
    else if(!strcasecmp(data->username.c_str(),login_data.username.c_str()))
    {
        login_data.password = data->password; // 同一用户更新密码
    }
    else
    {
        login_data = *data; // 不同用户
    }
}

这种策略确保:

  1. 同一用户的多次尝试只更新密码字段
  2. 不同用户的尝试会完全更新登录数据
  3. 避免不必要的数据复制

4. 进阶功能和扩展建议

4.1 单元测试集成

可以考虑集成Google Test等单元测试框架:

# 在Makefile中添加测试目标
TEST_TGT := test_app
TEST_SRC := $(wildcard test_*.cpp)
TEST_OBJ := $(patsubst %.cpp,%.o,$(TEST_SRC))

$(TEST_TGT): $(filter-out main.o,$(OBJ)) $(TEST_OBJ)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) $^ -lgtest -lgtest_main -pthread -o $@

test: $(TEST_TGT)
	./$(TEST_TGT)

4.2 日志系统增强

可以添加更完善的日志系统:

// 简单的日志级别定义
enum class LogLevel { DEBUG, INFO, WARNING, ERROR };

// 日志记录函数
void logMessage(LogLevel level, const std::string& message) {
    // 根据级别输出不同颜色的日志
    // 可以添加时间戳、文件名和行号等信息
}

4.3 配置文件支持

添加配置文件支持,使验证逻辑更灵活:

// 从配置文件加载有效凭据
std::map<std::string, std::string> loadCredentials(const std::string& filename) {
    std::map<std::string, std::string> credentials;
    // 读取文件并解析用户名-密码对
    return credentials;
}

// 修改验证逻辑使用配置文件
bool LoginStateMachine::validateCredentials(const LoginData& data) {
    static auto valid_credentials = loadCredentials("credentials.cfg");
    auto it = valid_credentials.find(data.username);
    return it != valid_credentials.end() && it->second == data.password;
}

4.4 超时处理增强

添加更完善的超时处理机制:

#include <chrono>
#include <thread>

// 在状态机中添加超时处理
void LoginStateMachine::startTimeoutTimer(int seconds) {
    std::thread([this, seconds]() {
        std::this_thread::sleep_for(std::chrono::seconds(seconds));
        if (this->current_state == LoginState::AUTHENTICATING) {
            this->handleEvent(LoginEvent::TIMEOUT, nullptr);
        }
    }).detach();
}

5. 故障排除和常见问题

5.1 编译问题

  1. 头文件找不到:检查HEADER_DIRS是否正确发现了头文件目录
  2. 链接错误:确保所有必要的源文件都包含在SRC变量中
  3. 权限问题:确保对项目目录有读写权限

5.2 调试问题

  1. 断点不生效:确保使用DEBUG=1编译以生成调试信息
  2. 变量查看不到:检查GDB的pretty-printing是否正常工作
  3. 调试器连接失败:确认miDebuggerPath指向正确的GDB路径

5.3 运行时问题

  1. 状态转移异常:检查事件处理逻辑,特别是递归调用部分
  2. 数据不一致:验证数据更新策略是否正确处理了各种情况
  3. 多线程问题:如果添加了超时处理,注意线程安全问题

这个项目提供了一个完整的C++状态机实现,结合了现代开发工具链的最佳实践,是学习C++编程、状态机设计和开发环境配置的优秀示例。


网站公告

今日签到

点亮在社区的每一天
去签到