Bootstrap

【项目学习】C++实现高并发服务器——代码学习(三)用户注册登录功能

项目来源:WebServer

上一篇:存储解析HTTP请求报文,创建响应报文
本文介绍以下功能的代码实现

  • 利用RAII机制实现了数据库连接池,减少数据库连接建立与关闭的开销,同时实现了用户注册登录功能。

一、RAII机制介绍

RAII全程为Resource Acquisition Is Initialization(资源获取即初始化),RAII是C++语法体系中的一种常用的合理管理资源避免出现内存泄漏的常用方法。以对象管理资源,利用的就是C++构造的对象最终会被对象的析构函数销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。

RAII是合理管理资源避免出现内存泄漏的常用方法。那么所谓的资源是如何进行定义的呢?在计算机系统中,资源是数量有限且对系统正常运行具有一定作用的元素。比如:网络套接字、互斥锁、文件句柄和内存等等,它们属于系统资源。由于系统的资源是有限的,就好比自然界的石油,铁矿一样,不是取之不尽,用之不竭的,所以,我们在编程使用系统资源时,都必须遵循一个步骤:

申请资源→使用资源→释放资源

如果在申请和使用资源后未进行资源的释放,此时就造成了资源的泄漏。资源使用后必须要将其释放。

使用RAII机制的优点

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源只在其生命期内始终保持有效。

RAII机制的使用方法

由于系统的资源不具有自动释放的功能,而C++中的类具有自动调用析构函数的功能。如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用程序员显示的去调用释放资源的操作了。

二、RAII机制应用在数据库连接池

数据库连接池: sqlconnpool.h

class SqlConnPool {
public:
    static SqlConnPool *Instance();//静态实例

    MYSQL *GetConn();//获取连接
    void FreeConn(MYSQL * conn);//释放连接
    int GetFreeConnCount();//获取空闲的数量

    void Init(const char* host, int port,//主机名  端口
              const char* user,const char* pwd, //用户名  密码
              const char* dbName, int connSize);//数据库名  连接大小
    void ClosePool();

private:
    SqlConnPool();
    ~SqlConnPool();

    int MAX_CONN_;//最大连接数
    int useCount_;//当前用户数
    int freeCount_;//空闲的用户

    std::queue<MYSQL *> connQue_;
    std::mutex mtx_;//互斥锁
    sem_t semId_;//信号量
};

SqlConnRAII.h

/* 资源在对象构造初始化 资源在对象析构时释放*/
class SqlConnRAII {
public:
    SqlConnRAII(MYSQL** sql, SqlConnPool *connpool) {
        assert(connpool);
        *sql = connpool->GetConn();
        sql_ = *sql;
        connpool_ = connpool;
    }
    
    ~SqlConnRAII() {
        if(sql_) { connpool_->FreeConn(sql_); }//连接放回池子里
    }
    
private:
    MYSQL *sql_;
    SqlConnPool* connpool_;
};

三、用户注册登录功能

void HttpRequest::ParseBody_(const string& line) {
    body_ = line;
    ParsePost_();
    state_ = FINISH;
    LOG_DEBUG("Body:%s, len:%d", line.c_str(), line.size());
}

在解析请求报文体的函数中ParsePost_ 用来解析POST请求报文

void HttpRequest::ParsePost_() {
    if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {
        ParseFromUrlencoded_();
        if(DEFAULT_HTML_TAG.count(path_)) {
            int tag = DEFAULT_HTML_TAG.find(path_)->second;
            LOG_DEBUG("Tag:%d", tag);
            if(tag == 0 || tag == 1) {//登录或者注册界面
                bool isLogin = (tag == 1);//是否登录
                if(UserVerify(post_["username"], post_["password"], isLogin)) {
                    path_ = "/welcome.html";
                } 
                else {
                    path_ = "/error.html";
                }
            }
        }
    }   
}

运行程序
在这里插入图片描述
输入服务器ip,端口。进入注册界面。同时按F12打开网页源代码
在这里插入图片描述
点击“注册”之后能看到浏览器发送的POST请求报文
以及Content-Type: application/x-www-form-urlencoded
在这里插入图片描述
对应报文的body是注册时输入的账户密码
在这里插入图片描述
对应程序中UserVerify(post_[“username”], post_[“password”], isLogin)) 验证是否登录状态以及账户密码是否正确

bool HttpRequest::UserVerify(const string &name, const string &pwd, bool isLogin) {//验证账户密码
    if(name == "" || pwd == "") { return false; }
    LOG_INFO("Verify name:%s pwd:%s", name.c_str(), pwd.c_str());
    MYSQL* sql;
    SqlConnRAII(&sql,  SqlConnPool::Instance());
    assert(sql);
    
    bool flag = false;
    unsigned int j = 0;
    char order[256] = { 0 };
    MYSQL_FIELD *fields = nullptr;
    MYSQL_RES *res = nullptr;
    
    if(!isLogin) { flag = true; }
    /* 查询用户及密码 */
    snprintf(order, 256, "SELECT username, password FROM user WHERE username='%s' LIMIT 1", name.c_str());
    LOG_DEBUG("%s", order);

    if(mysql_query(sql, order)) { 
        mysql_free_result(res);
        return false; 
    }
    res = mysql_store_result(sql);
    j = mysql_num_fields(res);
    fields = mysql_fetch_fields(res);

    while(MYSQL_ROW row = mysql_fetch_row(res)) {//提取行
        LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
        string password(row[1]);
        /* 注册行为 且 用户名未被使用*/
        if(isLogin) {
            if(pwd == password) { flag = true; }
            else {
                flag = false;
                LOG_DEBUG("pwd error!");
            }
        } 
        else { 
            flag = false; 
            LOG_DEBUG("user used!");
        }
    }
    mysql_free_result(res);

    /* 注册行为 且 用户名未被使用*/
    if(!isLogin && flag == true) {
        LOG_DEBUG("regirster!");
        bzero(order, 256);
        snprintf(order, 256,"INSERT INTO user(username, password) VALUES('%s','%s')", name.c_str(), pwd.c_str());
        LOG_DEBUG( "%s", order);
        if(mysql_query(sql, order)) { 
            LOG_DEBUG( "Insert error!");
            flag = false; 
        }
        flag = true;
    }
    SqlConnPool::Instance()->FreeConn(sql);//连接放回池子
    LOG_DEBUG( "UserVerify success!!");
    return flag;
}
;