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;// 验证状态为LOCKEDassert(sm.getCurrentState() == LoginState::LOCKED);return 0;
}
1.4 Makefile
# 添加目标
TGT := appCUR_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 环境准备
-
确保远程Ubuntu系统已安装以下工具:
- g++ (GNU C++编译器)
- gdb (GNU调试器)
- make (构建工具)
- VSCode Remote-SSH扩展
-
使用以下命令安装所需工具:
sudo apt update
sudo apt install g++ gdb make
2.2 项目设置
- 在远程Ubuntu上创建项目目录:
mkdir login_state_machine
cd login_state_machine
-
将上述源码文件保存到项目目录中
-
使用VSCode Remote-SSH连接到远程Ubuntu,打开项目目录
2.3 构建和运行
-
使用Makefile手动构建:
# 调试版本 make DEBUG=1# 发布版本 make# 清理构建文件 make clean
-
使用VSCode任务构建:
- 按下
Ctrl+Shift+P
,输入"Tasks: Run Task" - 选择相应的构建任务(调试/发布/清理)
- 按下
-
使用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 状态机设计模式
状态机是一种行为设计模式,允许对象在其内部状态改变时改变其行为。在这个项目中:
- 状态(State):定义了对象在不同情况下的行为
- 事件(Event):触发状态转换的外部输入
- 转换(Transition):状态之间根据事件发生的迁移
3.2 Makefile 关键概念
- 变量定义:使用变量简化和维护构建规则
- 自动发现:使用
wildcard
和find
自动发现源文件和头文件 - 模式规则:使用
%.o: %.cpp
定义通用编译规则 - 条件编译:使用
ifdef
区分调试和发布版本
3.3 VSCode 调试配置
- preLaunchTask:调试前自动执行构建任务
- problemMatcher:解析编译器输出,在IDE中显示错误
- setupCommands:配置GDB初始化命令
- 变量替换:使用
${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; // 不同用户}
}
这种策略确保:
- 同一用户的多次尝试只更新密码字段
- 不同用户的尝试会完全更新登录数据
- 避免不必要的数据复制
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 编译问题
- 头文件找不到:检查
HEADER_DIRS
是否正确发现了头文件目录 - 链接错误:确保所有必要的源文件都包含在
SRC
变量中 - 权限问题:确保对项目目录有读写权限
5.2 调试问题
- 断点不生效:确保使用
DEBUG=1
编译以生成调试信息 - 变量查看不到:检查GDB的pretty-printing是否正常工作
- 调试器连接失败:确认
miDebuggerPath
指向正确的GDB路径
5.3 运行时问题
- 状态转移异常:检查事件处理逻辑,特别是递归调用部分
- 数据不一致:验证数据更新策略是否正确处理了各种情况
- 多线程问题:如果添加了超时处理,注意线程安全问题
这个项目提供了一个完整的C++状态机实现,结合了现代开发工具链的最佳实践,是学习C++编程、状态机设计和开发环境配置的优秀示例。