cpp-httplib 源码剖析

news2025/6/18 12:21:30

文章目录

  • 前言
  • 一、cpp-httplib 是什么?
  • 二、Server类整体架构
  • 三、绑定和监听
    • bind_internal
    • listen_internal
  • 四、路由
    • 添加路由
    • 路由
  • 五、处理接受请求
    • process_server_socket_core
    • process_request


前言

之前实现自己的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;
  }
  if (svr_sock_ != INVALID_SOCKET) {
    detail::close_socket(svr_sock_);
    ret = false;
  } else {
    ; // The server socket was closed by user.
  }
  break;
}

如果 accept() 失败,则根据错误码进行相应处理。如果错误码是 EMFILE,表示打开文件描述符的数量达到了系统设置的最大值,此时暂停一段时间后继续尝试。如果错误码是 EINTREAGAIN,表示该操作被中断或者资源暂时不可用,需要继续重试。如果发生其他错误,则关闭服务器 socket,并将返回值设为 false,表示函数执行失败。此时跳出循环,结束监听。

{
#ifdef _WIN32
  auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
                                       read_timeout_usec_ / 1000);
  setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
             sizeof(timeout));
#else
  timeval tv;
  tv.tv_sec = static_cast<long>(read_timeout_sec_);
  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
  setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
#endif
}
{
#ifdef _WIN32
  auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
                                       write_timeout_usec_ / 1000);
  setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
             sizeof(timeout));
#else
  timeval tv;
  tv.tv_sec = static_cast<long>(write_timeout_sec_);
  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
  setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
#endif
}

为新的客户端 socket sock 设置读超时和写超时。

task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });

sock 加入 task_queue,并用 process_and_close_socket() 函数来处理这个连接。

task_queue->shutdown();

循环结束后,调用 task_queue->shutdown() 关闭任务队列。

最后返回变量 ret,表示函数执行结果。如果 ret 是 true,则表示函数执行成功,否则表示失败。

四、路由

添加路由

使用正则表达式来指定URL的模式,并注册不同类型请求的处理函数。
比如说Get函数用于注册GET请求的处理器。将匹配的URL模式(pattern)和处理函数(handler)作为键值对添加到get_handlers_列表中。


inline Server &Server::Get(const std::string &pattern, Handler handler) {
  get_handlers_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

inline Server &Server::Post(const std::string &pattern, Handler handler) {
  post_handlers_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

inline Server &Server::Post(const std::string &pattern,
                            HandlerWithContentReader handler) {
  post_handlers_for_content_reader_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

inline Server &Server::Put(const std::string &pattern, Handler handler) {
  put_handlers_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

inline Server &Server::Put(const std::string &pattern,
                           HandlerWithContentReader handler) {
  put_handlers_for_content_reader_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

路由

主要函数有routing以及dispatch_request。
dispatch_request方法用于根据请求的路径(path)匹配相应的处理器(handler),并执行该处理器来处理请求。

inline bool Server::dispatch_request(Request &req, Response &res,
                                     const Handlers &handlers) {
  for (const auto &x : handlers) {
    const auto &pattern = x.first;
    const auto &handler = x.second;
    if (std::regex_match(req.path, req.matches, pattern)) {
      handler(req, res);
      return true;
    }
  }
  return false;
}

routing根据请求的方法和内容,将请求分发给相应的处理函数进行处理,并生成对应的响应

主要功能如下:

  1. 首先,如果存在预处理器(pre_routing_handler_)且该预处理器处理了请求(req)并返回HandlerResponse::Handled,则直接返回true,表示请求已被处理完成。
  2. 接着,判断请求的方法(method)是否为"GET"或"HEAD",如果是,则调用handle_file_request函数处理文件请求,并返回true,表示请求已被处理完成。
  3. 如果请求中包含内容,则进入内容读取器(ContentReader)的处理流程。首先,创建ContentReader对象,该对象根据请求的方法选择相应的处理逻辑。具体处理逻辑包括:
    • 对于"POST"方法,调用dispatch_request_for_content_reader函数分发请求到post_handlers_for_content_reader_列表中的处理器。
    • 对于"PUT"方法,调用dispatch_request_for_content_reader函数分发请求到put_handlers_for_content_reader_列表中的处理器。
    • 对于"PATCH"方法,调用dispatch_request_for_content_reader函数分发请求到patch_handlers_for_content_reader_列表中的处理器。
    • 对于"DELETE"方法,调用dispatch_request_for_content_reader函数分发请求到delete_handlers_for_content_reader_列表中的处理器。
  4. 如果请求中包含内容,在上述步骤完成后,继续读取请求的内容,并将内容存储到req.body中。
  5. 最后,根据请求的方法分发请求到相应的处理器列表中去处理请求,并返回处理结果。如果请求方法未被识别,或者没有找到匹配的处理器,则设置响应的状态码为400,并返回false。

通过这个路由方法,服务器可以根据请求的方法和内容,将请求分发给相应的处理函数进行处理,并生成对应的响应。

inline bool Server::routing(Request &req, Response &res, Stream &strm) {
  if (pre_routing_handler_ &&
      pre_routing_handler_(req, res) == HandlerResponse::Handled) {
    return true;
  }

  // File handler
  bool is_head_request = req.method == "HEAD";
  if ((req.method == "GET" || is_head_request) &&
      handle_file_request(req, res, is_head_request)) {
    return true;
  }

  if (detail::expect_content(req)) {
    // Content reader handler
    {
      ContentReader reader(
          [&](ContentReceiver receiver) {
            return read_content_with_content_receiver(
                strm, req, res, std::move(receiver), nullptr, nullptr);
          },
          [&](MultipartContentHeader header, ContentReceiver receiver) {
            return read_content_with_content_receiver(strm, req, res, nullptr,
                                                      std::move(header),
                                                      std::move(receiver));
          });

      if (req.method == "POST") {
        if (dispatch_request_for_content_reader(
                req, res, std::move(reader),
                post_handlers_for_content_reader_)) {
          return true;
        }
      } else if (req.method == "PUT") {
        if (dispatch_request_for_content_reader(
                req, res, std::move(reader),
                put_handlers_for_content_reader_)) {
          return true;
        }
      } else if (req.method == "PATCH") {
        if (dispatch_request_for_content_reader(
                req, res, std::move(reader),
                patch_handlers_for_content_reader_)) {
          return true;
        }
      } else if (req.method == "DELETE") {
        if (dispatch_request_for_content_reader(
                req, res, std::move(reader),
                delete_handlers_for_content_reader_)) {
          return true;
        }
      }
    }

    // Read content into `req.body`
    if (!read_content(strm, req, res)) { return false; }
  }

  // Regular handler
  if (req.method == "GET" || req.method == "HEAD") {
    return dispatch_request(req, res, get_handlers_);
  } else if (req.method == "POST") {
    return dispatch_request(req, res, post_handlers_);
  } else if (req.method == "PUT") {
    return dispatch_request(req, res, put_handlers_);
  } else if (req.method == "DELETE") {
    return dispatch_request(req, res, delete_handlers_);
  } else if (req.method == "OPTIONS") {
    return dispatch_request(req, res, options_handlers_);
  } else if (req.method == "PATCH") {
    return dispatch_request(req, res, patch_handlers_);
  }

  res.status = 400;
  return false;
}

五、处理接受请求

process_and_close_socket() 函数处理连接,其中最核心的是process_server_socket_core函数以及process_request函数。

process_server_socket_core

process_server_socket_core函数用于在服务器套接字上处理连接,并根据保持活动的最大次数和超时时间执行回调函数。在每次循环中,会根据剩余的保持活动次数判断是否需要关闭连接,并将回调函数的执行结果保存在变量ret中。函数最终返回回调函数的执行结果。

template <typename T>
inline bool
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
                           size_t keep_alive_max_count,
                           time_t keep_alive_timeout_sec, T callback) {
  assert(keep_alive_max_count > 0);
  auto ret = false;
  auto count = keep_alive_max_count;
  while (svr_sock != INVALID_SOCKET && count > 0 &&
         keep_alive(sock, keep_alive_timeout_sec)) {
    auto close_connection = count == 1;
    auto connection_closed = false;
    ret = callback(close_connection, connection_closed);
    if (!ret || connection_closed) { break; }
    count--;
  }
  return ret;
}

process_request

回调函数是process_request处理请求。
请求处理步骤如下:

  1. 设置默认的HTTP响应版本号为"HTTP/1.1"。
  2. 将默认的HTTP响应头加入响应对象中,如果响应对象中没有相同的头字段。
  3. 检查请求的URI长度是否超过最大限制,如果超过则返回状态码414(请求URI过长)。
  4. 解析请求行和请求头,如果解析失败则返回状态码400(错误的请求)。
  5. 根据请求的Connection头判断是否需要关闭连接。
  6. 获取客户端和服务器端的IP地址和端口,并添加到请求头中。
  7. 如果请求头包含Range字段,解析Range字段的值,并存储在请求对象的ranges成员中。
  8. 调用用户提供的请求处理回调函数,对请求做进一步处理。
  9. 如果请求头包含Expect字段并且值为"100-continue",则发送100 Continue响应。
  10. 根据路由规则处理请求,并设置相应的状态码。
  11. 如果成功匹配到路由规则,根据请求中的ranges判断是否返回部分内容或全部内容。
  12. 如果无法匹配到路由规则,则返回404 Not Found状态码。
  13. 最后根据routing路由处理结果,将响应发送回客户端。

inline bool
Server::process_request(Stream &strm, bool close_connection,
                        bool &connection_closed,
                        const std::function<void(Request &)> &setup_request) {
  std::array<char, 2048> buf{};

  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());

  // Connection has been closed on client
  if (!line_reader.getline()) { return false; }

  Request req;
  Response res;

  res.version = "HTTP/1.1";

  for (const auto &header : default_headers_) {
    if (res.headers.find(header.first) == res.headers.end()) {
      res.headers.insert(header);
    }
  }

#ifdef _WIN32
  // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
#else
#ifndef CPPHTTPLIB_USE_POLL
  // Socket file descriptor exceeded FD_SETSIZE...
  if (strm.socket() >= FD_SETSIZE) {
    Headers dummy;
    detail::read_headers(strm, dummy);
    res.status = 500;
    return write_response(strm, close_connection, req, res);
  }
#endif
#endif

  // Check if the request URI doesn't exceed the limit
  if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
    Headers dummy;
    detail::read_headers(strm, dummy);
    res.status = 414;
    return write_response(strm, close_connection, req, res);
  }

  // Request line and headers
  if (!parse_request_line(line_reader.ptr(), req) ||
      !detail::read_headers(strm, req.headers)) {
    res.status = 400;
    return write_response(strm, close_connection, req, res);
  }

  if (req.get_header_value("Connection") == "close") {
    connection_closed = true;
  }

  if (req.version == "HTTP/1.0" &&
      req.get_header_value("Connection") != "Keep-Alive") {
    connection_closed = true;
  }

  strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
  req.set_header("REMOTE_ADDR", req.remote_addr);
  req.set_header("REMOTE_PORT", std::to_string(req.remote_port));

  strm.get_local_ip_and_port(req.local_addr, req.local_port);
  req.set_header("LOCAL_ADDR", req.local_addr);
  req.set_header("LOCAL_PORT", std::to_string(req.local_port));

  if (req.has_header("Range")) {
    const auto &range_header_value = req.get_header_value("Range");
    if (!detail::parse_range_header(range_header_value, req.ranges)) {
      res.status = 416;
      return write_response(strm, close_connection, req, res);
    }
  }

  if (setup_request) { setup_request(req); }

  if (req.get_header_value("Expect") == "100-continue") {
    auto status = 100;
    if (expect_100_continue_handler_) {
      status = expect_100_continue_handler_(req, res);
    }
    switch (status) {
    case 100:
    case 417:
      strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
                        detail::status_message(status));
      break;
    default: return write_response(strm, close_connection, req, res);
    }
  }

  // Rounting
  bool routed = false;
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
  routed = routing(req, res, strm);
#else
  try {
    routed = routing(req, res, strm);
  } catch (std::exception &e) {
    if (exception_handler_) {
      auto ep = std::current_exception();
      exception_handler_(req, res, ep);
      routed = true;
    } else {
      res.status = 500;
      std::string val;
      auto s = e.what();
      for (size_t i = 0; s[i]; i++) {
        switch (s[i]) {
        case '\r': val += "\\r"; break;
        case '\n': val += "\\n"; break;
        default: val += s[i]; break;
        }
      }
      res.set_header("EXCEPTION_WHAT", val);
    }
  } catch (...) {
    if (exception_handler_) {
      auto ep = std::current_exception();
      exception_handler_(req, res, ep);
      routed = true;
    } else {
      res.status = 500;
      res.set_header("EXCEPTION_WHAT", "UNKNOWN");
    }
  }
#endif

  if (routed) {
    if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
    return write_response_with_content(strm, close_connection, req, res);
  } else {
    if (res.status == -1) { res.status = 404; }
    return write_response(strm, close_connection, req, res);
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/998983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虹科分享 | Chae$4:针对金融和物流客户的新Chaes恶意软件变体 | 自动移动目标防御

介绍--Chae$4 随着网络威胁的世界以惊人的速度发展&#xff0c;保持领先于这些数字危险对企业来说变得越来越关键。2023年1月&#xff0c;Morphisec发现了一个令人震惊的趋势&#xff0c;许多客户&#xff0c;主要是物流和金融部门的客户&#xff0c;受到了Chaes恶意软件的新的…

【踩坑】Latex中multicolumn/multirow单元格竖线消失的恢复方法

消失的情况&#xff1a; 修复方法&#xff1a; 1、第一点是确保单元格数量正确&#xff1b; 2、第二点是一个小细节&#xff0c;这里的c后面要加个"|"&#xff1a; \multicolumn{3}{c|} 当然&#xff0c;如果是左边少&#xff0c;那就加左边&#xff1b;或者直接左…

Python爬虫实战:抓取和分析新闻数据与舆情分析

在信息爆炸的时代&#xff0c;新闻和舆情分析对于企业和个人来说都具有重要意义。而Python作为一门优秀的编程语言&#xff0c;非常适合用于构建强大的爬虫工具&#xff0c;并用于抓取和分析新闻数据。本文将分享使用Python爬虫抓取和分析新闻数据&#xff0c;并进行舆情分析的…

2023年下半年杭州/深圳软考(中/高级)认证报名,来这呀

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…

OceanMind海睿思加入信通院TC1-WG7工作组,推进IT内控与审计标准体系化发展

近日&#xff0c;中新赛克海睿思通过中国通信标准化协会下&#xff08;CCSA&#xff09;的互联网与应用技术工作委员会&#xff08;TC1&#xff09;审议批准&#xff0c;正式成为IT内控与审计技术工作组&#xff08;WG7&#xff09;成员单位。 IT内控与审计技术工作组 是 中国信…

用了5年的fiddler抓包,这个超级实用的功能今天才知道!

我们在使用fiddler抓包获取请求响应时间时都会看Statics页面中的Overall Elapsed值&#xff01;如果只看单个请求的响应时间没有什么问题&#xff1b;但是如果我们需要看多个请求的响应时间或者想对页面中所有抓包的请求排序进而找到最耗时的请求&#xff0c;使用该方法显然是无…

C#难点语法讲解之委托---从应用需求开始讲解

一、委托的定义 委托&#xff08;Delegate&#xff09; 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。 简单解释&#xff1a;变量好控制&#xff0c;方法不好控制&#xff0c;委托可以把方法变成变量 二、例子解释定义 如果我们有一个数组,里面有10个…

如何注册喀麦隆商标?

想象一下&#xff0c;你正在喀麦隆的雨林中寻找宝藏&#xff0c;突然你发现了一个从未被人发现的部落。这个部落的人们用一种独特的图案作为他们的标记&#xff0c;来展示他们的身份和与众不同。这个图案就是喀麦隆的商标&#xff01; 在商业世界中&#xff0c;商标就像这个独特…

开启更高效之路,美创科技暗数据发现和数据分类分级系统全新升级

数字经济时代&#xff0c;数据分类分级作为平衡数据保护和流通利用的基础工作&#xff0c;愈发受到广泛的关注。但面对海量繁杂的数据&#xff0c;如何快速地实现数据梳理与分类分级&#xff0c;对于绝大多数组织而言&#xff0c;并非易事—— ◼︎ 在缺少标准方法和自动化、智…

洁净在线监测系统-天文台日冕仪内洁净环境监测应用

近日&#xff0c;北京中邦兴业技术部在云南完成了一项极具创新性的洁净环境在线监测项目&#xff0c;此次项目的交付&#xff0c;代表着中邦兴业技术的又一次突破与创新&#xff0c;打破了洁净检测仪器在传统行业的应用&#xff0c;成功将激光尘埃粒子计数器LIGHTHOUSE-3016,应…

20230911java面经整理

1.java线程安全的数据类型 Vector&#xff1a;每个方法都有synchronized hashttable&#xff1a;每个方法都有synchronized stack&#xff1a;继承了vector arrayblockingqueue&#xff1a;阻塞队列 concurrentHashMap&#xff1a;使用segment分段锁 concurrentLinkedQueue&am…

flink on yarn任务中文乱码问题解决记录

开发反馈预生产部分部分flink任务出现中文乱码的问题 找到乱码的flink任务所在的节点&#xff0c;登录服务器&#xff0c;执行locale命令&#xff1a; 发现是locale没有设置好&#xff0c;使用vim编辑文本&#xff0c;写入中文都直接乱码 对比其他几台机器&#xff0c;发现主…

【算法】常见位运算总结

目录 1.基础位运算2. 给一个数n&#xff0c;确定它的二进制表示中的第x位是0还是13.将一个数n的二进制表示的第x位修改成14.将一个数n的二进制表示的第x位修改成0、5. 位图的思想6.提取一个数(n)二进制表示中最右侧的17.干掉一个数(n)二进制表示中最右侧的18.位运算的优先级9.异…

瞄准热门需求:7个最受欢迎的跑腿小程序开发灵感

跑腿服务在如今快节奏的社会中扮演着重要角色&#xff0c;而跑腿小程序成为满足人们日常需求的利器。如果你正计划开发一款跑腿小程序&#xff0c;那么把握住最热门的需求绝对是成功的关键。在本文中&#xff0c;我作为跑腿小程序开发领域的专家&#xff0c;将分享七个最受欢迎…

【UIPickerView案例07-省市选择界面数据展示03-城市选择Bug修复 Objective-C语言】

一、咱们先把前面的内容捋一下——省市选择界面: 1.首先呢,我们说,实现一个案例,第一步,先看界面, 1)第一步:先看界面, 2)第二步:分析一下它的基本结构, 3)第三步:搭建界面, 4)第四步:加载数据, 5)第五步:显示数据, 是不是五步, 大的就这五步, …

SpringMVC文件上传与下载、JREBEL使用

目录 一、引言 二、文件的上传 1、单文件上传 1.1、数据表准备 1.2、添加依赖 1.3、配置文件 1.4、编写表单 1.5、编写controller层 2、多文件上传 2.1、编写form表单 2.2、编写controller层 2.3、测试 三、文件下载 四、JREBEL使用 1、下载注册 2、离线设置 一…

“文件的上传与下载:实现与优化“

目录 引言1.文件的上传2.文件的下载3. JRebel安装使用4. 文件批量上传总结 引言 在开发过程中&#xff0c;文件的上传与下载是常见的需求。本篇博客将以CSND为例&#xff0c;介绍文件上传与下载的常见方式&#xff0c;以及如何通过优化提升性能和用户体验。 1.文件的上传 使…

【Python算法Algorithm】专栏导读

1 什么是算法&#xff08;Algorithm&#xff09;&#xff1f; 算法是一组用于解决特定问题或执行特定任务的有序、精确的计算步骤的集合。它可以被认为是一种计算机程序&#xff0c;但更加抽象和数学化。 算法的主要目标是将输入数据转化为所期望的输出结果&#xff0c;而且要在…

华为云云耀云服务器L实例评测|一键部署,畅享高效华为云MySQL

本文目录 一、前言1、云耀云服务器L实例介绍2、产品优势 二、购买/使用1、首界面购买2、根据自己需求购买对应的3、购买成功过后返回首页面---》启动4、重置密码5、远程登录6、输入账户密码提示登陆成功7、为了方便拷贝安装命令&#xff0c;可以选择本地鼠标8、点击复制粘贴9、…

【Spring面试】五、Bean扩展、JavaConfig、@Import

文章目录 Q1、如何在Spring创建完所有的Bean之后做扩展&#xff1f;Q2、Spring容器启动时&#xff0c;为什么先加载BeanFactoryPostProcess?Q3、Bean的生产顺序是由什么决定的&#xff1f;Q4、Spring有哪几种配置方式Q5、JavaConfig是如何替代spring.xml的&#xff1f;Q6、Com…