cpp-httplib 源码剖析


前言

之前实现自己的http库的时候感觉有一些设计的不是很好,这几天对cpp-httplib 源码进行剖析,对如何设计http库有了更深入的认识。(主要是对server类进行拆解分析)

一、cpp-httplib 是什么?

cpp-httplib是一个c++封装的http开源库,仅包含一个头文件,不过代码行数达到8000多行。
cpp-httplib 服务端采用select IO多路复用模型,工作线程池的处理方式,主要包含的类Server、Client、Request、Response。

二、Server类整体架构

server类的工作流程基本如下:
1、 搭建tcp服务,注册资源路径与处理方法,并开启监听。
2、 创建工作线程,select循环监听,当收到客户端消息,放入jobs中,通知工作线程处理。
3、工作线程从jobs中取出待处理请求。
4、 处理请求,包含校验、协议解析、根据请求方式分发,最终调用用户注册方法处理上层业务。
在这里插入图片描述

三、绑定和监听

绑定和监听是由Servert中两个成员函数bind_internal和listen_internal的实现的。

bind_internal

bind internal函数的作用是绑定服务器的地址和端口。函数首先检查服务器套接字是否有效,如果无效则返回-1。然后调用create_server_socket函数创建服务器套接字,并将其赋值给成员变量svr_sock.。如果创建套接字失败,则返回-1。接下来,根据传入的ort参数判断是否为0。如果为0,则通过调用getsockname函数获取绑定的地址和端口,并返回解析得到的端口号。否则,直接返回传入的port参数。


inline int Server::bind_internal(const std::string &host, int port,
                                 int socket_flags) {
   
  if (!is_valid()) {
    return -1; }

  svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
  if (svr_sock_ == INVALID_SOCKET) {
    return -1; }

  if (port == 0) {
   
    struct sockaddr_storage addr;
    socklen_t addr_len = sizeof(addr);
    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
                    &addr_len) == -1) {
   
      return -1;
    }
    if (addr.ss_family == AF_INET) {
   
      return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
    } else if (addr.ss_family == AF_INET6) {
   
      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
    } else {
   
      return -1;
    }
  } else {
   
    return port;
  }
}

listen_internal

首先是对Select的封装:
创建了一个 fd_set 对象并将要监听的套接字加入其中,然后设置超时时间,最后调用 select 函数进行阻塞等待可读事件或超时。通过这种方式可以同时监听多个套接字,并在有可读事件发生时进行处理。

inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
   
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(sock, &fds);

  timeval tv;
  tv.tv_sec = static_cast<long>(sec);
  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);

  return handle_EINTR([&]() {
   
    return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
  });
}

Server::listen_internal() 函数,它用于监听连接并处理客户端请求。

auto ret = true;
is_running_ = true;
auto se = detail::scope_exit([&]() {
    is_running_ = false; });

首先将返回值 ret 设为 true,表示函数执行成功。将服务器运行状态 is_running_ 设为 true,并创建 detail::scope_exit 对象,在函数退出时将 is_running_ 设置为 false。

std::unique_ptr<TaskQueue> task_queue(new_task_queue());

创建一个 TaskQueue 的智能指针 task_queue,用于保存待处理的任务。

while (svr_sock_ != INVALID_SOCKET) {
   
#ifndef _WIN32
  if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
   
#endif
    auto val = detail::select_read(svr_sock_, idle_interval_sec_,
                                   idle_interval_usec_);
    if (val == 0) {
    // Timeout
      task_queue->on_idle();
      continue;
    }
#ifndef _WIN32
  }
#endif
  socket_t sock = accept(svr_sock_, nullptr, nullptr);

  // ...
}

进入主循环,只要服务器 socket svr_sock_ 是有效的,就继续监听连接。在循环中,先调用 detail::select_read() 函数等待读事件,如果超时则调用 task_queue->on_idle() 处理空闲状态,并继续等待下一个连接。如果有新的连接到达,调用 accept() 函数接受客户端连接,并返回一个新的客户端 socket sock

if (sock == INVALID_SOCKET) {
   
  if (errno == EMFILE) {
   
    // The per-process limit of open file descriptors has been reached.
    // Try to accept new connections after a short sleep.
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    continue;
  } else if (errno == EINTR || errno == EAGAIN) {
   
    continue;
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值