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++編程、狀態機設計和開發環境配置的優秀示例。