sylar高性能服务器-日志(P74-p79)内容记录

文章目录

P74-P79HttpConnection

最后一个基础模块的封装,同样继承于SocketStream,实现发送请求报文,接收响应报文。此外实现了连接池、使用状态机解析URI

class Uri

Uri格式
c 复制代码
foo://user@example.com:8042/over/there?name=ferret#nose
_/   ___________________/_________/ _________/ __/
 |              |              |            |        |
scheme      authority         path        query   fragment
 |   __________________________|__
/ \ /                             \
urn:example:animal:ferret:nose

authority   = [ userinfo "@" ] host [ ":" port ]
成员变量
c 复制代码
/// schema
std::string m_scheme;
/// 用户信息
std::string m_userinfo;
/// host
std::string m_host;
/// 路径
std::string m_path;
/// 查询参数
std::string m_query;
/// fragment
std::string m_fragment;
/// 端口
int32_t m_port;
Create
c 复制代码
Uri::ptr Uri::Create(const std::string& uristr) {
    # 初始化一个uri对象
    Uri::ptr uri(new Uri);
    # 状态机初始状态
    int cs = 0;
    const char* mark = 0;
    %% write init;
    # 获取传入str的头节点
    const char *p = uristr.c_str();
    # 获取传入str的尾节点
    const char *pe = p + uristr.size();
    const char* eof = pe;
    %% write exec;
    # 如果在解析过程中出现错误,cs会变成uri_parser_error
    if(cs == uri_parser_error) {
        return nullptr;
    # 解析成功,返回uri
    } else if(cs >= uri_parser_first_final) {
        return uri;
    }
    return nullptr;
}

struct HttpResult

c 复制代码
/**
 * @brief HTTP响应结果
 */
struct HttpResult {
    /// 智能指针类型定义
    typedef std::shared_ptr<HttpResult> ptr;
    /**
     * @brief 错误码定义
     */
    enum class Error {
        /// 正常
        OK = 0,
        /// 非法URL
        INVALID_URL = 1,
        /// 无法解析HOST
        INVALID_HOST = 2,
        /// 连接失败
        CONNECT_FAIL = 3,
        /// 连接被对端关闭
        SEND_CLOSE_BY_PEER = 4,
        /// 发送请求产生Socket错误
        SEND_SOCKET_ERROR = 5,
        /// 超时
        TIMEOUT = 6,
        /// 创建Socket失败
        CREATE_SOCKET_ERROR = 7,
        /// 从连接池中取连接失败
        POOL_GET_CONNECTION = 8,
        /// 无效的连接
        POOL_INVALID_CONNECTION = 9,
    };

    /**
     * @brief 构造函数
     * @param[in] _result 错误码
     * @param[in] _response HTTP响应结构体
     * @param[in] _error 错误描述
     */
    HttpResult(int _result
               ,HttpResponse::ptr _response
               ,const std::string& _error)
        :result(_result)
        ,response(_response)
        ,error(_error) {}

    /// 错误码
    int result;
    /// HTTP响应结构体
    HttpResponse::ptr response;
    /// 错误描述
    std::string error;

    std::string toString() const;
};

class HttpConnection

成员变量
c 复制代码
// 创建时间
uint64_t m_createTime = 0;
// 请求超时时间
uint64_t m_request = 0;
构造函数
c 复制代码
HttpConnection::HttpConnection(Socket::ptr sock, bool owner)
    :SocketStream(sock, owner) {
}
recvResponse
c 复制代码
// 接收响应报文
HttpResponse::ptr HttpConnection::recvResponse() {
    // 创建响应报文解析器
    HttpResponseParser::ptr parser(new HttpResponseParser);
    // 获取http请求缓冲区的大小
    uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize();
    // 使用智能指针托管一个读取buffer
    std::shared_ptr<char> buffer(
            new char[buff_size + 1], [](char* ptr){
                delete[] ptr;
            });
    char* data = buffer.get();
    // 读取偏移量
    int offset = 0;
    // 循环读取buffer中的数据
    do {
        // 返回读取的长度
        int len = read(data + offset, buff_size - offset);
        // 读取发生错误,返回空指针
        if(len <= 0) {
            close();
            return nullptr;
        }
        // 当前已经读取的数据长度
        len += offset;
        // 这里是为了防止使用的状态机解析http时,一个断言报错
        data[len] = '\0';
        // 解析缓冲区data中的数据
        // execute会将data向前移动nparse个字节,nparse为已经成功解析的字节数
        size_t nparse = parser->execute(data, len, false);
        // 解析失败返回空指针
        if(parser->hasError()) {
            close();
            return nullptr;
        }
        // data剩余的长度
        offset = len - nparse;
        // 缓冲区满了还没解析完
        if(offset == (int)buff_size) {
            close();
            return nullptr;
        }
        // 解析结束
        if(parser->isFinished()) {
            break;
        }
    } while(true);
    // 获取刚才的解析结果
    auto& client_parser = parser->getParser();
    // 消息体
    std::string body;
    // 是否分块传输
    if(client_parser.chunked) {
        // 缓冲区剩余数据
        int len = offset;
        do {
            // 是否为新的读取操作
            bool begin = true;
            do {
                // // 如果不是新的读取操作或者缓冲区数据已经读取完毕
                if(!begin || len == 0) {
                    // 从数据流中读取数据到缓冲区
                    int rt = read(data + len, buff_size - len);
                    // 如果读取失败或者连接关闭,则关闭连接并返回空指针
                    if(rt <= 0) {
                        close();
                        return nullptr;
                    }
                    // 更新缓冲区数据长度
                    len += rt;
                }
                data[len] = '\0';
                // 解析操作,和上面类似
                size_t nparse = parser->execute(data, len, true);
                if(parser->hasError()) {
                    close();
                    return nullptr;
                }
                len -= nparse;
                if(len == (int)buff_size) {
                    close();
                    return nullptr;
                }
                begin = false;
            } while(!parser->isFinished());
            //len -= 2;
            
            SYLAR_LOG_DEBUG(g_logger) << "content_len=" << client_parser.content_len;
            // 如果当前块的长度小于等于缓冲区数据长度
            if(client_parser.content_len + 2 <= len) {
                // 将当前块的数据追加到响应体中
                body.append(data, client_parser.content_len);
                // 移动数据,删除已经处理过的块
                memmove(data, data + client_parser.content_len + 2
                        , len - client_parser.content_len - 2);
                // 更新缓冲区数据长度
                len -= client_parser.content_len + 2;
            } else {
                // 如果当前块的长度大于缓冲区数据长度
                // 将缓冲区数据追加到响应体中
                body.append(data, len);
                // 计算剩余需要读取的数据长度
                int left = client_parser.content_len - len + 2;
                // 继续从数据流中读取数据直到读取到足够的数据为止
                while(left > 0) {
                    int rt = read(data, left > (int)buff_size ? (int)buff_size : left);
                    if(rt <= 0) {
                        close();
                        return nullptr;
                    }
                    // 将读取的数据追加到响应体中
                    body.append(data, rt);
                    // 更新剩余需要读取的数据长度
                    left -= rt;
                }
                // 删除末尾的结束符
                body.resize(body.size() - 2);
                // 更新缓冲区数据长度为0
                len = 0;
            }
        } while(!client_parser.chunks_done);
    } else {
        // 如果未使用分块传输编码
        // 获取消息体的长度
        int64_t length = parser->getContentLength();
        // 如果消息体长度大于0
        if(length > 0) {
            body.resize(length);
            // 初始化响应体数据的起始位置
            int len = 0;
            // 如果消息体长度大于等于缓冲区数据长度
            if(length >= offset) {
                memcpy(&body[0], data, offset);
                len = offset;
            } else {
                memcpy(&body[0], data, length);
                len = length;
            }
            // 计算剩余需要读取的数据长度
            length -= offset;
            if(length > 0) {
                // 从数据流中读取数据到响应体的剩余位置
                if(readFixSize(&body[len], length) <= 0) {
                    close();
                    return nullptr;
                }
            }
            parser->getData()->setBody(body);
        }
    }
    return parser->getData();
}
sendRequest
c 复制代码
// 发送请求报文
int HttpConnection::sendRequest(HttpRequest::ptr rsp) {
    std::stringstream ss;
    ss << *rsp;
    std::string data = ss.str();
    return writeFixSize(data.c_str(), data.size());
}
DoRequest
c 复制代码
// 执行HTTP请求,接受 HTTP 方法、URL、超时时间、请求头部和请求体作为参数,并返回一个 HttpResult::ptr 类型的智能指针。
HttpResult::ptr HttpConnection::DoRequest(HttpMethod method
                            , const std::string& url
                            , uint64_t timeout_ms
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body) {
    // 根据传入的url创建一个uri对象
    Uri::ptr uri = Uri::Create(url);
    // 如果创建失败返回错误消息
    if(!uri) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_URL
                , nullptr, "invalid url: " + url);
    }
    return DoRequest(method, uri, timeout_ms, headers, body);
}


HttpResult::ptr HttpConnection::DoRequest(HttpMethod method
                            , Uri::ptr uri
                            , uint64_t timeout_ms
                            , const std::map<std::string, std::string>& headers
                            , const std::string& body) {
    // 创建一个HttpRequest对象
    HttpRequest::ptr req = std::make_shared<HttpRequest>();
    // 使用uri初始化创建一个HttpRequest对象
    req->setPath(uri->getPath());
    req->setQuery(uri->getQuery());
    req->setFragment(uri->getFragment());
    req->setMethod(method);
    bool has_host = false;
    // 处理请求头
    for(auto& i : headers) {
        // 如果请求头部中包含了 Connection 字段并且值为 keep-alive,则设置请求不关闭连接,并继续下一次循环。
        if(strcasecmp(i.first.c_str(), "connection") == 0) {
            if(strcasecmp(i.second.c_str(), "keep-alive") == 0) {
                req->setClose(false);
            }
            continue;
        }
        // 如果请求头部中包含了 Host 字段并且值非空,则将 has_host 设置为 true。
        if(!has_host && strcasecmp(i.first.c_str(), "host") == 0) {
            has_host = !i.second.empty();
        }
        // 设置请求头部中除 Connection 和 Host 之外的其他字段。
        req->setHeader(i.first, i.second);
    }
    // 如果请求头部中没有 Host 字段,则根据解析得到的 URI 设置 Host 字段。
    if(!has_host) {
        req->setHeader("Host", uri->getHost());
    }
    req->setBody(body);
    return DoRequest(req, uri, timeout_ms);
}

// 最终的实现
HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req
                            , Uri::ptr uri
                            , uint64_t timeout_ms) {
    // bool is_ssl = uri->getScheme() == "https";
    // 根据解析得到的 URI 创建地址对象
    Address::ptr addr = uri->createAddress();
    if(!addr) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::INVALID_HOST
                , nullptr, "invalid host: " + uri->getHost());
    }
    // Socket::ptr sock = is_ssl ? SSLSocket::CreateTCP(addr) : Socket::CreateTCP(addr);
    // 创建TCP套接字
    Socket::ptr sock = Socket::CreateTCP(addr);
    if(!sock) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::CREATE_SOCKET_ERROR
                , nullptr, "create socket fail: " + addr->toString()
                        + " errno=" + std::to_string(errno)
                        + " errstr=" + std::string(strerror(errno)));
    }
    // 连接到目标主机
    if(!sock->connect(addr)) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::CONNECT_FAIL
                , nullptr, "connect fail: " + addr->toString());
    }
    // 设置套接字的接收超时时间。
    sock->setRecvTimeout(timeout_ms);
    // 创建一个 HttpConnection 对象,传入创建的套接字。
    HttpConnection::ptr conn = std::make_shared<HttpConnection>(sock);
    // 发送请求
    int rt = conn->sendRequest(req);
    if(rt == 0) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_CLOSE_BY_PEER
                , nullptr, "send request closed by peer: " + addr->toString());
    }
    if(rt < 0) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_SOCKET_ERROR
                    , nullptr, "send request socket error errno=" + std::to_string(errno)
                    + " errstr=" + std::string(strerror(errno)));
    }
    // 接收服务器的响应
    auto rsp = conn->recvResponse();

    if(!rsp) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::TIMEOUT
                    , nullptr, "recv response timeout: " + addr->toString()
                    + " timeout_ms:" + std::to_string(timeout_ms));
    }
    return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "ok");

}

class HttpConnectionPool

成员变量
c 复制代码
// 主机
std::string m_host;
std::string m_vhost;
// 端口号
uint32_t m_port;
// 连接最大数
uint32_t m_maxSize;
// 最长连接时间
uint32_t m_maxAliveTime;
// 最长请求时间
uint32_t m_maxRequest;
// 是否是https
bool m_isHttps;
// 锁
MutexType m_mutex;
// HttpConnection指针链表
std::list<HttpConnection*> m_conns;
// 连接的数量
std::atomic<int32_t> m_total = {0};
构造函数
c 复制代码
HttpConnectionPool::HttpConnectionPool(const std::string& host
                                        ,const std::string& vhost
                                        ,uint32_t port
                                        ,bool is_https
                                        ,uint32_t max_size
                                        ,uint32_t max_alive_time
                                        ,uint32_t max_request)
    :m_host(host)
    ,m_vhost(vhost)
    ,m_port(port ? port : (is_https ? 443 : 80))
    ,m_maxSize(max_size)
    ,m_maxAliveTime(max_alive_time)
    ,m_maxRequest(max_request)
    ,m_isHttps(is_https) {
}
getConnection
c 复制代码
// 获得连接
HttpConnection::ptr HttpConnectionPool::getConnection() {
    // 记录当前的时间
    uint64_t now_ms = sylar::GetCurrentMS();
    // 存储非法连接
    std::vector<HttpConnection*> invalid_conns;
    // 定义一个空的连接指针
    HttpConnection* ptr = nullptr;
    // 加锁
    MutexType::Lock lock(m_mutex);
    // 如果HttpConnection指针链表不为空
    while(!m_conns.empty()) {
        // 取出第一个connection
        auto conn = *m_conns.begin();
        m_conns.pop_front();
        // 不在连接状态,放入非法vec中
        if(!conn->isConnected()) {
            invalid_conns.push_back(conn);
            continue;
        }
        // 已经超过了最大连接时间,放入非法vec中
        if((conn->m_createTime + m_maxAliveTime) > now_ms) {
            invalid_conns.push_back(conn);
            continue;
        }
        // 获得当前connection
        ptr = conn;
        break;
    }
    lock.unlock();
    // 删除非法连接
    for(auto i : invalid_conns) {
        delete i;
    }
    // 更新总连接数
    m_total -= invalid_conns.size();

    // 如果没有连接
    if(!ptr) {
        // 根据host创建地址
        IPAddress::ptr addr = Address::LookupAnyIPAdress(m_host); 
        if(!addr) {
            SYLAR_LOG_ERROR(g_logger) << "get addr fail: " << m_host;
            return nullptr;
        }
        // 设置端口号
        addr->setPort(m_port);
        // Socket::ptr sock = m_isHttps ? SSLSocket::CreateTCP(addr) : Socket::CreateTCP(addr);
        // 创建TCPSocket
        Socket::ptr sock = Socket::CreateTCP(addr);
        if(!sock) {
            SYLAR_LOG_ERROR(g_logger) << "create sock fail: " << *addr;
            return nullptr;
        }
        // 连接
        if(!sock->connect(addr)) {
            SYLAR_LOG_ERROR(g_logger) << "sock connect fail: " << *addr;
            return nullptr;
        }
        // 成功创建一个连接
        ptr = new HttpConnection(sock);
        ++m_total;
    }
    return HttpConnection::ptr(ptr, std::bind(&HttpConnectionPool::ReleasePtr
                               , std::placeholders::_1, this));
}
ReleasePtr
c 复制代码
void HttpConnectionPool::ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool) {
    // 请求次数+1
    ++ptr->m_request;
    // 已经关闭了链接,超时,超过最大请求数量
    if(!ptr->isConnected()
            || ((ptr->m_createTime + pool->m_maxAliveTime) >= sylar::GetCurrentMS())
            || (ptr->m_request >= pool->m_maxRequest)) {
        delete ptr;
        --pool->m_total;
        return;
    }
    // 重新放入连接池中
    MutexType::Lock lock(pool->m_mutex);
    pool->m_conns.push_back(ptr);
}
相关推荐
UestcXiye几秒前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
一只哒布刘1 小时前
NFS服务器
运维·服务器
霁月风1 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08282 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i2 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1072 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客2 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
lihuhelihu2 小时前
第3章 CentOS系统管理
linux·运维·服务器·计算机网络·ubuntu·centos·云计算
山东布谷科技官方3 小时前
布谷直播源码部署服务器关于数据库配置的详细说明
运维·服务器·数据库·直播系统源码·直播源码·直播系统搭建·直播软件开发
爱吃喵的鲤鱼3 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++