当今,正处于互联网高速发展的时代,每个人的生活都离不开互联网,互联网已经影响了每个人生活的方方面面。我们使用淘宝、京东进行购物,使用微信进行沟通,使用美图秀秀进行拍照美化等等。而这些每一步的操作下面,都离不开一个技术概念HTTP(Hypertext Transfer Protocol,超文本传输协议)。

举个,当我们打开京东APP的时候,首先进入的是开屏页面,然后进入首页。在开屏一般是广告,而首页是内容相关,包括秒杀,商品推荐以及各个tag页面,而各个tag也有其对应的内容。当我们在进入开屏之前或者开屏之后(这块依赖于各个app的技术实现),会向后端服务发送一个http请求,这个请求会带上该页面广告位信息,向后端要内容,后端根据广告位的配置,挑选一个合适的广告或者推荐商品返回给APP端进行展示。在这里,为了描述方便,后端当做一个简单的整体,实际上,后端会有非常复杂的业务调度,比如获取用户画像,广告定向,获取素材,计算坐标,返回APP,APP端根据坐标信息,下载素材,然后进行渲染,从而在用户端进行展示,这一切都是秒级甚至毫秒级响应,一个高效的HTTP Client在这里就显得尤为重要,本文主要从业务场景来分析,如何实现一个高效的HTTP Client。

1 概念

当我们需要模拟发送一个http请求的时候,往往有两种方式:

1、通过浏览器

2、通过curl命令进行发送请求



如果我们在大规模高并发的业务中,如果使用curl来进行http请求,其效果以及性能是不能满足业务需求的,这就引入了另外一个概念libcurl。

curl

  • 利用URL语法在命令行方式下工作的开源文件传输工具。

    它支持很多协议:DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP。

  • 支持SSL证书,HTTP POST, HTTP PUT,FTP上传,基于表单的HTTP上传,代理(proxies)、cookies、用户名/密码认证(Basic, Digest, NTLM等)、下载文件断点续传,上载文件断点续传(file transfer resume),http代理服务器管道(proxy tunneling)以及其他特性。

libcurl

  • 一个免费开源的,客户端url传输库,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP等协议。

  • 支持SSL证书,HTTP POST, HTTP PUT,FTP上传,基于表单的HTTP上传,代理(proxies)、cookies、用户名/密码认证(Basic, Digest, NTLM等)、下载文件断点续传,上载文件断点续传(file transfer resume),http代理服务器管道(proxy tunneling)等。

  • 高度可移植,可以工作在不同的平台上,支持Windows,Unix,Linux等。

  • 免费的,线程安全的,IPV6兼容的,同事它还有很多其他非常丰富的特性。libcurl已经被很多知名的大企业以及应用程序所采用。

特点

  • curl和libcurl都可以利用多种多样的协议来传输文件,包括HTTP, HTTPS, FTP, FTPS, GOPHER, LDAP, DICT, TELNET and FILE等

  • 支持SSL证书,HTTP POST, HTTP PUT,FTP上传,基于表单的HTTP上传,代理(proxies)、cookies、用户名/密码认证(Basic, Digest, NTLM等)、下载文件断点续传,上载文件断点续传(file transfer resume),http代理服务器管道(proxy tunneling)等。

  • libcurl是一个库,通常与别的程序绑定在一起使用,如命令行工具curl就是封装了libcurl库。所以我们也可以在你自己的程序或项目中使用libcurl以获得类似CURL的强大功能。

2 实现

在开始实现client发送http请求之前,我们先理解两个概念:

同步请求

当客户端向服务器发送同步请求时,服务处理在请求的过程中,客户端会处于等待的状态,一直等待服务器处理完成,客户端将服务端处理后的结果返回给调用方。



异步请求

客户端把请求发送给服务器之后,不会等待服务器返回,而是去做其他事情,待服务器处理完成之后,通知客户端该事件已经完成,客户端在获取到通知后,将服务器处理后的结果返回给调用方。



通过这俩概念,就能看出,异步在实现上,要比同步复杂的多。同步,即我们简单的等待处理结果,待处理结果完成之后,再返回调用方。而对于异步,往往在实现上,需要各种回调机制,各种通知机制,即在处理完成的时候,需要知道是哪个任务完成了,从而通知客户端去处理该任务完成后剩下的逻辑。

下面,我们将从代码实现的角度,来更深一步的理解libcurl在实现同步和异步请求操作上的区别,从而更近异步的了解同步和异步的实现原理图片。

同步

使用libcurl完成同步http请求,原理和代码都比较简单,主要是分位以下几个步骤:

1、初始化easy handle

2、在该easy handle上设置相关参数,在本例中主要有以下几个参数

  • CURLOPT_URL,即请求的url

  • CURLOPT_WRITEFUNCTION,即回调函数,将http server返回数据写入对应的地方

  • CURLOPT_FOLLOWLOCATION,是否获取302跳转后的内容

  • CURLOPT_POSTFIELDSIZE,此次发送的数据大小

  • CURLOPT_POSTFIELDS,此次发送的数据内容

更多的参数设置,请参考libcurl官网

3、curl_easy_perform,调用该函数发送http请求,并同步等待返回结果

4、curl_easy_cleanup,释放步骤一中申请的easy handle资源

代码实现(easy_curl.cc)

  1. #include <curl/curl.h>
  2. #include <iostream>
  3. #include <string>
  4. std::string resp;
  5. size_t WriteData(
  6. char* buffer, size_t size,
  7. size_t nmemb, void* userp) {
  8. resp.append(buffer, size * nmemb);
  9. return size * nmemb;
  10. }
  11. int main(void) {
  12. CURLcode res;
  13. CURL *curl = curl_easy_init();
  14. std::string post = "abc";
  15. if(curl) {
  16. // 设置url
  17. curl_easy_setopt(curl, CURLOPT_URL, "https://www.baidu.com");
  18. // 设置回调函数,即当有返回的时候,调用回调函数WriteData
  19. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData);
  20. // 抓取302跳转后d额内容
  21. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,1);
  22. // 设置发送的内容大小
  23. curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post.size());
  24. // 设置发送的内容
  25. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.c_str());
  26. // 开始执行http请求,此处是同步的,即等待http服务器响应
  27. res = curl_easy_perform(curl);
  28. /* Check for errors */
  29. if(res != CURLE_OK)
  30. fprintf(stderr, "curl_easy_perform() failed: %s\n",
  31. curl_easy_strerror(res));
  32. /* always cleanup */
  33. curl_easy_cleanup(curl);
  34. }
  35. std::cout << resp << std::endl;
  36. return 0;
  37. }

编译

  1. g++ --std=c++11 easy_curl.cc -I ../artifacts/include/ -L ../artifacts/lib -lcurl -o easy_curl

结果

  1. <!DOCTYPE html>
  2. <!--STATUS OK-->
  3. <html>
  4. <head>
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  6. <meta http-equiv="content-type" content="text/html;charset=utf-8">
  7. <meta content="always" name="referrer">
  8. <script src="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/nocache/imgdata/seErrorRec.js"></script>
  9. <title>页面不存在_百度搜索</title>
  10. <style data-for="result">
  11. body {color: #333; background: #fff; padding: 0; margin: 0; position: relative; min-width: 700px; font-family: arial; font-size: 12px }
  12. p, form, ol, ul, li, dl, dt, dd, h3 {margin: 0; padding: 0; list-style: none }
  13. input {padding-top: 0; padding-bottom: 0; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box } img {border: none; }
  14. .logo {width: 117px; height: 38px; cursor: pointer }
  15. #wrapper {_zoom: 1 }
  16. #head {padding-left: 35px; margin-bottom: 20px; width: 900px }
  17. ...

异步

接触过网络编程的读者,都或多或少的了解多路复用的原理。IO多路复用在Linux下包括了三种,select、poll、epoll,抽象来看,他们功能是类似的,但具体细节各有不同:首先都会对一组文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时。

在使用Libcurl进行异步请求,从上层结构来看,简单来说,就是对easy handle 和 multi 接口的结合使用。其中,easy handle底层也是一个socket,multi接口,其底层实现也用的是epoll,那么我们如何使用easy handle和multi接口,来实现一个高性能的异步http 请求client呢?下面我们将使用代码的形式,使得读者能够进一步了解其实现机制。

multi 接口的使用是在easy 接口的基础之上,将easy handle放到一个队列中(multi handle),然后并发发送请求。与easy 接口相比,multi接口是一个异步的,非阻塞的传输方式。

  • multi接口的使用,主要有以下几个步骤:
  • curl_multi _init初始化一个multi handler对象
  • 初始化多个easy handler对象,使用curl_easy_setopt进行相关设置
  • 调用curl_multi _add_handle把easy handler添加到multi curl对象中
  • 添加完毕后执行curl_multi_perform方法进行并发的访问
  • 访问结束后curl_multi_remove_handle移除相关easy curl对象,先用curl_easy_cleanup清除easy handler对象,最后curl_multi_cleanup清除multi handler对象。

http_request.h

  1. /*
  2. 该类是对easy handle的封装,主要做一些初始化操作,设置url 、发送的内容
  3. header以及回调函数
  4. */
  5. class HttpRequest {
  6. public:
  7. using FinishCallback = std::function<int()>;
  8. HttpRequest(const std::string& url, const std::string& post_data) :
  9. url_(url), post_data_(post_data) {
  10. }
  11. int Init(const std::vector<std::string> &headers);
  12. CURL* GetHandle();
  13. const std::string& Response();
  14. void SetFinishCallback(const FinishCallback& cb);
  15. int OnFinish(int response_ret);
  16. CURLcode Perform();
  17. void AddMultiHandle(CURLM* multi_handle);
  18. void Clear(CURLM* multi_handle);
  19. private:
  20. void AppendData(char* buffer, size_t size, size_t nmemb);
  21. static size_t WriteData(char *buffer, size_t size, size_t nmemb, void *userp);
  22. std::string url_;
  23. std::string post_data_;
  24. CURL* handle_ = nullptr;
  25. struct curl_slist *chunk_ = nullptr;
  26. FinishCallback cb_;
  27. int response_ret_;
  28. };

http_request.cc

  1. /*http_request.h的实现*/
  2. int HttpRequest::Init(const std::vector<std::string> &headers) {
  3. handle_ = curl_easy_init();
  4. if (handle_ == nullptr) {
  5. return kCurlEasyInitFailed;
  6. }
  7. CURLcode ret = curl_easy_setopt(handle_, CURLOPT_URL, url_.c_str());
  8. if (ret != CURLE_OK) {
  9. return ret;
  10. }
  11. ret = curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION,
  12. &HttpRequest::WriteData);
  13. if (ret != CURLE_OK) {
  14. return ret;
  15. }
  16. ret = curl_easy_setopt(handle_, CURLOPT_WRITEDATA, this);
  17. if (ret != CURLE_OK) {
  18. return ret;
  19. }
  20. ret = curl_easy_setopt(handle_, CURLOPT_NOSIGNAL, 1);
  21. if (ret != CURLE_OK) {
  22. return ret;
  23. }
  24. ret = curl_easy_setopt(handle_, CURLOPT_PRIVATE, this);
  25. if (ret != CURLE_OK) {
  26. return ret;
  27. }
  28. ret = curl_easy_setopt(handle_, CURLOPT_POSTFIELDSIZE, post_data_.length());
  29. if (ret != CURLE_OK) {
  30. return ret;
  31. }
  32. ret = curl_easy_setopt(handle_, CURLOPT_POSTFIELDS, post_data_.c_str());
  33. if (ret != CURLE_OK) {
  34. return ret;
  35. }
  36. ret = curl_easy_setopt(handle_, CURLOPT_TIMEOUT_MS, 100);
  37. if (ret != CURLE_OK) {
  38. return ret;
  39. }
  40. // ret = curl_easy_setopt(handle_, CURLOPT_CONNECTTIMEOUT_MS, 10);
  41. ret = curl_easy_setopt(handle_, CURLOPT_DNS_CACHE_TIMEOUT,
  42. 600);
  43. if (ret != CURLE_OK) {
  44. return ret;
  45. }
  46. chunk_ = curl_slist_append(chunk_, "Expect:");
  47. for (auto item : headers) {
  48. chunk_ = curl_slist_append(chunk_, item.c_str());
  49. }
  50. ret = curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, chunk_);
  51. if (ret != CURLE_OK) {
  52. return ret;
  53. }
  54. // 设置http header
  55. if (boost::algorithm::starts_with(url_, "https://")) {
  56. curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYPEER, false);
  57. curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYHOST, false);
  58. }
  59. return 0;
  60. }
  61. //获取easy handle
  62. CURL* HttpRequest::GetHandle() {
  63. return handle_;
  64. }
  65. // 获取http server端的响应
  66. const std::string& HttpRequest::Response() {
  67. return response_;
  68. }
  69. //设置回调函数,当server返回完成之后,调用
  70. void HttpRequest::SetFinishCallback(const FinishCallback& cb) {
  71. cb_ = cb;
  72. }
  73. // libcurl 错误码信息
  74. int HttpRequest::OnFinish(int response_ret) {
  75. response_ret_ = response_ret;
  76. if (cb_) {
  77. return cb_();
  78. }
  79. return -1;
  80. }
  81. // 执行http请求
  82. CURLcode HttpRequest::Perform() {
  83. CURLcode ret = curl_easy_perform(handle_);
  84. return ret;
  85. }
  86. // 将easy handle 加入到被监控的multi handle
  87. void HttpRequest::AddMultiHandle(CURLM* multi_handle) {
  88. if (multi_handle != nullptr) {
  89. curl_multi_add_handle(multi_handle, handle_);
  90. }
  91. }
  92. // 释放资源
  93. void HttpRequest::Clear(CURLM* multi_handle) {
  94. curl_slist_free_all(chunk_);
  95. if (multi_handle != nullptr) {
  96. curl_multi_remove_handle(multi_handle, handle_);
  97. }
  98. curl_easy_cleanup(handle_);
  99. }
  100. // 获取返回
  101. void HttpRequest::AppendData(char* buffer, size_t size, size_t nmemb) {
  102. response_.append(buffer, size * nmemb);
  103. }
  104. // 回调函数,获取返回内容
  105. size_t HttpRequest::WriteData(
  106. char* buffer, size_t size,
  107. size_t nmemb, void* userp) {
  108. HttpRequest* req = static_cast<HttpRequest*>(userp);
  109. req->AppendData(buffer, size, nmemb);
  110. return size * nmemb;
  111. }

main.cc

  1. curl_global_init(CURL_GLOBAL_DEFAULT);
  2. auto multi_handle_ = curl_multi_init();
  3. int numfds = 0;
  4. int running_handles = 0;
  5. while (true) {
  6. //此处读者来实现,基本功能如下:
  7. // 1、获取上游的请求内容,从里面获取要发送http的相关信息
  8. // 2、通过步骤1获取的相关信息,来创建HttpRequest对象
  9. // 3、将该HttpRequest对象跟multi_handle_对象关联起来
  10. curl_multi_perform(multi_handle_, &running_handles);
  11. CURLMcode mc = curl_multi_wait(multi_handle_, nullptr, 0,
  12. 200, &numfds);
  13. if (mc != CURLM_OK) {
  14. std::cerr << "RequestDispatcher::Run"
  15. << " curl_multi_wait failed, ret: "
  16. << mc;
  17. continue;
  18. }
  19. curl_multi_perform(multi_handle_, &running_handles);
  20. CURLMsg* msg = nullptr;
  21. int left = 0;
  22. while ((msg = curl_multi_info_read(multi_handle_, &left))) {
  23. // msg->msg will always equals to CURLMSG_DONE.
  24. // CURLMSG_NONE and CURLMSG_LAST were not used.
  25. if (msg->msg != CURLMSG_DONE) {
  26. std::cerr << "RequestDispatcher::Run"
  27. << " curl_multi_info_read failed, msg: "
  28. << msg->msg;
  29. continue;
  30. }
  31. CURL* easy_handle = msg->easy_handle;
  32. CURLcode curl_ret = msg->data.result;
  33. int response_ret = kOk;
  34. if (curl_ret != CURLE_OK) {
  35. std::cerr << "RequestDispatcher::Run"
  36. << " msg->data.result != CURLE_OK, curl_ret: "
  37. << curl_ret;
  38. response_ret = static_cast<int>(curl_ret);
  39. }
  40. int http_status_code = 0;
  41. curl_ret = curl_easy_getinfo(easy_handle,
  42. CURLINFO_RESPONSE_CODE, &http_status_code);
  43. if (curl_ret != CURLE_OK) {
  44. std::cerr << "RequestDispatcher::Run"
  45. << " curl_easy_getinfo failed, curl_ret="
  46. << curl_ret;
  47. if (response_ret == kOk) {
  48. response_ret = static_cast<int>(curl_ret);
  49. }
  50. }
  51. if (http_status_code != 200) {
  52. std::cerr << "RequestDispatcher::Run"
  53. << " http_status_code=" << http_status_code;
  54. if (response_ret == kOk) {
  55. response_ret = http_status_code;
  56. }
  57. }
  58. HttpRequest* req = nullptr;
  59. // In fact curl_easy_getinfo will
  60. // always return CURLE_OK if CURLINFO_PRIVATE was set.
  61. curl_ret = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &req);
  62. if (curl_ret != CURLE_OK) {
  63. std::cerr << "RequestDispatcher::Run"
  64. << " curl_easy_getinfo failed, curlf_ret="
  65. << curl_ret;
  66. if (response_ret == kOk) {
  67. response_ret = static_cast<int>(curl_ret);
  68. }
  69. }
  70. if (req != nullptr) {
  71. ret = req->OnFinish(response_ret);
  72. }
  73. }
  74. }

至此,我们已经可以使用libcurl来实现并发发送http请求,当然这个只是一个简单异步实现功能,更多的功能,还需要读者去使用libcurl中的其他功能去实现,此处留给读者一个问题(这个问题,也是笔者项目中使用的一个功能,该项目已经线上稳定运行4年,日请求量在20E图片),业务需要,某一个请求需要并发发送给指定的几家,即该请求,需要并发发送给几个http server,在一个特定的超时时间内,获取这几个http server的返回内容,并进行处理,那么这种功能应该如何使用libcurl来实现呢?透露下,可以使用libcurl的另外一个参数CURLOPT_PRIVATE。

3 性能对比

至此,我们已经基本完成了高性能http 并发功能的设计,那么到底性能如何呢?笔者从 以下几个角度来做了测试:

1、串行发送同步请求

2、多线程情况下,发送同步请求(此处线程为4个,笔者测试的服务器为4C)

3、使用multi接口

4、使用multi接口,并复用其对应的easy handle

5、使用dns cache(对easy handle设置CURLOPT_DNS_CACHE_TIMEOUT),即不用每次都进行dns解析

方法 平均耗时(ms) 最大耗时(ms)
串行同步 21.381 30.617
多线程同步 4.331 16.751
multi接口 1.376 11.974
multi接口 连接复用 0.352 0.748
multi 接口使用dns cache 0.381 0.731

4 一点心得

libcurl是一个高性能,较易用的HTTP client,在使用其直接,一定要对其接口功能进行详细的了解,否则很容易入坑,犹记得在18年中的时候,上线了某一个功能,会偶现coredump(在上线之前,也进行了大量的性能测试,都没有出现过一次coredump),为了分析这个原因,笔者将服务的代码一直精简精简,然后模拟测试,缩小coredump定位范围,最终发现,只有在超时的时候,才会导致coredump,这就说明了为什么测试环境没有coredump,而线上会产生coredump,这是因为线上的超时时间设置的是5ms,而测试环境超时时间是20ms,这就基本把原因定位到超时导致的coredump。

然后,分析libcurl源码,发送时一个libcurl的参数设置导致coredump,至此,笔者耗费了23个小时,问题才得以解决。

更多精彩内容,请移步

studyinfo.top

高并发HHTP实践的更多相关文章

  1. MySQL面试必考知识点:揭秘亿级高并发数据库调优与最佳实践法则

    做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离... 数据库的使用,是开发人员的基本功,对它掌握越清晰越深入,你能做的事情就越多. 今天我们用10分钟 ...

  2. 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力

    1.引言   达达创立于2014年5月,业务覆盖全国37个城市,拥有130万注册众包配送员,日均配送百万单,是全国领先的最后三公里物流配送平台. 达达的业务模式与滴滴以及Uber很相似,以众包的方式利 ...

  3. 一套高可用、易伸缩、高并发的IM群聊架构方案设计实践

    本文原题为“一套高可用群聊消息系统实现”,由作者“于雨氏”授权整理和发布,内容有些许改动,作者博客地址:alexstocks.github.io.应作者要求,如需转载,请联系作者获得授权. 一.引言 ...

  4. [转]10分钟梳理MySQL知识点:揭秘亿级高并发数据库调优与最佳实践法则

    转:https://mp.weixin.qq.com/s/RYIiHAHHStIMftQT6lQSgA 做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离 ...

  5. 朱晔的互联网架构实践心得S2E6:浅谈高并发架构设计的16招

    朱晔的互联网架构实践心得S2E6:浅谈高并发架构设计的16招 概览 标题中的高并发架构设计是指设计一套比较合适的架构来应对请求.并发量很大的系统,使系统的稳定性.响应时间符合预期并且能在极端的情况下自 ...

  6. 高并发IM系统架构优化实践

    互联网+时代,消息量级的大幅上升,消息形式的多元化,给即时通讯云服务平台带来了非常大的挑战.高并发的IM系统背后究竟有着什么样的架构和特性? 以上内容由网易云信首席架构师内部分享材料整理而成 相关阅读 ...

  7. 心知天气数据API 产品的高并发实践

    心知天气数据API 产品的高并发实践 心知天气作为国内领先的商业气象服务提供商,天气数据API 产品从公司创立以来就一直扮演着很重要的角色.2009 年API 产品初次上线,历经十年,我们不断用心迭代 ...

  8. MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"

    本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...

  9. Nginx Ingress 高并发实践

    概述 Nginx Ingress Controller 基于 Nginx 实现了 Kubernetes Ingress API,Nginx 是公认的高性能网关,但如果不对其进行一些参数调优,就不能充分 ...

随机推荐

  1. PGSQL数据库里物化视图【materialized view】

    1.普通视图 数据库中的视图(view)是从一张或多张数据库表查询导出的虚拟表,反映基础表中数据的变化,且本身不存储数据. 2.物化视图[materialized view]     2.1.概念:  ...

  2. 最长公共子序列问题(LCS)——Python实现

      # 最长公共子序列问题 # 作用:求两个序列的最长公共子序列 # 输入:两个字符串数组:A和B # 输出:最长公共子序列的长度和序列 def LCS(A,B): print('输入字符串数组A', ...

  3. XSS靶机

    第一关 localhost:8083/xss/level1.php?name=test<script>alert(1)</script> 第二关 源码 文本框输入js代码,查看 ...

  4. QT经验(一)——按钮长按事件分析

    引言 最近在做qt项目,需要对button按钮添加一个长按事件(比如点击按钮,开始运动.松开按钮,运动停止).查了些许资料,(差点误把QPushButton的press信号和长按事件混淆)在此记录一下 ...

  5. upload-lab 靶场实战

    文件上传/下载 漏洞 冲冲冲,好好学习 2020.02.13 淦靶场之前,先来点知识铺垫铺垫. 文件上传漏洞 前端Js绕过. MIME类型绕过 后缀名大写写绕过 / php4 .php5 00截断 覆 ...

  6. 关于C语言中对数字的扩展和缩短

    关于对数字的扩展:如果需要在不改变他的类型的情况下去扩展一个数字 有符号数字: 如果最高位为0---向左按位复制0 如果最高位为1---向左按位复制1 无符号数字:向左按位复制0即可 对于数字的缩短: ...

  7. 我为什么选Markdown

    前沿说明:Yaml Front Matter MarkDown 目录 前沿说明:Yaml Front Matter 什么是MarkDown Markdown是一种轻量级标记语言, 它允许人们使用易读易 ...

  8. netty系列之:中国加油

    目录 简介 场景规划 启动Server 启动客户端 消息处理 消息处理中的陷阱 总结 简介 之前的系列文章中我们学到了netty的基本结构和工作原理,各位小伙伴一定按捺不住心中的喜悦,想要开始手写代码 ...

  9. docker容器网络bridge

    我们知道docker利用linux内核特性namespace实现了网络的隔离,让每个容器都处于自己的小世界里面,当这个小世界需要与外界(宿主机或其他容器)通信的时候docker的网络就发挥作用了,这篇 ...

  10. OpenStack虚拟网络与物理网络的衔接(flat方式)

    by 无若 这边以CentOS7+Liberty版本为例. 过去一段时间(Juno版本之前版本),OpenStack内的虚拟网络与真正的物理网络衔接主要使用openvswitch,其主要问题是在配置网 ...