├── C++博文 ├── 【NO.107】HTTP 请求之合并与拆分技术详解.md ├── 【NO.117】2022年腾讯C++研发类笔试面试试题及答案.md ├── 【NO.125】Linux网络编程 零拷贝 :sendfile、mmap、splice、tee.md ├── 【NO.126】TCP将成为历史?看看谷歌的QUIC协议都做了些什么你就知道了.md ├── 【NO.145】分布式事务解决方案.md ├── 【NO.14】TCP 的封包格式:TCP 为什么要粘包和拆包?.md ├── 【NO.151】p2p之网络穿透NAT,NAT、穿透的原理.md ├── 【NO.156】 设计模式之工厂设计模式.md ├── 【NO.173】QUIC 协议原理浅解.md ├── 【NO.175】2021 有哪些不容错过的后端技术趋势.md ├── 【NO.182】【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现.md ├── 【NO.198】互斥锁、自旋锁、原子操作的原理、区别及应用场景.md ├── 【NO.19】C++中STL用法超详细总结(收藏级).md ├── 【NO.201】后端开发-MySQL数据库相关流程图原理图.md ├── 【NO.207】详解内存池设计与实现.md ├── 【NO.212】亿级流量架构之网关设计思路、常见网关对比.md ├── 【NO.213】【Redis】利用 Redis 实现分布式锁.md ├── 【NO.219】perf-网络协议栈性能分析.md ├── 【NO.221】实例分析Linux内存泄漏检测方法.md ├── 【NO.23】一篇文章彻底搞懂websocket协议的原理与应用(一).md ├── 【NO.248】从一道数据库面试题彻谈MySQL加锁机制.md ├── 【NO.257】百亿规模API网关服务Shepherd的设计与实现.md ├── 【NO.295】C++高性能服务器框架——日志系统详解.md ├── 【NO.356】QQ音乐高可用架构体系.md ├── 【NO.374】一文带你了解大厂亿级并发下高性能服务器是如何实现的!.md ├── 【NO.375】你真的懂Redis与MySQL双写一致性如何保证吗?.md ├── 【NO.380】深入理解 ProtoBuf 原理与工程实践.md ├── 【NO.428】最强阿里巴巴历年经典面试题汇总:C++研发岗.md ├── 【NO.511】Linux网络分析必备技能:tcpdump实战详解.md ├── 【NO.53】高并发系统建设经验总结.md ├── 【NO.593】一道腾讯面试题目:没有listen,能否建立TCP连接.md ├── 【NO.608】Linux 进程间通信:管道、共享内存、消息队列、信号量.md ├── 【NO.91】带你快速了解 Docker 和 Kubernetes.md └── 【NO.99】Linux服务器检查性能瓶颈.md ├── Dijkstral.cpp ├── Floyd.cpp ├── KReverse.cpp ├── LeastWaysOfMoney.cpp ├── MedianNumOfTwoArrs.cpp ├── MyString.cpp ├── Producer_Consumer.cpp ├── README.md ├── SlidingWindows.cpp ├── TotalWaysOfMoney.cpp ├── TwoBig.cpp ├── canyane ├── demo.cpp ├── 数据库.md └── 计网.md ├── parseHttp.cpp ├── record.sql ├── threadPool.cpp ├── 常见手撕算法总结.cpp └── 第二次博客总结 ├── cmake知识点 ├── CMake基础 第10节 使用ninja构建.md ├── CMake基础 第11节 导入目标.md ├── CMake基础 第12节 设置C++标准.md ├── CMake基础 第13节 构建子项目.md ├── CMake基础 第14节 在文件中进行变量替换.md ├── CMake基础 第15节 使用Protobuf生成源文件.md ├── CMake基础 第16节 创建deb文件.md ├── CMake基础 第17节 Clang分析器.md ├── CMake基础 第18节 Boost单元测试框架.md ├── CMake基础 第1节 初识CMake.md ├── CMake基础 第2节 分离编译.md ├── CMake基础 第3节 静态库.md ├── CMake基础 第4节 动态库.md ├── CMake基础 第5节 安装项目.md ├── CMake基础 第6节 生成类型.md ├── CMake基础 第7节 编译标志.md ├── CMake基础 第8节 包含第三方库.md └── CMake基础 第9节 使用Clang编译.md ├── 【NO.120】十个问题理解Linux epoll工作原理.md ├── 【NO.133】深入理解异步IO+epoll+协程.md ├── 【NO.141】常见C++面试题及基本知识点总结.md ├── 【NO.566】万字长文漫谈高可用高并发技术.md ├── 【NO.577】httphttps服务器的实现.md ├── 【NO.581】C++高性能协程分布式服务框架设计.md ├── 【NO.583】一文搞懂 mmap 涉及的所有内容.md ├── 【NO.624】【进程管理】fork之后子进程到底复制了父进程什么?.md ├── 【NO.67】面试题Linux网络编程中可靠UDP,KCP协议快在哪.md └── 【NO.87】浅谈 Protobuf 编码.md /C++博文/【NO.107】HTTP 请求之合并与拆分技术详解.md: -------------------------------------------------------------------------------- 1 | # 【NO.107】HTTP 请求之合并与拆分技术详解 2 | 3 | 导语:HTTP/2 中,是否还需要减少请求数?来看看实验数据吧。 4 | 5 | ## **1. 背景** 6 | 7 | 随着网站升级 HTTP/2 协议,在浏览页面时常常会发现页面的请求数量很大,尤其是小图片请求,经典的雅虎前端性能优化军规中的第 1 条就是减少请求数,在 HTTP/1.1 时代合并雪碧图是这种场景减少请求数的一大途径,但是现在这些图片是使用 HTTP/2 协议传输的,这种方式是否也适用?另外,在都使用 HTTP/2 的情况,在浏览器并发这么多小图片请求时,是否会影响其他静态资源的拉取速度(例如页面 js 文件的请求耗时)? 8 | 9 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171542054725654.png) 10 | 11 | 基于上面问题的思考,本文进行了一个简单的实验,尝试通过数据来分析 HTTP 中的合并与拆分,以及并发请求是否影响其他请求。通过这次的实验我们对比了以下几个不同 HTTP 场景的耗时数据: 12 | 13 | - HTTP/1.1 合并 VS 拆分 14 | - HTTP/1.1 VS HTTP/2 并发请求 15 | - HTTP/2 合并 VS 拆分 16 | - 浏览器并发 HTTP/2 请求数(大量 VS 少量)时,其他请求的耗时 17 | 18 | ## **2. 实验准备** 19 | 20 | 理论:合并与拆分都是 HTTP 请求优化的常用方法,合并主要为了减少请求数,可以减少多次建立 TCP 连接耗时,不过相对的,缓存命中率会受到影响;拆分主要为了利用并发能力,浏览器可以并发多个 TCP 连接,还可以结合 HTTP/1.1 中的长链接,不过受 HTTP 队头阻塞影响,并发能力并不强,于是 HTTP/2 协议出现,使用多路复用、头部压缩等技术很好的解决了 HTTP 队头阻塞问题,实现了较强的并发能力。而 HTTP/2 由于基于 TCP,依然无法绕过 TCP 队头阻塞问题,于是又出现了 HTTP/3,不过本文并不讨论 HTTP/3,有兴趣的同学可以自行 Google。实验环境: 21 | 22 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171542251896603.png) 23 | 为了避免自己搭服务器可能存在的性能影响,实验中的图片资源数据使用腾讯云的 COS 存储,并开启了 CDN 加速。 24 | 25 | ## **3. 实验分析** 26 | 27 | 第一个实验:有 2 个 HTML。1 个 HTML 中并发加载 361 张小图片,记录所有图片加载完成时的耗时;另 1 个 HTML 加载合并图并记录其耗时。并分别记录基于 HTTP/1.1 和 HTTP/2 协议的不同限速情况的请求耗时情况。每个场景测试 5 次,每次都间隔一段时间避免某一时间段网络不好造成的数据偏差,最后计算平均耗时。实验数据: 28 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171542367883789.png) 29 | 30 | ### 3.1 HTTP/1.1 合并 VS 拆分 31 | 32 | 根据上面实验数据,抽出其中 HTTP/1.1 的合并和拆分的数据来看,很明显拆分的多个小请求耗时远大于合并的请求,且网速较低时差距更大。 33 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171543402585101.png) 34 | 35 | ### **3.2 HTTP/1.1 合并请求的优化原理** 36 | 37 | 简单看下 HTTP 请求的主要过程:DNS 解析(T1) -> 建立 TCP 连接(T2) -> 发送请求(T3) -> 等待服务器返回首字节(TTFB)(T4) -> 接收数据(T5)。 38 | 39 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171544051519645.png) 40 | 41 | 从上面请求过程中,可以看出当多个请求时,请求中的 DNS 解析、建立 TCP 连接等步骤都会重复执行多遍。那么如果合并 N 个 HTTP 请求为 1 个,理论上可以节省(N-1)* (T1+T2+T3+T4) 的时间。当然实际场景并没有这么理想,比如浏览器会缓存 DNS 信息,因此不是每次请求都需要 DNS 解析;比如 HTTP/1.1 keep-alive 的特性,使 HTTP 请求可以复用已有 TCP 连接,所以并不是每个 HTTP 请求都需要建立新的 TCP 连接;再比如浏览器可以并行发送多个 HTTP 请求,同样可能影响到资源的下载时间,而上面的分析显然只是基于同一时刻只有 1 个 HTTP 请求的场景。 42 | 43 | 感兴趣深入了解的可以参考网上一篇[HTTP/1.1 详细实验数据](https://segmentfault.com/a/1190000015665465),其结论是:当文件体积较小的时候,(网络延迟低的场景下)合并后的文件的加载耗时明显小于加载多个文件的总耗时;当文件体积较大的时候,合并请求对于加载耗时没有明显的影响,且拆分资源可以提高缓存命中率。但是注意有特殊的场景,由于合并资源后可能导致网络往返次数的增加,当网络延迟很大时,是会增大耗时的(参考 TCP 拥塞控制)。 44 | 45 | 【扩展:TCP 拥塞控制】 TCP 中包含一种称为拥塞控制的机制,拥塞控制的主要工作是确保网络不会同时被过多的数据传输导致过载。当前拥塞控制的方法有许多,主要原理是慢启动,例如,开始阶段只发送一点数据,观察是否能通过,如果能接收方将确认发送回发送方,只要所有数据都得到确认,发送方就在下次 RTT 时将发送数据量加倍,直到观察到丢包事件(丢包意味着过载,需要后退),每次发送的数据量即拥塞窗口,就是这样动态调整拥塞窗口来避免拥塞。拥塞控制机制对每个 TCP 连接都是独立的。 46 | 47 | ### 3.3 HTTP/1.1 VS HTTP/2 并发请求 48 | 49 | 抽出实验数据中的 HTTP/1.1 和 HTTP/2 并发请求来对比分析,可以看出 HTTP/2 的并发总耗时明显优于 HTTP/1.1,且网速越差,差距越大。 50 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171545464234968.png) 51 | 52 | ### **3.4 HTTP/2 多路复用和头部压缩的原理** 53 | 54 | **多路复用** :在一个 TCP 链接中可以并行处理多个 HTTP 请求,主要是通过流和帧实现,一个流代表一个 HTTP 请求,每个 HTTP 资源拆分成一个个的帧按顺序进行传输,不同流的帧可以穿插传输,最终依然能根据流 ID 组合成完整资源,以此实现多路复用。帧的类型有 11 种,例如 headers 帧(请求头/响应头),data 帧(body),settings 帧(控制传输过程的配置信息,例如流的并发上限数、缓冲容量、每帧大小上限)等等。 55 | 56 | **头部压缩** :为了节约传输消耗,通过压缩的方式传输同一个 TCP 链接中不同 HTTP 请求/响应的头部数据,主要利用了静态表和动态表来实现,静态表规定了常用的一些头部,只用传输一个索引即可表示,动态表用于管理一些头部数据的缓存,第一次出现的头部添加至动态表中,下次传输同样的头部时就只用传输一个索引即可。由于基于 TCP,头部帧的发送和接收后的处理顺序是保持一致的,因此两端维护的动态表也就保证一致。 57 | 58 | 多路复用允许一次 TCP 链接处理多次 HTTP 请求,头部压缩又大大减少了多个 HTTP 请求可能产生的重复头部数据消耗。因此 HTTP/2 可以很好的支持并发请求,感兴趣可以[深入 HTTP/2 浏览器源码分析](https://zhuanlan.zhihu.com/p/34662800)。 59 | 60 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171546037946496.png) 61 | 62 | 【扩展:队头阻塞】 HTTP/2 解决了 HTTP/1.1 中 HTTP 层面(应用层)队头阻塞的问题,但是由于 HTTP/2 仍然基于 TCP,因此 TCP 层面的队头阻塞依然存在。HTTP/3 使用 QUIC 解决了 TCP 队头阻塞的问题。感兴趣可以看看[队头阻塞](https://zhuanlan.zhihu.com/p/330300133)这篇文章。 63 | 64 | HTTP 层面的队头阻塞在于,HTTP/1.1 协议中同一个 TCP 连接中的多个 HTTP 请求只能按顺序处理,方式有两种标准,非管道化和管道化两种,非管道化方式:即串行执行,请求 1 发送并响应完成后才会发送请求 2,一但前面的请求卡住,后面的请求就被阻塞了;管道化方式:即请求可以并行发出,但是响应也必须串行返回(只提出过标准,没有真正应用过)。 65 | 66 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171546123896844.png) 67 | 68 | TCP 层面的队头阻塞在于,TCP 本身不知道传输的是 HTTP 请求,TCP 只负责传递数据,传递数据的过程中会将数据分包,由于网络本身是不可靠的,TCP 传输过程中,当存在数据包丢失的情况时,顺序排在丢失的数据包之后的数据包即使先被接收也不会进行处理,只会将其保存在接收缓冲区中,为了保证分包数据最终能完整拼接成可用数据,所丢失的数据包会被重新发送,待重传副本被接收之后再按照正确的顺序处理它以及它后面的数据包。 69 | 70 | But,由于 TCP 的握手协议存在,TCP 相对比较可靠,TCP 层面的丢包现象比较少见,需要明确的是,TCP 队头阻塞是真实存在的,但是对 Web 性能的影响比 HTTP 层面队头阻塞小得多,因此 HTTP/2 的性能提升还是很有作用的。 71 | 72 | HTTP/2 中存在 TCP 的队头阻塞问题主要由于 TCP 无法记录到流 id,因为如果 TCP 数据包携带流 id,所丢失的数据包就只会影响数据包中相关流的数据,不会影响其他流,所以顺序在后的其他流数据包被接收到后仍可处理。出于各种原因,无法改造 TCP 本身,因此为了解决 HTTP/2 中存在的 TCP 对头阻塞问题,HTTP/3 在传输层不再基于 TCP,改为基于 UDP,在 UDP 数据帧中加入了流 id 信息。 73 | 74 | ### 3.5 HTTP/2 合并 VS 拆分 75 | 76 | 由于 HTTP/2 支持多路复用和头部压缩,是不是原来 HTTP/1.1 中的合并请求的优化方式就没用了,在 HTTP/2 中合并雪碧图有优化效果吗? 77 | 78 | 抽出 HTTP/2 的合并和拆分的数据来看,拆分的多个小请求耗时仍大于合并的请求,不过差距明显缩小了很多。 79 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171546296426561.png))那么为什么差距还是挺大呢?理论上 HTTP/2 的场景下,带宽固定,总大小相同的话,拆分的多个请求最好的情况应该是接近合并的总耗时的才对吧。 80 | 81 | 分析一下,因为是复用一个 TCP 连接,所以首先排除重复 DNS 查询、建立 TCP 连接这些影响因素。那么再分析一下资源大小的影响: 82 | 83 | 1. 本身合并的图片(516kB)就比拆分的 361 张小图片总大小(总 646kB)要小。 84 | 2. 拆分的很多个小请求时,虽然有头部压缩,但是请求和响应中的头部数据以及一些 settings 帧数据还是会多一些。通过查看 chrome 的 transferred 可以知道小图片最终总传输数据 741kB,说明除 body 外多传输了将近 100kB 的数据。 85 | 86 | 结合上面两点,理论上拆分的小图片总耗时应该是合并图片的耗时的(741/516=)1.44 倍。但是很明显测试中各网速场景下拆分的小图片总耗时与合并图片耗时的比值都大于 1.44 这个理论值(2.62、2.96、1.84)。其中的原因这里有两点推测: 87 | 88 | 1. 并发多个请求的总耗时计算的是所有请求加载完的耗时,每个请求都有发送和响应过程,其中分为一个个帧的传输过程,只要其中某小部分发生阻塞,就会拖累总耗时情况。 89 | 2. 浏览器在处理并发请求过程存在一定的调度策略而导致。推测的依据来自 Chrome 开发者工具中的 Waterfall,可以看到很多并发请求的 Queueing Time、Stalled Time 很高,说明浏览器不会在一开始就并行发送所有请求。 90 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171546433126427.png) 91 | 92 | 不过这里只属于猜测,还未深入探究。 93 | 94 | ### 3.6 浏览器并发 HTTP/2 请求数(大量 VS 少量)时,其他请求的耗时 95 | 96 | 第二个实验:在 HTML 中分别基于 HTTP/2 加载 360+张小图片、130+张小图片、20+张小图片、0 张小图片,以及 1 张大图片和 1 个 js 文件,大图片在 DOM 中放在所有小图片的后面,图片都是同域名的,js 文件是不同域名的,然后记录大图片和脚本的耗时,同样也是利用 Chrome 限速工具在不同的网络限速下测试(不过这个连的 WIFI 与第一个实验中不同,无限速时的网速略微不同)。这个实验主要用于分析并发请求过多时是否会影响其他请求的访问速度。实验数据: 97 | 98 | ![img](https://linuxcpp.0voice.com/zb_users/upload/2022/12/202212171546554859125.png) 99 | 100 | 从实验数据中可以看出, 101 | 102 | 1. 图片并发数量不会影响 js 的加载速度,无限速时无论并发图片请求有多少,脚本加载都只要 0.12s 左右。 103 | 2. 很明显对大图片的加载速度有影响,可以看到并发量从大到小时,大图片的耗时明显一次减少。 104 | 105 | 但是其中也有几个反常的数据:Fast3G 和 Slow3G 的网速限制下,无小图片时的 js 加载耗时明显高于有并发小图片请求的 js 加载耗时。这是为啥?我们推测这里的原因是,由于图片和 js 不同域名,分别在两个 TCP 连接中传输,两个 TCP 是分享总网络带宽的,当有多个小图片时,小图片在 DOM 前优先级高,js 和小图片分享网络带宽,js 体积较大占用带宽较多,而无小图片时,js 是和大图片分享网络带宽,js 占用带宽比率变小,因此在限速时带宽不够的情况下表现出这样的反常数据。 106 | 107 | ## **4. 实验结论** 108 | 109 | 1. HTTP/1.1 中合并请求带来的优化效果还是明显的。 110 | 2. 对于多并发请求的场景 HTTP/2 比 HTTP/1.1 的优势也是挺明显的。不过也要结合具体环境,HTTP/2 中由于复用 1 个 TCP 链接,如果并发中某一个大请求资源丢包率严重,可能影响导致整个 TCP 链路的流量窗口一直很小,而这时 HTTP/1.1 中可以开启多个 TCP 链接可能其他资源的加载速度更快?当然这也只是个人猜测,没有具体实验过。 111 | 3. HTTP/2 中合并请求耗时依然会比拆分的请求总耗时低一些,但是相对来说效果没有 HTTP/1.1 那么明显,可以多结合其他因素,例如拆分的必要性、缓存命中率需求等,综合决策是否合并或拆分。 112 | 4. 网速较好的情况下,非同域名下的请求相互间不受影响,同域名的并发请求,随着并发量增大,优先级低的请求耗时也会增大。 113 | 114 | 不过,本文中的实验环境较为有限,说不定换了一个环境会得到不同的数据和结论?比如不同的浏览器(Firefox、IE 等)、不同的操作系统(Windows、Linux 等)、不同的服务端能力以及不同测试资源等等,大家感兴趣也可以抽点时间试一试。 115 | 116 | **5. 其他思考** 117 | 118 | 以上讨论主要针对低计算量的静态资源,那么高计算量的动态资源的请求呢,(例如涉及鉴权、数据库查询之类的),合并 vs. 拆分? 119 | 120 | **关于我们** 121 | 122 | 我们团队主要致力于前端相关技术的研究和在腾讯业务的应用,团队内部每周有内部分享会,有兴趣的读者可以加入我们或者参与一起讨论,微信 : darminzhou; camdyzeng。 123 | 124 | 原文作者:darminzhou,腾讯 CSIG 前端开发工程师 125 | 126 | 原文链接:https://mp.weixin.qq.com/s/gx5UCRcxG00YZq2Go_xsxQ -------------------------------------------------------------------------------- /C++博文/【NO.125】Linux网络编程 零拷贝 :sendfile、mmap、splice、tee.md: -------------------------------------------------------------------------------- 1 | # 【NO.125】Linux网络编程 | 零拷贝 :sendfile、mmap、splice、tee 2 | 3 | ## 1.传统文件传输的问题 4 | 5 | 在网络编程中,如果我们想要提供文件传输的功能,最简单的方法就是用read将数据从磁盘上的文件中读取出来,再将其用write写入到socket中,通过网络协议发送给客户端。 6 | 7 | ```text 8 | ssize_t read(int fd, void *buf, size_t count); 9 | ssize_t write(int fd, const void *buf, size_t count); 10 | ``` 11 | 12 | 但是就是这两个简单的操作,却带来了大量的性能丢失 13 | 14 | 例如我们的服务器需要为客户端提供一个下载操作,此时的操作如下 15 | 16 | ![img](https://pic2.zhimg.com/80/v2-d5bcafefde2ebf48d0b0c3efbb3c6f51_720w.webp) 17 | 18 | 从上图可以看出,虽然仅仅只有这两行代码,但是却在发生了**四次用户态和内核态的上下文切换**,以及**四次数据拷贝**,也就是在这个地方产生了大量不必要的损耗。 19 | 20 | 那么为什么会发生这些操作呢? 21 | 22 | **上下文切换** 23 | 24 | 由于read和recv是系统调用,所以每次调用该函数我们都需要从用户态切换至内核态,等待内核完成任务后再从内核态切换回用户态。 25 | 26 | **数据拷贝** 27 | 28 | 上面也说了,由于数据的读取与写入都是由系统进行的,那么我们就得将数据从用户的缓冲区中拷贝到内核, 29 | 30 | - 第一次拷贝:将磁盘中的数据拷贝到内核的缓冲区中 31 | - 第二次拷贝:内核将数据处理完,接着拷贝到用户缓冲区中 32 | - 第三次拷贝:此时需要通过socket将数据发送出去,将用户缓冲区中的数据拷贝至内核中socket的缓冲区中 33 | - 第四次拷贝:把内核中socket缓冲区的数据拷贝到网卡的缓冲区中,通过网卡将数据发送出去。 34 | 35 | 所以要想优化传输性能,就要从**减少数据拷贝和用户态内核态的上下文切换**下手,这也就是**零拷贝**技术的由来。 36 | 37 | ## 2.**什么是零拷贝呢?** 38 | 39 | **零拷贝的主要任务就是避免CPU将数据从一块存储中拷贝到另一块存储**,主要就是利用各种技术,避免让CPU做大量的数据拷贝任务,以此减少不必要的拷贝。或者借助其他的一些组件来完成简单的数据传输任务,让CPU解脱出来专注别的任务,使得系统资源的利用更加有效 40 | 41 | Linux中实现零拷贝的方法主要有以下几种,下面一一对其进行介绍 42 | 43 | 1. sendfile 44 | 2. mmap 45 | 3. splice 46 | 4. tee 47 | 48 | ## 3.sendfile 49 | 50 | sendfile函数的作用是直接在两个文件描述符之间传递数据。由于整个操作完全在内核中(直接从内核缓冲区拷贝到socket缓冲区),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝。 51 | 52 | 需要注意的是,in_fd必须是一个支持类似mmap函数的文件描述符,不能是socket或者管道,而out_fd必须是一个socket,由此可见sendfile是专门为了在网络上传输文件而实现的函数。 53 | 54 | ![img](https://pic3.zhimg.com/80/v2-0e9a420edd62f49ddcc0241a984a04f6_720w.webp) 55 | 56 | ```text 57 | #include 58 | ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); 59 | ``` 60 | 61 | > 参数: 62 | > out_fd : 待写入内容的文件描述符 63 | > in_fd : 待读出内容的文件描述符 64 | > offset : 文件的偏移量 65 | > count : 需要传输的字节数 66 | > 返回值: 67 | > 成功:返回传输的字节数 68 | > 失败:返回-1并设置errno 69 | 70 | ## 4.**mmap** 71 | 72 | mmap用于申请一段内存空间,也就是我们在进程间通信中提到过的**共享内存**,通过将内核缓冲区的数据映射到用户空间中,两者通过共享缓冲区直接访问统一资源,此时内核与用户空间就不需要再进行任何的数据拷贝操作了 73 | 74 | ![img](https://pic4.zhimg.com/80/v2-13655c6ca2a0154f7d75fd348251e287_720w.webp) 75 | 76 | 其中mmap用于申请空间,额munmap用于释放这段空间。 77 | 78 | ```text 79 | #include 80 | void *mmap(void *addr, size_t length, int prot, int flags, 81 | int fd, off_t offset); 82 | int munmap(void *addr, size_t length); 83 | ``` 84 | 85 | 参数: 86 | 87 | addr : 内存的起始地址,如果设置为空则系统会自动分配 88 | 89 | length : 指定内存段的长度 90 | 91 | prot : 内存段的访问权限,通过按位与或可以取以下几种值 92 | 93 | ![img](https://pic3.zhimg.com/80/v2-40c76a8d03204163345541e5b166640a_720w.webp) 94 | 95 | flag : 选项 96 | 97 | ![img](https://pic3.zhimg.com/80/v2-92610c23cd71caf6cee15fdceea1a356_720w.webp) 98 | 99 | fd : 被映射文件对应的文件描述符 100 | 101 | offset : 文件的偏移量 102 | 103 | 返回值: 104 | 105 | 成功:成功时返回指向内存区域的指针 106 | 107 | 失败:返回MAP_FAILED并设置errno 108 | 109 | ## 5.**splice** 110 | 111 | splice函数用于**在两个文件描述符之间移动数据**,而不需要数据在内核空间和用户空间中来回拷贝 112 | 113 | 需要注意的是,使用splice函数时fd_in和fd_out**至少有一个是管道文件描述符,即** 114 | 115 | ```text 116 | #include 117 | ssize_t splice(int fd_in, loff_t *off_in, int fd_out, 118 | loff_t *off_out, size_t len, unsigned int flags); 119 | ``` 120 | 121 | 参数: 122 | 123 | out_fd : 待写入内容的文件描述符 124 | 125 | off_out : 待写入文件描述符的偏移量,如果文件描述符为管道则必须为空 126 | 127 | in_fd : 待读出内容的文件描述符 128 | 129 | off_in : 待读出文件描述符的偏移量,如果文件描述符为管道则必须为空 130 | 131 | len : 需要复制的字节数 132 | 133 | flags : 选项 134 | 135 | ![img](https://pic1.zhimg.com/80/v2-27e32c19c51d98b2392b064a5821c450_720w.webp) 136 | 137 | 返回值: 138 | 139 | 成功:返回在两个文件描述符之间复制的字节数 140 | 141 | 没有数据:返回0 142 | 143 | 失败:返回-1并设置errno 144 | 145 | 可能产生的errno 146 | 147 | ![img](https://pic1.zhimg.com/80/v2-f31d8051fb9e4ce9374caf76b9e64f64_720w.webp) 148 | 149 | ## 6.**tee** 150 | 151 | tee函数用于**在两个管道文件描述符之间复制数据**,并且它是直接复制,不会将数据读出,所以源文件上的数据仍可以用于后面的读操作 152 | 153 | ```text 154 | #include 155 | ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags); 156 | ``` 157 | 158 | 参数: 159 | 160 | out_fd : 待写入内容的文件描述符 161 | 162 | in_fd : 待读出内容的文件描述符 163 | 164 | len : 需要复制的字节数 165 | 166 | flags : 选项 167 | 168 | 返回值: 169 | 170 | 成功:返回在两个文件描述符之间复制的字节数 171 | 172 | 没有数据:返回0 173 | 174 | 原文地址:https://zhuanlan.zhihu.com/p/592397046 175 | 176 | 作者:linux -------------------------------------------------------------------------------- /C++博文/【NO.126】TCP将成为历史?看看谷歌的QUIC协议都做了些什么你就知道了.md: -------------------------------------------------------------------------------- 1 | # 【NO.126】TCP将成为历史?看看谷歌的QUIC协议都做了些什么你就知道了 2 | 3 | 在过去几年中,QUIC已经成为谷歌服务网络通信的默认协议。正如这里所说的,QUIC现在被从Chrome浏览器到谷歌服务器的所有连接中的一半以上使用。它还得到了Microsoft Edge、Firefox和Opera的官方支持。 4 | 5 | ![img](https://pic2.zhimg.com/80/v2-5367efdcc38a220f8b239c1a7d46e3dd_720w.webp) 6 | 7 | 谷歌开发它时考虑到了网络安全,并用更先进和最新的技术取代了一些过时的标准。换句话说,QUIC可能代表着互联网的未来,这就是为什么理解它如此重要。 8 | 9 | 因此,让我们深入了解谷歌的QUIC协议,并介绍您需要了解的一切。 10 | 11 | ## **1.QUIC的历史** 12 | 13 | > QUIC最初代表“快速UDP Internet连接”,尽管该术语不再用作首字母缩略词。现在,“QUIC”被用来描述谷歌设计的通用传输层网络协议。 14 | > 2012年,最初开始实施和部署。 15 | > 2013年,在IETF会议上,随着实验范围的扩大,它被公开宣布。 16 | > 2015年6月,针对其规范的互联网草案提交给IETF进行标准化。 17 | > 2016年,QUIC工作组成立。 18 | > 2018年10月,QUIC上的HTTP映射开始被称为“HTTP/3”,使QUIC必然成为全球标准。 19 | > 2021 5月,IETF最终在RFC 9000中对其进行了标准化。 20 | 21 | ## **2.什么是QUIC?** 22 | 23 | > 谷歌的QUIC是一种基于UDP的低延迟互联网传输协议,该协议通常用于游戏、流媒体和VoIP服务。 24 | > UDP比TCP轻得多,但反过来,它的纠错服务比TCP少得多。 25 | > 通过QUIC,谷歌的目标是将UDP和TCP的一些最佳功能与现代安全工具相结合。-谷歌希望通过其QUIC协议加速网络 26 | 27 | 首先我们先来看看http协议,再带入到tcp,udp,quic,逐步探索。 28 | 29 | ## **3.HTTP 1:** 30 | 31 | 我们从HTTP/1.0开始,其中每个请求-响应对前面都是打开一个tcp连接,然后关闭同一个连接。这引入了大量延迟,并导致人们想出了一个名为keep-alive的解决方案,该解决方案在HTTP请求之间重用TCP连接,基本上延迟了连接的关闭。下面是有和没有keep alive的两个图表。 32 | 33 | ![img](https://pic1.zhimg.com/80/v2-06319b737a3b3a2e5ee9ae4748c4dfa0_720w.webp) 34 | 35 | 此示例还演示了在发出HTTP请求之前需要建立的TCP 3路握手。基本上,双方都使用序列号(SYN数据包)跟踪他们发送的内容。这样,如果他们丢失了任何数据包,就可以从最后一个序列号重新发送。请注意,作为每个连接建立的一部分,需要进行3路握手(SYN、SYN-ACK、ACK)。由于TCP是双向通信模式,因此需要在每个方向上发送ACK。FIN用于关闭TCP连接。 36 | 37 | 显而易见借助Keep-alive是一个很好的手段来保持连接。 38 | 39 | 仔细看,每个资源请求仍然需要在激发之前完成其先前的资源请求。例如,我不能发出索引请求。css在索引之前。html已返回。 40 | 41 | 嗯。他们是怎么解决的? 42 | 43 | ![img](https://pic1.zhimg.com/80/v2-631b5ce16c3b0ce1359371290608261c_720w.webp) 44 | 45 | 他们在HTTP/1.1中引入了一个名为pipeline流水线的特性。通过该特性,可以立即通过TCP连接发出每个HTTP请求,而无需等待前一个请求的响应返回。该图显示了HTTP/1.1.和pipeline。 46 | 47 | 报文的回复将以相同的顺序返回。这引入了一个称为HTTP线路头(HOL)阻塞的问题 48 | 49 | ## 4.什么是HOL? 50 | 51 | > 假设您正在请求一个猫的图像和一个javascript文件。如果猫的图像太大,服务器在完成发送图像之前不会开始发送javascript文件。 52 | 53 | 是不是听起来很可怕,那么如何解决这一问题? 54 | 55 | 最初的方法是让浏览器打开最多6个到同一服务器的连接,作为性能优化,开发人员开始在多个域之间共享资源,以支持服务器上超过6个资源的情况。此外,这并没有解决为每个单独的连接设置TCP(和TLS)握手的开销。在第2部分之前,我不会讨论TLS。 56 | 57 | 这也引入了一些创新,如css ,减少了要通过网络传输的单个资源的数量。 58 | 59 | 很快人们意识到这是不可扩展的,于是引入了一种新的方法,即HTTP/2和多路复用。 60 | 61 | 本质上,它声明TCP连接上的每个HTTP请求都可以立即发出,而无需等待上一个响应返回。响应可以按任何顺序返回。下图再次说明了使用流的HTTP/1.1流水线和HTTP/2复用。注意在HTTP/2中使用多路复用流的视图。css在cat.png之前返回 62 | 63 | ![img](https://pic4.zhimg.com/80/v2-355a531bcabba89f9939f763089f62cf_720w.webp) 64 | 65 | 此外,与HTTP/1.1不同,在HTTP/1.1中,作为HTTP头的一部分的资源标识仅在一组tcp包的第一个tcp包中,在HTTP/2中,每个tcp包都包含资源的标识。这使得被拆分为多个TCP数据包的HTTP响应可以很容易地重新组合,消除了HTTP/1.1的串行性 66 | 67 | 是的,看起来我们已经最大限度地改进了HTTP/2多路复用 68 | 69 | 不完全是这样,这里还有一个缺陷,但您必须更深入地研究TCP堆栈。 70 | 71 | ## 5.**TCP,现在有什么缺陷?** 72 | 73 | TCP是一种面向连接的协议。它会跟踪客户端和服务器之间传输的所有数据包,如果数据包在从服务器传输过程中丢失,它会将所有数据包保存在该数据包的序列号之后,直到丢失的数据包被重新发送,才将其传递给应用层。这里有一个图表来说明这一点。 74 | 75 | ![img](https://pic4.zhimg.com/80/v2-4fec9dd0ac0df8780db332c7539a31b7_720w.webp) 76 | 77 | 例如,在图中,如果数据包2来自视图。css丢失时,它会导致所有从3到20的数据包存储在接收缓冲区中,直到数据包2被重新发送后才传递到应用程序堆栈。尽管所有的包裹都来自猫。png将被接收,但底层TCP协议无法知道这一点,因此它强制重传。 78 | 79 | (注意:这个图不是一个非常准确的表示,因为我已经从HTTP响应切换到了TCP响应来说明这个问题)。 80 | 81 | 如果你仔细观察它,潜在的问题是因为TCP的面向连接的本质。例如,如果TCP知道数据包1和数据包2是view.css的一部分,则只会导致数据包2重新传输,而不会阻止数据包3–20。标识HTTP/2层中底层多路复用流的流ID与底层TCP数据包ID断开连接。 82 | 83 | ## 6.QUIC与TCP 84 | 85 | 与TCP不同,QUIC协议只允许以加密形式进行通信。由于QUIC中未加密的通信形式被设计为禁止,因此隐私和安全是QUIC数据传输的固有部分。在网络安全方面,这无疑是一个优势,但在不严格要求加密的情况下,这也可能是一个无用的开销。 86 | 87 | 但与TCP+TLS相比,QUIC建立安全连接所需的时间代表了真正的突破。换句话说,QUIC的主要目标是大大减少连接设置期间的开销。 88 | 89 | 这得益于QUIC的设计。事实上,QUIC使交换配置密钥和支持的协议成为初始握手过程的一部分更快。具体而言,当发送方打开连接时,响应包还包括使用加密所需的未来数据包所需的数据。这一步骤不需要建立TCP连接,然后通过其他数据包协商安全协议。这会导致更高的连接速度和显著的响应降低,甚至在主机间重新连接期间降低到0ms,这被称为“零RTT连接建立”。 90 | 91 | ![img](https://pic1.zhimg.com/80/v2-03de8426d4a2736e8beb370d960e73f0_720w.webp) 92 | 93 | 正如您所看到的,典型的安全TCP连接需要两到三次往返,发送方才能真正开始接收数据。这可能需要300毫秒。而通过使用QUIC,发送者可以立即开始与之前已经交互过的接收者进行交互。 94 | 95 | 与UDP相比,QUIC是赢家,因为它具有UDP所不具备的拥塞控制和自动重传等TCP功能。这使得它本质上比纯UDP更可靠。详细地说,虽然QUIC使用UDP作为基础,但它涉及丢失恢复。这是因为QUIC的行为类似于TCP,它分别检查每个流,并在数据丢失时重新传输数据。 96 | 97 | 此外,如果一个流中发生错误,QUIC可以继续独立地为其他流提供服务。这一特性对于提高易出错链路的性能非常有用,因为在TCP注意到丢失或丢失的数据包之前,可能会收到大量额外的数据。在QUIC中,在修复流时,可以自由处理这些数据。 98 | 99 | QUIC还提高了网络切换事件期间的性能,例如当移动设备用户从Wi-Fi网络移动到移动网络时。当在TCP上发生同样的事情时,将执行一个长过程,其中每个现有连接一次断开一个,然后按需重新建立。为了解决这个问题及其在性能方面的后果,QUIC包括到接收器的连接ID,而不考虑源。这允许简单地通过重新发送单个数据包来重新建立连接,该数据包始终包含该ID,即使发送方的IP地址发生了更改,接收方也会认为该ID有效。 100 | 101 | ## **7.那么,这是否足以让QUIC取代TCP?** 102 | 103 | 我知道一种叫做UDP的替代方案,它是无连接的。 104 | 105 | 但我们仍然需要联系,不是吗?我们如何在没有连接的情况下以可靠的方式请求资源(重新传输、确认、排序和整个沙邦)。我们需要一种方法来识别跨多个TCP数据包的资源。 106 | 107 | 如果我们删除TCP并使用IP上的流协议并将其与HTTP分层,该怎么办?该协议将像以前一样保持单个连接,但流将有自己的重传。实际上,每个流在一个更大的连接上都具有tcp特性。 108 | 109 | 这正是HTTP/3 到 QUIC所做的。QUIC作为TCP的替代品,在底层UDP数据包本身和HTTP/3之上维护流,而HTTP/2对流一无所知。将流视为小型TCP连接,但处于资源级别。换句话说,与HTTP相比,QUIC保留了单个资源流中的排序,但不再跨越单个流。这也意味着QUIC不再按照请求的顺序向应用程序层传递资源。 110 | 111 | QUIC系统的另一个目标是提高网络切换事件期间的性能,例如当移动设备的用户从本地wifi热点移动到移动网络时发生的情况。当这种情况发生在TCP上时,会开始一个漫长的过程,每个现有连接都会逐一超时,然后根据需要重新建立。为了解决这个问题,QUIC包括一个连接标识符,该标识符唯一地标识到服务器的连接,而不考虑源。这允许通过发送始终包含此ID的数据包来重新建立连接,因为即使用户的IP地址发生更改,原始连接ID仍然有效。 112 | 113 | 指示客户端和服务器的速度的流控制保持在流级别。这是通过发布/订阅一个窗口来实现的,该窗口指示每一方可以发送多少数据,并在每次成功传输后更新该窗口。 114 | 115 | 为了在不等待重传的情况下从丢失的数据包中恢复,QUIC可以用FEC数据包补充一组数据包。与RAID-4非常类似,FEC数据包包含FEC组中数据包的奇偶校验。如果组中的一个分组丢失,则可以从FEC分组和组中的剩余分组中恢复该分组的内容。发送者可以决定是否发送FEC分组以优化特定场景(例如,请求的开始和结束)。 116 | 117 | 嗯。为什么我们需要UDP?为什么不通过IP实现QUIC? 118 | 119 | 这是因为企业和互联网上的大多数防火墙仍然支持UDP协议。如果我们要在IP上分层QUIC,我们必须重新配置所有这些。UDP无论如何都是一个小开销(8字节报头)。 120 | 121 | QUIC的安全性问题呢?下次我们再来讨论 122 | 123 | 原文地址:https://zhuanlan.zhihu.com/p/592578434 124 | 125 | 作者:linux -------------------------------------------------------------------------------- /C++博文/【NO.145】分布式事务解决方案.md: -------------------------------------------------------------------------------- 1 | # 【NO.145】分布式事务解决方案 2 | 3 | ## **1.简述** 4 | 5 | 分布式事务是指事务的操作位于不同的节点上,需要保证事务的ACID特性。在分布式架构下,每个节点只知晓自身操作的成功与失败,无法知悉其他节点的操作状态。当一个事务跨多个节点时,为了保持事务的原子性与一致性,从而引入一个协调者来统一管控所有参与者的操作结果,并指引它们最终是否把操作结果进行诊治的提交(commit)和回滚(rollback)。例如在购物下单场景中,库存和订单如果不在同一个节点上,就涉及分布式事务。 6 | 7 | ## **2.解决方案** 8 | 9 | 在分布式系统中要实现分布式事务,常见的解决方案有两段提交(2PC)、三段提交(3PC)、事务补偿(TCC)、本地消息表(异步确保)、MQ事务方案(可靠消息事务)、最大努力通知和Saga事务。 10 | 11 | ### **2.1 两阶段提交(2PC)** 12 | 13 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213214017264-1669488684.png) 14 | 15 | 二阶段提交协议(Two-phase Commit,即 2PC)是常用的分布式事务解决方案,即将事务的提交过程分为准备阶段和提交阶段两个阶段来进行处理,通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。 16 | 17 | - 事务协调者(事务管理器):事务的发起者 18 | - 事务参与者(资源管理器):事务的执行者 19 | 20 | **准备阶段(投票阶段)** 21 | 22 | 协调者询问参与者事务是否执行成功,参与者发回事务执行结果,但该阶段并未提交事务。 23 | 24 | 1. 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复; 25 | 2. 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务); 26 | 3. 如参与者执行成功,给协调者反馈同意,否则反馈终止。 27 | 28 | **提交阶段(执行阶段)** 29 | 30 | 如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 31 | 32 | 在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。 33 | 34 | 1. 事务协调者节点向所有参与者节点发出正式提交(`commit`)的请求; 35 | 2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源; 36 | 3. 参与者节点向协调者节点发送ACK完成消息; 37 | 4. 事务协调者节点收到所有参与者节点反馈的ACK完成消息后,完成事务。 38 | 39 | **2PC优缺** 40 | 41 | **优点** 42 | 43 | - 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致,如宕机) 44 | 45 | **缺点** 46 | 47 | - **性能问题**:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。 48 | - **可靠性问题**:参与者发生故障。协调者需要给每个参与者额外指定超时机制,超时后整个事务失败。协调者发生故障。参与者会一直阻塞下去。需要额外的备机进行容错。 49 | - **数据一致性问题**:二阶段无法解决的问题如协调者在发出`commit`消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。 50 | - **实现复杂**:牺牲了可用性,对性能影响较大,不适合高并发高性能场景。 51 | 52 | ### **2.2 三阶段提交(3PC)** 53 | 54 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213214104494-1390181182.png) 55 | 56 | 三阶段提交协议是二阶段提交协议的改进版本,其有两个改动点。 57 | 58 | 1. 在协调者和参与者中都引入超时机制; 59 | 2. 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。 60 | 61 | 即除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有`CanCommit`、`PreCommit和``DoCommit`三个阶段。 62 | 63 | **3PC优缺点** 64 | 65 | **优点** 66 | 67 | 相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段 3 中协调者出现问题时,参与者会继续提交事务。 68 | 69 | **缺点** 70 | 71 | 数据不一致问题依然存在,当在参与者收到 `preCommit` 请求后等待 `doCommit` 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。 72 | 73 | ### **2.3 事务补偿(TCC)** 74 | 75 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213193657939-2019193487.png) 76 | 77 | TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交,是目前最火的一种柔性事务方案。TCC采用了补偿机制,其核心思想就是针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为Try、Confirm和Cancel三个阶段。 78 | 79 | 1. Try 阶段主要是对业务系统做检测及资源预留; 80 | 2. Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的,即:只要Try成功,Confirm一定成功; 81 | 3. Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。 82 | 83 | 转账例子:假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用。 84 | 85 | 1. 首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来; 86 | 2. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 87 | 3. 如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。 88 | 89 | **TCC优缺点** 90 | 91 | **优点** 92 | 93 | 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC差。 94 | 95 | **缺点** 96 | 97 | 在2和3步中都有可能会失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。 98 | 99 | ### **2.4 本地消息表(异步确保**) 100 | 101 | 本地消息表方案的核心思路是将分布式事务拆分成本地事务进行处理。通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。 102 | 103 | *![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213194544060-1699106774.png)* 104 | 105 | 上图中整体的处理步骤如下: 106 | 107 | 1. 事务主动方在同一个本地事务中处理业务和写消息表操作; 108 | 2. 事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息。消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息; 109 | 3. 事务被动方通过消息中间件,通知事务主动方事务已处理的消息; 110 | 4. 事务主动方接收中间件的消息,更新消息表的状态为已处理。 111 | 112 | 本地消息表优缺点 113 | 114 | **优点** 115 | 116 | 避免了分布式事务,实现了最终一致性。 117 | 118 | **缺点** 119 | 120 | 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。 121 | 122 | ### **2.5 MQ事务方案(可靠消息事务)** 123 | 124 | MQ事务方案是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。 125 | 126 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213195114938-2056723236.png) 127 | 128 | ### **2.6 最大努力通知** 129 | 130 | 最大努力通知方案是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。 131 | 132 | 其适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。 133 | 134 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213201201220-874864581.png) 135 | 136 | ### **2.7 Saga事务** 137 | 138 | Saga 事务核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。 139 | 140 | Saga 事务基本协议如下: 141 | 142 | - 每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) **Ti** 组成。 143 | - 每个 **Ti** 都有对应的幂等补偿动作 **Ci**,补偿动作用于撤销 **Ti** 造成的结果。 144 | 145 | Saga 的执行顺序有两种: 146 | 147 | - T1, T2, T3, ..., Tn 148 | - T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n 149 | 150 | TCC事务补偿机制有一个预留(Try)动作,相当于先报存一个草稿,然后才提交;而Saga事务没有预留动作,直接提交。对于事务异常,Saga提供了向后恢复和向前恢复两种恢复策略。 151 | 152 | **向后恢复(backward recovery)** 153 | 154 | backward recovery,即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。 155 | 156 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213202619446-202874736.png) 157 | 158 | **向前恢复(forward recovery)** 159 | 160 | forward recovery,即适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。 161 | ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213202633035-1675437339.png) 162 | 163 | ## **3.总结** 164 | 165 | 各分布式事务方案的常见使用场景: 166 | 167 | - **2PC/3PC**:依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。 168 | - **TCC**:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。 169 | - **本地消息表/MQ 事务**:都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 170 | - **Saga 事务**:由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 171 | 172 | ## **4.Seta** 173 | 174 | 上述几种方案都是分布式事务的理论知识,Seta是分布式解放方案的一个落地实现。 175 | 176 | Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 **AT**、**TCC**、**SAGA** 和 **XA** 事务模式,为用户打造一站式的分布式解决方案。 177 | 178 | - 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入; 179 | - 高性能:减少分布式事务解决方案所带来的性能消耗。 180 | - Seta官方文档:https://seata.io/zh-cn/index.html 181 | 182 | 原文作者:[涛姐涛哥](https://home.cnblogs.com/u/taojietaoge/) 183 | 184 | 原文链接:https://www.cnblogs.com/taojietaoge/p/15890289.html -------------------------------------------------------------------------------- /C++博文/【NO.14】TCP 的封包格式:TCP 为什么要粘包和拆包?.md: -------------------------------------------------------------------------------- 1 | # 【NO.14】TCP 的封包格式:TCP 为什么要粘包和拆包? 2 | 3 | 今天我们将**从稳定性角度深挖 TCP 协议的运作机制**。 4 | 5 | 如今,大半个互联网都建立在 TCP 协议之上,我们使用的 HTTP 协议、[消息队列](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/product/cmq%3Ffrom%3D10680)、存储、缓存,都需要用到 TCP 协议——**这是因为 TCP 协议提供了可靠性**。 6 | 7 | 简单来说,可靠性就是让数据无损送达。但若是考虑到成本,就会变得非常复杂——因为还需要尽可能地提升吞吐量、降低延迟、减少丢包率。 8 | 9 | **TCP 协议具有很强的实用性,而可靠性又是 TCP 最核心的能力** 。具体来说,从一个终端有序地发出多个数据包,经过一个复杂的网络环境,到达目的地的时候会变得无序,而可靠性要求数据恢复到原始的顺序。这里先提出两个问题: 10 | 11 | - TCP 协议是如何恢复数据的顺序的? 12 | - 拆包和粘包的作用是什么? 13 | 14 | 那么带着这两个问题开始今天的学习。 15 | 16 | ## **1.TCP 的拆包和粘包** 17 | 18 | ### **1.1TCP数据发送** 19 | 20 | **TCP 是一个传输层协议** 21 | 22 | TCP 发送数据的时候,往往不会将数据一次性发送 23 | 24 | ![img](https://pic2.zhimg.com/80/v2-74671914df274e2f6b82f3f0648d8745_720w.webp) 25 | 26 | 而是将数据拆分成很多个部分,然后再逐个发送。像下图这样: 27 | 28 | ![img](https://pic4.zhimg.com/80/v2-332d128b061e2f30eced1b7da19d8be3_720w.webp) 29 | 30 | **同样的,在目的地,TCP 协议又需要逐个接收数据。** 31 | 32 | 请 思考,TCP 为什么不一次发送完所有的数据?比如我们要传一个大小为 10M 的文件,对于应用层而言,就是一次传送完成的。而**传输层的协议为什么不选择将这个文件一次发送完呢?** 33 | 34 | 这里有很多原因, 35 | 36 | - 比如为了稳定性,一次发送的数据越多,出错的概率越大。 37 | - 再比如说为了效率,网络中有时候存在着并行的路径,拆分数据包就能更好地利用这些并行的路径。 38 | - 再有,比如发送和接收数据的时候,都存在着缓冲区。如下图所示: 39 | 40 | ![img](https://pic4.zhimg.com/80/v2-c50f2772885ad7262d78a61e2f34651b_720w.webp) 41 | 42 | **缓冲区是在内存中开辟的一块区域,目的是缓冲。因为大量的应用频繁地通过网卡收发数据,这个时候,网卡只能一个一个处理应用的请求。当网卡忙不过来的时候,数据就需要排队,也就是将数据放入缓冲区**。如果每个应用都随意发送很大的数据,可能导致其他应用实时性遭到破坏。 43 | 44 | 还有一些原因 比如内存的最小分配单位是页表,如果数据的大小超过一个页表,可能会存在页面置换问题,造成性能的损失。 45 | 46 | 总之,方方面面的原因:**在传输层封包不能太大**。 47 | 48 | 这种限制,往往是以缓冲区大小为单位的。也就是 **TCP 协议,会将数据拆分成不超过缓冲区大小的一个个部分**。每个部分有一个独特的名词,叫作 **TCP 段(TCP Segment)**。 49 | 50 | 在接收数据的时候,一个个 TCP 段又被重组成原来的数据。 51 | 52 | 像这样,**数据经过拆分,然后传输,然后在目的地重组,俗称拆包**。所以拆包是将数据拆分成多个 TCP 段传输。 53 | 54 | 那么粘包是什么呢?有时候,**如果发往一个目的地的多个数据太小了,为了防止多次发送占用资源,TCP 协议有可能将它们合并成一个 TCP 段发送,在目的地再还原成多个数据,这个过程俗称粘包。所以粘包是将多个数据合并成一个 TCP 段发送**。 55 | 56 | ### **1.2TCP Segment** 57 | 58 | 那么一个 TCP 段长什么样子呢?下图是一个 TCP 段的格式: 59 | 60 | ![img](https://pic1.zhimg.com/80/v2-5df0a9cbe0a5a0546c41ba83934a7dd0_720w.webp) 61 | 62 | 我们可以看到,TCP 的很多配置选项和数据粘在了一起,作为一个 TCP 段。 63 | 64 | 显然, 把每一部分都记住似乎不太现实,先把其中最主要的部分理解。 65 | 66 | 67 | 68 | **TCP 协议就是依靠每一个 TCP 段工作的,所以你每认识一个 TCP 的能力,几乎都会找到在 TCP Segment 中与之对应的字段**。 接下来 认识它们。 69 | 70 | - Source Port/Destination Port 描述的是发送端口号和目标端口号,代表发送数据的应用程序和接收数据的应用程序。比如 80 往往代表 HTTP 服务,22 往往是 SSH 服务…… 71 | - Sequence Number 和 Achnowledgment Number 是保证可靠性的两个关键 72 | - Data Offset 是一个偏移量。这个量存在的原因是 TCP Header 部分的长度是可变的,因此需要一个数值来描述数据从哪个字节开始。 73 | - Reserved 是很多协议设计会保留的一个区域,用于日后扩展能力。 74 | - URG/ACK/PSH/RST/SYN/FIN 是几个标志位,用于描述 TCP 段的行为。也就是一个 TCP 封包到底是做什么用的 75 | 76 | > 1)URG 代表这是一个紧急数据,比如远程操作的时候,用户按下了 Ctrl+C,要求终止程序,这种请求需要紧急处理。 2)ACK 代表响应, 所有的消息都必须有 ACK,这是 TCP 协议确保稳定性的一环。 3)PSH 代表数据推送,也就是在传输数据的意思。 4)SYN 同步请求,也就是申请握手。 5)FIN 终止请求,也就是挥手。 77 | 78 | **特别说明一下:以上这 5 个标志位,每个占了一个比特,可以混合使用。比如 ACK 和 SYN 同时为 1,代表同步请求和响应被合并了。这也是 TCP 协议,为什么是三次握手的原因之一**。 79 | 80 | - Window 也是 TCP 保证稳定性并进行流量控制的工具,后续会 TCP 的稳定性:滑动窗口和流速控制是中详细介绍。 81 | - Checksum 是校验和,用于校验 TCP 段有没有损坏。 82 | - Urgent Pointer 指向最后一个紧急数据的序号(Sequence Number)。它存在的原因是:有时候紧急数据是连续的很多个段,所以需要提前告诉接收方进行准备。 83 | - Options 中存储了一些可选字段,比如接下来我们要讨论的 MSS(Maximun Segment Size)。 84 | - Padding 存在的意义是因为 Options 的长度不固定,需要 Pading 进行对齐。 85 | 86 | ### **1.3Sequence Number 和 Acknowledgement Number** 87 | 88 | 在 TCP 协议的设计当中,数据被拆分成很多个部分,部分增加了协议头。合并成为一个 TCP 段,进行传输。这个过程,我们俗称拆包。这些 TCP 段经过复杂的网络结构,由底层的 IP 协议,负责传输到目的地,然后再进行重组。 89 | 90 | 这里请你思考一个问题:**稳定性要求数据无损地传输,也就是说拆包获得数据,又需要恢复到原来的样子。而在复杂的网络环境当中,即便所有的段是顺序发出的,也不能保证它们顺序到达,因此,发出的每一个 TCP 段都需要有序号。这个序号,就是 Sequence Number(Seq)**。 91 | 92 | ![img](https://pic1.zhimg.com/80/v2-facf4f565297f34847e4887beac638a0_720w.webp) 93 | 94 | 如上图所示。**发送数据的时候,为每一个 TCP 段分配一个自增的 Sequence Number。接收数据的时候,虽然得到的是乱序的 TCP 段,但是可以通过 Seq 进行排序。** 95 | 96 | 但是这样又会产生一个新的问题——**接收方如果要回复发送方,也需要这个 Seq。而网络的两个终端,去同步一个自增的序号是非常困难的**。因为任何两个网络主体间,时间都不能做到完全同步,又没有公共的存储空间,无法共享数据,更别说实现一个分布式的自增序号了。 97 | 98 | 其实这个问题的本质就好像两个人在说话一样,我们要确保他们说出去的话,和回答之间的顺序。因为 TCP 是一个双工的协议,两边可能会同时说话。所以聪明的**科学家想到了确定一句话的顺序,需要两个值去描述——也就是发送的字节数和接收的字节数**。 99 | 100 | ![img](https://pic2.zhimg.com/80/v2-e5d62634460b3dfbd17dc7a91604f955_720w.webp) 101 | 102 | 我们重新定义一下 Seq(如上图所示),对于任何一个接收方,如果知道了发送者发送某个 TCP 段时,已经发送了多少字节的数据,那么就可以确定发送者发送数据的顺序。 103 | 104 | 但是这里有一个问题。如果接收方也向发送者发送了数据请求(或者说双方在对话),接收方就不知道发送者发送的数据到底对应哪一条自己发送的数据了。 105 | 106 | 举个例子:下面 A 和 B 的对话中,我们可以确定他们彼此之间接收数据的顺序。但是无法确定数据之间的关联关系,所以只有 Sequence Number 是不够的。 107 | 108 | A:今天天气好吗? A:今天你开心吗? B:开心 B:天气不好 复制 109 | 110 | 人类很容易理解这几句话的顺序,但是对于机器来说就需要特别的标注。因此我们还需要另一个数据,就是每个 TCP 段发送时,发送方已经接收了多少数据。用 Acknowledgement Number 表示,下面简写为 ACK。 111 | 112 | 下图中,终端发送了三条数据,并且接收到四条数据,通过观察,根据接收到的数据中的 Seq 和 ACK,将发送和接收的数据进行排序。 113 | 114 | ![img](https://pic4.zhimg.com/80/v2-1dc149546ddb7f0d25f6934f0ed974f7_720w.webp) 115 | 116 | 例如上图中,**发送方发送了 100 字节的数据,而接收到的(Seq = 0 和 Seq =100)的两个封包,都是针对发送方(Seq = 0)这个封包的。发送 100 个字节,所以接收到的 ACK 刚好是 100。说明(Seq= 0 和 Seq= 100)这两个封包是针对接收到第 100 个字节数据后,发送回来的。这样就确定了整体的顺序**。 117 | 118 | **注意,无论 Seq 还是 ACK,都是针对“对方”而言的。是对方发送的数据和对方接收到的数据** 。 我们在实际的工作当中,可以通过 Whireshark 调试工具观察两个 TCP 连接的 Seq和 ACK。 119 | 120 | ![img](https://pic4.zhimg.com/80/v2-6137f6d93f3b6644cd4665431f805f0b_720w.webp) 121 | 122 | ### **1.4MSS(Maximun Segment Size)** 123 | 124 | 接下来,我们讨论下 MSS,它也是面试经常会问到的一个 TCP Header 中的可选项(Options),**这个可选项控制了 TCP 段的大小,它是一个协商字段(Negotiate)**。协议是双方都要遵循的标准,因此配置往往不能由单方决定,需要双方协商。 125 | 126 | **TCP 段的大小(MSS)涉及发送、接收缓冲区的大小设置,双方实际发送接收封包的大小,对拆包和粘包的过程有指导作用,因此需要双方去协商**。 127 | 128 | 如果这个字段设置得非常大,就会带来一些影响。 129 | 130 | - **首先对方可能会拒绝,作为服务的提供方,你可能不会愿意接收太大的 TCP 段。因为大的 TCP 段,会降低性能,比如内存使用的性能**。 还有就是资源的占用。一个用户占用[服务器](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/product/cvm%3Ffrom%3D10680)太多的资源,意味着其他的用户就需要等待或者降低他们的服务质量 131 | - **其次,支持 TCP 协议工作的 IP 协议,工作效率会下降** 132 | 133 | > TCP 协议不肯拆包,IP 协议就需要拆出大量的包。那么 IP 协议为什么需要拆包呢?这是因为在网络中,每次能够传输的数据不可能太大,这受限于具体的网络传输设备,也就是物理特性。但是 IP 协议,拆分太多的封包并没有意义。因为可能会导致属于同个 TCP 段的封包被不同的网络路线传输,这会加大延迟。同时,拆包,还需要消耗硬件和计算资源。 那是不是 MSS 越小越好呢?**MSS 太小的情况下,会浪费传输资源(降低吞吐量)。因为数据被拆分之后,每一份数据都要增加一个头部。如果 MSS 太小,那头部的数据占比会上升,这让吞吐量成为一个灾难。所以在使用的过程当中,MSS 的配置,往往都是一个折中的方案**。 134 | 135 | 不要去猜想什么样的方案是最合理的,而是要尝试去用实验证明它,一切都要用实验依据说话。 136 | 137 | ![img](https://pic2.zhimg.com/80/v2-80ad0f771f547c176bc32911f207cd71_720w.webp) 138 | 139 | ## **2.Question : TCP 协议是如何恢复数据的顺序的,TCP 拆包和粘包的作用是什么?** 140 | 141 | Answer: 142 | 143 | TCP 拆包的作用是将任务拆分处理,降低整体任务出错的概率,以及减小底层网络处理的压力。拆包过程需要保证数据经过网络的传输,又能恢复到原始的顺序。这中间,需要数学提供保证顺序的理论依据。 **TCP 利用(发送字节数、接收字节数)的唯一性来确定封包之间的顺序关系**。 144 | 145 | 粘包是为了防止数据量过小,导致大量的传输,而将多个 TCP 段合并成一个发送。 146 | 147 | 原文链接:https://zhuanlan.zhihu.com/p/583732173 -------------------------------------------------------------------------------- /C++博文/【NO.151】p2p之网络穿透NAT,NAT、穿透的原理.md: -------------------------------------------------------------------------------- 1 | # 【NO.151】p2p之网络穿透NAT,NAT、穿透的原理 2 | 3 | ## 1.p2p是什么? 4 | 5 | p2p是对等网络(peer-to-peer networking)其可以定义为:端对端的资源共享,每一端即可是服务端,也可以是客户端。既可以是资源的提供者,也可以是资源的共享者。 6 | 7 | 传统C/S模型需要实现端和端的资源共享, 需要将资源上传到中转服务器。另外一端再去中转服务器下载,如下图: 8 | 9 | ![img](https://pic3.zhimg.com/80/v2-b02447c4cd4ead9cb6169e86dfff507e_720w.webp) 10 | 11 | 传统CS架构,客户端1和客户端2之间是无直接交互.png 12 | 13 | 而P2P则不需要将资源上传到服务器,它是端对端传输,每一个端既可以是服务器,也可以是客户端 14 | 15 | ![img](https://pic4.zhimg.com/80/v2-8b4235fc0df3291ebf4bc91396a594fb_720w.webp) 16 | 17 | p2p架构,无需中转服务器.png 18 | 19 | 优势:实时性最高,流量少,更加安全。在视频直播,在线教育,视频安防行业用的比较多 20 | 劣势:一旦进行p2p传输之后,用户之间的内容将无法监管,浪费用户带宽,频繁进行读写磁盘 21 | 22 | 客户端1和客户端2这样交互是p2p最理想的情况 23 | 图中客户端1和客户端2直接连接, 假如他们处于两个不同的内网呢? 24 | 25 | ## 2.NAT是什么? 26 | 27 | NAT俗称网络地址转换,它是一种把内部私有网络地址(IP地址)转换成公网网络IP地址的技术。比如我们电脑里面网卡地址是192.168.1.100,但是我们再百度搜索“IP”却显示220.112.224.53,这就是NAT的功能。 28 | **NAT主要是部署在路由器或者交换机上。** 29 | 30 | ## 3.为什么需要NAT? 31 | 32 | 主要还是IP地址的不足,使用少量的公有IP 地址代表较多的私有IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。用大白话:比如你有一个路由器(家用的那种就可以)这个路由器本身连接了公网(被分配到了一个公网的IP地址)。路由器后面有接了N多个设备,每个设备都分配到了一个私有的地址(内网地址),这些地址可以通过这个路由器和外网交互。 33 | 34 | 其次能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。 35 | RFC3489 中将 NAT 的实现分为四大类: 36 | 37 | - Full Cone NAT(完全圆锥型) 38 | - Address Restricted Cone NAT(地址限制圆锥型 ) 39 | - Port Restricted Cone NAT(端口限制圆锥型) 40 | - Symmetric NAT(对称型) 41 | 42 | ## 4.完全圆锥型NAT 43 | 44 | 在完全圆锥型NAT(Full Cone NAT)中,NAT会将客户机地址{X:y}转换成公网地址{A:b}并绑定。任何包都可以通过地址{A:b}送到客户主机的{X:y}地址上。如图所示: 45 | 46 | ![img](https://pic3.zhimg.com/80/v2-b84f4a39e311e786608cf01f7d62cf62_720w.webp) 47 | 48 | RFC3581——完全锥型NAT 49 | 50 | ## 5. 地址限制圆锥型NAT 51 | 52 | 地址限制圆锥型NAT(Address Restricted Cone NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定,只有来自主机{P}的包才能和主机{X:y}通信。如下图所示: 53 | 54 | ![img](https://pic4.zhimg.com/80/v2-a329e90e007c0a1ce406349b47819c93_720w.webp) 55 | 56 | RFC3581——地址限制型NAT 57 | 58 | ## 6.端口限制圆锥型NAT 59 | 60 | 端口限制圆锥型NAT(Port Restricted Cone NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定,只有来自主机{P,q}的包才能和主机{X:y}通信。如下图所示: 61 | 62 | ![img](https://pic1.zhimg.com/80/v2-baaea3df8eccf4422cd8c688889e4d04_720w.webp) 63 | 64 | RFC3581——端口限制型NAT 65 | 66 | ## 7.对称型NAT 67 | 68 | 对称型NAT(Symmetric NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定为{X:y}|{A:b}<->{P:q}。对称型NAT只接受来自{P:q}的连接,将它转给{X:y} ,每次客户机请求一个不同的公网地址和端口,NAT会新分配一个端口号{C,d} 。如下图所示: 69 | 70 | ![img](https://pic4.zhimg.com/80/v2-beb4537f62e9b1897b1ccee19e945747_720w.webp) 71 | 72 | RFC3581——对称型NAT 73 | 74 | **其中完全最上层的完全圆锥形NAT的穿透性最好,而最下层的对称形NAT的安全性最高。** 75 | 76 | ## 8.如何穿透NAT? 77 | 78 | 事实上两个客户端相互通信还需要一个辅助服务器(p2pserver) 来保存两个用户的外网地址端口。 79 | 当用户A连接B时、或者B连接A时, 会向辅助服务器询问对方的外网地址和端口 80 | 81 | ![img](https://pic3.zhimg.com/80/v2-00c8c5a560148ae21565b5f8ee1dc5aa_720w.webp) 82 | 83 | NAT穿透组合情况.png 84 | 85 | 从上面的NAT类型中可以看出,有4种NAT,一共10种组合 86 | 87 | **1. 完全圆锥型NAT和完全圆锥型NAT** 88 | 这种最简单, 只需要B从辅助服务器拿到A的内外网信息, 就可以和A进行连接 89 | **2. 完全圆锥型NAT和地址限制型NAT** 90 | 同上 91 | **3. 完全圆锥型NAT和端口限制性NAT** 92 | 同上 93 | **4. 完全圆锥型NAT和对称型NAT** 94 | 同上 95 | **5.地址限制型NAT和地址限制型NAT** 96 | 97 | - 当B从辅助服务器拿到A的内外网信息, B向A发送连接, 这个时候NAT A设备会丢弃掉B发送过来的连接。 98 | - 这个时候B就向辅助服务器发送请求,让A连接B一次, 连完后B就可以连接到A了,NAT A不再拦截B过来的连接。 99 | 100 | **6.地址限制型NAT和端口限制型NAT** 101 | 同上 102 | **7.地址限制性NAT和对称型NAT** 103 | 同上 104 | **8.端口限制型NAT和端口限制型NAT** 105 | 同上 106 | **9.端口限制型NAT和对称型NAT** 107 | 这种无法穿透, 因为A需要连过B,B才能连到A,但是A无法连接到B,因为B的是对称型NAT,端口一直在变 108 | **10.对称型NAT和对称型NAT** 109 | 这种也无法穿透,因为客户机每次请求一个不同的公网地址和端口, NAT会新分配一个端口号,所以从辅助服务器拿到的端口号是无效的(只是针对和服务器相连的端口号)。 110 | eg:A和辅助服务器相连,NAT A会分配一个端口 8081。 111 | A和B相连, NAT A会分配一个端口号10020,所以B连A并不知道A需要从10020进,所以无法穿透过NAT A。不过也有人通过端口预测算法成功连接, 但是这种并不可靠。 112 | 113 | ![img](https://pic1.zhimg.com/80/v2-80b87d01775cd205828d9a651ab6b48c_720w.webp) 114 | 115 | ## 9.为什么需要保活链路? 116 | 117 | 因为一个连接经过NAT设备之后,在NAT设备上面绑定的端口是有时效性的,一般是30分钟,但是最少的三五分钟就失效了,所以要不停的发送心跳包来保活NAT上的这个“洞”。 118 | 119 | ## 10.移动、联通网络为什么没有电信快? 120 | 121 | 原因是电信拨号之后分配的是公网IP。而联通、移动拨号之后还是内网IP,也就是NAT设备上面还有多层NAT, 多次转发并且最终的出口只有一个,所以总体来说比较慢 122 | 123 | 原文链接:https://zhuanlan.zhihu.com/p/299524798 124 | 125 | 作者:[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10) -------------------------------------------------------------------------------- /C++博文/【NO.156】 设计模式之工厂设计模式.md: -------------------------------------------------------------------------------- 1 | # 【NO.156】 设计模式之工厂设计模式 2 | 3 | # 1.开发环境 4 | 5 | - IDEA版本: 2022.1.4 6 | - JDK版本:17.0.3 7 | 8 | 9 | 10 | # 2.模式由来 11 | 12 | ## 2.1 自定义MailSender类 13 | 14 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102526533-1287688327.png) 15 | 16 | ## 2.2 自定义Computer类 17 | 18 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102544456-933689781.png) 19 | 20 | ## 2.3 分析图 21 | 22 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103216889-954434698.png) 23 | 24 | ## 2.4 案例分析 25 | 26 | - 由于Computer类和MailSender类之间的耦合度比较高,而且创建对象的代码太固定了,若希望使用短信发送的功能需要修改Computer类,这种修改违背了开闭原则,为了解决该问题,就可以将创建对象的工作移交出去,而工厂设计模式就是一种创建对象的设计模式,而且可以在创建对象时不对外暴露具体的创建逻辑。 27 | 28 | 29 | 30 | # 3 普通工厂方法模式 31 | 32 | ## 3.1 基本概念 33 | 34 | - 普通工厂方法模式就是建立一个工厂类,通过生产方法的参数来进行具体实例的创建。 35 | 36 | ## 3.2 自定义Sender接口 37 | 38 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102635389-1815819930.png) 39 | 40 | ## 3.3 修改MailSender类 41 | 42 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102649414-57587530.png) 43 | 44 | ## 3.4 自定义SmsSender类 45 | 46 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102719414-1997499446.png) 47 | 48 | ## 3.5 自定义SendFactory类 49 | 50 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102732353-849206262.png) 51 | 52 | ## 3.6 修改Computer类 53 | 54 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102746458-1732378018.png) 55 | 56 | ## 3.7 分析图 57 | 58 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103217100-2038713447.png) 59 | 60 | ## 3.8 案例分析 61 | 62 | - 在普通工厂方法模式中,若传递的字符串出错,则不能正确创建对象,而且可能出现空指针异常,所以容错率不高。 63 | 64 | 65 | 66 | # 4.多个工厂方法模式 67 | 68 | ## 4.1 基本概念 69 | 70 | - 多个工厂方法模式就是通过多个不同的生产方法对实现同一接口的不同实现类分别进行对象的创建。 71 | 72 | ## 4.2 修改SendFactory类 73 | 74 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102823586-931852715.png) 75 | 76 | ## 4.3 修改Computer类 77 | 78 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102836547-837935901.png) 79 | 80 | ## 4.4 分析图 81 | 82 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103217099-1967070422.png) 83 | 84 | ## 4.5 案例分析 85 | 86 | - 在多个工厂方法模式中,为了能够正确创建对象,需要先创建工厂类的对象才能调用工厂类中的生产方法。 87 | 88 | 89 | 90 | # 5.静态工厂方法模式 91 | 92 | ## 5.1 基本概念 93 | 94 | - 静态工厂方法模式就是将多个不同的生产方法加上static关键字提升为类层级,此时只需要通过类名.的方式就可以进行方法的调用,从而进行不同实例的创建。 95 | 96 | ## 5.2 修改SendFactory类 97 | 98 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102912371-2099806645.png) 99 | 100 | ## 5.3 修改Computer类 101 | 102 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102931067-656536113.png) 103 | 104 | ## 5.4 分析图 105 | 106 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103217104-1842082073.png) 107 | 108 | ## 5.5 案例分析 109 | 110 | - 静态工厂方法模式中类的创建依赖工厂类,如果想要扩展程序来生产新的产品,就必须对工厂类的代码进行修改,这就违背了开闭原则。 111 | 112 | 113 | 114 | # 6.抽象工厂模式 115 | 116 | ## 6.1 基本概念 117 | 118 | - 抽象工厂模式就是将多个不同的生产方法放在不同的工厂类中,让多个工厂类实现同一个接口,此时只需要通过不同的工厂类就可以进行不同实例的创建。 119 | 120 | ## 6.2 自定义SendFactory接口 121 | 122 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213102956517-1621069227.png) 123 | 124 | ## 6.3 自定义MailSendFactory类 125 | 126 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103008325-189179913.png) 127 | 128 | ## 6.4 自定义SmsSendFactory类 129 | 130 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103019013-41445781.png) 131 | 132 | ## 6.5 修改Computer类 133 | 134 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103030995-591961697.png) 135 | 136 | ## 6.6 分析图 137 | 138 | ![img](https://img2023.cnblogs.com/blog/3038036/202212/3038036-20221213103217101-1976135503.png) 139 | 140 | ## 6.7 案例分析 141 | 142 | - 现在想要拓展程序生产新的产品,就只需要增加新的工厂类即可,不用违背开闭原则,同时每个工厂类对应一个产品,符合单一职责的原则。 143 | 144 | 原文作者:[格子衫007](https://home.cnblogs.com/u/gezishan007/) 145 | 146 | 原文链接:https://www.cnblogs.com/gezishan007/p/16977899.html -------------------------------------------------------------------------------- /C++博文/【NO.198】互斥锁、自旋锁、原子操作的原理、区别及应用场景.md: -------------------------------------------------------------------------------- 1 | # 【NO.198】互斥锁、自旋锁、原子操作的原理、区别及应用场景 2 | 3 | ## 1.互斥锁 4 | 5 | **原理:** 6 | 7 | 互斥锁属于sleep-waiting类型的锁,例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,Core0会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其它的任务而不必进行忙等待。 8 | 9 | **适用场景:** 10 | 11 | 因互斥锁会引起线程的切换,效率较低。使用互斥锁会引起线程阻塞等待,不会一直占用这cpu,因此当锁的内容较多,切换不频繁时,建议使用互斥锁 12 | 13 | **使用方法:** 14 | 15 | ```text 16 | /*初始化一个互斥锁*/ 17 | int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 18 | 参数: 19 | mutex:互斥锁地址。类型是pthread_mutex_t. 20 | attr:设置互斥量的属性,通常采用默认属性,即将attr设为NULL。 21 | 可以使用宏PTHREAD_MUTEX_INITALIZER静态初始化互斥锁,如: 22 | pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 23 | 这种方法等价于NULL指定的attr参数调用pthread_mutex_init()来完成动态初始化,不同之处在于PTHREAD_MUTEX_INITIALIZER宏不进行错误检查。 24 | 返回值: 25 | 成功:0 26 | 失败:非0 27 | ``` 28 | 29 | 30 | 31 | ```text 32 | /*销毁指定的一个互斥锁,释放资源*/ 33 | int pthread_mutex_destroy(pthread_mutex_t *mutex); 34 | 参数: 35 | mutex:互斥锁地址 36 | 返回值: 37 | 成功:0 38 | 失败:非0 39 | ``` 40 | 41 | 42 | 43 | ```text 44 | /*对互斥锁上锁,若互斥锁已经上锁,则调用这阻塞,直到互斥锁解锁后再上锁*/ 45 | int pthread_mutex_lock(pthread_mutex_t *mutex) 46 | 参数: 47 | mutex:互斥锁地址 48 | 返回值: 49 | 成功:0 50 | 失败:非0 51 | ``` 52 | 53 | 54 | 55 | ```text 56 | /*对指定的互斥锁解锁*/ 57 | int pthread_mutex_unlock(pthread_mutex_t *mutex); 58 | 参数: 59 | mutex:互斥锁地址 60 | 返回值: 61 | 成功:0 62 | 失败:非0 63 | ``` 64 | 65 | ## 2.自旋锁 66 | 67 | **原理:** 68 | 69 | Spin lock(自旋锁)属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。 70 | 71 | **使用场景:** 72 | 73 | “自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。因此如果锁的内容较少,阻塞的时间较短,使用自旋锁比较好。 74 | 75 | 自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。 76 | 77 | **使用方法** 78 | 79 | ```text 80 | /*初始化一个自旋锁*/ 81 | int pthread_spin_init(pthread_spinlock_t *lock, int pshared); 82 | 参数: 83 | pthread_spinlock_t :初始化自旋锁 84 | pshared取值: 85 | PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。(可以被其他进程中的线程看到) 86 | PTHREAD_PROCESS_PRIVATE:仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。 87 | 返回值: 88 | 若成功,返回0;否则,返回错误编号 89 | ``` 90 | 91 | 92 | 93 | ```text 94 | /*销毁一个锁*/ 95 | int pthread_spin_destroy(pthread_spinlock_t *lock); 96 | 返回值: 97 | 若成功,返回0;否则,返回错误编号 98 | ``` 99 | 100 | 101 | 102 | ```text 103 | /*用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。*/ 104 | int pthread_spin_lock(pthread_spinlock_t *lock); 105 | 返回值: 106 | 若成功,返回0;否则,返回错误编号 107 | ``` 108 | 109 | 110 | 111 | ```text 112 | /*解锁*/ 113 | int pthread_spin_unlock(pthread_spinlock_t *lock); 114 | 返回值: 115 | 若成功,返回0;否则,返回错误编号 116 | ``` 117 | 118 | 119 | 120 | ## 3.原子操作 121 | 122 | 所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。因此这里的原子实际是使用了物理学里的物质微粒的概念。 123 | 124 | 原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。 125 | 126 | 原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。 127 | 128 | ## 4.总结分析 129 | 130 | Mutex(互斥锁): 131 | 132 | sleep-waiting类型的锁 133 | 134 | 与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。 135 | 136 | 互斥锁适用于那些可能会阻塞很长时间的场景。 137 | 138 | 1、 临界区有IO操作 139 | 140 | 2 、临界区代码复杂或者循环量大 141 | 142 | 3 、临界区竞争非常激烈 143 | 144 | 4、 单核处理器 145 | 146 | Spin lock(自旋锁): 147 | 148 | busy-waiting类型的锁 149 | 150 | 对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。 151 | 152 | 自旋锁适用于那些仅需要阻塞很短时间的场景 153 | 154 | ## 5. 代码实例 155 | 156 | ```text 157 | /*实现count从0自增到100万*/ 158 | #include 159 | #include 160 | #include 161 | #define THREAD_COUNT 10 162 | pthread_mutex_t mutex; 163 | pthread_spinlock_t spinlock; 164 | //原子操作 165 | int inc(int *value,int add) 166 | { 167 | int old; 168 | __asm__ volatile( 169 | "lock; xaddl %2, %1;" 170 | : "=a" (old) 171 | : "m" (*value), "a"(add) 172 | : "cc", "memory" 173 | ); 174 | return old; 175 | } 176 | void *thread_callback(void *arg) 177 | { 178 | int *pcount=(int *)arg; 179 | int i=0; 180 | while(i++<100000) 181 | { 182 | #if 0 183 | (*pcount)++; 184 | #elif 0 185 | pthread_mutex_lock(&mutex); 186 | (*pcount)++; 187 | pthread_mutex_unlock(&mutex); 188 | #elif 0 189 | pthread_spin_lock(&spinlock); 190 | (*pcount)++; 191 | pthread_spin_unlock(&spinlock); 192 | #else 193 | inc(pcount,1); 194 | #endif 195 | usleep(1);//休眠一微秒 196 | } 197 | } 198 | int main() 199 | { 200 | 201 | pthread_t threadid[THREAD_COUNT]={0}; 202 | pthread_mutex_init(&mutex,NULL); 203 | //自旋锁 204 | pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED); 205 | int i=0; 206 | int count=0; 207 | for(i=0;i 内存池是池化技术中的一种形式。通常我们在编写程序的时候回使用 new delete 这些关键字来向操作系统申请内存,而这样造成的后果就是每次申请内存和释放内存的时候,都需要和操作系统的系统调用打交道,从堆中分配所需的内存。如果这样的操作太过频繁,就会找成大量的内存碎片进而降低内存的分配性能,甚至出现内存分配失败的情况。 19 | > 而内存池就是为了解决这个问题而产生的一种技术。从内存分配的概念上看,内存申请无非就是向内存分配方索要一个指针,当向操作系统申请内存时,操作系统需要进行复杂的内存管理调度之后,才能正确的分配出一个相应的指针。而这个分配的过程中,我们还面临着分配失败的风险。 20 | > 所以,每一次进行内存分配,就会消耗一次分配内存的时间,设这个时间为 T,那么进行 n 次分配总共消耗的时间就是 nT;如果我们一开始就确定好我们可能需要多少内存,那么在最初的时候就分配好这样的一块内存区域,当我们需要内存的时候,直接从这块已经分配好的内存中使用即可,那么总共需要的分配时间仅仅只有 T。当 n 越大时,节约的时间就越多。 21 | 22 | ## 3.内存池设计 23 | 24 | ![img](https://pic3.zhimg.com/80/v2-589698ace8dbf7801cb79a2c21a41762_720w.webp) 25 | 26 | 内存池设计实现中主要分为以下几部分: 27 | 28 | - 重载new 29 | - 创建内存节点 30 | - 创建内存池 31 | - 管理内存池 32 | 33 | 下面,比较详细的来说说设计细节: 34 | 35 | 重载new就不说了,直接从内存节点开始; 36 | 37 | > 内存池节点 38 | 39 | 内存池节点需要包含以下几点元素: 40 | 41 | 1. 所属池子(pMem),因为后续在内存池管理中可以直接调用申请内存和释放内存 42 | 2. 下一个节点(pNext),这里主要是使用链表的思路,将所有的内存块关联起来; 43 | 3. 节点是否被使用(bUsed),这里保证每次使用前,该节点是没有被使用的; 44 | 4. 是否属于内存池(bBelong),主要是一般内存池维护的空间都不是特别大,但是用户申请了特别大的内存时,就走正常的申请流程,释放时也就正常释放; 45 | 46 | > 内存池设计 47 | 48 | 内存池设计就是上面的图片类似,主要包含以下几点元素: 49 | 50 | 1. 内存首地址(_pBuffer),也就是第一块内存,这样以后方面寻找后面的内存块; 51 | 2. 内存块头(_pHeader),也就是上面说的内存池节点; 52 | 3. 内存块大小(_nSize),也就是每个节点多大; 53 | 4. 节点数(_nBlock),及时有多少个节点; 54 | 55 | 这里面需要的注意的是,申请内存块的时候,需要加上节点头,但是申请完后返回给客户使用的需要去掉头;但是释放的时候,需要前移到头,不然就会出现异常; 56 | 57 | **释放内存:** 58 | 59 | 释放内存的时候,将使用过的内存置为false,然后指向头部,将头部作为下一个节点,这样的话,节点每次回收就可以相应的被找到; 60 | 61 | > 内存池管理 62 | 63 | 内存池创建后,会根据节点大小和个数创建相应的内存池; 64 | 65 | 内存池管理主要就是根据不同的需求创建不同的内存池,以达到管理的目的; 66 | 67 | 这里主要有一个概念:数组映射 68 | 69 | **数组映射**就是不同的范围内,选择不同的内存池; 70 | 71 | 添一段代码: 72 | 73 | ``` 74 | void InitArray(int nBegin,int nEnd, MemoryPool*pMemPool) 75 | { 76 | for (int i = nBegin; i <= nEnd; i++) 77 | { 78 | _Alloc[i] = pMemPool; 79 | } 80 | } 81 | ``` 82 | 83 | 根据范围进行绑定; 84 | 85 | ## 4.内存池实现 86 | 87 | ManagerPool.hpp 88 | 89 | ``` 90 | #ifndef _MEMORYPOOL_HPP_ 91 | #define _MEMORYPOOL_HPP_ 92 | #include 93 | #include 94 | ////一个内存块的最大内存大小,可以扩展 95 | #define MAX_MEMORY_SIZE 256 96 | class MemoryPool; 97 | //内存块 98 | struct MemoryBlock 99 | { 100 | MemoryBlock* pNext;//下一块内存块 101 | bool bUsed;//是否使用 102 | bool bBelong;//是否属于内存池 103 | MemoryPool* pMem;//属于哪个池子 104 | }; 105 | class MemoryPool 106 | { 107 | public: 108 | MemoryPool(size_t nSize=128,size_t nBlock=10) 109 | { 110 | //相当于申请10块内存,每块内存是1024 111 | _nSize = nSize; 112 | _nBlock = nBlock; 113 | _pHeader = NULL; 114 | _pBuffer = NULL; 115 | } 116 | virtual ~MemoryPool() 117 | { 118 | if (_pBuffer != NULL) 119 | { 120 | free(_pBuffer); 121 | } 122 | } 123 | //申请内存 124 | void* AllocMemory(size_t nSize) 125 | { 126 | std::lock_guard lock(_mutex); 127 | //如果首地址为空,说明没有申请空间 128 | if (_pBuffer == NULL) 129 | { 130 | InitMemory(); 131 | } 132 | MemoryBlock* pRes = NULL; 133 | //如果内存池不够用时,需要重新申请内存 134 | if (_pHeader == NULL) 135 | { 136 | pRes = (MemoryBlock*)malloc(nSize+sizeof(MemoryBlock)); 137 | pRes->bBelong = false; 138 | pRes->bUsed = false; 139 | pRes->pNext = NULL; 140 | pRes->pMem = NULL; 141 | } 142 | else 143 | { 144 | pRes = _pHeader; 145 | _pHeader = _pHeader->pNext; 146 | pRes->bUsed = true; 147 | } 148 | //返回只返回头后面的信息 149 | return ((char*)pRes + sizeof(MemoryBlock)); 150 | } 151 | //释放内存 152 | void FreeMemory(void* p) 153 | { 154 | std::lock_guard lock(_mutex); 155 | //和申请内存刚好相反,这里需要包含头,然后全部释放 156 | MemoryBlock* pBlock = ((MemoryBlock*)p - sizeof(MemoryBlock)); 157 | if (pBlock->bBelong) 158 | { 159 | pBlock->bUsed = false; 160 | //循环链起来 161 | pBlock->pNext = _pHeader; 162 | pBlock = _pHeader; 163 | } 164 | else 165 | { 166 | //不属于内存池直接释放就可以 167 | free(pBlock); 168 | } 169 | } 170 | //初始化内存块 171 | void InitMemory() 172 | { 173 | if (_pBuffer) 174 | return; 175 | //计算每块的大小 176 | size_t PoolSize = _nSize + sizeof(MemoryBlock); 177 | //计算需要申请多少内存 178 | size_t BuffSize = PoolSize * _nBlock; 179 | _pBuffer = (char*)malloc(BuffSize); 180 | //初始化头 181 | _pHeader = (MemoryBlock*)_pBuffer; 182 | _pHeader->bUsed = false; 183 | _pHeader->bBelong = true; 184 | _pHeader->pMem = this; 185 | //初始化_nBlock块,并且用链表的形式连接 186 | //保存头指针 187 | MemoryBlock* tmp1 = _pHeader; 188 | for (size_t i = 1; i < _nBlock; i++) 189 | { 190 | MemoryBlock* tmp2 = (MemoryBlock*)(_pBuffer + i*PoolSize); 191 | tmp2->bUsed = false; 192 | tmp2->pNext = NULL; 193 | tmp2->bBelong = true; 194 | _pHeader->pMem = this; 195 | tmp1->pNext = tmp2; 196 | tmp1 = tmp2; 197 | } 198 | } 199 | public: 200 | //内存首地址(第一块内存的地址) 201 | char* _pBuffer; 202 | //内存块头 203 | MemoryBlock* _pHeader; 204 | //内存块大小 205 | size_t _nSize; 206 | //多少块 207 | size_t _nBlock; 208 | std::mutex _mutex; 209 | }; 210 | //可以使用模板传递参数 211 | template 212 | class MemoryPoolor:public MemoryPool 213 | { 214 | public: 215 | MemoryPoolor() 216 | { 217 | _nSize = nSize; 218 | _nBlock = nBlock; 219 | } 220 | }; 221 | //需要重新对内存池就行管理 222 | class ManagerPool 223 | { 224 | public: 225 | static ManagerPool& Instance() 226 | { 227 | static ManagerPool memPool; 228 | return memPool; 229 | } 230 | void* AllocMemory(size_t nSize) 231 | { 232 | if (nSize < MAX_MEMORY_SIZE) 233 | { 234 | return _Alloc[nSize]->AllocMemory(nSize); 235 | } 236 | else 237 | { 238 | MemoryBlock* pRes = (MemoryBlock*)malloc(nSize + sizeof(MemoryBlock)); 239 | pRes->bBelong = false; 240 | pRes->bUsed = true; 241 | pRes->pMem = NULL; 242 | pRes->pNext = NULL; 243 | return ((char*)pRes + sizeof(MemoryBlock)); 244 | } 245 | } 246 | //释放内存 247 | void FreeMemory(void* p) 248 | { 249 | MemoryBlock* pBlock = (MemoryBlock*)((char*)p - sizeof(MemoryBlock)); 250 | //释放内存池 251 | if (pBlock->bBelong) 252 | { 253 | pBlock->pMem->FreeMemory(p); 254 | } 255 | else 256 | { 257 | free(pBlock); 258 | } 259 | } 260 | private: 261 | ManagerPool() 262 | { 263 | InitArray(0,128, &_memory128); 264 | InitArray(129, 256, &_memory256); 265 | } 266 | ~ManagerPool() 267 | { 268 | } 269 | void InitArray(int nBegin,int nEnd, MemoryPool*pMemPool) 270 | { 271 | for (int i = nBegin; i <= nEnd; i++) 272 | { 273 | _Alloc[i] = pMemPool; 274 | } 275 | } 276 | //可以根据不同内存块进行分配 277 | MemoryPoolor<128, 1000> _memory128; 278 | MemoryPoolor<256, 1000> _memory256; 279 | //映射数组 280 | MemoryPool* _Alloc[MAX_MEMORY_SIZE + 1]; 281 | }; 282 | #endif 283 | ``` 284 | 285 | OperatorMem.hpp 286 | 287 | ``` 288 | #ifndef _OPERATEMEM_HPP_ 289 | #define _OPERATEMEM_HPP_ 290 | #include 291 | #include 292 | #include "MemoryPool.hpp" 293 | void* operator new(size_t nSize) 294 | { 295 | return ManagerPool::Instance().AllocMemory(nSize); 296 | } 297 | void operator delete(void* p) 298 | { 299 | return ManagerPool::Instance().FreeMemory(p); 300 | } 301 | void* operator new[](size_t nSize) 302 | { 303 | return ManagerPool::Instance().AllocMemory(nSize); 304 | } 305 | void operator delete[](void* p) 306 | { 307 | return ManagerPool::Instance().FreeMemory(p); 308 | } 309 | #endif 310 | ``` 311 | 312 | mian.cpp 313 | 314 | ``` 315 | #include "OperateMem.hpp" 316 | using namespace std; 317 | int main() 318 | { 319 | char* p = new char[128]; 320 | delete[] p; 321 | return 0; 322 | } 323 | ``` 324 | 325 | 原文链接:https://zhuanlan.zhihu.com/p/356612864 326 | 327 | 作者:Hu先生的Linux -------------------------------------------------------------------------------- /C++博文/【NO.219】perf-网络协议栈性能分析.md: -------------------------------------------------------------------------------- 1 | # 【NO.219】perf-网络协议栈性能分析 2 | 3 | 分析 Linux 网络协议栈性能有多种方式和工具。本文主要通过 Perf 生成 On-CPU 火焰图的方式,分析 Linux 内核网络协议栈在特定场景下的性能瓶颈,从而知晓当前协议栈的网络状况。 4 | 5 | ## 1.关于 On/Off-CPU 6 | 7 | ### 1.1.概念定义 8 | 9 | ![img](https://pic4.zhimg.com/80/v2-140bbf8313007f59c92008d0cf6d87c3_720w.webp) 10 | 11 | ### 1.2.On/Off-CPU 选择 12 | 13 | 在工程实践中,如果是 CPU 消耗型使用 On-CPU 火焰图,如果是 IO 消耗型则使用 Off-CPU 火焰图。如果无法确定, 可以通过压测工具来确认: 通过压测工具看能否让 CPU 使用率趋于饱和,从而判断是否为 CPU 消耗型。 14 | 15 | ### 1.3.分析方法 16 | 17 | Perf 火焰图整个图形看起来就像一团跳动的火焰,这也正是其名字的由来。燃烧在火苗尖部的就是 CPU 正在执行的操作,不过需要说明的是颜色是随机的,本身并没有特殊的含义,纵向表示调用栈的深度,横向表示消耗的时间。因为调用栈在横向会按照字母排序,并且同样的调用栈会做合并,所以一个格子的宽度越大越说明其可能是瓶颈。综上所述,主要就是看那些比较宽大的火苗,特别留意那些类似平顶山的火苗。 18 | 19 | ## 2.火焰图原理 20 | 21 | 火焰图是基于 stack 信息生成图片, 用来展示 CPU 调用栈。 22 | 23 | - y 轴表示调用栈, 每一层都是一个函数。调用栈越深, 火焰就越高, 顶部就是正在执行的函数, 下方都是它的父函数。 24 | - x 轴表示抽样数, 如果一个函数在x轴占据越宽, 则表示它被抽到的次数多, 即执行的时间长。 25 | 26 | 火焰图就是看顶层的哪个函数占据的宽度最大。只要有“平顶”(plateaus),就表示该函数可能存在性能问题。 27 | 28 | ## 3.On-CPU 采集原理 29 | 30 | ![img](https://pic1.zhimg.com/80/v2-a682f2717063b9dc0ca22c4da9989d74_720w.webp) 31 | 32 | ![img](https://pic1.zhimg.com/80/v2-081e26d5bef572c4e585d05ca522990c_720w.webp) 33 | 34 | Linux 内核网络协议栈分析 35 | 36 | server 37 | 38 | ``` 39 | yum install iperfyum install perfgit clone https://github.com/brendangregg/FlameGraph.git 40 | ``` 41 | 42 | 运行 iperf server 43 | 44 | ``` 45 | [root@bogon ~]# iperf -s -u -DRunning Iperf Server as a daemon[root@bogon ~]# 46 | ``` 47 | 48 | 查看 CPU 以及 softirqd 进程号 49 | 50 | ``` 51 | [root@bogon ~]# lscpuArchitecture: aarch64Byte Order: Little EndianCPU(s): 64On-line CPU(s) list: 0-63Thread(s) per core: 1Core(s) per socket: 4Socket(s): 16NUMA node(s): 4Model: 2BogoMIPS: 100.00NUMA node0 CPU(s): 0-15NUMA node1 CPU(s): 16-31NUMA node2 CPU(s): 32-47NUMA node3 CPU(s): 48-63Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid 52 | ``` 53 | 54 | ![img](https://pic4.zhimg.com/80/v2-a4150c37c195fd209c70f582b0959d9b_720w.webp) 55 | 56 | 查看网卡中断亲和性 57 | 58 | ![img](https://pic2.zhimg.com/80/v2-12bd0c929fad16b85392da3063e4f001_720w.webp) 59 | 60 | ![img](https://pic2.zhimg.com/80/v2-a2d31aad24d547a15829d926cb6bbdcd_720w.webp) 61 | 62 | ``` 63 | VM001: 通过如下方式采集信息(采集原理见上)perf record -F 1000 -a -g -p 162 -- sleep 60 64 | ``` 65 | 66 | 发送 UDP 数据报文 67 | 68 | ``` 69 | iperf -c 10.254.2.161 -i 1 -P 10 -t 10 -u -b 1000M 70 | ``` 71 | 72 | ## 4.生成 On-CPU 火焰图 73 | 74 | ![img](https://pic1.zhimg.com/80/v2-b088f3056d212b56812a3b60285b3a04_720w.webp) 75 | 76 | ![img](https://pic1.zhimg.com/80/v2-bddd0d6ad7f632560c3f4ae6dfc8ff48_720w.webp) 77 | 78 | ![img](https://pic1.zhimg.com/80/v2-503a3f2918d0b2e0474fc90de58b6558_720w.webp) 79 | 80 | 原文链接:https://linuxcpp.0voice.com/?id=442 81 | 82 | 作者:[HG ](https://linuxcpp.0voice.com/?auth=10) -------------------------------------------------------------------------------- /C++博文/【NO.248】从一道数据库面试题彻谈MySQL加锁机制.md: -------------------------------------------------------------------------------- 1 | # 【NO.248】从一道数据库面试题彻谈MySQL加锁机制 2 | 3 | 以下内容来自于腾讯工程师joeycheng。 4 | 5 | | 导语有一道关于数据库锁的面试题,发现其实很多DBA包括工作好几年的DBA都答的不太好,说明MySQL锁的机制其实还是比较复杂,值得深入研究。本文对3条简单的查询语句加锁情况进行分析,彻底搞清楚加锁细节。 6 | 7 | 首先来看这个面试题: 8 | 已知表t是innodb引擘,有主键:id(int类型) ,下面3条语句是否加锁?加锁的话,是什么锁? 9 | \1. select * from t where id=X; 10 | \2. begin;select * from t where id=X; 11 | \3. begin;select * from t where id=X for update; 12 | 13 | 这里其实有很多依赖条件,并不能一开始就给出一个很确定的答复。我们一层层展开来分析。 14 | 15 | ## 1.MySQL有哪些锁? 16 | 17 | ![img](https://pic4.zhimg.com/80/v2-1c319cbb084c605470802c180b3dfdaf_720w.webp) 18 | 19 | 首先要知道MySQL有哪些锁,如上图所示,至少有12类锁(其中**自增锁**是事务向包含了AUTO_INCREMENT列的表中新增数据时会持有,**predicate locks for spatial index** 为空间索引专用,本文不讨论这2类锁)。 20 | 21 | 锁按**粒度**可分为分为**全局,表级,行级**3类。 22 | 23 | ### **1.1 全局锁** 24 | 25 | 对整个数据库实例加锁。 26 | 加锁表现:数据库处于只读状态,阻塞对数据的所有DML/DDL 27 | 加锁方式:**Flush tables with read lock** 释放锁:**unlock tables**(发生异常时会自动释放) 28 | 作用场景:全局锁主要用于做数据库实例的逻辑备份,与设置数据库只读命令**set global readonly=true**相比,全局锁在发生异常时会自动释放 29 | 30 | ### 1.2 表锁 31 | 32 | 对操作的整张表加锁, 锁定颗粒度大,资源消耗少,不会出现死锁,但会导致写入并发度低。具体又分为3类: 33 | **1)显式表锁:**分为共享锁(S)和排他锁(X) 34 | 显示加锁方式:**lock tables ... read/write** 35 | 释放锁:**unlock tables**(连接中断也会自动释放) 36 | **2)Metadata-Lock(元数据锁)**:MySQL5.5版本开始引入,主要功能是并发条件下,防止session1的查询事务未结束的情况下,session2对表结构进行修改,保护元数据的一致性。在session1持有 metadata-lock的情况下,session2处于等待状态:show processlist可见**Waiting for table metadata lock**。其具体加锁机制如下: 37 | 38 | - DML->先加MDL 读锁(SHARED_READ,SHARED_WRITE) 39 | - DDL->先加MDL 写锁(EXCLUSIVE) 40 | - 读锁之间兼容 41 | - 读写锁之间、写锁之间互斥 42 | 43 | **3)Intention Locks(意向锁)**:**意向锁**为表锁(表示为IS或者IX),由存储引擎自己维护,用户无法干预。下面举一个例子说明其功能: 44 | 假设有2个事务:T1和T2 45 | T1: 锁住表中的一行,只能读不能写(行级读锁)。 46 | T2: 申请整个表的写锁(表级写锁)。 47 | 如T2申请成功,则能任意修改表中的一行,但这与T1持有的行锁是冲突的。故数据库应识别这种冲突,让T2的锁申请被阻塞,直到T1释放行锁。 48 | 有2种方法可以实现冲突检测: 49 | \1. 判断表是否已被其它事务用表锁锁住。 50 | \2. 判断表中的每一行是否已被行锁锁住。 51 | 其中2需要遍历整个表,**效率太低**。因此innodb使用意向锁来解决这个问题: 52 | T1需要先申请表的**意向共享锁(IS)**,成功后再申请某一行的**记录锁S**。 53 | 在意向锁存在的情况下,上面的判断可以改为: 54 | T2发现表上有意向共享锁IS,因此申请表的写锁被阻塞。 55 | 56 | 57 | **1.3 行锁** 58 | 59 | InnoDB引擘支持行级别锁,行锁粒度小,并发度高,但加锁开销大,也可能会出现死锁。 60 | 加锁机制:innodb行锁锁住的是索引页,回表时,主键的聚簇索引也会加上锁。 61 | 62 | ![img](https://pic2.zhimg.com/80/v2-d736695de3e65e17a52737669895e56d_720w.webp) 63 | 64 | 行锁具体类别如上图所示,包括:**Record lock/Gap Locks/Next-Key Locks**,每类又可分为**共享锁(S)**或者**排它锁(X)**,一共2*3=6类,最后还有1类插入意向锁: 65 | **Record lock(记录锁):**最简单的行锁,仅仅锁住一行。记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。 66 | **Gap Locks(间隙锁):**加在两个索引值之间的锁,或者加在第一个索引值之前,或最后一个索引值之后的间隙。使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。间隙锁只阻止其他事务插入到间隙中,不阻止其它事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。它是一个**左开右开**区间:如(1,3) 67 | **Next-Key Locks:记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。它是一个左开右闭**区间:如(1,3】 68 | **Insert Intention(插入意向锁)**:该锁只会出现在insert操作执行前(并不是所有insert操作都会出现),目的是为了提高并发插入能力。它在插入一行记录操作之前设置一种特殊的间隙锁,多个事务在相同的索引间隙插入时,如果不是插入间隙中相同的位置就不需要互相等待。 69 | 70 | **TIPS:** 71 | 72 | 1.不存在unlock tables … read/write,只有unlock tables 73 | 2.If a session begins a transaction, an implicit UNLOCK TABLES is performed 74 | 75 | ## 2.锁的兼容情况 76 | 77 | 引入意向锁后,表锁之间的兼容性情况如下表: 78 | 79 | ![img](https://pic2.zhimg.com/80/v2-eb3fda6550b257f3f9f101ed349de9c9_720w.webp) 80 | 81 | 总结: 82 | 83 | 1. 意向锁之间都兼容 84 | 2. X,IX和其它都不兼容(除了1) 85 | 3. S,IS和其它都兼容(除了1,2) 86 | 87 | ## 3.锁信息查看方式 88 | 89 | - MySQL 5.6.16版本之前,需要建立一张特殊表innodb_lock_monitor,然后使用**show engine innodb status**查看 90 | 91 | CREATE TABLE innodb_lock_monitor (a INT) ENGINE=INNODB; 92 | 93 | DROP TABLE innodb_lock_monitor; 94 | 95 | - MySQL 5.6.16版本之后,修改系统参数innodb_status_output后,使用**show engine innodb status**查看 96 | 97 | set GLOBAL innodb_status_output=ON; 98 | 99 | set GLOBAL innodb_status_output_locks=ON; 100 | 101 | 每15秒输出一次INNODB运行状态信息到错误日志 102 | 103 | ![img](https://pic4.zhimg.com/80/v2-64281e84309ecc4678faf76a9b6acc0b_720w.webp) 104 | 105 | - MySQL5.7版本之后 106 | 107 | 可以通过information_schema.innodb_locks查看事务的锁情况,但只能看到阻塞事务的锁;如果事务并未被阻塞,则在该表中看不到该事务的锁情况 108 | 109 | - MySQL8.0 110 | 111 | 删除information_schema.innodb_locks,添加performance_schema.data_locks,即使事务并未被阻塞,依然可以看到事务所持有的锁,同时通过performance_schema.table_handles、performance_schema.metadata_locks可以非常方便的看到元数据锁等表锁。 112 | 113 | ## 4.测试环境搭建 114 | 115 | ### 4.1 建立测试表 116 | 117 | 该表包含一个主键,一个唯一键和一个非唯一键: 118 | 119 | CREATE TABLE `t` ( 120 | 121 | `id` int(11) NOT NULL, 122 | 123 | `a` int(11) DEFAULT NULL, 124 | 125 | `b` int(11) DEFAULT NULL, 126 | 127 | `c` varchar(10), 128 | 129 | PRIMARY KEY (`id`), 130 | 131 | unique KEY `a` (`a`), 132 | 133 | key b(b)) 134 | 135 | ENGINE=InnoDB; 136 | 137 | ### 4.2 写入测试数据 138 | 139 | insert into t values(1,10,100,'a'),(3,30,300,'c'),(5,50,500,'e'); 140 | 141 | ## 5.记录存在时的加锁 142 | 143 | 对于innodb引擘来说,加锁的2个决定因素: 144 | 145 | 1)当前的**事务隔离级别** 146 | 2)当前**记录是否存在** 147 | 148 | 假设id为3的记录存在,则在不同的4个隔离级别下3个语句的加锁情况汇总如下表(select 3表示 select * from t where id=3): 149 | 150 | | 隔离级别 | select 3 | begin;select 3 | begin;select 3 for update | 151 | | -------- | -------- | ----------------------------- | ------------------------------ | 152 | | RU | 无 | SHARED_READ | SHARED_WRITEIXX,REC_NOT_GAP:3 | 153 | | RC | 无 | SHARED_READ | SHARED_WRITEIXX,REC_NOT_GAP:3 | 154 | | RR | 无 | SHARED_READ | SHARED_WRITEIXX,REC_NOT_GAP:3 | 155 | | Serial | 无 | SHARED_READISS,REC_NOT_GAP:1 | SHARED_WRITEIXX,REC_NOT_GAP:3 | 156 | 157 | 分析: 158 | 159 | 1. 使用以下语句在4种隔离级别之间切换: 160 | set global transaction_isolation='READ-UNCOMMITTED'; 161 | set global transaction_isolation='READ-COMMITTED'; 162 | set global transaction_isolation='REPEATABLE-READ'; 163 | set global transaction_isolation='Serializable'; 164 | 2. 对于auto commit=true,select 没有显式开启事务(begin)的语句,元数据锁和行锁都不加,是真的“**读不加锁**” 165 | 3. 对于begin; select ... where id=3这种只读事务,会加**元数据锁SHARED_READ**,防止事务执行期间表结构变化,查询**performance_schema.metadata_locks**表可见此锁: 166 | 167 | ![img](https://pic4.zhimg.com/80/v2-09617f06b0066268a365d3d2b528e6b3_720w.webp) 168 | 169 | 4.对于begin; select ... where id=3这种只读事务,MySQL在RC和RR隔离级别下,使用MVCC快照读,不加行锁,但在Serial隔离级别下,读写互斥,会加**意向共享锁(表锁)**和**共享记录锁(行锁)** 170 | 171 | 5.对于begin; select ... where id=3 for update,会加**元数据锁SHARED_WRITE** 172 | 173 | 6.对于begin; select ... where id=3 or update,4种隔离级别都会加**意向排它锁(表锁)**和**排它记录锁(行锁),**查询**performance_schema.data_locks**可见此2类锁 174 | 175 | ![img](https://pic4.zhimg.com/80/v2-f05caee858dd450aed01c78de8385f43_720w.webp) 176 | 177 | 6 记录不存在时的加锁 178 | 179 | | 隔离级别 | select 2 | begin;select 2 | begin;select 2 for update | 180 | | -------- | -------- | --------------------- | ------------------------- | 181 | | RU | 无 | SHARED_READ | SHARED_WRITEIX | 182 | | RC | 无 | SHARED_READ | SHARED_WRITEIX | 183 | | RR | 无 | SHARED_READ | SHARED_WRITEIXX,GAP:3 | 184 | | Serial | 无 | SHARED_READISS,GAP:3 | SHARED_WRITEIXX,GAP:3 | 185 | 186 | 分析: 187 | 188 | 1. 当记录不存在的时候,RU和RC隔离级别只有意向锁,没有行锁了 189 | 2. RR,Serial隔离级别下,记录锁变成了**Gap Locks(间隙锁),**可以防止**幻读,**lock_data为3的GAP lock锁住区间(1,3),此时ID=2的记录插入会被阻塞。 190 | 191 | ![img](https://pic2.zhimg.com/80/v2-6bb1c557a252a61d90f2cb52722d8259_720w.webp) 192 | 193 | ![img](https://pic2.zhimg.com/80/v2-b35275724285a207814701d26d886f3d_720w.webp) 194 | 195 | 那么对于主键范围查询,唯一键查询,非唯一键查询,在不同隔离级别下又是如何加锁的呢? 196 | 《从一道数据库面试题彻谈MySQL加锁机制(下)》再做分享。 197 | 198 | 原文作者:鹅厂架构师 199 | 200 | 原文链接:https://zhuanlan.zhihu.com/p/566194828 -------------------------------------------------------------------------------- /C++博文/【NO.374】一文带你了解大厂亿级并发下高性能服务器是如何实现的!.md: -------------------------------------------------------------------------------- 1 | # 【NO.374】一文带你了解大厂亿级并发下高性能服务器是如何实现的! 2 | 3 | ## 1.多进程 4 | 5 | 历史上最早出现也是最简单的一种并行处理多个请求的方法就是利用**多进程**。 6 | 7 | 比如在Linux世界中,我们可以使用fork、exec等**系统调用**创建多个进程,我们可以在父进程中接收用户的连接请求,然后创建子进程去处理用户请求,就像这样: 8 | 9 | ![img](https://pic3.zhimg.com/80/v2-8943df0d0fc077832f017c8a38ebb8d2_720w.webp) 10 | 11 | 这种方法的优点就在于: 12 | 13 | 1. 编程简单,非常容易理解 14 | 2. 由于各个进程的地址空间是相互隔离的,因此一个进程崩溃后并不会影响其它进程 15 | 3. 充分利用多核资源 16 | 17 | 多进程并行处理的优点很明显,但是缺点同样明显: 18 | 19 | 1. 各个进程地址空间相互隔离,这一优点也会变成缺点,那就是进程间要想通信就会变得比较困难,你需要借助进程间通信(IPC,interprocess communications)机制,想一想你现在知道哪些进程间通信机制,然后让你用代码实现呢?显然,进程间通信编程相对复杂,而且性能也是一大问题 20 | 2. 我们知道创建进程开销是比线程要大的,频繁的创建销毁进程无疑会加重系统负担。 21 | 22 | 幸好,除了进程,我们还有线程。 23 | 24 | ## 2.多线程 25 | 26 | 不是创建进程开销大吗?不是进程间通信困难吗?这些对于线程来说统统不是问题。 27 | 28 | 由于线程共享进程地址空间,因此线程间通信天然不需要借助任何通信机制,直接读取内存就好了。线程创 29 | 30 | 建销毁的开销也变小了,要知道线程就像寄居蟹一样,房子(地址空间)都是进程的,自己只是一个租客,因此非常的**轻量级**,创建销毁的开销也非常小。 31 | 32 | 我们可以为每个请求创建一个线程,即使一个线程因执行I/O操作——比如读取数据库等——被阻塞暂停运行也不会影响到其它线程,就像这样: 33 | 34 | ![img](https://pic3.zhimg.com/80/v2-201fefe461b9c10d75c34343a74cf2a2_720w.webp) 35 | 36 | 但线程就是完美的、包治百病的吗,显然,计算机世界从来没有那么简单。 37 | 38 | 由于线程共享进程地址空间,这在为线程间通信带来便利的同时也带来了无尽的麻烦。 39 | 40 | 正是由于线程间共享地址空间,因此一个线程崩溃会导致整个进程崩溃退出,同时线程间通信简直太简单了,简单到线程间通信只需要直接读取内存就可以了,也简单到出现问题也极其容易,死锁、线程间的同步互斥、等等,这些极容易产生bug,**无数程序员宝贵的时间就有相当一部分用来解决多线程带来的无尽问题**。 41 | 42 | 虽然线程也有缺点,但是相比多进程来说,线程更有优势,**但想单纯的利用多线程就能解决高并发问题也是不切实际的**。 43 | 44 | 因为虽然线程创建开销相比进程小,但依然也是有开销的,对于动辄数万数十万的链接的高并发服务器来说,创建数万个线程会有性能问题,这包括内存占用、线程间切换,也就是调度的开销。 45 | 46 | 因此,我们需要进一步思考。 47 | 48 | ## 3.Event Loop:事件驱动 49 | 50 | 到目前为止,我们提到“并行”二字就会想到进程、线程。但是,并行编程只能依赖这两项技术吗,并不是这样的。 51 | 52 | 还有另一项并行技术广泛应用在GUI编程以及服务器编程中,这就是近几年非常流行的事件驱动编程,event-based concurrency。 53 | 54 | 大家不要觉得这是一项很难懂的技术,实际上事件驱动编程原理上非常简单。这一技术需要两种原料: 55 | 56 | 1. event 57 | 2. 处理event的函数,这一函数通常被称为event handler 58 | 59 | 剩下的就简单了: 60 | 61 | 你只需要安静的等待event到来就好,当event到来之后,检查一下event的类型,并根据该类型找到对应的event处理函数,也就是event handler,然后直接调用该event handler就好了。 62 | 63 | ![img](https://pic2.zhimg.com/80/v2-5fc49b3d520c2b2506651642dcefb725_720w.webp) 64 | 65 | **That’s it !** 66 | 67 | 以上就是事件驱动编程的全部内容,是不是很简单!从上面的讨论可以看到,我们需要不断的接收event然后处理event,因此我们需要一个循环(用while或者for循环都可以),这个循环被称为Event loop。 68 | 69 | 使用伪代码表示就是这样: 70 | 71 | ``` 72 | while(true) { 73 | event = getEvent(); 74 | handler(event); 75 | } 76 | ``` 77 | 78 | Event loop中要做的事情其实是非常简单的,只需要等待event的带来,然后调用相应的event处理函数即可。 79 | 80 | 注意,这段代码只需要运行在一个线程或者进程中,只需要这一个event loop就可以同时处理多个用户请求。 81 | 82 | 有的同学可以依然不明白为什么这样一个event loop可以同时处理多个请求呢? 83 | 84 | 原因很简单,对于web服务器来说,处理一个用户请求时大部分时间其实都用在了I/O操作上,像数据库读写、文件读写、网络读写等。当一个请求到来,简单处理之后可能就需要查询数据库等I/O操作,我们知道I/O是非常慢的,当发起I/O后**我们大可以不用等待该I/O操作完成就可以继续处理接下来的用户请求**。 85 | 86 | ![img](https://pic3.zhimg.com/80/v2-aa4b29f3965e2d035b120035d9817b72_720w.webp) 87 | 88 | 现在你应该明白了吧,虽然上一个用户请求还没有处理完我们其实就可以处理下一个用户请求了,这也是并行,这种并行就可以用事件驱动编程来处理。 89 | 90 | 这就好比餐厅服务员一样,一个服务员不可能一直等上一个顾客下单、上菜、吃饭、买单之后才接待下一个顾客,服务员是怎么做的呢?当一个顾客下完单后直接处理下一个顾客,当顾客吃完饭后会自己回来买单结账的。 91 | 92 | 看到了吧,同样是一个服务员也可以同时处理多个顾客,这个服务员就相当于这里的Event loop,即使这个event loop只运行在一个线程(进程)中也可以同时处理多个用户请求。 93 | 94 | 相信你已经对事件驱动编程有一个清晰的认知了,那么接下来的问题就是事件驱动、事件驱动,那么这个事件也就是event该怎么获取呢? 95 | 96 | ## 4.事件来源:IO多路复用 97 | 98 | 在Linux/Unix世界中一切皆文件,而我们的程序都是通过文件描述符来进行I/O操作的,当然对于socket也不例外,那我们该如何同时处理多个文件描述符呢? 99 | 100 | IO多路复用技术正是用来解决这一问题的,通过IO多路复用技术,我们一次可以监控多个文件描述,当某个文件(socket)可读或者可写的时候我们就能得到通知啦。 101 | 102 | 这样IO多路复用技术就成了event loop的原材料供应商,源源不断的给我们提供各种event,这样关于event来源的问题就解决了。 103 | 104 | ![img](https://pic4.zhimg.com/80/v2-ee98b1280d796711d00ef3972fff8eff_720w.webp) 105 | 106 | 至此,关于利用事件驱动来实现并发编程的所有问题都解决了吗?event的来源问题解决了,当得到event后调用相应的handler,看上去大功告成了。 107 | 108 | 想一想还有没有其它问题? 109 | 110 | ## 5.问题:阻塞式IO 111 | 112 | 现在,我们可以使用一个线程(进程)就能基于事件驱动进行并行编程,再也没有了多线程中让人恼火的各种锁、同步互斥、死锁等问题了。 113 | 114 | 但是,计算机科学中从来没有出现过一种能解决所有问题的技术,现在没有,在可预期的将来也不会有。 115 | 116 | 那上述方法有什么问题吗? 117 | 118 | 不要忘了,我们event loop是运行在一个线程(进程),这虽然解决了多线程问题,但是如果在处理某个event时需要进行IO操作会怎么样呢? 119 | 120 | 程序员最常用的这种IO方式被称为阻塞式IO,也就是说,当我们进行IO操作,比如读取文件时,如果文件没有读取完成,那么我们的程序(线程)会被阻塞而暂停执行,这在多线程中不是问题,因为操作系统还可以调度其它线程。 121 | 122 | 但是在单线程的event loop中是有问题的,原因就在于当我们在event loop中执行阻塞式IO操作时整个线程(event loop)会被暂停运行,这时操作系统将没有其它线程可以调度,因为系统中只有一个event loop在处理用户请求,这样当event loop线程被阻塞暂停运行时所有用户请求都没有办法被处理,你能想象当服务器在处理其它用户请求读取数据库导致你的请求被暂停吗? 123 | 124 | ![img](https://pic4.zhimg.com/80/v2-acd3c9bea7d909a144b4936b69c46b17_720w.webp) 125 | 126 | 因此,在基于事件驱动编程时有一条注意事项,**那就是不允许发起阻塞式IO**。 127 | 128 | 有的同学可能会问,如果不能发起阻塞式IO的话,那么该怎样进行IO操作呢?有阻塞式IO,就有非阻塞式IO。 129 | 130 | ## 6.非阻塞IO 131 | 132 | 为克服阻塞式IO所带来的问题,现代操作系统开始提供一种新的发起IO请求的方法,这种方法就是异步IO,对应的,阻塞式IO就是同步IO。 133 | 134 | 异步IO时,假设调用aio_read函数(具体的异步IO API请参考具体的操作系统平台),也就是异步读取,当我们调用该函数后可以**立即返回**,并继续其它事情,虽然此时该文件可能还没有被读取,这样就不会阻塞调用线程了。此外,操作系统还会提供其它方法供调用线程来检测IO操作是否完成。 135 | 136 | 就这样,在操作系统的帮助下IO的阻塞调用问题也解决了。 137 | 138 | ## 7.基于事件编程的难点 139 | 140 | 虽然有异步IO来解决event loop可能被阻塞的问题,但是基于事件编程依然是困难的。 141 | 142 | 首先,我们提到,event loop是运行在一个线程中的,显然一个线程是没有办法充分利用多核资源的,有的同学可能会说那就创建多个event loop实例不就可以了,这样就有多个event loop线程了,但是这样一来多线程问题又会出现。 143 | 144 | 另一点在于编程方面,编程方式需要把处理逻辑分为两部分,一部分调用方自己处理,另一部分在回调函数中处理,这一编程方式的改变加重了程序员在理解上的负担,基于事件编程的项目后期会很难扩展以及维护。 145 | 146 | 那么有没有更好的方法呢? 147 | 148 | 要找到更好的方法,我们需要解决问题的本质,那么这个本质问题是什么呢? 149 | 150 | ## 8.更好的方法 151 | 152 | 为什么我们要使用异步这种难以理解的方式编程呢? 153 | 154 | 是因为阻塞式编程虽然容易理解但会导致线程被阻塞而暂停运行。 155 | 156 | 那么聪明的你一定会问了,有没有一种方法既能结合同步IO的简单理解又不会因同步调用导致线程被阻塞呢? 157 | 158 | 答案是肯定的,这就是用户态线程,user level thread,也就是大名鼎鼎的协程,关于协程值得单独拿出一篇文章来讲解,就在下一篇。 159 | 160 | 虽然基于事件编程有这样那样的缺点,但是在当今的高性能高并发服务器上基于事件编程方式依然非常流行,但已经不是纯粹的基于单一线程的事件驱动了,而是event loop + multi thread + user level thread。 161 | 162 | ## 9.总结 163 | 164 | 高并发技术从最开始的多进程一路演进到当前的事件驱动,计算机技术就像生物一样也在不断演变进化,但不管怎样,了解历史才能更深刻的理解当下。希望这篇文章能对大家理解高并发服务器有所帮助。 165 | 166 | 原文链接:https://zhuanlan.zhihu.com/p/398701843 167 | 168 | 作者:[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10) -------------------------------------------------------------------------------- /C++博文/【NO.375】你真的懂Redis与MySQL双写一致性如何保证吗?.md: -------------------------------------------------------------------------------- 1 | # 【NO.375】你真的懂Redis与MySQL双写一致性如何保证吗? 2 | 3 | ![img](https://pic2.zhimg.com/80/v2-bf659604e4c91ca143c6d076860d76c1_720w.webp) 4 | 5 | 一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。 6 | 7 | - **强一致性**:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大 8 | - **弱一致性**:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态 9 | - **最终一致性**:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。 10 | 11 | ![img](https://pic2.zhimg.com/80/v2-b6e219313e5a7bb717ebe5c7e4986145_720w.webp) 12 | 13 | ## 1.三个经典的缓存模式 14 | 15 | 缓存可以提升性能、缓解数据库压力,但是使用缓存也会导致数据**不一致性**的问题。一般我们是如何使用缓存呢?有三种经典的缓存使用模式: 16 | 17 | - Cache-Aside Pattern 18 | - Read-Through/Write-through 19 | - Write-behind 20 | 21 | ## 2.Cache-Aside Pattern 22 | 23 | Cache-Aside Pattern,即**旁路缓存模式**,它的提出是为了尽可能地解决缓存与数据库的数据不一致问题。 24 | 25 | ## 3.Cache-Aside读流程 26 | 27 | **Cache-Aside Pattern**的读请求流程如下: 28 | 29 | ![img](https://pic4.zhimg.com/80/v2-07928a879bba5403e8c612686d827f4b_720w.webp) 30 | 31 | 1. 读的时候,先读缓存,缓存命中的话,直接返回数据 32 | 2. 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。 33 | 34 | ## 4.Cache-Aside 写流程 35 | 36 | **Cache-Aside Pattern**的写请求流程如下: 37 | 38 | ![img](https://pic1.zhimg.com/80/v2-b13d98e2bdd1e3dadfb94cafadc2353c_720w.webp) 39 | 40 | 更新的时候,先**更新数据库,然后再删除缓存**。 41 | 42 | ## 5.Read-Through/Write-Through(读写穿透) 43 | 44 | **Read/Write-Through**模式中,服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过**抽象缓存层**完成的。 45 | 46 | ## 6.Read-Through 47 | 48 | **Read-Through**的简要流程如下 49 | 50 | ![img](https://pic1.zhimg.com/80/v2-e6b5141a91306068a7d50d8f525ccd58_720w.webp) 51 | 52 | 1. 从缓存读取数据,读到直接返回 53 | 2. 如果读取不到的话,从数据库加载,写入缓存后,再返回响应。 54 | 55 | 这个简要流程是不是跟**Cache-Aside**很像呢?其实**Read-Through**就是多了一层**Cache-Provider**而已,流程如下: 56 | 57 | ![img](https://pic2.zhimg.com/80/v2-8b32355be4dd53489a5c3d7244431fcd_720w.webp) 58 | 59 | ## 7.Write-behind (异步缓存写入) 60 | 61 | **Write-behind** 跟Read-Through/Write-Through有相似的地方,都是由**Cache Provider**来负责缓存和数据库的读写。它们又有个很大的不同:**Read/Write-Through**是同步更新缓存和数据的,**Write-Behind**则是只更新缓存,不直接更新数据库,通过**批量异步**的方式来更新数据库。 62 | 63 | ![img](https://pic1.zhimg.com/80/v2-3dd2fa657d87e1077be59747a6bd09b8_720w.webp) 64 | 65 | 这种方式下,缓存和数据库的一致性不强,**对一致性要求高的系统要谨慎使用**。但是它适合频繁写的场景,MySQL的**InnoDB Buffer Pool机制**就使用到这种模式。 66 | 67 | ## 8.操作缓存的时候,到底是删除缓存呢,还是更新缓存? 68 | 69 | 日常开发中,我们一般使用的就是**Cache-Aside**模式。有些小伙伴可能会问, **Cache-Aside**在写入请求的时候,为什么是**删除缓存而不是更新缓存**呢? 70 | 71 | ![img](https://pic4.zhimg.com/80/v2-f4ec7a0a0eff2dc7d1b99b48b0d422af_720w.webp) 72 | 73 | 我们在操作缓存的时候,到底应该删除缓存还是更新缓存呢?我们先来看个例子: 74 | 75 | ![img](https://pic1.zhimg.com/80/v2-47f6dbdd8a3fd153edb68880608c2c8c_720w.webp) 76 | 77 | 1. 线程A先发起一个写操作,第一步先更新数据库 78 | 2. 线程B再发起一个写操作,第二步更新了数据库 79 | 3. 由于网络等原因,线程B先更新了缓存 80 | 4. 线程A更新缓存。 81 | 82 | 这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据**不一致**了,脏数据出现啦。如果是**删除缓存取代更新缓存**则不会出现这个脏数据问题。 83 | 84 | **更新缓存相对于删除缓存**,还有两点劣势: 85 | 86 | - 如果你写入的缓存值,是经过复杂计算才得到的话。更新缓存频率高的话,就浪费性能啦。 87 | - 在写数据库场景多,读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能呢(实际上,写多的场景,用缓存也不是很划算的,哈哈) 88 | 89 | ## 9.双写的情况下,先操作数据库还是先操作缓存? 90 | 91 | Cache-Aside缓存模式中,有些小伙伴还是会有疑问,在写请求过来的时候,为什么是**先操作数据库呢**?为什么**不先操作缓存**呢? 92 | 93 | 假设有A、B两个请求,请求A做更新操作,请求B做查询读取操作。 94 | 95 | ![img](https://pic2.zhimg.com/80/v2-db77fe1eaf1ded90fce5d8f57ae43bd1_720w.webp) 96 | 97 | 1. 线程A发起一个写操作,第一步del cache 98 | 2. 此时线程B发起一个读操作,cache miss 99 | 3. 线程B继续读DB,读出来一个老数据 100 | 4. 然后线程B把老数据设置入cache 101 | 5. 线程A写入DB最新的数据 102 | 103 | 酱紫就有问题啦,**缓存和数据库的数据不一致了。缓存保存的是老数据,数据库保存的是新数据**。因此,Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存。 104 | 105 | - 个别小伙伴可能会问,先操作数据库再操作缓存,不一样也会导致数据不一致嘛?它俩又不是原子性操作的。这个是**会的**,但是这种方式,一般因为删除缓存失败等原因,才会导致脏数据,这个概率就很低。小伙伴们可以画下操作流程图,自己先分析下哈。接下来我们再来分析这种**删除缓存失败**的情况,**如何保证一致性**。 106 | 107 | ## 10.数据库和缓存数据保持强一致,可以嘛? 108 | 109 | 实际上,没办法做到数据库与缓存**绝对的一致性**。 110 | 111 | - 加锁可以嘛?并发写期间加锁,任何读操作不写入缓存? 112 | - 缓存及数据库封装CAS乐观锁,更新缓存时通过lua脚本? 113 | - 分布式事务,3PC?TCC? 114 | 115 | 其实,这是由**CAP理论**决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。**个人觉得,追求绝对一致性的业务场景,不适合引入缓存**。 116 | 117 | ![img](https://pic1.zhimg.com/80/v2-c27e30e3e562611267e217a293e14e44_720w.webp) 118 | 119 | 但是,通过一些方案优化处理,是可以**保证弱一致性,最终一致性**的。 120 | 121 | ## 11.3种方案保证数据库与缓存的一致性 122 | 123 | ### 11.1.缓存延时双删 124 | 125 | 有些小伙伴可能会说,并不一定要先操作数据库呀,采用**缓存延时双删**策略,就可以保证数据的一致性啦。什么是延时双删呢? 126 | 127 | ![img](https://pic2.zhimg.com/80/v2-0ce4b2d379ea35b61ce7a23e7c43ec2d_720w.webp) 128 | 129 | 1. 先删除缓存 130 | 2. 再更新数据库 131 | 3. 休眠一会(比如1秒),再次删除缓存。 132 | 133 | 这个休眠一会,一般多久呢?都是1秒 134 | 135 | ![img](https://pic1.zhimg.com/80/v2-e24ffa86eafb57aed70e214684917d08_720w.webp) 136 | 137 | 这种方案还算可以,只有休眠那一会(比如就那1秒),可能有脏数据,一般业务也会接受的。但是如果**第二次删除缓存失败**呢?缓存和数据库的数据还是可能不一致,对吧?给Key设置一个自然的expire过期时间,让它自动过期怎样?那业务要接受**过期时间**内,数据的不一致咯?还是有其他更佳方案呢? 138 | 139 | ### 11.2.删除缓存重试机制 140 | 141 | 不管是**延时双删**还是**Cache-Aside的先操作数据库再删除缓存**,都可能会存在第二步的删除缓存失败,导致的数据不一致问题。可以使用这个方案优化:删除失败就多删除几次呀,保证删除缓存成功就可以了呀~ 所以可以引入**删除缓存重试机制** 142 | 143 | ![img](https://pic1.zhimg.com/80/v2-0fadcdbd48c120488995ef21589b4ae4_720w.webp) 144 | 145 | 1. 写请求更新数据库 146 | 2. 缓存因为某些原因,删除失败 147 | 3. 把删除失败的key放到消息队列 148 | 4. 消费消息队列的消息,获取要删除的key 149 | 5. 重试删除缓存操作 150 | 151 | ### 11.3.读取biglog异步删除缓存 152 | 153 | 重试删除缓存机制还可以吧,就是会造成好多**业务代码入侵**。其实,还可以这样优化:通过数据库的**binlog来异步淘汰key**。 154 | 155 | ![img](https://pic1.zhimg.com/80/v2-4aaccecf677e2f0a0e532090ffcf3d38_720w.webp) 156 | 157 | 以mysql为例吧 158 | 159 | - 可以使用阿里的canal将binlog日志采集发送到MQ队列里面 160 | - 然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性 161 | 162 | 觉得对你有帮助的话就点个赞吧~~ 163 | 164 | 原文链接:https://zhuanlan.zhihu.com/p/401477769 165 | 166 | 作者:[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10) -------------------------------------------------------------------------------- /C++博文/【NO.380】深入理解 ProtoBuf 原理与工程实践.md: -------------------------------------------------------------------------------- 1 | # 【NO.380】深入理解 ProtoBuf 原理与工程实践 2 | 3 | ProtoBuf 作为一种跨平台、语言无关、可扩展的序列化结构数据的方法,已广泛应用于网络数据交换及存储。随着互联网的发展,系统的异构性会愈发突出,跨语言的需求会愈加明显,同时 gRPC 也大有取代Restful之势,而 ProtoBuf 作为g RPC 跨语言、高性能的法宝,我们技术人有必要 4 | 5 | 深入理解 ProtoBuf 原理,为以后的技术更新和选型打下基础。 6 | 7 | 我将过去的学习过程以及实践经验,总结成系列文章,与大家一起探讨学习,希望大家能有所收获,当然其中有不正确的地方也欢迎大家批评指正。 8 | 9 | 本系列文章主要包含: 10 | 11 | 1. 深入理解 ProtoBuf 原理与工程实践(概述) 12 | 2. 深入理解 ProtoBuf 原理与工程实践(编码) 13 | 3. 深入理解 ProtoBuf 原理与工程实践(序列化) 14 | 4. 深入理解 ProtoBuf 原理与工程实践(工程实践) 15 | 16 | ## 1.什么是ProtoBuf 17 | 18 | ProtoBuf(Protocol Buffers)是一种跨平台、语言无关、可扩展的序列化结构数据的方法,可用于网络数据交换及存储。 19 | 20 | 在序列化结构化数据的机制中,ProtoBuf是灵活、高效、自动化的,相对常见的XML、JSON,描述同样的信息,ProtoBuf序列化后数据量更小、序列化/反序列化速度更快、更简单。 21 | 22 | 一旦定义了要处理的数据的数据结构之后,就可以利用ProtoBuf的代码生成工具生成相关的代码。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言(proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#)或从各种不同流中对你的结构化数据轻松读写。 23 | 24 | ## 2.为什么是 ProtoBuf 25 | 26 | 大家可能会觉得 Google 发明 ProtoBuf 是为了解决序列化速度的,其实真实的原因并不是这样的。 27 | 28 | ProtoBuf最先开始是 Google用来解决索引服务器 request/response 协议的。没有ProtoBuf之前,Google 已经存在了一种 request/response 格式,用于手动处理 request/response 的编解码。它也能支持多版本协议,不过代码不够优雅: 29 | 30 | ``` 31 | if (protocolVersion=1) { 32 | doSomething(); 33 | } else if (protocolVersion=2) { 34 | doOtherThing(); 35 | } ... 36 | ``` 37 | 38 | 如果是非常明确的格式化协议,会使新协议变得非常复杂。因为开发人员必须确保请求发起者与处理请求的实际服务器之间的所有服务器都能理解新协议,然后才能切换开关以开始使用新协议。 39 | 40 | 这也就是每个服务器开发人员都遇到过的低版本兼容、新旧协议兼容相关的问题。 41 | 42 | 为了解决这些问题,于是ProtoBuf就诞生了。 43 | 44 | ProtoBuf 最初被寄予以下 2 个特点: 45 | 46 | - **更容易引入新的字段**,并且不需要检查数据的中间服务器可以简单地解析并传递数据,而无需了解所有字段。 47 | - **数据格式更加具有自我描述性**,可以用各种语言来处理(C++, Java 等各种语言)。 48 | 49 | 这个版本的 ProtoBuf 仍需要自己手写解析的代码。 50 | 51 | 不过随着系统慢慢发展,演进,ProtoBuf具有了更多的特性: 52 | 53 | - 自动生成的序列化和反序列化代码避免了手动解析的需要。(官方提供自动生成代码工具,各个语言平台的基本都有)。 54 | - 除了用于数据交换之外,ProtoBuf被用作持久化数据的便捷自描述格式。 55 | 56 | ProtoBuf 现在是 Google 用于数据交换和存储的通用语言。谷歌代码树中定义了 48162 种不同的消息类型,包括 12183 个 .proto 文件。它们既用于 RPC 系统,也用于在各种存储系统中持久存储数据。 57 | 58 | ProtoBuf 诞生之初是为了解决服务器端新旧协议(高低版本)兼容性问题,名字也很体贴,“协议缓冲区”。只不过后期慢慢发展成用于传输数据。 59 | 60 | Protocol Buffers 命名由来: 61 | 62 | > Why the name “Protocol Buffers”? 63 | > The name originates from the early days of the format, before we had the protocol buffer compiler to generate classes for us. At the time, there was a class called ProtocolBuffer which actually acted as a buffer for an individual method. Users would add tag/value pairs to this buffer individually by calling methods like AddValue(tag, value). The raw bytes were stored in a buffer which could then be written out once the message had been constructed. 64 | > Since that time, the “buffers” part of the name has lost its meaning, but it is still the name we use. Today, people usually use the term “protocol message” to refer to a message in an abstract sense, “protocol buffer” to refer to a serialized copy of a message, and “protocol message object” to refer to an in-memory object representing the parsed message. 65 | 66 | ## 3.如何使用 ProtoBuf 67 | 68 | ### 3.1. ProtoBuf 协议的工作流程 69 | 70 | ![img](https://pic4.zhimg.com/80/v2-6bd90593f1d04ef0c50be6f503ae4f0b_720w.webp) 71 | 72 | 可以看到,对于序列化协议来说,使用方只需要关注业务对象本身,即 idl 定义,序列化和反序列化的代码只需要通过工具生成即可。 73 | 74 | ### 3.2 .ProtoBuf 消息定义 75 | 76 | ProtoBuf 的消息是在idl文件(.proto)中描述的。下面是本次样例中使用到的消息描述符customer.proto: 77 | 78 | ``` 79 | syntax = "proto3"; 80 | package domain; 81 | option java_package = "com.protobuf.generated.domain"; 82 | option java_outer_classname = "CustomerProtos"; 83 | message Customers { 84 | repeated Customer customer = 1; 85 | } 86 | message Customer { 87 | int32 id = 1; 88 | string firstName = 2; 89 | string lastName = 3; 90 | enum EmailType { 91 | PRIVATE = 0; 92 | PROFESSIONAL = 1; 93 | } 94 | message EmailAddress { 95 | string email = 1; 96 | EmailType type = 2; 97 | } 98 | repeated EmailAddress email = 5; 99 | } 100 | ``` 101 | 102 | 上面的消息比较简单,Customers包含多个Customer,Customer包含一个id字段,一个firstName字段,一个lastName字段以及一个email的集合。 103 | 104 | 除了这些定义外,文件顶部还有三行可帮助代码生成器: 105 | 106 | 1. 首先,syntax = “proto3”用于idl语法版本,目前有两个版本proto2和proto3,两个版本语法不兼容,如果不指定,默认语法是proto2。由于proto3比proto2支持的语言更多,语法更简洁,本文使用的是proto3。 107 | 2. 其次有一个package domain;定义。此配置用于嵌套生成的类/对象。 108 | 3. 有一个option java_package定义。生成器还使用此配置来嵌套生成的源。此处的区别在于这仅适用于Java。在使用Java创建代码和使用JavaScript创建代码时,使用了两种配置来使生成器的行为有所不同。也就是说,Java类是在包com.protobuf.generated.domain下创建的,而JavaScript对象是在包domain下创建的。 109 | 110 | ProtoBuf 提供了更多选项和数据类型,本文不做详细介绍,感兴趣可以参考这里。 111 | 112 | ### 3.3 .代码生成 113 | 114 | 首先安装 ProtoBuf 编译器 protoc,这里有详细的安装教程,安装完成后,可以使用以下命令生成 Java 源代码: 115 | 116 | ``` 117 | protoc --java_out=./src/main/java ./src/main/idl/customer.proto 118 | ``` 119 | 120 | 从项目的根路径执行该命令,并添加了两个参数:java_out,定义./src/main/java/为Java代码的输出目录;而./src/main/idl/customer.proto是.proto文件所在目录。 121 | 122 | 生成的代码非常复杂,但是幸运的是它的用法却非常简单。 123 | 124 | ``` 125 | CustomerProtos.Customer.EmailAddress email = CustomerProtos.Customer.EmailAddress.newBuilder() 126 | .setType(CustomerProtos.Customer.EmailType.PROFESSIONAL) 127 | .setEmail("crichardson@email.com").build(); 128 | CustomerProtos.Customer customer = CustomerProtos.Customer.newBuilder() 129 | .setId(1) 130 | .setFirstName("Lee") 131 | .setLastName("Richardson") 132 | .addEmail(email) 133 | .build(); 134 | // 序列化 135 | byte[] binaryInfo = customer.toByteArray(); 136 | System.out.println(bytes_String16(binaryInfo)); 137 | System.out.println(customer.toByteArray().length); 138 | // 反序列化 139 | CustomerProtos.Customer anotherCustomer = CustomerProtos.Customer.parseFrom(binaryInfo); 140 | System.out.println(anotherCustomer.toString()); 141 | ``` 142 | 143 | ### 3.4. 性能数据 144 | 145 | 我们简单地以Customers为模型,分别构造、选取小对象、普通对象、大对象进行性能对比。 146 | 147 | 序列化耗时以及序列化后数据大小对比 148 | 149 | ![img](https://pic2.zhimg.com/80/v2-6b3bca2954b3717861c50ada727cd9c9_720w.webp) 150 | 151 | 反序列化耗时 152 | 153 | ![img](https://pic2.zhimg.com/80/v2-97d226a1fce4f599183f115aab7db929_720w.webp) 154 | 155 | ## 4.总结 156 | 157 | 上面介绍了 ProtoBuf 是什么、产生的背景、基本用法,我们再总结下。 158 | 159 | **优点:** 160 | 161 | > \1. 效率高 162 | > 从序列化后的数据体积角度,与XML、JSON这类文本协议相比,ProtoBuf通过T-(L)-V(TAG-LENGTH-VALUE)方式编码,不需要”, {, }, :等分隔符来结构化信息,同时在编码层面使用varint压缩,所以描述同样的信息,ProtoBuf序列化后的体积要小很多,在网络中传输消耗的网络流量更少,进而对于网络资源紧张、性能要求非常高的场景,ProtoBuf协议是不错的选择。 163 | 164 | ``` 165 | // 我们简单做个对比// 要描述如下JSON数据{"id":1,"firstName":"Chris","lastName":"Richardson","email":[{"type":"PROFESSIONAL","email":"crichardson@email.com"}]}# 使用JSON序列化后的数据大小为118byte7b226964223a312c2266697273744e616d65223a224368726973222c226c6173744e616d65223a2252696368617264736f6e222c22656d61696c223a5b7b22747970
65223a2250524f46455353494f4e414c222c22656d61696c223a226372696368617264736f6e40656d61696c2e636f6d227d5d7d# 而使用ProtoBuf序列化后的数据大小为48byte0801120543687269731a0a52696368617264736f6e2a190a156372696368617264736f6e40656d61696c2e636f6d1001 166 | ``` 167 | 168 | > 从序列化/反序列化速度角度,与XML、JSON相比,ProtoBuf序列化/反序列化的速度更快,比XML要快20-100倍。 169 | > \2. 支持跨平台、多语言 170 | > ProtoBuf是平台无关的,无论是Android与PC,还是C#与Java都可以利用ProtoBuf进行无障碍通讯。 171 | > proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#。 172 | > \3. 扩展性、兼容性好 173 | > 具有向后兼容的特性,更新数据结构以后,老版本依旧可以兼容,这也是ProtoBuf诞生之初被寄予解决的问题。因为编译器对不识别的新增字段会跳过不处理。 174 | > \4. 使用简单 175 | > ProtoBuf 提供了一套编译工具,可以自动生成序列化、反序列化的样板代码,这样开发者只要关注业务数据idl,简化了编码解码工作以及多语言交互的复杂度。 176 | 177 | **缺点:** 178 | 179 | > 可读性差,缺乏自描述 180 | > XML,JSON是自描述的,而ProtoBuf则不是。 181 | > ProtoBuf是二进制协议,编码后的数据可读性差,如果没有idl文件,就无法理解二进制数据流,对调试不友好。 182 | 183 | 不过Charles已经支持ProtoBuf协议,导入数据的描述文件即可。 184 | 185 | 此外,由于没有idl文件无法解析二进制数据流,ProtoBuf在一定程度上可以保护数据,提升核心数据被破解的门槛,降低核心数据被盗爬的风险。 186 | 187 | 原文链接:https://zhuanlan.zhihu.com/p/407874981 188 | 189 | 作者:[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10) -------------------------------------------------------------------------------- /C++博文/【NO.428】最强阿里巴巴历年经典面试题汇总:C++研发岗.md: -------------------------------------------------------------------------------- 1 | # 【NO.428】最强阿里巴巴历年经典面试题汇总:C++研发岗 2 | 3 | ![img](https://pic1.zhimg.com/80/v2-8c0605f70d3c3cc20b6a6407525943d0_720w.webp) 4 | 5 | (1)、B树、存储模型 6 | 7 | (2)、字典树构造及其优化与应用 8 | 9 | (3)、持久化数据结构,序列化与反序列化时机(4)、在无序数组中找最大的K个数? 10 | 11 | (4)、大规模文本文件,全是单词,求前10词频的单词 12 | 13 | (5)、堆排序与其在求10词频问题中的应用 14 | 15 | (6)、字典树与其在统计词频上的应用 16 | 17 | (7)、红黑树的特性与其在C++ STL中的应用 18 | 19 | (8)、红黑树的调整 20 | 21 | (9)、贪心算法与其弊端 22 | 23 | (10)、能取得全局最优解的算法 24 | 25 | (11)、动态规划的原理与本质 26 | 27 | (12)、01背包问题的详细解释 28 | 29 | (13)、进程间通信方式 30 | 31 | (14)、数据库中join的类型与区别 32 | 33 | (15)、数据库的ACID 34 | 35 | (16)、实现bitmap数据结构,包括数据的存储与插入方式 36 | 37 | (17)、实现unordered_map,键为string,value不限 38 | 39 | (18)、实现unordered_map过程中的冲突解决办法 40 | 41 | (19)、一串int型整数存放磁盘上的压缩存储方式,包括写入与读取及内存无法一次性读取时的解决办法 42 | 43 | (20)、对Java的了解 44 | 45 | (21)、Bloom过滤器处理大规模问题时的持久化,包括内存大小受限、磁盘换入换出问题 46 | 47 | (22)、线程池的了解、优点、调度处理方式和保护任务队列的方式 48 | 49 | (23)、对象复用的了解 50 | 51 | (24)、零拷贝的了解 52 | 53 | (25)、Linux的I/O模型 54 | 55 | (26)、异步I/O的详细解释 56 | 57 | (27)、线程池对线程的管理方式,包括初始化线程的方法、线程创建后的管理、指派任务的方式 58 | 59 | (28)、同步I/O与异步I/O的区别 60 | 61 | (29)、Direct I/O 和其与异步I/O的区别 62 | 63 | (30)、Linux内核如何调用Direct I/O 64 | 65 | (31)、Bloom过滤器的优点与原理 66 | 67 | (32)、字符串hash成状态位的具体实现方式 68 | 69 | (33)、hash函数如何保证冲突最小 70 | 71 | (34)、文件读写使用的系统调用 72 | 73 | (35)、文件读写中涉及的磁盘缓冲区与其手动flush问题 74 | 75 | (36)、数据库join的具体含义 76 | 77 | (37)、struct与class的区别 78 | 79 | (38)、STL库的介绍 80 | 81 | (39)、vector使用的注意点及其原因 82 | 83 | (40)、频繁对vector调用push_back()对性能的影响和原因 84 | 85 | (41)、vector重新分配内存的大小与方式 86 | 87 | (42)、hashmap的实现方式 88 | 89 | (43)、map的实现方式 90 | 91 | (44)、C++虚函数的具体实现原理 92 | 93 | (45)、实现编译器处理虚函数表应该如何处理 94 | 95 | (46)、析构函数一般写成虚函数的原因 96 | 97 | (47)、解释哲学家进餐问题 98 | 99 | (48)、描述银行家算法 100 | 101 | (49)、实现一种算法解决哲学家进餐问题 102 | 103 | (50)、大数量整数的去重问题 104 | 105 | (51)、如果用bitmap解决大数量整数去重问题,计算当全为int型整数时需要消耗的内存 106 | 107 | (52)、算法题:环形公路上加油站算法问题 108 | 109 | 现有一圆环形路,路上有n个加油站,第i个加油站储存有N[i]升容量的油,与下一个加油站之间有一定的距离g[i],一汽车初始无油,假设该车每公里消耗1升油,请问该车从哪个加油站出发可以绕该环形路行驶一圈。 110 | 111 | (53)、多个服务器通信,线程池的设定 112 | 113 | (54)、哈希表的冲突解决方式 114 | 115 | (55)、哈希表在桶固定的情况下,时间复杂度。怎么优化? 116 | 117 | (56)、多线程中哈希表保证线程安全 118 | 119 | (57)、哈希表特别大,桶特别多的时候怎么加锁 120 | 121 | (58)、C语言变量存放位置 122 | 123 | (59)、栈上的分配内存快还是堆上快 124 | 125 | (60)、http的长连接和短连接是什么,各有什么优缺点,然后使用场景 126 | 127 | (61)、在一个浏览器里面输入一个网址,后回车,在这后面发生了什么? 128 | 129 | (62)、进程线程的区别,多进程与多线程的区别 130 | 131 | (63)、什么是生产者消费者模型?如果一个人洗碗,另一个人马上用碗,是生产者消费者模型吗? 132 | 133 | (64)、GET/POST的区别,GET/POST的安全性问题,假如你来实现,你怎么实现GET/POST的安全性 134 | 135 | (65)、你做服务器压力测试时,用什么测试,如何配置参数,吞吐量大小,并发量大小 136 | 137 | (66)、类似Nginx这种web服务器是用什么数据结构实现定时器事件的,四叉堆知道是什么吗,与二叉堆有什么区别? 138 | 139 | (67)、动态规划与贪心算法的区别,什么情况下,动态规划可以转换为贪心算法 140 | 141 | (68)、说一下快排,快排是稳定的吗?为什么?哪些排序算法稳定?哪些不稳定? 142 | 143 | (69)、数据库有哪些索引,你知道哪些索引引擎,这些索引引擎有什么区别 144 | 145 | (70)、epoll与select的区别,epoll在什么情况下吞吐率比较高? 146 | 147 | (71)、非阻塞与异步的区别? 148 | 149 | (72)、HTTP1.0和HTTP1.1的区别,服务器端如何判断是长连接还是短连接? 150 | 151 | (73)、HTTP2.0的 新特性,它是如何实现共用一个长连接? 152 | 153 | (74)、tcp如何连接到服务器,你如何判断tcp连接到服务器,你服务器的输入是什么? 154 | 155 | (75)、epoll的底层实现 156 | 157 | **完整的linux c/c++高级开发技术路线图** 158 | 159 | ![img](https://pic2.zhimg.com/80/v2-dd61a798de3ebe741df54e9eb6da1a7d_720w.webp) 160 | 161 | ![img](https://pic2.zhimg.com/80/v2-b7bf75892f81a3ee5a9ad3b9c0f9f4e1_720w.webp) 162 | 163 | ![img](https://pic1.zhimg.com/80/v2-6a3a9de5f37c74528accdb5b84728034_720w.webp) 164 | 165 | ![img](https://pic4.zhimg.com/80/v2-522e78c886f5121c08990e57e9cd2b3b_720w.webp) 166 | 167 | ![img](https://pic3.zhimg.com/80/v2-82b0ac03ee96fa62235d85bbc6fbb242_720w.webp) 168 | 169 | ![img](https://pic2.zhimg.com/80/v2-d84edf93cf10529900617d85c19eda49_720w.webp) 170 | 171 | ![img](https://pic4.zhimg.com/80/v2-50cc437d64513556a447ad9b64591c83_720w.webp) 172 | 173 | 原文地址:https://zhuanlan.zhihu.com/p/372020083 174 | 175 | 作者:linux -------------------------------------------------------------------------------- /C++博文/【NO.511】Linux网络分析必备技能:tcpdump实战详解.md: -------------------------------------------------------------------------------- 1 | # 【NO.511】Linux网络分析必备技能:tcpdump实战详解 2 | 3 | `tcpdump`,它是 Linux 系统中特别有用的网络工具,通常用于故障诊断、网络分析,功能非常的强大。 4 | 5 | 相对于其它 Linux 工具而言,`tcpdump` 是复杂的。当然我也不推荐你去学习它的全部,学以致用,能够解决工作中的问题才是关键。 6 | 7 | 本文会从*应用场景*和*基础原理*出发,提供丰富的*实践案例*,让你快速的掌握 `tcpdump` 的核心使用方法,足以应对日常工作的需求。 8 | 9 | ## 1.**应用场景** 10 | 11 | 在日常工作中遇到的很多网络问题都可以通过 tcpdump 优雅的解决: 12 | 13 | *1.* 相信大多数同学都遇到过 SSH 连接服务器缓慢,通过 tcpdump 抓包,可以快速定位到具体原因,一般都是因为 DNS 解析速度太慢。 14 | 15 | *2.* 当我们工程师与用户面对网络问题争执不下时,通过 tcpdump 抓包,可以快速定位故障原因,轻松甩锅,毫无压力。 16 | 17 | *3.* 当我们新开发的网络程序,没有按照预期工作时,通过 tcpdump 收集相关数据包,从包层面分析具体原因,让问题迎刃而解。 18 | 19 | *4.* 当我们的网络程序性能比较低时,通过 tcpdump 分析数据流特征,结合相关协议来进行网络参数优化,提高系统网络性能。 20 | 21 | *5.* 当我们学习网络协议时,通过 tcpdump 抓包,分析协议格式,帮助我们更直观、有效、快速的学习网络协议。 22 | 23 | 上述只是简单罗列几种常见的应用场景,而 tcpdump 在网络诊断、网络优化、协议学习方面,确实是一款非常强大的网络工具,只要存在网络问题的地方,总能看到它的身影。 24 | 25 | 熟练的运用 `tcpdump`,可以帮助我们解决工作中各种网络问题,下边我们先简单学习下它的工作原理。 26 | 27 | ## 2.**工作原理** 28 | 29 | tcpdump 是 Linux 系统中非常有用的网络工具,运行在用户态,本质上是通过调用 `libpcap` 库的各种 `api` 来实现数据包的抓取功能。 30 | 31 | ![img](https://pic4.zhimg.com/80/v2-ffd675508f1cee9bff0a0e30fede9dbb_720w.webp) 32 | 33 | 通过上图,我们可以很直观的看到,数据包到达网卡后,经过数据包过滤器(BPF)筛选后,拷贝至用户态的 tcpdump 程序,以供 tcpdump 工具进行后续的处理工作,输出或保存到 pcap 文件。 34 | 35 | 数据包过滤器(BPF)主要作用,就是根据用户输入的过滤规则,只将用户关心的数据包拷贝至 tcpdump,这样能够减少不必要的数据包拷贝,降低抓包带来的性能损耗。 36 | 37 | **思考**:这里分享一个真实的面试题 38 | 39 | > 面试官:如果某些数据包被 iptables 封禁,是否可以通过 tcpdump 抓到包? 40 | 41 | 通过上图,我们可以很轻易的回答此问题。 42 | 43 | 因为 Linux 系统中 `netfilter` 是工作在协议栈阶段的,tcpdump 的过滤器(BPF)工作位置在协议栈之前,所以当然是可以抓到包了! 44 | 45 | 我们理解了 tcpdump 基本原理之后,下边直接进入实战! 46 | 47 | ## 3.**实战:基础用法** 48 | 49 | 我们先通过几个简单的示例来介绍 tcpdump 基本用法。 50 | 51 | *1.* 不加任何参数,默认情况下将抓取第一个非 lo 网卡上所有的数据包 52 | 53 | ```text 54 | $ tcpdump 55 | ``` 56 | 57 | *2.* 抓取 eth0 网卡上的所有数据包 58 | 59 | ```text 60 | $ tcpdump -i eth0 61 | ``` 62 | 63 | *3.* 抓包时指定 `-n` 选项,不解析主机和端口名。这个参数很关键,会影响抓包的性能,一般抓包时都需要指定该选项。 64 | 65 | ```text 66 | $ tcpdump -n -i eth0 67 | ``` 68 | 69 | *4.* 抓取指定主机 `192.168.1.100` 的所有数据包 70 | 71 | ```text 72 | $ tcpdump -ni eth0 host 192.168.1.100 73 | ``` 74 | 75 | *5.* 抓取指定主机 `10.1.1.2` 发送的数据包 76 | 77 | ```text 78 | $ tcpdump -ni eth0 src host 10.1.1.2 79 | ``` 80 | 81 | *6.* 抓取发送给 `10.1.1.2` 的所有数据包 82 | 83 | ```text 84 | $ tcpdump -ni eth0 dst host 10.1.1.2 85 | ``` 86 | 87 | *7.* 抓取 eth0 网卡上发往指定主机的数据包,抓到 10 个包就停止,这个参数也比较常用 88 | 89 | ```text 90 | $ tcpdump -ni eth0 -c 10 dst host 192.168.1.200 91 | ``` 92 | 93 | *8.* 抓取 eth0 网卡上所有 SSH 请求数据包,SSH 默认端口是 22 94 | 95 | ```text 96 | $ tcpdump -ni eth0 dst port 22 97 | ``` 98 | 99 | *9.* 抓取 eth0 网卡上 5 个 ping 数据包 100 | 101 | ```text 102 | $ tcpdump -ni eth0 -c 5 icmp 103 | ``` 104 | 105 | *10.* 抓取 eth0 网卡上所有的 arp 数据包 106 | 107 | ```text 108 | $ tcpdump -ni eth0 arp 109 | ``` 110 | 111 | *11.* 使用十六进制输出,当你想检查数据包内容是否有问题时,十六进制输出会很有帮助。 112 | 113 | ```text 114 | $ tcpdump -ni eth0 -c 1 arp -X 115 | listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 116 | 12:13:31.602995 ARP, Request who-has 172.17.92.133 tell 172.17.95.253, length 28 117 | 0x0000: 0001 0800 0604 0001 eeff ffff ffff ac11 ................ 118 | 0x0010: 5ffd 0000 0000 0000 ac11 5c85 _.........\. 119 | ``` 120 | 121 | *12.* 只抓取 eth0 网卡上 IPv6 的流量 122 | 123 | ```text 124 | $ tcpdump -ni eth0 ip6 125 | ``` 126 | 127 | *13.* 抓取指定端口范围的流量 128 | 129 | ```text 130 | $ tcpdump -ni eth0 portrange 80-9000 131 | ``` 132 | 133 | *14.* 抓取指定网段的流量 134 | 135 | ```text 136 | $ tcpdump -ni eth0 net 192.168.1.0/24 137 | ``` 138 | 139 | 140 | 141 | ## **4.实战:高级进阶** 142 | 143 | tcpdump 强大的功能和灵活的策略,主要体现在过滤器(BPF)强大的表达式组合能力。 144 | 145 | 本节主要分享一些常见的所谓高级用法,希望读者能够举一反三,根据自己实际需求,来灵活使用它。 146 | 147 | *1.* 抓取指定客户端访问 ssh 的数据包 148 | 149 | ```text 150 | $ tcpdump -ni eth0 src 192.168.1.100 and dst port 22 151 | ``` 152 | 153 | *2.* 抓取从某个网段来,到某个网段去的流量 154 | 155 | ```text 156 | $ tcpdump -ni eth0 src net 192.168.1.0/16 and dst net 10.0.0.0/8 or 172.16.0.0/16 157 | ``` 158 | 159 | *3.* 抓取来自某个主机,发往非 ssh 端口的流量 160 | 161 | ```text 162 | $ tcpdump -ni eth0 src 10.0.2.4 and not dst port 22 163 | ``` 164 | 165 | *4.* 当构建复杂查询的时候,你可能需要使用引号,单引号告诉 tcpdump 忽略特定的特殊字符,这里的 `()` 就是特殊符号,如果不用引号的话,就需要使用转义字符 166 | 167 | ```text 168 | $ tcpdump -ni eth0 'src 10.0.2.4 and (dst port 3389 or 22)' 169 | ``` 170 | 171 | *5.* 基于包大小进行筛选,如果你正在查看特定的包大小,可以使用这个参数 172 | 173 | 小于等于 64 字节: 174 | 175 | ```text 176 | $ tcpdump -ni less 64 177 | ``` 178 | 179 | 大于等于 64 字节: 180 | 181 | ```text 182 | $ tcpdump -ni eth0 greater 64 183 | ``` 184 | 185 | 等于 64 字节: 186 | 187 | ```text 188 | $ tcpdump -ni eth0 length == 64 189 | ``` 190 | 191 | *6.* 过滤 TCP 特殊标记的数据包 192 | 193 | 抓取某主机发送的 `RST` 数据包: 194 | 195 | ```text 196 | $ tcpdump -ni eth0 src host 192.168.1.100 and 'tcp[tcpflags] & (tcp-rst) != 0' 197 | ``` 198 | 199 | 抓取某主机发送的 `SYN` 数据包: 200 | 201 | ```text 202 | $ tcpdump -ni eth0 src host 192.168.1.100 and 'tcp[tcpflags] & (tcp-syn) != 0' 203 | ``` 204 | 205 | 抓取某主机发送的 `FIN` 数据包: 206 | 207 | ```text 208 | $ tcpdump -ni eth0 src host 192.168.1.100 and 'tcp[tcpflags] & (tcp-fin) != 0' 209 | ``` 210 | 211 | 抓取 TCP 连接中的 `SYN` 或 `FIN` 包 212 | 213 | ```text 214 | $ tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0' 215 | ``` 216 | 217 | *7.* 抓取所有非 ping 类型的 `ICMP` 包 218 | 219 | ```text 220 | $ tcpdump 'icmp[icmptype] != icmp-echo and icmp[icmptype] != icmp-echoreply' 221 | ``` 222 | 223 | *8.* 抓取端口是 80,网络层协议为 IPv4, 并且含有数据,而不是 SYN、FIN 以及 ACK 等不含数据的数据包 224 | 225 | ```text 226 | $ tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' 227 | ``` 228 | 229 | 解释一下这个复杂的表达式,具体含义就是,整个 IP 数据包长度减去 IP 头长度,再减去 TCP 头的长度,结果不为 0,就表示数据包有 `data`,如果还不是很理解,需要自行补一下 `tcp/ip` 协议 230 | 231 | *9.* 抓取 HTTP 报文,`0x4754` 是 `GET` 前两字符的值,`0x4854` 是 `HTTP` 前两个字符的值 232 | 233 | ```text 234 | $ tcpdump -ni eth0 'tcp[20:2]=0x4745 or tcp[20:2]=0x4854' 235 | ``` 236 | 237 | ## 5.**常用选项** 238 | 239 | 通过上述的实战案例,相信大家已经掌握的 `tcpdump` 基本用法,在这里来详细总结一下常用的选项参数。 240 | 241 | **(一)基础选项** 242 | 243 | - `-i`:指定接口 244 | - `-D`:列出可用于抓包的接口 245 | - `-s`:指定数据包抓取的长度 246 | - `-c`:指定要抓取的数据包的数量 247 | - `-w`:将抓包数据保存在文件中 248 | - `-r`:从文件中读取数据 249 | - `-C`:指定文件大小,与 `-w` 配合使用 250 | - `-F`:从文件中读取抓包的表达式 251 | - `-n`:不解析主机和端口号,这个参数很重要,一般都需要加上 252 | - `-P`:指定要抓取的包是流入还是流出的包,可以指定的值 `in`、`out`、`inout` 253 | 254 | **(二)输出选项** 255 | 256 | - `-e`:输出信息中包含数据链路层头部信息 257 | - `-t`:显示时间戳,`tttt` 显示更详细的时间 258 | - `-X`:显示十六进制格式 259 | - `-v`:显示详细的报文信息,尝试 `-vvv`,`v` 越多显示越详细 260 | 261 | ## 6.**过滤表达式** 262 | 263 | tcpdump 强大的功能和灵活的策略,主要体现在过滤器(BPF)强大的表达式组合能力。 264 | 265 | **(一)操作对象** 266 | 267 | 表达式中可以操作的对象有如下几种: 268 | 269 | - `type`,表示对象的类型,比如:`host`、`net`、`port`、`portrange`,如果不指定 type 的话,默认是 host 270 | - `dir`:表示传输的方向,可取的方式为:`src`、`dst`。 271 | - `proto`:表示协议,可选的协议有:`ether`、`ip`、`ip6`、`arp`、`icmp`、`tcp`、`udp`。 272 | 273 | **(二)条件组合** 274 | 275 | 表达对象之间还可以通过关键字 `and`、`or`、`not` 进行连接,组成功能更强大的表达式。 276 | 277 | - `or`:表示或操作 278 | - `and`:表示与操作 279 | - `not`:表示非操作 280 | 281 | 建议看到这里后,再回头去看实战篇章的示例,相信必定会有更深的理解。如果是这样,那就达到了我预期的效果了! 282 | 283 | ## 7.**经验** 284 | 285 | 到这里就不再加新知识点了,分享一些工作中总结的经验: 286 | 287 | *1.* 我们要知道 `tcpdump` 不是万能药,并不能解决所有的网络问题。 288 | 289 | *2.* 在高流量场景下,抓包可能会影响系统性能,如果是在生产环境,请谨慎使用! 290 | 291 | *3.* 在高流量场景下,`tcpdump` 并不适合做流量统计,如果需要,可以使用交换机镜像的方式去分析统计。 292 | 293 | *4.* 在 Linux 上使用 `tcpdump` 抓包,结合 `wireshark` 工具进行数据分析,能事半功倍。 294 | 295 | *5.* 抓包时,尽可能不要使用 `any` 接口来抓包。 296 | 297 | *6.* 抓包时,尽可能指定详细的数据包过滤表达式,减少无用数据包的拷贝。 298 | 299 | *7.* 抓包时,尽量指定 `-n` 选项,减少解析主机和端口带来的性能开销。 300 | 301 | ## 8.**最后** 302 | 303 | 通过上述内容,我们知道 tcpdump 是一款功能强大的故障诊断、网络分析工具。在我们的日常工作中,遇到的网络问题总是能够通过 tcpdump 来解决。 304 | 305 | 不过 tcpdump 相对于其它 Linux 命令来说,会复杂很多,但鉴于它强大功能的诱惑力,我们多花一些时间是值得的。要想很好地掌握 tcpdump,需要对网络报文(`TCP/IP`协议)有一定的了解。 306 | 307 | 当然,对于简单的使用来说,只要有网络基础概念就行,掌握了 tcpdump 常用方法,就足以应付工作中大部分网络相关的疑难杂症了。 308 | 309 | 原文地址:https://zhuanlan.zhihu.com/p/413937603 310 | 311 | 作者:linux -------------------------------------------------------------------------------- /C++博文/【NO.593】一道腾讯面试题目:没有listen,能否建立TCP连接.md: -------------------------------------------------------------------------------- 1 | # 【NO.593】一道腾讯面试题目:没有listen,能否建立TCP连接 2 | 3 | TCP与UDP最大的不同,就是有连接的概念,而连接的建立是由内核完成的。系统调用listen,就是为了告诉内核,它要处理发给这个TCP端口的连接请求。所以对于这个题目,最直接的想法就是由应用层自己负责TCP的连接。为了能够收到TCP的握手数据包,可以尝试使用原始套接字来接收IP报文,这样就可以在应用层替代内核做TCP的三次握手了。这个想法不错,可惜现实比较残酷。 4 | 5 | 当没有对于TCP 套接字处于listen状态时,使用raw socket处理握手报文时,即使收到了syn报文并给对端发送了syn+ack报文,也无法完成连接。因为内核一般会提前发送RST中断该连接。七年前,我当时只知道这个结果。现在呢,可以明确的知道为什么内核会发送RST报文,中断连接。内核在ip_local_deliver_finish先将报文复制一份给原始套接字,然后会继续后面的处理,进入tcp的接收函数tcp_v4_rcv。在这个函数中,要进行套接字的查找。 6 | 7 | ![img](https://pic2.zhimg.com/80/v2-8144d1faa8cbf55b27ef593dfcfad341_720w.webp) 8 | 9 | 因为没有监听的tcp套接字,自然无法找到对应的套接字。于是跳转到no_tcp_socket。 10 | 11 | ![img](https://pic4.zhimg.com/80/v2-cf9f42c760a1d4d22772de4d82b7f917_720w.webp) 12 | 13 | 在这个错误处理中,只要数据包skb的校验和没错,内核就会调用tcp_v4_send_reset发送RST中止这个连接。因此,这个单独使用raw socket的方案是行不通的。我们需要找到一种方法,禁止内核处理该TCP报文。这时,可以使用iptables的NFQUEUE,在网络层将数据包取到应用层,并回复syn+ack,同时在reinject时通知内核该数据包被“偷”了,即NF_STOLEN。这样内核就不会继续处理该数据包skb了。虽然当时我没有继续实验尝试,但理论上,通过这个IPtables的NFQUEUE+NFSTOLEN的方案,是可以实现“没有listen,建立TCP连接”的。 14 | 15 | 可惜,在与那位同学的讨论中,腾讯面试题目的本意不是这个意思,而是对于普通的TCP套接字来说,如果没有listen调用,是否可以创建连接。即使限定了条件,答案依然是肯定的。只不过限定了条件之后,我们需要确定2个事情: 16 | 17 | 1. 与前面类似,如何避免内核发送RST。在不能使用iptable的前提下,这意味着在tcp_v4_rcv中,要能够找到对应的套接字。 18 | 2. 没有listen状态的套接字,内核是否能够完成TCP的三次握手呢? 19 | 20 | 确定第一个问题,比较简单。只需要对三次握手深入的思考一下,就可以得到答案。在正常的三次握手中,当服务端回复syn+ack时,客户端实际上也没有处于listen状态的套接字,但却可以完成三次握手。这意味着,客户端进行connect调用后,该套接字一定被加入到某个表中,并可以被匹配到。跟踪内核源码tcp_v4_connect->inet_hash_connect->__inet_check_established,可以看到当调用connect时,对应的套接字就被加入了全局的tcp已连接的表中,即tcp_hashinfo.ehash中。对应的匹配TCP套接字过程,如下__inet_lookup_skb->__inet_lookup 21 | 22 | ![img](https://pic1.zhimg.com/80/v2-67c79f1cf00f61fa7dd93e1c86448f4c_720w.webp) 23 | 24 | 内核是先在已经连接的表中查找,再进行listen表的查找。对于客户端来说,syn+ack报文必然可以在已连接表中匹配上对应的套接字。那么,对于本题目来说,要想两端都可以找到套接字,就要求在报文到达前,两端都调用了connect。也就是说,当两端同时调用connect时,两端的syn包就都可以匹配上本地的套接字。 25 | 26 | 接下来只需要确定对于客户端套接字来说,收到syn报文,是否可以正常处理。tcp_rcv_synsent_state_process函数是TCP套接字处于synsent状态(即调用了connect)的处理函数。下面是其一部分实现代码: 27 | 28 | ![img](https://pic2.zhimg.com/80/v2-84b12d0dfd1238480d8643fd70d74c01_720w.webp) 29 | 30 | ![img](https://pic2.zhimg.com/80/v2-eeb5afdef2e1acb5724ea54eb7d6a889_720w.webp) 31 | 32 | 从上,可以得出,对于处于synsent状态的套接字来说,如果收到了syn报文,则会正常回复synack,完成TCP三次握手。 33 | 34 | 对于腾讯的这道面试题目来说,其答案就是当两端同时发起connect调用时,即使没有listen调用,也可以成功创建TCP连接。 35 | 36 | 如果去掉“两端”的限制,还有一个答案就是,TCP套接字可以connect它本身bind的地址和端口,也可以达成要求。下面是测试代码,实现了一个TCP套接字成功连接自己,并发送消息。 37 | 38 | ```text 39 | #include /* See NOTES */ 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #define LOCAL_IP_ADDR (0x7F000001) 49 | #define LOCAL_TCP_PORT (34567) 50 | 51 | int main(void) 52 | { 53 | struct sockaddr_in local, peer; 54 | int ret; 55 | char buf[128]; 56 | int sock = socket(AF_INET, SOCK_STREAM, 0); 57 | 58 | memset(&local, 0, sizeof(local)); 59 | memset(&peer, 0, sizeof(peer)); 60 | 61 | local.sin_family = AF_INET; 62 | local.sin_port = htons(LOCAL_TCP_PORT); 63 | local.sin_addr.s_addr = htonl(LOCAL_IP_ADDR); 64 | 65 | peer = local; 66 | 67 | int flag = 1; 68 | ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); 69 | if (ret == -1) { 70 | printf("Fail to setsocket SO_REUSEADDR: %s\n", strerror(errno)); 71 | exit(1); 72 | } 73 | 74 | ret = bind(sock, (const struct sockaddr *)&local, sizeof(local)); 75 | ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer)); 76 | 77 | if (ret) { 78 | printf("Fail to connect myself: %s\n", strerror(errno)); 79 | exit(1); 80 | } 81 | printf("Connect to myself successfully\n"); 82 | 83 | strcpy(buf, "Hello, myself~"); 84 | send(sock, buf, strlen(buf), 0); 85 | 86 | memset(buf, 0, sizeof(buf)); 87 | recv(sock, buf, sizeof(buf), 0); 88 | 89 | printf("Recv the msg: %s\n", buf); 90 | close(sock); 91 | return 0; 92 | } 93 | ``` 94 | 95 | 其连接截图如下: 96 | 97 | ![img](https://pic4.zhimg.com/80/v2-0e6f0cb40fc5878c214a6397169aa5b3_720w.webp) 98 | 99 | 从截图中,可以看到TCP套接字成功的“连接”了自己,并发送和接收了数据包围。netstat的输出更证明了TCP的两端地址和端口是完全相同的。 100 | 101 | 原文地址:https://zhuanlan.zhihu.com/p/506042622 102 | 103 | 作者:CPP后端技术 -------------------------------------------------------------------------------- /C++博文/【NO.99】Linux服务器检查性能瓶颈.md: -------------------------------------------------------------------------------- 1 | # 【NO.99】Linux服务器检查性能瓶颈 2 | 3 | ## 1.**概述** 4 | 5 | 如果Linux服务器突然访问卡顿变慢,负载暴增,如何在最短时间内找出Linux性能问题所在? 6 | 7 | 通过执行以下命令,可以在1分钟内对系统资源使用情况有个大致的了解。 8 | 9 | - uptime 10 | - dmesg | tail 11 | - vmstat 1 12 | - mpstat -P ALL 1 13 | - pidstat 1 14 | - iostat -xz 1 15 | - free -m 16 | - sar -n DEV 1 17 | - sar -n TCP,ETCP 1 18 | - top 19 | 20 | 其中一些命令需要安装sysstat包,有一些由procps包提供。这些命令的输出,有助于快速定位性能瓶颈,检查出所有资源(CPU、内存、磁盘IO等)的利用率(utilization)、饱和度(saturation)和错误(error)度量,也就是所谓的USE方法。 21 | 22 | 下面我们来逐一介绍下这些命令,有关这些命令更多的参数和说明,请参照命令的手册。 23 | 24 | ## 2.**平均负载** 25 | 26 | ```text 27 | uptime 28 | ``` 29 | 30 | 结果 31 | 32 | ![img](https://pic4.zhimg.com/80/v2-765adaf45d13f67cde79adbc913baacf_720w.webp) 33 | 34 | 这个命令可以快速查看机器的负载情况。在Linux系统中,这些数据表示等待CPU资源的进程和阻塞在不可中断IO进程(进程状态为D)的数量。这些数据可以让我们对系统资源使用有一个宏观的了解。 35 | 36 | 命令的输出分别表示1分钟、5分钟、15分钟的平均负载情况。通过这三个数据,可以了解服务器负载是在趋于紧张还是区域缓解。如果1分钟平均负载很 高,而15分钟平均负载很低,说明服务器正在命令高负载情况,需要进一步排查CPU资源都消耗在了哪里。反之,如果15分钟平均负载很高,1分钟平均负载 较低,则有可能是CPU资源紧张时刻已经过去。 37 | 38 | 上面例子中的输出,可以看见最近1分钟的平均负载非常高,且远高于最近15分钟负载,因此我们需要继续排查当前系统中有什么进程消耗了大量的资源。可以通过下文将会介绍的vmstat、mpstat等命令进一步排查。 39 | 40 | ## 3.**系统核心指标** 41 | 42 | ```text 43 | vmstat 1 44 | ``` 45 | 46 | 结果 47 | 48 | ![img](https://pic2.zhimg.com/80/v2-55685bfcb32dd0db63144f7a62e63985_720w.webp) 49 | 50 | vmstat 命令,每行会输出一些系统核心指标,这些指标可以让我们更详细的了解系统状态。后面跟的参数1,表示每秒输出一次统计信息,表头提示了每一列的含义,这几介绍一些和性能调优相关的列: 51 | 52 | - r:等待在CPU资源的进程数。这个数据比平均负载更加能够体现CPU负载情况,数据中不包含等待IO的进程。如果这个数值大于机器CPU核数,那么机器的CPU资源已经饱和。 53 | - free:系统可用内存数(以千字节为单位),如果剩余内存不足,也会导致系统性能问题。下文介绍到的free命令,可以更详细的了解系统内存的使用情况。 54 | - si, so:交换区写入和读取的数量。如果这个数据不为0,说明系统已经在使用交换区(swap),机器物理内存已经不足。 55 | - us, sy, id, wa, st:这些都代表了CPU时间的消耗,它们分别表示用户时间(user)、系统(内核)时间(sys)、空闲时间(idle)、IO等待时间(wait)和被偷走的时间(stolen,一般被其他虚拟机消耗)。 56 | 57 | 上述这些CPU时间,可以让我们很快了解CPU是否出于繁忙状态。 58 | 59 | 一般情况下,如果用户时间和系统时间相加非常大,CPU出于忙于执行指令。 60 | 61 | 如果IO等待时间很长,那么系统的瓶颈可能在磁盘IO。 62 | 63 | 如果大量CPU时间消耗在用户态,也就是用户应用程序消耗了CPU时间。这不一定是性能问题,需要结合r队列,一起分析。 64 | 65 | ## 4.**CPU占用情况-每个核心** 66 | 67 | ```text 68 | mpstat -P ALL 1 69 | ``` 70 | 71 | 结果 72 | 73 | ![img](https://pic1.zhimg.com/80/v2-1352123f501c3bcba8aefc1956022a5c_720w.webp) 74 | 75 | 该命令可以显示每个CPU的占用情况,如果有一个CPU占用率特别高,那么有可能是一个单线程应用程序引起的。 76 | 77 | ## 5.**CPU占用情况-每个进程** 78 | 79 | ```text 80 | pidstat 1 81 | ``` 82 | 83 | 结果 84 | 85 | ![img](https://pic1.zhimg.com/80/v2-c34442a841f5278222d85f8b3093a234_720w.webp) 86 | 87 | pidstat命令输出进程的CPU占用率,该命令会持续输出,并且不会覆盖之前的数据,可以方便观察系统动态。 88 | 89 | 如上的输出,可以看见两个JAVA进程占用了将近1600%的CPU时间,既消耗了大约16个CPU核心的运算资源。 90 | 91 | ## 6.**磁盘IO情况** 92 | 93 | ```text 94 | iostat -xz 1 95 | ``` 96 | 97 | 结果 98 | 99 | ![img](https://pic1.zhimg.com/80/v2-74131a6753a089832902e17904f49bdc_720w.webp) 100 | 101 | iostat命令主要用于查看机器磁盘IO情况。该命令输出的列,主要含义是: 102 | 103 | - r/s, w/s, rkB/s, wkB/s:分别表示每秒读写次数和每秒读写数据量(千字节)。读写量过大,可能会引起性能问题。 104 | - await:IO操作的平均等待时间,单位是毫秒。这是应用程序在和磁盘交互时,需要消耗的时间,包括IO等待和实际操作的耗时。如果这个数值过大,可能是硬件设备遇到了瓶颈或者出现故障。 105 | - avgqu-sz:向设备发出的请求平均数量。如果这个数值大于1,可能是硬件设备已经饱和(部分前端硬件设备支持并行写入)。 106 | - %util:设备利用率。这个数值表示设备的繁忙程度,经验值是如果超过60,可能会影响IO性能(可以参照IO操作平均等待时间)。如果到达100%,说明硬件设备已经饱和。 107 | 108 | 如果显示的是逻辑设备的数据,那么设备利用率不代表后端实际的硬件设备已经饱和。值得注意的是,即使IO性能不理想,也不一定意味这应用程序性能会不好,可以利用诸如预读取、写缓存等策略提升应用性能。 109 | 110 | ## 7.**内存情况** 111 | 112 | ```text 113 | free -m 114 | ``` 115 | 116 | 结果 117 | 118 | ![img](https://pic4.zhimg.com/80/v2-c696ca705ffe07c561315b563263c17b_720w.webp) 119 | 120 | free命令可以查看系统内存的使用情况,-m参数表示按照兆字节展示。最后两列分别表示用于IO缓存的内存数,和用于文件系统页缓存的内存数。需 要注意的是,第二行-/+ buffers/cache,看上去缓存占用了大量内存空间。这是Linux系统的内存使用策略,尽可能的利用内存,如果应用程序需要内存,这部分内存会 立即被回收并分配给应用程序。因此,这部分内存一般也被当成是可用内存。 121 | 122 | 如果可用内存非常少,系统可能会动用交换区(如果配置了的话),这样会增加IO开销(可以在iostat命令中提现),降低系统性能。 123 | 124 | ## **8.网络设备的吞吐率** 125 | 126 | ```text 127 | sar -n DEV 1 128 | ``` 129 | 130 | 结果 131 | 132 | ![img](https://pic1.zhimg.com/80/v2-497adddb31f7b761f37053205fe060b4_720w.webp) 133 | 134 | sar命令在这里可以查看网络设备的吞吐率。在排查性能问题时,可以通过网络设备的吞吐量,判断网络设备是否已经饱和。 135 | 136 | 如示例输出中,eth0网卡设备,吞吐率大概在22 Mbytes/s,既176 Mbits/sec,没有达到1Gbit/sec的硬件上限。 137 | 138 | ## 9.**TCP连接数** 139 | 140 | ```text 141 | sar -n TCP,ETCP 1 142 | ``` 143 | 144 | 结果 145 | 146 | ![img](https://pic2.zhimg.com/80/v2-a04ae32ac3edcd7f7c56a443aa3ba255_720w.webp) 147 | 148 | sar命令在这里用于查看TCP连接状态,其中包括: 149 | 150 | - active/s:每秒本地发起的TCP连接数,既通过connect调用创建的TCP连接; 151 | - passive/s:每秒远程发起的TCP连接数,即通过accept调用创建的TCP连接; 152 | - retrans/s:每秒TCP重传数量; 153 | 154 | TCP连接数可以用来判断性能问题是否由于建立了过多的连接,进一步可以判断是主动发起的连接,还是被动接受的连接。TCP重传可能是因为网络环境恶劣,或者服务器压力过大导致丢包。 155 | 156 | ## 10.**整体情况-TOP** 157 | 158 | ```text 159 | top 160 | ``` 161 | 162 | 结果 163 | 164 | ![img](https://pic3.zhimg.com/80/v2-fefd9a5f74294a1ead68fd19eed97e4e_720w.webp) 165 | 166 | top命令包含了前面好几个命令的检查的内容。 167 | 168 | 比如系统负载情况(uptime)、系统内存使用情况(free)、系统CPU使用情况 (vmstat)等。因此通过这个命令,可以相对全面的查看系统负载的来源。 169 | 170 | 同时,top命令支持排序,可以按照不同的列排序,方便查找出诸如内存占用最 多的进程、CPU占用率最高的进程等。 171 | 172 | 但是,top命令相对于下面的一些命令,输出是一个瞬间值,如果不持续盯着,可能会错过一些线索。这时可能需要暂停top命令刷新,来记录和比对数据。 173 | 174 | ## 11.**查看系统日志** 175 | 176 | ```text 177 | dmesg | tail 178 | ``` 179 | 180 | 该命令会输出系统日志的最后10行。 181 | 182 | 原文地址:https://zhuanlan.zhihu.com/p/567441428 183 | 184 | 作者:linux -------------------------------------------------------------------------------- /Dijkstral.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | 9 | vector pro(vector>& nodes,int n,int start,vector& path) 10 | { 11 | vector minDis(n,INT_MAX); 12 | //存储最近距离路径的前一个节点 13 | // vector path(n); 14 | // for(int i = 0;i < n;i++) 15 | // path[i] = i; 16 | 17 | vector isVisited(n,false); 18 | 19 | priority_queue,vector>,greater>> pq; 20 | minDis[start] = 0; 21 | pq.push({0,start}); 22 | 23 | while(!pq.empty()) 24 | { 25 | auto it = pq.top(); 26 | int curnode = it.second; 27 | pq.pop(); 28 | 29 | 30 | //之前已经访问过 31 | if(isVisited[curnode]) 32 | continue; 33 | 34 | isVisited[it.second] = true; 35 | for(int i = 0;i < n;i++) 36 | { 37 | if(i == curnode || nodes[curnode][i] == INT_MAX) 38 | continue; 39 | if(minDis[curnode] + nodes[curnode][i] < minDis[i]) 40 | { 41 | minDis[i] = minDis[curnode] + nodes[curnode][i]; 42 | pq.push({minDis[curnode] + nodes[curnode][i],i}); 43 | path[i] = curnode; 44 | } 45 | } 46 | } 47 | 48 | return minDis; 49 | } 50 | 51 | int main() 52 | { 53 | vector> nodes = {{0,2,INT_MAX,INT_MAX,1,INT_MAX},{2,0,INT_MAX,INT_MAX,INT_MAX,2},{INT_MAX,INT_MAX,0,4,INT_MAX,INT_MAX}, 54 | {INT_MAX,INT_MAX,4,0,3,INT_MAX},{1,INT_MAX,INT_MAX,3,0,INT_MAX},{INT_MAX,2,INT_MAX,INT_MAX,INT_MAX,0}}; 55 | 56 | vector path(6); 57 | 58 | 59 | vector minDis = pro(nodes,6,0,path); 60 | for(auto& it : minDis) 61 | cout << it << endl; 62 | system("pause"); 63 | return 0; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /Floyd.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limowang/Interview_algorithms/106ed62355b450d1a9c35f608326e10b33eb9caa/Floyd.cpp -------------------------------------------------------------------------------- /KReverse.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limowang/Interview_algorithms/106ed62355b450d1a9c35f608326e10b33eb9caa/KReverse.cpp -------------------------------------------------------------------------------- /LeastWaysOfMoney.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limowang/Interview_algorithms/106ed62355b450d1a9c35f608326e10b33eb9caa/LeastWaysOfMoney.cpp -------------------------------------------------------------------------------- /MedianNumOfTwoArrs.cpp: -------------------------------------------------------------------------------- 1 | //寻找两个正序数组的中位数 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | //两个长度相等的有序数组中查找中位数 9 | int pro(vector nums1,vector nums2) 10 | { 11 | int start1 = 0; 12 | int end1 = nums1.size() - 1; 13 | int start2 = 0; 14 | int end2 = nums2.size() - 1; 15 | int mid1 = 0; 16 | int mid2 = 0; 17 | 18 | while(start1 < end1) 19 | { 20 | mid1 = (start1 + end1) / 2; 21 | mid2 = (start2 + end2) / 2; 22 | int len = start1 - end1 + 1; 23 | 24 | if(nums1[mid1] > nums2[mid2]) 25 | { 26 | if(len % 2 == 0) 27 | { 28 | end1 = mid1; 29 | start2 = mid2 + 1; 30 | }else 31 | { 32 | end1 = mid1; 33 | start2 = mid2; 34 | } 35 | }else if(nums1[mid1] < nums2[mid2]) 36 | { 37 | if(len % 2 == 0) 38 | { 39 | end2 = mid2; 40 | start1 = mid1 + 1; 41 | }else 42 | { 43 | end2 = mid2; 44 | start1 = mid1; 45 | } 46 | }else 47 | { 48 | return nums1[mid1]; 49 | } 50 | } 51 | 52 | return min(nums1[start1],nums2[start2]); 53 | } 54 | 55 | //两个长度不等的数组找中位数 56 | int proNotEq(vector& nums1,vector& nums2) 57 | { 58 | vector sA = nums1.size() < nums2.size() ? nums1 : nums2; 59 | 60 | 61 | vector lA = sA == nums1 ? nums2 : nums1; 62 | int totalLen = sA.size() + lA.size(); 63 | 64 | if(lA[totalLen / 2 - sA.size() - 1] >= sA[sA.size() - 1]) 65 | return sA[sA.size() - 1]; 66 | else 67 | { 68 | vector tmp1 = sA; 69 | for(auto& it : tmp1) 70 | cout << it << endl; 71 | vector tmp2(lA.begin() + totalLen / 2 - sA.size(),lA.begin() + totalLen / 2); 72 | for(auto& it : tmp2) 73 | cout << it << endl; 74 | int res = pro(tmp1,tmp2); 75 | cout << res << endl; 76 | return res; 77 | } 78 | } 79 | 80 | int main() 81 | { 82 | vector nums1 = {1,3,5}; 83 | vector nums2 = {4,5,6,10,11,12}; 84 | 85 | cout << pro({1,3,5},{5,6,10}) << endl; 86 | 87 | cout << proNotEq(nums1,nums2) << endl; 88 | system("pause"); 89 | return 0; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /MyString.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //远端修改,测试冲突 6 | using namespace std; 7 | 8 | class MyString 9 | { 10 | public: 11 | MyString(const char* str = nullptr); 12 | MyString(const MyString& r); 13 | MyString(MyString &&other); //移动构造函数 14 | 15 | MyString& operator=(const MyString& x); 16 | MyString& operator=(MyString&& x); 17 | 18 | private: 19 | char* m_data; 20 | }; 21 | 22 | 23 | MyString::MyString(const char* str) 24 | { 25 | if(str == nullptr) 26 | { 27 | m_data = new char[1]; 28 | *m_data = '\0'; 29 | cout << "默认构造函数" << endl; 30 | }else 31 | { 32 | int len = strlen(str); 33 | m_data = new char[len + 1]; 34 | strcpy(m_data,str); 35 | cout << "有参构造" << endl; 36 | } 37 | } 38 | 39 | MyString::MyString(const MyString& r) 40 | { 41 | int len = strlen(r.m_data); 42 | m_data = new char[len + 1]; 43 | strcpy(m_data,r.m_data); 44 | cout << "拷贝构造" << endl; 45 | } 46 | 47 | MyString::MyString(MyString&& other) 48 | { 49 | //窃取 50 | m_data = other.m_data; 51 | other.m_data = nullptr; 52 | cout << "移动构造" << endl; 53 | } 54 | 55 | MyString& MyString::operator=(const MyString& x) 56 | { 57 | //非自赋值 58 | if(this != &x) 59 | { 60 | if(!m_data) 61 | delete[] m_data; 62 | int len = strlen(x.m_data); 63 | m_data = new char[len + 1]; 64 | strcpy(m_data,x.m_data); 65 | } 66 | 67 | cout << "拷贝赋值" << endl; 68 | return *this; 69 | } 70 | 71 | MyString& MyString::operator=(MyString&& x) 72 | { 73 | if(this != &x) 74 | { 75 | delete[] m_data; 76 | m_data = x.m_data; 77 | x.m_data = nullptr; 78 | } 79 | 80 | cout << "移动赋值" << endl; 81 | return *this; 82 | } 83 | 84 | int main() 85 | { 86 | //system("chcp 69001"); 87 | MyString s1; //默认构造 88 | MyString s2("hello"); //参数构造 89 | MyString s3(s2); //拷贝构造 90 | MyString s4(move(s3)); //移动构造 91 | MyString s5; 92 | s5 = s4; //拷贝赋值 93 | MyString s6; 94 | s6 = move(s5); //移动赋值 95 | 96 | system("pause"); 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /Producer_Consumer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | //环形缓冲区的实现,生产者与消费者的模式 10 | template 11 | class BoundBuffer 12 | { 13 | private: 14 | vector m_buffer; 15 | mutex m_mutex; 16 | int capacity; 17 | int curSize; 18 | int start; 19 | int end; 20 | condition_variable not_full; 21 | condition_variable not_empty; 22 | 23 | public: 24 | BoundBuffer(int c) : capacity(c),curSize(0),start(0),end(0) { 25 | m_buffer.resize(c); 26 | } 27 | 28 | void Produce(T x) 29 | { 30 | unique_lock lock(m_mutex); 31 | 32 | not_full.wait(lock,[=]{return curSize < capacity;}); 33 | m_buffer[end] = x; 34 | end = (end + 1) % capacity; 35 | ++curSize; 36 | cout << "Produce successfully: " << x << " in thread: " << this_thread::get_id() << endl; 37 | lock.unlock(); 38 | 39 | not_empty.notify_one(); 40 | } 41 | 42 | T Consume() 43 | { 44 | unique_lock lock(m_mutex); 45 | 46 | not_empty.wait(lock,[=]{return curSize > 0;}); 47 | T v = m_buffer[start]; 48 | start = (start + 1) % capacity; 49 | --curSize; 50 | cout << "Consume successfully: " << v << " in thread: " << this_thread::get_id() << endl; 51 | lock.unlock(); 52 | 53 | not_full.notify_one(); 54 | return v; 55 | } 56 | }; 57 | 58 | BoundBuffer m_buffer(10); 59 | 60 | void Producer() 61 | { 62 | for(int i = 0;i < 100;i++) 63 | { 64 | m_buffer.Produce(i); 65 | } 66 | 67 | m_buffer.Produce(-1); 68 | } 69 | 70 | void Consumer() 71 | { 72 | thread::id thread_id = this_thread::get_id(); 73 | 74 | int n = 0; 75 | do{ 76 | n = m_buffer.Consume(); 77 | }while(n != -1); 78 | 79 | m_buffer.Produce(-1); 80 | } 81 | 82 | int main() 83 | { 84 | vector m_threads; 85 | 86 | m_threads.push_back(thread(Producer)); 87 | m_threads.push_back(thread(Consumer)); 88 | m_threads.push_back(thread(Consumer)); 89 | m_threads.push_back(thread(Consumer)); 90 | 91 | for(auto& it : m_threads) 92 | it.join(); 93 | 94 | system("pause"); 95 | return 0; 96 | } 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 秋招C++面试过程中遇到的一些代码题 2 | 3 | 此仓库主要记录的是C++面试中的代码,主要包括手撕智能指针,手撕String,LRU,生产者消费者模式,快排,归并排序,单例模式,无锁队列,上中位数等等,持续更新! -------------------------------------------------------------------------------- /SlidingWindows.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | 5 | //生成窗口最大值数组 6 | //{4,3,5,4,3,3,6,7} -> {5,5,5,4,6,7} 7 | //滑动窗口来解决 8 | 9 | //w表示窗口大小, w > 0 && w <= nums.size() 10 | vector pro(vector& nums,int w) 11 | { 12 | int n = nums.size(); 13 | vector res(n - w + 1); 14 | //存储的是下标 15 | deque SlicdingQueue; 16 | int index = 0; 17 | 18 | for(int i = 0;i < n;i++) 19 | { 20 | while(!SlicdingQueue.empty() && nums[SlicdingQueue.back()] <= nums[i]) 21 | SlicdingQueue.pop_back(); 22 | 23 | //合法情况放入 24 | SlicdingQueue.push_back(i); 25 | 26 | if(SlicdingQueue.front() == i - w) 27 | SlicdingQueue.pop_front(); 28 | 29 | if(i - w >= -1) 30 | res[index++] = nums[SlicdingQueue.front()]; 31 | } 32 | 33 | return res; 34 | } 35 | 36 | int main() 37 | { 38 | vector nums = {4,3,5,4,3,3,6,7}; 39 | 40 | vector res = pro(nums,3); 41 | for(auto& it : res) 42 | cout << it << endl; 43 | return 0; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /TotalWaysOfMoney.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limowang/Interview_algorithms/106ed62355b450d1a9c35f608326e10b33eb9caa/TotalWaysOfMoney.cpp -------------------------------------------------------------------------------- /TwoBig.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limowang/Interview_algorithms/106ed62355b450d1a9c35f608326e10b33eb9caa/TwoBig.cpp -------------------------------------------------------------------------------- /canyane/demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | struct fast_ios {//取消同步,快速IO,让cin/cout比printf/scanf更快 5 | fast_ios() { 6 | cin.tie(nullptr), 7 | ios::sync_with_stdio(false); 8 | }; 9 | }fast_ios_; 10 | 11 | vector split_to_vi(const string &str, const char &delim) { 12 | //将字符串按delim分割为若干个子串(整数),插入到数组中 13 | vector res; 14 | stringstream ss(str); 15 | string tmp; 16 | while(getline(ss, tmp, delim)) 17 | {//按delim分割 18 | if(!tmp.empty()) res.push_back(stoi(tmp)); 19 | } 20 | return res; 21 | } 22 | 23 | vector split_to_vs(const string &str, const char &delim, bool flag = false) { 24 | //将字符串按delim分割为若干个子串,flag为true时,即word的前后带有双引号 25 | vector res; 26 | stringstream ss(str); 27 | string tmp; 28 | while(getline(ss, tmp, delim)) 29 | {//按delim分割 30 | if(!tmp.empty()) 31 | { 32 | if(flag) res.emplace_back(tmp.substr(1, tmp.size() - 2)); 33 | else res.emplace_back(tmp); 34 | } 35 | } 36 | return res; 37 | } 38 | 39 | void vecInt_in(){ 40 | //整数输入,以逗号分隔,eg:1,2,3,4,5 41 | vector arr; 42 | string str; 43 | cin>>str; 44 | arr = split_to_vi(str, ','); 45 | for(int i : arr) cout << i << " "; 46 | cout << endl; 47 | } 48 | 49 | void vecStr_in(){ 50 | //字符串输入,以逗号分隔,eg:hello,world,hello,uestc,hello,wavelab 51 | vector vstrs; 52 | string str; 53 | cin>>str; 54 | vstrs = split_to_vs(str, ','); 55 | for(const string &s : vstrs) cout << s << " "; 56 | cout << endl; 57 | } 58 | 59 | void lessInput(){ 60 | 61 | int i; 62 | vector arr; 63 | while(cin >> i) 64 | { 65 | arr.push_back(i); 66 | if(cin.get() == '\n') break; 67 | } 68 | 69 | vector brr; 70 | while(cin >> i) 71 | { 72 | brr.push_back(i); 73 | } 74 | 75 | } 76 | 77 | void matrix_In(vector> &matrix, int row = 1, int col = 1) 78 | { 79 | for(int i = 0; i < row; ++i) 80 | { 81 | for(int j = 0; j < col; ++j) cin >> matrix[i][j]; 82 | } 83 | } 84 | 85 | void lotsOfInput(bool flag = false) 86 | { 87 | int T;//测试数量 88 | cin >> T; 89 | if(!flag) 90 | {//规模固定 91 | int n, m; 92 | cin >> n >> m; 93 | for(int i = 0; i < T; ++i) 94 | { 95 | vector> matrix(n, vector(m, 0)); 96 | matrix_In(matrix, n, m); 97 | } 98 | }else{ 99 | int n, m; 100 | for(int i = 0; i < T; ++i) 101 | { 102 | cin >> n >> m; 103 | vector> matrix(n, vector(m, 0)); 104 | matrix_In(matrix, n, m); 105 | } 106 | } 107 | } 108 | 109 | void vecStr_in_Plus(){ 110 | //字符串输入(带双引号),以逗号分隔,eg:"hello","world","hello","uestc","hello","wavelab" 111 | vector vstrs; 112 | string str; 113 | cin>>str; 114 | vstrs = split_to_vs(str, ',', true); 115 | for(const string &s : vstrs) cout << s << " "; 116 | cout << endl; 117 | } 118 | 119 | 120 | int main(int argc, char** argv){ 121 | int start = clock(); 122 | /**************************Test IO*********************************/ 123 | //1、整数输入,以逗号分隔,eg:1,2,3,4,5 124 | //vecInt_in(); 125 | 126 | //2、字符串输入,以逗号分隔,eg:hello,world,hello,uestc,hello,wavelab 127 | //vecStr_in(); 128 | //vecStr_in_Plus();//word带双引号"hello" 129 | 130 | //3、多组测试数据(默认以空格分隔) 131 | lessInput(); 132 | lotsOfInput(); 133 | 134 | /******************************************************************/ 135 | printf("%.3lf\n",double(clock()-start)/CLOCKS_PER_SEC); 136 | return 0; 137 | } -------------------------------------------------------------------------------- /canyane/计网.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 计网 4 | 5 | ## 1. 简述HTTP和HTTPS,以及它们的区别? 6 | 7 | + HTTP:应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效。 8 | 9 | + #### HTTPS:HTTP的安全版,即HTTP下加入SSL层,主要作用:一种是建立信息安全通道,二是确认网站的真实性。 10 | 11 | + HTTP与HTTPS的区别 12 | 13 | + https协议需要到ca申请证书 14 | + http明文传输 ,https加密传输 15 | + http端口是80,https是443 16 | + http连接很简单,无状态;https == http + ssl,可进行加密传输、身份验证,更加安全 17 | 18 | ## 2.说说 HTTP 的方法有哪些 ? 19 | 20 | + GET: 用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器 21 | + POST:用于传输信息给服务器,与GET方法类似,但一般推荐使用POST方式。 22 | + PUT: 传输文件,报文主体中包含文件内容,保存到对应URI位置。 23 | + HEAD: 获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效。 24 | + DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件。 25 | + OPTIONS:查询相应URI支持的HTTP方法 26 | 27 | ## 3. 简述 HTTP1.0,1.1,2.0 的主要区别 28 | 29 | + HTTP1.0 30 | + 默认不支持长连接,需要设置keep-alive参数指定 31 | + 强缓存expired、协商缓存last-modified\if-modified-since 有一定的缺陷 32 | 33 | + HTTP1.1 34 | + 默认长连接(keep-alive),http请求可以复用Tcp连接,但是同一时间只能对应一个http请求(http请 35 | 求在一个Tcp中是串行的) 36 | + 增加了强缓存cache-control、协商缓存etag\if-none-match 是对http/1 缓存的优化 37 | 38 | + HTTP2.0 39 | + 多路复用,一个Tcp中多个http请求是并行的 40 | + 二进制格式编码传输 41 | + 使用HPACK算法做header压缩 42 | + 服务端推送 43 | 44 | ## 4. HTTP 常见的响应状态码及其含义 45 | 46 | + 1XX : 信息类状态码(表示接收请求状态处理) 47 | + 2XX : 成功状态码(表示请求正常处理完毕) 48 | + 3XX : 重定向(表示需要进行附加操作,已完成请求) 49 | + 4XX : 客户端错误(表示服务器无法处理请求) 50 | + 5XX : 服务器错误状态码(表示服务器处理请求的时候出错) 51 | 52 | ## 5. GET请求和 POST 请求的区别 53 | 54 | + GET请求在URL中传送的参数是有长度限制的,而POST没有 55 | + GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息 56 | + GET参数通过URL传递,POST放在Request body中 57 | + GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留 58 | + GET请求只能进行url编码,而POST支持多种编码方式 59 | + GET请求会被浏览器主动cache,而POST不会,除非手动设置 60 | + GET产生的URL地址可以被Bookmark,而POST不可以 61 | + GET在浏览器回退时是无害的,而POST会再次提交请求 62 | 63 | ## 6. 对称加密和非对称加密,以及它们的区别 64 | 65 | + 对称加密 66 | + 加密和解密使用同一个秘钥,只有一个密钥,作为私钥 67 | + 常见算法:DES,AES,3DES等 68 | 69 | + 非对称加密 70 | + 加密和解密使用不同的密钥,一把公钥,一把私钥。公钥加密的只有私钥才能解密,私钥加密的只有公钥才能解密。 71 | + 常见算法:RSA,ECC 72 | 73 | + 区别 74 | + 对称加密的加解密效率更高,非对称加密的安全性更高,一般会将两者混合使用。 -------------------------------------------------------------------------------- /parseHttp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | //分割字符串 8 | vector splitString(const string& str,const string& delimiter) 9 | { 10 | vector tokens; 11 | size_t pos = 0; 12 | size_t prev = 0; 13 | 14 | while((pos = str.find(delimiter,prev)) != string::npos) 15 | { 16 | tokens.push_back(str.substr(prev,pos - prev)); 17 | prev = pos + delimiter.size(); 18 | } 19 | 20 | tokens.push_back(str.substr(prev)); 21 | return tokens; 22 | } 23 | 24 | //解析http请求 25 | void parseHttpRequest(const string& request) 26 | { 27 | //分割请求行 28 | vector lines = splitString(request,"\r\n"); 29 | vector requestLine = splitString(lines[0]," "); 30 | if(requestLine.size() >= 3) 31 | { 32 | cout << "Method: " << requestLine[0] << endl; 33 | cout << "Path: " << requestLine[1] << endl; 34 | } 35 | 36 | cout << "print Headers---------" << endl; 37 | //分割头部字段 38 | for(int i = 1;i < lines.size();i++) 39 | { 40 | vector header = splitString(lines[i],": "); 41 | if(header.size() >= 2) 42 | { 43 | cout << "Header: " << header[0] << " - " << header[1] << endl; 44 | } 45 | } 46 | 47 | //找到正文的起始处 48 | size_t bodyStart = request.find("\r\n\r\n"); 49 | if(bodyStart != string::npos) 50 | { 51 | string body = request.substr(bodyStart + 4); 52 | cout << "Body: " << body << endl; 53 | } 54 | } 55 | 56 | int main() 57 | { 58 | string request = "GET /example HTTP/1.1\r\n" 59 | "Host: www.example.com\r\n" 60 | "Content-Type: application/json\r\n" 61 | "\r\n" 62 | "{\"key\": \"value\"}"; 63 | 64 | parseHttpRequest(request); 65 | 66 | system("pause"); 67 | return 0; 68 | } -------------------------------------------------------------------------------- /record.sql: -------------------------------------------------------------------------------- 1 | select cust_id,count(*) as orders 2 | from orders 3 | group by cust_id 4 | having count(*) >= 2; 5 | 6 | 7 | //列出具有2个及以上,价格为10及以上的供应商 8 | select vend_id,count(*) as num_prods 9 | from products 10 | where prod_price >= 10 11 | group by vend_id 12 | having count(8) >= 2; 13 | 14 | //group by 和order by结合使用 15 | select vend_id,count(*) as num_prods 16 | from products 17 | where prod_price >= 10 18 | group by vend_id 19 | having count(8) >= 2 20 | order by num_prods; 21 | 22 | //子查询 23 | select cust_name,cust_contact 24 | from customers 25 | where cust_id in(select cust_id 26 | from orders 27 | where order_num in(select order_num 28 | from orderitems 29 | where prod_id = 'TNT2')) 30 | //连结实现上面的查询语句 31 | select cust_name,cust_contact 32 | from customers,orders,orderitems 33 | where customers.cust_id = orders.cust_id 34 | and orderitems.order_num = orders.order_num 35 | and prod_id = 'TNT2'; 36 | 37 | //与上面功能相同只是使用了别名 38 | select cust_name,cust_contact 39 | from customers as c,orders as a,orderitems as oi; 40 | where c.cust_id = o.cust_id and oi.order_num = o.order_num and prod_id = 'TNT2'; 41 | 42 | 43 | select cust_name 44 | cust_state 45 | (select count(*) 46 | from orders 47 | where orders.cust_id == customers.cust_id) as orders 48 | from customers 49 | order by cust_name; 50 | 51 | //连结表 52 | select vend_name prod_name,prod_price 53 | from vendors,products 54 | where vendors.vend_id = products.vend_id 55 | order by vend_name prod_name; 56 | 57 | //与上面的效果相同,内部连结 58 | select vend_name,prod_name,prod_price 59 | from vendors inner join products 60 | on vendors.vend_id = products.vend_id; 61 | 62 | 63 | //创建高级连结 64 | //--->使用表别名 65 | 66 | //--->使用不同类型的连结 67 | //1.自联结,防止二义性 68 | select p1.prod_id,p1.prod_name 69 | from products as p1,products as p2 70 | where p1.vend_id = p2.vend_id 71 | and p2.vend_id = 'DTNTR'; 72 | 73 | 74 | //外连结,左连接,左边都有,右边可能为空 75 | select customers.cust_id,orders.order_num 76 | from customers left outer join orders 77 | on customers.cust_id = orders.cust_id; 78 | 79 | //使用带聚集函数的连结 80 | select customers.cust_name, 81 | customers.cust_id, 82 | count(orders.order_num) as num_prod 83 | from customers inner join orders 84 | on customers.cust_id = orders.cust_id 85 | group by customers.cust_id; 86 | 87 | //组合查询 88 | //表示两条select语句的联合 89 | select vend_id.prod_id,prod_price 90 | from products 91 | where prod_price <= 5 92 | union //union all 不取消重复的行 93 | select vend_id,prod_id,prod_price 94 | from products 95 | where vend_id in (1001,1002); 96 | 97 | //对组合结果排序 98 | select vend_id.prod_id,prod_price 99 | from products 100 | where prod_price <= 5 101 | union //union all 不取消重复的行 102 | select vend_id,prod_id,prod_price 103 | from products 104 | where vend_id in (1001,1002) 105 | order by vend_id,prod_price; 106 | 107 | //全文本搜索 108 | 109 | 110 | //插入数据 111 | insert into customers(cust_id, 112 | cust_contact, 113 | cust_email, 114 | cust_name, 115 | cust_address, 116 | cust_city, 117 | cust_state, 118 | cust_zip, 119 | cust_country) 120 | select cust_id, //或者用values包裹起来 121 | cust_contact, 122 | cust_email, 123 | cust_name, 124 | cust_address, 125 | cust_city, 126 | cust_state, 127 | cust_zip, 128 | cust_country 129 | from custnew; 130 | 131 | //更新和删除数据 132 | update customers 133 | set cust_name = 'The Fudds', 134 | cust_email = 'elmer@fudd.com' 135 | where cust_id = 10005; 136 | 137 | //删除行 138 | delete from customers 139 | where cust_id = 10006; 140 | 141 | //创建和操纵表 142 | create table customers 143 | ( 144 | cust_id int not null auto_increment, 145 | cust_name char(50) not null, 146 | cust_address char(50) null, 147 | cust_city char(50) null, 148 | cust_state char(5) null, 149 | cust_zip char(10) null, 150 | cust_country char(50) null, 151 | cust_contact char(50) null, 152 | cust_email char(255) null, 153 | primary key (cust_id) 154 | )engin = innodb; 155 | 156 | //修改表的结构 157 | alter table vendors 158 | add vend_phone char(20); 159 | 160 | //删除添加的列 161 | alter table vendors 162 | drop column vend_phone; 163 | 164 | //使用视图 165 | //创建视图 166 | create view productcustomers as 167 | select cust_name,cust_contact,prod_id 168 | from customers,orders,orderitems 169 | where customers.cust_id = orders.cust_id 170 | and orderitems.order_num = orders.order_num; 171 | 172 | //使用视图 173 | select cust_name,cust_contact 174 | from productcustomers 175 | where prod_id = 'TNT2'; 176 | 177 | //更新视图 178 | 179 | //使用存储过程 180 | //为以后的使用而保存的一条或多条Mysql语句的集合,可将其视为批文件 181 | //执行存储过程 182 | call produntpricing(@pricelow, 183 | @pricehigh, 184 | @priceaverage); 185 | 186 | //创建存储过程,无参数的存储过程 187 | create procedure produntpricing() 188 | begin 189 | select Avg(prod_price) as priceaverage 190 | from products; 191 | end; 192 | 193 | 194 | //删除存储过程 195 | drop procedure produntpricing; 196 | 197 | //创建带参数的存储过程 198 | create procedure productpricing( 199 | out p1 decimal(8,2), 200 | out ph decimal(8,2), 201 | out pa decimal(8,2), 202 | ) 203 | begin 204 | select Min(prod_price) 205 | into p1 206 | from products; 207 | select Max(prod_price) 208 | int ph 209 | from productpricing; 210 | select Avg(prod_price) 211 | int pa 212 | from productpricing; 213 | end; 214 | 215 | //执行并不显示数据 216 | call productpricing(@pricelow, 217 | @pricehigh, 218 | @priceaverage); 219 | //使用返回的变量 220 | select @priceaverage; 221 | 222 | //使用游标 223 | create procedure processorders() 224 | begin 225 | declare ordernumbers cursor 226 | for 227 | select order_num from orderitems; 228 | 229 | open ordernumbers; 230 | close ordernumbers; 231 | 232 | end; 233 | 234 | //具体使用游标 235 | create procedure processorders() 236 | begin 237 | 238 | //定义局部变量 239 | declare done boolean default 0; 240 | declare o int; 241 | declare t decimal(8,2); 242 | 243 | //定义游标 244 | declare ordernumbers cursor 245 | for 246 | select order_num from orders; 247 | 248 | //定义条件 249 | declare continue handler for sqlstate '02000' set done = 1; 250 | 251 | //创建一个表存储结果 252 | create table if not exists ordertotals 253 | (order_num int,total decimal(8,2)); 254 | 255 | //打开游标 256 | open ordernumbers; 257 | 258 | repeat 259 | fetch ordernumbers into o; 260 | call ordertotal(o,l,t); 261 | 262 | insert into ordernumbers(order_num,total) 263 | values(o,t); 264 | 265 | until done end repeat; 266 | close ordernumbers; 267 | 268 | end; 269 | 270 | 271 | 272 | 273 | //使用触发器 274 | //在事件发生的时候自动执行 275 | //创建触发器 276 | create trigger newproduct after insert on products 277 | for each row select 'Product added'; 278 | 279 | //一个表最多支持6个触发器,每条insert,update,delete的之前和之后 280 | 281 | 282 | 283 | //管理事务处理 284 | //rollback的使用 285 | select * from ordertotals; 286 | start transaction; 287 | delete from ordertotals; 288 | select * from ordernumbers; 289 | rollback; 290 | select * from ordernumbers; 291 | 292 | //commit的使用 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /threadPool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | //#include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | //线程池的实现,支持仿函数 17 | #define THREADPOOL_MAX_NUM 16 18 | 19 | class threadPool 20 | { 21 | private: 22 | using Task = function; 23 | unsigned short _initSize; 24 | vector m_pool; 25 | queue m_tasks; 26 | mutex m_q_mutex; 27 | condition_variable m_q_cv; 28 | atomic m_run{true}; 29 | atomic m_idThNum{0}; // 空闲的线程数量 30 | 31 | public: 32 | threadPool(unsigned short size = 4) 33 | { 34 | _initSize = size; 35 | addThread(size); 36 | } 37 | 38 | ~threadPool() 39 | { 40 | m_run = false; 41 | // 唤醒所有线程执行剩余的任务 42 | m_q_cv.notify_all(); 43 | 44 | for (auto &thread : m_pool) 45 | { 46 | if (thread.joinable()) 47 | thread.join(); 48 | } 49 | } 50 | 51 | // 空闲线程的数量 52 | int idlCount() { return m_idThNum; } 53 | 54 | // 线程池中线程的数量 55 | int thCount() { return m_pool.size(); } 56 | 57 | // 向线程池中注册任务 58 | template 59 | auto addTask(F &&f, Args &&...args) -> future //future从异步任务中获取结果 60 | { 61 | if (!m_run) 62 | throw runtime_error("commit on ThreadPool is Stopped."); 63 | 64 | using RetType = decltype(f(args...)); 65 | auto task = make_shared>(bind(forward(f), forward(args)...)); 66 | 67 | future fu = task->get_future(); 68 | 69 | { 70 | // 将任务放到队列 71 | lock_guard lock{m_q_mutex}; 72 | m_tasks.emplace([task]() 73 | { (*task)(); }); 74 | } 75 | 76 | // 唤醒一个线程 77 | m_q_cv.notify_one(); 78 | return fu; 79 | } 80 | 81 | // 添加循环工作线程 82 | void addThread(unsigned short size) 83 | { 84 | for (; m_pool.size() < THREADPOOL_MAX_NUM && size > 0; --size) 85 | { 86 | // 每个工作线程的任务,一直循环 87 | auto it = [this] 88 | { 89 | while (true) 90 | { 91 | Task task; 92 | { 93 | unique_lock lock{m_q_mutex}; 94 | m_q_cv.wait(lock, [this] 95 | { return !m_run || !m_tasks.empty(); }); 96 | if (!m_run && m_tasks.empty()) 97 | return; 98 | 99 | m_idThNum--; 100 | task = move(m_tasks.front()); 101 | m_tasks.pop(); 102 | } 103 | 104 | // 执行任务 105 | task(); 106 | { 107 | unique_lock lock{m_q_mutex}; 108 | m_idThNum++; 109 | } 110 | } 111 | }; 112 | 113 | //添加线程到线程池 114 | m_pool.emplace_back(it); 115 | } 116 | } 117 | }; 118 | 119 | int addTwo(int a,int b) 120 | { 121 | return a + b; 122 | } 123 | 124 | // void fun1(int slp) 125 | // { 126 | // printf(" hello, fun1 ! %d\n", this_thread::get_id()); 127 | // if (slp > 0) 128 | // { 129 | // printf(" ======= fun1 sleep %d ========= %d\n", slp, this_thread::get_id()); 130 | // this_thread::sleep_for(chrono::milliseconds(slp)); 131 | // // Sleep(slp ); 132 | // } 133 | // } 134 | 135 | // struct gfun 136 | // { 137 | // int operator()(int n) 138 | // { 139 | // printf("%d hello, gfun ! %d\n", n, this_thread::get_id()); 140 | // return 42; 141 | // } 142 | // }; 143 | 144 | // void print() 145 | // { 146 | // cout << "hello thread: " << this_thread::get_id() << endl; 147 | // } 148 | 149 | //测试函数 150 | int main() 151 | { 152 | 153 | threadPool mthreads(4); 154 | 155 | future f = mthreads.addTask(addTwo,10,20); 156 | 157 | //get函数如果任务还没有执行完,则会阻塞直到任务结束,主线程此时不能做其他事情 158 | cout << f.get() << endl; 159 | 160 | // threadPool executor{10}; 161 | 162 | // future ff = executor.addTask(fun1, 0); 163 | // future fg = executor.addTask(gfun(), 0); 164 | 165 | // for (int i = 0; i < 50; i++) 166 | // { 167 | // executor.addTask(fun1, i * 100); 168 | // } 169 | 170 | // cout << " ======= commit all ========= " << this_thread::get_id() << " idlsize=" << executor.idlCount() << endl; 171 | 172 | // cout << " ======= sleep ========= " << this_thread::get_id() << endl; 173 | // this_thread::sleep_for(chrono::seconds(3)); 174 | 175 | // ff.get(); // 调用.get()获取返回值会等待线程执行完,获取返回值 176 | // std::cout << fg.get() << " " << this_thread::get_id() << endl; 177 | 178 | // thread th(print); 179 | 180 | // th.join(); 181 | 182 | // cout << "main thread: " << this_thread::get_id() << endl; 183 | 184 | system("pause"); 185 | return 0; 186 | } -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第10节 使用ninja构建.md: -------------------------------------------------------------------------------- 1 | # 【NO.270】CMake基础 第10节 使用ninja构建 2 | 3 | ## 1.介绍 4 | 5 | 如前所述,CMake是一个元(meta)构建系统,可用于为许多其他构建工具创建构建文件。这个例子展示了如何让CMake使用ninja构建工具。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```shell 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ``` 15 | 16 | - [CMakeLists.txt] - 包含要运行的CMake命令 17 | 18 | ```cmake 19 | # Set the minimum version of CMake that can be used 20 | # To find the cmake version run 21 | # $ cmake --version 22 | cmake_minimum_required(VERSION 3.5) 23 | 24 | # Set the project name 25 | project (hello_cmake) 26 | 27 | # Add an executable 28 | add_executable(hello_cmake main.cpp) 29 | ``` 30 | 31 | - [main.cpp] - 一个简单的“Hello World”CPP文件 32 | 33 | ```cpp 34 | #include 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | std::cout << "Hello CMake!" << std::endl; 39 | return 0; 40 | } 41 | ``` 42 | 43 | ## 2.概念 44 | 45 | ### 2.1 生成器 46 | 47 | CMake生成器负责为底层构建系统编写输入文件(例如Makefile)。运行`cmake--help`将显示可用的生成器。对于cmake v2.8.12.2,我的系统支持的生成器包括: 48 | 49 | ```shell 50 | Generators 51 | 52 | The following generators are available on this platform: 53 | Unix Makefiles = Generates standard UNIX makefiles. 54 | Ninja = Generates build.ninja files (experimental). 55 | CodeBlocks - Ninja = Generates CodeBlocks project files. 56 | CodeBlocks - Unix Makefiles = Generates CodeBlocks project files. 57 | Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files. 58 | Eclipse CDT4 - Unix Makefiles 59 | = Generates Eclipse CDT 4.0 project files. 60 | KDevelop3 = Generates KDevelop 3 project files. 61 | KDevelop3 - Unix Makefiles = Generates KDevelop 3 project files. 62 | Sublime Text 2 - Ninja = Generates Sublime Text 2 project files. 63 | Sublime Text 2 - Unix Makefiles 64 | = Generates Sublime Text 2 project files.Generators 65 | ``` 66 | 67 | 正如本文所述,CMake包括不同类型的生成器,如命令行生成器、IDE生成器和其他生成器。 68 | 69 | #### 2.1.1 命令行生成工具生成器 70 | 71 | 这些生成器用于命令行构建工具,如Make和Ninja。在使用CMake生成构建系统之前,必须配置所选的工具链。 72 | 73 | 支持的生成器包括: 74 | 75 | - Borland Makefiles 76 | - MSYS Makefiles 77 | - MinGW Makefiles 78 | - NMake Makefiles 79 | - NMake Makefiles JOM 80 | - Ninja 81 | - Unix Makefiles 82 | - Watcom WMake 83 | 84 | #### 2.1.2 IDE构建工具生成器 85 | 86 | 这些生成器用于集成开发环境,其中包括它们自己的编译器。例如Visual Studio和Xcode,它们本身就包含一个编译器。 87 | 88 | 支持的生成器包括: 89 | 90 | - Visual Studio 6 91 | - Visual Studio 7 92 | - Visual Studio 7 .NET 2003 93 | - Visual Studio 8 2005 94 | - Visual Studio 9 2008 95 | - Visual Studio 10 2010 96 | - Visual Studio 11 2012 97 | - Visual Studio 12 2013 98 | - Xcode 99 | 100 | #### 2.1.3 其他生成器 101 | 102 | 这些生成器创建配置并与其他IDE工具共同工作,并且必须包含在IDE或命令行生成器中。 103 | 104 | 支持的生成器包括: 105 | 106 | - CodeBlocks 107 | - CodeLite 108 | - Eclipse CDT4 109 | - KDevelop3 110 | - Kate 111 | - Sublime Text 2 112 | 113 | | Note | 在本例中,ninja是通过命令`sudo apt-get install ninja-build`安装的。 | 114 | | ---- | ------------------------------------------------------------ | 115 | | | | 116 | 117 | ### 2.2 调用生成器 118 | 119 | 要调用CMake生成器,可以使用-G命令行开关,例如: 120 | 121 | ```undefined 122 | cmake .. -G Ninja 123 | ``` 124 | 125 | 完成上述操作后,CMake将生成所需的Ninja构建文件,这些文件可以通过使用Ninja命令运行。 126 | 127 | ```shell 128 | $ cmake .. -G Ninja 129 | 130 | $ ls 131 | build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake rules.ninja 132 | ``` 133 | 134 | ## 3.构建示例 135 | 136 | 下面是构建此示例的示例输出。 137 | 138 | ```shell 139 | $ mkdir build.ninja 140 | 141 | $ cd build.ninja/ 142 | 143 | $ cmake .. -G Ninja 144 | -- The C compiler identification is GNU 4.8.4 145 | -- The CXX compiler identification is GNU 4.8.4 146 | -- Check for working C compiler using: Ninja 147 | -- Check for working C compiler using: Ninja -- works 148 | -- Detecting C compiler ABI info 149 | -- Detecting C compiler ABI info - done 150 | -- Check for working CXX compiler using: Ninja 151 | -- Check for working CXX compiler using: Ninja -- works 152 | -- Detecting CXX compiler ABI info 153 | -- Detecting CXX compiler ABI info - done 154 | -- Configuring done 155 | -- Generating done 156 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/J-building-with-ninja/build.ninja 157 | 158 | $ ninja -v 159 | [1/2] /usr/bin/c++ -MMD -MT CMakeFiles/hello_cmake.dir/main.cpp.o -MF "CMakeFiles/hello_cmake.dir/main.cpp.o.d" -o CMakeFiles/hello_cmake.dir/main.cpp.o -c ../main.cpp 160 | [2/2] : && /usr/bin/c++ CMakeFiles/hello_cmake.dir/main.cpp.o -o hello_cmake -rdynamic && : 161 | 162 | $ ls 163 | build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake hello_cmake rules.ninja 164 | 165 | $ ./hello_cmake 166 | Hello CMake! 167 | ``` 168 | 169 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 170 | 171 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069678.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第11节 导入目标.md: -------------------------------------------------------------------------------- 1 | # 【NO.273】CMake基础 第11节 导入目标 2 | 3 | ## 1.介绍 4 | 5 | 正如前面在第8节中提到的,较新版本的CMake允许你使用导入的别名目标链接第三方库。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```objectivec 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ``` 15 | 16 | - [CMakeLists.txt] - 包含要运行的CMake命令 17 | 18 | ```cmake 19 | cmake_minimum_required(VERSION 3.5) 20 | 21 | # Set the project name 22 | project (imported_targets) 23 | 24 | 25 | # find a boost install with the libraries filesystem and system 26 | find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system) 27 | 28 | # check if boost was found 29 | if(Boost_FOUND) 30 | message ("boost found") 31 | else() 32 | message (FATAL_ERROR "Cannot find Boost") 33 | endif() 34 | 35 | # Add an executable 36 | add_executable(imported_targets main.cpp) 37 | 38 | # link against the boost libraries 39 | target_link_libraries( imported_targets 40 | PRIVATE 41 | Boost::filesystem 42 | ) 43 | ``` 44 | 45 | - [main.cpp] - 具有main的源文件 46 | 47 | ```cpp 48 | #include 49 | #include 50 | #include 51 | 52 | int main(int argc, char *argv[]) 53 | { 54 | std::cout << "Hello Third Party Include!" << std::endl; 55 | 56 | // use a shared ptr 57 | boost::shared_ptr isp(new int(4)); 58 | 59 | // trivial use of boost filesystem 60 | boost::filesystem::path path = "/usr/share/cmake/modules"; 61 | if(path.is_relative()) 62 | { 63 | std::cout << "Path is relative" << std::endl; 64 | } 65 | else 66 | { 67 | std::cout << "Path is not relative" << std::endl; 68 | } 69 | 70 | return 0; 71 | } 72 | ``` 73 | 74 | ## 2.要求 75 | 76 | 此示例需要以下条件: 77 | 78 | - CMake v3.5+ 79 | - 安装在默认系统位置的Boost库 80 | 81 | ## 3.概念 82 | 83 | ### 3.1 导入目标 84 | 85 | 导入目标是由FindXXX模块导出的只读目标(例如Boost::filesystem)。 86 | 87 | 要包括Boost文件系统,你可以执行以下操作: 88 | 89 | ```cmake 90 | target_link_libraries( imported_targets 91 | PRIVATE 92 | Boost::filesystem 93 | ) 94 | ``` 95 | 96 | 这将自动链接`Boost::FileSystem`和`Boost::System`库,同时还包括`Boost include`目录(即不必手动添加include目录)。 97 | 98 | ## 4.构建示例 99 | 100 | ```shell 101 | $ mkdir build 102 | 103 | $ cd build/ 104 | 105 | $ cmake .. 106 | -- The C compiler identification is GNU 5.4.0 107 | -- The CXX compiler identification is GNU 5.4.0 108 | -- Check for working C compiler: /usr/bin/cc 109 | -- Check for working C compiler: /usr/bin/cc -- works 110 | -- Detecting C compiler ABI info 111 | -- Detecting C compiler ABI info - done 112 | -- Detecting C compile features 113 | -- Detecting C compile features - done 114 | -- Check for working CXX compiler: /usr/bin/c++ 115 | -- Check for working CXX compiler: /usr/bin/c++ -- works 116 | -- Detecting CXX compiler ABI info 117 | -- Detecting CXX compiler ABI info - done 118 | -- Detecting CXX compile features 119 | -- Detecting CXX compile features - done 120 | -- Boost version: 1.58.0 121 | -- Found the following Boost libraries: 122 | -- filesystem 123 | -- system 124 | boost found 125 | -- Configuring done 126 | -- Generating done 127 | -- Build files have been written to: /data/code/01-basic/K-imported-targets/build 128 | 129 | $ make 130 | Scanning dependencies of target imported_targets 131 | [ 50%] Building CXX object CMakeFiles/imported_targets.dir/main.cpp.o 132 | [100%] Linking CXX executable imported_targets 133 | [100%] Built target imported_targets 134 | 135 | 136 | $ ./imported_targets 137 | Hello Third Party Include! 138 | Path is not relative 139 | ``` 140 | 141 | 142 | 143 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 144 | 145 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069682.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第13节 构建子项目.md: -------------------------------------------------------------------------------- 1 | # 【NO.275】CMake基础 第13节 构建子项目 2 | 3 | 4 | 5 | ## 1.介绍 6 | 7 | 此示例说明如何设置包含子项目的CMake项目。顶层CMakeLists.txt调用子目录中的CMakeLists.txt以创建以下内容: 8 | 9 | - sublibrary1 - 静态库 10 | - sublibrary2 - 头文件库 11 | - subbinary - 可执行文件 12 | 13 | 此示例中包含的文件包括: 14 | 15 | ```mipsasm 16 | $ tree 17 | . 18 | ├── CMakeLists.txt 19 | ├── subbinary 20 | │ ├── CMakeLists.txt 21 | │ └── main.cpp 22 | ├── sublibrary1 23 | │ ├── CMakeLists.txt 24 | │ ├── include 25 | │ │ └── sublib1 26 | │ │ └── sublib1.h 27 | │ └── src 28 | │ └── sublib1.cpp 29 | └── sublibrary2 30 | ├── CMakeLists.txt 31 | └── include 32 | └── sublib2 33 | └── sublib2.h 34 | ``` 35 | 36 | - [CMakeLists.txt] - 顶级CMakeLists.txt 37 | 38 | ```cmake 39 | cmake_minimum_required (VERSION 3.5) 40 | 41 | project(subprojects) 42 | 43 | # Add sub directories 44 | add_subdirectory(sublibrary1) 45 | add_subdirectory(sublibrary2) 46 | add_subdirectory(subbinary) 47 | ``` 48 | 49 | - [subbinary/CMakeLists.txt] - 生成可执行文件 50 | 51 | ```cmake 52 | project(subbinary) 53 | 54 | # Create the executable 55 | add_executable(${PROJECT_NAME} main.cpp) 56 | 57 | # Link the static library from subproject1 using it's alias sub::lib1 58 | # Link the header only library from subproject2 using it's alias sub::lib2 59 | # This will cause the include directories for that target to be added to this project 60 | target_link_libraries(${PROJECT_NAME} 61 | sub::lib1 62 | sub::lib2 63 | ) 64 | ``` 65 | 66 | - [subbinary/main.cpp] - 可执行文件的源代码 67 | 68 | ```cpp 69 | #include "sublib1/sublib1.h" 70 | #include "sublib2/sublib2.h" 71 | 72 | int main(int argc, char *argv[]) 73 | { 74 | sublib1 hi; 75 | hi.print(); 76 | 77 | sublib2 howdy; 78 | howdy.print(); 79 | 80 | return 0; 81 | } 82 | ``` 83 | 84 | - [sublibrary1/CMakeLists.txt] - 创建静态库 85 | 86 | ```cmake 87 | # Set the project name 88 | project (sublibrary1) 89 | 90 | # Add a library with the above sources 91 | add_library(${PROJECT_NAME} src/sublib1.cpp) 92 | add_library(sub::lib1 ALIAS ${PROJECT_NAME}) 93 | 94 | target_include_directories( ${PROJECT_NAME} 95 | PUBLIC ${PROJECT_SOURCE_DIR}/include 96 | ) 97 | ``` 98 | 99 | - [sublibrary1/include/sublib1/sublib1.h] 100 | 101 | ```cpp 102 | #ifndef __SUBLIB_1_H__ 103 | #define __SUBLIB_1_H__ 104 | 105 | class sublib1 106 | { 107 | public: 108 | void print(); 109 | }; 110 | 111 | #endif 112 | ``` 113 | 114 | - [sublibrary1/src/sublib1.cpp] 115 | 116 | ```cpp 117 | #include 118 | 119 | #include "sublib1/sublib1.h" 120 | 121 | void sublib1::print() 122 | { 123 | std::cout << "Hello sub-library 1!" << std::endl; 124 | } 125 | ``` 126 | 127 | - [sublibrary2/CMakeLists.txt] - 设置仅含头文件的库 128 | 129 | ```cmake 130 | # Set the project name 131 | project (sublibrary2) 132 | 133 | add_library(${PROJECT_NAME} INTERFACE) 134 | add_library(sub::lib2 ALIAS ${PROJECT_NAME}) 135 | 136 | target_include_directories(${PROJECT_NAME} 137 | INTERFACE 138 | ${PROJECT_SOURCE_DIR}/include 139 | ) 140 | ``` 141 | 142 | - [sublibrary2/include/sublib2/sublib2.h] 143 | 144 | ```cpp 145 | #ifndef __SUBLIB_2_H__ 146 | #define __SUBLIB_2_H__ 147 | 148 | #include 149 | 150 | class sublib2 151 | { 152 | public: 153 | void print() 154 | { 155 | std::cout << "Hello header only sub-library 2!" << std::endl; 156 | } 157 | }; 158 | 159 | #endif 160 | ``` 161 | 162 | | Tip | 在本例中,我将头文件移动到每个项目include目录下的一个子文件夹中,同时将目标include保留为根include文件夹。这是防止文件名冲突的好主意,因为你必须包含如下所示的文件:`#include“subib1/subib1.h”`。这也意味着,如果你为其他用户安装库,默认安装位置将是`/usr/local/include/subib1/subib1.h`。 | 163 | | ---- | ------------------------------------------------------------ | 164 | | | | 165 | 166 | ## 2.概念 167 | 168 | ### 2.1 添加子目录 169 | 170 | CMakeLists.txt文件可以包含和调用含CMakeLists.txt的子目录 171 | 172 | ```cmake 173 | add_subdirectory(sublibrary1) 174 | add_subdirectory(sublibrary2) 175 | add_subdirectory(subbinary) 176 | ``` 177 | 178 | ### 2.2 引用子项目目录 179 | 180 | 当使用project()命令创建项目时,CMake将自动创建许多变量,这些变量可用于引用有关项目的详细信息。然后,其他子项目或主项目可以使用这些变量。例如,引用你可以使用的不同项目的源目录。 181 | 182 | ```cmake 183 | ${sublibrary1_SOURCE_DIR} 184 | ${sublibrary2_SOURCE_DIR} 185 | ``` 186 | 187 | CMake创建的变量包括: 188 | 189 | | Variable | Info | 190 | | ------------------ | ------------------------------------------------------------ | 191 | | PROJECT_NAME | 由当前`project()`设置的项目名称 | 192 | | CMAKE_PROJECT_NAME | 由project()命令设置的第一个项目的名称,即顶级项目 | 193 | | PROJECT_SOURCE_DIR | 当前项目的源目录 | 194 | | PROJECT_BINARY_DIR | 当前项目的生成目录 | 195 | | name_SOURCE_DIR | 名为“name”的项目的源目录。在本例中,创建的源目录将是`sublibrary1_SOURCE_DIR`、`sublibrary2_SOURCE_DIR`和`subbinary_SOURCE_DIR` | 196 | | name_BINARY_DIR | 名为“name”的项目的二进制目录。在本例中,创建的二进制目录为`sublibrary1_BINARY_DIR` 、`sublibrary2_BINARY_DIR`和`subbinary_BINARY_DIR`。 | 197 | 198 | ### 2.3 头文件库 199 | 200 | 如果你有一个被创建为只包含头文件的库,cmake支持接口目标,以允许在没有任何构建输出的情况下创建目标。有关更多详细信息,请单击[此处](https://cmake.org/cmake/help/v3.4/command/add_library.html#interface-libraries)。 201 | 202 | ```cmake 203 | add_library(${PROJECT_NAME} INTERFACE) 204 | ``` 205 | 206 | 在创建目标时,你还可以使用INTERFACE作用域包含该目标的目录。INTERFACE作用域用于制定目标要求,这些要求在链接此目标的任何库中使用,但不用于目标本身的编译。如下示例,链接至此目标的任何目标都将包含一个include目录,但此目标本身并不进行编译(即不产生任何实体内容): 207 | 208 | ```cmake 209 | target_include_directories(${PROJECT_NAME} 210 | INTERFACE 211 | ${PROJECT_SOURCE_DIR}/include 212 | ) 213 | ``` 214 | 215 | ### 2.4 从子项目中引用库 216 | 217 | 如果某个子项目创建库,则其他项目可以通过在`target_link_library()`命令中调用该项目的名称来引用该库。这意味着你不必引用新库的完整路径,它将作为依赖项被添加。 218 | 219 | ```cmake 220 | target_link_libraries(subbinary 221 | PUBLIC 222 | sublibrary1 223 | ) 224 | ``` 225 | 226 | 或者,你可以创建一个别名目标,使你可以在只读上下文中引用该目标。 227 | 228 | 要创建别名目标运行,请执行以下操作: 229 | 230 | ```cmake 231 | add_library(sublibrary2) 232 | add_library(sub::lib2 ALIAS sublibrary2) 233 | ``` 234 | 235 | 要引用别名,只需如下所示: 236 | 237 | ```cmake 238 | target_link_libraries(subbinary 239 | sub::lib2 240 | ) 241 | ``` 242 | 243 | ### 2.5 包含来自子项目的头文件目录 244 | 245 | 当从子项目添加库时,从`cmake v3`开始,不需要使用它们在二进制文件的include目录中添加项目include目录。 246 | 247 | 这由创建库时`target_include_directory()`命令中的作用域控制。在本例中,因为子二进制可执行文件链接了subibrary1和subibrary2库,所以它将自动包括`${subibrary1_source_DIR}/include`和`${subibrary2_source_DIR}/include`文件夹,因为它们是随库的PUBLIC和INTERFACE范围导出的。 248 | 249 | ## 3.构建示例 250 | 251 | ```shell 252 | $ mkdir build 253 | 254 | $ cd build/ 255 | 256 | $ cmake .. 257 | -- The C compiler identification is GNU 4.8.4 258 | -- The CXX compiler identification is GNU 4.8.4 259 | -- Check for working C compiler: /usr/bin/cc 260 | -- Check for working C compiler: /usr/bin/cc -- works 261 | -- Detecting C compiler ABI info 262 | -- Detecting C compiler ABI info - done 263 | -- Check for working CXX compiler: /usr/bin/c++ 264 | -- Check for working CXX compiler: /usr/bin/c++ -- works 265 | -- Detecting CXX compiler ABI info 266 | -- Detecting CXX compiler ABI info - done 267 | -- Configuring done 268 | -- Generating done 269 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/02-sub-projects/A-basic/build 270 | 271 | $ make 272 | Scanning dependencies of target sublibrary1 273 | [ 50%] Building CXX object sublibrary1/CMakeFiles/sublibrary1.dir/src/sublib1.cpp.o 274 | Linking CXX static library libsublibrary1.a 275 | [ 50%] Built target sublibrary1 276 | Scanning dependencies of target subbinary 277 | [100%] Building CXX object subbinary/CMakeFiles/subbinary.dir/main.cpp.o 278 | Linking CXX executable subbinary 279 | [100%] Built target subbinary 280 | ``` 281 | 282 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 283 | 284 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069693.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第14节 在文件中进行变量替换.md: -------------------------------------------------------------------------------- 1 | # 【NO.276】CMake基础 第14节 在文件中进行变量替换 2 | 3 | ## 1.介绍 4 | 5 | 在调用cmake期间,可以创建使用CMakeLists.txt和cmake缓存中的变量的文件。在cmake生成期间,文件被复制到新位置,并替换所有cmake变量。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```dos 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ├── path.h.in 15 | ├── ver.h.in 16 | ``` 17 | 18 | - [CMakeLists.txt] - 包含要运行的CMake命令 19 | 20 | ```cmake 21 | cmake_minimum_required(VERSION 3.5) 22 | 23 | # Set the project name 24 | project (cf_example) 25 | 26 | # set a project version 27 | set (cf_example_VERSION_MAJOR 0) 28 | set (cf_example_VERSION_MINOR 2) 29 | set (cf_example_VERSION_PATCH 1) 30 | set (cf_example_VERSION "${cf_example_VERSION_MAJOR}.${cf_example_VERSION_MINOR}.${cf_example_VERSION_PATCH}") 31 | 32 | # Call configure files on ver.h.in to set the version. 33 | # Uses the standard ${VARIABLE} syntax in the file 34 | configure_file(ver.h.in ${PROJECT_BINARY_DIR}/ver.h) 35 | 36 | # configure the path.h.in file. 37 | # This file can only use the @VARIABLE@ syntax in the file 38 | configure_file(path.h.in ${PROJECT_BINARY_DIR}/path.h @ONLY) 39 | 40 | # Add an executable 41 | add_executable(cf_example 42 | main.cpp 43 | ) 44 | 45 | # include the directory with the new files 46 | target_include_directories( cf_example 47 | PUBLIC 48 | ${CMAKE_BINARY_DIR} 49 | ) 50 | ``` 51 | 52 | - [main.cpp] - 具有main的源文件 53 | 54 | ```cpp 55 | #include 56 | #include "ver.h" 57 | #include "path.h" 58 | 59 | int main(int argc, char *argv[]) 60 | { 61 | std::cout << "Hello Version " << ver << "!" << std::endl; 62 | std::cout << "Path is " << path << std::endl; 63 | return 0; 64 | } 65 | ``` 66 | 67 | - [path.h.in] - 包含构建目录路径的文件 68 | 69 | ```cpp 70 | #ifndef __PATH_H__ 71 | #define __PATH_H__ 72 | 73 | // version variable that will be substituted by cmake 74 | // This shows an example using the @ variable type 75 | const char* path = "@CMAKE_SOURCE_DIR@"; 76 | 77 | #endif 78 | ``` 79 | 80 | - [ver.h.in] - 包含项目版本的文件 81 | 82 | ```cpp 83 | #ifndef __VER_H__ 84 | #define __VER_H__ 85 | 86 | // version variable that will be substituted by cmake 87 | // This shows an example using the $ variable type 88 | const char* ver = "${cf_example_VERSION}"; 89 | 90 | #endif 91 | ``` 92 | 93 | ## 2.概念 94 | 95 | ### 2.1 配置文件 96 | 97 | 要在文件中进行变量替换,可以使用CMake中的`configure_file()`函数。此函数的核心参数是源文件和目标文件。 98 | 99 | ```cmake 100 | configure_file(ver.h.in ${PROJECT_BINARY_DIR}/ver.h) 101 | 102 | configure_file(path.h.in ${PROJECT_BINARY_DIR}/path.h @ONLY) 103 | ``` 104 | 105 | 上面的第一个示例允许使用像CMake变量一样的`${}`语法或ver.h.in文件中的`@@`定义变量。生成后,新文件ver.h将在`PROJECT_BINARY_DIR`中可用。 106 | 107 | ```cpp 108 | const char* ver = "${cf_example_VERSION}"; 109 | ``` 110 | 111 | 第二个示例只允许在path.h.in文件中使用`@@`语法定义变量。生成后,将在`PROJECT_BINARY_DIR`中提供新文件path.h。 112 | 113 | ```cpp 114 | const char* path = "@CMAKE_SOURCE_DIR@"; 115 | ``` 116 | 117 | ## 3.构建示例 118 | 119 | ```shell 120 | $ mkdir build 121 | 122 | $ cd build/ 123 | 124 | $ cmake .. 125 | -- The C compiler identification is GNU 4.8.4 126 | -- The CXX compiler identification is GNU 4.8.4 127 | -- Check for working C compiler: /usr/bin/cc 128 | -- Check for working C compiler: /usr/bin/cc -- works 129 | -- Detecting C compiler ABI info 130 | -- Detecting C compiler ABI info - done 131 | -- Check for working CXX compiler: /usr/bin/c++ 132 | -- Check for working CXX compiler: /usr/bin/c++ -- works 133 | -- Detecting CXX compiler ABI info 134 | -- Detecting CXX compiler ABI info - done 135 | -- Configuring done 136 | -- Generating done 137 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/03-code-generation/configure-files/build 138 | 139 | $ ls 140 | CMakeCache.txt CMakeFiles cmake_install.cmake Makefile path.h ver.h 141 | 142 | $ cat path.h 143 | #ifndef __PATH_H__ 144 | #define __PATH_H__ 145 | 146 | // version variable that will be substituted by cmake 147 | // This shows an example using the @ variable type 148 | const char* path = "/home/matrim/workspace/cmake-examples/03-code-generation/configure-files"; 149 | 150 | #endif 151 | 152 | $ cat ver.h 153 | #ifndef __VER_H__ 154 | #define __VER_H__ 155 | 156 | // version variable that will be substituted by cmake 157 | // This shows an example using the $ variable type 158 | const char* ver = "0.2.1"; 159 | 160 | #endif 161 | 162 | $ make 163 | Scanning dependencies of target cf_example 164 | [100%] Building CXX object CMakeFiles/cf_example.dir/main.cpp.o 165 | Linking CXX executable cf_example 166 | [100%] Built target cf_example 167 | 168 | $ ./cf_example 169 | Hello Version 0.2.1! 170 | Path is /home/matrim/workspace/cmake-examples/03-code-generation/configure-files 171 | ``` 172 | 173 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 174 | 175 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069698.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第16节 创建deb文件.md: -------------------------------------------------------------------------------- 1 | # 【NO.278】CMake基础 第16节 创建deb文件 2 | 3 | ## 1.介绍 4 | 5 | 此示例显示如何使用deb格式生成Linux安装程序。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```ruby 10 | $ tree 11 | . 12 | ├── cmake-examples.conf 13 | ├── CMakeLists.txt 14 | ├── include 15 | │ └── Hello.h 16 | └── src 17 | ├── Hello.cpp 18 | └── main.cpp 19 | ``` 20 | 21 | - [CMakeLists.txt] - 包含要运行的CMake命令。 22 | 23 | ```cmake 24 | cmake_minimum_required(VERSION 3.5) 25 | 26 | project(cmake_examples_deb) 27 | 28 | # set a project version 29 | set (deb_example_VERSION_MAJOR 0) 30 | set (deb_example_VERSION_MINOR 2) 31 | set (deb_example_VERSION_PATCH 2) 32 | set (deb_example_VERSION "${deb_example_VERSION_MAJOR}.${deb_example_VERSION_MINOR}.${deb_example_VERSION_PATCH}") 33 | 34 | 35 | ############################################################ 36 | # Create a library 37 | ############################################################ 38 | 39 | #Generate the shared library from the library sources 40 | add_library(cmake_examples_deb SHARED src/Hello.cpp) 41 | 42 | target_include_directories(cmake_examples_deb 43 | PUBLIC 44 | ${PROJECT_SOURCE_DIR}/include 45 | ) 46 | ############################################################ 47 | # Create an executable 48 | ############################################################ 49 | 50 | # Add an executable with the above sources 51 | add_executable(cmake_examples_deb_bin src/main.cpp) 52 | 53 | # link the new hello_library target with the hello_binary target 54 | target_link_libraries( cmake_examples_deb_bin 55 | PUBLIC 56 | cmake_examples_deb 57 | ) 58 | 59 | ############################################################ 60 | # Install 61 | ############################################################ 62 | 63 | # Binaries 64 | install (TARGETS cmake_examples_deb_bin 65 | DESTINATION bin) 66 | 67 | # Library 68 | # Note: may not work on windows 69 | install (TARGETS cmake_examples_deb 70 | LIBRARY DESTINATION lib) 71 | 72 | # Config 73 | install (FILES cmake-examples.conf 74 | DESTINATION etc) 75 | 76 | ############################################################ 77 | # Create DEB 78 | ############################################################ 79 | 80 | # Tell CPack to generate a .deb package 81 | set(CPACK_GENERATOR "DEB") 82 | 83 | # Set a Package Maintainer. 84 | # This is required 85 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Thom Troy") 86 | 87 | # Set a Package Version 88 | set(CPACK_PACKAGE_VERSION ${deb_example_VERSION}) 89 | 90 | # Include CPack 91 | include(CPack) 92 | ``` 93 | 94 | - [cmake-examples.conf] - 示例配置文件。 95 | 96 | ```ini 97 | # Sample configuration file that could be installed 98 | ``` 99 | 100 | - [include/Hello.h] - 要包含的头文件。 101 | 102 | ```cpp 103 | #ifndef __HELLO_H__ 104 | #define __HELLO_H__ 105 | 106 | class Hello 107 | { 108 | public: 109 | void print(); 110 | }; 111 | 112 | #endif 113 | ``` 114 | 115 | - [src/Hello.cpp] - 要编译的源文件。 116 | 117 | ```cpp 118 | #include 119 | 120 | #include "Hello.h" 121 | 122 | void Hello::print() 123 | { 124 | std::cout << "Hello Install!" << std::endl; 125 | } 126 | ``` 127 | 128 | - [src/main.cpp] - 具有main的源文件。 129 | 130 | ```cpp 131 | #include "Hello.h" 132 | 133 | int main(int argc, char *argv[]) 134 | { 135 | Hello hi; 136 | hi.print(); 137 | return 0; 138 | } 139 | ``` 140 | 141 | ## 2.概念 142 | 143 | ### 2.1 生成器 144 | 145 | `make package`目标可以使用CPack生成器来创建安装程序。 146 | 147 | 对于Debian包,你可以使用以下命令告诉CMake创建一个生成器: 148 | 149 | ```cmake 150 | set(CPACK_GENERATOR "DEB") 151 | ``` 152 | 153 | 在设置了描述软件包的各种设置之后,你必须使用以下命令告诉CMake包含CPack生成器。 154 | 155 | ```cmake 156 | include(CPack) 157 | ``` 158 | 159 | 包含后,通常使用make install目标安装的所有文件现在都可以打包到Debian包中。 160 | 161 | ### 2.2 Debian包设置 162 | 163 | CPack公开了软件包的各种设置。在本例中,我们设置了以下内容: 164 | 165 | ```cmake 166 | # Set a Package Maintainer. 167 | # This is required 168 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Thom Troy") 169 | 170 | # Set a Package Version 171 | set(CPACK_PACKAGE_VERSION ${deb_example_VERSION}) 172 | ``` 173 | 174 | 它设置维护人员和版本。下面指定了更多Debian特定的设置。 175 | 176 | | Variable | Info | 177 | | ---------------------------------- | ------------------------------------------------------------ | 178 | | CPACK_DEBIAN_PACKAGE_MAINTAINER | Maintainer information | 179 | | CPACK_PACKAGE_DESCRIPTION_SUMMARY | Package short description | 180 | | CPACK_PACKAGE_DESCRIPTION | Package description | 181 | | CPACK_DEBIAN_PACKAGE_DEPENDS | For advanced users to add custom scripts. | 182 | | CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA | The build directory you are currently in. | 183 | | CPACK_DEBIAN_PACKAGE_SECTION | Package section (see [here](http://packages.debian.org/stable/)) | 184 | | CPACK_DEBIAN_PACKAGE_VERSION | Package version | 185 | 186 | ## 3.构建示例 187 | 188 | ```shell 189 | $ mkdir build 190 | 191 | $ cd build/ 192 | 193 | $ cmake .. 194 | -- The C compiler identification is GNU 4.8.4 195 | -- The CXX compiler identification is GNU 4.8.4 196 | -- Check for working C compiler: /usr/bin/cc 197 | -- Check for working C compiler: /usr/bin/cc -- works 198 | -- Detecting C compiler ABI info 199 | -- Detecting C compiler ABI info - done 200 | -- Check for working CXX compiler: /usr/bin/c++ 201 | -- Check for working CXX compiler: /usr/bin/c++ -- works 202 | -- Detecting CXX compiler ABI info 203 | -- Detecting CXX compiler ABI info - done 204 | -- Configuring done 205 | -- Generating done 206 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/06-installer/deb/build 207 | 208 | $ make help 209 | The following are some of the valid targets for this Makefile: 210 | ... all (the default if no target is provided) 211 | ... clean 212 | ... depend 213 | ... cmake_examples_deb 214 | ... cmake_examples_deb_bin 215 | ... edit_cache 216 | ... install 217 | ... install/local 218 | ... install/strip 219 | ... list_install_components 220 | ... package 221 | ... package_source 222 | ... rebuild_cache 223 | ... src/Hello.o 224 | ... src/Hello.i 225 | ... src/Hello.s 226 | ... src/main.o 227 | ... src/main.i 228 | ... src/main.s 229 | 230 | $ make package 231 | Scanning dependencies of target cmake_examples_deb 232 | [ 50%] Building CXX object CMakeFiles/cmake_examples_deb.dir/src/Hello.cpp.o 233 | Linking CXX shared library libcmake_examples_deb.so 234 | [ 50%] Built target cmake_examples_deb 235 | Scanning dependencies of target cmake_examples_deb_bin 236 | [100%] Building CXX object CMakeFiles/cmake_examples_deb_bin.dir/src/main.cpp.o 237 | Linking CXX executable cmake_examples_deb_bin 238 | [100%] Built target cmake_examples_deb_bin 239 | Run CPack packaging tool... 240 | CPack: Create package using DEB 241 | CPack: Install projects 242 | CPack: - Run preinstall target for: cmake_examples_deb 243 | CPack: - Install project: cmake_examples_deb 244 | CPack: Create package 245 | CPack: - package: /home/matrim/workspace/cmake-examples/06-installer/deb/build/cmake_examples_deb-0.2.2-Linux.deb generated. 246 | 247 | $ ls 248 | CMakeCache.txt cmake_examples_deb-0.2.2-Linux.deb cmake_examples_deb_bin CMakeFiles cmake_install.cmake CPackConfig.cmake _CPack_Packages CPackSourceConfig.cmake install_manifest.txt libcmake_examples_deb.so Makefile 249 | ``` 250 | 251 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 252 | 253 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069925.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第17节 Clang分析器.md: -------------------------------------------------------------------------------- 1 | # 【NO.279】CMake基础 第17节 Clang分析器 2 | 3 | ## 1.介绍 4 | 5 | 此示例说明如何调用Clang Static Analyzer以使用scan-build工具执行静态分析。 6 | 7 | 此示例中包含的文件包括: 8 | 9 | ```shell 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── subproject1 14 | │ ├── CMakeLists.txt 15 | │ └── main1.cpp 16 | └── subproject2 17 | ├── CMakeLists.txt 18 | └── main2.cpp 19 | ``` 20 | 21 | - [CMakeLists.txt] - 顶级CMakeLists.txt。 22 | 23 | ```cmake 24 | cmake_minimum_required (VERSION 3.5) 25 | 26 | project(cppcheck_analysis) 27 | 28 | # Use debug build as recommended 29 | set(CMAKE_BUILD_TYPE Debug) 30 | 31 | # Have cmake create a compile database 32 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 33 | 34 | # Add sub directories 35 | add_subdirectory(subproject1) 36 | add_subdirectory(subproject2) 37 | ``` 38 | 39 | - [subproject1/CMakeLists.txt] - C子项目1的Make命令。 40 | 41 | ```cmake 42 | # Set the project name 43 | project (subproject1) 44 | 45 | # Add an executable with the above sources 46 | add_executable(${PROJECT_NAME} main1.cpp) 47 | ``` 48 | 49 | - [subproject1/main.cpp] - 子项目的源代码,没有错误。 50 | 51 | ```cpp 52 | #include 53 | 54 | int main(int argc, char *argv[]) 55 | { 56 | std::cout << "Hello Main1!" << std::endl; 57 | return 0; 58 | } 59 | ``` 60 | 61 | - [subproject2/CMakeLists.txt] - C子项目2的Make命令。 62 | 63 | ```cmake 64 | # Set the project name 65 | project (subproject2) 66 | 67 | # Add an executable with the above sources 68 | add_executable(${PROJECT_NAME} main2.cpp) 69 | ``` 70 | 71 | - [subproject2/main2.cpp] - 包含错误的子项目的源代码。 72 | 73 | ```cpp 74 | #include 75 | 76 | int main(int argc, char *argv[]) 77 | { 78 | std::cout << "Hello Main2!" << std::endl; 79 | int* x = NULL; 80 | std::cout << *x << std::endl; 81 | return 0; 82 | } 83 | ``` 84 | 85 | ## 2.要求 86 | 87 | 要运行此示例,必须安装CLANG分析器和scan-build工具。可以使用以下命令将其安装在Ubuntu上。 88 | 89 | ```shell 90 | $ sudo apt-get install clang 91 | $ sudo apt-get install clang-tools 92 | ``` 93 | 94 | 该工具以下方式可用: 95 | 96 | ```shell 97 | $ scan-build-3.6 98 | ``` 99 | 100 | ## 3.概念 101 | 102 | ### 3.1 scan-build 103 | 104 | 要运行clang静态分析器,你可以在运行编译器的同时使用工具Scan-Build来运行分析器。 这会覆盖CC和CXX环境变量,并用它自己的工具替换它们。要运行它,你可以执行以下操作: 105 | 106 | ```shell 107 | $ scan-build-3.6 cmake .. 108 | $ scan-build-3.6 make 109 | ``` 110 | 111 | 默认情况下,这将运行你的平台的标准编译器,即LINUX上的GCC。但是,如果要覆盖此设置,可以将命令更改为: 112 | 113 | ```shell 114 | $ scan-build-3.6 --use-cc=clang-3.6 --use-c++=clang++-3.6 -o ./scanbuildout/ make 115 | ``` 116 | 117 | ### 3.2 scan-build输出 118 | 119 | scan-build仅在编译时输出警告,还将生成包含错误详细分析的HTML文件列表。 120 | 121 | ```shell 122 | $ cd scanbuildout/ 123 | $ tree 124 | . 125 | └── 2017-07-03-213514-3653-1 126 | ├── index.html 127 | ├── report-42eba1.html 128 | ├── scanview.css 129 | └── sorttable.js 130 | 131 | 1 directory, 4 files 132 | ``` 133 | 134 | 默认情况下,这些文件输出到`/tmp/scanbuildout/{run folder}`。你可以使用 `scan-build -o /output/folder`文件夹更改此设置。 135 | 136 | ## 4.构建示例 137 | 138 | ```shell 139 | $ mkdir build 140 | 141 | $ cd build/ 142 | 143 | $ scan-build-3.6 -o ./scanbuildout make 144 | scan-build: Using '/usr/lib/llvm-3.6/bin/clang' for static analysis 145 | make: *** No targets specified and no makefile found. Stop. 146 | scan-build: Removing directory '/data/code/clang-analyzer/build/scanbuildout/2017-07-03-211632-937-1' because it contains no reports. 147 | scan-build: No bugs found. 148 | devuser@91457fbfa423:/data/code/clang-analyzer/build$ scan-build-3.6 -o ./scanbuildout cmake .. 149 | scan-build: Using '/usr/lib/llvm-3.6/bin/clang' for static analysis 150 | -- The C compiler identification is GNU 5.4.0 151 | -- The CXX compiler identification is GNU 5.4.0 152 | -- Check for working C compiler: /usr/share/clang/scan-build-3.6/ccc-analyzer 153 | -- Check for working C compiler: /usr/share/clang/scan-build-3.6/ccc-analyzer -- works 154 | -- Detecting C compiler ABI info 155 | -- Detecting C compiler ABI info - done 156 | -- Detecting C compile features 157 | -- Detecting C compile features - done 158 | -- Check for working CXX compiler: /usr/share/clang/scan-build-3.6/c++-analyzer 159 | -- Check for working CXX compiler: /usr/share/clang/scan-build-3.6/c++-analyzer -- works 160 | -- Detecting CXX compiler ABI info 161 | -- Detecting CXX compiler ABI info - done 162 | -- Detecting CXX compile features 163 | -- Detecting CXX compile features - done 164 | -- Found CPPCHECK: /usr/local/bin/cppcheck 165 | cppcheck found. Use cppccheck-analysis targets to run it 166 | -- Configuring done 167 | -- Generating done 168 | -- Build files have been written to: /data/code/clang-analyzer/build 169 | scan-build: Removing directory '/data/code/clang-analyzer/build/scanbuildout/2017-07-03-211641-941-1' because it contains no reports. 170 | scan-build: No bugs found. 171 | 172 | $ $ scan-build-3.6 -o ./scanbuildout make 173 | scan-build: Using '/usr/lib/llvm-3.6/bin/clang' for static analysis 174 | Scanning dependencies of target subproject1 175 | [ 25%] Building CXX object subproject1/CMakeFiles/subproject1.dir/main1.cpp.o 176 | [ 50%] Linking CXX executable subproject1 177 | [ 50%] Built target subproject1 178 | Scanning dependencies of target subproject2 179 | [ 75%] Building CXX object subproject2/CMakeFiles/subproject2.dir/main2.cpp.o 180 | /data/code/clang-analyzer/subproject2/main2.cpp:7:17: warning: Dereference of null pointer (loaded from variable 'x') 181 | std::cout << *x << std::endl; 182 | ^~ 183 | 1 warning generated. 184 | [100%] Linking CXX executable subproject2 185 | [100%] Built target subproject2 186 | scan-build: 1 bug found. 187 | scan-build: Run 'scan-view /data/code/clang-analyzer/build/scanbuildout/2017-07-03-211647-1172-1' to examine bug reports. 188 | 189 | $ cd scanbuildout/ 190 | $ tree 191 | . 192 | └── 2017-07-03-213514-3653-1 193 | ├── index.html 194 | ├── report-42eba1.html 195 | ├── scanview.css 196 | └── sorttable.js 197 | 198 | 1 directory, 4 files 199 | ``` 200 | 201 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 202 | 203 | 原文链接:https://www.cnblogs.com/juzaizai/p/15072093.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第18节 Boost单元测试框架.md: -------------------------------------------------------------------------------- 1 | # 【NO.280】CMake基础 第18节 Boost单元测试框架 2 | 3 | ## 1.介绍 4 | 5 | 使用CTest,你可以生成`make test`目标来运行自动化单元测试。这个例子展示了如何找到Boost单元测试框架,创建测试并运行它们。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```shell 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── Reverse.h 14 | ├── Reverse.cpp 15 | ├── Palindrome.h 16 | ├── Palindrome.cpp 17 | ├── unit_tests.cpp 18 | ``` 19 | 20 | - [CMakeLists.txt] - 包含要运行的CMake命令。 21 | 22 | ```cmake 23 | cmake_minimum_required(VERSION 3.5) 24 | 25 | # Set the project name 26 | project (boost_unit_test) 27 | 28 | 29 | # find a boost install with the libraries unit_test_framework 30 | find_package(Boost 1.46.1 REQUIRED COMPONENTS unit_test_framework) 31 | 32 | # Add an library for the example classes 33 | add_library(example_boost_unit_test 34 | Reverse.cpp 35 | Palindrome.cpp 36 | ) 37 | 38 | target_include_directories(example_boost_unit_test 39 | PUBLIC 40 | ${CMAKE_CURRENT_SOURCE_DIR} 41 | ) 42 | 43 | target_link_libraries(example_boost_unit_test 44 | PUBLIC 45 | Boost::boost 46 | ) 47 | 48 | ############################################# 49 | # Unit tests 50 | 51 | # enable CTest testing 52 | enable_testing() 53 | 54 | # Add a testing executable 55 | add_executable(unit_tests unit_tests.cpp) 56 | 57 | target_link_libraries(unit_tests 58 | example_boost_unit_test 59 | Boost::unit_test_framework 60 | ) 61 | 62 | target_compile_definitions(unit_tests 63 | PRIVATE 64 | BOOST_TEST_DYN_LINK 65 | ) 66 | 67 | add_test(test_all unit_tests) 68 | ``` 69 | 70 | - [Reverse.h] / [.cpp] - 反转字符串的类。 71 | 72 | ```cpp 73 | #ifndef __REVERSE_H__ 74 | #define __REVERSE_H__ 75 | 76 | #include 77 | 78 | /** 79 | * Trivial class whose only function is to reverse a string. 80 | * Should use std::reverse instead but want to have example with own class 81 | */ 82 | class Reverse 83 | { 84 | public: 85 | std::string reverse(std::string& toReverse); 86 | }; 87 | #endif 88 | ``` 89 | 90 | ```cpp 91 | #include "Reverse.h" 92 | 93 | std::string Reverse::reverse(std::string& toReverse) 94 | { 95 | std::string ret; 96 | 97 | for(std::string::reverse_iterator rit=toReverse.rbegin(); rit!=toReverse.rend(); ++rit) 98 | { 99 | ret.insert(ret.end(), *rit); 100 | } 101 | return ret; 102 | } 103 | ``` 104 | 105 | - [Palindrome.h] / [.cpp] - 测试字符串是否为回文的类。 106 | 107 | ```cpp 108 | #ifndef __PALINDROME_H__ 109 | #define __PALINDROME_H__ 110 | 111 | #include 112 | 113 | /** 114 | * Trivial class to check if a string is a palindrome. 115 | */ 116 | class Palindrome 117 | { 118 | public: 119 | bool isPalindrome(const std::string& toCheck); 120 | }; 121 | 122 | #endif 123 | ``` 124 | 125 | ```cpp 126 | #include "Palindrome.h" 127 | 128 | bool Palindrome::isPalindrome(const std::string& toCheck) 129 | { 130 | 131 | if (toCheck == std::string(toCheck.rbegin(), toCheck.rend())) { 132 | return true; 133 | } 134 | 135 | return false; 136 | } 137 | ``` 138 | 139 | - [unit_tests.cpp] - 使用Boost单元测试框架的单元测试。 140 | 141 | ```cpp 142 | #include 143 | #include "Reverse.h" 144 | #include "Palindrome.h" 145 | 146 | #define BOOST_TEST_MODULE VsidCommonTest 147 | #include 148 | 149 | BOOST_AUTO_TEST_SUITE( reverse_tests ) 150 | 151 | BOOST_AUTO_TEST_CASE( simple ) 152 | { 153 | std::string toRev = "Hello"; 154 | 155 | Reverse rev; 156 | std::string res = rev.reverse(toRev); 157 | 158 | BOOST_CHECK_EQUAL( res, "olleH" ); 159 | 160 | } 161 | 162 | 163 | BOOST_AUTO_TEST_CASE( empty ) 164 | { 165 | std::string toRev; 166 | 167 | Reverse rev; 168 | std::string res = rev.reverse(toRev); 169 | 170 | BOOST_CHECK_EQUAL( res, "" ); 171 | } 172 | 173 | BOOST_AUTO_TEST_SUITE_END() 174 | 175 | 176 | BOOST_AUTO_TEST_SUITE( palindrome_tests ) 177 | 178 | BOOST_AUTO_TEST_CASE( is_palindrome ) 179 | { 180 | std::string pal = "mom"; 181 | Palindrome pally; 182 | 183 | BOOST_CHECK_EQUAL( pally.isPalindrome(pal), true ); 184 | 185 | } 186 | 187 | BOOST_AUTO_TEST_SUITE_END() 188 | ``` 189 | 190 | ## 2.要求 191 | 192 | 此示例要求将Boost库安装在默认系统位置。 193 | 194 | 使用的库是Boost单元测试框架。 195 | 196 | ## 3.概念 197 | 198 | ### 3.1 启用测试 199 | 200 | 要启用测试,你必须在顶级CMakeLists.txt中包含以下行。 201 | 202 | ```cmake 203 | enable_testing() 204 | ``` 205 | 206 | 这将启用对当前文件夹及其下面所有文件夹的测试。 207 | 208 | ### 3.2 添加测试可执行文件 209 | 210 | 此步骤的要求将取决于你的单元测试框架。在Boost的示例中,你可以创建包含要运行的所有单元测试的二进制文件。 211 | 212 | ```cmake 213 | add_executable(unit_tests unit_tests.cpp) 214 | 215 | target_link_libraries(unit_tests 216 | example_boost_unit_test 217 | Boost::unit_test_framework 218 | ) 219 | 220 | target_compile_definitions(unit_tests 221 | PRIVATE 222 | BOOST_TEST_DYN_LINK 223 | ) 224 | ``` 225 | 226 | 在上面的代码中,添加了一个单元测试二进制文件,它链接到Boost单元测试框架,并包含一个定义来告诉它我们正在使用动态链接。 227 | 228 | ### 3.3 添加测试 229 | 230 | 要添加测试,可以调用`add_test()`函数。这将创建一个命名测试,该测试将运行提供的命令。 231 | 232 | ```cmake 233 | add_test(test_all unit_tests) 234 | ``` 235 | 236 | 在本例中,我们创建了一个名为test_all的测试,该测试将运行由调用`add_executable`创建的unit_test可执行文件创建的可执行文件。 237 | 238 | ## 4.构建示例 239 | 240 | ```shell 241 | $ mkdir build 242 | 243 | $ cd build/ 244 | 245 | $ cmake .. 246 | -- The C compiler identification is GNU 4.8.4 247 | -- The CXX compiler identification is GNU 4.8.4 248 | -- Check for working C compiler: /usr/bin/cc 249 | -- Check for working C compiler: /usr/bin/cc -- works 250 | -- Detecting C compiler ABI info 251 | -- Detecting C compiler ABI info - done 252 | -- Check for working CXX compiler: /usr/bin/c++ 253 | -- Check for working CXX compiler: /usr/bin/c++ -- works 254 | -- Detecting CXX compiler ABI info 255 | -- Detecting CXX compiler ABI info - done 256 | -- Boost version: 1.54.0 257 | -- Found the following Boost libraries: 258 | -- unit_test_framework 259 | -- Configuring done 260 | -- Generating done 261 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/05-unit-testing/boost/build 262 | 263 | $ make 264 | Scanning dependencies of target example_boost_unit_test 265 | [ 33%] Building CXX object CMakeFiles/example_boost_unit_test.dir/Reverse.cpp.o 266 | [ 66%] Building CXX object CMakeFiles/example_boost_unit_test.dir/Palindrome.cpp.o 267 | Linking CXX static library libexample_boost_unit_test.a 268 | [ 66%] Built target example_boost_unit_test 269 | Scanning dependencies of target unit_tests 270 | [100%] Building CXX object CMakeFiles/unit_tests.dir/unit_tests.cpp.o 271 | Linking CXX executable unit_tests 272 | [100%] Built target unit_tests 273 | 274 | $ make test 275 | Running tests... 276 | Test project /home/matrim/workspace/cmake-examples/05-unit-testing/boost/build 277 | Start 1: test_all 278 | 1/1 Test #1: test_all ......................... Passed 0.00 sec 279 | 280 | 100% tests passed, 0 tests failed out of 1 281 | 282 | Total Test time (real) = 0.01 sec 283 | ``` 284 | 285 | 如果更改代码并导致单元测试产生错误。然后,在运行测试时,你将看到以下输出。 286 | 287 | ```shell 288 | $ make test 289 | Running tests... 290 | Test project /home/matrim/workspace/cmake-examples/05-unit-testing/boost/build 291 | Start 1: test_all 292 | 1/1 Test #1: test_all .........................***Failed 0.00 sec 293 | 294 | 0% tests passed, 1 tests failed out of 1 295 | 296 | Total Test time (real) = 0.00 sec 297 | 298 | The following tests FAILED: 299 | 1 - test_all (Failed) 300 | Errors while running CTest 301 | make: *** [test] Error 8 302 | ``` 303 | 304 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 305 | 306 | 原文链接:https://www.cnblogs.com/juzaizai/p/15072101.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第1节 初识CMake.md: -------------------------------------------------------------------------------- 1 | # 【NO.261】CMake基础 第1节 初识CMake 2 | 3 | ## 1.介绍 4 | 5 | 本节展示一个非常基本的hello world的例子。 6 | 7 | 本节中的文件如下: 8 | 9 | ```objectivec 10 | A-hello-cmake$ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ``` 15 | 16 | - [CMakeLists.txt] - 包含你希望运行的 CMake 命令 17 | 18 | ```cmake 19 | # Set the minimum version of CMake that can be used 20 | # To find the cmake version run 21 | # $ cmake --version 22 | cmake_minimum_required(VERSION 3.5) 23 | 24 | # Set the project name 25 | project (hello_cmake) 26 | 27 | # Add an executable 28 | add_executable(hello_cmake main.cpp) 29 | ``` 30 | 31 | - [main.cpp-]一个简单的"Hello World"的C++文件。 32 | 33 | ```cc 34 | #include 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | std::cout << "Hello CMake!" << std::endl; 39 | return 0; 40 | } 41 | ``` 42 | 43 | ## 2.概念 44 | 45 | ### 2.1 CMakeLists.txt 46 | 47 | CMakeLists.txt是存储所有CMake命令的文件。当cmake在文件夹中运行时,它将查找此文件,如果不存在,cmake 将因错误退出。 48 | 49 | ### 2.2 最小 CMake 版本 50 | 51 | 使用 CMake 创建项目时,你可以指定支持的最低版本的 CMake。 52 | 53 | ```cmake 54 | cmake_minimum_required(VERSION 3.5) 55 | ``` 56 | 57 | ### 2.3 项目 58 | 59 | 一个CMake构建文件可以包括一个项目名称,以便在使用多个项目时更容易引用某些变量。 60 | 61 | ```scss 62 | project (hello_cmake) 63 | ``` 64 | 65 | ### 2.4 创建可执行文件 66 | 67 | `add_executable()`命令规定,应从指定的源文件构建可执行文件,在此示例中是main.cpp。`add_executable()`函数的第一个参数是要构建的可执行文件的名称,第二个参数是要编译的源文件列表。 68 | 69 | ```cmake 70 | add_executable(hello_cmake main.cpp) 71 | ``` 72 | 73 | | 注意 | 有些人使用的一种简写方式是使项目名称和可执行文件名称相同。这允许你按如下方式指定CMakeLists.txt。在本例中,project()函数将创建一个值为hello_cmake的变量${PROJECT_NAME}。然后可以将其传递给add_executable()函数以输出‘hello_cmake’可执行文件。 | 74 | | ---- | ------------------------------------------------------------ | 75 | | | | 76 | 77 | ```cmake 78 | cmake_minimum_required(VERSION 2.6) 79 | project (hello_cmake) 80 | add_executable(${PROJECT_NAME} main.cpp) 81 | ``` 82 | 83 | ### 2.5 二进制目录 84 | 85 | 运行cmake命令的根文件夹或顶级文件夹称为CMAKE_BINARY_DIR,是所有二进制文件的根文件夹。CMake既支持就地构建和生成二进制文件,也支持在源代码外构建和生成二进制文件。 86 | 87 | #### 2.5.1 就地构建 88 | 89 | 就地构建将会在与源代码相同的目录结构中生成所有临时文件。这意味着所有的Makefile和目标文件都散布在你的普通代码中。要创建就地构建目标,请在根目录中运行cmake命令。例如: 90 | 91 | ```shell 92 | A-hello-cmake$ cmake . 93 | -- The C compiler identification is GNU 4.8.4 94 | -- The CXX compiler identification is GNU 4.8.4 95 | -- Check for working C compiler: /usr/bin/cc 96 | -- Check for working C compiler: /usr/bin/cc -- works 97 | -- Detecting C compiler ABI info 98 | -- Detecting C compiler ABI info - done 99 | -- Check for working CXX compiler: /usr/bin/c++ 100 | -- Check for working CXX compiler: /usr/bin/c++ -- works 101 | -- Detecting CXX compiler ABI info 102 | -- Detecting CXX compiler ABI info - done 103 | -- Configuring done 104 | -- Generating done 105 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/A-hello-cmake 106 | 107 | A-hello-cmake$ tree 108 | . 109 | ├── CMakeCache.txt 110 | ├── CMakeFiles 111 | │ ├── 2.8.12.2 112 | │ │ ├── CMakeCCompiler.cmake 113 | │ │ ├── CMakeCXXCompiler.cmake 114 | │ │ ├── CMakeDetermineCompilerABI_C.bin 115 | │ │ ├── CMakeDetermineCompilerABI_CXX.bin 116 | │ │ ├── CMakeSystem.cmake 117 | │ │ ├── CompilerIdC 118 | │ │ │ ├── a.out 119 | │ │ │ └── CMakeCCompilerId.c 120 | │ │ └── CompilerIdCXX 121 | │ │ ├── a.out 122 | │ │ └── CMakeCXXCompilerId.cpp 123 | │ ├── cmake.check_cache 124 | │ ├── CMakeDirectoryInformation.cmake 125 | │ ├── CMakeOutput.log 126 | │ ├── CMakeTmp 127 | │ ├── hello_cmake.dir 128 | │ │ ├── build.make 129 | │ │ ├── cmake_clean.cmake 130 | │ │ ├── DependInfo.cmake 131 | │ │ ├── depend.make 132 | │ │ ├── flags.make 133 | │ │ ├── link.txt 134 | │ │ └── progress.make 135 | │ ├── Makefile2 136 | │ ├── Makefile.cmake 137 | │ ├── progress.marks 138 | │ └── TargetDirectories.txt 139 | ├── cmake_install.cmake 140 | ├── CMakeLists.txt 141 | ├── main.cpp 142 | ├── Makefile 143 | ``` 144 | 145 | #### 2.5.2 源外构建 146 | 147 | 使用源外构建,你可以创建单个生成文件夹,该文件夹可以位于文件系统的任何位置。所有临时构建的目标文件都位于此目录中,以保持源码目录结构的整洁。要进行源外构建,请运行build文件夹中的cmake命令,并将其指向根CMakeLists.txt文件所在的目录。使用源外构建时,如果你想从头开始重新创建cmake环境,只需删除构建目录,然后重新运行cmake。 148 | 149 | 举个例子: 150 | 151 | ```shell 152 | A-hello-cmake$ mkdir build 153 | 154 | A-hello-cmake$ cd build/ 155 | 156 | A-hello-cmake/build$ make .. 157 | make: Nothing to be done for `..'. 158 | matrim@freyr:~/workspace/cmake-examples/01-basic/A-hello-cmake/build$ cmake .. 159 | -- The C compiler identification is GNU 4.8.4 160 | -- The CXX compiler identification is GNU 4.8.4 161 | -- Check for working C compiler: /usr/bin/cc 162 | -- Check for working C compiler: /usr/bin/cc -- works 163 | -- Detecting C compiler ABI info 164 | -- Detecting C compiler ABI info - done 165 | -- Check for working CXX compiler: /usr/bin/c++ 166 | -- Check for working CXX compiler: /usr/bin/c++ -- works 167 | -- Detecting CXX compiler ABI info 168 | -- Detecting CXX compiler ABI info - done 169 | -- Configuring done 170 | -- Generating done 171 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/A-hello-cmake/build 172 | 173 | A-hello-cmake/build$ cd .. 174 | 175 | A-hello-cmake$ tree 176 | . 177 | ├── build 178 | │ ├── CMakeCache.txt 179 | │ ├── CMakeFiles 180 | │ │ ├── 2.8.12.2 181 | │ │ │ ├── CMakeCCompiler.cmake 182 | │ │ │ ├── CMakeCXXCompiler.cmake 183 | │ │ │ ├── CMakeDetermineCompilerABI_C.bin 184 | │ │ │ ├── CMakeDetermineCompilerABI_CXX.bin 185 | │ │ │ ├── CMakeSystem.cmake 186 | │ │ │ ├── CompilerIdC 187 | │ │ │ │ ├── a.out 188 | │ │ │ │ └── CMakeCCompilerId.c 189 | │ │ │ └── CompilerIdCXX 190 | │ │ │ ├── a.out 191 | │ │ │ └── CMakeCXXCompilerId.cpp 192 | │ │ ├── cmake.check_cache 193 | │ │ ├── CMakeDirectoryInformation.cmake 194 | │ │ ├── CMakeOutput.log 195 | │ │ ├── CMakeTmp 196 | │ │ ├── hello_cmake.dir 197 | │ │ │ ├── build.make 198 | │ │ │ ├── cmake_clean.cmake 199 | │ │ │ ├── DependInfo.cmake 200 | │ │ │ ├── depend.make 201 | │ │ │ ├── flags.make 202 | │ │ │ ├── link.txt 203 | │ │ │ └── progress.make 204 | │ │ ├── Makefile2 205 | │ │ ├── Makefile.cmake 206 | │ │ ├── progress.marks 207 | │ │ └── TargetDirectories.txt 208 | │ ├── cmake_install.cmake 209 | │ └── Makefile 210 | ├── CMakeLists.txt 211 | ├── main.cpp 212 | ``` 213 | 214 | 在本教程的所有例子中,都将使用源外构建。 215 | 216 | ## 3.构建示例 217 | 218 | 以下是构建此示例的输出: 219 | 220 | ```shell 221 | $ mkdir build 222 | 223 | $ cd build 224 | 225 | $ cmake .. 226 | -- The C compiler identification is GNU 4.8.4 227 | -- The CXX compiler identification is GNU 4.8.4 228 | -- Check for working C compiler: /usr/bin/cc 229 | -- Check for working C compiler: /usr/bin/cc -- works 230 | -- Detecting C compiler ABI info 231 | -- Detecting C compiler ABI info - done 232 | -- Check for working CXX compiler: /usr/bin/c++ 233 | -- Check for working CXX compiler: /usr/bin/c++ -- works 234 | -- Detecting CXX compiler ABI info 235 | -- Detecting CXX compiler ABI info - done 236 | -- Configuring done 237 | -- Generating done 238 | -- Build files have been written to: /workspace/cmake-examples/01-basic/hello_cmake/build 239 | 240 | $ make 241 | Scanning dependencies of target hello_cmake 242 | [100%] Building CXX object CMakeFiles/hello_cmake.dir/hello_cmake.cpp.o 243 | Linking CXX executable hello_cmake 244 | [100%] Built target hello_cmake 245 | 246 | $ ./hello_cmake 247 | Hello CMake! 248 | ``` 249 | 250 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 251 | 252 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069300.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第3节 静态库.md: -------------------------------------------------------------------------------- 1 | # 【NO.263】CMake基础 第3节 静态库 2 | 3 | ## 1.介绍 4 | 5 | 继续展示一个hello world示例,它首先创建并链接一个静态库。这是一个简化示例,这里的库和二进制文件在同一个文件夹中。通常,这些将会被放到子项目中,这些内容将会在以后描述。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```objectivec 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── include 14 | │ └── static 15 | │ └── Hello.h 16 | └── src 17 | ├── Hello.cpp 18 | └── main.cpp 19 | ``` 20 | 21 | - [CMakeLists.txt] - 包含你希望运行的 CMake 命令 22 | 23 | ```cmake 24 | cmake_minimum_required(VERSION 3.5) 25 | 26 | project(hello_library) 27 | 28 | ############################################################ 29 | # Create a library 30 | ############################################################ 31 | 32 | #Generate the static library from the library sources 33 | add_library(hello_library STATIC 34 | src/Hello.cpp 35 | ) 36 | 37 | target_include_directories(hello_library 38 | PUBLIC 39 | ${PROJECT_SOURCE_DIR}/include 40 | ) 41 | 42 | 43 | ############################################################ 44 | # Create an executable 45 | ############################################################ 46 | 47 | # Add an executable with the above sources 48 | add_executable(hello_binary 49 | src/main.cpp 50 | ) 51 | 52 | # link the new hello_library target with the hello_binary target 53 | target_link_libraries( hello_binary 54 | PRIVATE 55 | hello_library 56 | ) 57 | ``` 58 | 59 | - [include/static/Hello.h] - 要包含的头文件 60 | 61 | ```c++ 62 | #ifndef __HELLO_H__ 63 | #define __HELLO_H__ 64 | 65 | class Hello 66 | { 67 | public: 68 | void print(); 69 | }; 70 | 71 | #endif 72 | ``` 73 | 74 | - [src/Hello.cpp] - 要编译的源文件 75 | 76 | ```c++ 77 | #include 78 | 79 | #include "static/Hello.h" 80 | 81 | void Hello::print() 82 | { 83 | std::cout << "Hello Static Library!" << std::endl; 84 | } 85 | ``` 86 | 87 | - [src/main.cpp] - 主源文件 88 | 89 | ```c++ 90 | #include "static/Hello.h" 91 | 92 | int main(int argc, char *argv[]) 93 | { 94 | Hello hi; 95 | hi.print(); 96 | return 0; 97 | } 98 | ``` 99 | 100 | ## 2.概念 101 | 102 | ### 2.1 添加静态库 103 | 104 | `add_library()`功能用于从某些源文件创建库。调用方式如下: 105 | 106 | ```cmake 107 | add_library(hello_library STATIC 108 | src/Hello.cpp 109 | ) 110 | ``` 111 | 112 | 此命令将使用add_library()调用中的源代码创建一个名为`libhello_library.a`的静态库 113 | 114 | | 注意 | 如上一节所述,我们按照现代 CMake 的建议,将源文件直接传递给add_library调用。 | 115 | | ---- | ------------------------------------------------------------ | 116 | | | | 117 | 118 | ### 2.2 添加头文件目录 119 | 120 | 在本例中,我们使用`target_include_directory()`函数将include目录包含在库中,并将范围设置为PUBLIC。 121 | 122 | ```cmake 123 | target_include_directories(hello_library 124 | PUBLIC 125 | ${PROJECT_SOURCE_DIR}/include 126 | ) 127 | ``` 128 | 129 | 这将导致包含的目录在以下位置使用: 130 | 131 | - 在编译该库时 132 | - 在编译链接至该库的任何其他目标时。 133 | 134 | 作用域的含义是: 135 | 136 | - PRIVATE - 将目录添加到此目标的include目录中 137 | - INTERFACE - 将该目录添加到任何链接到此库的目标的include目录中(不包括自己)。 138 | - PUBLIC - 它包含在此库中,也包含在链接此库的任何目标中。 139 | 140 | | 提示 | 对于公共头文件,让你的include文件夹使用子目录“命名空间”通常是个好主意。传递给target_include_directories的目录将是你的Include目录树的根目录,并且你的C++文件应该包含从那里到你要使用的头文件的路径。在本例中,你可以看到我们按如下方式执行操作。使用此方法意味着,在项目中使用多个库时,头文件名冲突的可能性较小。比如: | 141 | | ---- | ------------------------------------------------------------ | 142 | | | | 143 | 144 | ```c++ 145 | #include "static/Hello.h" 146 | ``` 147 | 148 | ### 2.3 链接库 149 | 150 | 在创建使用库的可执行文件时,你必须告诉编译器有关库的信息。这可以使用target_link_library()函数来完成。 151 | 152 | ```cmake 153 | add_executable(hello_binary 154 | src/main.cpp 155 | ) 156 | 157 | target_link_libraries( hello_binary 158 | PRIVATE 159 | hello_library 160 | ) 161 | ``` 162 | 163 | 这告诉CMake在链接时将hello_library与hello_binary可执行文件链接起来。它还将从hello_library传递任何具有PUBLIC或INTERFACE作用范围的include目录到hello_binary。 164 | 165 | 编译器调用它的一个示例是: 166 | 167 | ```shell 168 | /usr/bin/c++ CMakeFiles/hello_binary.dir/src/main.cpp.o -o hello_binary -rdynamic libhello_library.a 169 | ``` 170 | 171 | ## 3.构建示例 172 | 173 | ```shell 174 | $ mkdir build 175 | 176 | $ cd build 177 | 178 | $ cmake .. 179 | -- The C compiler identification is GNU 4.8.4 180 | -- The CXX compiler identification is GNU 4.8.4 181 | -- Check for working C compiler: /usr/bin/cc 182 | -- Check for working C compiler: /usr/bin/cc -- works 183 | -- Detecting C compiler ABI info 184 | -- Detecting C compiler ABI info - done 185 | -- Check for working CXX compiler: /usr/bin/c++ 186 | -- Check for working CXX compiler: /usr/bin/c++ -- works 187 | -- Detecting CXX compiler ABI info 188 | -- Detecting CXX compiler ABI info - done 189 | -- Configuring done 190 | -- Generating done 191 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/C-static-library/build 192 | 193 | $ make 194 | Scanning dependencies of target hello_library 195 | [ 50%] Building CXX object CMakeFiles/hello_library.dir/src/Hello.cpp.o 196 | Linking CXX static library libhello_library.a 197 | [ 50%] Built target hello_library 198 | Scanning dependencies of target hello_binary 199 | [100%] Building CXX object CMakeFiles/hello_binary.dir/src/main.cpp.o 200 | Linking CXX executable hello_binary 201 | [100%] Built target hello_binary 202 | 203 | $ ls 204 | CMakeCache.txt CMakeFiles cmake_install.cmake hello_binary libhello_library.a Makefile 205 | 206 | $ ./hello_binary 207 | Hello Static Library! 208 | ``` 209 | 210 | 211 | 212 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 213 | 214 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069323.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第4节 动态库.md: -------------------------------------------------------------------------------- 1 | # 【NO.264】CMake基础 第4节 动态库 2 | 3 | ## 1.介绍 4 | 5 | 继续展示一个hello world示例,它将首先创建并链接一个共享库。 6 | 7 | 这里还显示了如何创建[别名目标](https://cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html#alias-targets) 8 | 9 | 本教程中的文件如下: 10 | 11 | ```shell 12 | $ tree 13 | . 14 | ├── CMakeLists.txt 15 | ├── include 16 | │ └── shared 17 | │ └── Hello.h 18 | └── src 19 | ├── Hello.cpp 20 | └── main.cpp 21 | ``` 22 | 23 | - [CMakeLists.txt] - 包含要运行的 CMake 命令 24 | 25 | ```cmake 26 | cmake_minimum_required(VERSION 3.5) 27 | 28 | project(hello_library) 29 | 30 | ############################################################ 31 | # Create a library 32 | ############################################################ 33 | 34 | #Generate the shared library from the library sources 35 | add_library(hello_library SHARED 36 | src/Hello.cpp 37 | ) 38 | add_library(hello::library ALIAS hello_library) 39 | 40 | target_include_directories(hello_library 41 | PUBLIC 42 | ${PROJECT_SOURCE_DIR}/include 43 | ) 44 | 45 | ############################################################ 46 | # Create an executable 47 | ############################################################ 48 | 49 | # Add an executable with the above sources 50 | add_executable(hello_binary 51 | src/main.cpp 52 | ) 53 | 54 | # link the new hello_library target with the hello_binary target 55 | target_link_libraries( hello_binary 56 | PRIVATE 57 | hello::library 58 | ) 59 | ``` 60 | 61 | - [include/shared/Hello.h] - 要包含的头文件 62 | 63 | ```cpp 64 | #ifndef __HELLO_H__ 65 | #define __HELLO_H__ 66 | 67 | class Hello 68 | { 69 | public: 70 | void print(); 71 | }; 72 | 73 | #endif 74 | ``` 75 | 76 | - [src/Hello.cpp] - 要编译的源文件 77 | 78 | ```cpp 79 | #include 80 | 81 | #include "shared/Hello.h" 82 | 83 | void Hello::print() 84 | { 85 | std::cout << "Hello Shared Library!" << std::endl; 86 | } 87 | ``` 88 | 89 | - [src/main.cpp] - 具有main的源文件 90 | 91 | ```cpp 92 | #include "shared/Hello.h" 93 | 94 | int main(int argc, char *argv[]) 95 | { 96 | Hello hi; 97 | hi.print(); 98 | return 0; 99 | } 100 | ``` 101 | 102 | ## 2.概念 103 | 104 | ### 2.1 添加共享库 105 | 106 | 与前面的静态库示例一样,add_library()函数也用于从一些源文件创建共享库。它的用法如下: 107 | 108 | ```cmake 109 | add_library(hello_library SHARED 110 | src/Hello.cpp 111 | ) 112 | ``` 113 | 114 | 这将使用传递给add_library()函数的源码文件创建一个名为libhello_library.so的共享库。 115 | 116 | ### 2.2 别名目标 117 | 118 | 顾名思义,别名目标是目标的替代名称,可以在只读上下文中替代真实的目标名称。 119 | 120 | ```cmake 121 | add_library(hello::library ALIAS hello_library) 122 | ``` 123 | 124 | ALIAS类似于“同义词”。ALIAS目标只是原始目标的另一个名称。因此ALIAS目标的要求是不可修改的——您无法调整其属性、安装它等。 125 | 126 | 如下所示,这允许你在将目标链接到其他目标时使用别名引用该目标。 127 | 128 | ### 2.3 链接共享库 129 | 130 | 链接共享库与链接静态库相同。创建可执行文件时,请使用`target_link_library()`函数指向库。 131 | 132 | ```cmake 133 | add_executable(hello_binary 134 | src/main.cpp 135 | ) 136 | 137 | target_link_libraries(hello_binary 138 | PRIVATE 139 | hello::library 140 | ) 141 | ``` 142 | 143 | 这告诉CMake使用别名目标名称将hello_library链接到hello_binary可执行文件。 144 | 145 | 链接器调用它的一个示例是: 146 | 147 | ```shell 148 | /usr/bin/c++ CMakeFiles/hello_binary.dir/src/main.cpp.o -o hello_binary -rdynamic libhello_library.so -Wl,-rpath,/home/matrim/workspace/cmake-examples/01-basic/D-shared-library/build 149 | ``` 150 | 151 | ## 3.构建示例 152 | 153 | ```shell 154 | $ mkdir build 155 | 156 | $ cd build 157 | 158 | $ cmake .. 159 | -- The C compiler identification is GNU 4.8.4 160 | -- The CXX compiler identification is GNU 4.8.4 161 | -- Check for working C compiler: /usr/bin/cc 162 | -- Check for working C compiler: /usr/bin/cc -- works 163 | -- Detecting C compiler ABI info 164 | -- Detecting C compiler ABI info - done 165 | -- Check for working CXX compiler: /usr/bin/c++ 166 | -- Check for working CXX compiler: /usr/bin/c++ -- works 167 | -- Detecting CXX compiler ABI info 168 | -- Detecting CXX compiler ABI info - done 169 | -- Configuring done 170 | -- Generating done 171 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/D-shared-library/build 172 | 173 | $ make 174 | Scanning dependencies of target hello_library 175 | [ 50%] Building CXX object CMakeFiles/hello_library.dir/src/Hello.cpp.o 176 | Linking CXX shared library libhello_library.so 177 | [ 50%] Built target hello_library 178 | Scanning dependencies of target hello_binary 179 | [100%] Building CXX object CMakeFiles/hello_binary.dir/src/main.cpp.o 180 | Linking CXX executable hello_binary 181 | [100%] Built target hello_binary 182 | 183 | $ ls 184 | CMakeCache.txt CMakeFiles cmake_install.cmake hello_binary libhello_library.so Makefile 185 | 186 | $ ./hello_binary 187 | Hello Shared Library! 188 | ``` 189 | 190 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 191 | 192 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069328.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第7节 编译标志.md: -------------------------------------------------------------------------------- 1 | # 【NO.267】CMake基础 第7节 编译标志 2 | 3 | ## 1.引言 4 | 5 | CMake支持以多种不同方式设置编译标志: 6 | 7 | - 使用target_compile_definitions()函数 8 | - 使用CMAKE_C_FLAGS和CMAKE_CXX_FLAGS变量。 9 | 10 | 本教程中的文件如下: 11 | 12 | ```shell 13 | $ tree 14 | . 15 | ├── CMakeLists.txt 16 | ├── main.cpp 17 | ``` 18 | 19 | - [CMakeLists.txt] - 包含要运行的CMake命令 20 | 21 | ```cmake 22 | cmake_minimum_required(VERSION 3.5) 23 | 24 | # Set a default C++ compile flag 25 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE) 26 | 27 | # Set the project name 28 | project (compile_flags) 29 | 30 | # Add an executable 31 | add_executable(cmake_examples_compile_flags main.cpp) 32 | 33 | target_compile_definitions(cmake_examples_compile_flags 34 | PRIVATE EX3 35 | ) 36 | ``` 37 | 38 | - [main.cpp] - 具有main的源文件 39 | 40 | ```cpp 41 | #include 42 | 43 | int main(int argc, char *argv[]) 44 | { 45 | std::cout << "Hello Compile Flags!" << std::endl; 46 | 47 | // only print if compile flag set 48 | #ifdef EX2 49 | std::cout << "Hello Compile Flag EX2!" << std::endl; 50 | #endif 51 | 52 | #ifdef EX3 53 | std::cout << "Hello Compile Flag EX3!" << std::endl; 54 | #endif 55 | 56 | return 0; 57 | } 58 | ``` 59 | 60 | ## 2.概念 61 | 62 | ### 2.1 设置每个目标的C++标志 63 | 64 | 在现代CMake中设置C++标志的推荐方式是使用每个目标的标志,这些标志可以通过`target_compile_definitions()`函数的作用域(或者说接口范围)递到其他目标(INTERFACE或PUBLIC)。这将填充库的`INTERFACE_COMPILE_DEFINITIONS`,并根据作用域将定义传递到链接的目标。 65 | 66 | ```cmake 67 | target_compile_definitions(cmake_examples_compile_flags 68 | PRIVATE EX3 69 | ) 70 | ``` 71 | 72 | 这将导致编译器在编译目标时添加定义 -DEX3。 73 | 74 | 如果目标是库,并且已经选择了作用域PUBLIC或者INTERFACE,则该定义也将包含在链接该目标的任何可执行文件中。 75 | 76 | 对于编译器选项,你还可以使用`target_compile_options()`函数。 77 | 78 | ### 2.2 设置默认C++标志 79 | 80 | `CMAKE_CXX_FLAGS`的默认值为空或包含生成类型的相应标志。 81 | 82 | 要设置其他默认编译标志,可以将以下内容添加到顶级CMakeLists.txt。 83 | 84 | ```cmake 85 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE) 86 | ``` 87 | 88 | 与CMAKE_CXX_FLAGS类似的其他选项包括: 89 | 90 | - 使用CMAKE_C_FLAGS设置 C 编译器标志 91 | - 使用CMAKE_LINKER_FLAGS设置链接器标志 92 | 93 | | Note | 上述命令中的值 `CACHE STRING "Set C++ Compiler Flags" FORCE` 用于强制在CMakeCache.txt文件中设置此变量。有关更多详细信息,请参阅[此处](https://cmake.org/cmake/help/v3.0/command/set.html)。 | 94 | | ---- | ------------------------------------------------------------ | 95 | | | | 96 | 97 | 一旦设置,CMAKE_C_FLAGS和CMAKE_CXX_FLAGS将为该目录或任何包含的子目录中的所有目标全局设置编译器标志/定义。现在不建议将此方法用于一般用途,最好使用`target_compile_definitions`函数。 98 | 99 | ### 2.3 设置CMake标志 100 | 101 | 与构建类型类似,可以使用以下方法设置全局C++编译器标志。 102 | 103 | - 使用GUI工具,如ccmake/cmake-gui 104 | - 传递到cmake 105 | 106 | ```shell 107 | cmake .. -DCMAKE_CXX_FLAGS="-DEX3" 108 | ``` 109 | 110 | ## 3.构建示例 111 | 112 | ```shell 113 | $ mkdir build 114 | 115 | $ cd build/ 116 | 117 | $ cmake .. 118 | -- The C compiler identification is GNU 4.8.4 119 | -- The CXX compiler identification is GNU 4.8.4 120 | -- Check for working C compiler: /usr/bin/cc 121 | -- Check for working C compiler: /usr/bin/cc -- works 122 | -- Detecting C compiler ABI info 123 | -- Detecting C compiler ABI info - done 124 | -- Check for working CXX compiler: /usr/bin/c++ 125 | -- Check for working CXX compiler: /usr/bin/c++ -- works 126 | -- Detecting CXX compiler ABI info 127 | -- Detecting CXX compiler ABI info - done 128 | -- Configuring done 129 | -- Generating done 130 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build 131 | 132 | $ make VERBOSE=1 133 | /usr/bin/cmake -H/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags -B/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build --check-build-system CMakeFiles/Makefile.cmake 0 134 | /usr/bin/cmake -E cmake_progress_start /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/progress.marks 135 | make -f CMakeFiles/Makefile2 all 136 | make[1]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 137 | make -f CMakeFiles/cmake_examples_compile_flags.dir/build.make CMakeFiles/cmake_examples_compile_flags.dir/depend 138 | make[2]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 139 | cd /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/cmake_examples_compile_flags.dir/DependInfo.cmake --color= 140 | Dependee "/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/cmake_examples_compile_flags.dir/DependInfo.cmake" is newer than depender "/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/cmake_examples_compile_flags.dir/depend.internal". 141 | Dependee "/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles/cmake_examples_compile_flags.dir/depend.internal". 142 | Scanning dependencies of target cmake_examples_compile_flags 143 | make[2]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 144 | make -f CMakeFiles/cmake_examples_compile_flags.dir/build.make CMakeFiles/cmake_examples_compile_flags.dir/build 145 | make[2]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 146 | /usr/bin/cmake -E cmake_progress_report /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles 1 147 | [100%] Building CXX object CMakeFiles/cmake_examples_compile_flags.dir/main.cpp.o 148 | /usr/bin/c++ -DEX2 -o CMakeFiles/cmake_examples_compile_flags.dir/main.cpp.o -c /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/main.cpp 149 | Linking CXX executable cmake_examples_compile_flags 150 | /usr/bin/cmake -E cmake_link_script CMakeFiles/cmake_examples_compile_flags.dir/link.txt --verbose=1 151 | /usr/bin/c++ -DEX2 CMakeFiles/cmake_examples_compile_flags.dir/main.cpp.o -o cmake_examples_compile_flags -rdynamic 152 | make[2]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 153 | /usr/bin/cmake -E cmake_progress_report /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles 1 154 | [100%] Built target cmake_examples_compile_flags 155 | make[1]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build' 156 | /usr/bin/cmake -E cmake_progress_start /home/matrim/workspace/cmake-examples/01-basic/G-compile-flags/build/CMakeFiles 0 157 | ``` 158 | 159 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 160 | 161 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069664.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第8节 包含第三方库.md: -------------------------------------------------------------------------------- 1 | # 【NO.268】CMake基础 第8节 包含第三方库 2 | 3 | ## 1.介绍 4 | 5 | 几乎所有重要的项目都需要包含第三方库、头文件或程序。CMake支持使用`find_package()`函数查找这些工具的路径。这将从`CMAKE_MODULE_PATH`中的文件夹列表中搜索格式为`FindXXX.cmake`的CMake模块。在Linux上,默认搜索路径将包含`/usr/share/cmake/Modules`。在我的系统上,这包括对大约1420个通用第三方库的支持。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```objectivec 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ``` 15 | 16 | - [CMakeLists.txt] - 包含要运行的CMake命令 17 | 18 | ```cmake 19 | cmake_minimum_required(VERSION 3.5) 20 | 21 | # Set the project name 22 | project (third_party_include) 23 | 24 | 25 | # find a boost install with the libraries filesystem and system 26 | find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system) 27 | 28 | # check if boost was found 29 | if(Boost_FOUND) 30 | message ("boost found") 31 | else() 32 | message (FATAL_ERROR "Cannot find Boost") 33 | endif() 34 | 35 | # Add an executable 36 | add_executable(third_party_include main.cpp) 37 | 38 | # link against the boost libraries 39 | target_link_libraries( third_party_include 40 | PRIVATE 41 | Boost::filesystem 42 | ) 43 | ``` 44 | 45 | - [main.cpp] - 具有main的源文件 46 | 47 | ```cpp 48 | #include 49 | #include 50 | #include 51 | 52 | int main(int argc, char *argv[]) 53 | { 54 | std::cout << "Hello Third Party Include!" << std::endl; 55 | 56 | // use a shared ptr 57 | boost::shared_ptr isp(new int(4)); 58 | 59 | // trivial use of boost filesystem 60 | boost::filesystem::path path = "/usr/share/cmake/modules"; 61 | if(path.is_relative()) 62 | { 63 | std::cout << "Path is relative" << std::endl; 64 | } 65 | else 66 | { 67 | std::cout << "Path is not relative" << std::endl; 68 | } 69 | 70 | return 0; 71 | } 72 | ``` 73 | 74 | ## 2.要求 75 | 76 | 此示例要求将Boost库安装在默认系统位置。 77 | 78 | ```shell 79 | sudo apt-get install libboost-all-dev -y 80 | ``` 81 | 82 | ## 3.概念 83 | 84 | ### 3.1 查找一个包 85 | 86 | 如上所述,`find_package()`函数将从`CMAKE_MODULE_PATH`中的文件夹列表中搜索格式为`FindXXX.cmake`的CMake模块。`find_package`的参数的确切格式将取决于你要查找的模块。这通常记录在文件`FindXXX.cmake`的顶部 87 | 88 | 下面是查找Boost的基本示例: 89 | 90 | ```cmake 91 | find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system) 92 | ``` 93 | 94 | 这些参数是: 95 | 96 | - Boost -库的名称。这是用于查找模块文件FindBoost.cmake的一部分。 97 | - 1.46.1 - 要查找的Boost的最低版本。 98 | - REQUIRED - 告诉模块这是必需的,如果失败,则找不到该模块。 99 | - COMPONENTS - 要查找的库列表。 100 | 101 | Boost includes可以接受更多参数,还可以利用其他变量。更复杂的设置将在后面的示例中提供。 102 | 103 | ### 3.2 检查是否找到该包 104 | 105 | 大多数包含的软件包都会设置一个变量`XXX_FOUND`,该变量可用于检查该软件包在系统上是否可用。 106 | 107 | 在本例中,变量为`BOOST_FOUND`: 108 | 109 | ```cmake 110 | if(Boost_FOUND) 111 | message ("boost found") 112 | include_directories(${Boost_INCLUDE_DIRS}) 113 | else() 114 | message (FATAL_ERROR "Cannot find Boost") 115 | endif() 116 | ``` 117 | 118 | ### 3.3 导出变量 119 | 120 | 在找到包之后,它通常会导出变量,这些变量可以告诉用户在哪里可以找到库、头文件或可执行文件。与`XXX_FOUND`变量类似,它们是特定于包的,通常记录在`FindXXX.cmake`文件的顶部。 121 | 122 | 本例中导出的变量包括: 123 | 124 | - `Boost_INCLUDE_DIRS` - Boost头文件的路径 125 | 126 | 在某些情况下,你还可以通过使用ccmake或cmake-gui检查缓存来检查这些变量。 127 | 128 | ### 3.4 别名/导入目标 129 | 130 | 大多数现代CMake库在其模块文件中导出别名目标。导入目标的好处在于,它们还可以填充头文件目录和链接库。 131 | 132 | 例如,从CMake的3.5版开始,Boost模块就支持此功能。 133 | 134 | 类似于将你自己的别名目标用于库,模块中的别名可以让引用找到的目标变得更容易。 135 | 136 | 在Boost的例子中,所有目标通过使用标识符`Boost::`加子模块的名字来导出。例如,你可以使用: 137 | 138 | - `Boost::boost` 仅适用于库的头文件 139 | - `Boost::system` 对于Boost系统库 140 | - `Boost::filesystem` 对于文件系统库 141 | 142 | 与你自己的目标一样,这些目标包含它们的依赖项,因此链接到 `Boost::filesystem` 将自动添加 `Boost::boost`和`Boost::system`依赖。 143 | 144 | 要链接到导入的目标,可以使用以下命令: 145 | 146 | ```cmake 147 | target_link_libraries( third_party_include 148 | PRIVATE 149 | Boost::filesystem 150 | ) 151 | ``` 152 | 153 | ### 3.5 非别名目标 154 | 155 | 虽然大多数现代库使用导入的目标,但并非所有模块都已更新。在库尚未更新的情况下,你通常会发现以下变量可用: 156 | 157 | - xxx_INCLUDE_DIRS - 指向库的include目录的变量 158 | - xxx_LIBRARY - 指向库路径的变量. 159 | 160 | 然后,可以将这些文件添加到target_include_directory和target_link_library中: 161 | 162 | ```cmake 163 | # Include the boost headers 164 | target_include_directories( third_party_include 165 | PRIVATE ${Boost_INCLUDE_DIRS} 166 | ) 167 | 168 | # link against the boost libraries 169 | target_link_libraries( third_party_include 170 | PRIVATE 171 | ${Boost_SYSTEM_LIBRARY} 172 | ${Boost_FILESYSTEM_LIBRARY} 173 | ) 174 | ``` 175 | 176 | ## 4.构建示例 177 | 178 | ```shell 179 | $ mkdir build 180 | 181 | $ cd build/ 182 | 183 | $ cmake .. 184 | -- The C compiler identification is GNU 4.8.4 185 | -- The CXX compiler identification is GNU 4.8.4 186 | -- Check for working C compiler: /usr/bin/cc 187 | -- Check for working C compiler: /usr/bin/cc -- works 188 | -- Detecting C compiler ABI info 189 | -- Detecting C compiler ABI info - done 190 | -- Check for working CXX compiler: /usr/bin/c++ 191 | -- Check for working CXX compiler: /usr/bin/c++ -- works 192 | -- Detecting CXX compiler ABI info 193 | -- Detecting CXX compiler ABI info - done 194 | -- Boost version: 1.54.0 195 | -- Found the following Boost libraries: 196 | -- filesystem 197 | -- system 198 | boost found 199 | -- Configuring done 200 | -- Generating done 201 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/H-third-party-library/build 202 | 203 | $ make 204 | Scanning dependencies of target third_party_include 205 | [100%] Building CXX object CMakeFiles/third_party_include.dir/main.cpp.o 206 | Linking CXX executable third_party_include 207 | [100%] Built target third_party_include 208 | matrim@freyr:~/workspace/cmake-examples/01-basic/H-third-party-library/build$ ./ 209 | CMakeFiles/ third_party_include 210 | matrim@freyr:~/workspace/cmake-examples/01-basic/H-third-party-library/build$ ./third_party_include 211 | Hello Third Party Include! 212 | Path is not relative 213 | $ cmake .. 214 | -- The C compiler identification is GNU 4.8.4 215 | -- The CXX compiler identification is GNU 4.8.4 216 | -- Check for working C compiler: /usr/bin/cc 217 | -- Check for working C compiler: /usr/bin/cc -- works 218 | -- Detecting C compiler ABI info 219 | -- Detecting C compiler ABI info - done 220 | -- Check for working CXX compiler: /usr/bin/c++ 221 | -- Check for working CXX compiler: /usr/bin/c++ -- works 222 | -- Detecting CXX compiler ABI info 223 | -- Detecting CXX compiler ABI info - done 224 | -- Boost version: 1.54.0 225 | -- Found the following Boost libraries: 226 | -- filesystem 227 | -- system 228 | boost found 229 | -- Configuring done 230 | -- Generating done 231 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/H-third-party-library/build 232 | 233 | $ make 234 | Scanning dependencies of target third_party_include 235 | [100%] Building CXX object CMakeFiles/third_party_include.dir/main.cpp.o 236 | Linking CXX executable third_party_include 237 | [100%] Built target third_party_include 238 | 239 | $ ./third_party_include 240 | Hello Third Party Include! 241 | Path is not relative 242 | ``` 243 | 244 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 245 | 246 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069669.html -------------------------------------------------------------------------------- /第二次博客总结/cmake知识点/CMake基础 第9节 使用Clang编译.md: -------------------------------------------------------------------------------- 1 | # 【NO.269】CMake基础 第9节 使用Clang编译 2 | 3 | ## 1.引言 4 | 5 | 当使用CMake构建时,可以设置C和C++编译器。此示例与hello-cmake示例相同,只是它显示了将编译器从默认的GCC更改为clang的最基本方法。 6 | 7 | 本教程中的文件如下: 8 | 9 | ```objectivec 10 | $ tree 11 | . 12 | ├── CMakeLists.txt 13 | ├── main.cpp 14 | ``` 15 | 16 | - [CMakeLists.txt] - 包含要运行的CMake命令。 17 | 18 | ```cmake 19 | # Set the minimum version of CMake that can be used 20 | # To find the cmake version run 21 | # $ cmake --version 22 | cmake_minimum_required(VERSION 3.5) 23 | 24 | # Set the project name 25 | project (hello_cmake) 26 | 27 | # Add an executable 28 | add_executable(hello_cmake main.cpp) 29 | ``` 30 | 31 | - [main.cpp] - 一个简单的“Hello World”C++文件。 32 | 33 | ```cpp 34 | #include 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | std::cout << "Hello CMake!" << std::endl; 39 | return 0; 40 | } 41 | ``` 42 | 43 | ## 2.概念 44 | 45 | ### 2.1 编译器选项 46 | 47 | CMake公开了用于控制编译和链接代码的程序的选项。这些程序包括: 48 | 49 | - CMAKE_C_COMPILER - 用于编译c代码的程序. 50 | - CMAKE_CXX_COMPILER - 用于编译c++代码的程序. 51 | - CMAKE_LINKER - 用于链接二进制文件的程序. 52 | 53 | | Note | 在本例中,clang-3.6是通过命令`sudo apt-get install clang-3.6`安装的。 | 54 | | ---- | ------------------------------------------------------------ | 55 | | | | 56 | 57 | | Note | 这是调用clang的最基本、最简单的方式。未来的示例将展示调用编译器的更好方法。 | 58 | | ---- | ------------------------------------------------------------ | 59 | | | | 60 | 61 | ### 2.2 设置标志 62 | 63 | 如第6节示例中所述,你可以使用cmake gui或通过命令行来设置CMake选项。 64 | 65 | 下面是通过命令行向编译器传递参数的示例。 66 | 67 | ```cmake 68 | cmake .. -DCMAKE_C_COMPILER=clang-3.6 -DCMAKE_CXX_COMPILER=clang++-3.6 69 | ``` 70 | 71 | 在设置这些选项之后,当你运行make时,clang将用于编译你的二进制文件。这可以从make输出中的以下几行看出。 72 | 73 | ```shell 74 | /usr/bin/clang++-3.6 -o CMakeFiles/hello_cmake.dir/main.cpp.o -c /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/main.cpp 75 | Linking CXX executable hello_cmake 76 | /usr/bin/cmake -E cmake_link_script CMakeFiles/hello_cmake.dir/link.txt --verbose=1 77 | /usr/bin/clang++-3.6 CMakeFiles/hello_cmake.dir/main.cpp.o -o hello_cmake -rdynamic 78 | ``` 79 | 80 | ## 3.构建示例 81 | 82 | 下面是示例输出: 83 | 84 | ```shell 85 | $ mkdir build.clang 86 | 87 | $ cd build.clang/ 88 | 89 | $ cmake .. -DCMAKE_C_COMPILER=clang-3.6 -DCMAKE_CXX_COMPILER=clang++-3.6 90 | -- The C compiler identification is Clang 3.6.0 91 | -- The CXX compiler identification is Clang 3.6.0 92 | -- Check for working C compiler: /usr/bin/clang-3.6 93 | -- Check for working C compiler: /usr/bin/clang-3.6 -- works 94 | -- Detecting C compiler ABI info 95 | -- Detecting C compiler ABI info - done 96 | -- Check for working CXX compiler: /usr/bin/clang++-3.6 97 | -- Check for working CXX compiler: /usr/bin/clang++-3.6 -- works 98 | -- Detecting CXX compiler ABI info 99 | -- Detecting CXX compiler ABI info - done 100 | -- Configuring done 101 | -- Generating done 102 | -- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang 103 | 104 | $ make VERBOSE=1 105 | /usr/bin/cmake -H/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang -B/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang --check-build-system CMakeFiles/Makefile.cmake 0 106 | /usr/bin/cmake -E cmake_progress_start /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/progress.marks 107 | make -f CMakeFiles/Makefile2 all 108 | make[1]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 109 | make -f CMakeFiles/hello_cmake.dir/build.make CMakeFiles/hello_cmake.dir/depend 110 | make[2]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 111 | cd /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/hello_cmake.dir/DependInfo.cmake --color= 112 | Dependee "/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/hello_cmake.dir/DependInfo.cmake" is newer than depender "/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/hello_cmake.dir/depend.internal". 113 | Dependee "/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles/hello_cmake.dir/depend.internal". 114 | Scanning dependencies of target hello_cmake 115 | make[2]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 116 | make -f CMakeFiles/hello_cmake.dir/build.make CMakeFiles/hello_cmake.dir/build 117 | make[2]: Entering directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 118 | /usr/bin/cmake -E cmake_progress_report /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles 1 119 | [100%] Building CXX object CMakeFiles/hello_cmake.dir/main.cpp.o 120 | /usr/bin/clang++-3.6 -o CMakeFiles/hello_cmake.dir/main.cpp.o -c /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/main.cpp 121 | Linking CXX executable hello_cmake 122 | /usr/bin/cmake -E cmake_link_script CMakeFiles/hello_cmake.dir/link.txt --verbose=1 123 | /usr/bin/clang++-3.6 CMakeFiles/hello_cmake.dir/main.cpp.o -o hello_cmake -rdynamic 124 | make[2]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 125 | /usr/bin/cmake -E cmake_progress_report /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles 1 126 | [100%] Built target hello_cmake 127 | make[1]: Leaving directory `/home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang' 128 | /usr/bin/cmake -E cmake_progress_start /home/matrim/workspace/cmake-examples/01-basic/I-compiling-with-clang/build.clang/CMakeFiles 0 129 | 130 | $ ./hello_cmake 131 | Hello CMake! 132 | ``` 133 | 134 | 原文作者:[橘崽崽啊](https://www.cnblogs.com/juzaizai/) 135 | 136 | 原文链接:https://www.cnblogs.com/juzaizai/p/15069674.html -------------------------------------------------------------------------------- /第二次博客总结/【NO.133】深入理解异步IO+epoll+协程.md: -------------------------------------------------------------------------------- 1 | # 【NO.133】深入理解异步I/O+epoll+协程 2 | 3 | ## 1.前言 4 | 5 | **同步和异步**的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。 6 | **阻塞和非阻塞**的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。 7 | 8 | ## 2.异步I/O 9 | 10 | 在理解异步I/O之前,我们先要知道什么是**同步I/O** 11 | 在**阻塞同步I/O**模型下,用户线程向内核发起 recvfrom 系统调用,当数据没有准备好的时候,用户线程阻塞。 12 | 此外还有一种**非阻塞同步I/O**,此时用户线程不阻塞于 recvfrom,而是反复向系统查询数据状态。当数据准备好了,就对数据进行后续处理。 13 | 14 | ![img](https://pic2.zhimg.com/80/v2-b555a5fbc20f5471f20e87f7d34f9e41_720w.webp) 15 | 16 | Paste_Image.png 17 | 18 | 而在**异步I/O**模式下,用户线程在数据还没有准备好的时候**既不阻塞也不反复查询**,而是**继续干自己该干的事情**。内核会开启一个**内核线程**去读取数据,等到数据准备好了,内核给用户线程一个信号,用户线程中断去执行信号处理函数。 19 | 20 | ![img](https://pic1.zhimg.com/80/v2-369b5305eb19bdb26a628aad2926b72c_720w.webp) 21 | 22 | Paste_Image.png 23 | 24 | **node.js**中的异步回调也是采用开线程的方式实现的 25 | 26 | ## 3.epoll 27 | 28 | epoll/select 是一种**I/O多路复用模型** 29 | 用户线程可以先通过 epoll **注册多个I/O事件** 30 | 然后用户线程反复执行调用 epoll/select 查询是否有准备好的事件 31 | 如果有准备好的I/O事件则进行处理 32 | 关键点是**一个用户线程处理多个I/O事件** 33 | 34 | **epoll/select 的区别在于** 35 | select 的底层原理是遍历所有注册的I/O事件,找出准备好的的I/O事件。 36 | 而 epoll 则是由内核主动通知哪些I/O事件需要处理,不需要用户线程主动去反复查询,因此大大提高了事件处理的效率。 37 | 38 | ![img](https://pic3.zhimg.com/80/v2-e071e80242c1d2d5ade493e9cb7ba532_720w.webp) 39 | 40 | Paste_Image.png 41 | 42 | ## 4.协程 43 | 44 | 协程是一种轻量级的线程 45 | **本质上协程就是用户空间下的线程** 46 | 如果把线程/进程当作虚拟“CPU”,协程即跑在这个“CPU”上的线程。 47 | 48 | **协程的特点** 49 | 50 | 1. 占用的资源更少。 51 | 2. 所有的切换和调度都发生在用户态。 52 | 53 | 不管是进程还是线程,每次阻塞、切换都需要陷入系统调用,先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个线程。而且由于**抢占式调度执行顺序无法确定**的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题。 54 | 因为**协程可以在用户态显示控制切换** 55 | 56 | **例子** 57 | 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。 58 | 59 | 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高: 60 | 61 | ``` 62 | import timedef consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) time.sleep(1) r = '200 OK'def produce(c): c.next() n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close()if __name__=='__main__': c = consumer() produce(c) 63 | ``` 64 | 65 | 协程的优点是可以用同步的处理方式实现异步回调的性能 66 | 67 | 原文链接:https://zhuanlan.zhihu.com/p/254990291 68 | 69 | 作者:[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10) -------------------------------------------------------------------------------- /第二次博客总结/【NO.624】【进程管理】fork之后子进程到底复制了父进程什么?.md: -------------------------------------------------------------------------------- 1 | # 【NO.624】【进程管理】fork之后子进程到底复制了父进程什么? 2 | 3 | 4 | 5 | ```text 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void main() 12 | { 13 | char str[6]="hello"; 14 | 15 | pid_t pid=fork(); 16 | 17 | if(pid==0) 18 | { 19 | str[0]='b'; 20 | printf("子进程中str=%s\n",str); 21 | printf("子进程中str指向的首地址:%x\n",(unsigned int)str); 22 | } 23 | else 24 | { 25 | sleep(1); 26 | printf("父进程中str=%s\n",str); 27 | printf("父进程中str指向的首地址:%x\n",(unsigned int)str); 28 | } 29 | } 30 | ``` 31 | 32 | 子进程中str=bello 33 | 34 | 子进程中str指向的首地址:bfdbfc06 35 | 36 | 父进程中str=hello 37 | 38 | 父进程中str指向的首地址:bfdbfc06 39 | 40 | ## 1、背景介绍 41 | 42 | 这里就涉及到物理地址和逻辑地址(或称虚拟地址)的概念。 43 | 44 | 从逻辑地址到物理地址的映射称为地址重定向。分为: 45 | 46 | 静态重定向--在程序装入主存时已经完成了逻辑地址到物理地址和变换,在程序执行期间不会再发生改变。 47 | 48 | 动态重定向--程序执行期间完成,其实现依赖于硬件地址变换机构,如基址寄存器。 49 | 50 | 逻辑地址:CPU所生成的地址。CPU产生的逻辑地址被分为 :p (页号) 它包含每个页在物理内存中的基址,用来作为页表的索引;d (页偏移),同基址相结合,用来确定送入内存设备的物理内存地址。 51 | 52 | 物理地址:内存单元所看到的地址。用户程序看不见真正的物理地址。用户只生成逻辑地址,且认为进程的地址空间为0到max。物理地址范围从R+0到R+max,R为基地址,地址映射-将程序地址空间中使用的逻辑地址变换成内存中的物理地址的过程。由内存管理单元(MMU)来完成。 53 | 54 | 可执行程序在存储(没有调入内存)时分为代码区,数据区,未初始化数据区三部分。 55 | 56 | (1)代码区存放CPU执行的机器指令。通常代码区是共享的,即其它执行程序可调用它。代码段(code segment/text segment)通常是只读的,有些构架也允许自行修改。 57 | 58 | (2)数据区存放已初始化的全局变量,静态变量(包括全局和局部的),常量。static全局变量和static函数只能在当前文件中被调用。 59 | 60 | (3)未初始化数据区(Block Started by Symbol,BSS)存放全局未初始化的变量。BSS的数据在程序开始执行之前被初始化为0或NULL。 61 | 62 | 代码区所在的地址空间最低,往上依次是数据区和BSS区,并且数据区和BSS区在内存中是紧挨着的。 63 | 64 | text段和data段在编译时已分配了空间,而bss段并不占用可执行文件的大小,它是由链接器来获取内存的。 65 | 66 | bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。 67 | 68 | data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。 69 | 70 | 数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。 71 | 72 | 可执行程序在运行时又多出了两个区域:栈区和堆区。 73 | 74 | (4)栈区。由编译器自动释放,存放函数的参数值,局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存储到栈中。然后这个被调用的函数再为它的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内在区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。 75 | 76 | (5)堆区。用于动态内存分配,位于BSS和栈中间的地址位。由程序员申请分配(malloc)和释放(free)。堆是从低地址位向高地址位增长,采用链式存储结构。频繁地malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。 77 | 78 | ## 2、fork 79 | 80 | fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。 81 | 82 | fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。 83 | 84 | 每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。 85 | 86 | 具体过程是这样的: 87 | 88 | fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。 89 | 90 | 这就是所谓的“写时复制”。正因为fork采用了这种写时复制的机制,所以fork出来子进程之后,父子进程哪个先调度呢?内核一般会先调度子进程,因为很多情况下子进程是要马上执行exec,会清空栈、堆。。这些和父进程共享的空间,加载新的代码段。。。,这就避免了“写时复制”拷贝共享页面的机会。如果父进程先调度很可能写共享页面,会产生“写时复制”的无用功。所以,一般是子进程先调度滴。 91 | 92 | 假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个地址相互之间没有任何关系。 93 | 94 | (注1:在理解时,你可以认为fork后,这两个相同的虚拟地址指向的是不同的物理地址,这样方便理解父子进程之间的独立性) 95 | 96 | (注2:但实际上,linux为了提高 fork 的效率,采用了 copy-on-write 技术,fork后,这两个虚拟地址实际上指向相同的物理地址(内存页),只有任何一个进程试图修改这个虚拟地址里的内容前,两个虚拟地址才会指向不同的物理地址(新的物理地址的内容从原物理地址中复制得到)) 97 | 98 | ## 3、exec家族 99 | 100 | exec家族一共有六个函数,分别是: 101 | 102 | ```text 103 | 104 | (1)int execl(const char *path, const char *arg, ......); 105 | (2)int execle(const char *path, const char *arg, ...... , char * const envp[]); 106 | (3)int execv(const char *path, char *const argv[]); 107 | (4)int execve(const char *filename, char *const argv[], char *const envp[]); 108 | (5)int execvp(const char *file, char * const argv[]); 109 | (6)int execlp(const char *file, const char *arg, ......); 110 | ``` 111 | 112 | 其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。 113 | 114 | exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 115 | 116 | 与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。 117 | 118 | 原文地址:https://zhuanlan.zhihu.com/p/370705498 119 | 120 | 作者:Linux --------------------------------------------------------------------------------