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 环境准备
确保远程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++编程、状态机设计和开发环境配置的优秀示例。