目錄
🍉整體內容
🌼流程圖
🎂載入數據庫表
提取用戶名和密碼
🚩同步線程登錄注冊
補充解釋
代碼
😘頁面跳轉
補充解釋
代碼
🍉整體內容
概述
TinyWebServer 中,使用數據庫連接池實現服務器訪問數據庫的功能,使用 POST請求 完成 注冊和登錄的校驗工作
內容
本博客介紹同步實現注冊登錄功能,具體涉及:流程圖,載入數據庫表,提取用戶名和密碼,注冊登錄流程,以及頁面跳轉的代碼實現
- 流程圖
服務器從報文中提取用戶名密碼,接著,完成注冊登錄校驗后,實現頁面跳轉邏輯- 載入數據庫表
將數據庫的數據載入服務器- 提取用戶名和密碼
解析報文,提取用戶名和密碼- 注冊登錄流程
描述服務器注冊和登錄校驗的流程- 頁面跳轉
詳解頁面跳轉機制
🌼流程圖
具體地,描述了 GET 和 POST 請求下的頁面跳轉流程👇
🎂載入數據庫表
將數據庫的用戶名和密碼,載入服務器的 map 中,map中,key是用戶名,value是密碼
// 用戶名和密碼
map<string, string> users;void http_conn::initmysql_result(connection_pool *connPool)
{// 先從連接池取一個連接MYSQL *mysql = NULL;connectionRAII mysqlcon(&mysql, connPool); // 利用connectionRAII封裝的RAII機制獲取數據庫連接// 在 user 表中檢索username, passwd數據,瀏覽器輸入if (mysql_query(mysql, "SELECT username,passwd FROM user")) // 執行查詢語句{LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); // 輸出錯誤信息}// 表中檢索完整的結果集MYSQL_RES *result = mysql_store_result(mysql); // 存儲查詢結果// 返回結果集中的列數int num_fields = mysql_num_fields(result); // 獲取結果集中列的數量// 返回所有字段結構的數組MYSQL_FIELD *fields = mysql_fetch_fields(result); // 獲取結果集中所有字段的信息// 從結果集獲取下一行,將對應用戶名和密碼,存入 mapwhile (MYSQL_ROW row = mysql_fetch_row(result)) // 迭代每一行數據{string temp1(row[0]); // 提取用戶名string temp2(row[1]); // 提取密碼users[temp1] = temp2; // 將用戶名和密碼存入map中}
}
提取用戶名和密碼
服務器解析瀏覽器的請求報文,當解析為POST請求時,cgi 標志位設置為1,并將請求報文的消息體賦值給 m_string,進而提取出用戶名和密碼
// 用戶名和密碼
map<string, string> users;void http_conn::initmysql_result(connection_pool *connPool)
{// 先從連接池取一個連接MYSQL *mysql = NULL;connectionRAII mysqlcon(&mysql, connPool); // 利用connectionRAII封裝的RAII機制獲取數據庫連接// 在 user 表中檢索username, passwd數據,瀏覽器輸入if (mysql_query(mysql, "SELECT username,passwd FROM user")) // 執行查詢語句{LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); // 輸出錯誤信息}// 表中檢索完整的結果集MYSQL_RES *result = mysql_store_result(mysql); // 存儲查詢結果// 返回結果集中的列數int num_fields = mysql_num_fields(result); // 獲取結果集中列的數量// 返回所有字段結構的數組MYSQL_FIELD *fields = mysql_fetch_fields(result); // 獲取結果集中所有字段的信息// 從結果集獲取下一行,將對應用戶名和密碼,存入 mapwhile (MYSQL_ROW row = mysql_fetch_row(result)) // 迭代每一行數據{string temp1(row[0]); // 提取用戶名string temp2(row[1]); // 提取密碼users[temp1] = temp2; // 將用戶名和密碼存入map中}
}
🚩同步線程登錄注冊
通過 m_url 定位 / 所在位置,根據 / 后第一個字符,判斷是登錄還是注冊校驗
- 2
- 登錄校驗
- 3
- 注冊校驗
根據校驗結果,跳轉對應頁面;此外,對數據庫操作時,需要通過鎖來同步
補充解釋
首先通過解析URL判斷用戶是要進行注冊還是登錄操作,這是通過檢查URL中的下一個字符來實現的
如果是注冊操作,首先會檢查數據庫中是否已經存在相同的用戶名,如果不存在則向數據庫中插入新的用戶名和密碼,并在map中記錄該用戶的信息
如果是登錄操作,會直接在map中查找用戶輸入的用戶名和密碼,如果存在且匹配,則返回歡迎頁面,否則返回登錄錯誤頁面
無論是注冊還是登錄,操作完成后都會修改URL,將用戶重定向到相應的頁面,以提供反饋給用戶
std::strrchr - cppreference.com
👆返回字符串中,最后一次出現該字符的位置?
std::strcpy - cppreference.com
👆strcpy(dest, src)? ? ? ?src 復制到 dest
std::strcat - cppreference.com
👆strcat(dest, src)? ? ?src 追加到 dest 后
代碼
const char *p = strrchr(m_url, '/'); // 在字符串 m_url 中查找最后一次出現字符 '/' 的位置,并返回指向該位置的指針if (0 == m_SQLVerify) {if (*(p + 1) == '3') // 如果 URL 中的下一個字符是 '3'{// 如果是注冊,先檢測數據庫中是否有重名// 沒有重名,就增加數據char *sql_insert = (char *)malloc(sizeof(char) * 200); // 分配內存空間strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); // 拼接SQL語句strcat(sql_insert, "'"); // 拼接SQL語句strcat(sql_insert, name); // 拼接SQL語句strcat(sql_insert, "', '"); // 拼接SQL語句strcat(sql_insert, "password"); // 拼接SQL語句strcat(sql_insert, "')"); // 拼接SQL語句// 判斷 map 中能否找到重復的用戶名if (user.find(name) == users.end()) { // 如果在map中找不到重復的用戶名// 向數據庫插入數據時,需要通過鎖來同步數據m_lock.lock(); // 加鎖int res = mysql_query(mysql, sql_insert); // 執行SQL語句users.insert(pair<string, string>(name, password)); // 將用戶名和密碼插入map中m_lock.unlock(); // 解鎖// 校驗成功,跳轉登錄頁面if (!res)strcpy(m_url, "/log.html"); // 修改URL,跳轉至登錄頁面// 校驗失敗,跳轉注冊失敗頁面else strcpy(m_url, "/registerError.html"); // 修改URL,跳轉至注冊失敗頁面}else strcpy(m_url, "/registerError.html"); // 修改URL,跳轉至注冊失敗頁面}// 如果是登錄,直接判斷// 若瀏覽器輸入的用戶名和密碼在表中可以查找到,返回 1,否則返回 0else if (*(p + 1) == '2') { // 如果 URL 中的下一個字符是 '2'if (users.find(name) != users.end() && users[name]) // 如果在map中找到用戶名,并且密碼正確strcpy(m_url, "/welcome.html"); // 修改URL,跳轉至歡迎頁面elsestrcpy(m_url, "/logError.html"); // 修改URL,跳轉至登錄錯誤頁面}
}
😘頁面跳轉
通過 m_url 定位 / 所在位置,根據 / 后的第一個字符,使用分支語句實現頁面跳轉,具體👇
- 0
- 跳轉注冊頁面,GET
- 1
- 跳轉登錄頁面,GET
- 5
- 顯示圖片頁面,POST
- 6
- 顯示視頻頁面,POST
- 7
- 顯示關注頁面,POST
補充解釋
malloc - cppreference.com
1)👆動態分配內存
#include <stdio.h>
#include <stdlib.h>int main(void)
{int *p1 = malloc(4*sizeof(int)); // 分配足夠空間以存儲一個包含 4 個整數的數組int *p2 = malloc(sizeof(int[4])); // 同上,直接命名類型int *p3 = malloc(4*sizeof *p3); // 同上,無需重復類型名稱if(p1) {for(int n=0; n<4; ++n) // 填充數組p1[n] = n*n;for(int n=0; n<4; ++n) // 打印數組內容printf("p1[%d] == %d\n", n, p1[n]);}free(p1); // 釋放動態分配的內存free(p2);free(p3);
}
std::strncpy - cppreference.com
2)👆char *strncpy(char *dest, const char *src, size_t n)
src 復制到 dest,最多賦值 n 個字符,如果 src 長度 < n,dest 剩余部分空字節 \0 填充
eg:
#include <cstring>
#include <iostream>int main()
{const char* src = "hi";char dest[6] = {'a', 'b', 'c', 'd', 'e', 'f'};std::strncpy(dest, src, 5);std::cout << "The contents of dest are: ";for (char c : dest){if (c)std::cout << c << ' ';elsestd::cout << "\\0" << ' ';}std::cout << '\n';
}
前 5 個字符被替換為 h i \0 \0 \0,第 6 個字符保留原來的 f
The contents of dest are: h i \0 \0 \0 f
代碼
// 找到 url 中 / 所在位置,進而判斷 / 后第一個字符
const char *p = strrchr(m_url, '/');// 注冊頁面
if (*(p + 1) == '0') {// 分配內存以存儲 URL 字符串,使用類型轉換將返回的指針轉換為 char 類型指針char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/register.html");// 將注冊頁面的 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url_real, strlen(m_url_real));// 釋放內存free(m_url_real);
}// 登錄頁面
else if (*(p + 1) == '1') {char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/log.html");// 將登錄頁面的 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url_real, strlen(m_url_real));// 釋放內存free(m_url_real);
}// 圖片頁面
else if (*(p + 1) == '5') {char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/picture.html");// 將圖片頁面的 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url_real, strlen(m_url_real));// 釋放內存free(m_url_real);
}// 視頻頁面
else if (*(p + 1) == '6') {char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/vedio.html");// 將視頻頁面的 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url_real, strlen(m_url_real));// 釋放內存free(m_url_real);
}// 關注頁面
else if (*(p + 1) == '7') {char *m_url_real = (char *)malloc(sizeof(char) * 200);strcpy(m_url_real, "/fans.html");// 將關注頁面的 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url_real, strlen(m_url_real));// 釋放內存free(m_url_real);
}// 否則發送 url 實際請求的文件
else// 將原始 URL 復制到實際文件路徑中strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);