[{"content":"爸爸,这个是什么花? 爸爸也不知道叫什么名字,等爸爸有空上网查查再跟你聊\n蓝花草 这个就叫蓝花草，顾名思义,是草，会开蓝色小花 微信扫一扫挺好用 微信扫一扫其实可以直接识别植物，平时没怎么用过，体验还不错 非兰花草 我从山中来,带着兰花草\n以上出自胡适小诗，也叫绶草\n","permalink":"https://www.becool.vip/posts/life/%E6%97%A5%E5%B8%B8%E5%B0%8F%E4%BA%8B_%E5%85%B0%E8%8A%B1%E8%8D%89/","summary":"\u003ch1 id=\"爸爸这个是什么花\"\u003e爸爸,这个是什么花?\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e爸爸也不知道叫什么名字,等爸爸有空上网查查再跟你聊\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/%e8%93%9d%e8%8a%b1%e8%8d%89.jpg\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"蓝花草\"\u003e蓝花草\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e这个就叫蓝花草，顾名思义,是草，会开蓝色小花\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"微信扫一扫挺好用\"\u003e微信扫一扫挺好用\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e微信扫一扫其实可以直接识别植物，平时没怎么用过，体验还不错\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"非兰花草\"\u003e非兰花草\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e我从山中来,带着兰花草\u003c/p\u003e\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e以上出自胡适小诗，也叫\u003ca href=\"http://www.isenlin.cn/sf_A93BCC9194904C69AF72DBD609EA87FE_209_A952252C215.html\"\u003e绶草\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/%e5%85%b0%e8%8a%b1%e8%8d%89.jpg\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"爸爸,这个是什么花?"},{"content":"背景起因 上午同事小孩ipad死机了，输入密码没有反应，无法进入设备，使用下面的方法都无法强制关机重启\n长按电源键N秒\n长按 电源键+任意音量键（出现滑动关机按钮 但无法滑动）\n悬浮球\u0026mdash;\u0026gt;更多\u0026mdash;\u0026gt;设备\u0026mdash;\u0026gt;重启 无法点击确定重启\n上网搜了一些教程 尝试成功 这里简单记录下\n全面屏iPad 没有Home键 步骤1：分别快速按下 iPad 的音量高、低键； 步骤2：再持续按电源键，即开机启动键 这里注意按住不要松 步骤3：直到 iPad 黑屏后出现 Apple 标志，松开所有按键； 步骤4：等待 iPad 重新启动即可 带Home键的iPad 步骤1：同时按住 Home 键与开机电源键 这里注意不要松 步骤2：直到 iPad 黑屏后出现 Apple 标志，松开所有按键； 步骤3：等待设备重新启动即可 ","permalink":"https://www.becool.vip/posts/tech/ipad%E5%BC%BA%E5%88%B6%E5%85%B3%E6%9C%BA%E9%87%8D%E5%90%AF/","summary":"\u003ch1 id=\"背景起因\"\u003e背景起因\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e上午同事小孩ipad死机了，输入密码没有反应，无法进入设备，使用下面的方法都无法强制关机重启\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e长按电源键N秒\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e长按 电源键+任意音量键（出现滑动关机按钮 但无法滑动）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e悬浮球\u0026mdash;\u0026gt;更多\u0026mdash;\u0026gt;设备\u0026mdash;\u0026gt;重启 无法点击确定重启\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e上网搜了一些教程 尝试成功 这里简单记录下\u003c/p\u003e","title":"ipad强制关机重启"},{"content":"安装 https://github.com/gildas-lormeau/SingleFile-MV3\nDownload the zip file (https://github.com/gildas-lormeau/SingleFile-MV3/archive/master.zip) of the project and install it manually by unzipping it somewhere on your disk and following these instructions:\nChrome: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked Microsoft Edge: https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/extension-sideloading 关于保存路径 默认配置是保存到本地的 也可以配置同步到github、网盘等 单个网页抓取 打开网页\u0026mdash;\u0026gt;点击插件图标\u0026mdash;\u0026gt;按照提示设置保存路径和文件名即可 最后为单一的html文件 没有别的依赖，基本全部保留了网页的结构和数据 批量网页抓取 打开侧边栏 添加所有url 批量抓取 ","permalink":"https://www.becool.vip/posts/tech/%E6%89%B9%E9%87%8F%E4%BF%9D%E5%AD%98%E5%AE%8C%E6%95%B4%E7%BD%91%E9%A1%B5%E6%95%B0%E6%8D%AE%E5%88%B0%E6%96%87%E4%BB%B6/","summary":"\u003ch1 id=\"安装\"\u003e安装\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/gildas-lormeau/SingleFile-MV3\"\u003ehttps://github.com/gildas-lormeau/SingleFile-MV3\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDownload the zip file (\u003ca href=\"https://github.com/gildas-lormeau/SingleFile-MV3/archive/master.zip\"\u003ehttps://github.com/gildas-lormeau/SingleFile-MV3/archive/master.zip\u003c/a\u003e) of the project and install it manually by unzipping it somewhere on your disk and following these instructions:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eChrome: \u003ca href=\"https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked\"\u003ehttps://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eMicrosoft Edge: \u003ca href=\"https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/extension-sideloading\"\u003ehttps://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/extension-sideloading\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"关于保存路径\"\u003e关于保存路径\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e默认配置是保存到本地的 也可以配置同步到github、网盘等\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"单个网页抓取\"\u003e单个网页抓取\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e打开网页\u0026mdash;\u0026gt;点击插件图标\u0026mdash;\u0026gt;按照提示设置保存路径和文件名即可\u003c/li\u003e\n\u003cli\u003e最后为单一的html文件 没有别的依赖，基本全部保留了网页的结构和数据\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"批量网页抓取\"\u003e批量网页抓取\u003c/h1\u003e\n\u003ch2 id=\"打开侧边栏\"\u003e打开侧边栏\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/singlefile%e6%8f%92%e4%bb%b6%e6%89%93%e5%bc%80%e4%be%a7%e8%be%b9%e6%a0%8f.jpg\" alt=\"\"  /\u003e\n\u003c/p\u003e","title":"批量保存完整网页数据到文件"},{"content":"Redis核心技术与实战学习笔记 最近想沉下心来看下redis，买了蒋德钧老师的《Redis 核心技术与实战》,这里记录一些学习笔记 希望能够坚持下去 有想一起学习的童鞋，可以点击跳转到文章尾部获取学习资源,仅供学习不要用于任何商业用途!!! redis知识全景图 学习要有一个全局的系统观念\nredis问题画像图 redis问题可以多套到这个图中分析形成统一的印象 问题 \u0026ndash;\u0026gt; 主线 \u0026ndash;\u0026gt; 技术点 SimpleKV需要考虑的问题 存什么样的数据\nKV对 key为string，value为基本类型 数据有哪些操作\n增删改查 PUT/GET/DEL/Scan 不论是增删改查都涉及到索引问题，因为要先定位到key对应的内存位置再读写 这里有涉及到索引 可以考虑全局hash 这里如果value是复杂结构 hash后还要再次用到不同的索引算法 如果是增和删除 又涉及到内存管理 因为需要分配内存或者释放内存，如果value大小不一致 内存分配算法至关重要 数据存在哪里\n存在内存比较快 但内存没了容易丢失，所以还要解决持久化的问题，每次都落地则性能不行，可定时落地到文件 客户端怎么访问\n.so动态连接库 单机访问 socket连接 可以跨机器 但要解决连接管理，协议解析 多个客户端并发访问 如何高性能 涉及到IO模型 容灾怎么来做\n涉及到主从或者集群 重启后怎么快速初始化或者恢复\n也是涉及到持久化\nredis数据结构 值的数据类型 String、List、Hash、Set、sortedSet KV保存用到的数据结构 整体图示 在 Redis 3.0 版本中 List 对象的底层数据结构由「双向链表」或「压缩表列表」实现，但是在 3.2 版本之后，List 数据类型底层数据结构是由 quicklist 实现的；\n在最新的 Redis 代码（还未发布正式版本）中，压缩列表数据结构已经废弃了，交由 listpack 数据结构来实现了\n全局Hash索引示意 hash冲突解决办法 链式hash 冲突的时候用链表的方式保存冲突的数据，所以多个冲突的数据要遍历就要逐个遍历了\n2个全局hash，渐进式rehash\n直接全量rehash涉及大量内存复制操作，可能阻塞客户端请求处理，所以采取的是渐进式rehash\nredis数据结构全景图_xiaolin redisDb 结构，表示 Redis 数据库的结构，结构体里存放了指向了 dict 结构的指针；\ndict 结构，结构体里存放了 2 个哈希表，正常情况下都是用「哈希表1」，「哈希表2」只有在 rehash 的时候才用，具体什么是 rehash，我在本文的哈希表数据结构会讲；\nditctht 结构，表示哈希表的结构，结构里存放了哈希表数组，数组中的每个元素都是指向一个哈希表节点结构（dictEntry）的指针；\ndictEntry 结构，表示哈希表节点的结构，结构里存放了 void * key 和 void * value 指针， *key 指向的是 String 对象，而 *value 则可以指向 String 对象，也可以指向集合类型的对象，比如 List 对象、Hash 对象、Set 对象和 Zset 对象\nvoid * key 和 void * value 指针指向的是 Redis 对象\nredisObject数据结构示意 type，标识该对象是什么类型的对象（String 对象、 List 对象、Hash 对象、Set 对象和 Zset 对象）；\nencoding，标识该对象使用了哪种底层的数据结构；\nptr，指向底层数据结构的指针\n简单字符串SDS(simple dynamic string) SDS数据结构 struct SDS{ len //这样获取字符串长度的时候，只需要返回这个成员变量值就行，时间复杂度只需要 O（1） alloc //分配空间长度 类似capicity 分配给字符数组的空间长度。这样在修改字符串的时候， 可以通过 alloc - len 计算出剩余的空间大小，可以用来判断空间是否满足修改需求，如果不满足的话，就会自动将 SDS 的空间\t扩展至执行修改所需的大小，然后才执行实际的修改操作，所以使用 SDS 既不需要手动修改 SDS 的空间大小，也不会出现前面所说\t的缓冲区溢出的问题 当判断出缓冲区大小不够用时，Redis 会自动将扩大 SDS 的空间大小（小于 1MB 翻倍扩容，大于 1MB 按 1MB 扩容 flag //用来表示不同类型的 SDS。一共设计了 5 种类型，分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64 每种类型占用内存不一样 更加灵活 而且是禁止内存字节对齐的，节省内存 buf[] 字符数组，用来保存实际数据。不仅可以保存字符串，也可以保存二进制数据 } 链表List typedef struct listNode { //前置节点 struct listNode *prev; //后置节点 struct listNode *next; //节点的值 void *value; } listNode; 在这个基础上增加 typedef struct list { //链表头节点 listNode *head; //链表尾节点 listNode *tail; //节点值复制函数 void *(*dup)(void *ptr); //节点值释放函数 void (*free)(void *ptr); //节点值比较函数 int (*match)(void *ptr, void *key); //链表节点数量 unsigned long len; } list; Redis 的链表实现优点如下：\nlistNode 链表节点的结构里带有 prev 和 next 指针，获取某个节点的前置节点或后置节点的时间复杂度只需O(1)，而且这两个指针都可以指向 NULL，所以链表是无环链表； list 结构因为提供了表头指针 head 和表尾节点 tail，所以获取链表的表头节点和表尾节点的时间复杂度只需O(1)； list 结构因为提供了链表节点数量 len，所以获取链表中的节点数量的时间复杂度只需O(1)； listNode 链表节使用 void* 指针保存节点值，并且可以通过 list 结构的 dup、free、match 函数指针为节点设置该节点类型特定的函数，因此链表节点可以保存各种不同类型的值； 链表的缺陷也是有的：\n链表每个节点之间的内存都是不连续的，意味着无法很好利用 CPU 缓存。能很好利用 CPU 缓存的数据结构就是数组，因为数组的内存是连续的，这样就可以充分利用 CPU 缓存来加速访问。 还有一点，保存一个链表节点的值都需要一个链表节点结构头的分配，内存开销较大。 因此，Redis 3.0 的 List 对象在数据量比较少的情况下，会采用「压缩列表」作为底层数据结构的实现，它的优势是节省内存空间，并且是内存紧凑型的数据结构。\n不过，压缩列表存在性能问题（具体什么问题，下面会说），所以 Redis 在 3.2 版本设计了新的数据结构 quicklist，并将 List 对象的底层数据结构改由 quicklist 实现。\n然后在 Redis 5.0 设计了新的数据结构 listpack，沿用了压缩列表紧凑型的内存布局，最终在最新的 Redis 版本，将 Hash 对象和 Zset 对象的底层数据结构实现之一的压缩列表，替换成由 listpack 实现。\n压缩列表 压缩列表的最大特点，就是它被设计成一种内存紧凑型的数据结构，占用一块连续的内存空间，不仅可以利用 CPU 缓存，而且会针对不同长度的数据，进行相应编码，这种方法可以有效地节省内存开销。\n但是，压缩列表的缺陷也是有的：\n不能保存过多的元素，否则查询效率就会降低； 新增或修改某个元素时，压缩列表占用的内存空间需要重新分配，甚至可能引发连锁更新的问题。 因此，Redis 对象（List 对象、Hash 对象、Zset 对象）包含的元素数量较少，或者元素值不大的情况才会使用压缩列表作为底层数据结构。\n*zlbytes*，记录整个压缩列表占用对内存字节数；\n*zltail*，记录压缩列表「尾部」节点距离起始地址由多少字节，也就是列表尾的偏移量；\n*zllen*，记录压缩列表包含的节点数量；\n*zlend*，标记压缩列表的结束点，固定值 0xFF（十进制255）。\n*prevlen*，记录了「前一个节点」的长度；\n*encoding*，记录了当前节点实际数据的类型以及长度；\n*data*，记录了当前节点的实际数据；\n当我们往压缩列表中插入数据时，压缩列表就会根据数据是字符串还是整数，以及数据的大小，会使用不同空间大小的 prevlen 和 encoding 这两个元素里保存的信息，这种根据数据大小和类型进行不同的空间大小分配的设计思想，正是 Redis 为了节省内存而采用的。\n分别说下，prevlen 和 encoding 是如何根据数据的大小和类型来进行不同的空间大小分配。\n压缩列表里的每个节点中的 prevlen 属性都记录了「前一个节点的长度」，而且 prevlen 属性的空间大小跟前一个节点长度值有关，比如：\n如果前一个节点的长度小于 254 字节，那么 prevlen 属性需要用 1 字节的空间来保存这个长度值； 如果前一个节点的长度大于等于 254 字节，那么 prevlen 属性需要用 5 字节的空间来保存这个长度值； encoding 属性的空间大小跟数据是字符串还是整数，以及字符串的长度有关：\n如果当前节点的数据是整数，则 encoding 会使用 1 字节的空间进行编码。 如果当前节点的数据是字符串，根据字符串的长度大小，encoding 会使用 1 字节/2字节/5字节的空间进行编码。 连锁更新 压缩列表除了查找复杂度高的问题，还有一个问题。\n压缩列表新增某个元素或修改某个元素时，如果空间不不够，压缩列表占用的内存空间就需要重新分配。而当新插入的元素较大时，可能会导致后续元素的 prevlen 占用空间都发生变化，从而引起「连锁更新」问题，导致每个元素的空间都要重新分配，造成访问压缩列表性能的下降。\n前面提到，压缩列表节点的 prevlen 属性会根据前一个节点的长度进行不同的空间大小分配：\n如果前一个节点的长度小于 254 字节，那么 prevlen 属性需要用 1 字节的空间来保存这个长度值； 如果前一个节点的长度大于等于 254 字节，那么 prevlen 属性需要用 5 字节的空间来保存这个长度值； 现在假设一个压缩列表中有多个连续的、长度在 250～253 之间的节点\n​ 因为这些节点长度值小于 254 字节，所以 prevlen 属性需要用 1 字节的空间来保存这个长度值\n这时，如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点，即新节点将成为 e1 的前置节点\ne1 原本的长度在 250～253 之间，因为刚才的扩展空间，此时 e1 的长度就大于等于 254 了，因此原本 e2 保存 e1 的 prevlen 属性也必须从 1 字节扩展至 5 字节大小。\n正如扩展 e1 引发了对 e2 扩展一样，扩展 e2 也会引发对 e3 的扩展，而扩展 e3 又会引发对 e4 的扩展…. 一直持续到结尾。\n这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」，就像多米诺牌的效应一样，第一张牌倒下了，推动了第二张牌倒下；第二张牌倒下，又推动了第三张牌倒下….，\n压缩列表的缺陷 空间扩展操作也就是重新分配内存，因此连锁更新一旦发生，就会导致压缩列表占用的内存空间要多次重新分配，这就会直接影响到压缩列表的访问性能。\n所以说，虽然压缩列表紧凑型的内存布局能节省内存开销，但是如果保存的元素数量增加了，或是元素变大了，会导致内存重新分配，最糟糕的是会有「连锁更新」的问题。\n因此，压缩列表只会用于保存的节点数量不多的场景，只要节点数量足够小，即使发生连锁更新，也是能接受的。\n虽说如此，Redis 针对压缩列表在设计上的不足，在后来的版本中，新增设计了两种数据结构：quicklist（Redis 3.2 引入） 和 listpack（Redis 5.0 引入）。这两种数据结构的设计目标，就是尽可能地保持压缩列表节省内存的优势，同时解决压缩列表的「连锁更新」的问题。\n仅供学习 切记用于任何商业用途 关注 _微_信_公_众_号 “疯子爱淡定” 回复 “Redis核心技术学习”\nHash typedef struct dict { … //两个Hash表，交替使用，用于rehash操作 dictht ht[2]; … } dict; ----------------------------\u0026gt; typedef struct dictht { //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码，用于计算索引值 unsigned long sizemask; //该哈希表已有的节点数量 unsigned long used; } dictht; ----------------------------\u0026gt; typedef struct dictEntry { //键值对中的键 void *key; //键值对中的值 union { void *val; uint64_t u64; int64_t s64; double d; } v; //指向下一个哈希表节点，形成链表 struct dictEntry *next; } dictEntry; 负载因子=已保存节点数量/hash表大小 当负载因子大于等于 1 ，并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令，也就是没有执行 RDB 快照或没有进行 AOF 重写的时候，就会进行 rehash 操作。 当负载因子大于等于 5 时，此时说明哈希冲突非常严重了，不管有没有有在执行 RDB 快照或 AOF 重写，都会强制进行 rehash 操作 ","permalink":"https://www.becool.vip/posts/tech/redis%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E4%B8%8E%E5%AE%9E%E6%88%98%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","summary":"\u003ch1 id=\"redis核心技术与实战学习笔记\"\u003eRedis核心技术与实战学习笔记\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e最近想沉下心来看下redis，买了蒋德钧老师的《Redis 核心技术与实战》,这里记录一些学习笔记 希望能够坚持下去\u003c/li\u003e\n\u003cli\u003e有想一起学习的童鞋，可以\u003ca href=\"#jumpdownload\"\u003e点击跳转到文章尾部获取学习资源,仅供学习不要用于任何商业用途!!!\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"redis知识全景图\"\u003eredis知识全景图\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e学习要有一个全局的系统观念\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/redis%e7%9f%a5%e8%af%86%e5%85%a8%e6%99%af%e5%9b%be.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"redis问题画像图\"\u003eredis问题画像图\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003eredis问题可以多套到这个图中分析形成统一的印象\u003c/li\u003e\n\u003cli\u003e问题 \u0026ndash;\u0026gt; 主线 \u0026ndash;\u0026gt; 技术点\u003c/li\u003e\n\u003cli\u003e\u003cimg loading=\"lazy\" src=\"/img/redis%e9%97%ae%e9%a2%98%e7%94%bb%e5%83%8f%e5%9b%be.png\" alt=\"\"  /\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"simplekv需要考虑的问题\"\u003eSimpleKV需要考虑的问题\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e存什么样的数据\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eKV对 key为string，value为基本类型\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e数据有哪些操作\u003c/p\u003e","title":"Redis核心技术与实战学习笔记"},{"content":"个人观感 浮躁的社会,总是像快进，放视频也是至少1.5倍速，很少能沉下心来欣赏一步作品\n这次是出差过程中,一个人在酒店沉下心来,没有快进，慢慢的看完了这部剧，很多地方挺有感触，值得一看\n本人不是专业点评人，只是这部剧确实让我内心有很多触动，我也确实像真正的慢下来，感受生活\nPS: 没有给爱奇艺做贡献,惭愧惭愧,个人是把我的阿勒泰4K视频拉到了本地电脑，需要免费拉取的童鞋可以直接移步到文章最后\n我的阿勒泰中的那些经典台词 再颠簸的生活，也要闪亮地过呀！ 啥叫有用？李文秀，生你下来是为了让你服务别人的？你看看草原上的这个树啊，草啊，有人吃，有人用，便叫有用；要是没有人用，它就这么待在草原上也挺好嘛，自由自在的嘛！ 家里练不出千里马，花盆里栽不出万年松，孩子们出去走走看看，也是一件好事。 不要总在过去的回忆里缠绵，不要总是想让昨天的阴雨淋湿今天的行装。昨天的太阳，晒不干今天的衣裳。 生命自己会寻找出路，因为只有在无际的弯路中，才会有更多的机会不停地靠近世界的种种真实之处，才会有强大生活的强大根基。 世界就在手边，躺倒就是睡眠，嘴里吃的是食物，身上裹的是衣服，在这里，我不知道还能有什么遗憾。 哈萨克文化里，人与人之间产生友情或者爱情，是由于被看见。所以在哈萨克语中，我喜欢你，意思是我清楚地看见你。 这深山里的稀薄社会的确没有过被明确监督着的秩序。一切全靠心灵的自我约束。那种人与人相互间、人和自然之间的本能的相互需求所进行的制约是有限的，却也是足够的。 可是传统不是一直都是那样的，没有什么是一成不变的，只有一直变化才是不变的。现在这个时代，又是一个变化，适应新的时代，调整生活才是正经，固守旧的传统，不见得就是对的。 他喜欢你很正常，因为我也喜欢你，我知道你的好。 这荒野真是不讲道理。但慢慢地，这荒野又会让你觉得自己曾努力去明白的那些道理也许才是真正没道理的。 生活在前方牵拽，命运的暗流在庞杂浩荡的人间穿梭进退，见缝插针，摸索前行。 ","permalink":"https://www.becool.vip/posts/read/%E6%88%91%E7%9A%84%E9%98%BF%E5%8B%92%E6%B3%B0/","summary":"\u003ch1 id=\"个人观感\"\u003e个人观感\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e浮躁的社会,总是像快进，放视频也是至少1.5倍速，很少能沉下心来欣赏一步作品\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e这次是出差过程中,一个人在酒店沉下心来,没有快进，慢慢的看完了这部剧，很多地方挺有感触，值得一看\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e本人不是专业点评人，只是这部剧确实让我内心有很多触动，我也确实像真正的慢下来，感受生活\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ePS: 没有给爱奇艺做贡献,惭愧惭愧,个人是把\u003cstrong\u003e我的阿勒泰4K\u003c/strong\u003e视频拉到了本地电脑，需要免费拉取的童鞋可以直接移步到文章最后\u003c/p\u003e","title":"我的阿勒泰"},{"content":"玻璃杯罩住的高低蜡烛谁先灭? 大致场景 大致原理 蜡烛燃烧需要氧气，同时会释放二氧化碳 但蜡烛燃烧特定场景下，释放的二氧化碳温度很高，密度比空气底 所以就会有上传的趋势(水高温变成水蒸气也是会往上升的) 持续的燃烧，二氧化碳不停的往上聚集，最终高蜡烛周围的空气先被二氧化碳包围，因为没有氧气所以就先熄灭了 室内火灾哪里氧气相对较多? 拿个湿毛巾捂住口鼻(避免烟呛到)，趴在地上(地面的氧气更多，站起来二氧化碳更多[高蜡烛先熄灭]) ","permalink":"https://www.becool.vip/posts/life/%E5%83%8F%E4%B9%8C%E9%B8%A6%E4%B8%80%E6%A0%B7%E6%80%9D%E8%80%83%E7%AC%AC1%E9%9B%86/","summary":"\u003ch1 id=\"玻璃杯罩住的高低蜡烛谁先灭\"\u003e玻璃杯罩住的高低蜡烛谁先灭?\u003c/h1\u003e\n\u003ch2 id=\"大致场景\"\u003e大致场景\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/%e7%8e%bb%e7%92%83%e6%9d%af%e7%bd%a9%e4%bd%8f%e9%ab%98%e4%bd%8e2%e6%a0%b9%e8%9c%a1%e7%83%9b.jpg\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003ch2 id=\"大致原理\"\u003e大致原理\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e蜡烛燃烧需要氧气，同时会释放二氧化碳\u003c/li\u003e\n\u003cli\u003e但蜡烛燃烧特定场景下，释放的二氧化碳温度很高，密度比空气底\u003c/li\u003e\n\u003cli\u003e所以就会有上传的趋势(水高温变成水蒸气也是会往上升的)\u003c/li\u003e\n\u003cli\u003e持续的燃烧，二氧化碳不停的往上聚集，最终高蜡烛周围的空气先被二氧化碳包围，因为没有氧气所以就先熄灭了\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"室内火灾哪里氧气相对较多\"\u003e室内火灾哪里氧气相对较多?\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e拿个湿毛巾捂住口鼻(避免烟呛到)，趴在地上(地面的氧气更多，站起来二氧化碳更多[高蜡烛先熄灭])\u003c/li\u003e\n\u003c/ul\u003e","title":"像乌鸦一样思考S01E01"},{"content":"个人博客评论诉求 能简单的在页面看到评论即可 能找到评论人的邮箱 评论可以折叠 能私有化部署 之前用过twikoo,个人体验没有isso丝滑简洁 私有化安装isso 官网 https://isso-comments.de/ https://github.com/isso-comments/isso 部署方法 服务端安装python, pip 通过以下命令安装isso sudo apt install python3-pip sqlite3 build-essential pip install --upgrade pip pip install isso pip install gevent 建立目录 mkdir /home/ubuntu/isso 新增配置 isso.cfg 支持新新评论发送邮箱提醒 个人目前没用 有新评论输出到本地日志 可以在浏览器看到所有评论信息 本地存储路径/home/ubuntu/isso/isso.cfg [general] # 数据库文件位置 dbpath = /home/ubuntu/isso/comments.db # 你准备部署的主机域名，多个域名用换行隔开，例如 host = https://becool.vip # 部署多个 isso 需要用到，不然可以删除 # 允许用户修改/删除评论的最长时间 max-age = 5m # 新评论提醒方式，默认为 stdout，我这里选择通过邮件提醒，后面需要设置 smtp 信息 notify = stdout # 日志文件，可以不开启 log-file = /home/ubuntu/isso/isso.log # 使用 Gravatar 头像，如果评论者没有设置邮箱会随机生成 gravatar = true gravatar-url = https://cn.gravatar.com/avatar/{}?d=identicon [moderation] # 是否开始评论审核，以及多少天未审核的评论自动删除 enabled = false purge-after = 30d [server] # 需要监听的地址 listen = http://localhost:8080/ [smtp] # smtp 设置，如果前面未选择可以删除 username = password = host = port = security = to = from = timeout = [guard] # 开启 Spam 过滤 enabled = true # 每分钟最多评论数 ratelimit = 2 # 评论最多回复次数 direct-reply = 3 # 能否回复自己的评论 reply-to-self = false # 评论必须输入用户名 require-author = true # 评论必须输入邮箱 require-email = true [hash] # 加密邮箱地址的方式 salt = Eech7co8Ohloopo9Ol6baimi algorithm = pbkdf2 [admin] # 是否开启后台管理，开启后通过 your-url/admin 访问 enabled = true password = 密码 启动 nohup /home/ubuntu/.local/bin/isso -c /home/ubuntu/isso/isso.cfg run \u0026gt;/home/ubuntu/isso/isso.log 2\u0026gt;\u0026amp;1 \u0026amp; nginx代理配置 修改nginx配置 在server配置中加入以下内容\nlocation /isso { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Script-Name /isso; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://localhost:8080; } 重启nginx\nsudo systemctl restart nginx 验证 浏览器打开 https://becool.vip/isso/ 有下面提示说明成功\nBad Request missing uri query 浏览器打开 https://becool.vip/isso/admin/ 输入密码可以管理评论\nhugo配置 主题 个人使用的papermod主题，如果你是从头搭建，也想用papermod主题 建议直接git clone https://github.com/xyming108/sulv-hugo-papermod 然后直接使用即可 集成isso 在你主题能够生成评论的地方 放入以下内容 我这边\nyourBlogDir/layouts/partials/comments.html 注意替换域名\n\u0026lt;div\u0026gt; \u0026lt;script data-isso=\u0026#34;https://becool.vip/isso/\u0026#34; data-isso-id=\u0026#34;thread-id\u0026#34; data-isso-css=\u0026#34;true\u0026#34; data-isso-lang=\u0026#34;zh\u0026#34; data-isso-reply-to-self=\u0026#34;false\u0026#34; data-isso-require-author=\u0026#34;true\u0026#34; data-isso-require-email=\u0026#34;true\u0026#34; data-isso-max-comments-top=\u0026#34;10\u0026#34; data-isso-max-comments-nested=\u0026#34;5\u0026#34; data-isso-reveal-on-click=\u0026#34;5\u0026#34; data-isso-avatar=\u0026#34;true\u0026#34; data-isso-avatar-bg=\u0026#34;#f0f0f0\u0026#34; data-isso-avatar-fg=\u0026#34;#9abf88 #5698c4 #e279a3 #9163b6 ...\u0026#34; data-isso-vote=\u0026#34;true\u0026#34; data-isso-vote-levels=\u0026#34;\u0026#34; src=\u0026#34;https://becool.vip/isso/js/embed.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;section id=\u0026#34;isso-thread\u0026#34;\u0026gt;\u0026lt;/section\u0026gt; \u0026lt;/div\u0026gt; 用hugo命令 重新生成静态网页即可\ncd YourBlogDir; sudo /home/ubuntu/shell/hugo --buildDrafts 大致效果 评论效果 管理界面 评论数据存放位置及日志 配置文件有指定对应db目录\n/home/ubuntu/isso txy\u0026gt;ll total 32K -rw-r--r-- 1 ubuntu root 24K Jul 19 17:36 comments.db -rw-r--r-- 1 ubuntu root 1.5K Jul 19 17:18 isso.cfg -rw-r--r-- 1 ubuntu root 2.0K Jul 19 17:37 isso.log ","permalink":"https://www.becool.vip/posts/tech/hugo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0isso%E8%AF%84%E8%AE%BA/","summary":"\u003ch1 id=\"个人博客评论诉求\"\u003e个人博客评论诉求\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e能简单的在页面看到评论即可\u003c/li\u003e\n\u003cli\u003e能找到评论人的邮箱\u003c/li\u003e\n\u003cli\u003e评论可以折叠\u003c/li\u003e\n\u003cli\u003e能私有化部署 之前用过twikoo,个人体验没有isso丝滑简洁\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"私有化安装isso\"\u003e私有化安装isso\u003c/h1\u003e\n\u003ch2 id=\"官网\"\u003e官网\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://isso-comments.de/\"\u003ehttps://isso-comments.de/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/isso-comments/isso\"\u003ehttps://github.com/isso-comments/isso\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"部署方法\"\u003e部署方法\u003c/h2\u003e\n\u003ch3 id=\"服务端安装python-pip\"\u003e服务端安装python, pip\u003c/h3\u003e\n\u003ch3 id=\"通过以下命令安装isso\"\u003e通过以下命令安装isso\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install python3-pip sqlite3 build-essential\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epip install --upgrade pip\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epip install isso\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epip install gevent\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"建立目录\"\u003e建立目录\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir /home/ubuntu/isso\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"新增配置-issocfg\"\u003e新增配置 isso.cfg\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e支持新新评论发送邮箱提醒 个人目前没用 有新评论输出到本地日志 可以在浏览器看到所有评论信息\u003c/li\u003e\n\u003cli\u003e本地存储路径/home/ubuntu/isso/isso.cfg\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[general]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 数据库文件位置\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edbpath = /home/ubuntu/isso/comments.db\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 你准备部署的主机域名，多个域名用换行隔开，例如\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehost = https://becool.vip\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 部署多个 isso 需要用到，不然可以删除\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 允许用户修改/删除评论的最长时间\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emax-age = 5m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 新评论提醒方式，默认为 stdout，我这里选择通过邮件提醒，后面需要设置 smtp 信息\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enotify = stdout\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 日志文件，可以不开启\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elog-file = /home/ubuntu/isso/isso.log\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 使用 Gravatar 头像，如果评论者没有设置邮箱会随机生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egravatar = true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egravatar-url = https://cn.gravatar.com/avatar/{}?d=identicon\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[moderation]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 是否开始评论审核，以及多少天未审核的评论自动删除\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eenabled = false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epurge-after = 30d\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[server]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 需要监听的地址\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elisten = http://localhost:8080/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[smtp]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# smtp 设置，如果前面未选择可以删除\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eusername =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epassword =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehost =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eport =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esecurity =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eto =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efrom =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etimeout =\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[guard]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 开启 Spam 过滤\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eenabled = true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 每分钟最多评论数\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eratelimit = 2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 评论最多回复次数\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edirect-reply = 3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 能否回复自己的评论\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ereply-to-self = false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 评论必须输入用户名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erequire-author = true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 评论必须输入邮箱\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erequire-email = true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[hash]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 加密邮箱地址的方式\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esalt = Eech7co8Ohloopo9Ol6baimi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ealgorithm = pbkdf2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 是否开启后台管理，开启后通过 your-url/admin 访问\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eenabled = true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epassword = 密码\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"启动\"\u003e启动\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enohup /home/ubuntu/.local/bin/isso -c /home/ubuntu/isso/isso.cfg run \u0026gt;/home/ubuntu/isso/isso.log 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"nginx代理配置\"\u003enginx代理配置\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e修改nginx配置 在server配置中加入以下内容\u003c/p\u003e","title":"hugo博客添加isso评论"},{"content":"关于go test的一些说明 golang安装后可以使用go test工具进行单元测试 代码片段对比的性能测试,使用起来还是比较方便,下面是一些应用场景 平时自己想做一些简单函数的单元测试，不用每次都新建一个main.go 然后go run main.go 相对某个功能做下性能测试 看下cpu/内存消耗情况 可以针对性的建立一些目录 存放自己的一些测试代码 一些使用规则 创建代码保存目录 这里以mytest目录为例 目录下的文件名都要以_test.go结尾 比如common_test.go package mytest 不要为main import testing包 普通单元测试 函数名以Test开头 性能测试 函数名称以Benchmark开头 目录下面函数名不要重名 普通单元测试常见用法 样例代码 common_test.go文件内容如下 一个加法函数一个乘法函数\npackage mytest import \u0026#34;testing\u0026#34; // Add 加法 func Add(a int, b int) int { return a + b } // Mul 乘法 func Mul(a int, b int) int { return a * b } // 测试加法函数功能是否正常 正数 func TestAddPositive(t *testing.T) { if ans := Add(1, 2); ans != 3 { t.Fatalf(\u0026#34;1 + 2 expected be 3, but %d got\u0026#34;, ans) } t.Log(\u0026#34;TestAddPositive run Success\u0026#34;) } // 测试加法函数功能是否正常 负数 func TestAddNegative(t *testing.T) { if ans := Add(-10, -20); ans != -30 { t.Fatalf(\u0026#34;-10 + -20 expected be -30, but %d got\u0026#34;, ans) } t.Log(\u0026#34;TestAddNegative run Success\u0026#34;) } // 测试乘法函数功能是否正常 func TestMul(t *testing.T) { if ans := Mul(3, 4); ans != 12 { t.Fatalf(\u0026#34;3*4 expected be 12, but %d got\u0026#34;, ans) } t.Log(\u0026#34;TestMul run Success\u0026#34;) } 指定文件测试 命令 顺序测试文件中的所有函数\ngo test -v common_test.go 假设mytest目录下有多个xx_test.go文件 想指定common_test.go文件中的所有函数 输出\n=== RUN TestAddPositive common_test.go:20: TestAddPositive run Success --- PASS: TestAddPositive (0.00s) === RUN TestAddNegative common_test.go:28: TestAddNegative run Success --- PASS: TestAddNegative (0.00s) === RUN TestMul common_test.go:36: TestMul run Success --- PASS: TestMul (0.00s) PASS ok command-line-arguments\t0.309s 指定测试某一个函数 命令\ngo test -v -run TestAddPositive go test -run=TestAddPositive -v 指定只测试目录下的TestAddPositive函数 输出\n=== RUN TestAddPositive common_test.go:20: TestAddPositive run Success --- PASS: TestAddPositive (0.00s) PASS ok mytest\t0.307s 指定测试某一类函数 命令\ngo test -run=^TestAdd -v 可以使用正则表达式 指定一类函数,比如想测试所有TestAdd开头的函数 输出 会按照顺序测试目录下所有TestAdd打头的函数\n=== RUN TestAddPositive common_test.go:20: TestAddPositive run Success --- PASS: TestAddPositive (0.00s) === RUN TestAddNegative common_test.go:28: TestAddNegative run Success --- PASS: TestAddNegative (0.00s) PASS ok mytest\t0.359s 测试目录下所有文件中的全部函数 命令 会按照顺序测试目录下所有_test.go结尾的所有函数\ngo test -v . 性能测试的常见用法 样例代码 forRangeBenchMark_test.go样例代码如下\npackage mytest import \u0026#34;testing\u0026#34; // ************ benchmark range loop ************ type Person struct { name [4096]byte age int } var ( AllPerson [1024]Person ) // BenchmarkForIndexVisit AllPerson[i].xxx 使用下标方式访问 func BenchmarkForIndexVisit(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { var age int for i := 0; i \u0026lt; len(AllPerson); i++ { age = AllPerson[i].age } _ = age } } // BenchmarkRangeLoopVisit range循环的方式访问 func BenchmarkRangeLoopVisit(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { var age int for _, person := range AllPerson { age = person.age } _ = age } } -benchmem会打印内存申请信息,建议都打开这个选项 指定某一个文件测试 命令\ngo test -bench=. -benchmem forRangeBenchMark_test.go 输出\ngoos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz BenchmarkForIndexVisit-6 4570186\t246.8 ns/op\t0 B/op\t0 allocs/op BenchmarkRangeLoopVisit-6 4777\t228750 ns/op\t0 B/op\t0 allocs/op PASS ok command-line-arguments\t2.858s 这里可以看出直接for range的方式遍历性能很差 因为for range会有一个临时变量复制的过程，这个过程比较消耗时间\n指定某一个函数测试 命令\ngo test -bench=BenchmarkRangeLoopVisit -benchmem 输出\ngoos: darwin goarch: amd64 pkg: mytest cpu: Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz BenchmarkRangeLoopVisit-6 4657\t345054 ns/op\t0 B/op\t0 allocs/op PASS ok mytest\t1.950s 指定某一类函数 命令\ngo test -bench=^BenchmarkRange -benchmem 所有以BenchmarkRange打头的都会测试 指定测试时间 命令\ngo test -bench=. -benchmem forRangeBenchMark_test.go -benchtime=5s 默认1s 下面指定运行时间为5s 输出\ngoos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz BenchmarkForIndexVisit-6 24010926\t248.3 ns/op\t0 B/op\t0 allocs/op BenchmarkRangeLoopVisit-6 24183\t242653 ns/op\t0 B/op\t0 allocs/op PASS ok command-line-arguments\t14.918s BenchmarkForIndexVisit函数在5s内运行了24010926次 每次大概耗时 248.3 ns BenchmarkRangeLoopVisit函数在5s内运行了24183次 每次大概耗时 242653 ns 性能相当差 指定测试次数 命令\ngo test -bench=. -benchmem forRangeBenchMark_test.go -benchtime=10000x 输出\ngoos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz BenchmarkForIndexVisit-6 10000\t246.6 ns/op\t0 B/op\t0 allocs/op BenchmarkRangeLoopVisit-6 10000\t300624 ns/op\t0 B/op\t0 allocs/op PASS ok command-line-arguments\t3.347s 测试过程也可以看到BenchmarkRangeLoopVisit运行耗时很长,而BenchmarkForIndexVisit很快就结束了 性能测试-timer相关api ResetTimer 如果在 benchmark 开始前，需要一些准备工作，如果准备工作比较耗时，则需要将这部分代码的耗时忽略掉 StopTimer \u0026amp; StartTimer 每次函数调用前后需要一些准备工作和清理工作，我们可以使用 StopTimer 暂停计时以及使用 StartTimer 开始计时\nfunc Benchmarkxxx(b *testing.B) { for n := 0; n \u0026lt; b.N; n++ { b.StopTimer() // 准备数据 b.StartTimer() Sort(nums) } } func Benchmarkxxx(b *testing.B) { //准备工作部分代码 b.ResetTimer() // 重置定时器 for n := 0; n \u0026lt; b.N; n++ { fib(30) // run fib(30) b.N times } } ","permalink":"https://www.becool.vip/posts/tech/golang%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E5%B8%B8%E8%A7%81%E7%94%A8%E6%B3%95/","summary":"\u003ch1 id=\"关于go-test的一些说明\"\u003e关于go test的一些说明\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003egolang安装后可以使用go test工具进行单元测试 代码片段对比的性能测试,使用起来还是比较方便,下面是一些应用场景\n\u003cul\u003e\n\u003cli\u003e平时自己想做一些简单函数的单元测试，不用每次都新建一个main.go 然后go run main.go\u003c/li\u003e\n\u003cli\u003e相对某个功能做下性能测试 看下cpu/内存消耗情况\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e可以针对性的建立一些目录 存放自己的一些测试代码\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e一些使用规则\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e创建代码保存目录 这里以\u003ccode\u003emytest\u003c/code\u003e目录为例\u003c/li\u003e\n\u003cli\u003e目录下的文件名都要以\u003ccode\u003e_test.go\u003c/code\u003e结尾 比如\u003ccode\u003ecommon_test.go\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003epackage mytest 不要为main\u003c/li\u003e\n\u003cli\u003eimport testing包\u003c/li\u003e\n\u003cli\u003e普通单元测试 函数名以Test开头\u003c/li\u003e\n\u003cli\u003e性能测试 函数名称以Benchmark开头\u003c/li\u003e\n\u003cli\u003e目录下面函数名不要重名\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"普通单元测试常见用法\"\u003e普通单元测试常见用法\u003c/h1\u003e\n\u003ch2 id=\"样例代码\"\u003e样例代码\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003ecommon_test.go\u003c/code\u003e文件内容如下 一个加法函数一个乘法函数\u003c/p\u003e","title":"golang单元测试性能测试常见用法"},{"content":"脚本统计空间被谁占用了 #!/bin/bash # 指定要检查的目录 target_directory=\u0026#34;/\u0026#34; # 指定要显示的文件和目录数量 num_files=10 num_directories=10 # 打印磁盘空间使用情况 df -h # 打印占用空间最大的目录 echo \u0026#34;Top $num_directories Directories:\u0026#34; du -ah $target_directory | sort -rh | head -n $num_directories # 打印占用空间最大的文件 echo \u0026#34;Top $num_files Files:\u0026#34; find $target_directory -type f -exec du -Sh {} + | sort -rh | head -n $num_files mysql数据清理 mysql数据占用空间较大 170G\t/data 140G\t/data/mysql-data 3.2G\t/data/mysql-data/VM-100-15-centos.err 1.1G\t/data/mysql-data/VM-100-15-centos-bin.000112 1.1G\t/data/mysql-data/VM-100-15-centos-bin.000111 1.1G\t/data/mysql-data/VM-100-15-centos-bin.000110 1.1G\t/data/mysql-data/VM-100-15-centos-bin.000109 1.1G\t/data/mysql-data/VM-100-15-centos-bin.000108 ... 这些都是mysql的二进制文件 修改配置文件 my.cnf或my.ini binlog过期时间 log_bin = /path/to/binlog expire_logs_days = N mysql客户端手工删除二进制文件 删除指定文件及其之前的所有二进制日志文件 PURGE BINARY LOGS TO \u0026#39;VM-100-15-centos-bin.000110\u0026#39;; 重启mysql sudo service mysql restart ","permalink":"https://www.becool.vip/posts/tech/linux%E7%A3%81%E7%9B%98%E7%A9%BA%E9%97%B4%E7%B4%A7%E5%BC%A0%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95/","summary":"\u003ch1 id=\"脚本统计空间被谁占用了\"\u003e脚本统计空间被谁占用了\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# 指定要检查的目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etarget_directory\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 指定要显示的文件和目录数量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enum_files\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enum_directories\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 打印磁盘空间使用情况\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edf -h\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 打印占用空间最大的目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Top \u003c/span\u003e$num_directories\u003cspan style=\"color:#e6db74\"\u003e Directories:\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edu -ah $target_directory | sort -rh | head -n $num_directories\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 打印占用空间最大的文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Top \u003c/span\u003e$num_files\u003cspan style=\"color:#e6db74\"\u003e Files:\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efind $target_directory -type f -exec du -Sh \u003cspan style=\"color:#f92672\"\u003e{}\u003c/span\u003e + | sort -rh | head -n $num_files\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"mysql数据清理\"\u003emysql数据清理\u003c/h1\u003e\n\u003ch2 id=\"mysql数据占用空间较大\"\u003emysql数据占用空间较大\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e170G\t/data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e140G\t/data/mysql-data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e3.2G\t/data/mysql-data/VM-100-15-centos.err\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1.1G\t/data/mysql-data/VM-100-15-centos-bin.000112\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1.1G\t/data/mysql-data/VM-100-15-centos-bin.000111\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1.1G\t/data/mysql-data/VM-100-15-centos-bin.000110\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1.1G\t/data/mysql-data/VM-100-15-centos-bin.000109\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1.1G\t/data/mysql-data/VM-100-15-centos-bin.000108\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e... 这些都是mysql的二进制文件\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"修改配置文件-mycnf或myini-binlog过期时间\"\u003e修改配置文件 my.cnf\u003ccode\u003e或\u003c/code\u003emy.ini binlog过期时间\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elog_bin = /path/to/binlog\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexpire_logs_days = N\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"mysql客户端手工删除二进制文件\"\u003emysql客户端手工删除二进制文件\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e删除指定文件及其之前的所有二进制日志文件\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePURGE BINARY LOGS TO \u0026#39;VM-100-15-centos-bin.000110\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"重启mysql\"\u003e重启mysql\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo service mysql restart\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"linux磁盘空间紧张处理方法"},{"content":"安装git sudo apt update sudo apt install git 创建git用户 sudo adduser git 创建证书登录 收集所有需要登录的用户的公钥，就是他们自己的id_rsa.pub文件，把所有公钥导入到/home/git/.ssh/authorized_keys文件里，一行一个 或者在需要登录的客户端 执行以下命令 ssh-keygen -t rsa -b 4096 -C \u0026#34;your_email@example.com\u0026#34; ssh-copy-id git@your_server_ip 这里的作用也是将公钥匙复制到/home/git/.ssh/authorized_keys文件 创建仓库 下面例子为创建blog仓库 cd /home/git;sudo git init --bare blog.git;sudo chown -R git:git blog.git 禁用git用户ssh登录 可选 出于安全考虑，第二步创建的git用户不允许登录shell，这可以通过编辑/etc/passwd文件完成。找到类似下面的一行\ngit:x:1001:1001:,,,:/home/git:/bin/bash 将其改为\ngit:x:1001:1001:,,,:/home/git:/bin/bash 修改ssh配置 只允许使用公钥登录 建议 sudo vi /etc/ssh/sshd_config 确保以下配置项处于启用状态 2个配置按照顺序逐个修改，确保能用公钥登录后再禁用密码登录\nPubkeyAuthentication yes PasswordAuthentication no PubkeyAuthentication为是否允许pubkey登录 PasswordAuthentication为是否允许使用密码登录\nsudo systemctl restart ssh 重启ssh服务\n客户端测试git拉取提交是否成功 git clone git@xx.com:/home/git/blog.git 定期备份git项目源码 Backup.sh 将xxx.git项目打包 用网盘工具备份 每天定时备份一次 rm -rf /tmp/gitbackup/blog.git.tgz rm -rf /tmp/gitbackup/golang.git.tgz rm -rf /tmp/gitbackup/becool.git.tgz sudo tar -czvf /tmp/gitbackup/blog.git.tgz /home/git/blog.git \u0026gt;/tmp/blog.git.tar.log 2\u0026gt;\u0026amp;1 sudo tar -czvf /tmp/gitbackup/becool.git.tgz /home/git/becool.git \u0026gt;/tmp/becool.git.tar.log 2\u0026gt;\u0026amp;1 sudo tar -czvf /tmp/gitbackup/golang.git.tgz /home/git/golang.git \u0026gt;/tmp/golang.git.tar.log 2\u0026gt;\u0026amp;1 sudo chown -R ketonghe:ketonghe /tmp/gitbackup/* sudo /home/ketonghe/shell/189cloud backup /tmp/gitbackup/ /selfgit/ Crontab 配置 0 1 * * * /home/ketonghe/shell/backupSelfGit.sh \u0026gt; /home/ketonghe/shell/backupSelfGit.log 2\u0026gt;\u0026amp;1 ","permalink":"https://www.becool.vip/posts/tech/ubuntu%E5%AE%89%E8%A3%85git%E6%9C%8D%E5%8A%A1%E5%99%A8/","summary":"\u003ch1 id=\"安装git\"\u003e安装git\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"创建git用户\"\u003e创建git用户\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo adduser git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"创建证书登录\"\u003e创建证书登录\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e收集所有需要登录的用户的公钥，就是他们自己的id_rsa.pub文件，把所有公钥导入到/home/git/.ssh/authorized_keys文件里，一行一个\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e或者在需要登录的客户端 执行以下命令\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003essh-keygen -t rsa -b 4096 -C \u0026#34;your_email@example.com\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003essh-copy-id git@your_server_ip\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e这里的作用也是将公钥匙复制到/home/git/.ssh/authorized_keys文件\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"创建仓库\"\u003e创建仓库\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e下面例子为创建blog仓库\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd /home/git;sudo git init --bare blog.git;sudo chown -R git:git blog.git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"禁用git用户ssh登录-可选\"\u003e禁用git用户ssh登录 可选\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e出于安全考虑，第二步创建的git用户不允许登录shell，这可以通过编辑\u003ccode\u003e/etc/passwd\u003c/code\u003e文件完成。找到类似下面的一行\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit:x:1001:1001:,,,:/home/git:/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e将其改为\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit:x:1001:1001:,,,:/home/git:/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"修改ssh配置-只允许使用公钥登录-建议\"\u003e修改ssh配置 只允许使用公钥登录 建议\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003esudo vi /etc/ssh/sshd_config\u003c/code\u003e 确保以下配置项处于启用状态 2个配置按照顺序逐个修改，确保能用公钥登录后再禁用密码登录\u003c/p\u003e","title":"ubuntu安装git搭建自己专属的git服务器"},{"content":"小孩的一些问题 1 爸爸,什么是闰年? 2 为什么每个月天数还不一样的? 闰年到底是怎么来的? 小时候老师怎么说的 我已经忘记小时候老师怎么说的了,只大概记得貌似数学课上老师说过以下2种情况 不是整百的年份,如果除以4没有余数,那就是闰年，否则就是平年 2004为闰年 2005为平年 是整百的年份,如果除以400没有余数,那就是闰年,否则就是平年 2000就是闰年 2100就是平年 四年一闰，百年不闰，四百年再闰 可是为什么是这样呢? 我反正已经不记得我小时候是否问过为什么了,可能没有问过,或者问过但老师说记住就好了 可能国外很多小朋友都会问,从这一点看,现在的小朋友确实好很多了,至少很多都会问了,但老师是否会认真讲清楚也不一定 我自己是不太清楚,也没法说服小孩,所以上网谷歌了下，把个人理解的一些情况整理下 蔡国庆-《三百六十五个祝福》 一年有三百六十五个日出 我送你三百六十五个祝福 时钟每天转了一千四百四十圈 我的心每天都藏着 一千四百四十多个思念 ... 我小时候反正没少听到这首歌,但为什么一年有365天呢，为什么闰年又是366天呢？\n地球自转\u0026mdash;所谓一天就是这么被定义的 地球在宇宙中不断在自己转圈圈,完成1次自转大概为24小时 这就是1天 地球公转\u0026mdash;所谓一年就是这么被定义的 地球也在不停的绕着太阳转圈 这个叫公转 地球完成1次公转大概需要365天5小时48分46秒（合365.24219天）这个叫做一回归年（tropical year） 这个也是蔡国庆老师歌曲里面一年有三百六十五个日出的大致原因 闰年是个\u0026quot;人为定义\u0026quot;的数学问题 地球公转一次不是严格的365天，而是365.24天\n平年按照365天算，比回归年短约0.2422天\n一年少个0.24219天,累计下来大概4年就会少一天的时间出来\n所以在第4年的2月加上1天，这一年也就变成了366天,这一年也就变成了闰年\n0.24219*4=0.96876 如果再详细计算 其实4年是累积少了0.96876天 还不到严格的1天\n所以如果严格按照每四年一个闰年计算，平均每年就又要多算出0.0078天，这样，每128年就会多算出1天，经过400年就会多算出3天多。因此，每400年中又要减少3个闰年\n这就是为什么数学老师告诉我们**整百的年份,如果除以400没有余数,那就是闰年,否则就是平年**\n为什么每个月天数不一样呢? 月的概念是怎么来的 古代人类根据月亮的盈亏周期（约29.5天）来安排生活和劳动，逐渐形成了以月为基本单位的时间观念 周的概念是怎么来的 一个月中月相变化有四个阶段（新月到上弦月，再到满月，再到下弦月，再到残月），“月”的时间定下来之后，每个月相变化的时间也就定下来了，大约为7天,这就是“周”的概念. 每个月天数的变化历史 因为1年365天，一年又有12个月，每个月30天话 凑不够一年365或者366天，所以就有了大小月的概念 罗马凯撒大帝时期 单数月就是大月,偶数月就是小月 也就是 1、3、5、7、9、11 月份分别有31天 4、6、8、10、12 月份分别有30天 2月平年有29天，闰年有30天 另外古罗马时代每年统一在二月处决犯人，这种不吉利的月份自然是天数越少越好 凯撒大帝死后，他的继任者奥古斯都（屋大维）皇帝却因为自己的生日是在8月，便从2月中再抽出一天加在8月上，使8月也成为大月，即31天，享有与7月一样的待遇（7月是凯撒的生日所在月） 为了协调，大小月尽量错开，又不得不同时把8月以后的大、 小月全都反了过来，于是9月、11月变成了30天，而10月、12月则变成了31天 所以就变成了下面的情况 一直影响至今 1、3、 5、 7、 8、 10、 12(一三五七八十腊) 月份31天 4、6、9、11 月份30天 2月平年28天 闰年29天 七月的英文July源自凯撒的名字Julius，八月的英文August源自奥古斯都的名字Augustus，也跟这两个人息息相关 参考文献 https://en.wikipedia.org/wiki/Month ","permalink":"https://www.becool.vip/posts/life/%E5%85%AC%E5%8E%86%E4%B8%AD%E7%9A%84%E9%97%B0%E5%B9%B4%E6%98%AF%E6%80%8E%E4%B9%88%E6%9D%A5%E7%9A%84/","summary":"\u003ch1 id=\"小孩的一些问题\"\u003e小孩的一些问题\u003c/h1\u003e\n\u003ch2 id=\"1-爸爸什么是闰年\"\u003e1 爸爸,什么是闰年?\u003c/h2\u003e\n\u003ch2 id=\"2-为什么每个月天数还不一样的\"\u003e2 为什么每个月天数还不一样的?\u003c/h2\u003e\n\u003ch1 id=\"闰年到底是怎么来的\"\u003e闰年到底是怎么来的?\u003c/h1\u003e\n\u003ch2 id=\"小时候老师怎么说的\"\u003e小时候老师怎么说的\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e我已经忘记小时候老师怎么说的了,只大概记得貌似数学课上老师说过以下2种情况\n\u003cul\u003e\n\u003cli\u003e不是整百的年份,如果除以4没有余数,那就是闰年，否则就是平年\n\u003cul\u003e\n\u003cli\u003e2004为闰年\u003c/li\u003e\n\u003cli\u003e2005为平年\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e是整百的年份,如果除以400没有余数,那就是闰年,否则就是平年\n\u003cul\u003e\n\u003cli\u003e2000就是闰年\u003c/li\u003e\n\u003cli\u003e2100就是平年\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e四年一闰，百年不闰，四百年再闰\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"可是为什么是这样呢\"\u003e可是为什么是这样呢?\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e我反正已经不记得我小时候是否问过为什么了,可能没有问过,或者问过但老师说记住就好了\u003c/li\u003e\n\u003cli\u003e可能国外很多小朋友都会问,从这一点看,现在的小朋友确实好很多了,至少很多都会问了,但老师是否会认真讲清楚也不一定\u003c/li\u003e\n\u003cli\u003e我自己是不太清楚,也没法说服小孩,所以上网谷歌了下，把个人理解的一些情况整理下\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"蔡国庆-三百六十五个祝福\"\u003e蔡国庆-《三百六十五个祝福》\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e一年有三百六十五个日出\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我送你三百六十五个祝福\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e时钟每天转了一千四百四十圈\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我的心每天都藏着\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e一千四百四十多个思念\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e我小时候反正没少听到这首歌,但为什么一年有365天呢，为什么闰年又是366天呢？\u003c/p\u003e","title":"公历中的闰年到底是怎么来的?"},{"content":"背景 家里有台旧windows笔记本，PentiumB940 2.00GHz的cpu 4G内存，512G硬盘 放在家里吃灰很久,最近几个月折腾折腾，装了linux操作系统，换了一个2T的硬盘 这里记录下折腾的过程,有需要的可以参考 开通公网IP 打电话给运营商一般都可以免费开通 但这个公网IP(ipv4)不是一成不变的,一般都是会变化的 不过这里可以监控公网ip变化 直接修改dns解析就好 参考下方连接 https://becool.vip/posts/tech/golang%E7%9B%91%E6%8E%A7%E5%85%AC%E7%BD%91ip%E5%8F%98%E5%8C%96%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5dns%E8%A7%A3%E6%9E%90/ 个人宽带网关上网方式用桥接，然后路由器选择拨号，个人服务器直接网线连接路由器，路由器开启DMZ功能，将流量打向家里的旧电脑 搭建个人git服务器 访问github总是很慢，暂时也没弄科学上网 具体方法可参考下方连接 https://becool.vip/posts/tech/ubuntu%E5%AE%89%E8%A3%85git%E6%9C%8D%E5%8A%A1%E5%99%A8/ hugo搭建个人博客 目前个人有一台1核2G的腾讯云服务器 一个域名 最终静态页面是部署在腾讯云服务器\n平时会分享一些自己闲言碎语 包括技术、音乐、运动等等 有兴趣可关注\nhttps://becool.vip/\nhugo建站步骤\nhttps://becool.vip/posts/tech/create_hugo_blog/ hugo网站增加评论\nhttps://becool.vip/posts/tech/hugo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0tkikoo%E8%AF%84%E8%AE%BA/ 网站https证书申请 自动续期\nhttps://becool.vip/posts/tech/certbot%E7%94%B3%E8%AF%B7ssl%E8%AF%81%E4%B9%A6%E5%B9%B6%E5%90%AF%E5%8A%A8%E7%BB%AD%E6%9C%9F/ 离线下载 安装aria2 apt install ariac2 配置 可以参考一下git 建议配置rpc和token 这样加上域名+路由器NAT 可以随时随地添加下载任务 https://github.com/P3TERX/aria2.conf.git 启动命令 sudo aria2c --conf-path=/xxpath/aria2.conf -D 安装YAAW插件 实现浏览器直接离线下载 平时搜到磁力连接 点击插件就可以让家里的服务器直接下载了 https://chromewebstore.google.com/detail/yaaw-for-chrome/dennnbdlpgjgbcjfgaohdahloollfgoc?hl=zh-TW 我是部署到家里的服务器，动态监控家里公网IP变化 然后修改dns解析 IOS自定义通知 自己定时发一些通知到手机 提醒喝水、休息、睡觉之类 公网IP变化了 实时通知到手机 方法可参考下方连接 https://becool.vip/posts/tech/%E8%87%AA%E5%BB%BA%E8%8B%B9%E6%9E%9C%E6%B6%88%E6%81%AF%E9%80%9A%E7%9F%A5%E6%8E%A8%E9%80%81%E5%B7%A5%E5%85%B7/ 静态文件服务器 安装nodejs 安装httpserver npm install http-server -g cd /sharefiles;nohup http-server -a 0.0.0.0 -p 6666 \u0026gt;~/log/httpServer.log 2\u0026gt;\u0026amp;1 \u0026amp; 结合动态域名解析 可以随时随地访问家里的文件服务器 包括一些电影/歌曲/纪录片等等 私人云笔记 这里是无意间逛github看到的一个开源云笔记软件，目前个人一直还在使用 有兴趣的可以试试\n相关介绍网站\nhttps://trilium.netlify.app/ https://github.com/zadam/trilium docker安装部署\n拉取镜像docker pull zadam/trilium:0.61-latest\n选定数据目录 修改配置文件config.ini\ndocker内部端口可以修改 也可以用默认 可以复制直接使用 [General] # Instance name can be used to distinguish between different instances using backend api.getInstanceName() instanceName= # set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn\u0026#39;t need password) noAuthentication=false # set to true to disable backups (e.g. because of limited space on server) noBackup=false # Disable automatically generating desktop icon # noDesktopIcon=true [Network] # host setting is relevant only for web deployments - set the host on which the server will listen # host=0.0.0.0 # port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable) port=8080 # true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure). https=false # path to certificate (run \u0026#34;bash bin/generate-cert.sh\u0026#34; to generate self-signed certificate). Relevant only if https=true certPath= keyPath= # setting to give trust to reverse proxies, a comma-separated list of trusted rev. proxy IPs can be specified (CIDR notation is permitted), # alternatively \u0026#39;true\u0026#39; will make use of the leftmost IP in X-Forwarded-For, ultimately an integer can be used to tell about the number of hops between # Trilium (which is hop 0) and the first trusted rev. proxy. # once set, expressjs will use the X-Forwarded-For header set by the rev. proxy to determinate the real IPs of clients. # expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7) trustedReverseProxy=false docker启动\n将本机的8081端口映射到docker的8080端口 将本地/home/xxx/trilium_data挂载到docker内部的/home/node/trilium-data目录\nsudo docker run -d -p 0.0.0.0:8081:8080 -v /home/xxx/trilium_data:/home/node/trilium-data zadam/trilium:0.61-latest docker logs 实例ID 查看没有报错 docker ps实例一直存在就是没有问题\n浏览器打开http://xxip:8081 初始化登录密码后就可以享受私人云笔记了\n软件版本升级\ndocker stop 实例id 停掉原有实例 docker pull zadam/trilium:0.62-latest 拉取新版本 sudo docker run -d -p 0.0.0.0:8081:8080 -v /home/xx/trilium_data:/home/node/trilium-data zadam/trilium:0.62-latest 镜像地址换掉 磁盘目录映射 端口映射都不变，数据会自动迁移 开启smb文件共享,在小米电视看电影 安装samba sudo apt-get install samba 修改配置 sudo vi /etc/samba/smb.conf 在最后添加一下内容\n[share] comment = ubuntu_share path = /共享目录绝对路径 public = yes writable = yes available = yes browseable = yes 小米电视用的协议比较老 还需要做以下配置修改\n在global下面一行 加入 server min protocol = CORE 添加用户 sudo smbpasswd -a ftp 重启samba服务 sudo service smbd restart 其它局域网电脑查看方法 smb://ip 小米电视访问samba共享文件 在电视上看电影 应用里面有个**\u0026ldquo;高清播放器\u0026rdquo;** 打开，没有就搜索安装 菜单左侧 \u0026ldquo;设备\u0026rdquo; 添加设备 输入局域网ip 然后可能要输入账号或者密码 接下来就可以愉快的看电影了 目前离线下载了一些记录片 mv,英文动画片等等,小孩子看的比较多 老婆大人偶尔也想看个高清电影，晚上离线，第二天电视看高清的体验确实不错. ","permalink":"https://www.becool.vip/posts/tech/%E9%97%B2%E7%BD%AE%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%BA%9F%E7%89%A9%E5%88%A9%E7%94%A8/","summary":"\u003ch1 id=\"背景\"\u003e背景\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e家里有台旧windows笔记本，PentiumB940 2.00GHz的cpu 4G内存，512G硬盘\u003c/li\u003e\n\u003cli\u003e放在家里吃灰很久,最近几个月折腾折腾，装了linux操作系统，换了一个2T的硬盘\u003c/li\u003e\n\u003cli\u003e这里记录下折腾的过程,有需要的可以参考\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"开通公网ip\"\u003e开通公网IP\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e打电话给运营商一般都可以免费开通 但这个公网IP(ipv4)不是一成不变的,一般都是会变化的\u003c/li\u003e\n\u003cli\u003e不过这里可以监控公网ip变化 直接修改dns解析就好 参考下方连接\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://becool.vip/posts/tech/golang%E7%9B%91%E6%8E%A7%E5%85%AC%E7%BD%91ip%E5%8F%98%E5%8C%96%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5dns%E8%A7%A3%E6%9E%90/\"\u003ehttps://becool.vip/posts/tech/golang%E7%9B%91%E6%8E%A7%E5%85%AC%E7%BD%91ip%E5%8F%98%E5%8C%96%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5dns%E8%A7%A3%E6%9E%90/\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e个人宽带网关上网方式用桥接，然后路由器选择拨号，个人服务器直接网线连接路由器，路由器开启DMZ功能，将流量打向家里的旧电脑\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"搭建个人git服务器\"\u003e搭建个人git服务器\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e访问github总是很慢，暂时也没弄科学上网\u003c/li\u003e\n\u003cli\u003e具体方法可参考下方连接\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://becool.vip/posts/tech/ubuntu%E5%AE%89%E8%A3%85git%E6%9C%8D%E5%8A%A1%E5%99%A8/\"\u003ehttps://becool.vip/posts/tech/ubuntu%E5%AE%89%E8%A3%85git%E6%9C%8D%E5%8A%A1%E5%99%A8/\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"hugo搭建个人博客\"\u003ehugo搭建个人博客\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e目前个人有一台1核2G的腾讯云服务器 一个域名 最终静态页面是部署在腾讯云服务器\u003c/p\u003e","title":"闲置服务器废物利用"},{"content":"适合用户 IOS苹果手机用户\n有一些自定义推送场景,比如服务器IP切换了，自动通知到自己的苹果手机\n我这里是imac所以就直接用brew 其它操作系统也很简单 google\n实现方案简单介绍 有个免费APP 能拿到自己的appToken,IOS证书,苹果IOS推送需要用到\nAppStore搜索Bark 下载安装 感谢作者无私分享 这里也为其免费推广下\nhttps://bark.day.app/ https://github.com/Finb/bark-server https://github.com/Finb/Bark 使用github.com/sideshow/apns2库推送消息\nLinux_download https://download.csdn.net/download/imheketong/89530308\n1 IOS安装 Bark 应用---\u0026gt;设置获取token 2 点击上方连接获取SelfNotify工具 3 export BARK_IOS_TOKEN=\u0026#34;\u0026#34; #设置token环境变量 4 ./SelfNotify \u0026#34;hello,我是一条推送测试消息\u0026#34; 详细操作及其他平台代码分享 安装Bark app获取token AppStore搜索Bark 安装 打开Bark App\u0026mdash;\u0026gt;Settings(设置)\u0026mdash;\u0026gt;Device Token(设备令牌) 点击复制 记录下自己的token golang使用apns2库定制苹果手机推送工具 cert.go 这里存放证书相关信息 可以直接复制 package main // Bark push private key const apnsPrivateKey = `-----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4vtC3g5L5HgKGJ2+ T1eA0tOivREvEAY2g+juRXJkYL2gCgYIKoZIzj0DAQehRANCAASmOs3JkSyoGEWZ sUGxFs/4pw1rIlSV2IC19M8u3G5kq36upOwyFWj9Gi3Ejc9d3sC7+SHRqXrEAJow 8/7tRpV+ -----END PRIVATE KEY----- ` // Currently known APNS CA var apnsCAs = [...]string{ // AppleComputerRootCertificate.cer `-----BEGIN CERTIFICATE----- MIIFujCCBKKgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhjELMAkGA1UEBhMCVVMx HTAbBgNVBAoTFEFwcGxlIENvbXB1dGVyLCBJbmMuMS0wKwYDVQQLEyRBcHBsZSBD b21wdXRlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxKTAnBgNVBAMTIEFwcGxlIFJv b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTA1MDIxMDAwMTgxNFoXDTI1MDIx MDAwMTgxNFowgYYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBcHBsZSBDb21wdXRl ciwgSW5jLjEtMCsGA1UECxMkQXBwbGUgQ29tcHV0ZXIgQ2VydGlmaWNhdGUgQXV0 aG9yaXR5MSkwJwYDVQQDEyBBcHBsZSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOSRqQkfkdseR1DrBe1e eYQt6zaiV0xV7IsZid75S2z1B6siMALoGD74UAnTf0GomPnRymacJGsR0KO75Bsq wx+VnnoMpEeLW9QWNzPLxA9NzhRp0ckZcvVdDtV/X5vyJQO6VY9NXQ3xZDUjFUsV WR2zlPf2nJ7PULrBWFBnjwi0IPfLrCwgb3C2PwEwjLdDzw+dPfMrSSgayP7OtbkO 2V4c1ss9tTqt9A8OAJILsSEWLnTVPA3bYharo3GSR1NVwa8vQbP4++NwzeajTEV+ H0xrUJZBicR0YgsQg0GHM4qBsTBY7FoEMoxos48d3mVz/2deZbxJ2HafMxRloXeU yS0CAwEAAaOCAi8wggIrMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ MB0GA1UdDgQWBBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAfBgNVHSMEGDAWgBQr0GlH lHYJ/vRrjS5ApvdHTX8IXjCCASkGA1UdIASCASAwggEcMIIBGAYJKoZIhvdjZAUB MIIBCTBBBggrBgEFBQcCARY1aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmlj YXRlYXV0aG9yaXR5L3Rlcm1zLmh0bWwwgcMGCCsGAQUFBwICMIG2GoGzUmVsaWFu Y2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2Nl cHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5k IGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRp ZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wRAYDVR0fBD0wOzA5oDegNYYz aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L3Jvb3Qu Y3JsMFUGCCsGAQUFBwEBBEkwRzBFBggrBgEFBQcwAoY5aHR0cHM6Ly93d3cuYXBw bGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L2Nhc2lnbmVycy5odG1sMA0GCSqG SIb3DQEBBQUAA4IBAQCd2i0oWC99dgS5BNM+zrdmY06PL9T+S61yvaM5xlJNBZhS 9YlRASR5vhoy9+VEi0tEBzmC1lrKtCBe2a4VXR2MHTK/ODFiSF3H4ZCx+CRA+F9Y m1FdV53B5f88zHIhbsTp6aF31ywXJsM/65roCwO66bNKcuszCVut5mIxauivL9Wv Hld2j383LS4CXN1jyfJxuCZA3xWNdUQ/eb3mHZnhQyw+rW++uaT+DjUZUWOxw961 kj5ReAFziqQjyqSI8R5cH0EWLX6VCqrpiUGYGxrdyyC/R14MJsVVNU3GMIuZZxTH CR+6R8faAQmHJEKVvRNgGQrv6n8Obs3BREM6StXj -----END CERTIFICATE----- `, // AppleIncRootCertificate.cer `-----BEGIN CERTIFICATE----- MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0 MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+ +FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1 XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3 R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93 d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0 YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7 R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX UKqK1drk/NAJBzewdXUh -----END CERTIFICATE----- `, // AppleRootCA-G2.cer `-----BEGIN CERTIFICATE----- MIIFkjCCA3qgAwIBAgIIAeDltYNno+AwDQYJKoZIhvcNAQEMBQAwZzEbMBkGA1UE AwwSQXBwbGUgUm9vdCBDQSAtIEcyMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0 aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMw HhcNMTQwNDMwMTgxMDA5WhcNMzkwNDMwMTgxMDA5WjBnMRswGQYDVQQDDBJBcHBs ZSBSb290IENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgREkhI2imKScUcx+xuM23+TfvgHN6s XuI2pyT5f1BrTM65MFQn5bPW7SXmMLYFN14UIhHF6Kob0vuy0gmVOKTvKkmMXT5x ZgM4+xb1hYjkWpIMBDLyyED7Ul+f9sDx47pFoFDVEovy3d6RhiPw9bZyLgHaC/Yu OQhfGaFjQQscp5TBhsRTL3b2CtcM0YM/GlMZ81fVJ3/8E7j4ko380yhDPLVoACVd J2LT3VXdRCCQgzWTxb+4Gftr49wIQuavbfqeQMpOhYV4SbHXw8EwOTKrfl+q04tv ny0aIWhwZ7Oj8ZhBbZF8+NfbqOdfIRqMM78xdLe40fTgIvS/cjTf94FNcX1RoeKz 8NMoFnNvzcytN31O661A4T+B/fc9Cj6i8b0xlilZ3MIZgIxbdMYs0xBTJh0UT8TU gWY8h2czJxQI6bR3hDRSj4n4aJgXv8O7qhOTH11UL6jHfPsNFL4VPSQ08prcdUFm IrQB1guvkJ4M6mL4m1k8COKWNORj3rw31OsMiANDC1CvoDTdUE0V+1ok2Az6DGOe HwOx4e7hqkP0ZmUoNwIx7wHHHtHMn23KVDpA287PT0aLSmWaasZobNfMmRtHsHLD d4/E92GcdB/O/WuhwpyUgquUoue9G7q5cDmVF8Up8zlYNPXEpMZ7YLlmQ1A/bmH8 DvmGqmAMQ0uVAgMBAAGjQjBAMB0GA1UdDgQWBBTEmRNsGAPCe8CjoA1/coB6HHcm jTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwF AAOCAgEAUabz4vS4PZO/Lc4Pu1vhVRROTtHlznldgX/+tvCHM/jvlOV+3Gp5pxy+ 8JS3ptEwnMgNCnWefZKVfhidfsJxaXwU6s+DDuQUQp50DhDNqxq6EWGBeNjxtUVA eKuowM77fWM3aPbn+6/Gw0vsHzYmE1SGlHKy6gLti23kDKaQwFd1z4xCfVzmMX3z ybKSaUYOiPjjLUKyOKimGY3xn83uamW8GrAlvacp/fQ+onVJv57byfenHmOZ4VxG /5IFjPoeIPmGlFYl5bRXOJ3riGQUIUkhOb9iZqmxospvPyFgxYnURTbImHy99v6Z SYA7LNKmp4gDBDEZt7Y6YUX6yfIjyGNzv1aJMbDZfGKnexWoiIqrOEDCzBL/FePw N983csvMmOa/orz6JopxVtfnJBtIRD6e/J/JzBrsQzwBvDR4yGn1xuZW7AYJNpDr FEobXsmII9oDMJELuDY++ee1KG++P+w8j2Ud5cAeh6Squpj9kuNsJnfdBrRkBof0 Tta6SqoWqPQFZ2aWuuJVecMsXUmPgEkrihLHdoBR37q9ZV0+N0djMenl9MU/S60E inpxLK8JQzcPqOMyT/RFtm2XNuyE9QoB6he7hY1Ck3DDUOUUi78/w0EP3SIEIwiK um1xRKtzCTrJ+VKACd+66eYWyi4uTLLT3OUEVLLUNIAytbwPF+E= -----END CERTIFICATE----- `, // AppleRootCA-G3.cer `-----BEGIN CERTIFICATE----- MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49 AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517 IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4 at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM 6BgD56KyKA== -----END CERTIFICATE----- `, // AAACertificateServices.crt `-----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe 3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- `, // COMODORSAAAACA.crt `-----BEGIN CERTIFICATE----- MIIFfjCCBGagAwIBAgIQZ970PvF72uJP9ZQGBtLAhDANBgkqhkiG9w0BAQwFADB7 MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4 MTIzMTIzNTk1OVowgYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1h bmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBM aW1pdGVkMSswKQYDVQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9y aXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTd xc9EZ3SZKzejfSNwAHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH 9IY+Cvo+XPmT5jR62RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Z f7X8Z0NyvQwA1onrayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a 5JtsaZn4eEgwRdWt4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNG N4Tr6MyBSENnTnIqm1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R 7Qu2XK8sYxrfV8g/vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0 Czr56ENCHonYhMsT8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMAR gexWO/bTouJbt7IEIlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMo YX9w0MOiqiwhqkfOKJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKI f6MzOi5cHkERgWPOGHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPyw TEHl7R09XiidnMy/s1Hap0flhFMCAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSgEQoj PpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQw DgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRV HSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUAA4IB AQB/8lY1sG2VSk50rzribwGLh9Myl+34QNJ3UxHXxxYuxp3mSFa+gKn4vHjSyGMX roztFjH6HxjJDsfuSHmfx8m5vMyIFeNoYdGfHUthgddWBGPCCGkm8PDlL9/ACiup BfQCWmqJ17SEQpXj6/d2IF412cDNJQgTTHE4joewM4SRmR6R8ayeP6cdYIEsNkFU oOJGBgusG8eZNoxeoQukntlCRiTFxVuBrq2goNyfNriNwh0V+oitgRA5H0TwK5/d EFQMBzSxNtEU/QcCPf9yVasn1iyBQXEpjUH0UFcafmVgr8vFKHaYrrOoU3aL5iFS a+oh0IQOSU6IU9qSLucdCGbX -----END CERTIFICATE----- `, // USERTrustRSAAAACA.crt `-----BEGIN CERTIFICATE----- MIIFgTCCBGmgAwIBAgIQOXJEOvkit1HX02wQ3TE1lTANBgkqhkiG9w0BAQwFADB7 MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4 MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5 MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sI s9CsVw127c0n00ytUINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnG vDoZtF+mvX2do2NCtnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQ Ijy8/hPwhxR79uQfjtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfb IWax1Jt4A8BQOujM8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0 tyA9yn8iNK5+O2hmAUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97E xwzf4TKuzJM7UXiVZ4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNV icQNwZNUMBkTrNN9N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5 D9kCnusSTJV882sFqV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJ WBp/kjbmUZIO8yZ9HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ 5lhCLkMaTLTwJUdZ+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzG KAgEJTm4Diup8kyXHAc/DVL17e8vgg8CAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSg EQojPpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rID ZsswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAG BgRVHSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29t L0FBQUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA A4IBAQAYh1HcdCE9nIrgJ7cz0C7M7PDmy14R3iJvm3WOnnL+5Nb+qh+cli3vA0p+ rvSNb3I8QzvAP+u431yqqcau8vzY7qN7Q/aGNnwU4M309z/+3ri0ivCRlv79Q2R+ /czSAaF9ffgZGclCKxO/WIu6pKJmBHaIkU4MiRTOok3JMrO66BQavHHxW/BBC5gA CiIDEOUMsfnNkjcZ7Tvx5Dq2+UUTJnWvu6rvP3t3O9LEApE9GQDTF1w52z97GA1F zZOFli9d31kWTz9RvdVFGD/tSo7oBmF0Ixa1DVBzJ0RHfxBdiSprhTEUxOipakyA vGp4z7h/jnZymQyd/teRCBaho1+V -----END CERTIFICATE----- `, // GeoTrust Global CA 2 `-----BEGIN CERTIFICATE----- MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL 5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe 2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv /NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz 4iIprn2DQKi6bA== -----END CERTIFICATE----- `, // GeoTrust Global CA `-----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- `, } main.go 使用apns库发送消息 package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/mritd/logger\u0026#34; \u0026#34;github.com/sideshow/apns2\u0026#34; \u0026#34;github.com/sideshow/apns2/payload\u0026#34; \u0026#34;github.com/sideshow/apns2/token\u0026#34; \u0026#34;golang.org/x/net/http2\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; ) var cli *apns2.Client type PushMessage struct { DeviceToken string `form:\u0026#34;-\u0026#34; json:\u0026#34;-\u0026#34; xml:\u0026#34;-\u0026#34; query:\u0026#34;-\u0026#34;` DeviceKey string `form:\u0026#34;device_key,omitempty\u0026#34; json:\u0026#34;device_key,omitempty\u0026#34; xml:\u0026#34;device_key,omitempty\u0026#34; query:\u0026#34;device_key,omitempty\u0026#34;` Category string `form:\u0026#34;category,omitempty\u0026#34; json:\u0026#34;category,omitempty\u0026#34; xml:\u0026#34;category,omitempty\u0026#34; query:\u0026#34;category,omitempty\u0026#34;` Title string `form:\u0026#34;title,omitempty\u0026#34; json:\u0026#34;title,omitempty\u0026#34; xml:\u0026#34;title,omitempty\u0026#34; query:\u0026#34;title,omitempty\u0026#34;` Body string `form:\u0026#34;body,omitempty\u0026#34; json:\u0026#34;body,omitempty\u0026#34; xml:\u0026#34;body,omitempty\u0026#34; query:\u0026#34;body,omitempty\u0026#34;` // ios notification sound(system sound please refer to http://iphonedevwiki.net/index.php/AudioServices) Sound string `form:\u0026#34;sound,omitempty\u0026#34; json:\u0026#34;sound,omitempty\u0026#34; xml:\u0026#34;sound,omitempty\u0026#34; query:\u0026#34;sound,omitempty\u0026#34;` ExtParams map[string]interface{} `form:\u0026#34;ext_params,omitempty\u0026#34; json:\u0026#34;ext_params,omitempty\u0026#34; xml:\u0026#34;ext_params,omitempty\u0026#34; query:\u0026#34;ext_params,omitempty\u0026#34;` } const ( topic = \u0026#34;me.fin.bark\u0026#34; keyID = \u0026#34;LH4T9V5U4R\u0026#34; teamID = \u0026#34;5U8LBRXG3A\u0026#34; PayloadMaximum = 4096 ) func main() { if len(os.Args) != 2 { return } var msg PushMessage msg.DeviceToken = os.Getenv(\u0026#34;BARK_IOS_TOKEN\u0026#34;) if msg.DeviceToken==\u0026#34;\u0026#34;{ logger.Error(\u0026#34;请设置环境变量BARK_IOS_TOKEN\u0026#34;) return } msg.Title = \u0026#34;通知\u0026#34; msg.Body = os.Args[1] msg.Sound = \u0026#34;alarm\u0026#34; err := Push(\u0026amp;msg) if err != nil { logger.Error(err.Error()) } } func init() { authKey, err := token.AuthKeyFromBytes([]byte(apnsPrivateKey)) if err != nil { logger.Fatalf(\u0026#34;failed to create APNS auth key: %v\u0026#34;, err) } var rootCAs *x509.CertPool if runtime.GOOS == \u0026#34;windows\u0026#34; { rootCAs = x509.NewCertPool() } else { rootCAs, err = x509.SystemCertPool() if err != nil { logger.Fatalf(\u0026#34;failed to get rootCAs: %v\u0026#34;, err) } } for _, ca := range apnsCAs { rootCAs.AppendCertsFromPEM([]byte(ca)) } cli = \u0026amp;apns2.Client{ Token: \u0026amp;token.Token{ AuthKey: authKey, KeyID: keyID, TeamID: teamID, }, HTTPClient: \u0026amp;http.Client{ Transport: \u0026amp;http2.Transport{ DialTLS: apns2.DialTLS, TLSClientConfig: \u0026amp;tls.Config{ RootCAs: rootCAs, }, }, Timeout: apns2.HTTPClientTimeout, }, Host: apns2.HostProduction, } //logger.Info(\u0026#34;init apns client success...\u0026#34;) } func Push(msg *PushMessage) error { pl := payload.NewPayload(). AlertTitle(msg.Title). AlertBody(msg.Body). Sound(msg.Sound). Category(msg.Category) group, exist := msg.ExtParams[\u0026#34;group\u0026#34;] if exist { pl = pl.ThreadID(group.(string)) } for k, v := range msg.ExtParams { // Change all parameter names to lowercase to prevent inconsistent capitalization pl.Custom(strings.ToLower(k), fmt.Sprintf(\u0026#34;%v\u0026#34;, v)) } // JSON payload maximum size of 4 KB (4096 bytes) // https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns#2947607 plContentForJson, _ := pl.MarshalJSON() if len(plContentForJson) \u0026gt; PayloadMaximum { return fmt.Errorf(\u0026#34;APNS Push Msg Payload too Large %d \u0026gt; 4096 bytes\u0026#34;, len(plContentForJson)) } resp, err := cli.Push(\u0026amp;apns2.Notification{ DeviceToken: msg.DeviceToken, Topic: topic, Payload: pl.MutableContent(), Expiration: time.Now().Add(24 * time.Hour), }) if err != nil { return err } if resp.StatusCode != 200 { return fmt.Errorf(\u0026#34;APNS push failed: %s\u0026#34;, resp.Reason) } return nil } go build编译及使用命令 假设编译出工具名称为SelfNotify\nlinux 使用方法\nexport BARK_IOS_TOKEN=\u0026quot;\u0026quot; 设置token环境变量 ./SelfNotify \u0026quot;hello,我是一条推送测试消息\u0026quot; 然后你的苹果手机就会收到一条消息，这个工具就可以被集成到各种自动化脚本工具中去了，尽情享受自定义推送吧. Windows/Mac平台 复制代码 自己go build编译后设置环境变量就可以直接使用了\n","permalink":"https://www.becool.vip/posts/tech/%E8%87%AA%E5%BB%BA%E8%8B%B9%E6%9E%9C%E6%B6%88%E6%81%AF%E9%80%9A%E7%9F%A5%E6%8E%A8%E9%80%81%E5%B7%A5%E5%85%B7/","summary":"\u003ch1 id=\"适合用户\"\u003e适合用户\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eIOS苹果手机用户\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e有一些自定义推送场景,比如服务器IP切换了，自动通知到自己的苹果手机\u003c/p\u003e\n\u003cp\u003e我这里是imac所以就直接用brew 其它操作系统也很简单 google\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"实现方案简单介绍\"\u003e实现方案简单介绍\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e有个免费APP 能拿到自己的appToken,IOS证书,苹果IOS推送需要用到\u003c/p\u003e\n\u003cp\u003eAppStore搜索\u003ccode\u003eBark\u003c/code\u003e 下载安装 感谢作者无私分享 这里也为其免费推广下\u003c/p\u003e","title":"自建苹果消息通知推送工具"},{"content":"私有化安装twikoo 官网 https://twikoo.js.org/ https://twikoo.js.org/backend.html#%E7%A7%81%E6%9C%89%E9%83%A8%E7%BD%B2 私有化部署 个人诉求 能简单的在页面看到评论即可 能找到评论人的邮箱 评论可以折叠 部署方法 服务端安装node.js\nhttps://nodejs.org/zh-cn/download/package-manager 通过以下命令安装twikoo server\nnpm i -g tkserver 启动tkserver\ncd /home/ubuntu/twikoo; ## 指定一个目录 存放评论数据 我个人没有使用mongodb 直接使用默认的loki nohup tkserver \u0026gt;./tkserver.log 2\u0026gt;\u0026amp;1 \u0026amp; txy\u0026gt;tail ./tkserver.log ## 输出一下命令就代表成功了 7/10/2024, 11:28:38 AM Twikoo: Twikoo database stored at /home/ubuntu/twikoo/data 7/10/2024, 11:28:38 AM Twikoo: Connecting to database... 7/10/2024, 11:28:38 AM Twikoo: Twikoo is using loki database 7/10/2024, 11:28:38 AM Twikoo: Twikoo function started on host :: port 8080 7/10/2024, 11:28:38 AM Twikoo: Connected to database 浏览器打开 http://xxip:8080 页面输出类似下面的内容 代表成功\n{\u0026#34;code\u0026#34;:100,\u0026#34;message\u0026#34;:\u0026#34;Twikoo 云函数运行正常，请参考 https://twikoo.js.org/frontend.html 完成前端的配置\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;1.6.38\u0026#34;} 这里记录下version版本号 后面会用到 nginx代理配置 修改nginx配置 在server配置中加入以下内容\nserver {\tlocation /twikoo { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Script-Name /isso; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://localhost:8080; } } 接下来输入 https://becool.vip:twikoo 即可访问了 这个https://becool.vip:twikoo 也是twikoo的id\nhugo配置 主题 个人使用的papermod主题，如果你是从头搭建，也想用papermod主题 建议直接git clone https://github.com/xyming108/sulv-hugo-papermod 然后直接使用即可 集成twikoo config.yml修改\n# 这里的参数会被代码以 .Site.Params 的形式读取 params: comments: true # 这里打开评论 # twikoo评论 twikoo: version: 1.6.38 id: https://becool.vip/twikoo region: # 环境地域，默认为 ap-shanghai，如果您的环境地域不是上海，需传此参数，请看twikoo官方文档 yourBlogDir/layouts/partials/comments.html 不用修改用默认的下面内容即可\n\u0026lt;!-- {{- /* Comments area start */ -}} {{- /* to add comments read =\u0026gt; https://gohugo.io/content-management/comments/ */ -}} {{- /* Comments area end */ -}} --\u0026gt; \u0026lt;style\u0026gt; .comments_details summary::marker { font-size: 20px; content: \u0026#39;👉展开评论\u0026#39;; color: var(--content); } .comments_details[open] summary::marker{ font-size: 20px; content: \u0026#39;👇关闭评论\u0026#39;; color: var(--content); } \u0026lt;/style\u0026gt; \u0026lt;!-- Twikoo --\u0026gt; \u0026lt;div\u0026gt; \u0026lt;details class=\u0026#34;comments_details\u0026#34;\u0026gt; \u0026lt;summary style=\u0026#34;cursor: pointer; margin: 50px 0 20px 0;width: 130px;\u0026#34;\u0026gt; \u0026lt;span style=\u0026#34;font-size: 20px;color: var(--content);\u0026#34;\u0026gt;...\u0026lt;/span\u0026gt; \u0026lt;/summary\u0026gt; \u0026lt;div id=\u0026#34;tcomment\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/details\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js\u0026#34;\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; twikoo.init({ envId: {{ .Site.Params.twikoo.id }}, el: \u0026#34;#tcomment\u0026#34;, lang: \u0026#39;zh-CN\u0026#39;, region: {{ .Site.Params.twikoo.region }}, path: window.TWIKOO_MAGIC_PATH||window.location.pathname, }) \u0026lt;/script\u0026gt; \u0026lt;/div\u0026gt;% 用hugo命令 重新生成静态网页即可\ncd YourBlogDir; sudo /home/ubuntu/shell/hugo --buildDrafts 接下来就可以看到结果了 本站的任意文章尾部点展开评论就能看到效果\n评论数据存放位置及日志 存放在tkserver启动目录 data目录 json格式存放\nll /home/ubuntu/twikoo/data total 8.0K -rw-r--r-- 1 ubuntu root 2.5K Jul 10 11:40 db.json -rw-r--r-- 1 ubuntu root 1.7K Jul 10 11:40 db.json.0 -rw-r--r-- 1 ubuntu root 0 Jul 10 11:13 db.json.1 -rw-r--r-- 1 ubuntu root 0 Jul 10 11:13 db.json.2 ","permalink":"https://www.becool.vip/posts/tech/hugo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0tkikoo%E8%AF%84%E8%AE%BA/","summary":"\u003ch1 id=\"私有化安装twikoo\"\u003e私有化安装twikoo\u003c/h1\u003e\n\u003ch2 id=\"官网\"\u003e官网\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://twikoo.js.org/\"\u003ehttps://twikoo.js.org/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://twikoo.js.org/backend.html#%E7%A7%81%E6%9C%89%E9%83%A8%E7%BD%B2\"\u003ehttps://twikoo.js.org/backend.html#%E7%A7%81%E6%9C%89%E9%83%A8%E7%BD%B2\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"私有化部署\"\u003e私有化部署\u003c/h2\u003e\n\u003ch3 id=\"个人诉求\"\u003e个人诉求\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e能简单的在页面看到评论即可\u003c/li\u003e\n\u003cli\u003e能找到评论人的邮箱\u003c/li\u003e\n\u003cli\u003e评论可以折叠\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"部署方法\"\u003e部署方法\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e服务端安装node.js\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://nodejs.org/zh-cn/download/package-manager\"\u003ehttps://nodejs.org/zh-cn/download/package-manager\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e通过以下命令安装twikoo server\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enpm i -g tkserver\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e启动tkserver\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd /home/ubuntu/twikoo; ## 指定一个目录 存放评论数据 我个人没有使用mongodb 直接使用默认的loki\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enohup tkserver \u0026gt;./tkserver.log 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etxy\u0026gt;tail ./tkserver.log ## 输出一下命令就代表成功了\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e7/10/2024, 11:28:38 AM Twikoo: Twikoo database stored at /home/ubuntu/twikoo/data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e7/10/2024, 11:28:38 AM Twikoo: Connecting to database...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e7/10/2024, 11:28:38 AM Twikoo: Twikoo is using loki database\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e7/10/2024, 11:28:38 AM Twikoo: Twikoo function started on host :: port 8080\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e7/10/2024, 11:28:38 AM Twikoo: Connected to database\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e浏览器打开 http://xxip:8080 页面输出类似下面的内容 代表成功\u003c/p\u003e","title":"hugo博客添加tkikoo评论"},{"content":"github一些基友的分享 https://github.com/maxiaof/github-hosts\nhttps://hosts.gitcdn.top/hosts.txt\nhttps://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts\n这些基友本身已经写好工具定期更新了 直接使用即可 可手工也可用工具自动\n自动化工具体验 地址 https://github.com/Licoy/fetch-github-hosts https://github.com/Licoy/fetch-github-hosts/releases 使用方法 curl获取所有hosts\n脚本更新/etc/hosts文件\n重启nscd\n汇总后的脚本\n#!/bin/bash # 拉起githubhosts 检查文件中是否存在指定字样 校验是否拉取成功 curl -s https://hosts.gitcdn.top/hosts.txt \u0026gt;/tmp/githubhosts.txt if grep -q \u0026#34;fetch-github-hosts\u0026#34; /tmp/githubhosts.txt; then echo \u0026#34;hello\u0026#34; else exit 0 fi # 删除/etc/hosts文件中旧的github记录 # 指定开始和结束标记 begin_marker=\u0026#34;# fetch-github-hosts begin\u0026#34; end_marker=\u0026#34;# fetch-github-hosts end\u0026#34; # 临时文件路径 temp_file=\u0026#34;/tmp/hosts_temp\u0026#34; # 复制除了指定行之外的所有行到临时文件 sed \u0026#34;/$begin_marker/,/$end_marker/d\u0026#34; /etc/hosts \u0026gt; \u0026#34;$temp_file\u0026#34; # 将临时文件复制回原始文件 sudo cp \u0026#34;$temp_file\u0026#34; /etc/hosts # 删除临时文件 rm \u0026#34;$temp_file\u0026#34; #追加最新的hosts sudo sh -c \u0026#39;cat /tmp/githubhosts.txt \u0026gt;\u0026gt; /etc/hosts\u0026#39; #刷新 DNS sudo systemctl restart nscd 添加crontab每小时执行一次\n0 * * * * /home/ubuntu/shell/updateGithubHosts.sh \u0026gt; /home/ubuntu/shell/updateGithubHosts.log 2\u0026gt;\u0026amp;1 ","permalink":"https://www.becool.vip/posts/tech/github%E7%9B%B8%E5%85%B3hosts%E5%AE%9A%E6%97%B6%E6%9B%B4%E6%96%B0%E6%80%9D%E8%B7%AF/","summary":"\u003ch1 id=\"github一些基友的分享\"\u003egithub一些基友的分享\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/maxiaof/github-hosts\"\u003ehttps://github.com/maxiaof/github-hosts\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://hosts.gitcdn.top/hosts.txt\"\u003ehttps://hosts.gitcdn.top/hosts.txt\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts\"\u003ehttps://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e这些基友本身已经写好工具定期更新了 直接使用即可 可手工也可用工具自动\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"自动化工具体验\"\u003e自动化工具体验\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e地址\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/Licoy/fetch-github-hosts\"\u003ehttps://github.com/Licoy/fetch-github-hosts\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/Licoy/fetch-github-hosts/releases\"\u003ehttps://github.com/Licoy/fetch-github-hosts/releases\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"使用方法\"\u003e使用方法\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003ecurl获取所有hosts\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e脚本更新/etc/hosts文件\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e重启nscd\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e汇总后的脚本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# 拉起githubhosts 检查文件中是否存在指定字样 校验是否拉取成功\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl -s https://hosts.gitcdn.top/hosts.txt \u0026gt;/tmp/githubhosts.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e grep -q \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fetch-github-hosts\u0026#34;\u003c/span\u003e /tmp/githubhosts.txt; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hello\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    exit \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 删除/etc/hosts文件中旧的github记录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 指定开始和结束标记\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebegin_marker\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;# fetch-github-hosts begin\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eend_marker\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;# fetch-github-hosts end\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 临时文件路径\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etemp_file\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/tmp/hosts_temp\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 复制除了指定行之外的所有行到临时文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u003c/span\u003e$begin_marker\u003cspan style=\"color:#e6db74\"\u003e/,/\u003c/span\u003e$end_marker\u003cspan style=\"color:#e6db74\"\u003e/d\u0026#34;\u003c/span\u003e /etc/hosts \u0026gt; \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$temp_file\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 将临时文件复制回原始文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo cp \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$temp_file\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e /etc/hosts\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 删除临时文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erm \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$temp_file\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#追加最新的hosts\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo sh -c \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cat /tmp/githubhosts.txt \u0026gt;\u0026gt; /etc/hosts\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#刷新 DNS\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl restart nscd\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加crontab每小时执行一次\u003c/p\u003e","title":"定期更新github相关hosts"},{"content":"大致思路 有一个地方保存旧公网ip 可以是文件/redis等\n定时访问一些网站或者执行脚本获取最新的公网ip\n网站\nhttps://api.ipify.org http://myexternalip.com/raw 脚本\ncurl -s ifconfig.me curl ipv4.ip.sb 新公网ip 与 旧公网ip不一致，则调用腾讯云golang sdk修改域名的ip解析\n正文有详细的demo代码 循环定时执行巡检即可\n获取最新公网ip func getExternalIp1() string { // 使用http.Get函数请求外部服务 resp, err := http.Get(\u0026#34;https://api.ipify.org\u0026#34;) if err != nil { log.Printf(\u0026#34;请求失败: %s\\n\u0026#34;, err) return \u0026#34;\u0026#34; } defer resp.Body.Close() // 确保关闭响应体 // 读取响应体内容 ip, err := io.ReadAll(resp.Body) if err != nil { log.Printf(\u0026#34;读取响应失败: %s\\n\u0026#34;, err) return \u0026#34;\u0026#34; } return string(ip) } 为了兼容一些异常 校验下ip是否合法 不是一些errorcode之类的\nfunc IsValidIPv4(ip string) bool { parsedIP := net.ParseIP(ip) return parsedIP != nil \u0026amp;\u0026amp; parsedIP.To4() != nil } 动态更新域名对应ip 腾讯云启用API密钥,记录自己的secertId和和secertKey 管理地址 https://console.cloud.tencent.com/cam/capi\n引入腾讯云golangapi 其它也类似 import ( \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common\u0026#34; \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors\u0026#34; \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile\u0026#34; dnspod \u0026#34;github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323\u0026#34; ) 查询域名下面的所有解析记录 下面demo代码注意修改 你的SecertId/你的SecertKey/域名 会打印域名下面的所有的A记录解析情况 credential := common.NewCredential( \u0026#34;你的SecertId\u0026#34;, \u0026#34;你的SecertKey\u0026#34;, ) // 实例化一个client选项，可选的，没有特殊需求可以跳过 cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = \u0026#34;dnspod.tencentcloudapi.com\u0026#34; // 实例化要请求产品的client对象,clientProfile是可选的 client, _ := dnspod.NewClient(credential, \u0026#34;\u0026#34;, cpf) // 实例化一个请求对象,每个接口都会对应一个request对象 request := dnspod.NewDescribeRecordListRequest() var Domain string =\u0026#34;becool.vip\u0026#34; var RecordType string =\u0026#34;A\u0026#34; request.Domain =\u0026amp;Domain request.RecordType =\u0026amp;RecordType // 返回的resp是一个DescribeRecordListResponse的实例，与请求对象对应 response, err := client.DescribeRecordList(request) if _, ok := err.(*errors.TencentCloudSDKError); ok { fmt.Printf(\u0026#34;An API error has returned: %s\\n\u0026#34;, err) return \u0026#34;\u0026#34; } if err != nil { fmt.Println(\u0026#34;getdnsrecord err:%s\u0026#34;,err.Error()) return \u0026#34;\u0026#34; } for i:=0;i\u0026lt;len(response.Response.RecordList);i++{ item:=response.Response.RecordList[i] fmt.Println(*item.Value,*item.RecordId,*item.Line,*item.LineId,*item.Remark,*item.MX,*item.Name,*item.UpdatedOn) } 更改域名对应ip解析 建议指定var SubDomain string =\u0026quot;bereal\u0026quot; 具体主机记录 比如@/www/子域名 // 实例化一个认证对象，入参需要传入腾讯云账户secretId，secretKey,此处还需注意密钥对的保密 // 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取 credential := common.NewCredential( \u0026#34;你的SecertId\u0026#34;, \u0026#34;你的SecertKey\u0026#34;, ) // 实例化一个client选项，可选的，没有特殊需求可以跳过 cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = \u0026#34;dnspod.tencentcloudapi.com\u0026#34; // 实例化要请求产品的client对象,clientProfile是可选的 client, _ := dnspod.NewClient(credential, \u0026#34;\u0026#34;, cpf) // 实例化一个请求对象,每个接口都会对应一个request对象 request := dnspod.NewModifyRecordRequest() var Domain string =\u0026#34;becool.vip\u0026#34; var SubDomain string =\u0026#34;blog\u0026#34; // 我这里指定子域名 对应全称就是blog.becool.vip var RecordType string = \u0026#34;A\u0026#34; var RecordLine string = \u0026#34;默认\u0026#34; var RecordLineId string =\u0026#34;0\u0026#34; var RecordId uint64 = 1811353533 // 对应上面的item.RecordId var Value string = extIp request.Domain =\u0026amp;Domain request.RecordType = \u0026amp;RecordType request.RecordLine = \u0026amp;RecordLine request.RecordLineId =\u0026amp;RecordLineId request.Value =\u0026amp;Value request.RecordId =\u0026amp;RecordId request.SubDomain=\u0026amp;SubDomain // 返回的resp是一个ModifyRecordResponse的实例，与请求对象对应 _, err := client.ModifyRecord(request) if _, ok := err.(*errors.TencentCloudSDKError); ok { fmt.Printf(\u0026#34;An API error has returned: %s\u0026#34;, err) return false } if err != nil { fmt.Println(\u0026#34;modify error\u0026#34;,err.Error()) } ","permalink":"https://www.becool.vip/posts/tech/golang%E7%9B%91%E6%8E%A7%E5%85%AC%E7%BD%91ip%E5%8F%98%E5%8C%96%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5dns%E8%A7%A3%E6%9E%90/","summary":"\u003ch1 id=\"大致思路\"\u003e大致思路\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e有一个地方保存旧公网ip 可以是文件/redis等\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e定时访问一些网站或者执行脚本获取最新的公网ip\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e网站\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://api.ipify.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttp://myexternalip.com/raw\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e脚本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl -s ifconfig.me\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl ipv4.ip.sb\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e新公网ip 与 旧公网ip不一致，则调用腾讯云golang sdk修改域名的ip解析\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e正文有详细的demo代码\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e循环定时执行巡检即可\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"获取最新公网ip\"\u003e获取最新公网ip\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e getExternalIp1() string {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e使用\u003c/span\u003ehttp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet函数请求外部服务\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tresp, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://api.ipify.org\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tlog\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;请求失败: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, err)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdefer resp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBody\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClose() \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e确保关闭响应体\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e读取响应体内容\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tip, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e io\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReadAll(resp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBody)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tlog\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;读取响应失败: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, err)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string(ip)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e为了兼容一些异常 校验下ip是否合法 不是一些errorcode之类的\u003c/p\u003e","title":"golang监控公网IP变化自动同步dns解析"},{"content":"安装nginx sudo apt update sudo apt install nginx sudo systemctl status nginx 一般没有输出明显报错就是成功,安装成功一般也会输出类似以下信息 Jul 08 16:54:50 VM-0-7-ubuntu systemd[1]: Starting A high performance web server and a reverse proxy server.. Nginx配置修改 sudo vi /etc/nginx/sites-available/default 添加80/443端口 443指定安装证书路径 可以先配置上 becool.vip替换为自己的域名 root路径替换为自己的hugo静态网站路径 server { listen 80; server_name becool.vip; root /home/ubuntu/blog/public; index index.html; location / { try_files $uri $uri/ =404; } } server { listen 443 ssl; server_name becool.vip; ssl_certificate /etc/letsencrypt/live/becool.vip/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/becool.vip/privkey.pem; root /home/ubuntu/blog/public; index index.html; location / { try_files $uri $uri/ =404; } } 安装Certbot sudo apt-get install certbot 申请证书 注意替换自己的域名 申请成功会默认放在/etc/letsencrypt/live/目录 也会打印证书和privkey文件的路径 跟/etc/nginx/sites-available/default 里面的路径核对下是否一致 不一致就修改下/etc/nginx/sites-available/default里面的路径 调用命令之前注意先停掉nginx sudo certbot certonly --standalone -d becool.vip -d www.becool.vip --email your@email.com --agree-tos --no-eff-email --force-renewal 启动脚本定时监控续期 脚本内容 默认提前5天续期 可自行修改 # 定义证书存储目录 certs_directory=\u0026#34;/etc/letsencrypt/live/\u0026#34; days_before_expiry=5 # 设置在证书到期前几天触发续签 # 遍历所有证书文件 for cert_dir in $certs_directory*; do # 获取域名 domain=$(basename \u0026#34;$cert_dir\u0026#34;) # 忽略 README 目录 if [ \u0026#34;$domain\u0026#34; = \u0026#34;README\u0026#34; ]; then continue fi # 输出正在检查的证书信息 echo \u0026#34;检查证书过期日期： ${domain}\u0026#34; # 获取fullchain.pem文件路径 cert_file=\u0026#34;${cert_dir}/fullchain.pem\u0026#34; # 获取证书过期日期 expiration_date=$(openssl x509 -enddate -noout -in \u0026#34;${cert_file}\u0026#34; | cut -d \u0026#34;=\u0026#34; -f 2-) # 输出证书过期日期 echo \u0026#34;过期日期： ${expiration_date}\u0026#34; # 将日期转换为时间戳 expiration_timestamp=$(date -d \u0026#34;${expiration_date}\u0026#34; +%s) current_timestamp=$(date +%s) # 计算距离过期还有几天 days_until_expiry=$(( ($expiration_timestamp - $current_timestamp) / 86400 )) # 检查是否需要续签（在满足续签条件的情况下） if [ $days_until_expiry -le $days_before_expiry ]; then echo \u0026#34;证书将在${days_before_expiry}天内过期，正在进行自动续签。\u0026#34; # 停止 Nginx sudo systemctl stop nginx iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -F ip6tables -P INPUT ACCEPT ip6tables -P FORWARD ACCEPT ip6tables -P OUTPUT ACCEPT ip6tables -F # 续签证书 certbot certonly --standalone -d $domain -d \u0026#34;www.$domain\u0026#34; --email your@email.com --agree-tos --no-eff-email --force-renewal # 启动 Nginx sudo systemctl start nginx echo \u0026#34;证书已成功续签。\u0026#34; else # 若未满足续签条件，则输出证书仍然有效 echo \u0026#34;证书仍然有效，距离过期还有 ${days_until_expiry} 天。\u0026#34; fi # 输出分隔线 echo \u0026#34;--------------------------\u0026#34; done 配置crontab自动执行 每天执行一次 0 0 * * * sudo /home/ubuntu/shell/auto_cert_renewal.sh \u0026gt;/home/ubuntu/shell/auto_cert_renewal.sh.log ","permalink":"https://www.becool.vip/posts/tech/certbot%E7%94%B3%E8%AF%B7ssl%E8%AF%81%E4%B9%A6%E5%B9%B6%E5%90%AF%E5%8A%A8%E7%BB%AD%E6%9C%9F/","summary":"\u003ch1 id=\"安装nginx\"\u003e安装nginx\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl status nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e一般没有输出明显报错就是成功,安装成功一般也会输出类似以下信息\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eJul 08 16:54:50 VM-0-7-ubuntu systemd[1]: Starting A high performance web server and a reverse proxy server..\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"nginx配置修改\"\u003eNginx配置修改\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003esudo vi /etc/nginx/sites-available/default 添加80/443端口 443指定安装证书路径 可以先配置上 becool.vip替换为自己的域名 root路径替换为自己的hugo静态网站路径\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eserver {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    listen 80;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    server_name becool.vip;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    root /home/ubuntu/blog/public;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    index index.html;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    location / {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        try_files $uri $uri/ =404;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eserver {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    listen 443 ssl;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    server_name becool.vip;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ssl_certificate /etc/letsencrypt/live/becool.vip/fullchain.pem;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ssl_certificate_key /etc/letsencrypt/live/becool.vip/privkey.pem;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    root /home/ubuntu/blog/public;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    index index.html;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    location / {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        try_files $uri $uri/ =404;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"安装certbot\"\u003e安装Certbot\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt-get install certbot\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"申请证书\"\u003e申请证书\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e注意替换自己的域名 申请成功会默认放在/etc/letsencrypt/live/目录 也会打印证书和privkey文件的路径 跟/etc/nginx/sites-available/default 里面的路径核对下是否一致 不一致就修改下/etc/nginx/sites-available/default里面的路径\u003c/li\u003e\n\u003cli\u003e调用命令之前注意先停掉nginx\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo certbot certonly --standalone -d becool.vip -d www.becool.vip --email your@email.com --agree-tos --no-eff-email --force-renewal\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"启动脚本定时监控续期\"\u003e启动脚本定时监控续期\u003c/h1\u003e\n\u003ch2 id=\"脚本内容\"\u003e脚本内容\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e默认提前5天续期 可自行修改\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 定义证书存储目录\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecerts_directory=\u0026#34;/etc/letsencrypt/live/\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edays_before_expiry=5  # 设置在证书到期前几天触发续签\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 遍历所有证书文件\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efor cert_dir in $certs_directory*; do\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 获取域名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    domain=$(basename \u0026#34;$cert_dir\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 忽略 README 目录\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    if [ \u0026#34;$domain\u0026#34; = \u0026#34;README\u0026#34; ]; then\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        continue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 输出正在检查的证书信息\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    echo \u0026#34;检查证书过期日期： ${domain}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 获取fullchain.pem文件路径\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    cert_file=\u0026#34;${cert_dir}/fullchain.pem\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 获取证书过期日期\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    expiration_date=$(openssl x509 -enddate -noout -in \u0026#34;${cert_file}\u0026#34; | cut -d \u0026#34;=\u0026#34; -f 2-)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 输出证书过期日期\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    echo \u0026#34;过期日期： ${expiration_date}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 将日期转换为时间戳\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    expiration_timestamp=$(date -d \u0026#34;${expiration_date}\u0026#34; +%s)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    current_timestamp=$(date +%s)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 计算距离过期还有几天\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    days_until_expiry=$(( ($expiration_timestamp - $current_timestamp) / 86400 ))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 检查是否需要续签（在满足续签条件的情况下）\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    if [ $days_until_expiry -le $days_before_expiry ]; then\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        echo \u0026#34;证书将在${days_before_expiry}天内过期，正在进行自动续签。\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        # 停止 Nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        sudo systemctl stop nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        iptables -P INPUT ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        iptables -P FORWARD ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        iptables -P OUTPUT ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        iptables -F\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ip6tables -P INPUT ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ip6tables -P FORWARD ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ip6tables -P OUTPUT ACCEPT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ip6tables -F\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        # 续签证书\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        certbot certonly --standalone -d $domain -d \u0026#34;www.$domain\u0026#34; --email your@email.com --agree-tos --no-eff-email --force-renewal\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        # 启动 Nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        sudo systemctl start nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        echo \u0026#34;证书已成功续签。\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    else\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        # 若未满足续签条件，则输出证书仍然有效\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        echo \u0026#34;证书仍然有效，距离过期还有 ${days_until_expiry} 天。\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # 输出分隔线\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    echo \u0026#34;--------------------------\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edone\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"配置crontab自动执行\"\u003e配置crontab自动执行\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e每天执行一次\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e0 0 * * * sudo /home/ubuntu/shell/auto_cert_renewal.sh \u0026gt;/home/ubuntu/shell/auto_cert_renewal.sh.log\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"CertBot申请SSL证书并启动续期"},{"content":"背景 个人是网易云忠实用户,家人也是,到现在都还不喜欢QQ音乐的界面和操作,QQ音乐的推荐也是一般般\n作为一个80后,如果问我听谁的歌最多,那应该是周杰伦\n可惜网易云听不了周杰伦,每次想听Jay的歌都要切换到QQ音乐,这个让我有点不舒服，最近把Jay的歌都down了下来，想了个办法弄到了网易云，实现了听Jay自由 有需要的可以参考,歌曲获取方法详见下方\n将本地歌曲上传到网易云音乐云盘 电脑打开chrome浏览器操作\n网易云音乐云盘管理chrome浏览器插件,严重安利,超级好用\nhttps://chromewebstore.google.com/detail/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E4%BA%91%E7%9B%98%E7%AE%A1%E7%90%86/gnfemfddeadngnfhcpbdhlgbbnokokcg 使用方法 使用下面的方法 将所有Jay的歌曲都上传到网易云音乐云盘\n网易云音乐创建歌单从云盘导入 推荐电脑操作然后手机app打开同步即可\ndownload下载 更多详细内容请移步 http://www.becool.vip/posts/music/jaychou/\nhuifu以下关键字获取对应专辑 八度空间 叶惠美 七里香 范特西 依然范特西 FantasyPlus 十一月的萧邦 Jay 2007世界巡回演唱会 不能说的秘密 寻找周杰伦 TheOne演唱会Live 天台电影原声带 十二新作 周杰伦的床边故事 惊叹号 我很忙 等你下课 无与伦比演唱会Live 超时代演唱会Live 跨时代 霍元甲 魔杰座 单曲及未收录发行 黄金甲 ","permalink":"https://www.becool.vip/posts/tech/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E7%94%A8%E7%BD%91%E6%98%93%E4%BA%91%E5%90%AC%E5%91%A8%E6%9D%B0%E4%BC%A6/","summary":"\u003ch1 id=\"背景\"\u003e背景\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e个人是网易云忠实用户,家人也是,到现在都还不喜欢QQ音乐的界面和操作,QQ音乐的推荐也是一般般\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e作为一个80后,如果问我听谁的歌最多,那应该是周杰伦\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e可惜网易云听不了周杰伦,每次想听Jay的歌都要切换到QQ音乐,这个让我有点不舒服，最近把Jay的歌都down了下来，想了个办法弄到了网易云，实现了听Jay自由 有需要的可以参考,歌曲获取方法详见下方\u003c/p\u003e","title":"手把手教你用网易云听周杰伦"},{"content":"下载安装 https://github.com/iawia002/lux\n依赖ffmpeg库 linux需要首先安装这个库\nsudo apt-get install ffmpeg 其它环境自行前往官网下载安装\nhttps://www.ffmpeg.org/download.html\n安装方式 go install\ngo install github.com/iawia002/lux@latest mac\nbrew install lux 更多请参考lux github readme\n使用方式 获取B站cookie 参见http://www.becool.vip/posts/tech/%E8%8E%B7%E5%8F%96%E7%BD%91%E7%AB%99cookie/ 查看介绍 其中-c为对应cookie 不指定你的会员cookie，只能下载非高清视频 请自行先获取再替换\n其中-p 下载专辑 一系列视频\nlux -c \u0026#34;yourcookies\u0026#34; -p -i \u0026#34;https://www.bilibili.com/video/BV1Hy4y1M7WF\u0026#34; lux -i -p \u0026#34;https://www.bilibili.com/bangumi/play/ep198061\u0026#34; 上述命令可能输出如下:\nSite: 哔哩哔哩 bilibili.com Title: 恒星恒心 P1 恒星恒心 Type: video Streams: # All available quality [32-7] ------------------- Quality: 清晰 480P avc1.64001F Size: 18.61 MiB (19514801 Bytes) # download with: lux -f 32-7 ... [32-12] ------------------- Quality: 清晰 480P hev1.1.6.L120.90 Size: 12.31 MiB (12906566 Bytes) # download with: lux -f 32-12 ... [16-7] ------------------- Quality: 流畅 360P avc1.64001E Size: 9.73 MiB (10207419 Bytes) # download with: lux -f 16-7 ... [16-12] ------------------- Quality: 流畅 360P hev1.1.6.L120.90 Size: 6.95 MiB (7292810 Bytes) # download with: lux -f 16-12 ... 下载 默认下载最高清 支持断点续传 其中-n为并发数 可以nohup放后台下载 lux -c \u0026#34;yourcookies\u0026#34; -p -n 30 \u0026#34;https://www.bilibili.com/video/BV1Hy4y1M7WF\u0026#34; nohup lux -c \u0026#34;yourcookies\u0026#34; -p -n 30 \u0026#34;https://www.bilibili.com/video/BV1Hy4y1M7WF\u0026#34; \u0026gt;\u0026gt; ./luxDownload_BV1Hy4y1M7WF.log 2\u0026gt;\u0026amp;1 \u0026amp; 指定质量下载 首先运行lux -i会输出介绍信息和指定质量下载命令,比如上方需要下载360P avc1.64001E对应的命令就是 lux -f 16-7\n下面是一个例子\nlux -f 16-7 \u0026#34;https://www.bilibili.com/video/BV1Hy4y1M7WF\u0026#34; Site: 哔哩哔哩 bilibili.com Title: 恒星恒心 Type: video Stream: [16-7] ------------------- Quality: 流畅 360P avc1.64001E Size: 9.73 MiB (10207419 Bytes) # download with: lux -f 16-7 ... 9.73 MiB / 9.73 MiB [===================================================================================================================================================================] 1.95 MiB p/s 100.00% 5.2s Merging video parts into 恒星恒心.mp4 ","permalink":"https://www.becool.vip/posts/tech/lux%E4%B8%8B%E8%BD%BDb%E7%AB%99%E8%A7%86%E9%A2%91/","summary":"\u003ch1 id=\"下载安装\"\u003e下载安装\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/iawia002/lux\"\u003ehttps://github.com/iawia002/lux\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e依赖ffmpeg库 linux需要首先安装这个库\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt-get install ffmpeg\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e其它环境自行前往官网下载安装\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.ffmpeg.org/download.html\"\u003ehttps://www.ffmpeg.org/download.html\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"安装方式\"\u003e安装方式\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003ego install\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego install github.com/iawia002/lux@latest\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003emac\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebrew install lux\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e更多请参考lux github readme\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"使用方式\"\u003e使用方式\u003c/h1\u003e\n\u003ch2 id=\"获取b站cookie\"\u003e获取B站cookie\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e参见http://www.becool.vip/posts/tech/%E8%8E%B7%E5%8F%96%E7%BD%91%E7%AB%99cookie/\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"查看介绍\"\u003e查看介绍\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e其中-c为对应cookie 不指定你的会员cookie，只能下载非高清视频 请自行先获取再替换\u003c/p\u003e","title":"lux下载B站视频"},{"content":"常用写法 ioutil/io.ReadAll code demo httpresp, err := http.DefaultClient.Do(httpreq.WithContext(httpctx)) defer httpresp.Body.Close() bodyByte, err := ioutil.ReadAll(httpresp.Body) golang标准库实现 func ReadAll(r Reader) ([]byte, error) { b := make([]byte, 0, 512) for { if len(b) == cap(b) { // Add more capacity (let append pick how much). b = append(b, 0)[:len(b)] } n, err := r.Read(b[len(b):cap(b)]) b = b[:len(b)+n] if err != nil { if err == EOF { err = nil } return b, err } } } 问题 一次申请512字节 如果应答很大，大量并发 则会出现问题，因为在频繁大量的申请512字节内存，申请速度超过gc的速度 很可能导致内存OOM 优化方案使用bytebufferpool bytebufferpool介绍 bytebufferpool将不同大小的byte[]分成了多个区间，动态调整，可以理解成一组可以动态调整大小的syncpool(byte[])\nfasthttp库在大量使用\ncode demo ​\timport \u0026quot;github.com/valyala/bytebufferpool\u0026quot;\n业务代码\nhttpresp, err := http.DefaultClient.Do(httpreq.WithContext(httpctx)) defer httpresp.Body.Close() var bodyByte []byte buffer := bytebufferpool.Get() buffer.Reset() if _, err = buffer.ReadFrom(httpresp.Body); err == nil { bodyByte = buffer.Bytes() } bytebufferpool.Put(buffer) // 注意放入pool后不要再使用之前的buffer 因为可能会被修改 ","permalink":"https://www.becool.vip/posts/tech/golang%E9%AB%98%E6%95%88%E8%AF%BB%E5%8F%96http%E5%BA%94%E7%AD%94%E5%8C%85/","summary":"\u003ch1 id=\"常用写法-ioutilioreadall\"\u003e常用写法 ioutil/io.ReadAll\u003c/h1\u003e\n\u003ch2 id=\"code-demo\"\u003ecode demo\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttpresp, err := http.DefaultClient.Do(httpreq.WithContext(httpctx))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edefer httpresp.Body.Close()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebodyByte, err := ioutil.ReadAll(httpresp.Body)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"golang标准库实现\"\u003egolang标准库实现\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e ReadAll(r Reader) ([]byte, error) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tb :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]byte, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e512\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(b) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e cap(b) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Add more capacity (let append pick how much)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tb \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(b, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)[:len(b)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tn, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e r\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRead(b[len(b):cap(b)])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tb \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e b[:len(b)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003en]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e EOF {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\terr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nil\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e b, err\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"问题\"\u003e问题\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e一次申请512字节 如果应答很大，大量并发 则会出现问题，因为在频繁大量的申请512字节内存，申请速度超过gc的速度 很可能导致内存OOM\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"优化方案使用bytebufferpool\"\u003e优化方案使用bytebufferpool\u003c/h1\u003e\n\u003ch2 id=\"bytebufferpool介绍\"\u003ebytebufferpool介绍\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003ebytebufferpool将不同大小的byte[]分成了多个区间，动态调整，可以理解成一组可以动态调整大小的syncpool(byte[])\u003c/p\u003e","title":"golang高效读取http应答包"},{"content":"ubuntu下载安装 sudo apt update wget https://github.com/trojan-gfw/trojan/releases/download/v1.16.0/trojan-1.16.0-linux-amd64.tar.xz tar -xf trojan-1.16.0-linux-amd64.tar.xz sudo mv trojan/trojan /usr/local/bin/ sudo mkdir -p /etc/trojan/ sudo vi /etc/trojan/config.json ### 配置内容参考如下 配置修改 run_type: client local_addr: 本地地址建议 0.0.0.0 local_port: 本地端口 1080(这个随意设置) remote_addr:远程服务器地址 remote_port:代理服务器端口 password: 代理服务器访问密码 sni: 主机sni名称 以上注意针对性修改 其它可以不用修改 下面是demo { \u0026#34;run_type\u0026#34;: \u0026#34;client\u0026#34;, \u0026#34;local_addr\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;local_port\u0026#34;: 1080, \u0026#34;remote_addr\u0026#34;: \u0026#34;代理服务器地址\u0026#34;, \u0026#34;remote_port\u0026#34;: 443, \u0026#34;password\u0026#34;: [ \u0026#34;此处配置密码\u0026#34; ], \u0026#34;ssl\u0026#34;: { \u0026#34;verify\u0026#34;: true, \u0026#34;verify_hostname\u0026#34;: true, \u0026#34;cert\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;cipher\u0026#34;: \u0026#34;ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256\u0026#34;, \u0026#34;cipher_tls13\u0026#34;: \u0026#34;TLS_AES_128_GCM_SHA256\u0026#34;, \u0026#34;sni\u0026#34;: \u0026#34;此处配置主机sni名称\u0026#34;, \u0026#34;alpn\u0026#34;: [ \u0026#34;h2\u0026#34;, \u0026#34;http/1.1\u0026#34; ], \u0026#34;reuse_session\u0026#34;: true, \u0026#34;session_ticket\u0026#34;: false, \u0026#34;curves\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;prefer_server_cipher\u0026#34;: false }, \u0026#34;tcp\u0026#34;: { \u0026#34;no_delay\u0026#34;: true, \u0026#34;keep_alive\u0026#34;: true, \u0026#34;fast_open\u0026#34;: false, \u0026#34;fast_open_qlen\u0026#34;: 20 } } 创建systemd服务 sudo vi /etc/systemd/system/trojan.service ## 以下为内容 [Unit] Description=Trojan Service After=network.target [Service] Type=simple ExecStart=/usr/local/bin/trojan -c /etc/trojan/config.json Restart=on-failure [Install] WantedBy=multi-user.target 重新加载 systemd 服务并启动 Trojan sudo systemctl daemon-reload sudo systemctl start trojan sudo systemctl enable trojan ##检查状态 sudo systemctl status trojan ## 重新启动 sudo systemctl restart trojan chrome安装SwitchyOmega 地址 https://chromewebstore.google.com/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif https://github.com/FelisCatus/SwitchyOmega/releases 配置SwitchyOmega 在选项中设置prxoy里代理协议SOCKS5，代理服务器：127.0.0.1，端口1080（即浏览器连接本地） 然后选中这一项（proxy）即可 流量不够用的注意使用完毕停止就好 https://chatgpt.com/ ","permalink":"https://www.becool.vip/posts/tech/linux_trojan/","summary":"\u003ch1 id=\"ubuntu下载安装\"\u003eubuntu下载安装\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewget https:\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003egfw\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ereleases\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003edownload\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ev1\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e16.0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1.16\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elinux\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eamd64\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etar\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etar \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003exf trojan\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1.16\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elinux\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eamd64\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etar\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mv trojan\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eusr\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elocal\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ebin\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mkdir \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ep \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eetc\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo vi \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eetc\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etrojan\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejson\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e### 配置内容参考如下\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"配置修改\"\u003e配置修改\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003erun_type: client\u003c/li\u003e\n\u003cli\u003elocal_addr: 本地地址建议 0.0.0.0\u003c/li\u003e\n\u003cli\u003elocal_port: 本地端口 1080(这个随意设置)\u003c/li\u003e\n\u003cli\u003eremote_addr:远程服务器地址\u003c/li\u003e\n\u003cli\u003eremote_port:代理服务器端口\u003c/li\u003e\n\u003cli\u003epassword: 代理服务器访问密码\u003c/li\u003e\n\u003cli\u003esni: 主机sni名称\u003c/li\u003e\n\u003cli\u003e以上注意针对性修改 其它可以不用修改 下面是demo\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;run_type\u0026#34;: \u0026#34;client\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;local_addr\u0026#34;: \u0026#34;0.0.0.0\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;local_port\u0026#34;: 1080,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;remote_addr\u0026#34;: \u0026#34;代理服务器地址\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;remote_port\u0026#34;: 443,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;password\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;此处配置密码\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;ssl\u0026#34;: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;verify\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;verify_hostname\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;cert\u0026#34;: \u0026#34;\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;cipher\u0026#34;: \u0026#34;ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;cipher_tls13\u0026#34;: \u0026#34;TLS_AES_128_GCM_SHA256\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;sni\u0026#34;: \u0026#34;此处配置主机sni名称\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;alpn\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u0026#34;h2\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u0026#34;http/1.1\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;reuse_session\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;session_ticket\u0026#34;: false,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;curves\u0026#34;: \u0026#34;\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;prefer_server_cipher\u0026#34;: false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#34;tcp\u0026#34;: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;no_delay\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;keep_alive\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;fast_open\u0026#34;: false,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u0026#34;fast_open_qlen\u0026#34;: 20\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"创建systemd服务\"\u003e创建systemd服务\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo vi /etc/systemd/system/trojan.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e## 以下为内容\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Unit]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDescription=Trojan Service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfter=network.target\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Service]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eType=simple\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExecStart=/usr/local/bin/trojan -c /etc/trojan/config.json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRestart=on-failure\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Install]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWantedBy=multi-user.target\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"重新加载-systemd-服务并启动-trojan\"\u003e重新加载 \u003ccode\u003esystemd\u003c/code\u003e 服务并启动 Trojan\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl daemon\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ereload\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl start trojan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl enable trojan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e##检查状态\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl status trojan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e## 重新启动\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl restart trojan\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"chrome安装switchyomega\"\u003echrome安装SwitchyOmega\u003c/h1\u003e\n\u003ch2 id=\"地址\"\u003e地址\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://chromewebstore.google.com/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://github.com/FelisCatus/SwitchyOmega/releases\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"配置switchyomega\"\u003e配置SwitchyOmega\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e在选项中设置prxoy里代理协议SOCKS5，代理服务器：127.0.0.1，端口1080（即浏览器连接本地）\n然后选中这一项（proxy）即可\u003c/li\u003e\n\u003cli\u003e流量不够用的注意使用完毕停止就好\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1\u003e\u003ca href=\"https://chatgpt.com/\"\u003ehttps://chatgpt.com/\u003c/a\u003e\u003c/h1\u003e","title":"开启gpt环境_使用trojan代理"},{"content":"标题 # 一级标题 #号和标题名称之间1个空格 ## 二级标题 ### 三级标题 段落 这里是段落一,切换段落请使用空白行隔开\n这里是段落二 跟上面隔着一个空行\n换行(非段落) 推荐使用html标签\u0026lt;br\u0026gt;换行 在行尾部输入\u0026lt;br\u0026gt; 第1行\n强调 *斜体* **粗体** ***斜体+粗体*** 斜体 粗体 斜体+粗体 引用 \u0026gt; 此处为引用内容 \u0026gt;和内容之间要有空格 这里为引用内容1\n这里为引用内容2\n嵌套引用 \u0026gt; 此处为引用内容1 \u0026gt;\u0026gt; 此处为嵌套引用1 此处为引用内容1\n此处为嵌套引用1\n带有其它元素的引用 \u0026gt; ```带有代码块的引用``` \u0026gt; - 带有列表的引用 \u0026gt; 自行尝试支持的其它元素 带有代码块的引用\n带有列表的引用\n自行尝试支持的其它元素\n列表 有序列表 1. 有序列表一 1.和列表内容要用空格 数字不必一定按数学顺序排列 1. 有序列表二 2. 有序列表三 有序列表一 1.和列表内容要用空格 数字不必一定按数学顺序排列 有序列表二 有序列表三 无需列表 - 无需列表一 - 无需列表二 - 无需列表三 无需列表一 无需列表二 无需列表三 在列表中嵌套其它元素 要在保留列表连续性的同时在列表中添加另一种元素，请将该元素缩进四个空格或一个制表符\n- 列表1 - 列表2 列表2段落 - 列表3 - 子列表1 - 子列表2 列表1\n列表2 列表2段落\n列表3\n子列表1 子列表2 代码 要创建代码块，请将代码块的每一行缩进至少四个空格或一个制表符。 `短代码` 单个反引号扩起来 ``` echo \u0026#34;hello world\u0026#34; echo \u0026#34;day day up\u0026#34; ``` echo \u0026#34;hello world\u0026#34; echo \u0026#34;day day up\u0026#34; 分割线 要创建分隔线，请在单独一行上使用三个或多个星号 (***)、破折号 (---) 或下划线 (___) ，并且不能包含其他内容\n为了兼容性，请在分隔线的前后均添加空白行\n*** --- _________________ 连接/网址/邮箱 [疯子爱淡定](http://becool.vip) \u0026lt;http://becool.vip\u0026gt; \u0026lt;353175775@qq.com\u0026gt; 疯子爱淡定 http://becool.vip 353175775@qq.com\n图片 ![图片alt](图片链接 \u0026#34;图片title\u0026#34;) ![微信公众号_欢迎关注](/img/mpwechat.jpg \u0026#34;疯子爱淡定\u0026#34;) 嵌入音乐 本身不支持可以用html标签实现 不一定所有编辑器都支持\n\u0026lt;audio controls=\u0026#34;\u0026#34;\u0026gt; \u0026lt;source src=\u0026#34;http://bereal.becool.vip:6431/music/完美落地_徐佳莹.mp3\u0026#34; type=\u0026#34;audio/mpeg\u0026#34;\u0026gt; \u0026lt;/audio\u0026gt; 完美落地_徐佳莹\n嵌入视频 本身不支持可以用html标签实现 不一定所有编辑器都支持\n\u0026lt;video width=\u0026#34;640\u0026#34; height=\u0026#34;480\u0026#34; controls\u0026gt; \u0026lt;source src=\u0026#34;http://bereal.becool.vip:6431/music/瓦依那X任素汐_大梦.mp4\u0026#34; type=\u0026#34;video/mp4\u0026#34;\u0026gt; \u0026lt;/video\u0026gt; 瓦依那X任素汐_大梦\n# 实现页面内跳转 html标签 先定义一个锚点(id) 这个是目的地 在别的地方点击后跳转到锚点所在地方 上面图片章节大标题我标题我已经设置了锚\n\u0026lt;span id=\u0026#34;jumpimg\u0026#34;\u0026gt;图片\u0026lt;/span\u0026gt; 使用markdown语法设置点击跳转链接 在typora使用需要按住control键再点击鼠标\n[点我跳转到图片章节](#jumpimg) 点我跳转到图片章节\nmarkdown生成目录跳转 在正文最开始地方输入以下代码会自动生成目录 [toc] ","permalink":"https://www.becool.vip/posts/tech/markdown_demo/","summary":"\u003ch1 id=\"标题\"\u003e标题\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 一级标题 #号和标题名称之间1个空格\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e## 二级标题\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e### 三级标题\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"段落\"\u003e段落\u003c/h1\u003e\n\u003cp\u003e这里是段落一,切换段落请使用空白行隔开\u003c/p\u003e\n\u003cp\u003e这里是段落二 跟上面隔着一个空行\u003c/p\u003e\n\u003ch1 id=\"换行非段落\"\u003e换行(非段落)\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e推荐使用html标签\u0026lt;br\u0026gt;换行 在行尾部输入\u0026lt;br\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e第1行\u003cbr\u003e\u003c/p\u003e\n\u003ch1 id=\"强调\"\u003e强调\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e*斜体*\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e**粗体**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e***斜体+粗体***\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003cem\u003e斜体\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e粗体\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003e\u003cstrong\u003e斜体+粗体\u003c/strong\u003e\u003c/em\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"引用\"\u003e引用\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; 此处为引用内容 \u0026gt;和内容之间要有空格\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e这里为引用内容1\u003c/p\u003e\n\u003cp\u003e这里为引用内容2\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"嵌套引用\"\u003e嵌套引用\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; 此处为引用内容1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt;\u0026gt; 此处为嵌套引用1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e此处为引用内容1\u003c/p\u003e","title":"MarkDown语法模版"},{"content":"开发环境准备 公众号开启 开发者密码 记录公众号AppID/AppSecret 配置IP白名单 要有一台固定IP的服务器 定期扫描新增博客md文档 find ~/blog/content -name \u0026ldquo;*.md\u0026rdquo; \u0026gt;AllMdFiles.dat\ngrep -v -F -x -f doneMdFiles.dat AllMdFiles.dat \u0026gt; todoMdFiles.dat\n可以采用上述脚本实现 处理完毕后写入doneMdFiles.dat\n解析md文档 解析元数据信息 包括title标题 作者 cover名称 解析正文 上传图片正文的图片 url := fmt.Sprintf(\u0026#34;https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s\u0026amp;type=%s\u0026#34;, WechatAccessToken, mediaType) body := \u0026amp;bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile(\u0026#34;media\u0026#34;, fileName) req.Header.Set(\u0026#34;Content-Type\u0026#34;, writer.FormDataContentType()) resp, err := http.DefaultClient.Do(req) var result struct { MediaID string `json:\u0026#34;media_id\u0026#34;` Url string `json:\u0026#34;url\u0026#34;` } err = json.NewDecoder(resp.Body).Decode(\u0026amp;result) 会应答图片的media_id和可以在公众号使用的图片url 正文markdown转公众号格式 golang没有找到特别好的库 曲线救国\n访问https://doocs.github.io/md/ 然后将正文内容自动输入 自动获取output内容\n个人目前使用的chromedp库 核心代码\nerr = chromedp.Run(ctx, chromedp.Navigate(MarkDownToWechatUrl), // 打开目标网页 chromedp.WaitVisible(`[class=\u0026#34;CodeMirror cm-s-xq-light CodeMirror-wrap\u0026#34;]`, chromedp.ByQuery), //编辑区出现 等待元素可见 chromedp.EvaluateAsDevTools(` var codeMirrorDiv = document.querySelector(\u0026#39;[class=\u0026#34;CodeMirror cm-s-xq-light CodeMirror-wrap\u0026#34;]\u0026#39;); var setValueContent = \u0026#34;`+mdContentInfo+`\u0026#34;; codeMirrorDiv.CodeMirror.setValue(setValueContent); `, nil), // 输入markdown格式内容 chromedp.Sleep(3*time.Second), //等待3秒钟 chromedp.OuterHTML(\u0026#34;#output-wrapper\u0026#34;, \u0026amp;previewContent, chromedp.NodeVisible), //chromedp.OuterHTML(\u0026#34;.preview\u0026#34;, \u0026amp;previewContent, chromedp.NodeVisible), ) 发送草稿 调用API本身不复杂 核心代码如下:\nurl := fmt.Sprintf(\u0026#34;https://api.weixin.qq.com/cgi-bin/draft/add?access_token=%s\u0026#34;, WechatAccessToken) // 构建请求体 article := Article{ Title: meta.Title, Author: meta.Author[0], Digest: meta.Description, Content: wechatContent, ContentSourceURL: meta.OriginalUrlPath, ThumbMediaID: coverImgMediaId, } requestBody := DraftAddRequest{ Articles: []Article{article}, } // 将请求体转换为 JSON 格式 jsonData, err := json.Marshal(requestBody) response, err := http.Post(url, \u0026#34;application/json\u0026#34;, bytes.NewBuffer(jsonData)) err = json.NewDecoder(response.Body).Decode(\u0026amp;draftAddResponse) log.Printf(\u0026#34;草稿添加成功 media_id:%s\\n\u0026#34;, draftAddResponse.MediaID) 手工登录公众号平台发布 通过API发布的文章 不会推荐 不显示在主页 无法选择原创,开启赞赏等 所以目前个人自动发布到草稿 然后发消息给手机 等有空集中处理下 ","permalink":"https://www.becool.vip/posts/tech/%E5%8D%9A%E5%AE%A2%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E5%88%B0%E5%85%AC%E4%BC%97%E5%8F%B7/","summary":"\u003ch1 id=\"开发环境准备\"\u003e开发环境准备\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e公众号开启 \u003cstrong\u003e开发者密码\u003c/strong\u003e 记录公众号AppID/AppSecret\u003c/li\u003e\n\u003cli\u003e配置\u003cstrong\u003eIP白名单\u003c/strong\u003e 要有一台固定IP的服务器\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"定期扫描新增博客md文档\"\u003e定期扫描新增博客md文档\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003efind ~/blog/content -name \u0026ldquo;*.md\u0026rdquo; \u0026gt;AllMdFiles.dat\u003c/p\u003e\n\u003cp\u003egrep -v -F -x -f doneMdFiles.dat AllMdFiles.dat \u0026gt; todoMdFiles.dat\u003c/p\u003e\n\u003cp\u003e可以采用上述脚本实现 处理完毕后写入doneMdFiles.dat\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch1 id=\"解析md文档\"\u003e解析md文档\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e解析元数据信息 包括title标题 作者 cover名称\u003c/li\u003e\n\u003cli\u003e解析正文\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"上传图片正文的图片\"\u003e上传图片正文的图片\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eurl :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e fmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026amp;type=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, WechatAccessToken, mediaType)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebody :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ebytes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuffer{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewriter :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e multipart\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNewWriter(body)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epart, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e writer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateFormFile(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;media\u0026#34;\u003c/span\u003e, fileName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ereq\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHeader\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSet(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e, writer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFormDataContentType())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eresp, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDefaultClient\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDo(req)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e result struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tMediaID string \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003ejson:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;media_id\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tUrl     string \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003ejson:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;url\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eerr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e json\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNewDecoder(resp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBody)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDecode(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eresult)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e会应答图片的\u003c/span\u003emedia_id和可以在公众号使用的图片url\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003ch1 id=\"正文markdown转公众号格式\"\u003e正文markdown转公众号格式\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003egolang没有找到特别好的库 曲线救国\u003c/p\u003e","title":"博客自动同步到微信公众号"},{"content":"脚本扫描hugo网站所有网页 调用API进行百度搜索收录 hugo博客会将所有网页都生成到sitemap.xml中 扫描即可 取出10条未成功收录过的网址 token需要事先从https://ziyuan.baidu.com/site/index获取 需要登录百度帐号 site:www.becool.vip 需要替换成自己的网站 #!/usr/bin/zsh source ~/.zshrc #找到所有的网址 grep -o \u0026#39;\u0026lt;loc\u0026gt;.*\u0026lt;/loc\u0026gt;\u0026#39; /home/ubuntu/blog/public/sitemap.xml | sed \u0026#39;s/\u0026lt;loc\u0026gt;\\(.*\\)\u0026lt;\\/loc\u0026gt;/\\1/\u0026#39; \u0026gt;/tmp/sendSitemap/allurl.txt #从allurl.txt中找到10条不在success.txt中的文本行 写入sendurl.txt grep -v -F -x -f /tmp/sendSitemap/success.txt /tmp/sendSitemap/allurl.txt | head -n 10 \u0026gt;/tmp/sendSitemap/sendurl.txt #调用百度API curl -H \u0026#39;Content-Type:text/plain\u0026#39; --data-binary @/tmp/sendSitemap/sendurl.txt \u0026#34;http://data.zz.baidu.com/urls?site=www.becool.vip\u0026amp;token=xxxtoken\u0026#34; -o /tmp/sendSitemap/result.txt \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 # 收录成功则追加 sendurl.txt 的内容到 success.txt 文件 if grep -q \u0026#34;success\u0026#34; /tmp/sendSitemap/result.txt; then cat /tmp/sendSitemap/sendurl.txt \u0026gt;\u0026gt; /tmp/sendSitemap/success.txt fi result=\u0026#34;百度搜索网址收录同步结果 $(cat /tmp/sendSitemap/result.txt)\u0026#34; #将结果通知到自己手机 /home/ubuntu/shell/SelfNotify $result 添加定时任务 crontab -e 每天7点调用 0 7 * * * nohup /home/ubuntu/shell/BaiduSiteRecord.sh \u0026gt; /home/ubuntu/shell/BaiduSiteRecord.log 2\u0026gt;\u0026amp;1 \u0026amp; ","permalink":"https://www.becool.vip/posts/tech/baidusiterecord/","summary":"\u003ch1 id=\"脚本扫描hugo网站所有网页-调用api进行百度搜索收录\"\u003e脚本扫描hugo网站所有网页 调用API进行百度搜索收录\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003ehugo博客会将所有网页都生成到sitemap.xml中 扫描即可 取出10条未成功收录过的网址\u003c/li\u003e\n\u003cli\u003etoken需要事先从https://ziyuan.baidu.com/site/index获取 需要登录百度帐号\u003c/li\u003e\n\u003cli\u003esite:www.becool.vip 需要替换成自己的网站\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#!/usr/bin/zsh\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003esource ~/.zshrc\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#找到所有的网址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egrep -o \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;loc\u0026gt;.*\u0026lt;/loc\u0026gt;\u0026#39;\u003c/span\u003e /home/ubuntu/blog/public/sitemap.xml | sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/\u0026lt;loc\u0026gt;\\(.*\\)\u0026lt;\\/loc\u0026gt;/\\1/\u0026#39;\u003c/span\u003e \u0026gt;/tmp/sendSitemap/allurl.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#从allurl.txt中找到10条不在success.txt中的文本行 写入sendurl.txt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egrep -v -F -x -f /tmp/sendSitemap/success.txt /tmp/sendSitemap/allurl.txt | head -n \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e \u0026gt;/tmp/sendSitemap/sendurl.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#调用百度API \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl -H \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Type:text/plain\u0026#39;\u003c/span\u003e --data-binary @/tmp/sendSitemap/sendurl.txt \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://data.zz.baidu.com/urls?site=www.becool.vip\u0026amp;token=xxxtoken\u0026#34;\u003c/span\u003e -o /tmp/sendSitemap/result.txt \u0026gt;/dev/null 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 收录成功则追加 sendurl.txt 的内容到 success.txt 文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e grep -q \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;success\u0026#34;\u003c/span\u003e /tmp/sendSitemap/result.txt; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cat /tmp/sendSitemap/sendurl.txt \u0026gt;\u0026gt; /tmp/sendSitemap/success.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eresult\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;百度搜索网址收录同步结果 \u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ecat /tmp/sendSitemap/result.txt\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#将结果通知到自己手机\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/home/ubuntu/shell/SelfNotify $result\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"添加定时任务\"\u003e添加定时任务\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003ecrontab -e\u003c/li\u003e\n\u003cli\u003e每天7点调用\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e0 7 * * * nohup /home/ubuntu/shell/BaiduSiteRecord.sh \u0026gt; /home/ubuntu/shell/BaiduSiteRecord.log 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"脚本定期将hugo博客网址收录到百度"},{"content":"手机安装Send Anywhere 安卓/IOS都有 目前免费 电脑打开网址 https://send-anywhere.com/#transfer 电脑给手机发送文件 https://send-anywhere.com/#transfer\n点击Send 之后选择要传输的文件、点击send、过几秒会收到一个二维码\n手机打开App\u0026mdash;\u0026gt;Receive/接收 扫码下载到手机 手机和电脑浏览器都会显示传输速度\n手机可能会看几秒广告\n视频或者图片会默认下载到相册 其它常规文件 IOS用文件管理器(Files)查看 ","permalink":"https://www.becool.vip/posts/tech/send-anywhere-%E6%89%8B%E6%9C%BA%E7%94%B5%E8%84%91%E9%97%B4%E4%BC%A0%E8%BE%93%E6%96%87%E4%BB%B6/","summary":"\u003ch1 id=\"手机安装send-anywhere\"\u003e手机安装Send Anywhere\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e安卓/IOS都有 目前免费\u003c/li\u003e\n\u003cli\u003e\n\u003cimg src=\"/img/sendAnywhere_Ios.jpg\" style=\"zoom:33%;\" /\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"电脑打开网址\"\u003e电脑打开网址\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://send-anywhere.com/#transfer\"\u003ehttps://send-anywhere.com/#transfer\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"电脑给手机发送文件\"\u003e电脑给手机发送文件\u003c/h1\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://send-anywhere.com/#transfer\"\u003ehttps://send-anywhere.com/#transfer\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e点击Send 之后选择要传输的文件、点击send、过几秒会收到一个二维码\u003c/p\u003e\n\u003cimg src=\"/img/sendAnyWhere_sendFile.jpg\" style=\"zoom:33%;\" /\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e手机打开App\u0026mdash;\u0026gt;Receive/接收 扫码下载到手机 手机和电脑浏览器都会显示传输速度\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e手机可能会看几秒广告\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e视频或者图片会默认下载到相册\u003c/li\u003e\n\u003cli\u003e其它常规文件 IOS用文件管理器(Files)查看\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e","title":"手机电脑间传输文件"},{"content":"目的 工具非本人，仅供分享学习，切勿用于任何商业目的！！！ music-dl(python) https://github.com/0xHJK/music-dl 安装使用 使用pip安装（推荐，注意前面有一个py）： $ pip3 install pymusic-dl 手动安装（最新）： $ git clone https://github.com/0xHJK/music-dl.git $ cd music-dl $ python3 setup.py install 不安装直接运行： $ git clone https://github.com/0xHJK/music-dl.git $ cd music-dl $ pip3 install -r requirements.txt $ ./music-dl # 或 python3 music-dl 使用 music-dl(php) https://github.com/guanguans/music-dl/ 安装 Download the music-dl file curl \u0026#39;https://raw.githubusercontent.com/guanguans/music-dl/master/builds/music-dl\u0026#39; -o music-dl --progress-bar chmod +x music-dl Install via Composer composer global require guanguans/music-dl:dev-master --dev -v --ignore-platform-req=ext-pcntl # global composer require guanguans/music-dl:dev-master --dev -v --ignore-platform-req=ext-pcntl # local 使用 用空格取消选择/选择 网易云音乐歌单下载 https://github.com/magicdawn/yun-playlist-downloader 安装 # pnpm (recommend) $ pnpm add -g yun-playlist-downloader # npm $ npm i yun-playlist-downloader -g 使用 -c 为并发数量 \u0026ndash;cookie 使用cookie 下载vip需要登录 具体获取方法 http://www.becool.vip/posts/tech/%E8%8E%B7%E5%8F%96%E7%BD%91%E7%AB%99cookie/ 需要保存到yun.cookie.txt文件，并存放于程序运行当前目录 否则就要指定具体路径 执行样例 yun -f \u0026#34;:singer - :songName.:ext\u0026#34; --cookie -c 10 https://music.163.com/\\#/playlist\\?id\\=8494569029 ","permalink":"https://www.becool.vip/posts/music/%E9%9F%B3%E4%B9%90%E4%B8%8B%E8%BD%BD/","summary":"\u003ch1 id=\"目的\"\u003e目的\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e工具非本人，仅供分享学习，切勿用于任何商业目的！！！\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"music-dlpython\"\u003emusic-dl(python)\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/0xHJK/music-dl\"\u003ehttps://github.com/0xHJK/music-dl\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"安装使用\"\u003e安装使用\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e使用pip安装（推荐，注意前面有一个\u003ccode\u003epy\u003c/code\u003e）：\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ pip3 install pymusic-dl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e手动安装（最新）：\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ git clone https://github.com/0xHJK/music-dl.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cd music-dl\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ python3 setup.py install\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e不安装直接运行：\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ git clone https://github.com/0xHJK/music-dl.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cd music-dl\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ pip3 install -r requirements.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ ./music-dl\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 或 python3 music-dl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"使用\"\u003e使用\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/music-dl_python_use.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003ch1 id=\"music-dlphp\"\u003emusic-dl(php)\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/guanguans/music-dl/\"\u003ehttps://github.com/guanguans/music-dl/\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eDownload the \u003ca href=\"https://github.com/guanguans/music-dl/blob/master/builds/music-dl\"\u003emusic-dl\u003c/a\u003e file\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl \u0026#39;https://raw.githubusercontent.com/guanguans/music-dl/master/builds/music-dl\u0026#39; -o music-dl --progress-bar\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echmod +x music-dl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003eInstall via Composer\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecomposer global require guanguans/music-dl:dev-master --dev -v --ignore-platform-req=ext-pcntl # global\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecomposer require guanguans/music-dl:dev-master --dev -v --ignore-platform-req=ext-pcntl # local\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"使用-1\"\u003e使用\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e用空格取消选择/选择\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/music_dl_php_use.gif\" alt=\"\"  /\u003e\n\u003c/p\u003e","title":"音乐下载"},{"content":"获取网站cookie_chrome插件 推荐 安装 chrome 插件 EditThisCookie https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg\n设置插件导出格式为: \u0026ldquo;分号分隔键值对\u0026rdquo; 登录某个网站 goto https://music.163.com/#\n使用 EditThisCookie 导出 cookie到粘贴板 复制使用即可 使用chrome浏览器开发者工具获取 ","permalink":"https://www.becool.vip/posts/tech/%E8%8E%B7%E5%8F%96%E7%BD%91%E7%AB%99cookie/","summary":"\u003ch1 id=\"获取网站cookie_chrome插件-推荐\"\u003e获取网站cookie_chrome插件 推荐\u003c/h1\u003e\n\u003ch2 id=\"安装-chrome-插件\"\u003e安装 chrome 插件\u003c/h2\u003e\n\u003cp\u003eEditThisCookie \u003ca href=\"https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg\"\u003ehttps://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"设置插件导出格式为-分号分隔键值对\"\u003e设置插件导出格式为: \u0026ldquo;分号分隔键值对\u0026rdquo;\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/getCookiePluginOption.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003ch2 id=\"登录某个网站\"\u003e登录某个网站\u003c/h2\u003e\n\u003cp\u003egoto \u003ca href=\"https://music.163.com/#\"\u003ehttps://music.163.com/#\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"使用-editthiscookie-导出-cookie到粘贴板-复制使用即可\"\u003e使用 EditThisCookie 导出 cookie到粘贴板 复制使用即可\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/exportCookie.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003ch1 id=\"使用chrome浏览器开发者工具获取\"\u003e使用chrome浏览器开发者工具获取\u003c/h1\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/chrome%e5%bc%80%e5%8f%91%e8%80%85%e5%b7%a5%e5%85%b7%e8%8e%b7%e5%8f%96cookie.jpg\" alt=\"\"  /\u003e\n\u003c/p\u003e","title":"获取网站cookie"},{"content":"乐队介绍 瓦依那 壮语 稻花飘香的田野 岜農 主创兼主唱 十八 吉他手 路民 打击乐 大梦_乐夏S3 瓦依那_任素汐 前几日看乐夏听到，近几日刷短视频又频繁遇到，简单做下记录 任素汐声音挺好听 感觉挺适合唱歌 从《乐队的夏天》S3中听到 跟任素汐合唱版本，相信大部份能共情，我自己有几秒是忍者没落泪，也许是触碰到一些往事吧。现实就是如此，有太多的担忧，太多的不知道怎么办，放到现代大城市中，加之比较来比较去，人也容易焦虑。 歌词最多是**“该怎么办”，以前的自己也遇到类似场景，担心，害怕，焦虑过，不过自己大多时候能够自我解决了，不管是通过网上查找解决方案，还是自己思考与自己和解、时间长了自然放弃或者遗忘等等，此刻如果再问我“该怎么办”，我的回答便是“该怎么办就怎么办”** 该怎办就怎么办 听上去这个回答很扯淡，其实这个就是答案，可能也是唯一正确的答案 小时候弄脏新衣，弄坏东西，首先肯定是担心被批评、被打，基本上也真的被批评 被打。自然会习惯，这个时候大多还不知道什么是该，所以顺其自然大部份都是ok的 有自己的独立思考，成人后，也许这里的“该”或许会更加渺茫，但其实可以很简单，力所能及即可，当时的认知造就了当时的怎么办，接受就好，也不需要过分犹豫，相信当时的判断(也许未来大概率不是最优，但又有何妨) 歌词 我已经六岁 走在田野里， 一个不小心 扑倒在水里， 该怎么办~ 弄脏了新衣 弄坏了玩具， 爸爸会生气 妈妈会着急， 该怎么办~ 站在春风里 大声哭泣 该怎么办~ 我已十二岁 没离开过家， 要去上中学 离家有几十里， 该怎么办~ 若是生了病 若弄丢了钱， 被人看不顺眼 我单薄的身体， 该怎么办， 我的父亲 总沉默无语 该怎么办~ 我已十八岁 没考上大学， 是应该继续 还是打工去， 该怎么办~ 我来到了深圳 转悠了些日子， 没找到工作 钱花得差不多， 该怎么办~ 十字路口 人往往返返 该怎么办~ 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒~ 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒~ 我已二十三 大学就要毕业， 看身边的人 渐渐地远去， 该怎么办~ 害怕谈恋爱 害怕找工作， 害怕回家里 害怕去外地， 该怎么办~ 躲在角落里 掩面哭泣 该怎么办~ 我已二十八 处了个对象， 与哥哥姐姐们 相遇在街上， 于是 就吃个饭， 她姐姐问我 没正式工作， 要不要房子 要不要孩子呀, 要怎么办~ 我措手不及 仓皇离去 要怎么办~ 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒~ 我已三十八 孩子很听话， 想给她多陪伴 但必须加班， 该怎么办~ 柴米和油盐 学校和医院， 我转个不停 赚不到更多钱， 该怎么办~ 我像部机器 不能停歇 该怎么办~ 我已四十八 孩子已长大， 她在外玩耍 很晚都不回家， 该怎么办~ 所有的希望 在孩子身上， 我们的关系 却渐渐地疏离， 该怎么办~ 半生已过 仍不得解脱 该怎么办~ 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒~ 我已五十八 早就白了发， 很多的地方 已变得不听话， 该怎么办~ 年小的孩子 常年在外地， 年迈的母亲 什么已记不起， 该怎么办~ 担心不完 聚了又散 该怎么办~ 我已六十八 母亲已不在， 老二离了婚 娃交给我来带， 该怎么办... 他说趁年轻 再去闯一闯， 说不定归来时 会有一番景象， 我只求他平安， 太多的错误 总在重复 该怎么办... 我已七十八 突然间倒下， 躺在病床上 时间变很漫长， 该怎么办... 面对那个未知 无助得像孩子， 在老伴面前 装作却很释然， 说这 只是小坎， 生命的烛火 在风中摇摆 该怎么办... 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒~ 滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒， 滴滴哒哒 滴滴哒哒哒， 我已八十八 走在田野里， 看见个小孩子 在风里哭泣， 春光正灿烂， 过往的执念 过往如云烟， 太多的风景 没人全看清， 放不下 怎圆满， 如果生命 只是大梦一场， 你会怎么办...... 如果生命 只是大梦一场， 你会怎么办...… ","permalink":"https://www.becool.vip/posts/music/%E5%A4%A7%E6%A2%A6/","summary":"\u003ch1 id=\"乐队介绍\"\u003e乐队介绍\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e瓦依那 壮语 稻花飘香的田野\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://baike.baidu.com/item/%E5%B2%9C%E8%BE%B2/63309569?fromModule=lemma_inlink\"\u003e岜農\u003c/a\u003e   主创兼主唱\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://baike.baidu.com/item/%E5%8D%81%E5%85%AB/63309522?fromModule=lemma_inlink\"\u003e十八\u003c/a\u003e 吉他手\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://baike.baidu.com/item/%E8%B7%AF%E6%B0%91/63309597?fromModule=lemma_inlink\"\u003e路民\u003c/a\u003e 打击乐\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"大梦_\"\u003e\u003ca href=\"https://www.bilibili.com/video/BV1Lj411C7Z7/?spm_id_from=333.337.search-card.all.click\u0026amp;vd_source=0f6dad8d4796f84a268a0c87c54f6758\"\u003e大梦_乐夏S3 瓦依那_任素汐\u003c/a\u003e\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e前几日看乐夏听到，近几日刷短视频又频繁遇到，简单做下记录\u003c/li\u003e\n\u003cli\u003e任素汐声音挺好听 感觉挺适合唱歌\u003c/li\u003e\n\u003cli\u003e从《乐队的夏天》S3中听到 跟任素汐合唱版本，相信大部份能共情，我自己有几秒是忍者没落泪，也许是触碰到一些往事吧。现实就是如此，有太多的担忧，太多的不知道怎么办，放到现代大城市中，加之比较来比较去，人也容易焦虑。\u003c/li\u003e\n\u003cli\u003e歌词最多是**“该怎么办”，\u003cstrong\u003e以前的自己也遇到类似场景，担心，害怕，焦虑过，不过自己大多时候能够自我解决了，不管是通过网上查找解决方案，还是自己思考与自己和解、时间长了自然放弃或者遗忘等等，此刻如果再问我“该怎么办”，我的回答便是\u003c/strong\u003e“该怎么办就怎么办”**\u003c/li\u003e\n\u003cli\u003e该怎办就怎么办\n\u003cul\u003e\n\u003cli\u003e听上去这个回答很扯淡，其实这个就是答案，可能也是唯一正确的答案\u003c/li\u003e\n\u003cli\u003e小时候弄脏新衣，弄坏东西，首先肯定是担心被批评、被打，基本上也真的被批评 被打。自然会习惯，这个时候大多还不知道什么是\u003cstrong\u003e该，所以顺其自然大部份都是ok的\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e有自己的独立思考，成人后，也许这里的“\u003cstrong\u003e该”\u003cstrong\u003e或许会更加渺茫，但其实可以很简单，力所能及即可，当时的认知造就了当时的\u003c/strong\u003e怎么办\u003c/strong\u003e，接受就好，也不需要过分犹豫，相信当时的判断(也许未来大概率不是最优，但又有何妨)\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"歌词\"\u003e歌词\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已经六岁 走在田野里，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e一个不小心 扑倒在水里，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e弄脏了新衣 弄坏了玩具，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e爸爸会生气 妈妈会着急，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e站在春风里 大声哭泣 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已十二岁 没离开过家，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e要去上中学 离家有几十里，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e若是生了病 若弄丢了钱，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e被人看不顺眼 我单薄的身体，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我的父亲 总沉默无语 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已十八岁 没考上大学，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e是应该继续 还是打工去，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我来到了深圳 转悠了些日子，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e没找到工作 钱花得差不多，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e十字路口 人往往返返 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已二十三 大学就要毕业，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e看身边的人 渐渐地远去，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e害怕谈恋爱 害怕找工作，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e害怕回家里 害怕去外地，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e躲在角落里 掩面哭泣 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已二十八 处了个对象，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e与哥哥姐姐们 相遇在街上，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e于是 就吃个饭，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e她姐姐问我 没正式工作，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e要不要房子 要不要孩子呀,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e要怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我措手不及 仓皇离去 要怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已三十八 孩子很听话，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e想给她多陪伴 但必须加班，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e柴米和油盐 学校和医院，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我转个不停 赚不到更多钱，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我像部机器 不能停歇 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已四十八 孩子已长大，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e她在外玩耍 很晚都不回家，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e所有的希望 在孩子身上，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我们的关系 却渐渐地疏离，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e半生已过 仍不得解脱 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已五十八 早就白了发，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e很多的地方 已变得不听话，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e年小的孩子 常年在外地，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e年迈的母亲 什么已记不起，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e担心不完 聚了又散 该怎么办~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已六十八 母亲已不在，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e老二离了婚 娃交给我来带，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e他说趁年轻 再去闯一闯，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e说不定归来时 会有一番景象，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我只求他平安，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e太多的错误 总在重复 该怎么办...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已七十八 突然间倒下，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e躺在病床上 时间变很漫长，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e该怎么办...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e面对那个未知 无助得像孩子，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e在老伴面前 装作却很释然，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e说这 只是小坎，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e生命的烛火 在风中摇摆 该怎么办...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒~\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒 滴滴哒哒 滴滴哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e滴滴哒哒 滴滴哒哒哒，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我已八十八 走在田野里，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e看见个小孩子 在风里哭泣，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e春光正灿烂，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e过往的执念 过往如云烟，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e太多的风景 没人全看清，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e放不下 怎圆满，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e如果生命 只是大梦一场，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你会怎么办......\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e如果生命 只是大梦一场，\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你会怎么办...…\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"大梦 瓦依那X任素汐"},{"content":"乐队介绍 这是一支由彝族流浪诗人、山东三开流神医、东北夜行骑士、西南原始摩登人、淄博长发小贝所建立的摇滚乐队，成立于2001年初。他们来自不同的地方，有着不同的经历，他们的音乐中从平静到狂躁、从孤独到幸福，从自恋自伤到纵情高歌，听者往往会将自身最隐秘的情感释放出来，进入一种宣泄状态 前奏真好听，声音碎片的鼓真心不错，虽然无法解决你的迷茫，听起来有些释怀的感觉 听 致我的迷茫兄弟_qq音乐\n致我的迷茫兄弟_声音碎片\n致我的迷茫兄弟_歌词 你好 让我们一起忘掉今天 让我们一起抵抗虚无 请你把鼓声敲得响亮 飞扬的不该止于这里 让我们再次回到街上 像从前那样头脑清楚 哦是的 岁月让生命变得脆弱 机器让人性变得可疑 娱乐让思考变得可笑 当你在洪流之中挣扎 什么是你的救命稻草 你不能带着迷惑离开 你好 沙漠里不长虚弱的草 大海里没有无名之辈 你母亲让你独一无二 你不是谁的一颗棋子 你不要轻易变成工具 你发誓完整你的生命 哦不管 风会向哪个方向吹拂 握紧你手中琴和酒杯 听它在午夜叮当作响 这不是孤雁离群悲鸣 这声音来自西南之南 他孤独 可是无限清醒 ","permalink":"https://www.becool.vip/posts/music/%E8%87%B4%E6%88%91%E7%9A%84%E8%BF%B7%E8%8C%AB%E5%85%84%E5%BC%9F/","summary":"\u003ch1 id=\"乐队介绍\"\u003e乐队介绍\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e这是一支由\u003ca href=\"https://baike.baidu.com/item/%E5%BD%9D%E6%97%8F/130821?fromModule=lemma_inlink\"\u003e彝族\u003c/a\u003e流浪诗人、山东三开流神医、东北夜行骑士、西南原始摩登人、\u003ca href=\"https://baike.baidu.com/item/%E6%B7%84%E5%8D%9A/167509?fromModule=lemma_inlink\"\u003e淄博\u003c/a\u003e长发小贝所建立的\u003ca href=\"https://baike.baidu.com/item/%E6%91%87%E6%BB%9A%E4%B9%90%E9%98%9F/6817285?fromModule=lemma_inlink\"\u003e摇滚乐队\u003c/a\u003e，成立于2001年初。他们来自不同的地方，有着不同的经历，他们的\u003ca href=\"https://baike.baidu.com/item/%E9%9F%B3%E4%B9%90/61907?fromModule=lemma_inlink\"\u003e音乐\u003c/a\u003e中从平静到狂躁、从孤独到幸福，从自恋自伤到纵情\u003ca href=\"https://baike.baidu.com/item/%E9%AB%98%E6%AD%8C/4610935?fromModule=lemma_inlink\"\u003e高歌\u003c/a\u003e，听者往往会将自身最隐秘的情感释放出来，进入一种宣泄状态\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e前奏真好听，声音碎片的鼓真心不错，虽然无法解决你的迷茫，听起来有些释怀的感觉\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"听\"\u003e听\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://y.qq.com/n/ryqq/songDetail/001VZLLN0C2vyN\"\u003e致我的迷茫兄弟_qq音乐\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://bereal.becool.vip:6431/music/%E8%87%B4%E6%88%91%E7%9A%84%E8%BF%B7%E8%8C%AB%E5%85%84%E5%BC%9F_%E5%A3%B0%E9%9F%B3%E7%A2%8E%E7%89%87%E4%B9%90%E9%98%9F.mp3\"\u003e致我的迷茫兄弟_声音碎片\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"致我的迷茫兄弟_歌词\"\u003e致我的迷茫兄弟_歌词\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你好 让我们一起忘掉今天\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e让我们一起抵抗虚无\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e请你把鼓声敲得响亮\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e飞扬的不该止于这里\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e让我们再次回到街上\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e像从前那样头脑清楚\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e哦是的 岁月让生命变得脆弱\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e机器让人性变得可疑\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e娱乐让思考变得可笑\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e当你在洪流之中挣扎\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e什么是你的救命稻草\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你不能带着迷惑离开\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你好 沙漠里不长虚弱的草\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e大海里没有无名之辈\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你母亲让你独一无二\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你不是谁的一颗棋子\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你不要轻易变成工具\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你发誓完整你的生命\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e哦不管 风会向哪个方向吹拂\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e握紧你手中琴和酒杯\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e听它在午夜叮当作响\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e这不是孤雁离群悲鸣\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e这声音来自西南之南\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e他孤独 可是无限清醒\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"致我的迷茫兄弟"},{"content":"介绍 原唱 乱弹阿翔，电影《翻滚吧!阿信》主题曲 目前能听到的大多是徐佳莹的这个版本 2个版本各有味道 最近很喜欢,作为手机铃声也不错 听__徐佳莹版本 看__声音为原版乱彈阿翔 完美落地歌词 完美落地 - 徐佳莹 (LALA Xu) 词：乱弹阿翔 曲：乱弹阿翔 从现在我不会再逃避 重新的唤起 埋葬在我心底的血液 沉住气 我的心不再移 屏息不放弃 慢慢的朝着我的梦前进 忘了吧 混乱的事不再提 放了吧 把心思变唯一 我会用尽所有力 奋力的跃起在天际 迎着光明 我会更用力呼吸 飞到另一个灿烂天空 完美 落地 我相信有努力会开启 久违的光明 老天爷总是会看得清 重新又回到最初自己 每一次练习 我只想朝着我的梦前进 忘了吧 混乱的事不再提 放了吧 把心思变唯一 我会用尽所有力 奋力的跃起在天际 迎着光明 我会更用力呼吸 飞到另一个灿烂天空 完美 落地 ","permalink":"https://www.becool.vip/posts/music/%E5%AE%8C%E7%BE%8E%E8%90%BD%E5%9C%B0/","summary":"\u003ch1 id=\"介绍\"\u003e介绍\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e原唱 乱弹阿翔，电影《翻滚吧!阿信》主题曲\u003c/li\u003e\n\u003cli\u003e目前能听到的大多是徐佳莹的这个版本 2个版本各有味道\u003c/li\u003e\n\u003cli\u003e最近很喜欢,作为手机铃声也不错\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"听__徐佳莹版本\"\u003e听__徐佳莹版本\u003c/h1\u003e\n\u003caudio controls=\"\"\u003e\n    \u003csource src=\"http://bereal.becool.vip:6431/music/完美落地_徐佳莹.mp3\" type=\"audio/mpeg\"\u003e\n\u003c/audio\u003e\n\u003ch1 id=\"看__声音为原版乱彈阿翔\"\u003e看__声音为原版乱彈阿翔\u003c/h1\u003e\n\u003cvideo width=\"640\" height=\"480\" controls\u003e\n    \u003csource src=\"http://bereal.becool.vip:6431/music/完美落地.mp4\" type=\"video/mp4\"\u003e\n\u003c/video\u003e\n\u003ch1 id=\"完美落地歌词\"\u003e完美落地歌词\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e完美落地 - 徐佳莹 (LALA Xu)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e词：乱弹阿翔\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e曲：乱弹阿翔\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e从现在我不会再逃避\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e重新的唤起\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e埋葬在我心底的血液\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e沉住气\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我的心不再移\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e屏息不放弃\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e慢慢的朝着我的梦前进\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e忘了吧\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e混乱的事不再提\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e放了吧\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e把心思变唯一\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我会用尽所有力\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e奋力的跃起在天际\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e迎着光明\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我会更用力呼吸\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e飞到另一个灿烂天空\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e完美 落地\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我相信有努力会开启\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e久违的光明\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e老天爷总是会看得清\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e重新又回到最初自己\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e每一次练习\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我只想朝着我的梦前进\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e忘了吧\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e混乱的事不再提\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e放了吧\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e把心思变唯一\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我会用尽所有力\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e奋力的跃起在天际\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e迎着光明\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e我会更用力呼吸\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e飞到另一个灿烂天空\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e完美 落地\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"完美落地"},{"content":"安利一个最近喜欢的文件传输工具 https://github.com/schollz/croc https://schollz.com/tinker/croc6/ 安全高效 目前我个人一直在使用 所有电脑安装croc https://github.com/schollz/croc curl https://getcroc.schollz.com | bash https://github.com/schollz/croc/releases 建立自己的文件互传中继 挑选1台由公网IP的服务器做中继 记录自己的ip nohup croc relay \u0026gt;croc_relay.log 2\u0026gt;\u0026amp;1 \u0026amp; ubuntu@VM-0-7-ubuntu:~/shell$ cat croc_relay.log nohup: ignoring input [info]\t2023/05/09 14:59:07 starting croc relay version v9.6.4-1fce28e [info]\t2023/05/09 14:59:07 starting TCP server on :9009 [info]\t2023/05/09 14:59:07 starting TCP server on :9010 [info]\t2023/05/09 14:59:07 starting TCP server on :9011 [info]\t2023/05/09 14:59:07 starting TCP server on :9012 [info]\t2023/05/09 14:59:07 starting TCP server on :9013 如果没有公网ip也可以用下面我自己的服务器体验 http://bereal.becool.vip:6431/gethomeip 发送文件 切换到文件所在目录 [root@VM-100-15-centos ~/shell]# croc --relay \u0026#34;113.109.242.138:9009\u0026#34; send Mazu_shark.log.2023050815 Sending \u0026#39;Mazu_shark.log.2023050815\u0026#39; (29.8 MB) Code is: 4248-never-jazz-japan On the other computer run croc --relay 113.109.242.138:9009 4248-never-jazz-japan 接收文件 在另外一台机器 执行上面的命令 tmp croc --relay 113.109.242.138:9009 4248-never-jazz-japan connecting...2023/05/09 15:09:34 could not connect to 113.109.242.138:9009: initial bytes are not magic: 48545450 ➜ tmp croc --relay 113.109.242.138:9009 4248-never-jazz-japan Accept \u0026#39;Mazu_shark.log.2023050815\u0026#39; (29.8 MB)? (Y/n) Y Receiving (\u0026lt;-14.22.11.164:23787) Mazu_shark.log.2023050815 100% |████████████████████| (30/30 MB, 23 MB/s) 安全发送消息 ➜ tmp croc --relay \u0026#34;113.109.242.138:9009\u0026#34; send --text \u0026#34;你在哪里？\u0026#34; Sending \u0026#39;text\u0026#39; (15 B) Code is: 5801-thermos-index-polo On the other computer run croc --relay 113.109.242.138:9009 5801-thermos-index-polo 安全接受消息 [root@VM-100-15-centos ~/shell]# croc --relay 113.109.242.138:9009 5801-thermos-index-polo Display text message (15 B)? (Y/n) Y Receiving (\u0026lt;-183.60.88.11:49740) 你在哪里？ ","permalink":"https://www.becool.vip/posts/tech/croc_sendfile/","summary":"\u003ch1 id=\"安利一个最近喜欢的文件传输工具\"\u003e安利一个最近喜欢的文件传输工具\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/schollz/croc\"\u003ehttps://github.com/schollz/croc\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://schollz.com/tinker/croc6/\"\u003ehttps://schollz.com/tinker/croc6/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e安全高效 目前我个人一直在使用\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"所有电脑安装croc\"\u003e所有电脑安装croc\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://github.com/schollz/croc\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl https://getcroc.schollz.com | bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://github.com/schollz/croc/releases\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"建立自己的文件互传中继\"\u003e建立自己的文件互传中继\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e挑选1台由公网IP的服务器做中继 记录自己的ip\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enohup croc relay \u0026gt;croc_relay.log 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eubuntu@VM-0-7-ubuntu:~/shell$ cat croc_relay.log\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enohup: ignoring input\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting croc relay version v9.6.4-1fce28e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting TCP server on :9009\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting TCP server on :9010\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting TCP server on :9011\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting TCP server on :9012\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[info]\t2023/05/09 14:59:07 starting TCP server on :9013\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"如果没有公网ip也可以用下面我自己的服务器体验\"\u003e如果没有公网ip也可以用下面我自己的服务器体验\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttp://bereal.becool.vip:6431/gethomeip\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"发送文件\"\u003e发送文件\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e切换到文件所在目录\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM-100-15-centos ~/shell]# croc --relay \u0026#34;113.109.242.138:9009\u0026#34; send Mazu_shark.log.2023050815\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSending \u0026#39;Mazu_shark.log.2023050815\u0026#39; (29.8 MB)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCode is: 4248-never-jazz-japan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOn the other computer run\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecroc --relay 113.109.242.138:9009 4248-never-jazz-japan\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"接收文件\"\u003e接收文件\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e在另外一台机器 执行上面的命令\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etmp croc --relay 113.109.242.138:9009 4248-never-jazz-japan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econnecting...2023/05/09 15:09:34 could not connect to 113.109.242.138:9009: initial bytes are not magic: 48545450\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e➜  tmp croc --relay 113.109.242.138:9009 4248-never-jazz-japan\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccept \u0026#39;Mazu_shark.log.2023050815\u0026#39; (29.8 MB)? (Y/n) Y\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving (\u0026lt;-14.22.11.164:23787)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMazu_shark.log.2023050815 100% |████████████████████| (30/30 MB, 23 MB/s)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"安全发送消息\"\u003e安全发送消息\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e➜  tmp croc --relay \u0026#34;113.109.242.138:9009\u0026#34; send --text \u0026#34;你在哪里？\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSending \u0026#39;text\u0026#39; (15 B)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCode is: 5801-thermos-index-polo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOn the other computer run\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecroc --relay 113.109.242.138:9009 5801-thermos-index-polo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"安全接受消息\"\u003e安全接受消息\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM-100-15-centos ~/shell]# croc --relay 113.109.242.138:9009 5801-thermos-index-polo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDisplay text message (15 B)? (Y/n) Y\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving (\u0026lt;-183.60.88.11:49740)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e你在哪里？\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"任意2台电脑之间快速安全的传输文件"},{"content":"观影时间 2022-12-06 13:16 使者 张骞\n超级越野专家，出走13年，两次被抓获，两次出逃成功，描画西域地图，带回未知植物种子. 古代条件那么差，还能完成如此之壮举，佩服佩服。 从前车马慢，但人心坚毅\n。\n坚毅，正是当代人所缺，而今到处充满着浮躁，无法坚定、长期做一件事情，反观自己亦是如此。 思考下，给自己一个长期目标(当前几个月小目标，顺利完成百日跑)，往自己收获一份坚毅。 通道 The Silk Road 汉武帝刘彻穷其一生，终于打通丝绸之路这条贯通欧亚大陆的贸易通道 霍去病 《出塞作》 唐.王维 居延城外猎天骄，白草连天野火烧。 暮云空碛时驱马，秋日平原好射雕。 护羌校尉朝乘障，破虏将军夜渡辽。 玉靶角弓珠勒马，汉家将赐霍嫖姚 匈奴未灭，何以家为？ 为征服匈奴而生的男人,16岁崭露头角，23岁与世长辞，骁勇善战，战法灵活，彻底征服了匈奴，打通了河西走廊。 光环四射的英雄人物，征服匈奴的战神。 驿站 悬泉置 置在汉代就是驿站的意思 五里设一邮、十里设一亭、三十里设一驿/传 驿用马 传用车 汉代驿站制度相当完善，丝绸之路在汉代被打通，贸易往来不断 解忧公主 汉代公主 与 乌孙国通婚的，为两国外交做了很多贡献，最终终于长安 乌孙国 从匈奴拆分出来 在当今巴尔喀什湖东南、伊犁河流域附近立国 常惠 汉代外交家 多次出使西域 与解忧公主自小相识 汉长城与屯田戍边 打败了匈奴，就开始在边疆建筑长城，建设天然的屏障抵御外敌 历史上第一次正式的屯田戍边，平时种地耕田日常生后，战时则切换为士兵，上阵杀敌 当今的新疆兵团亦是如此 根脉 儒家文化 汉代算是打败了匈奴，统一了版图，大致的轮廓已经跟当今相似了。 汉代也算是时间较长的一个朝代了，其后就陷入了四分五裂的局势，也多次被匈奴或其它族占领过 战乱使得很多学者迁徙到了河西走廊，儒家文化在河西走廊上面发光发热，正是因为相对远离战争，很多书籍得以出版和保存，为中国文化的传承起到了重要作用。 造像 河西走廊上面保存有很多宏观的石窟 禅修讲究清净，上山建石窟，佛像在当时非常流行 丝路 裴矩 隋朝人士 经历了北齐、北周、隋、唐4个朝代 为丝绸之路重新绽放做出了巨大贡献 隋炀帝 其罪也彰，其功也卓，弊在当代，利在千秋 打通运河，重开丝路，再次统一祖国 焉支山世博会\u0026mdash;可能是世界上第一个世博会 敦煌 唐朝时期的中国是古代最强大的朝代 敦煌一半以上的石窟都是在唐朝修建的 唐朝艺术有创新 有发展 梦回大唐 凉州 葡萄美酒夜光杯，欲饮琵琶马上催。醉卧沙场君莫笑，古来征战几人回？ 唐代边塞诗人王翰的《凉州词》 甘肃省武威市下辖的一个区了 边关要塞 会盟 与西藏高僧在河西走廊会盟，签订协议，元朝将西藏正式纳入中国版图 元朝是蒙古族统治的 元朝佛教也成为了第一宗教 苍生 西方以前叫中国为契丹 明朝基本上关闭了丝绸之路，想要贸易只能纳贡于明政府。 林则徐将统一西域的理想托付于左宗棠 左宗棠在清朝率领湘军收复新疆 天下第一雄关\u0026mdash;-嘉峪关 宝藏 甘肃金川\u0026mdash;中国镍都 ，世界第2大硫化镍存储量。\n甘肃玉门\u0026mdash;中国第1个油田\n甘肃嘉峪关\u0026mdash;铁矿资源丰富\n甘肃白银\u0026mdash;\n资源丰富，境内发现矿产45种，金属矿藏有铜、铅、锌、金、银等30多种。煤炭储量16亿吨，凹凸棒资源初步探明储量占世界总量的70%。累计堆存各种剥离矿石、废料4.2亿吨，含有可回收金属元素18种。\n铜城(现已无铜可挖) 中国地图 再看丝绸之路 指从长安或洛阳[2][3]出发，经甘肃、新疆，到中亚、西亚、欧洲，并联结地中海各国的陆上通道\n河西走廊在历史上才是真正的交通枢纽啊。\nsilk road wiki\n","permalink":"https://www.becool.vip/posts/read/%E6%B2%B3%E8%A5%BF%E8%B5%B0%E5%BB%8A/","summary":"\u003ch1 id=\"观影时间\"\u003e观影时间\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e2022-12-06 13:16\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"使者\"\u003e使者\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e张骞\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e超级越野专家，出走13年，两次被抓获，两次出逃成功，描画西域地图，带回未知植物种子.\u003c/li\u003e\n\u003cli\u003e古代条件那么差，还能完成如此之壮举，佩服佩服。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e从前车马慢，但人心坚毅\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e坚毅，正是当代人所缺，而今到处充满着浮躁，无法坚定、长期做一件事情，反观自己亦是如此。\u003c/li\u003e\n\u003cli\u003e思考下，给自己一个长期目标(当前几个月小目标，顺利完成百日跑)，往自己收获一份坚毅。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"通道\"\u003e通道\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003eThe Silk Road\n\u003cul\u003e\n\u003cli\u003e汉武帝刘彻穷其一生，终于打通丝绸之路这条贯通欧亚大陆的贸易通道\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e霍去病\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e《出塞作》 唐.王维\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e居延城外猎天骄，白草连天野火烧。\u003c/li\u003e\n\u003cli\u003e暮云空碛时驱马，秋日平原好射雕。\u003c/li\u003e\n\u003cli\u003e护羌校尉朝乘障，破虏将军夜渡辽。\u003c/li\u003e\n\u003cli\u003e玉靶角弓珠勒马，汉家将赐霍嫖姚\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e匈奴未灭，何以家为？\u003c/li\u003e\n\u003cli\u003e为征服匈奴而生的男人,16岁崭露头角，23岁与世长辞，骁勇善战，战法灵活，彻底征服了匈奴，打通了河西走廊。\u003c/li\u003e\n\u003cli\u003e光环四射的英雄人物，征服匈奴的战神。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"驿站\"\u003e驿站\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e悬泉置\n\u003cul\u003e\n\u003cli\u003e置在汉代就是驿站的意思\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e五里设一邮、十里设一亭、三十里设一驿/传\n\u003cul\u003e\n\u003cli\u003e驿用马 传用车\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e汉代驿站制度相当完善，丝绸之路在汉代被打通，贸易往来不断\u003c/li\u003e\n\u003cli\u003e解忧公主\n\u003cul\u003e\n\u003cli\u003e汉代公主 与 乌孙国通婚的，为两国外交做了很多贡献，最终终于长安\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e乌孙国\n\u003cul\u003e\n\u003cli\u003e从匈奴拆分出来 在当今\u003ca href=\"https://baike.baidu.com/item/%E5%B7%B4%E5%B0%94%E5%96%80%E4%BB%80%E6%B9%96/652775?fromModule=lemma_inlink\"\u003e巴尔喀什湖\u003c/a\u003e东南、\u003ca href=\"https://baike.baidu.com/item/%E4%BC%8A%E7%8A%81%E6%B2%B3%E6%B5%81%E5%9F%9F/6808723?fromModule=lemma_inlink\"\u003e伊犁河流域\u003c/a\u003e附近立国\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e常惠 汉代外交家\n\u003cul\u003e\n\u003cli\u003e多次出使西域 与解忧公主自小相识\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e汉长城与屯田戍边\n\u003cul\u003e\n\u003cli\u003e打败了匈奴，就开始在边疆建筑长城，建设天然的屏障抵御外敌\u003c/li\u003e\n\u003cli\u003e历史上第一次正式的屯田戍边，平时种地耕田日常生后，战时则切换为士兵，上阵杀敌\n\u003cul\u003e\n\u003cli\u003e当今的新疆兵团亦是如此\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"根脉\"\u003e根脉\u003c/h1\u003e\n\u003ch2 id=\"儒家文化\"\u003e儒家文化\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e汉代算是打败了匈奴，统一了版图，大致的轮廓已经跟当今相似了。\u003c/li\u003e\n\u003cli\u003e汉代也算是时间较长的一个朝代了，其后就陷入了四分五裂的局势，也多次被匈奴或其它族占领过\u003c/li\u003e\n\u003cli\u003e战乱使得很多学者迁徙到了河西走廊，儒家文化在河西走廊上面发光发热，正是因为相对远离战争，很多书籍得以出版和保存，为中国文化的传承起到了重要作用。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"造像\"\u003e造像\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e河西走廊上面保存有很多宏观的石窟\u003c/li\u003e\n\u003cli\u003e禅修讲究清净，上山建石窟，佛像在当时非常流行\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"丝路\"\u003e丝路\u003c/h2\u003e\n\u003ch3 id=\"裴矩\"\u003e裴矩\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e隋朝人士 经历了北齐、北周、隋、唐4个朝代\u003c/li\u003e\n\u003cli\u003e为丝绸之路重新绽放做出了巨大贡献\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"隋炀帝\"\u003e隋炀帝\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e其罪也彰，其功也卓，弊在当代，利在千秋\u003c/li\u003e\n\u003cli\u003e打通运河，重开丝路，再次统一祖国\u003c/li\u003e\n\u003cli\u003e焉支山世博会\u0026mdash;可能是世界上第一个世博会\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"敦煌\"\u003e敦煌\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e唐朝时期的中国是古代最强大的朝代\u003c/li\u003e\n\u003cli\u003e敦煌一半以上的石窟都是在唐朝修建的\u003c/li\u003e\n\u003cli\u003e唐朝艺术有创新 有发展\u003c/li\u003e\n\u003cli\u003e梦回大唐\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"凉州\"\u003e凉州\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e葡萄美酒夜光杯，欲饮琵琶马上催。醉卧沙场君莫笑，古来征战几人回？ 唐代边塞诗人王翰的《凉州词》\u003c/li\u003e\n\u003cli\u003e甘肃省武威市下辖的一个区了 边关要塞\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"会盟\"\u003e会盟\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e与西藏高僧在河西走廊会盟，签订协议，元朝将西藏正式纳入中国版图\u003c/li\u003e\n\u003cli\u003e元朝是蒙古族统治的\u003c/li\u003e\n\u003cli\u003e元朝佛教也成为了第一宗教\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"苍生\"\u003e苍生\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e西方以前叫中国为契丹\u003c/li\u003e\n\u003cli\u003e明朝基本上关闭了丝绸之路，想要贸易只能纳贡于明政府。\u003c/li\u003e\n\u003cli\u003e林则徐将统一西域的理想托付于左宗棠\u003c/li\u003e\n\u003cli\u003e左宗棠在清朝率领湘军收复新疆\u003c/li\u003e\n\u003cli\u003e天下第一雄关\u0026mdash;-嘉峪关\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"宝藏\"\u003e宝藏\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e甘肃金川\u0026mdash;中国镍都 ，世界第2大硫化镍存储量。\u003c/p\u003e","title":"河西走廊"},{"content":" 👉友链格式 名称： 疯子爱淡定的博客 网址： http://www.becool.vip http://heketong.github.io/ 图标： http://www.becool.vip/img/heketong_profile_photo.JPG 描述： 一个记录技术、阅读、生活的博客 👉友链申请要求 秉承互换友链原则、文章定期更新、不能有太多广告、个人描述字数控制在15字内\n","permalink":"https://www.becool.vip/links/","summary":"\u003cdiv class=\"friend\"\u003e\n\u003cdiv style=\"font-size: 20px;\" class=\"youlian\"\u003e👉友链格式\u003c/div\u003e\n\u003cdiv style=\"font-size: 16px;\"\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e\u003c/th\u003e\n          \u003cth\u003e\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e名称：\u003c/td\u003e\n          \u003ctd\u003e疯子爱淡定的博客\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e网址：\u003c/td\u003e\n          \u003ctd\u003e\u003ca href=\"http://www.becool.vip\"\u003ehttp://www.becool.vip\u003c/a\u003e   \u003ca href=\"http://heketong.github.io/\"\u003ehttp://heketong.github.io/\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e图标：\u003c/td\u003e\n          \u003ctd\u003e\u003ca href=\"http://www.becool.vip/img/heketong_profile_photo.JPG\"\u003ehttp://www.becool.vip/img/heketong_profile_photo.JPG\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e描述：\u003c/td\u003e\n          \u003ctd\u003e一个记录技术、阅读、生活的博客\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\u003cbr/\u003e\n\u003cdiv style=\"font-size: 20px;\"\u003e👉友链申请要求\u003c/div\u003e\n\u003cblockquote\u003e\n\u003cp\u003e秉承互换友链原则、文章定期更新\u003c!-- 、网站在工信部备案 --\u003e、不能有太多广告、个人描述字数控制在15字内\u003c/p\u003e\u003c/blockquote\u003e\n\u003cbr/\u003e","title":"🤝友链"},{"content":"100-Marathons 1 广州花都半马赛事 2019.2.24 ​\t【广州花都摇滚马组委会】祝贺贺 克通 完成2019广州花都摇滚马拉松半程马拉松项目，参赛号：C11949，枪声成绩：02:17:13，净成绩：02:13:56。此成绩仅供参考，最终成绩以组委会发布成绩证书上为准\n2 清远半马赛事 2019 3.17 ​\t【中体体育】祝贺贺克通完成2019时代中国清远马拉松赛 ，项目：半程马拉松，参赛号：C0085 ，枪声成绩：02:13:22 ，净成绩：02:05:10。此成绩仅供参考，解释权归组委会。\n3 韶关全马赛事 2019.11.24 ​\t第一次全马，竟然没收到短信，😓\n4 阳江海陵岛全马赛事 2019.12.29 ​\t【露营之家】恭喜您完成了十八子作·2019阳江海陵岛环岛国际马拉松赛“全程马拉松”，参赛号A0791，枪声成绩05:03:26，净成绩05:03:07。最终解释权归组委会所有。\n疫情影响取消清远全马赛事 2020.3.22 ​\t【清远马拉松】尊敬的贺克通，恭喜您中签2020时代中国清远马拉松，中签结果同步更新在微信公众号第一赛道，请及时关注。感谢您对清马的关注和支持，谢谢！2020.1.13 15:30，2020年太多事情发生，疫情影响，清远赛事取消，昨天看到消息，说癌症患者贺明发现自己病情后，想跑满100场马拉松，看后非常感动，是啊，每个不曾起舞的日子，都是对生命的辜负，癌症患者尚坚持跑了61场马拉松，自己又有什么不能的呢，当然这件事情也让我觉得更要珍惜自己的生命，不要等到事情真到了无可挽回的时候再决定找寻自己的健康，而应该在现在，此时此刻开始，运动起来，享受自己的人生。\n​\t戒烟这个事情反反复复说了太多遍，到现在还是没能如愿以偿，那就从此时此刻开始继续加油吧\n","permalink":"https://www.becool.vip/posts/life/marathons/","summary":"\u003ch1 id=\"100-marathons\"\u003e100-Marathons\u003c/h1\u003e\n\u003ch2 id=\"1-广州花都半马赛事-2019224\"\u003e1 广州花都半马赛事 2019.2.24\u003c/h2\u003e\n\u003cp\u003e​\t【广州花都摇滚马组委会】祝贺贺 克通 完成2019广州花都摇滚马拉松半程马拉松项目，参赛号：C11949，枪声成绩：02:17:13，净成绩：02:13:56。此成绩仅供参考，最终成绩以组委会发布成绩证书上为准\u003c/p\u003e\n\u003cimg src=\"/img/20190224_huadu_guangzhou_marathons.JPG\" alt=\"20190224_huadu_guangzhou_marathons\" style=\"zoom:30%;\" /\u003e\n\u003ch2 id=\"2-清远半马赛事-2019-317\"\u003e2 清远半马赛事 2019 3.17\u003c/h2\u003e\n\u003cp\u003e​\t【中体体育】祝贺贺克通完成2019时代中国清远马拉松赛 ，项目：半程马拉松，参赛号：C0085 ，枪声成绩：02:13:22 ，净成绩：02:05:10。此成绩仅供参考，解释权归组委会。\u003c/p\u003e","title":"100场马拉松"},{"content":"时间复杂度 常数时间操作O(1) 操作时间是固定的次数，跟数据量本身没有关系，就是其时间复杂度是O(1),比如总是比较1次或者3次，比如总是累加3次，总是赋值3次等等，无论样本数据量是多少，总是如此，我们就说起操作时间是常数时间，也就是O(1)\n实际评估过程中一般是循环多多少次，比较了多少次等等计算出一个表达式，然后对于表达式只要高阶项，不要低阶项，也不要高阶项的系数，剩下的部分 如果记为f(N)，那么时间复杂度为O(f(N)). N为样本量\n一个简单的例子：\n比如计算出的表达式为f(N)=2$N^2$+3N+3 这里面的高阶项就是$N^2$，对应高阶项的系数就是2 低阶项就是3N和3 最后f(N)=$N^2$\n算法流程的好坏，先看时间复杂度的指标，然后再分 析不同数据样本下的实际运行时间，也就是常数项时间\n例子 一个有序数组A，另一个无序数组B，请打印B中的所有不在A中的数，A数 组长度为N，B数组长度为M。\n算法流程1:对于数组B中的每一个数，都在A中通过遍历的方式找一下;\n算法流程2:对于数组B中的每一个数，都在A中通过二分的方式找一下;\n算法流程3:先把数组B排序，然后用类似外排的方式打印所有在A中出现 的数;\n代码实现及算法时间复杂度分析 三种算法代码实现 二分有递归和非递归 排序用的是归并递归实现 ```c++ // 一个**有序**数组A，另一个**无序**数组B，请打印B中的所有不在A中的数，A数 组长度为N，B数组长度为M。 // 算法流程1:对于数组B中的每一个数，都在A中通过遍历的方式找一下; // 算法流程2:对于数组B中的每一个数，都在A中通过二分的方式找一下; // 算法流程3:先把数组B排序，然后用类似外排的方式打印所有在A中出现 的数; #include using namespace std; //打印数组 void printArray(int a[],int la){ cout\u003c\u003c\"[\"; for(int i=0;i","permalink":"https://www.becool.vip/posts/tech/%E5%B8%B8%E7%94%A8%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","summary":"\u003ch1 id=\"时间复杂度\"\u003e时间复杂度\u003c/h1\u003e\n\u003ch2 id=\"常数时间操作o1\"\u003e常数时间操作O(1)\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e操作时间是固定的次数，跟数据量本身没有关系，就是其时间复杂度是O(1),比如总是比较1次或者3次，比如总是累加3次，总是赋值3次等等，无论样本数据量是多少，总是如此，我们就说起操作时间是常数时间，也就是O(1)\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e实际评估过程中一般是循环多多少次，比较了多少次等等计算出一个表达式，然后对于表达式只要高阶项，不要低阶项，也不要高阶项的系数，剩下的部分 如果记为f(N)，那么时间复杂度为O(f(N)). N为样本量\u003c/p\u003e","title":"常用数据结构"},{"content":"设计模式介绍 设计模式最关键的作用是为了可复用，减少开发工作量。\n模式就是针对现实世界重复出现的问题，给出核心解决方案，忽略掉一些不重要的细节。\n分解和抽象是2种解决现实问题的通用方法。\n底层思维是向下的，多数是理解计算机的，抽象是向上思维，多数是理解现实世界的。\n在现实工程开发中，要寻找需求频繁变化点，应用对应的设计模式，从而提高代码复用性，降低开发成本，测试成本。\n设计模式的应用不宜先入为主，最好是Refactoring to Patterns 也就是针对现实的痛点(变化点)，重构到设计模式\n重构的关键技巧 静态\u0026mdash;-动态 早绑定\u0026mdash;-晚绑定 继承\u0026mdash;\u0026ndash;组合 编译时依赖\u0026mdash;-运行时依赖 紧耦合\u0026mdash;-松耦合 面向对象设计原则 重新认识面向对象 理解隔离变化\n宏观层面来看 面向对象的构建方式能够适应软件的变化，能将变化带来的影响减为最小\n各司其职\n微观层面来说，面向对象更强调独立的各个类\n需求变化带来的新增类 不应该改变原来类的功能，也就是每个类各司其职\n对象到底是什么\n从语言的角度来说，对象封装了代码和数据\n从规格层面来说，对象是一些列可以使用的公共接口\n从概念层面来说，对象是拥有某种责任的抽象\n1 依赖倒置原则(DIP) 高层模块(往往是稳定的)不应该依赖底层模块(往往是不停的变化的)，二者都应该依赖于抽象。\n抽象(稳定)不应该依赖于实现细节(变化)，实现细节应该依赖于抽象(稳定)\n2 开放封闭原则(OCP) 对于扩展是开放的，对于修改是封闭的 类模块应该是是可以扩展的，但是不能修改。 在设计之初应该考虑其扩展性，不应该因为新增一个功能，反而要更改原先的功能，应该让新增功能为扩展，不应该修改原先的代码。\n3 单一职责原则(SRP) 一个类应该仅有一个引起其变化的原因 其变化的原因 往往就是其该承担的责任。 4 Liskov替换原则(LSP) 子类必须能够替换它的父类(IS-A) 继承表达类型抽象 5 接口隔离原则(ISP) 不应该强迫客户程序依赖他们不使用的方法 接口应该小而完备 6 优先使用对象组合 而不是类继承 类继承通常是白箱复用 对象组合通常为黑箱复用 继承在某种程度上破坏了封装性，子类父类耦合度较高 对象组合只要求被组合的对象有良好的接口定义，耦合度低 7 封装变化点 使用封装来创建对象之间的分界层，让设计者可以在分界层一侧修改，不影响另外一侧，实现分层间的松耦合。\n封装其实是封装变化点。\n8 针对接口编程，不要针对实现编程 不将变量声明为特定的类，而是声明为接口。 客户程序不需要知道对象具体类型，只需要知道其开放的接口 减少各部分依赖关系，实现高内聚 松耦合 产业强盛 最好是接口相当标准化。 模版方法Template method\u0026mdash;组件协作 需求背景： 需要实现多个app，都需要5个操作步骤(5个步骤操作顺序也都是固定的),有3个已经有框架或者library库实现了，实现这个app\n传统结构化思想实现 #include\u0026lt;iostream\u0026gt; using namespace std; class Library{//这里往往是库函数或者别人的实现 实现了3个方法 public: bool Step1(){ //... cout\u0026lt;\u0026lt;\u0026#34;Library::Step1()\u0026#34;\u0026lt;\u0026lt;endl; return true; } void Step3(){ cout\u0026lt;\u0026lt;\u0026#34;Library::Step3()\u0026#34;\u0026lt;\u0026lt;endl; //... } void Step5(){ cout\u0026lt;\u0026lt;\u0026#34;Library::Step5()\u0026#34;\u0026lt;\u0026lt;endl; //... } }; class App{//应用程序 实现Step2 Step4方法 这2个方法经常变化 不同的应用不一样的实现 public: void Step2(){ cout\u0026lt;\u0026lt;\u0026#34;App::Step2()\u0026#34;\u0026lt;\u0026lt;endl; //... } void Step4(){ cout\u0026lt;\u0026lt;\u0026#34;App::Step4()\u0026#34;\u0026lt;\u0026lt;endl; } }; int main(){//如果要实现另外一个程序 则需要重新再写一个main 重新写一个App类 实现Step2和Step4方法 App app; Library lib; //以下为程序实现逻辑 也就是算法框架结构 if(lib.Step1()){ app.Step2(); } lib.Step3(); for (int i=1;i\u0026lt;4;++i) { app.Step4(); } lib.Step5(); } //如果34--41的算法步骤整体是稳定的 只有Step2和Step4是经常变化的 可以考虑用TempalteMethod设计模式来实现 动机 某项任务 常常有稳定的整体操作结构，但是各个子步骤需要经常变化，或者由于固有的原因(框架和应用的关系)，无法和任务的整体结构同时实现 这就要思考如何确定稳定的操作结构的前提下，来灵活应对各个子步骤的变化或者晚期实现需求 模式定义 定义一个操作中的算法骨架(往往是稳定的)，将一些步骤(往往是变化了，前期确定不下来)延迟到子类中实现，Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(Override重写)该算法的某些特定子步骤。 要点总结 tempalte method是一种非常基础性的设计模式 在面向对象系统中有大量的应用，机制非常简洁(虚函数的多态)，为很多应用程序的框架提供了灵活的扩展点(继承+虚函数多态度)，是代码复用的基本实现结构 不要调用我，让我来调用你 具体实现方面，被template method调用的虚方法可以有实现也可以没有实现，一般将被调用的方法设置为protectrd。因为这些被template method调用的虚方法离开了主流程往往没有意义，也不应该设置为public暴漏给外部。 几张图解 模版方法代码实现 #include\u0026lt;iostream\u0026gt; using namespace std; class Library{ public: void run(){//template method //这里的run方法也就是所说的相对稳定的整体处理结构 Step2和Step4会根据不同的应用实现为不同的逻辑，所以声明为纯虚函数 if(Step1()){ Step2(); } Step3(); for (int i=1;i\u0026lt;4;++i) { Step4(); } Step5(); } virtual ~Library(){} protected: bool Step1(){ //... cout\u0026lt;\u0026lt;\u0026#34;Library::Step1()\u0026#34;\u0026lt;\u0026lt;endl; return true; } void Step3(){ cout\u0026lt;\u0026lt;\u0026#34;Library::Step3()\u0026#34;\u0026lt;\u0026lt;endl; //... } void Step5(){ cout\u0026lt;\u0026lt;\u0026#34;Library::Step5()\u0026#34;\u0026lt;\u0026lt;endl; //... } virtual void Step2()=0;//这里声明为纯虚函数 让子类去实现 virtual void Step4()=0; }; class App :public Library{//继承框架类 实现容易变化的2个函数 protected: void Step2(){ cout\u0026lt;\u0026lt;\u0026#34;App::Step2()\u0026#34;\u0026lt;\u0026lt;endl; //... } void Step4(){ cout\u0026lt;\u0026lt;\u0026#34;App::Step4()\u0026#34;\u0026lt;\u0026lt;endl; } }; int main(){ Library* pLib=new App(); pLib-\u0026gt;run(); delete pLib; return 0; } 策略模式 strategy\u0026mdash;组件协作 需求背景 需要实现一个类，计算各个国家的税，每个国家的计税算法不一样，后期可能修改或者增加一些国家的计税算法\n传统实现 //传统解决方案 traditional solution enum TaxBase { CN_Tax, US_Tax, DE_Tax, FR_Tax //如果要增加支持法国税法就要修改 }; class SalesOrder{ TaxBase tax; public: double CalculateTax(){ //传统解决方案 直接ifelse 分而治之 需要经常修改这个函数 很可能出现引入新的需求 导致原先的实现方案引入bug if (tax == CN_Tax){ //CN*********** } else if (tax == US_Tax){ //US*********** } else if (tax == DE_Tax){ //DE*********** } else if (tax == FR_Tax){ //如果要增加支持法国税法就要修改 这个地方违背了开闭原则(用扩展的方式支持修改 而不是直接修改) //经验告诉我们，直接修改的方式 也比较容易引入bug } } }; 动机 如果现实软件开发过程中，某一个对象使用了多种算法，而且这些算法可能经常改变，或者经常需要新增类似算法(比如计算不同国家的税)，这个时候如果将这些算法全部编码到对象中，这个对象就会变的非常复杂，容易出错，策略模式可以解决问题 更直观点 如果使用了大量if else或者switch case，而且不稳定，经常变化，就可以考虑用strategy模式改造 要考虑使用多态机制在运行时动态的使用相应的算法解决问题 模式的定义 定义一系列算法(动机章节提到的经常变化的算法)，把它们一个个封装起来(不同的子类)，并且使它们可互相替换(变化)\u0026ndash;(使用多态运行时决定使用哪种算法)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展，子类化)。——《设计模式》GoF 要点总结 Strategy及其子类为组件提供了一系列可重用的算法，从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换(多态)。\nStrategy模式提供了用条件判断语句以外的另一种选择，消除条件判断语句，就是在解耦合。含有许多条件判断语句的代码通常都需 要Strategy模式。\n如果Strategy对象没有实例变量，那么各个上下文可以共享同一个 Strategy对象，从而节省对象开销\n图解 策略模式代码实现 //策略模式 class Context{}; class TaxStrategy{//策略类基类 public: virtual double Calculate(const Context\u0026amp; context)=0;//纯虚函数 不同的算法抽象 运行时调用相应的算法 virtual ~TaxStrategy(){} }; class StrategyFactory{//工厂类 根据现实情况生成不同的子类，解决想要的问题 public: TaxStrategy* NewStrategy(){ return nullptr;} }; class CNTax : public TaxStrategy{//子类 继承策略基类 实现虚方法 用于多态 public: virtual double Calculate(const Context\u0026amp; context){ //*********** } }; class USTax : public TaxStrategy{ public: virtual double Calculate(const Context\u0026amp; context){ //*********** } }; class DETax : public TaxStrategy{ public: virtual double Calculate(const Context\u0026amp; context){ //*********** } }; //这里如果新增一个法国的，就是扩展的方式支持 class FRTax : public TaxStrategy{ public: virtual double Calculate(const Context\u0026amp; context){ //......... } }; class SalesOrder{ private: TaxStrategy* strategy; public: SalesOrder(StrategyFactory* strategyFactory){ this-\u0026gt;strategy = strategyFactory-\u0026gt;NewStrategy(); } ~SalesOrder(){ delete this-\u0026gt;strategy; } double CalculateTax(){ //... Context context; double val = strategy-\u0026gt;Calculate(context); //多态调用 //... } }; 观察者模式Observer\u0026mdash;组件协作 需求背景 需要实现一个文件分割业务，同时需要显示进度条，也就是文件分割时候达到一定状态需要通知某个类，后续很有可能还有通知多个类\n传统实现 //traditional solution传统解决方案 //分割大文件，显示或者打印处理进度 #include\u0026lt;string\u0026gt; #include\u0026lt;iostream\u0026gt; #include \u0026#34;common.h\u0026#34; using namespace std; class FileSplitter//该类是被依赖的对象，状态发生变化需要通知ProgressBar { string m_filePath; int m_fileNumber; ProgressBar* m_progressBar;//该类负责收到通知更新进度条 //如果要新增其它观察者，这里需要新增具体的实现类指针 ProgressBar1* 比较麻烦，这个业务类也要频繁改变 public: FileSplitter(const string\u0026amp; filePath, int fileNumber, ProgressBar* progressBar) : m_filePath(filePath), m_fileNumber(fileNumber), m_progressBar(progressBar){ } void split(){ //1.读取大文件 //2.分批次向小文件中写入 for (int i = 0; i \u0026lt; m_fileNumber; i++){ //... float progressValue = m_fileNumber; progressValue = (i + 1) / progressValue; m_progressBar-\u0026gt;setValue(progressValue); //从编译层面如果ProgressBar发生变化 也就是观察者发生变化，FileSplitter类也需要变化 重新编译 //从业务发展来看，如果需要新增观察者也是比较麻烦的 //这个违背了依赖倒置原则，高层模块不应该依赖此层模块变化，FileSplitter不应该依赖ProgressBar //个人觉得也违背了开闭原则，要用扩展的方式支持新增，不应该直接修改 } } }; //traditional solution传统解决方案 //分割大文件，显示或者打印处理进度 #include \u0026#34;common.h\u0026#34; class MainForm : public Form { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar; public: void Button1_Click(){ string filePath = txtFilePath-\u0026gt;getText(); int number = atoi(txtFileNumber-\u0026gt;getText().c_str()); FileSplitter splitter(filePath, number, progressBar); //从业务发展来看，如果需要新增观察者也是比较麻烦的 需要修改FileSplitter 构造方法，传入新增的观察者类指针 //也需要修改FileSplitter通知逻辑 增加对新增观察者的处理 splitter.split(); } }; 动机 我们要为某些对象之间建立\u0026quot;通知依赖关系\u0026quot;，也就是一个对象状态发生变更，需要自动通知其它依赖与它的对象。如果这种依赖关系过于紧密，将使软件不能很好的抵御变化。\n可以使用面向对象技术，将这种依赖关系弱化，形成一种较为稳定的依赖关系，从而实现松耦合。\n大致实现，被依赖的对象需要包含一个集合(链表或者数组都可以),集合保存的是抽象接口基类(观察者的基类指针)，当自己状态变化的时候遍历集合，调用抽象接口基类的通知函数(多态实现运行时调用多个观察者的相应处理函数)。\n模式的定义 定义对象见的一对多(变化也就是观察者)的依赖关系，以便当被依赖对象(Subject)的状态发生变化时，所有依赖与它的对象(观察者)都能得到通知并且自动更新 结构图解 要点总结 使用Observer观察者模式可以独立的改变目标和观察者，实现松耦合 目标发送通知的时候，无需指定具体的观察者，通知(也可以写态通知信息作为参数)会自动传播 观察者自己决定是否需要订阅通知，如果需要请调用Subject的添加观察者API，目标对象对此可以是透明的 当听到状态变更需要通知多个对象的场景就往往可以考虑Observer观察者模式了 Observer模式是基于事件的UI框架中非常常用的模式，也是MVC模式的一个重要组成部分 观察者模式代码实现 #include\u0026lt;string\u0026gt; #include\u0026lt;iostream\u0026gt; #include \u0026lt;list\u0026gt; using namespace std; class IProgress{//观察者抽象基类 public: virtual void DoProgress(float value)=0;//观察者需要实现的函数(收到通知后被调用) virtual ~IProgress(){} }; class FileSplitter { string m_filePath; int m_fileNumber; list\u0026lt;IProgress*\u0026gt; m_iprogressList; // 抽象通知机制，支持多个观察者 //这种机制就不再依赖具体的观察者 保证FileSplitter类处理逻辑相对稳定 public: FileSplitter(const string\u0026amp; filePath, int fileNumber) : m_filePath(filePath), m_fileNumber(fileNumber){ } void split(){ //1.读取大文件 //2.分批次向小文件中写入 for (int i = 0; i \u0026lt; m_fileNumber; i++){ //... float progressValue = m_fileNumber; progressValue = (i + 1) / progressValue; onProgress(progressValue);//发送通知 } } void addIProgress(IProgress* iprogress){//增加观察者API m_iprogressList.push_back(iprogress); } void removeIProgress(IProgress* iprogress){//移除观察者API m_iprogressList.remove(iprogress); } protected: virtual void onProgress(float value){//通知函数 list\u0026lt;IProgress*\u0026gt;::iterator itor=m_iprogressList.begin(); while (itor != m_iprogressList.end() ){ (*itor)-\u0026gt;DoProgress(value); //更新进度条 虚函数多态调用多个观察者的处理函数 itor++; } } }; #include \u0026#34;common.h\u0026#34; #include\u0026lt;string\u0026gt; using namespace std; class MainForm : public Form, public IProgress { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar; public: void Button1_Click(){ string filePath = txtFilePath-\u0026gt;getText(); int number = atoi(txtFileNumber-\u0026gt;getText().c_str()); ConsoleNotifier cn; FileSplitter splitter(filePath, number); splitter.addIProgress(this); //订阅通知 splitter.addIProgress(\u0026amp;cn)； //订阅通知 splitter.split(); splitter.removeIProgress(this); } virtual void DoProgress(float value){ progressBar-\u0026gt;setValue(value); } }; class ConsoleNotifier : public IProgress { public: virtual void DoProgress(float value){ cout \u0026lt;\u0026lt; \u0026#34;.\u0026#34;; } }; 装饰模式Decorator\u0026mdash;单一职责 需求背景 需要实现文件流操作简易系统，支持的操作大概就多些 read seek write，存在普通文件流，网络流，内存流等等\n随着系统的发展，需要增加各种流的加密功能\n随着系统的发展，需要增加各种流的缓存功能\n随着系统的发展，需要增加各种流的加密同时缓存功能\n传统实现 //业务操作 class Stream{//流操作基类，定义相关接口虚函数 public: virtual char Read(int number)=0; virtual void Seek(int position)=0; virtual void Write(char data)=0; virtual ~Stream(){} }; //文件流操作主体类 应该继承Stream基类 class FileStream: public Stream{ public: virtual char Read(int number){ //读文件流 } virtual void Seek(int position){ //定位文件流 } virtual void Write(char data){ //写文件流 } }; //网络流操作类 应该继承Stream基类 class NetworkStream :public Stream{ public: virtual char Read(int number){ //读网络流 } virtual void Seek(int position){ //定位网络流 } virtual void Write(char data){ //写网络流 } }; //内存流操作类 应该继承Stream基类 class MemoryStream :public Stream{ public: virtual char Read(int number){ //读内存流 } virtual void Seek(int position){ //定位内存流 } virtual void Write(char data){ //写内存流 } }; //扩展加密的功能 传统方案新增多个类 分别继承文件流、网络流、内存流操作类 大量子类生成 class CryptoFileStream :public FileStream{ public: virtual char Read(int number){ //额外的加密操作... 跟网络流、内存流代码存在大量重复 FileStream::Read(number);//读文件流 } virtual void Seek(int position){ //额外的加密操作... 跟网络流、内存流代码存在大量重复 FileStream::Seek(position);//定位文件流 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... FileStream::Write(data);//写文件流 //额外的加密操作... } }; //扩展加密的功能 传统方案新增多个类 分别继承文件流、网络流、内存流操作类 class CryptoNetworkStream :public NetworkStream{ public: virtual char Read(int number){ //额外的加密操作... NetworkStream::Read(number);//读网络流 } virtual void Seek(int position){ //额外的加密操作... NetworkStream::Seek(position);//定位网络流 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... NetworkStream::Write(data);//写网络流 //额外的加密操作... } }; //扩展加密的功能 传统方案新增多个类 分别继承文件流、网络流、内存流操作类 class CryptoMemoryStream : public MemoryStream{ public: virtual char Read(int number){ //额外的加密操作... MemoryStream::Read(number);//读内存流 } virtual void Seek(int position){ //额外的加密操作... MemoryStream::Seek(position);//定位内存流 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... MemoryStream::Write(data);//写内存流 //额外的加密操作... } }; //扩展缓存的功能 传统方案新增多个类 分别继承文件流、网络流、内存流操作类 class BufferedFileStream : public FileStream{ //额外的缓存操作... 跟网络流、内存流代码存在大量重复 }; class BufferedNetworkStream : public NetworkStream{ //... }; class BufferedMemoryStream : public MemoryStream{ //... } //扩展加密+缓存组合的的功能 传统方案新增多个类 分别继承文件流、网络流、内存流操作类 class CryptoBufferedFileStream :public FileStream{ public: virtual char Read(int number){ //代码大量重复 生成大量子类 扩展一个功能非常不灵活，增加维护成本 FileStream::Read(number);//读文件流 } virtual void Seek(int position){ //额外的加密操作... //额外的缓冲操作... FileStream::Seek(position);//定位文件流 //额外的加密操作... //额外的缓冲操作... } virtual void Write(byte data){ //额外的加密操作... //额外的缓冲操作... FileStream::Write(data);//写文件流 //额外的加密操作... //额外的缓冲操作... } }; void Process(){ //编译时装配 也就是在编译的时候已经决定了要使用哪种扩展功能对应的子类 CryptoFileStream *fs1 = new CryptoFileStream(); BufferedFileStream *fs2 = new BufferedFileStream(); CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); } 动机 滥用继承来扩展对象的功能，由于继承为类型引入的静态特质，导致这种扩展方式变得非常不灵活，而且如果扩展的功能很多或者需要组合，将会产生大量的子类，对项目的维护成本大幅度增加。 模式的定义 不使用继承，改用组合的方式动态的给对象增加一些扩展功能。对于新增扩展功能而言，Decorator装饰模式比滥用继承的方式更加灵活(因为可以消除大量的重复代码同时减少子类的数量，减少维护的成本) 结构图解 要点总结 采用组合而不是继承的方式，Decorator装饰模式实现了在运行时动态扩展对象功能的能力，而且可以根据需要扩展多个功能，避免了大量的重复代码，同时大量的子类产生的问题 装饰类在接口上变现为is-a关系，也就是继承业务操作组件基类，但在实现上又是has-a组合的关系，成员拥有业务操作组件基类指针，在运行时动态的传入不同类别的业务操作子类，来实现相应操作，同时扩展功能 Decorator装饰模式的目的并不是解决“多子类衍生的多继承问题”，重要是在解决“主体类在多个方向上的扩展问题” 简易代码 //业务操作 class Stream{//流操作基类，定义相关接口虚函数 public： virtual char Read(int number)=0; virtual void Seek(int position)=0; virtual void Write(char data)=0; virtual ~Stream(){} }; //文件流操作主体类 应该继承Stream基类 class FileStream: public Stream{ public: virtual char Read(int number){ //读文件流 } virtual void Seek(int position){ //定位文件流 } virtual void Write(char data){ //写文件流 } }; //网络流操作类 应该继承Stream基类 class NetworkStream :public Stream{ public: virtual char Read(int number){ //读网络流 } virtual void Seek(int position){ //定位网络流 } virtual void Write(char data){ //写网络流 } }; //内存流操作类 应该继承Stream基类 class MemoryStream :public Stream{ public: virtual char Read(int number){ //读内存流 } virtual void Seek(int position){ //定位内存流 } virtual void Write(char data){ //写内存流 } }; //扩展功能装饰基类 DecoratorStream: public Stream{//继承业务组件操作基类 主要是用于实现相关接口 protected: Stream* stream;//保存业务组件操作基类指针，运行时动态的传入相应的业务操作主体子类实现相关操作并扩展功能 DecoratorStream(Stream * stm):stream(stm){ } }; //加密扩展功能 class CryptoStream: public DecoratorStream {//继承装饰基类 public: CryptoStream(Stream* stm):DecoratorStream(stm){ } virtual char Read(int number){ //额外的加密操作... stream-\u0026gt;Read(number);//这里会多态的调用不同的流操作(文件、网络、内存) 没有重复代码 } virtual void Seek(int position){ //额外的加密操作... stream::Seek(position);//这里会多态的调用不同的流操作(文件、网络、内存)没有重复代码 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... stream::Write(data);//这里会多态的调用不同的流操作(文件、网络、内存)没有重复代码 //额外的加密操作... } }; //不同流操作增加缓存功能 跟增加加密功能一样 只生成了一个类 同时在运行时多态的调用不同的流操作(文件、网络、内存)没有重复代码 class BufferedStream : public DecoratorStream{ Stream* stream;//... public: BufferedStream(Stream* stm):DecoratorStream(stm){ } //... }; void Process(){ //运行时装配 FileStream* s1=new FileStream(); //这里s1可以动态的传入文件流、网络流、内存流，来实现不同类别流操作的加密扩展 CryptoStream* s2=new CryptoStream(s1); //这里s1可以动态的传入文件流、网络流、内存流，来实现不同类别流操作的缓存扩展 BufferedStream* s3=new BufferedStream(s1); //这里s2可以动态的传入文件流、网络流、内存流，来实现不同类别流操作的加密缓存扩展 BufferedStream* s4=new BufferedStream(s2); } 桥模式Bridge\u0026mdash;单一职责 需求背景 需要实现多平台(pc、mobile、pad，tv)等等，这个是一个纬度，同时也要发布一个经典轻量版本和完美复杂版本(比如发文字消息的时候也播放声音)，这又是一个纬度的变化 下面看看经典的实现\n传统实现 class Messager{ public: //下面是三个通用接口方法 virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0; //下面是涉及不同的平台需要实现不同的方法 PlaySound方法只有完美版用到 virtual void PlaySound()=0; virtual void DrawShape()=0; virtual void WriteText()=0; virtual void Connect()=0; virtual ~Messager(){} }; //PC平台实现 class PCMessagerBase : public Messager{ public: virtual void PlaySound(){ //********** } virtual void DrawShape(){ //********** } virtual void WriteText(){ //********** } virtual void Connect(){ //********** } }; //手机平台实现 class MobileMessagerBase : public Messager{ public: virtual void PlaySound(){ //========== } virtual void DrawShape(){ //========== } virtual void WriteText(){ //========== } virtual void Connect(){ //========== } }; //业务抽象 经典轻量发布版本 本身是业务抽象纬度，但因为另外一个纬度也要变化(平台纬度) 所以经典发布版本也要有多个平台类的实现 class PCMessagerLite : public PCMessagerBase { public: virtual void Login(string username, string password){ PCMessagerBase::Connect(); //........ } virtual void SendMessage(string message){ PCMessagerBase::WriteText(); //........ } virtual void SendPicture(Image image){ PCMessagerBase::DrawShape(); //........ } }; //业务抽象 完美复杂版本 本身是业务抽象纬度，但因为另外一个纬度也要变化(平台纬度) 所以完美复杂版本也要有多个平台类的实现 class PCMessagerPerfect : public PCMessagerBase { public: virtual void Login(string username, string password){ PCMessagerBase::PlaySound(); //******** PCMessagerBase::Connect(); //........ } virtual void SendMessage(string message){ PCMessagerBase::PlaySound(); //******** PCMessagerBase::WriteText(); //........ } virtual void SendPicture(Image image){ PCMessagerBase::PlaySound(); //******** PCMessagerBase::DrawShape(); //........ } }; // class MobileMessagerLite : public MobileMessagerBase { public: virtual void Login(string username, string password){ MobileMessagerBase::Connect(); //........ } virtual void SendMessage(string message){ MobileMessagerBase::WriteText(); //........ } virtual void SendPicture(Image image){ MobileMessagerBase::DrawShape(); //........ } }; //业务抽象 经典轻量发布版本 本身是业务抽象纬度，但因为另外一个纬度也要变化(平台纬度) 所以经典发布版本也要有多个平台类的实现 //这里是手机平台实现 跟pc平台存在大量类似重复代码 class MobileMessagerPerfect : public MobileMessagerBase { public: virtual void Login(string username, string password){ MobileMessagerBase::PlaySound(); //******** MobileMessagerBase::Connect(); //........ } virtual void SendMessage(string message){ MobileMessagerBase::PlaySound(); //******** MobileMessagerBase::WriteText(); //........ } virtual void SendPicture(Image image){ MobileMessagerBase::PlaySound(); //******** MobileMessagerBase::DrawShape(); //........ } }; //实际调用 void Process(){ //编译时装配 需要指定不同平台的不用业务抽象 比如手机平台经典版本 手机平台完美版本等等 //所以这里如有一个纬度变化--业务抽象有M个(经典版本、完美版本...),另外一个纬度变化平台实现有N个(pc、mobile、pad、tv...) //那么类至少需要1(messager)+N(平台实现基类)+M*N(最终用于用于发布的类 比如下面的手机平台完美版本类) Messager *m =new MobileMessagerPerfect(); } 动机 某些类型的固有实现逻辑，使得它们有2个纬度的变化，或者多个纬度的变化\n所以要利用面向对象技术使得类型可以轻松的沿着2个或者多个方向变化\n假设需要实现一个发消息的业务，大致有三个方法login登录，sendMessage发文字消息 sendPicture发图片\n模式定义 将抽象部分(业务功能)与实现部分(平台实现)分离，使它们 都可以独立地变化 结构图解 要点总结 Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系，使得抽象和实现可以沿着各自的维度来变化。所谓 抽象和实现沿着各自纬度的变化，即“子类化”它们。\nBridge模式有时候类似于多继承方案，但是多继承方案往往违背 单一职责原则(即一个类只有一个变化的原因)，复用性比较差。 Bridge模式是比多继承方案更好的解决方法。\nBridge模式的应用一般在“两个非常强的变化维度”，有时一个 类也有多于两个的变化维度，这时可以使用Bridge的扩展模式\n代码实现 //业务抽象基类 定义相关接口方法 class Messager{ protected: MessagerImp* messagerImp;//用于平台实现基类指针 在平台切换方面可以运行时灵活变化 public: virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0; virtual ~Messager(){} }; //平台实现基类 定义相关接口方法 class MessagerImp{ public: virtual void PlaySound()=0; virtual void DrawShape()=0; virtual void WriteText()=0; virtual void Connect()=0; virtual MessagerImp(){} }; //不同平台实现子类 这里是pc class PCMessagerImp : public MessagerImp{ public: virtual void PlaySound(){ //********** } virtual void DrawShape(){ //********** } virtual void WriteText(){ //********** } virtual void Connect(){ //********** } }; //不同平台实现子类 这里是mobile class MobileMessagerImp : public MessagerImp{ public: virtual void PlaySound(){ //========== } virtual void DrawShape(){ //========== } virtual void WriteText(){ //========== } virtual void Connect(){ //========== } }; //业务抽象子类实现 这里是经典版本 class MessagerLite :public Messager { public: virtual void Login(string username, string password){ messagerImp-\u0026gt;Connect();//通过基类拥有的平台实现基类指针 在不同平台间灵活切换 //........ } virtual void SendMessage(string message){ messagerImp-\u0026gt;WriteText(); //........ } virtual void SendPicture(Image image){ messagerImp-\u0026gt;DrawShape(); //........ } }; //业务抽象子类实现 这里是完美版本 class MessagerPerfect :public Messager { public: virtual void Login(string username, string password){ messagerImp-\u0026gt;PlaySound();//通过基类拥有的平台实现基类指针 在不同平台间灵活切换 //******** messagerImp-\u0026gt;Connect(); //........ } virtual void SendMessage(string message){ messagerImp-\u0026gt;PlaySound(); //******** messagerImp-\u0026gt;WriteText(); //........ } virtual void SendPicture(Image image){ messagerImp-\u0026gt;PlaySound(); //******** messagerImp-\u0026gt;DrawShape(); //........ } }; void Process(){ //运行时装配 类的数量大幅减少 但是实现并没有打折扣，这里在运行是初始化想要的平台实现子类就可以动态灵活变化 MessagerImp* mImp=new PCMessagerImp(); Messager *m =new MessagerPerfect(mImp);//传入pc平台子类 实现pc完美版本 } 工厂模式factory\u0026mdash;对象创建 需求背景 我们需要实现一个文件分割器，支持分割多种类型文件(文本文件、普通二进制文件、图片、视频等等)，所以我们就要根据需求创建对象\n传统实现 //文件分割基类 文件分割实现 class ISplitter{ public: virtual void split()=0; virtual ~ISplitter(){} }; //文件分割子类 二进制文件、文本文件、图片、视频 class BinarySplitter : public ISplitter{ }; class TxtSplitter: public ISplitter{ }; class PictureSplitter: public ISplitter{ }; class VideoSplitter: public ISplitter{ }; // --------------- //高层调用模块 class MainForm : public Form { public: void Button1_Click(){ ISplitter * splitter=new BinarySplitter(); //依赖具体类 违反了依赖倒置原则 高层稳定模块应该依赖抽象而不应该依赖具体的实现类 //这里的依赖为编译时依赖 具体的实现类可能反复变化就会导致高层模块也需要跟着编译甚至修改 splitter-\u0026gt;split(); } }; 动机 在软件系统中，经常面临着创建对象的工作;由于需求的变化， 需要创建的对象的具体类型经常变化 因为直接new会依赖具体的实现类 所以要想办法绕过new 运行时动态的new出相应的类 一般是接口抽象之后的第一步工作 模式定义 定义一个用于创建对象的接口，让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟(目的:解耦， 手段:虚函数)到子类 工厂模式代码实现 //业务抽象基类 与工厂抽象基类可以放在一起 class ISplitter{ public: virtual void split()=0; virtual ~ISplitter(){} }; //工厂抽象基类 class SplitterFactory{ public: virtual ISplitter* CreateSplitter()=0; virtual ~SplitterFactory(){} }; //------------------------------ //高层调用模块 class MainForm : public Form { SplitterFactory* factory;//工厂抽象基类 public: MainForm(SplitterFactory* factory){//通过构造方法 让更高层实际使用者 传入具体的工厂子类 this-\u0026gt;factory=factory; } void Button1_Click(){ ISplitter * splitter= factory-\u0026gt;CreateSplitter(); //多态new 通过具体的工厂子类实例化具体的业务子类 这里不再依赖具体的业务子类 依赖的是工厂基类 具体的工厂子类变化或者业务处理子类变化对这个类没有编译时的影响，相对稳定，变化少一般问题也少，有变化就带来了测试工作量 splitter-\u0026gt;split(); } }; //------------------------------------- //具体实现类和工厂子类放到同一个文件 class BinarySplitter : public ISplitter{ }; class TxtSplitter: public ISplitter{ }; class PictureSplitter: public ISplitter{ }; class VideoSplitter: public ISplitter{ }; //具体工厂子类 负责创建具体的业务处理子类 class BinarySplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new BinarySplitter(); } }; class TxtSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new TxtSplitter(); } }; class PictureSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new PictureSplitter(); } }; class VideoSplitterFactory: public SplitterFactory{ public: virtual ISplitter* CreateSplitter(){ return new VideoSplitter(); } }; 结构图解 要点总结 Factory Method模式用于隔离类对象的使用者和具体类型之间的 耦合关系。面对一个经常变化的具体类型，紧耦合关系(new)会导 致软件的脆弱。\nFactory Method模式通过面向对象的手法，将所要创建的具体对 象工作延迟到子类，从而实现一种扩展(而非更改)的策略，较好 地解决了这种紧耦合关系，只需要增加相应的业务子类和对应的工厂子类 使用者MainFrom无需更改\nFactory Method模式解决“单个对象”的需求变化。缺点在于要 求创建方法/参数相同。\n抽象工厂AbstractFactory\u0026mdash;对象创建 需求背景 需要写一个Dao数据访问层，刚开始只有oracle，后面要支持mysql，sqlserver db2等等,每个数据库系列都有相关的多个对象(connection、command、dataReader\u0026hellip;)，这些对象时相关的，有依赖的，不能传一个oracle的connection给mysql的command\n传统解决方案 class EmployeeDAO{ public: vector\u0026lt;EmployeeDO\u0026gt; GetEmployees(){ //刚开始只有一种数据库 后面可能要新增其它数据库 所以这里可能会写成if else来创建对象 给个入参来判断不同的类型 //这就是依赖了底层具体的对象 违反了依赖倒置原则DIP 应该依赖抽象 SqlConnection* connection = new SqlConnection(); connection-\u0026gt;ConnectionString = \u0026#34;...\u0026#34;; SqlCommand* command = new SqlCommand(); command-\u0026gt;CommandText=\u0026#34;...\u0026#34;; command-\u0026gt;SetConnection(connection); SqlDataReader* reader = command-\u0026gt;ExecuteReader(); while (reader-\u0026gt;Read()){ } } }; 用原始工厂方法模式改造 //数据库访问有关的基类 class IDBConnection{ }; class IDBConnectionFactory{ public: virtual IDBConnection* CreateDBConnection()=0; }; class IDBCommand{ }; class IDBCommandFactory{ public: virtual IDBCommand* CreateDBCommand()=0; }; class IDataReader{ }; class IDataReaderFactory{ public: virtual IDataReader* CreateDataReader()=0; }; //支持SQL Server class SqlConnection: public IDBConnection{ }; class SqlConnectionFactory:public IDBConnectionFactory{ }; class SqlCommand: public IDBCommand{ }; class SqlCommandFactory:public IDBCommandFactory{ }; class SqlDataReader: public IDataReader{ }; class SqlDataReaderFactory:public IDataReaderFactory{ }; //支持Oracle class OracleConnection: public IDBConnection{ }; class OracleCommand: public IDBCommand{ }; class OracleDataReader: public IDataReader{ }; //如果系列对象(有相关性的多个对象)有3个 使用者就拥有3个对应的工厂基类对象指针 class EmployeeDAO{ IDBConnectionFactory* dbConnectionFactory; IDBCommandFactory* dbCommandFactory; IDataReaderFactory* dataReaderFactory; public: vector\u0026lt;EmployeeDO\u0026gt; GetEmployees(){ IDBConnection* connection = dbConnectionFactory-\u0026gt;CreateDBConnection(); connection-\u0026gt;ConnectionString(\u0026#34;...\u0026#34;); IDBCommand* command = dbCommandFactory-\u0026gt;CreateDBCommand(); command-\u0026gt;CommandText(\u0026#34;...\u0026#34;); command-\u0026gt;SetConnection(connection); //关联性 //这里没法保证传给oracle的command的connection是mysql类型的工厂初始化的 这个时候就会出现错误 IDBDataReader* reader = command-\u0026gt;ExecuteReader(); //关联性 while (reader-\u0026gt;Read()){ } } }; 因为系列对象彼此间有相关性，会产生依赖，所以直接多个工厂基类的方法，无法建立其相关性，拿这里的例子来说，法保证传给oracle的command的connection是mysql类型的工厂初始化的 这个时候就会出现错误。\n动机 在软件系统中，经常面临着“一系列相互依赖的对象”的创建工 作;同时，由于需求的变化，往往存在更多系列对象的创建工作 模式定义 供一个接口，让该接口负责创建一系列“相关或者相互依 赖的对象”，无需指定它们具体的类 结构图解 要点总结 如果没有应对“多系列对象构建”的需求变化，则没有必要使用 Abstract Factory模式，这时候使用简单的工厂完全可以。\n“系列对象”指的是在某一特定系列下的对象之间有相互依赖、 或作用的关系。不同系列的对象之间不能相互依赖。\nAbstract Factory模式主要在于应对“新系列”的需求变动。其缺 点在于难以应对“新对象”的需求变动\n代码实现 //数据库访问有关的基类 class IDBConnection{ }; class IDBCommand{ }; class IDataReader{ }; //支持SQL Server class SqlConnection: public IDBConnection{ }; class SqlCommand: public IDBCommand{ }; class SqlDataReader: public IDataReader{ }; class SqlDBFactory:public IDBFactory{ public: virtual IDBConnection* CreateDBConnection()=0; virtual IDBCommand* CreateDBCommand()=0; virtual IDataReader* CreateDataReader()=0; }; class IDBFactory{ public: virtual IDBConnection* CreateDBConnection()=0; virtual IDBCommand* CreateDBCommand()=0; virtual IDataReader* CreateDataReader()=0; }; //支持Oracle 类似操作 增加相关类即可 class OracleConnection: public IDBConnection{ }; class OracleCommand: public IDBCommand{ }; class OracleDataReader: public IDataReader{ }; class EmployeeDAO{ IDBFactory* dbFactory; public: vector\u0026lt;EmployeeDO\u0026gt; GetEmployees(){ IDBConnection* connection = dbFactory-\u0026gt;CreateDBConnection(); connection-\u0026gt;ConnectionString(\u0026#34;...\u0026#34;); IDBCommand* command = dbFactory-\u0026gt;CreateDBCommand(); command-\u0026gt;CommandText(\u0026#34;...\u0026#34;); command-\u0026gt;SetConnection(connection); //关联性解决了多对象之间的依赖关系 因为是同一个工厂创建的 IDBDataReader* reader = command-\u0026gt;ExecuteReader(); //关联性 while (reader-\u0026gt;Read()){ } } }; 原型模式 prototype\u0026mdash;对象创建 需求背景及动机 ​\t如果需要初始化一个比较复杂的对象，这个对象刚开始初始化的状态不是你想要的状态，这个时候用原始工厂模式初始化就比较尴尬了，或者更直白的说就是需要一个对象运行一段时间后，达到了某种状态后，我才要这个对象去做一些处理，就可以用原型模式，用拷贝对象的方式创建一个对象。\n​\t如果创建对象只是非常简单原始的new即可，那原始工厂模式足够了，如果在最开始很难创建出来或者你希望对象运行到一定状态后要保留这个状态就可以考虑原型模式\n模式定义 使用原型实例指定创建对象的种类，然后通过拷贝这些原型来创建新的对象，达到保存某种状态的目的。\n是专门用来初始化相对很复杂的对象的时候用的，是工厂模式的变形。\n现实项目用的不多\n结构图解 要点总结 同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系，同样要求这些“易变类”拥有“稳定的接口” 使用原型克隆的方法来实现创建一个包含有中间状态的对象 Clone方法要是深拷贝 c++可以用拷贝构造函数实现 代码实现 对比工厂模式看 //原型基类包含克隆具体子类的虚函数 也包括其它一些业务函数 class ISplitter{ public: virtual void split()=0;//实际业务虚函数 virtual ISplitter* clone()=0; //通过克隆自己来创建对象 virtual ~ISplitter(){} }; //具体的业务子类 继承原型基类 要实现clone自己的虚函数 class BinarySplitter : public ISplitter{ public: virtual ISplitter* clone(){ return new BinarySplitter(*this); } }; class TxtSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new TxtSplitter(*this); } }; class PictureSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new PictureSplitter(*this); } }; class VideoSplitter: public ISplitter{ public: virtual ISplitter* clone(){ return new VideoSplitter(*this); } }; //使用者----------------- class MainForm : public Form{ ISplitter* prototype;//原型基类指针 public: MainForm(ISplitter* prototype){ this-\u0026gt;prototype=prototype;//传入含有中间状态的子类对象(或者本身很难初始化完全) } void Button1_Click(){ ISplitter * splitter= prototype-\u0026gt;clone(); //克隆原型 深拷贝一个已经运行了一段时间的对象 保存其中间状态 splitter-\u0026gt;split(); } }; 构建器模式Builder\u0026mdash;对象创建 需求背景 //支持建不同房子 都需要几个共同的步骤，但是不同房子每个步骤实现不一样\n//我们可以将这些稳定的步骤抽象出来，下面的init函数就是这样\n传统实现 //支持建不同房子 都需要几个共同的步骤，但是不同房子每个步骤实现不一样 //我们可以将这些稳定的步骤抽象出来，下面的init函数就是这样 class House{ public: void init(){ this-\u0026gt;BuildPart1(); for (int i = 0; i \u0026lt; 4; i++){ this-\u0026gt;BuildPart2(); } bool flag=this-\u0026gt;BuildPart3(); if(flag){ this-\u0026gt;BuildPart4(); } this-\u0026gt;BuildPart5(); } virtual ~House(){} protected: virtual void BuildPart1()=0; virtual void BuildPart2()=0; virtual bool BuildPart3()=0; virtual void BuildPart4()=0; virtual void BuildPart5()=0; //.... }; // 建造石头房子 override5个建造房子的步骤 class StoneHouse: public House{ protected: virtual void BuildPart1(){} virtual void BuildPart2(){} virtual bool BuildPart3(){} virtual void BuildPart4(){} virtual void BuildPart5(){} }; int main(){ House *p=new StoneHouse(); p-\u0026gt;init(); //p的其它操作 } 传统解决方案，将构建房子的方法都写到房子类本身中，如果构建房子的方法经常改变，那么我们就应该将构建部分拆分出去，否则修改原先的代码很有可能导致房子本身除了构建的其它逻辑出现bug。\n有时候构建一个对象需要的传入大量的参数，然后又要执行很复杂的多个方法调用，而且还经常变化，这个时候应该改考虑将构建对象的工作单独拆分出去。\n动机 软件系统中，有时候面临着“一个复杂对象”的创建工作，其 通常由各个部分的子对象用一定的算法构成;由于需求的变化，这 个复杂对象的各个部分经常面临着剧烈的变化，但是将它们组合在 一起的算法却相对稳定\n比如盖房子的大致流程固定，但是盖不同的房子每个子流程的实现各不相同，还可能经常变化。\n模式定义 将一个复杂对象的构建与其表示相分离，使得同样的构建过 程(稳定)可以创建不同的表示(变化)\n结构图解 代码实现 //产品基类 定义房子除了构建本身的其它接口 class House{ //.... }; //产品构建器基类 负责定义相关共有子接口 不实现 class HouseBuilder { public: House* GetResult(){ return pHouse; } virtual ~HouseBuilder(){} protected: House* pHouse; virtual void BuildPart1()=0; virtual void BuildPart2()=0; virtual void BuildPart3()=0; virtual void BuildPart4()=0; virtual void BuildPart5()=0; }; //石头房子 继承房子，实现除了构建工作外的的其它方法 class StoneHouse: public House{ }; //石头房子构建器 实现构建房子对象需要的几个共有的抽象接口，届时会组合这些接口生成石头房子 class StoneHouseBuilder: public HouseBuilder{ protected: virtual void BuildPart1(){ //pHouse-\u0026gt;Part1 = ...; } virtual void BuildPart2(){ } virtual void BuildPart3(){ } virtual void BuildPart4(){ } virtual void BuildPart5(){ } }; //监工 含有产品builder基类指针 传入不同产品的builder子类，就会构建出来不同的产品 //当然这里也要定义实现共用的构建算法(不同房子的建造流程是一直的 都要先打地基 然后....) //如果项目发展后期需要建造一个更新奇的房子 不需要先打地基，而是先筑地梁 那就搞一个新的监工实现不同的构建算法 class HouseDirector{ public: HouseBuilder* pHouseBuilder;//基类builder指针 HouseDirector(HouseBuilder* pHouseBuilder){ this-\u0026gt;pHouseBuilder=pHouseBuilder; } //统一的构建方法 House* Construct(){ pHouseBuilder-\u0026gt;BuildPart1(); for (int i = 0; i \u0026lt; 4; i++){ pHouseBuilder-\u0026gt;BuildPart2(); } bool flag=pHouseBuilder-\u0026gt;BuildPart3(); if(flag){ pHouseBuilder-\u0026gt;BuildPart4(); } pHouseBuilder-\u0026gt;BuildPart5(); return pHouseBuilder-\u0026gt;GetResult(); } }; 要点总结 builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中 “分步骤”是一个稳定的算法，而复杂对象的各个部分则经常变化 变化点在哪里，封装哪里—— Builder模式主要在于应对“复杂对 象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建 算法”的需求变动 builder模式跟模版方法有点类似，但builder模式往往只是为了解决构建对象困难而产生的，而且builder模式有一个监工，模版方法的监工其实是父类。 单例模式singleton\u0026mdash;性能问题 动机 实际开发项目中中就是需要一些这样的类，保证它们在系统中只有一个实例 ，才能保证逻辑正确性，或者大部分时候也是为了提高性能，因为频繁的产生多个实例，会带来性能下降，内存消耗等等\n代码实现 class Singleton { private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static Singleton m_singleton; public: static Singleton* getInstance(){ return \u0026amp;m_singleton; } }; //这种模式绝对是ok的，只是有些人会说有一定的内存浪费，因为即使后面不使用也会生成相应对象 //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 Singleton Singleton::m_singleton; int main(){ Singleton *a=Singleton::getInstance(); Singleton *b=Singleton::getInstance(); Singleton *c=Singleton::getInstance(); Singleton *d=Singleton::getInstance(); } class Singleton { public: static Singleton* getInstance(){ //如果线程1 执行到下面一行(还没有new出来) cpu让给线程2，线程2也到这行 最后2个线程都会new出来 if ( m_singleton==nullptr){ //线程不安全 可能会出现new出多个的情况 return new Singleton(); } return m_singleton; } private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static Singleton *m_singleton; }; //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 Singleton* Singleton::m_singleton=nullptr; } class Singleton { public: static Singleton* getInstance(){ m_lock.lock(); //还没有初始化的这个地方是要加锁的 但是如果已经new出来了 多个线程每次调用都要加锁就会影响性能 if ( m_singleton==nullptr){ m_singleton= new Singleton(); } m_lock.unlock(); return m_singleton; } private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static Singleton *m_singleton; static mutex m_lock; }; //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 Singleton* Singleton::m_singleton=nullptr; mutex Singleton::m_lock; class Singleton { public: static Singleton* getInstance(){ if ( m_singleton==nullptr){//如果已经初始化 则直接返回即可 m_lock.lock(); //还没有初始化的这个地方是要加锁的 //再次检查是否new过了 下面判空逻辑不能去掉 去掉了还是会new出多个实例 因为如果2个线程同时进入上面的盼空后 就会出现 if ( m_singleton==nullptr){ m_singleton= new Singleton(); // 对象的new不是原子操作 1、分配内存，2 调用构造，3 赋值操作，到第3步的时候才是m_singleton非空 //1、分配内存，2 赋值操作 3 调用构造，到第2步的时候才是m_singleton非空 //如果顺序是先2在3 2做完后 另外一个线程就判断非空 直接使用但还没有调用构造函数这个时候就会让另外线程出错 } m_lock.unlock(); } return m_singleton; } private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static Singleton *m_singleton; static mutex m_lock; }; //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 Singleton* Singleton::m_singleton=nullptr; mutex Singleton::m_lock; //多线程安全版本 双检查为空并加锁 利用atomic库消除内存读写reorder问题 相对实现复杂 可以使用 class Singleton { public: static Singleton* getInstance(){ Singleton* tmp = m_singleton.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence if ( tmp==nullptr){//如果已经初始化 则直接返回即可 std::lock_guard\u0026lt;std::mutex\u0026gt; lock(m_lock);; //还没有初始化的这个地方是要加锁的 if ( tmp==nullptr){//再次检查是否new过了 这行不能去掉 去掉了还是会new出多个实例 因为如果2个线程同时进入67行代码 就会出现 tmp = new Singleton(); // 1、分配内存，2 调用构造，3 赋值操作 这里不会再出现内存读写reorder问题 std::atomic_thread_fence(std::memory_order_release);//释放内存fence m_singleton.store(tmp, std::memory_order_relaxed); } } return tmp; } private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static std::atomic\u0026lt;Singleton*\u0026gt; m_singleton; static mutex m_lock; }; //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 std::atomic\u0026lt;Singleton*\u0026gt; Singleton::m_singleton; mutex Singleton::m_lock; 通过返回局部静态变量方式实现 线程安全 写法简单 推荐使用\nclass Singleton { public: static Singleton* getInstance(){ static Singleton instance; //这种相对于使用atomic的写法更简洁 而且又是懒汉模式 使用的时候才生成 推荐使用 //局部静态变量初始化是会加锁的 也就是线程安全的,而且是c++和编译器保证的 推荐使用 return \u0026amp;instance; } private: Singleton() {cout \u0026lt;\u0026lt; \u0026#34;Singleton construct\\n\u0026#34;;} Singleton(const Singleton\u0026amp; s) = delete; // 禁用拷贝构造函数 Singleton\u0026amp; operator=(const Singleton\u0026amp; s) = delete; // 禁用拷贝赋值操作符 static Singleton* m_singleton; }; //类的静态变量在类外部初始化 而且是存放在静态存储区 进入main函数之前就已经执行了 Singleton* Singleton::m_singleton=nullptr; 享元模式FlyWeight\u0026mdash;性能问题 动机 如果一切皆对象，有些对象在系统中大量存在(万、十万、百万、千万等)，这个时候就会带来大量的运行时开销，大部分情况下可能是内存浪费。比如设计围棋游戏，每一个黑旗、白棋都搞成对象，成千上万的人在玩，那就是十万甚至百万个对象，比如设计字体处理软件，每个有大量的字符，每个字符又有大量的字体，每个字符每个字体都生成1个对象，最终占用内存将是非常夸张的，而大部分时候是不需要，因为这些对象都是只读的，完全可以用池的概念来共享，如果已经创建过了，则直接使用 不再创建，否则就创建并且加到共享池中。\n模式定义 通过共享技术有效支持大量细粒度的对象。\n代码 class Font { private: //unique object key string key; //object state //.... public: Font(const string\u0026amp; key){ //... } }; //对象生成工厂 用map实现共享池 class FontFactory{ private: map\u0026lt;string,Font* \u0026gt; fontPool; public: Font* GetFont(const string\u0026amp; key){ map\u0026lt;string,Font*\u0026gt;::iterator item=fontPool.find(key); if(item!=footPool.end()){ return fontPool[key]; } else{ Font* font = new Font(key); fontPool[key]= font; return font; } } void clear(){ //... } }; 要点总结 享元模式主要是解决大量对象带来的内存消耗问题，如果评估不可能发生，也没有必要使用。\n门面模式 facade\u0026mdash;接口隔离 需求背景与动机 项目中用到了多种数据库，比如mysql、oracle、DB2、Timesten等等，如果多个程序直接与这些数据库打交道，每个程序都会非常复杂，二期如果数据库升级，数据库接口变动，相关的程序都需要改动，这个时候就需要一个数据访问层(提供固定的数据访问接口)，给外部提供统一的访问接口，将各个数据库的处理细节屏蔽，即使数据库升级改动，只修改接口实现，外部程序不用改动。\n模式定义 为系统中的一组接口提供一个一致稳定的界面，facade模式定义一个高层接口，使得系统外部使用者更加易用，稳定，不用频繁变化。\n要点总结 facade模式没有固定的代码模式，更多的是一种架构思维 facade模式内部必须是高相关的组件，不能什么都放 代理模式Proxy\u0026mdash;接口隔离 需求背景和动机 由于一些实际的现实原因，不能直接使用某个对象(比如使用之前要做一些安全控制，或者说直接创建某个对象的开销太大，又或者根本无法直接创建[访问一些分布式机器上的资源])，所以直接访问会给使用者或者系统架构的设计代码麻烦，所以就需要一种代理，就像现实世界的代理一样，不用管一些细节，只要它们提供相应的服务即可(软件层面可能就是提供一些可用的接口)。\n代理的操作往往跟直接操作对象一致，可以理解增加了一个间接层也就是代理，来实现控制这些对象。\n模式定义 为其它对象提供一种代理以控制(隔离，使用接口)对这个对象的访问。\n我的理解就是增加一个间接层 不让直接使用，这个有很多好处，跟门面模式一样，可以隔离很多变化。\n结构图解 代码 class ISubject{ public: virtual void process(); }; class RealSubject: public ISubject{ public: virtual void process(){ //.... } }; //客户端直接使用某个对象 如果现实中不能让直接使用 比如要增加安全控制 //合法的client才能使用就要考虑改造了 而且不能对每个client都改造 所以最后实现起可能比较复杂混乱 class ClientApp{ ISubject* subject; public: ClientApp(){ subject=new RealSubject(); } void DoTask(){ //... subject-\u0026gt;process(); //.... } }; class ISubject{ public: virtual void process(); }; class RealSubject: public ISubject{ public: virtual void process(){ //.... } }; //Proxy的设计 继承ISubject实现 一致性的接口 class SubjectProxy: public ISubject{ public: virtual void process(){ //对RealSubject的一种间接访问 比如调用前做安全控制 统计下调用次数，使用rpc访问别的机器等等 //.... } }; //客户端使用代理跟直接使用RealSubject没有太大区别 class ClientApp{ ISubject* subject; public: ClientApp(){ subject=new SubjectProxy(); } void DoTask(){ //... subject-\u0026gt;process(); //.... } }; 要点总结 “增加一个中间层”是软件系统对许多复杂问题的一种常见解决方案，如果直接使用某个对象会带来很多麻烦，就尝试使用代理模式 具体proxy设计模式的实现方法，千差万别。 适配器模式Adapter\u0026mdash;接口隔离 需求背景、动机 ​\t现实项目中由于应用环境的变化，常常需要将“一些现存的对象”放在新环境使用，但是新环境要求的接口又是一些老对象所不能直接满足的，这个时候就会想到适配器模式，简单来说就是定义一个适配器类继承新接口的基类，但是用对象组合的方式拥有老对象的基类或者子类，用老对象的某些方法实现新的接口。\n​\t现实生活中也有很多适配器，比如电源的适配器，HTMI转VGA等等，说白了就是把一个老的接口转成新的接口。\n模式定义 将一个类的接口转换成客户希望的另外一个新接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。\n结构图解 要点总结 Adapter模式主要应用于“希望复用一些现存的类”，但接口又与复用环境要求的不一致，在遗留代码复用，类库迁移等方面非常有用。 GoF23定义的适配器有2种，对象适配器(上图的这种)和类适配器(直接多继承老对象)，类适配器不推荐使用，因为使用了多继承，一般都是被摒弃的。对象组合的方式更符合松耦合精神 其实实际项目中完全也不用拘泥于书中定义的结构，即使把老对象作为新对象新接口的参数也是一样的。 代码 Adapter适配器模式 c++\n//目标接口（新接口） class ITarget{ public: virtual void process()=0; }; //遗留接口（老接口） class IAdaptee{ public: virtual void foo(int data)=0; virtual int bar()=0; }; //遗留类型 class OldClass: public IAdaptee{ //.... }; //对象适配器 class Adapter: public ITarget{ //继承新接口基类 protected: IAdaptee* pAdaptee;//组合方式拥有老对象 这样就可以通过加工老对象的方法实现新接口 public: Adapter(IAdaptee* pAdaptee){ this-\u0026gt;pAdaptee=pAdaptee; } virtual void process(){ int data=pAdaptee-\u0026gt;bar(); pAdaptee-\u0026gt;foo(data); } }; //类适配器 class Adapter: public ITarget, protected OldClass{ //多继承 不推荐使用 }; int main(){ IAdaptee* pAdaptee=new OldClass(); ITarget* pTarget=new Adapter(pAdaptee); pTarget-\u0026gt;process(); } //stl的栈和队列 大致也是通过对象组合的方式实现的 也可以理解为Adapter模式 class stack{ deqeue container; }; class queue{ deqeue container; }; 中介者Mediator\u0026mdash;接口隔离 需求背景和动机 如果现实项目中出现了多个对象互相关联交互的情况，它们彼此之间徐奥维持一种复杂的引用关系，A变了 BC也要变，C变了D也要变，D变了可能A又要跟着做相应变化，这个时候如果遇到需求变更，改动起来将会非常麻烦，而且容易出错。\n这个时候就可以考虑引入一个中介者，让它们彼此的引用拆开，变成都给中介者打交道，A变了如果要改变B或者C，就通知中介者，然后中介去通知BC做改变，当然要建立良好的通知机制和协议。\n如果中介者最后非常庞大或者复杂，也要考虑继续拆分中介者\n模式定义 用中介对象来封装(变化)一些列的对象交互，让原先各个对象不需要显示相互引用(编译时依赖\u0026mdash;\u0026gt;运行时依赖)，从而使其耦合松散，便于管理变化，而且可以独立的改变它们之间的交互。\n结构图解 要点总结 将多个对象复杂的关联关系解耦，Mediator模式将多个对象间的控制逻辑进行统一管理，将“多个对象互相关联”变成了\u0026quot;多个对象都和中介者关联“，简化了系统的维护，抵御了可能的变化。 如果Mediator变得太多复杂也要对其拆分处理 facade模式是解耦系统间(单向)的对象关联关系，但Mediator模式是解耦系统内各个对象之间的(双向)关联关系。 状态模式State\u0026mdash;状态变化 需求背景、动机 在软件构建过程中，某些对象的状态如果改变，其行为也会随之发生变化，比如文档处于只读状态，则支持行为和读写状态的行为可能就完全不同了\n如何在运行时根据对象的状态来透明的更改对象的行为？而且不会为对象操作和状态转化之间引入耦合？\n例子 假设有一个网络状态处理类，大致有3种操作，但是对于每种操作，不同的状态有不同的处理，而且处理后要切换到对应的状态\n//枚举值 定义网络的多种状态 未来可能会新增状态的处理 enum NetworkState { Network_Open, Network_Close, Network_Connect, }; //网络处理类 class NetworkProcessor{ NetworkState state; public: //针对操作1 不同的状态会发生不同的行为，同时操作后状态要切换 这里面后续可能还要增加状态 一来修改可能引入bug，二来修改起来往往比较困难，因为太多ifelse了容易晕 void Operation1(){ if (state == Network_Open){ //********** state = Network_Close; } else if (state == Network_Close){ //.......... state = Network_Connect; } else if (state == Network_Connect){ //$$$$$$$$$$ state = Network_Open; } } //针对操作2 不同的状态会发生不同的行为，同时操作后状态要切换 public void Operation2(){ if (state == Network_Open){ //********** state = Network_Connect; } else if (state == Network_Close){ //..... state = Network_Open; } else if (state == Network_Connect){ //$$$$$$$$$$ state = Network_Close; } } //针对操作3 不同的状态会发生不同的行为，同时操作后状态要切换 public void Operation3(){ //。。。。。 } }; 模式定义 允许一个对象在其内部状态改变的时候改变它的行为，从而使对象看起来似乎修改了其行为。 结构图解 要点总结 State模式核心是将所有与一个特定状态相关的行为都放入一个State的子类对象中，在对象状态切换的时候，其实是切换了不同的子类对象指向，但同时维持State的接口(都继承同一个基类)，这样就实现了具体的操作和状态转换之间的解耦。 为不同的状态引入不同的对象使得状态转换变得更加明确，而且可以保证不会出现状态不一致的情况，因为转换是源自行的，要么彻底转换过来，要么不转换。 如果State对象没有实例变量，可以用singetone模式共享同一个对象，从而节省对象开销 代码 //状态基类，将不同的状态改为不同的类(对象)，实现同样的接口 class NetworkState{ public: NetworkState* pNext; virtual void Operation1()=0; virtual void Operation2()=0; virtual void Operation3()=0; virtual ~NetworkState(){} }; //打开状态类 要实现不同的操作对应应该怎么处理 同时操作后应该切换到什么状态 class OpenState :public NetworkState{ static NetworkState* m_instance; public: static NetworkState* getInstance(){ if (m_instance == nullptr) { m_instance = new OpenState(); } return m_instance; } //这里将只关注打开状态在Operation1时如何处理，不容易混淆，同时切换状态其实是切换子类指针指向，是有原子性的 void Operation1(){ //********** pNext = CloseState::getInstance(); } void Operation2(){ //.......... pNext = ConnectState::getInstance(); } void Operation3(){ //$$$$$$$$$$ pNext = OpenState::getInstance(); } }; //关闭状态类 要实现不同的操作对应应该怎么处理 同时操作后应该切换到什么状态 class CloseState:public NetworkState{ }; //.. class NetworkProcessor{ NetworkState* pState; public: //状态类初始化后 针对不同的状态 操作接口是类似的，那就是执行想要的操作，切换到下一个状态， //代码保持稳定，即使新增状态，如果接口不改变 NetworkProcessor类代码无需改变 NetworkProcessor(NetworkState* pState){ this-\u0026gt;pState = pState; } void Operation1(){ //... pState-\u0026gt;Operation1(); pState = pState-\u0026gt;pNext; //... } void Operation2(){ //... pState-\u0026gt;Operation2(); pState = pState-\u0026gt;pNext; //... } void Operation3(){ //... pState-\u0026gt;Operation3(); pState = pState-\u0026gt;pNext; //... } }; 备忘录Memento\u0026mdash;-状态变化 需求背景、动机 现实软件系统有时候需要保存某个对象的状态，相当于给当下的对象来个快照保存起来，以便未来某个时间恢复这个状态做相应的处理，又不能破坏对象的封装性，不能暴漏公共接口给外部。 最开始的时候就想到了备忘录模式，所谓备忘录简单理解就是深拷贝一个对象，保存起来，未来某个时间再拿这个备忘录重建对象，实现某些特定的功能。 模式定义 不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态，以实现特定的功能。 结构图解 要点总结 备忘录Memento需要存储原发器Originator对象的内部状态，以备需要时恢复原发器Originator的状态 Memento的核心是信息隐藏 因为设计模式是94年提出了，现在这个模式有点过时，因为很多高级语言都提供了序列化、反序列化的技术，但是思想是一样的。 代码 //备忘录类 class Memento { string state; //.. 这里为了保存内部的状态 可能实现比较复杂 仅为示意代码 public: Memento(const string \u0026amp; s) : state(s) {} string getState() const { return state; } void setState(const string \u0026amp; s) { state = s; } }; //原发器类 class Originator { string state; //.... public: Originator() {} //创建备忘录 Memento createMomento() { Memento m(state); return m; } //用备忘录恢复原始状态 void setMomento(const Memento \u0026amp; m) { state = m.getState(); } }; int main() { Originator orginator; //..做相应处理 改变状态 //捕获对象状态，存储到备忘录 Memento mem = orginator.createMomento(); //... 改变orginator状态 //从备忘录中恢复 orginator.setMomento(mem); } 组合模式Composit\u0026mdash;数据结构 需求背景和动机 如何客户代码过多的依赖对象容器复杂的内部实现的数据结构，对象容器内部的实现结构(不是抽象接口)如果发生变化，那么客户代码也会频繁变化，就带来了代码的维护性，扩展性等弊端 所以需要将客户代码与复杂的对象容器结构解耦，让客户不管是处理什么样的结构都调用统一的接口简单处理，不会因为对象内部数据结构的变化而发生变化 假设有一个需求需要处理单个叶子节点，也要复杂节点(list集合 装有多个节点) 模式定义 ​\t将对象祖辈成属性结构以表示“部分-整体”的层次结构，Composite是的用户对单个对象还是组合对象的使用具有一致性(稳定)。\n其实就是单个对象 组合对象都继承同一个基类，实现同一个公共接口，这样用多态实现，客户端的调用就是一致的，不用频繁改变。 结构图解 代码 Composite组合模式c++\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;list\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; //组件基类 定义公用接口 这些接口往往是直接暴漏给客户的 class Component { public: virtual void process() = 0; virtual ~Component(){} }; //支持复杂结构的处理 继承组件基类 实现process公共外部接口 class Composite : public Component{ string name; list\u0026lt;Component*\u0026gt; elements; public: Composite(const string \u0026amp; s) : name(s) {} //因为内部是一个list集合，所以需要add remove方法 add的可以是简单节点 也可以还是一个list void add(Component* element) { elements.push_back(element); } void remove(Component* element){ elements.remove(element); } //实现公共外部接口 void process(){ //1. process current node //2. process leaf nodes 循环对list内部的每个节点都处理 for (auto \u0026amp;e : elements) e-\u0026gt;process(); //多态调用 } }; //叶子节点 简单节点 也实现process公共接口 class Leaf : public Component{ string name; public: Leaf(string s) : name(s) {} //实现公共process接口 void process(){ //process current node } }; // 客户端的调用 void Invoke(Component \u0026amp; c){ //... c.process();//这里客户端不用关心组件到底是简单节点还是集合复杂节点 用多态实现了统一的处理 //如果这里不用多态，客户就要区分到底是简单节点还是list集合，针对list集合要做想要的循环遍历处理，把对象的更多细节暴漏给客户 //同时如果list数据结构发生了变化 比如改为了set实现，那么客户端的代码也要跟着改变 这是非常糟糕的。 } int main() { Composite root(\u0026#34;root\u0026#34;); Composite treeNode1(\u0026#34;treeNode1\u0026#34;); Composite treeNode2(\u0026#34;treeNode2\u0026#34;); Composite treeNode3(\u0026#34;treeNode3\u0026#34;); Composite treeNode4(\u0026#34;treeNode4\u0026#34;); Leaf leat1(\u0026#34;left1\u0026#34;); Leaf leat2(\u0026#34;left2\u0026#34;); root.add(\u0026amp;treeNode1); treeNode1.add(\u0026amp;treeNode2); treeNode2.add(\u0026amp;leaf1); root.add(\u0026amp;treeNode3); treeNode3.add(\u0026amp;treeNode4); treeNode4.add(\u0026amp;leaf2); //客户端不论是处理复杂节点还是叶子节点 代码保持一直稳定 不用理会组件对象内部数据结构容器实现的变化 Invoke(root); Invoke(leaf2); Invoke(treeNode3); } 要点总结 Composite模式采用树形结构实现部片存在的对象容器，从而将一对多 变为“一对一”，其实也就是原先对多个对象，现在变为对一个基类，让原先的多个对象都继承同一个基类((拥有同样的公共接口))，这样客户就不用关心某个复杂对象内部的数据结构实现，也不会因为其数据结构的变动而导致客户端代码修改。 Composite模式在具体实现中，可以让负对象中的子对象反向追溯，如果负对象有频繁的遍历需求，可以使用缓存来改善效率 迭代器模式Iterator\u0026mdash;数据结构 需求背景和动机 软件构建过程中，集合对象内部结构常常变化各异，但对于这些集合对象，我们不希望暴漏其内部数据结构，而且希望提供一个一致的接口让客户访问，同时如果提供统一的接口，就有可能提供统一的算法 使用面向对象技术(也就是多态 有个迭代器基类，定义统一的几个接口，让不同的集合迭代器对象都继承这个基类，实现其方法即可) 模式定义 提供一种方法顺序访问一个聚合对象中的各个元素，而不暴漏(稳定)该对象内部的表示(实现细节 比如某些数据结构) 结构图解 代码 //迭代器基类 定义公共操作接口 对客户暴漏 template\u0026lt;typename T\u0026gt; class Iterator { public: virtual void first() = 0; virtual void next() = 0; virtual bool isDone() const = 0; virtual T\u0026amp; current() = 0; }; //生成某一个类型的迭代器 template\u0026lt;typename T\u0026gt; class MyCollection{ public: Iterator\u0026lt;T\u0026gt; GetIterator(){ //... } }; //具体的迭代其子类实现 继承迭代器基类 实现公共接口 template\u0026lt;typename T\u0026gt; class CollectionIterator : public Iterator\u0026lt;T\u0026gt;{ MyCollection\u0026lt;T\u0026gt; mc; public: CollectionIterator(const MyCollection\u0026lt;T\u0026gt; \u0026amp; c): mc(c){ } void first() override { } void next() override { } bool isDone() const override{ } T\u0026amp; current() override{ } }; //调用 void MyAlgorithm() { MyCollection\u0026lt;int\u0026gt; mc;//某一个集合 Iterator\u0026lt;int\u0026gt; iter= mc.GetIterator();//针对某一个集合创建迭代器对象 for (iter.first(); !iter.isDone(); iter.next()){//遍历 cout \u0026lt;\u0026lt; iter.current() \u0026lt;\u0026lt; endl; } //这里每次调用都是多态 就要找到虚函数指针 如果量太大， //这个效率就会有问题 所以c++ STL的迭代器都是用模版实现了 也就是编译时多态，而不是运行时多态 } 要点总结 因为虚函数在性能上的消耗，现代c++ STL库的迭代器都是用模版实现的，也就是编译时多态，而不是运行时多态 但思想时一致的 迭代多态为遍历不同的集合结构提供了一个统一的接口，从而支持同样的算法在不同和集合结构上操作 使用迭代器一定要考虑健壮性，遍历的同时如果更改了所在的集合结构，很可能出问题，因为内存已经发生变化，大部分迭代器是只读的。 职责链ChainOfResponsibility\u0026mdash;数据结构 需求背景、动机 如果你的项目中一个请求可能会被多个对象处理，但是每个请求在运行时只能有一个接收者，如果显示指定，将必不可少的带来情趣发送者与接受者的紧耦合 如何让请求的发送者不需要指定具体的接受者？ 模式定义 定义一个请求处理基类，定义公共处理接口，同时有一个基类指针next 运行时可以指向具体的请求处理子类，这样就可以形成一个链条，第一个不处理扔给下一个，对每个具体的处理子类都有这样的处理逻辑，这样做就达到了不必指定具体的处理者。 沿着对象的一条链 传递请求，直到一个对象处理它为止，就想数据结构的链表一样。 结构图解 代码 ChainOfResponsibility职责链模式 c++\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; using namespace std; //请求枚举类型 enum class RequestType { REQ_HANDLER1, REQ_HANDLER2, REQ_HANDLER3 }; //请求对象定义 class Reqest { string description; RequestType reqType; public: Reqest(const string \u0026amp; desc, RequestType type) : description(desc), reqType(type) {} RequestType getReqType() const { return reqType; } const string\u0026amp; getDescription() const { return description; } }; //职责链基类 定义接口 有个指向自己的指针(运行时灵活指派下一个处理责任主体) class ChainHandler{ ChainHandler *nextChain; //将请求发送个链条上下一个处理责任主体 void sendReqestToNextHandler(const Reqest \u0026amp; req) { if (nextChain != nullptr) nextChain-\u0026gt;handle(req); } protected: virtual bool canHandleRequest(const Reqest \u0026amp; req) = 0; virtual void processRequest(const Reqest \u0026amp; req) = 0; public: ChainHandler() { nextChain = nullptr; } void setNextChain(ChainHandler *next) { nextChain = next; } //公共处理接口 void handle(const Reqest \u0026amp; req) { if (canHandleRequest(req))//如果自己能处理 则直接处理请求 processRequest(req); else sendReqestToNextHandler(req);//否则传递给下一个处理责任主体 } }; //实际处理主体1 继承职责链处理基类 class Handler1 : public ChainHandler{ protected: bool canHandleRequest(const Reqest \u0026amp; req) override { return req.getReqType() == RequestType::REQ_HANDLER1; } void processRequest(const Reqest \u0026amp; req) override { cout \u0026lt;\u0026lt; \u0026#34;Handler1 is handle reqest: \u0026#34; \u0026lt;\u0026lt; req.getDescription() \u0026lt;\u0026lt; endl; } }; //实际处理主体2 继承职责链处理基类 class Handler2 : public ChainHandler{ protected: bool canHandleRequest(const Reqest \u0026amp; req) override { return req.getReqType() == RequestType::REQ_HANDLER2; } void processRequest(const Reqest \u0026amp; req) override { cout \u0026lt;\u0026lt; \u0026#34;Handler2 is handle reqest: \u0026#34; \u0026lt;\u0026lt; req.getDescription() \u0026lt;\u0026lt; endl; } }; //实际处理主体3 继承职责链处理基类 class Handler3 : public ChainHandler{ protected: bool canHandleRequest(const Reqest \u0026amp; req) override { return req.getReqType() == RequestType::REQ_HANDLER3; } void processRequest(const Reqest \u0026amp; req) override { cout \u0026lt;\u0026lt; \u0026#34;Handler3 is handle reqest: \u0026#34; \u0026lt;\u0026lt; req.getDescription() \u0026lt;\u0026lt; endl; } }; //调用示意 int main(){ Handler1 h1; Handler2 h2; Handler3 h3; h1.setNextChain(\u0026amp;h2); h2.setNextChain(\u0026amp;h3); Reqest req(\u0026#34;process task ... \u0026#34;, RequestType::REQ_HANDLER3);//这里看着简单 运行着这个对象可能表复杂 或者比较多，都直接显示指定耦合太多 h1.handle(req);//调用的时候不用指定具体的责任主体 return 0; } 要点总结 职责链模式的应用，让对象职责分派更具有灵活性 如果最后一个请求处理主体也不能处理，应该有一个告警或者缺省的处理机制。这也是每一个接受请求对象的责任，而不是发送请求对象的责任 当然责任链模式很容易直接用数据结构实现了，所以也是这个模式不怎么流行的原因 比较简答 但思想可以借鉴 命令模式Command\u0026mdash;行为变化 需求背景、动机 软件构建过程中，“行为请求者”和“行为实现者”通常呈现一种紧耦合(对象和成员函数)，但在某些场合，比如要对行为进行“记录、撤销、重做(undo/redo)、事务”等处理，这种紧耦合无法抵御变化。 怎么样才能将“行为请求者”与“行为实现者”解耦？ 模式定义 将一个请求(行为、或者直接说成员函数)封装成一个对象，从而使你可用不同的请求对客户进行参数化，请请求排队或者记录日志、撤销等等。 结构图解 代码 Command命令模式c++\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;string\u0026gt; using namespace std; //命令基类定义接口方法 class Command { public: virtual void execute() = 0; }; //命令1实现 class ConcreteCommand1 : public Command { string arg; public: ConcreteCommand1(const string \u0026amp; a) : arg(a) {} void execute() override { cout\u0026lt;\u0026lt; \u0026#34;#1 process...\u0026#34;\u0026lt;\u0026lt;arg\u0026lt;\u0026lt;endl; } }; //命令2实现 class ConcreteCommand2 : public Command { string arg; public: ConcreteCommand2(const string \u0026amp; a) : arg(a) {} void execute() override { cout\u0026lt;\u0026lt; \u0026#34;#2 process...\u0026#34;\u0026lt;\u0026lt;arg\u0026lt;\u0026lt;endl; } }; //组合命令实现 class MacroCommand : public Command { vector\u0026lt;Command*\u0026gt; commands; public: void addCommand(Command *c) { commands.push_back(c); } void execute() override { for (auto \u0026amp;c : commands) { c-\u0026gt;execute(); } } }; int main() { //执行某些命令操作就变成了 构造某些对象 然后执行其相应方法 ConcreteCommand1 command1(receiver, \u0026#34;Arg ###\u0026#34;); ConcreteCommand2 command2(receiver, \u0026#34;Arg $$$\u0026#34;); MacroCommand macro; macro.addCommand(\u0026amp;command1); macro.addCommand(\u0026amp;command2); macro.execute(); } 要点总结 Command模式目的是将“行为请求者”和”行为实现者“解耦，在面向对象语言中，常见的手段是\u0026quot;\u0026ldquo;将行为抽象为对象\u0026rdquo; 这个模式跟c++的函数对象又些类似，相比Command模式接口更规范，但函数对象使用更加灵活，现代工程函数对象用的更多 所谓函数对象就是一个class对象重载了()运算符，所以对象名称加上()就相当于调用了一个函数 访问器模式Visitor\u0026mdash;-行为变化 需求背景、动机 由于需求的改变、某些类层次结构常常需要增加新的行文(对象成员方法)，如果考虑基类子类的实现方式，需要在基类、子类都添加对应的方法，如果直接在基类修改，将会给子类带来繁重的负担，甚至破坏原设计 如何在不改变类层次结构的前提下，在运行时透明地为类层次结构的各个类动态的添加新的行为操作？ 模式定义 表示一个作用于某个对象结构中的各元素的操作，使得可以在不改变(稳定)个元素的类的前提下定义(扩展)作用于这些元素的新操作(行为) 采用二次分发的方式实现 但前提是基类的子类非常确定 不会经常变化，是子类的行为经常变化。 结构图解 代码 #include \u0026lt;iostream\u0026gt; using namespace std; //业务基类 刚开始有2个方法 class Element { public: virtual void Func1() = 0; virtual void Func2(int data)=0; virtual void Func3(int data)=0;//随着项目发展 要不停的增加方法 那么就需要修改基类然后修改每一个子类 //... virtual ~Element(){} }; class ElementA : public Element { public: void Func1() override{ //... } void Func2(int data) override{ //... } //这里添加Func3 }; class ElementB : public Element { public: void Func1() override{ //*** } void Func2(int data) override { //*** } //这里添加Func3 Func4.... }; Visitor访问器模式\n#include \u0026lt;iostream\u0026gt; using namespace std; //先声明 class Visitor; //业务基类 class Element { public: virtual void accept(Visitor\u0026amp; visitor) = 0; //第一次多态辨析 virtual ~Element(){} }; //业务子类A class ElementA : public Element { public: void accept(Visitor \u0026amp;visitor) override { visitor.visitElementA(*this); } }; //业务子类B class ElementB : public Element { public: void accept(Visitor \u0026amp;visitor) override { visitor.visitElementB(*this); //第二次多态辨析 } }; //访问器基类 定义好访问每个子业务类的接口 后面有各个访问器实现(为每个子类增加成员方法) class Visitor{ public: virtual void visitElementA(ElementA\u0026amp; element) = 0; virtual void visitElementB(ElementB\u0026amp; element) = 0; virtual ~Visitor(){} }; //==========================上述如果只有2个子类 随便添加方法 以上稳定============================== //扩展1 为子类增加一个成员方法 class Visitor1 : public Visitor{ public: void visitElementA(ElementA\u0026amp; element) override{ cout \u0026lt;\u0026lt; \u0026#34;Visitor1 is processing ElementA\u0026#34; \u0026lt;\u0026lt; endl; } void visitElementB(ElementB\u0026amp; element) override{ cout \u0026lt;\u0026lt; \u0026#34;Visitor1 is processing ElementB\u0026#34; \u0026lt;\u0026lt; endl; } }; //扩展2 为子类增加再一个成员方法 class Visitor2 : public Visitor{ public: void visitElementA(ElementA\u0026amp; element) override{ cout \u0026lt;\u0026lt; \u0026#34;Visitor2 is processing ElementA\u0026#34; \u0026lt;\u0026lt; endl; } void visitElementB(ElementB\u0026amp; element) override{ cout \u0026lt;\u0026lt; \u0026#34;Visitor2 is processing ElementB\u0026#34; \u0026lt;\u0026lt; endl; } }; //扩展3 为子类增加再一个成员方法 class Visitor3 : public Visitor{}; int main() { Visitor2 visitor; ElementB elementB; elementB.accept(visitor);// double dispatch 目的实现调用elementB对应Visitor2定义的成员方法 ElementA elementA; elementA.accept(visitor);// double dispatch 目的实现调用elementA对应Visitor2定义的成员方法 return 0; } 要点总结 通过双重分发机制(double dispatch)来实现在不更新业务类(Element)类层次结构的前提下，运行时透明的为各个业务类增加方法 最大缺点 业务类的子类数量要确定 这就导致在现实世界很难用到 只能用于哪些子类个数不频繁变化，但是每个子类行为频繁变化的情况 解析器模式Interpreter\u0026mdash;领域规则 需求背景、动机 如果在某一特定领域的问题比较复杂，类似的结构又不断重复出现，如果使用普遍的变成方式实现将面临非常频繁的变化 如果能够将这个特定领域的问题表达为某种语法规则，然后构建一个解析器解释这样的情况，从而解决问题 比如要实现一个加减法的表达式问题 a+b-c+d 模式定义 给定一个语言，定义的文法表示，并实现一个解释器来解决 结构图解 代码 Interpreter解析器模式c++\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;map\u0026gt; #include \u0026lt;stack\u0026gt; using namespace std; //表达式基类 class Expression { public: //主要接口 解析表达式 virtual int interpreter(map\u0026lt;char, int\u0026gt; var)=0; virtual ~Expression(){} }; //变量表达式 class VarExpression: public Expression { char key; public: VarExpression(const char\u0026amp; key) { this-\u0026gt;key = key; } //变量表达式的解析就直接返回其内存值即可 int interpreter(map\u0026lt;char, int\u0026gt; var) override { return var[key]; } }; //符号表达式基类 class SymbolExpression : public Expression { // 运算符左右两个参数 抽象语法就是有左右2个表达式 protected: Expression* left; Expression* right; public: SymbolExpression( Expression* left, Expression* right): left(left),right(right){ } }; //加法运算表达式实现 class AddExpression : public SymbolExpression { public: AddExpression(Expression* left, Expression* right): SymbolExpression(left,right){ } //解析规则就是左右两个表达式的解析值相加 这中间会涉及多态递归调用 int interpreter(map\u0026lt;char, int\u0026gt; var) override { return left-\u0026gt;interpreter(var) + right-\u0026gt;interpreter(var); } }; //减法运算 class SubExpression : public SymbolExpression { public: SubExpression(Expression* left, Expression* right): SymbolExpression(left,right){ } //解析规则就是左右两个表达式的解析值相减 这中间会涉及多态递归调用 int interpreter(map\u0026lt;char, int\u0026gt; var) override { return left-\u0026gt;interpreter(var) - right-\u0026gt;interpreter(var); } }; //定义解析器 Expression* analyse(string expStr) { stack\u0026lt;Expression*\u0026gt; expStack; Expression* left = nullptr; Expression* right = nullptr; for(int i=0; i\u0026lt;expStr.size(); i++) { switch(expStr[i]) { case \u0026#39;+\u0026#39;: // 加法运算 left = expStack.top(); right = new VarExpression(expStr[++i]); expStack.push(new AddExpression(left, right)); break; case \u0026#39;-\u0026#39;: // 减法运算 left = expStack.top(); right = new VarExpression(expStr[++i]); expStack.push(new SubExpression(left, right)); break; default: // 变量表达式 expStack.push(new VarExpression(expStr[i])); } } Expression* expression = expStack.top(); return expression; } void release(Expression* expression){ //释放表达式树的节点内存... } int main(int argc, const char * argv[]) { string expStr = \u0026#34;a+b-c+d-e\u0026#34;; map\u0026lt;char, int\u0026gt; var; var.insert(make_pair(\u0026#39;a\u0026#39;,5)); var.insert(make_pair(\u0026#39;b\u0026#39;,2)); var.insert(make_pair(\u0026#39;c\u0026#39;,1)); var.insert(make_pair(\u0026#39;d\u0026#39;,6)); var.insert(make_pair(\u0026#39;e\u0026#39;,10)); Expression* expression= analyse(expStr);//将字符串解析成对象 int result=expression-\u0026gt;interpreter(var);//多态调用解析最终值 解决问题 cout\u0026lt;\u0026lt;result\u0026lt;\u0026lt;endl; release(expression); return 0; } 要点总结 现实项目中用到的不多 只能解决语法相对简单的问题 否则多态虚函数调用过多会产生很多性能问题 23种设计模式总结 目标不能忘记 管理变化，提高复用 两者主要的手段 分解和抽象 深刻立刻8大原则和重构的5种技巧 设计模式最终可能的对象模型 时刻要注意关注变化点和稳定点 哪些场景不太适合用模式 代码可读性很差的时候 不建议直接使用 要首先保证代码有良好的可读性 这个是前提条件 需求理解的不深刻时 此时不能深刻理解哪些是稳定点和变化点 变化没有显现的时候 也是无法深刻理解变化点的 根本就不是系统的关键依赖点 可以不用花大的精力来折腾设计模式 项目本身没有任何复用价值 没有复用价值也就不用考虑设计模式了 项目即将发布的时候 就不要在折腾设计模式了 别再整出一大堆bug 一些经验之谈 不要为了模式而模式 要重视抽象类和接口 理清楚变化点和稳定点非常重要 经常审视类之间的依赖关系非常重要 Framework和Application的区隔思维要有 良好的设计都是慢慢演化的结果 不能一蹴而就 成长之路 “手中无剑，心中无剑”： 见到模式不认识 “手中有剑，心中无剑”: 可以识别不同的模式 作为应用开发人员可以使用 \u0026ldquo;手中有剑，心中有剑\u0026rdquo;： 可以作为框架开发人员应用设计模式 让别人用你开发的产品框架 “手中无剑，心中有剑”： 已经忘记了死板的设计模式，深刻理解原则，甚至能够创造模式 ","permalink":"https://www.becool.vip/posts/tech/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","summary":"\u003ch1 id=\"设计模式介绍\"\u003e设计模式介绍\u003c/h1\u003e\n\u003cp\u003e设计模式最关键的作用是为了可复用，减少开发工作量。\u003c/p\u003e\n\u003cp\u003e模式就是针对现实世界重复出现的问题，给出核心解决方案，忽略掉一些不重要的细节。\u003c/p\u003e\n\u003cp\u003e分解和抽象是2种解决现实问题的通用方法。\u003c/p\u003e\n\u003cp\u003e底层思维是向下的，多数是理解计算机的，抽象是向上思维，多数是理解现实世界的。\u003c/p\u003e\n\u003cp\u003e在现实工程开发中，要寻找需求频繁变化点，应用对应的设计模式，从而提高代码复用性，降低开发成本，测试成本。\u003c/p\u003e","title":"设计模式"},{"content":"高并发性能相关指标或术语回顾 连接相关 服务端能保持，管理，处理多少客户端的连接\n活跃连接数：所有ESTABLISHED状态的TCP连接，某个瞬时，这些连接正在传输数据。如果您采用的是长连接的情况，一个连接会同时传输多个请求。也可以间接考察后端服务并发处理能力，注意不同于并发量。 非活跃连接数：表示除ESTABLISHED状态的其它所有状态的TCP连接数。 并发连接数：所有建立的TCP连接数量。=活跃连接数+非活跃连接数。 新建连接数：在统计周期内，从客户端连接到服务器端，新建立的连接请求的平均数。主要考察应对 突发流量或从正常到高峰流量的能力。如：秒杀、抢票场景。 丢弃连接数：每秒丢弃的连接数。如果连接服务器做了连接熔断处理，这部分数据即熔断的连接 在linux上socket连接体现上就是文件描述符，高并发中相关的参数一定要调优。\n流量相关 ​ 主要是网络带宽的配置。\n流入流量：从外部访问服务器所消耗的流量。 流出流量：服务器对外响应的流量。 数据包数 数据包是TCP三次握手建立连接后，传输的内容封装\n流入数据包数：服务器每秒接到的请求数据包数量。 流出数据包数：服务器每秒发出的数据包数量。 如果数据包太大可以考虑压缩，因为传输的数据包小 效率一般会提升，但解压缩也需要性能消耗，不能无限制压缩\n应用传输协议 ​\t传输协议压缩率好，传输性能好，对并发性能提升高。但是也需要看调用双方的语言可以使用协议才行。可以自己定义，也可以使用成熟的传输协议。比如redis的序列化传输协议、json传输协议、Protocol Buffers传输协议、http协议等。 尤其在 rpc调用过程中，这个传输协议选择需要仔细甄别选型。\n长连接、短连接 长连接是指在一个TCP连接上，可以重用多次发送数据包，在TCP连接保持期间，如果没有数据包发送，需要双方发检测包以维持此连接。 半开连接的处理：当客户端与服务器建立起正常的TCP连接后，如果客户主机掉线（网线断开）、电源掉电、或系统崩溃，服务器将永远不会知道。长连接中间件，需要处理这个细节。linux默认配置2小时，可以配置修改。不过现实项目一般不用系统的这个机制，很多直接使用客户端心跳包维持连接，服务端启动定时器，超时就close掉连接 短连接是指通信双方有数据交互时，就建立一个TCP连接，数据发送完成后，则断开此TCP连接。但是每次建立连接需要三次握手、断开连接需要四次挥手。 关闭连接最好由客户端主动发起，TIME_WAIT这个状态最好不要在服务器端，减少占用资源，当然如果是使用短连接，客户端高频的出现TIME_WAIT也要注意临时端口耗尽的问题，可以有很多优化(比如SO_REUSEADDR选项、SO_LINGER选项，临时端口范围调优等) 选择建议：\n在客户端数量少场景一般使用长连接。后端中间件、微服务之间通信最好使用长连接。如：数据库连接，duboo默认协议等。 而大型web、app应用，使用http短连接（http1.1的keep alive变相的支持长连接，但还是串行请求/响应交互）。http2.0支持真正的长连接。 长连接会对服务端耗费更多的资源，上百万用户，每个用户独占一个连接，对服务端压力多大，成本多高。IM、push应用会使用长连接，但是会做很多优化工作。 由于https需要加解密运算等，最好使用http2.0（强制ssl），传输性能很好。但是服务端需要维持更多的连接。 并发连接和并发量 并发连接数：=活跃连接数+非活跃连接数。所有建立的TCP连接数量。网络服务器能并行管理的连接数。 并发量：瞬时通过活跃连接传输数据的量，这个量一般在处理端好评估。跟活跃连接数没有绝对的关系。网络服务器能并行处理的业务请求数。 rt响应时间：各类操作单机rt肯定不相同。比如：从cache中读数据和分布式事务写数据库，资源的消耗不同，操作时间本身就不同。 吞吐量：QPS/TPS，每秒可以处理的查询或事务数，这个是关键指标。 IO多路复用 相关观念回顾 用户空间与内核空间 linux操作系统采用虚拟存储器技术，对于32位操作系统，内存寻址空间就是4G(2^32)。操作系统的核心叫做内核kernel，独立于普通的Application，内核是可以访问受保护的内存空间，也有访问底层硬件的权限。为了保证用户进程不能直接操作内核(内核要足够稳定)，所以操作系统将虚拟空间划分2部分，一部分叫做内核空间，一部分叫做用户空间。\nlinux操作系统32位，将最高的1G(虚拟地址0xC0000000到0xFFFFFFFF) 是给内核使用的，叫做内核空间，而较低的3G字节(虚拟地址从0x00000000到0xBFFFFFFF)，给各个应用进程使用，叫做用户空间。\n每个进程可以通过系统调用进入内核，因此linux内核有系统内的所有进程共享，空间分配图大致如下:\nlinux系统内部结构大值图示:\n当一个任务(往往是进程或者线程)执行系统调用也就是执行内核代码，进程就进入内核态，当任务执行用户自己的代码，称为处于用户运行态(用户态)\n进程切换 为了控制各个进程的执行，内核必须有能力挂起某个正在运行的进程(正在使用cpu)，并具有恢复以前挂起进程并执行的能力。这种挂起与恢复执行往往被称为进程切换，任何进程都是在操作系统内核的支持下才能正常运行，跟内核密切相关。\n进程切换大致涉及内容：\n保存处理机的上下文，比如程序计数器、寄存器 更新进程PCB(Process Control Block)信息 将进程PCB放入相应的队列，如就绪队列，某些时间的阻塞等待队列等等 选择另外一个进程执行，更新其PCB信息 更新内存管理的数据结构 恢复处理机上下文 linux一个注释\n当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时，它需要保存当前进程的所有状态，即保存当前进程的上下文，以便在再次执行该进程时，能够必得到切换时的状态执行下去。在LINUX中，当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中，在内核态下执行中断服务例程。但同时会保留所有需要用到的资源，以便中继服务结束时能恢复被中断进程的执行 进程阻塞 正在执行的进程，由于等待某些事件(等待系统资源，等待某种操作完成，新的数据尚未到达或者没有新的工作等)，则会有操作系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态，进度阻塞状态之后，进程不占用cpu资源，等待的事件完成后再由内核将其唤醒。 文件描述符 File Descriptor为计算机科学中的一个术语，是一个抽象概念，用于表述指向文件的引用 在形式上是一个非负整数，实际上是一个索引值，指向内核为每个进程维护的一个打开文件的记录表。 当进程打开一个现有文件或者创建一个新文件，内核将返回其一个文件描述符 一些类Unix底层程序往往会围绕文件描述符展开工作 缓冲I/O linux有I/O缓存机制，操作系统会将I/O数据缓存在文件系统的页缓存(page catch)中，也就是数据往往先被操作系统拷贝到内核的缓冲区，然后再由操作系统内核的缓冲区拷贝到用户程序的地址空间。 缓存I/O机制导致数据在传输过程中需要用户空间和内核空间进行多次拷贝，这就必然导致cpu和内存开销 常见I/O模式复习 就拿read距离，数据先被拷贝到内核缓存区，然后再拷贝到用户地址空间，所以会经过2个阶段\n内核等待数据准备(Waiting for the data to be ready) 数据从内核拷贝到进程(Copying the data from the kernel to the process) 正是因为I/O缓存机制带来的2阶段，所以有了linux系统常见的5种I/O模式\n阻塞I/O ( blocking IO)\n非阻塞I/O (nonblocking IO)\nI/O多路复用 (IO multiplexing)\n信号驱动I/O (signal driven IO)\n异步I/O ( asynchronous IO)\n其中信号驱动IO实际开发中用到的不太多，下面复习下几个常用的IO模式\nBlocking IO 阻塞IO 默认linux所有的socket都是blocking的(有API设置为nonblocking),典型读操作流程如下:\nprocess call recv/recvform \u0026mdash;\u0026gt;process blocking 进程执行系统调用 进程阻塞\nKernel wait for the data 内核等待缓冲区数据达到(网络IO数据到缓冲区一般需要一定时间) process still blocking Data is ready ,copy data to user 数据达到后 内核将其拷贝到用户空间 process unblock to run 进程解除阻塞，这个地方也需要cpu调度，其实也需要时间 blocking IO的特点就是IO执行的2个阶段，进程一致处于阻塞状态 不能执行其它任务\n下面再摘一个图:\nNonBlocking IO 非阻塞IO 可以调用API将socket设置为non-blocking 大致流程如下 while ( recv(data)!=OK ){//系统调用recv不会阻塞，如果data is not ready,内核立马返回失败 wait sometime;//进程判断失败 往往休眠一定时间 再次调用recv系统调用 } 因为没有阻塞 系统调用会立马返回，但往往数据不会立马ready，所以非阻塞IO往往要不断的执行系统调用询问内核数据是否准备好了 IO multiplexing IO多路复用 这里常常就是指的select、pol、epoll，多路是指管理多个文件描述符，复用是指同一个进程或者一个线程，所以总结起来就是同一个进程(线程)管理多个文件描述符，提高IO处理效率的技术\nselect方式多路复用图解 select过程大致如下\n进程首先要将需要管理的描述符扔进select集合 进程调用select API(往往会设置超时时间，因为不设置没有数据到达就需要循环select)，此时进程进入blocking状态 内核收到api调用就会循环监视注册的文件描述符对应的数据是否准备好了 如果有一个好了，select就立马返回 然后进程收到select返回 再调用read操作，将数据从内核拷贝到用户进程 select模式跟阻塞IO模式没有太大的不同，而且有2次系统调用(select、recv)，普通的阻塞只有一个recv,但select的优势在于一个进程可以管理很多个文件描述符，也就是同时处理多个链接(socket conn)\n如果需要处理的连接数不高的话，单任务select/poll/epoll的服务器 不一定比多任务的blocking IO性能高，因为让内核轮询每个描述符也是需要花费时间的，有可能延迟会更大，IO多路复用的优势在于能够处理更多的连接，而不是单个连接的处理速度更快。\nIO多路复用下一般都将socket设置为non-blocking，而且要注意此时进程还是blocking的 只不过是被select/poll/epoll调用blocking,之前是被recvfrom调用blocking，但这个blocking的代价较小，因为我们可以管理更多的套接字。\nAsynchronous IO 异步IO 异步IO个人接触的也不多，也是大致了解流程，看个图：\n用户发起异步read操作后，kernel直接返回，进程也该干嘛干嘛，没有blocking\nkernel等数据ready后 copy to user，然后给用户进程发送一个信号(singnal)\n进程收到信号，调用注册的处理函数进行数据读取\n貌似异步IO非常NB，但其实linux并没有实现一个完美的AIO，而且异步IO必须预先分配缓存，这个可能造成内存浪费，这都可能是AIO没有流行的原因\nBlocking与non-blocking 阻塞与非阻塞 通常是说调用操作后，如何数据尚未到达，或者不具备条件，kernel是否返回，如果立马返回(不管有无数据)，就是non-blocking,如果kernel等待数据到达，没有立马返回，那就是阻塞 Synchronous IO与asynchronous IO Posix定义如下：\nA synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes An asynchronous I/O operation does not cause the requesting process to be blocked 如果执行IO操作 进程被block就是同步，否则就是异步 上面的4种IO模式，除了最后的AIO都是同步IO，IO多路复用虽然将socket设置为non-blocking,但其实调用select/poll/epoll还是会blocking\n几种IO模式比较图 同步阻塞BIO 服务端单任务 listenFd=socket(...);//创建socket bind(listenFd,...);//绑定端口 listen(listenFd,...);//开启监听 while(1) { // accept阻塞 client_fd = accept(listen_fd);//这里会阻塞 导致没法及时处理已经连接的客户端的请求 if (recv(client_fd)) {//读取客户端请求数据 这里也会阻塞，会导致没法接受新的客户端请求 // logic send(client_fd,...);//回包 这里也会阻塞，会导致没法接受新的客户端请求 } } // 这种模式教学可以，实际项目中没法处理高并发的请求 同步非阻塞 服务端单任务 listenFd=socket(...);//创建socket setNonblocking(listen_fd) bind(listenFd,...);//绑定端口 listen(listenFd,...);//开启监听 while(1) { // accept阻塞 client_fd = accept(listen_fd);//这里会阻塞 导致没法及时处理已经连接的客户端的请求 setNonblocking(client_fd)；//设置为非阻塞 fds.add(client_fd);//加入轮询集合; for( fd in fds){//循环检查 if (recv(client_fd)) {//读取客户端请求数据 这里也会阻塞，会导致没法接受新的客户端请求 // logic send(client_fd,...);//回包 这里也会阻塞，会导致没法接受新的客户端请求 } } } //处理小量并发是没有问题，但比较浪费cpu，因为每一轮循环都要针对每一个客户端套接字执行检查 //同时因为是单任务，遇到高并发肯定也要歇菜，假设有1000个client，第一个发送了请求，但是for循环检查的时候跳过了，然后要等到其它999个连接都检查处理完了，才能轮到这个client，很有可能已经超时了 同步阻塞BIO 服务端多并发任务 listenFd=socket(...);//创建socket bind(listenFd,...);//绑定端口 listen(listenFd,...);//开启监听 while(1) { // accept阻塞 client_fd = accept(listen_fd);//主进程 或者主线程只负责accept新的client连接 new ThreadProcess(client_fd){//这里开启多任务(多线程或者多进程)，一个client一个处理任务线程(进程) if (recv(fd)) {// // logic process send(client_fd,...);//回包 } } } //并发量不是很大的时候，这种模式是能够良好应对的，但如果并发量很大，比如上万，大量的线程或者进程也会占用大量内存，而且进程切换或者线程切换都会浪费cpu，就会导致真正工作的任务比例往往很低，所以这种模式遇到真正高并发也会歇菜。 //用go语言实现一个这样的模式非常简单 来一个client就开启一个routine去处理即可 IO多路复用\u0026mdash;select API原型 //将一个fd从关心的fdset中移除 其实就是将对应的坑位置0 void\tFD_CLR(fd, fd_set *fdset); // 检查fd是否在对应的fdset中 也就是检查对应的坑位是否为1 int\tFD_ISSET(fd, fd_set *fdset); //将fd增加到要关心的fdset集合中 也就是将对应fdset坑位置1 比如客户端socket fd为9，就是将fdset[9]设置为1 void\tFD_SET(fd, fd_set *fdset); // 清空要关心的fdset集合 将fdset数组全部置0 void\tFD_ZERO(fd_set *fdset); //\tnfds为最大检查描述符 select将对fdset数组0\u0026mdash;\u0026gt;ndfs下标做检查 如果下标对应的value为1 则需要检查描述符是否具备条件了 不用1024个都检查，传入这个最大检查描述符是为了提高select轮询的效率 //readfds为关心读事件的描述符集合 writefds为关心写事件的描述符集合 errorfds为关心异常事件的描述符集合 //timeout为一个结构体 如何设置为null，则对应3个fdset若都没有就绪的描述符 进程将一直blocking //timeout如果设置为0 则select检查一轮将直接返回 不会等待 //timeout如果设置为具体的秒+微秒 则select最多等待设置的时间，如果没有就返回 //同时要说明的是select是轮询所有设置fdset中的所有value为1的描述符，如何符合要求就全部都返回 int\tselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout); 关于 fd_set一般是一个int类型数组 一般初始化大小为1024 用来保存哪些fd需要检查，这个1024是可以调整的，但比较麻烦这也是select不足的地方，而且能监控的一般没有1024，因为进程或者线程一般都有打开的文件描述符，比如标准输入输出出错，或者打开的有日志文件等等。 不足 能监控的描述符数量有限 修改起来也比较麻烦 每次select都需要全部设置一遍需要关心的fdset集合，这就涉及到用户态到内核态的拷贝 效率不会很高 select内部实现是轮询，不管描述符对应是否就绪，所以性能也不会太高 ，当然如果监控的套接字都非常活跃，那这个轮询性能问题也可以忽略(但正是因为都非常活跃，所以select返回后单任务处理也会遇到瓶颈，因为大量活跃的连接等待处理，但是只有一个单任务进程/线程在处理) 其它说明 select监控的socketfd 跟socketfd本身是否是blocking没有关系，所有套接字是阻塞时真正recv或者write的时候 如果数据不具备条件则内核直接返回或者阻塞等待，selelct只是检查数据是否具备读条件了，没有发生真正读写，而且是内核直接判断读写缓冲区是否符合条件的 但不能说调用select的进程是不阻塞的，理论上调用了select也是一直阻塞的，只是阻塞时间的长短(timeout设置为0 只轮询一次就返回，如果为null，无限等待直到至少有一个套接字符合条件，如果设置为具体时间，则最多等待这个设置时间)，只是进程不是因为调用recv或者write阻塞，而是调用select阻塞，而且返回的可以是多个符合读写的fd。 代码 select示例代码转载加注释，简单优化c ```c #include #include #include #include #include #include #include #include #include #include #include #include #define IPADDR \"127.0.0.1\" #define PORT 8787 #define MAXLINE 1024 #define LISTENQ 5 #define MAXCLIENTSIZE 10 //当前套接字连接信息结构体 typedef struct server_context_st { int cli_cnt; /*已连接过的客户端个数*/ int clifds[MAXCLIENTSIZE]; /*客户端套接字集合*/ fd_set allfds; /*句柄集合*/ int maxfd; /*句柄最大值*/ } server_context_st; static server_context_st *s_srv_ctx = NULL; /*=========================================================================== createSocketAndListen 根据传入字符串格式ip和端口port，创建socket并且开启监听 * ==========================================================================*/ static int createSocketAndListen(const char* ip,int port){ int fd; struct sockaddr_in servaddr; //int socket(int domain, int type, int protocol); fd = socket(AF_INET, SOCK_STREAM,0); if (fd == -1) { fprintf(stderr, \"create socket fail,erron:%d,reason:%s\\n\", errno, strerror(errno)); return -1; } /*一个端口释放后会等待一段时间后(比如两分钟)才能再被使用，SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/ int reuse = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, \u0026reuse, sizeof(reuse)) == -1) { return -1; } //套接字结构体初始化为0 bzero(\u0026servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; //ip ASCII字符串转为二进制 inet_pton(AF_INET,ip,\u0026servaddr.sin_addr); //port转为网络字节序 servaddr.sin_port = htons(port); //套接字绑定ip和端口 if (bind(fd,(struct sockaddr*)\u0026servaddr,sizeof(servaddr)) == -1) { perror(\"bind error: \"); return -1; } //开启监听 if ( listen(fd,LISTENQ) == -1){ perror(\"listen error: \"); return -1; } return fd; } /*=========================================================================== accept_client_proc 传入服务端监听套接字，接受客户端连接 * ==========================================================================*/ static int accept_client_proc(int srvfd) { struct sockaddr_in cliaddr; socklen_t cliaddrlen; cliaddrlen = sizeof(cliaddr); int clifd = -1; printf(\"accpet clint proc is called.\\n\"); ACCEPT: clifd = accept(srvfd,(struct sockaddr*)\u0026cliaddr,\u0026cliaddrlen); if (clifd == -1) { if (errno == EINTR) { goto ACCEPT; } else { fprintf(stderr, \"accept fail,error:%s\\n\", strerror(errno)); return -1; } } //打印客户端套接字信息 fprintf(stdout, \"accept a new client: %s:%d\\n\", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //将新的连接描述符添加到数组中 int i = 0; for (i = 0; i \u003c MAXCLIENTSIZE; i++) { if (s_srv_ctx-\u003eclifds[i] \u003c 0) { s_srv_ctx-\u003eclifds[i] = clifd; s_srv_ctx-\u003ecli_cnt++; break; } } //已经达到最大连接数 打印错误日志报错返回 if (i == MAXCLIENTSIZE) { fprintf(stderr,\"too many clients.\\n\"); return -1; } return 0; } /*=========================================================================== handle_client_msg 传入客户端套接字和缓存 处理客户端请求消息 实现回射 * ==========================================================================*/ static int handle_client_msg(int fd, char *buf) { assert(buf); printf(\"recv buf is :%s\\n\", buf);//打印客户端消息 if ( write(fd, buf, strlen(buf) +1) \u003c0){ perror(\"write to client error\"); return -1; }//并原样返回给客户端 return 0; } /*=========================================================================== recv_client_msg 传入fdset指针 处理可读的客户端套接字 * ==========================================================================*/ static void recv_client_msg(fd_set *readfds) { int i = 0, n = 0; int clifd; char buf[MAXLINE] = {0}; for (i = 0;i \u003c= s_srv_ctx-\u003ecli_cnt;i++) { clifd = s_srv_ctx-\u003eclifds[i]; if (clifd \u003c 0) { continue; } /*判断客户端套接字是否有数据*/ if (FD_ISSET(clifd, readfds)) { //接收客户端发送的信息 n = read(clifd, buf, MAXLINE); if (n \u003c= 0) { /*n==0表示读取完成，客户都关闭套接字*/ FD_CLR(clifd, \u0026s_srv_ctx-\u003eallfds); close(clifd); s_srv_ctx-\u003eclifds[i] = -1; continue; } //回传给客户端 handle_client_msg(clifd, buf); } } } /*=========================================================================== handle_client_proc 主处理函数 传入监听套接字 接受客户端连接 逐个处理客户端请求 * ==========================================================================*/ static void handle_client_proc(int srvfd) { int clifd = -1; int retval = 0; fd_set *readfds = \u0026s_srv_ctx-\u003eallfds; struct timeval tv; int i = 0; while (1) { /*每次调用select前都要重新设置文件描述符和时间，因为事件发生后，文件描述符和时间都被内核修改啦 这个也是使用select的缺点*/ FD_ZERO(readfds); /*添加监听套接字*/ FD_SET(srvfd, readfds); s_srv_ctx-\u003emaxfd = srvfd; tv.tv_sec = 30;//超时时间30秒 tv.tv_usec = 0; /*添加客户端套接字*/ for (i = 0; i \u003c s_srv_ctx-\u003ecli_cnt; i++) { clifd = s_srv_ctx-\u003eclifds[i]; /*去除无效的客户端句柄 比如客户端已经close*/ if (clifd != -1) { FD_SET(clifd, readfds); } //计算最大fd s_srv_ctx-\u003emaxfd = (clifd \u003e s_srv_ctx-\u003emaxfd ? clifd : s_srv_ctx-\u003emaxfd); } /*开始轮询接收处理服务端和客户端套接字*/ retval = select(s_srv_ctx-\u003emaxfd + 1, readfds, NULL, NULL, \u0026tv); if (retval == -1) { fprintf(stderr, \"select error:%s.\\n\", strerror(errno)); return; } if (retval == 0) { fprintf(stdout, \"select is timeout.\\n\"); continue; } if (FD_ISSET(srvfd, readfds)) { /*监听客户端请求*/ accept_client_proc(srvfd); } else { /*接受处理客户端消息*/ recv_client_msg(readfds); } } } /*=========================================================================== server_uninit 程序退出前相关工作处理 * ==========================================================================*/ static void server_uninit() { if (s_srv_ctx) { free(s_srv_ctx); s_srv_ctx = NULL; } } /*=========================================================================== server_init 服务器初始化主要是初始化套接字管理结构体 * ==========================================================================*/ static int server_init() { //申请内存 s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st)); if (s_srv_ctx == NULL) { return -1; } //memset初始化 memset(s_srv_ctx, 0, sizeof(server_context_st)); //初始化客户端套接字具柄为-1 int i = 0;//最大处理客户端为MAXCLIENTSIZE for (;i \u003c MAXCLIENTSIZE; i++) { s_srv_ctx-\u003eclifds[i] = -1; } return 0; } //main函数 int main(int argc,char *argv[]) { int srvfd; /*初始化服务端context*/ if (server_init() \u003c 0) { return -1; } /*创建服务,开始监听客户端请求*/ srvfd = createSocketAndListen(IPADDR, PORT); if (srvfd \u003c 0) { fprintf(stderr, \"socket create or bind fail.\\n\"); goto err; } /*开始接收并处理客户端请求 进入主函数处理*/ handle_client_proc(srvfd); //准备退出 退出前工作处理 server_uninit(); return 0; err: server_uninit(); return -1; } ``` IO多路复用\u0026mdash;poll API原型 # include \u0026lt;poll.h\u0026gt; int poll ( struct pollfd * fds, unsigned int nfds, int timeout); struct pollfd { int fd; /* 要监控的文件描述符 如果为负值 内核会直接忽略 可以通过修改为-1 让内核不再监控*/ short events; /* 期待等待的事件 */ short revents; /* 实际发生了的事件 */ } ; poll跟select整体机制是一样的 也是传入需要监控的fd集合 然后轮询 只是数据结构不是用int数组，而是改为pollfd结构体数组，所以没有了大小数量限制，对于每个关心的描述符可以更灵活的设置期待时间 代码使用起来更加灵活\n第一个参数fds为需要监控的fd信息结构体数组指针 每次poll之前都可以动态的添加或者移除(通常直接设置对应的fd为-1)\nnfds为第一个参数fds的size也就是需要监控的fd的个数\ntimeout为超时时间\nIf timeout is greater than zero, it specifies a maximum interval (in milliseconds) to wait for any file descriptor to\nbecome ready. \u0026gt;0 单位毫秒 内核最多等待timeout毫秒\nIf timeout is zero, then poll() will return without blocking. =0 如果没有描述符就位，直接返回不阻塞，如果有就位的就会立马返回\nIf the value of timeout is -1, the poll blocks indefinitely 如果是-1，则一直阻塞，直到至少一个描述符期待的事件发生\npollfd对应的events和revents取值解释\nevents revents 事件 描述 可作为输入 可作为输出 POLLIN 数据可读（包括普通数据\u0026amp;优先数据） 是 是 POLLOUT 数据可写（普通数据\u0026amp;优先数据） 是 是 POLLRDNORM 普通数据可读 是 是 POLLRDBAND 优先级带数据可读（linux不支持） 是 是 POLLPRI 高优先级数据可读，比如TCP带外数据 是 是 POLLWRNORM 普通数据可写 是 是 POLLWRBAND 优先级带数据可写 是 是 POLLRDHUP TCP连接被对端关闭，或者关闭了写操作，由GNU引入 是 是 POPPHUP 挂起 否 是 POLLERR 错误 否 是 POLLNVAL 文件描述符没有打开 否 是 可能的返回码\n正常返回fds数组中revents不为0的文件描述符的个数 如果超时之前没有任何事件发生 则poll返回0 失败时 poll返回-1 errno设置如下： 不同os返回码可能不一致 可以man下 EBADF　一个或多个结构体中指定的文件描述符无效 EFAULT　指针指向的地址超出进程的地址空间。 EINTR　请求的事件之前产生一个信号，调用可以重新发起 EINVAL　参数超出PLIMIT_NOFILE值 ENOMEM　可用内存不足，无法完成请求 不足及改进 相比select 没有了数量的限制 此为改进 每次poll fd数组都要经过用户态拷贝到内核态 性能还是会受影响 还是轮询机制 监控的fd数量如果太多 不论文件描述符是否就绪 开销随着监控数量增加而线性增大 代码 poll示例代码_转载整理加注释、简单优化 C ```c #include #include #include #include #include #include #include #include #include #include #define IPADDRESS \"127.0.0.1\" #define PORT 8787 #define MAXLINE 1024 #define LISTENQ 5 #define OPEN_MAX 1000 #define INFTIM -1 //函数声明 //创建套接字并进行绑定然后启动监听 static int createSocketAndListen(const char* ip,int port); //IO多路复用poll static void do_poll(int listenfd); //处理多个连接 static void handle_connection(struct pollfd *connfds,int num); int main(int argc,char *argv[]) { int listenfd,connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; listenfd = createSocketAndListen(IPADDRESS,PORT); do_poll(listenfd); return 0; } /*=========================================================================== createSocketAndListen 根据传入字符串格式ip和端口port，创建socket并且开启监听 * ==========================================================================*/ static int createSocketAndListen(const char* ip,int port) { int listenfd; struct sockaddr_in servaddr; listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1) { perror(\"socket error:\"); exit(1); } bzero(\u0026servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET,ip,\u0026servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)\u0026servaddr,sizeof(servaddr)) == -1) { perror(\"bind error: \"); exit(1); } //开启监听 if ( listen(listenfd,LISTENQ) == -1){ perror(\"listen error: \"); exit(1); } return listenfd; } /*=========================================================================== do_poll 传入监听监听套接字 执行主流程 * ==========================================================================*/ static void do_poll(int listenfd) { int connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; struct pollfd clientfds[OPEN_MAX];//常见pollfd数组 int maxi;//需要监控fd数组中最大不为-1的下标位置 可以计算出来当前真正需要监控的fd数量 int i; int nready; //添加监听描述符 clientfds[0].fd = listenfd;//第一个元素设置为监听套接字 clientfds[0].events = POLLIN;//期待读事件 //初始化客户连接描述符 for (i = 1;i \u003c OPEN_MAX;i++) clientfds[i].fd = -1; maxi = 0; //循环处理 for ( ; ; ) { //获取可用描述符的个数 nready = poll(clientfds,maxi+1,INFTIM); if (nready == -1) { perror(\"poll error:\"); exit(1); } //测试监听描述符是否准备好 if (clientfds[0].revents \u0026 POLLIN) { cliaddrlen = sizeof(cliaddr); //接受新的连接 if ((connfd = accept(listenfd,(struct sockaddr*)\u0026cliaddr,\u0026cliaddrlen)) == -1) { if (errno == EINTR) continue; else { perror(\"accept error:\"); exit(1); } } fprintf(stdout,\"accept a new client: %s:%d\\n\", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //将新的连接描述符添加到数组中 找到fd数组可以插入的位置 是-1就可以插入 而且是下标从小到达开始查找 for (i = 1;i \u003c OPEN_MAX;i++) { if (clientfds[i].fd \u003c 0)//顺序查找哪个槽位可以使用 刚开始只有第一个不为-1 { clientfds[i].fd = connfd;//加进pollfd数组 break; } } if (i == OPEN_MAX)//这里只是为了测试 超过这个数量的客户端 程序报错退出 { fprintf(stderr,\"too many clients.\\n\"); exit(1); } //将新的描述符添加到读描述符集合中 clientfds[i].events = POLLIN; //记录客户连接套接字的个数 maxi = (i \u003e maxi ? i : maxi); if (--nready \u003c= 0)//如果除了监听套接字没有就绪的事件 那就continue继续下次poll轮询 continue; } //处理客户连接 handle_connection(clientfds,maxi); } } /*=========================================================================== handle_connection 传入poll返回的fd数组 逐个处理就绪的客户端套接字 * ==========================================================================*/ static void handle_connection(struct pollfd *connfds,int num) { int i,n; char buf[MAXLINE]; memset(buf,0,MAXLINE); for (i = 1;i \u003c= num;i++) { if (connfds[i].fd \u003c 0) continue; //测试客户描述符是否准备好 if (connfds[i].revents \u0026 POLLIN) { //接收客户端发送的信息 n = read(connfds[i].fd,buf,MAXLINE); if (n == 0) { close(connfds[i].fd); connfds[i].fd = -1;//如果客户端关闭了套接字 从监控套接字数组中移除 continue; } // printf(\"read msg is: \"); write(STDOUT_FILENO,buf,n); //向客户端发送buf write(connfds[i].fd,buf,n); } } } ``` IO多路复用\u0026mdash;epoll API原型 #include \u0026lt;sys/epoll.h\u0026gt; int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; events可以是以下几个宏的集合： EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）； EPOLLOUT：表示对应的文件描述符可以写； EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）； EPOLLERR：表示对应的文件描述符发生错误； EPOLLHUP：表示对应的文件描述符被挂断； EPOLLET： 将EPOLL设为边缘触发(Edge Triggered)模式，这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里 epoll_create\n创建一个epoll文件描述符，size是要告诉内核这个监听套接字的数量，创建好epoll句柄后，就会占用一个fd值，在linux系统以下目录/proc/pid/fd是可以看到的，所以epoll结束 好习惯也要close掉 epoll_ctl\nctl就是control控制的缩写，是一个事件注册函数，epfd为epoll_create函数返回的epoll句柄 第二个参数op为操作类型 有以下三个 EPOLL_CTL_ADD：注册新的fd到epfd中 EPOLL_CTL_MOD：修改已经注册的fd的监听事件 EPOLL_CTL_DEL：从epfd中删除一个fd 第三个参数fd为需要监听的套接字句柄 第四个参数是告诉内核针对fd要监听什么事件 events枚举上面有列出来 epoll_wait\n类似select、poll调用名字也有wait，就是等待事件产生 第一个参数epfd为epoll_create返回的epoll句柄 第二个参数events用来从内核得到事件集合，内核会将就绪的描述符及发生的事件写到这个列表中 第三个参数是告知内核 这个列表有多大，不能大于调用epoll_create(int size)传入的size 第四个参数超时时间 单位毫秒 ： timeout\u0026gt;0 如果没有套接字就绪 内核最多等到timeout毫秒 timeout=0 如果没有套接字就绪 内核直接返回 不阻塞 timeout=-1 会一直阻塞 直到有套接字就绪 epoll两者工作模式\nLT(level trigger) 水平触发模式\nepoll_wait检测到fd事件发生并将此事件通知应用程序，如果应用程序不立即处理，下次调用epoll_wait会再次告知应用程序\nET(edge trigger) 边缘触发模式\n当epoll_wait检测到描述符事件发生并将此事件通知应用程序，应用程序必须立即处理该事件。如果不处理，下次调用epoll_wait时，不会再次响应应用程序并通知此事件\nET模式很大程度减少了epoll事件被重复触发的次数，相对于LT效率更高，在ET模式下，必须使用非阻塞套接字，以避免由于一个套接字阻塞读写导致整个处理多套接字的任务搞死。 改进、不足 相对于select没有数量限制 相对于select、poll 对于监控的描述符集合 不用每次都从用户态拷贝到内核态，只是在epoll_ctl添加的时候拷贝一次 epoll_wait不是轮询机制 而是采用回调函数的机制 所以效率更高 epoll目前只有linux有 是从linux内核2.6开始提出的 不过windows和Mac(bsd)也有类似的实现 代码 epoll示例代码 转载加注释 简单优化 C ```c #include #include #include #include #include #include #include #include #include #include #define IPADDRESS \"127.0.0.1\" #define PORT 8787 #define MAXSIZE 1024 #define LISTENQ 5 #define FDSIZE 1000 #define EPOLLEVENTS 100 //函数声明 //创建套接字并进行绑定 static int socket_bind(const char* ip,int port); //IO多路复用epoll static void do_epoll(int listenfd); //事件处理函数 static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd); //处理接收到的连接 static void handle_accpet(int epollfd,int listenfd); //读处理 static void handle_connection(int epollfd,int fd); //写处理 static void sendToClient(int epollfd,int fd,char *buf); //添加事件 static void add_event(int epollfd,int fd,int state); //修改事件 static void modify_event(int epollfd,int fd,int state); //删除事件 static void delete_event(int epollfd,int fd,int state); int main(int argc,char *argv[]) { int listenfd; listenfd = socket_bind(IPADDRESS,PORT); listen(listenfd,LISTENQ); do_epoll(listenfd); return 0; } /*=========================================================================== socket_bind 创建监听套接字 * ==========================================================================*/ static int socket_bind(const char* ip,int port) { int listenfd; struct sockaddr_in servaddr; listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1) { perror(\"socket error:\"); exit(1); } /*一个端口释放后会等待一段时间后(比如两分钟)才能再被使用，SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/ int reuse = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, \u0026reuse, sizeof(reuse)) == -1) { return -1; } bzero(\u0026servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET,ip,\u0026servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)\u0026servaddr,sizeof(servaddr)) == -1) { perror(\"bind error: \"); exit(1); } return listenfd; } /*=========================================================================== epoll处理主函数 需要传入监听套接字 * ==========================================================================*/ static void do_epoll(int listenfd) { int epollfd; struct epoll_event events[EPOLLEVENTS]; int ret; //创建一个描述符 epollfd = epoll_create(FDSIZE); //添加监听描述符事件 add_event(epollfd,listenfd,EPOLLIN); for ( ; ; ) { //获取已经准备好的描述符事件 ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1); handle_events(epollfd,events,ret,listenfd); } close(epollfd); } /*=========================================================================== handle_events epoll_wait 返回后针对就绪套接字 逐个处理 * ==========================================================================*/ static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd) { int i; int fd; //对内核返回就绪的events列表 遍历处理 for (i = 0;i \u003c num;i++) { fd = events[i].data.fd; //根据描述符的类型和事件类型进行处理 if ((fd == listenfd) \u0026\u0026(events[i].events \u0026 EPOLLIN))//监听套接字 需要accept同时将连接好的clientfd加入epollfd handle_accpet(epollfd,listenfd); else if (events[i].events \u0026 EPOLLIN)//读事件 handle_connection(epollfd,fd); else{ perror(\"非accept可读、非clinet可读\"); } } } /*=========================================================================== handle_accpet 监听套接字 需要accept同时将连接好的clientfd加入epollfd * ==========================================================================*/ static void handle_accpet(int epollfd,int listenfd) { int clifd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; clifd = accept(listenfd,(struct sockaddr*)\u0026cliaddr,\u0026cliaddrlen); if (clifd == -1) perror(\"accpet error:\"); else { printf(\"accept a new client: %s:%d\\n\",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //添加一个客户描述符和事件 add_event(epollfd,clifd,EPOLLIN); } } /*=========================================================================== do_read 处理读事件就绪的客户端连接套接字 * ==========================================================================*/ static void handle_connection(int epollfd,int fd) { char buf[MAXSIZE]; memset(buf,0,MAXSIZE); int nread; nread = read(fd,buf,MAXSIZE); if (nread == -1) { perror(\"read error:\"); close(fd); delete_event(epollfd,fd,EPOLLIN); } else if (nread == 0) { fprintf(stderr,\"client close.\\n\"); close(fd); delete_event(epollfd,fd,EPOLLIN); } else { printf(\"read message is : %s\\n\",buf); //直接回复给客户端 sendToClient(epollfd,fd,buf); } } static void sendToClient(int epollfd,int fd,char *buf) { int nwrite; nwrite = write(fd,buf,strlen(buf)); if (nwrite == -1) { perror(\"write error:\"); close(fd); delete_event(epollfd,fd,EPOLLIN); } } static void add_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,\u0026ev); } static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,\u0026ev); } static void modify_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,\u0026ev); } ``` # 多线程 为什么需要多进程多线程 本质原因cpu、内存IO、磁盘IO因为介质不同，工作效率天壤之别，cpu速度远远大于内存，内存速度又远远大于磁盘。所以这么一来最开始的计算使用者就发现，cpu太闲了，而cpu贵，实在是太浪费了，所以后面就想尽办法办法，让cpu忙起来 下面的是简单的发展历史\n单任务时代\n计算机只负责计算，不能直接写入指令和输出结果，程序员把程序写到纸上，然后穿孔称卡片，再把卡片输入到计算机，计算机计算结果打印出来，程序员最后拿到结果。这个时代的计算机cpu是很闲的，程序员干半天，计算机一会完事。\n批处理\n程序由卡片改为了磁带，这时候一次可以录入更多的程序，一次批处理的方式执行多个任务，但计算机同时还是只能干一件事情，进行IO的时候不能计算，进行计算的时候不能IO，而cpu干活又贼快，所以cpu整体还是很闲。\n支持多道程序设计 多进程时代\n出现了多进程，每个进程拥有自己独立的内存空间，不同进程之间互不干涉，因为cpu太快而且又贵，所以大家就轮着用，也就是cpu时间片的概念，不同的进程使用cpu由操作系统统一管理，争取公平的分配给每个进程所需要的cpu时间片，一个进程用一会cpu，操作系统就保存现场，同时把cpu转给其它进程(也就是进程切换)，这个时代进程如果够多，cpu其实已经很忙了，因为一堆进程轮番上阵，无休止的车轮战。\n多线程时代\n因为进程切换比较消耗时间，现场保存和切换工作因为进程比较重所以比较消耗时间和资源，所以就来了多线程，线程更加轻量级，一个进程的多个线程间共享进程的内存空间，所以切换起来更快，当然cpu也就会更累。\n因为多线程的出现，cpu变得更加繁忙。\n多cpu 多线程\n进入了多线程时代后，终于彻底可以把cpu搞死了，所以人们开始嫌弃cpu没有那么快了，所以就想着搞多个cpu，让多个cpu同时工作，这个时候其实才是真正的任务并行了，之前是多个任务不断的抢一个cpu，因为cpu太快了，所以基本没什么感觉，进入多核cpu后才真正的让多个任务并行了。\n多cpu并发可能产生的问题 多个cpu缓存导致的可见性问题 还是因为cpu太快，内存磁盘太慢所以有了cpu缓存，所以cpu执行数据操作过层导致如下：\n从磁盘加载数据到内存 从内存加载到cpu缓存 执行相关计算操作 更新cpu缓存 更新内存 更新磁盘 多核cpu，就有多个cpu缓存，而它们彼此不可见，每个cpu也是操作的自己独立的cpu缓存，这就必然产生不可见性，比如a=a+1\ncpu1修改了对应的cpu缓存，而此时cpu2是不知道的，必须等缓存同步以后才知道。\n所以线程见如果操作共享资源要注意加锁，否则可能出现意想不到的结果\n例子：2个线程 同时++一个变量10W次\ncpu切换线程导致的原子性问题 原子性就是一个操作(多个相关子操作)在执行的过程中不能被打断，要么都执行，要么都不执行。\n例子 int number=0;number=number+1;\nnumber=number+1的指令可能如下：\n指令1：CPU把number从内存拷贝到CPU缓存。\n指令2：把number进行+1的操作。\n指令3：把number回写到内存.\n如果有2个线程都在执行上面的例子，cpu的执行流程可能如下： 执行细节：\n​\t1、CPU先执行线程A的执行，把number=0拷贝到CUP寄存器。\n​\t2、然后CPU切换到线程B执行指令。\n​\t3、线程B 把number=0拷贝到CUP寄存器。\n​\t4、线程B 执行number=number+1 操作得到number=1。\n​\t5、线程B把number执行结果回写到缓存里面。\n​\t6、然后CPU切换到线程A执行指令。\n​\t7、线程A执行number=number+1 操作得到numbe=1。\n​\t8、线程A把number执行结果回写到缓存里面。\n​\t9、最后内存里面number的值为1。\n编译器优化带来的指令重排序问题 编译器的有些优化会打乱执行本来人为理解的顺序\n例子： 单例模式的double check，其实用了很长一段时间，直到内存reorder问题发现。\npublic class Singleton { private Singleton() {} private static Singleton sInstance; public static Singleton getInstance() { if (sInstance == null) {\t//第一次验证是否为null synchronized (Singleton.class) { //加锁 if (sInstance == null) {\t//第二次验证是否为null sInstance = new Singleton(); //创建对象 } } } return sInstance; } } Instance = new Singleton();这行代码时会分解成三个指令执行。\n1、为对象分配一个内存空间。\n2、在分配的内存空间实例化对象。\n3、把Instance 引用地址指向内存空间\n而且2 3的顺序可能发生变化，如果先执行3，然后另外一个线程抢到cpu 发现对象指针不为空，直接返回然后使用，而这个时候对象还没有完成初始化工作，是会出问题的。\nc++11 多线程 创建线程 c++11 创建一个线程相对比较简单 大致步骤如下： 1. #include\u0026lt;thread\u0026gt; 2. 定义线程代码执行路径(线程就是可以单独执行代码的通道，要定义线程启动后要从哪个地方开始从上往下执行代码) 3. 在main主线程 创建一个thread对象(这个时候线程就自动生成并且运行了) 4. 主线程做一些线程管理工作(比如调用join函数阻塞等待某个线程，也可以调用detach函数 跟某个线程脱离关系，让init进程接管然后去后台运行） 线程代码执行入口的常见方式 普通的函数\n//普通函数作为线程执行入口 void ordinaryFunc(){ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(1);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; } int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; thread t1(ordinaryFunc); t1.join();//main函数阻塞 等待子线程退出 cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to return\u0026#34;\u0026lt;\u0026lt;endl; } -----输出 主线程id:0x11a916dc0 is running 线程id:0x700001438000 is running 线程id:0x700001438000 is ready to exit 主线程id:0x11a916dc0 is ready to exit 函数对象(重载了()运算符的对象)\n//函数对象作为线程执行入口 class FuncObj{ public: void operator()(){ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(1);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; } }; int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; thread t1( ( FuncObj() )); t1.join(); cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } 类成员函数\n//类成员函数作为线程执行入口 class ObjTest{ public: void run(){ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(1);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; } }; int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; ObjTest runtest; thread t1(\u0026amp;ObjTest::run,\u0026amp;runtest);//类成员函数作为线程执行入口要多传入一个对象的this指针 t1.join(); cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } lambda表达式\nint main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; auto lambdaThread=[]{ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(1);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; }; thread t1(lambdaThread); t1.join(); cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } 线程执行入口传递参数 创建线程会复制新的堆栈 参数统一都会复制，如果要传递引用 可以使用std::ref()，否则即使函数参数写的是引用，最后也不是真的引用。 线程之间如果使用的是同一个变量，要注意作用域问题，特别是detach后，很可能一个线程已经挂了，另外一个线程还在使用某个挂了的线程中的变量，就会差生奇怪的后果。 线程管理函数 join\n主线程 执行了t1.join()后，主线程就会阻塞的等待子线程结束\ndetach\n主线程执行了t1.detach()后，t1线程就跑到后台执行了 跟当前主线程就没有关系了，但如果主线程退出，那么进程也会退出，所有的线程也都会退出，并不是说detach后线程就真的常驻了，只是跟主线程脱离控制关系了。\n锁(资源竞争) 互斥量\n#include\u0026lt;mutex\u0026gt; std::mutex s1; s1.lock(); //加锁 doSomething();//处理 s1.unlock();//解锁 最简单的可以通过互斥量来解决问题。只有锁成功的线程 才能对共享资源做处理 否则就要等待 //如果不想显示的unlock的 或者担心忘记写unlock也可以用std::lock_guard代为管理 { std::mutex s1; std::lock_guard\u0026lt;std::mutex\u0026gt; lg(s1); //加锁 会在构造函数内部 调用s1的lock函数 //doSomething();//处理 }//退出这个大括号 lg对象会析构，析构的时候会调用s1的unlock函数 死锁问题\n//死锁测试 class DeadLock{ public: void func1(){ for (int i=0;i\u0026lt;10000;++i){ m1.lock();// m2.lock(); cout\u0026lt;\u0026lt;\u0026#34;this is func1\u0026#34;\u0026lt;\u0026lt;endl; m2.unlock(); m1.unlock(); } } void func2(){ for (int i=0;i\u0026lt;10000;++i){ m2.lock(); m1.lock(); cout\u0026lt;\u0026lt;\u0026#34;this is func2\u0026#34;\u0026lt;\u0026lt;endl; m1.unlock(); m2.unlock(); } } private: std::mutex m1,m2; }; int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; DeadLock deadLockTest; thread t1(\u0026amp;DeadLock::func1,\u0026amp;deadLockTest); thread t2(\u0026amp;DeadLock::func2,\u0026amp;deadLockTest); t1.join(); t2.join(); cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } //----------大致死锁过程---------- //t1线程 执行func1函数 先锁了m1 准备去锁m2 //然后cpu切换到t2线程 //t2线程 先锁了m2 准备去锁m1 发现m1被锁了 等待 //cpu切换到t1线程 去锁m2 发现m2被锁了，等待 //2个线程一直等待下去 //不同线程之间加锁解锁顺序应该一致 避免死锁 std::lock() 一次锁多个互斥量 要么全部成功 要么全部失败 有原子性\nstd::mutex m1,m2; std::lock(m1,m2);//这里m1 m2要么同时锁成功 要么都不加锁 doSomething();// m1.lock(); m2.lock(); std::lock_guardstd::mutex l1(m1,std::adopt_lock)\n加上adopt_lock标记，必须m1在之前已经被锁了 就不再锁了，但析构l1的时候还是会尝试unlock m1\nstd::unique_lock\u0026lt;\u0026gt;模版类\nstd::unique_lock\u0026lt;std::mutex\u0026gt; ul1(m2,std::try_to_lock);//尝试去拿锁 不会阻塞 if( ul1.owns_lock){ //加锁处理 }else{ //做其它事情 } std::unique_lock\u0026lt;std::mutex\u0026gt; ul1(m2,std::defer_lock);//没有加锁 ul1.lock(); //共享资源处理 ul1.unlock();//暂时解锁 //处理非共享资源 ul1.lock(); //处理共享资源 //ul1析构的时候 还是会unlock unique_lock相对更加灵活 但性能上也会有所损失 std::unique_lock\u0026lt;std::mutex\u0026gt; ul1(m2,std::defer_lock);//没有加锁 只是跟m2这个互斥量关联 if (ul1.try_lock()==true){//不会阻塞 //处理共享资源或者共享代码段 }else{ //没有加锁成功 做些别的事情 } std::unique_lock\u0026lt;std::mutex\u0026gt; ul2(std::move(ul1)); //转移所有权 相当于un1.release ul2根m2互斥量绑定 //ul1.release()//会放弃跟关联的互斥量之间的关系 那么就需要自己unlock了 加锁的粒度要控制得当，因为加锁会影响效率，锁的东西必须是涉及资源竞争的东西，不要在lock()和unlock()之间加太多不涉及共享的代码，会影响效率\nstd::recursive_mutex 递归的独占互斥量 可以lock多次 效率低\nstd::timed_mutex 抢锁 只不过有超时时间\nstd::timed_mutex timedMutex; std::chrono::milliseconds timeout(100); if( timedMutex.try_lock_for(timeout)){//等待100毫秒 如果抢到了锁就执行 //共享资源处理 timedMutex.unlock(); }else{ std::this_thread::sleep_for(timeout);//抢不到就超时 然后做些别的事情 } timedMutex.try_lock_until(futureTime);/到未来的时间如果还没抢到锁 则超时 否则继续处理 条件变量 ​ 是c++11的一个新的类，如果线程之间需要按照预定的先后顺序来执行，就可以使用条件变量这个类了。\n假设一个线程生产数据，一个线程消费数据，如果没有数据，消费线程一直加锁解锁 非常消耗cpu资源，所以希望加锁后发现没有数据就等着，然后希望生产线程生产了数据就唤醒消费线程起来干活。比较常见的代码如下：\n消费线程\nstd::mutex myMutex; std::condition_variable cv; vector\u0026lt;int\u0026gt; myVec; ///--------------以下为消费线程代码逻辑 while(true){ std::unique_lock\u0026lt;std::mutex\u0026gt; uniquelock1(myMutex); cv.wait(uniquelock1,[](){ return myVec.size()\u0026gt;0;} ); //加锁后如果没有数据 wait就释放锁 同时消费线程则阻塞在这里等待 //如果生产线程 唤醒了它，wait这里还要加锁 同时判断队列是否有数据，有数据并且加锁成功了 就会执行下面的消费逻辑 //------------消费数据--------------- } 生产线程\nstd::mutex myMutex; std::condition_variable cv; vector\u0026lt;int\u0026gt; myVec; ///--------------以下为生产线程代码逻辑 while(true){ std::unique_lock\u0026lt;std::mutex\u0026gt; uniquelock1(myMutex); //--------生产数据-------------- myVec.push_back(\u0026#34;xxx\u0026#34;); //-------生产完毕 先解锁 uniquelock1.unlock(); //-----然后唤醒消费线程干活 cv.notify_all();//cv.notify_one();//唤醒因为cv条件不满足等待的所有线程或者某一个线程 } Std::async与std::future std::async创建一个后台线程执行传递的任务，这个任务只要是callable object均可，然后返回一个std::future。future储存一个多线程共享的状态,当调用future.get时会阻塞直到绑定的task执行完毕\n创建async的时候指定一个launch policy\nstd::launch::async当返回的future失效前会强制执行task，即不调用future.get也会保证task的执行 创建新线程 std::launch::deferred仅当调用future.get时才会执行task 不创建新线程 只是延迟执行 不指定策略，系统随机。 Wait_for可以指定时间 然后获取状态(ready 成功执行完毕、timeout超时、deferred延期的尚未执行的) 只能get一次 如果想get多次 可以std::shared_future resultShare(result.share());// 这种方式可以很方便的获取线程的计算结果。\nbool printThead(){ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(5);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; return true; } int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; std::future\u0026lt;bool\u0026gt; result=std::async(printThead); //std::future\u0026lt;bool\u0026gt; result=std::async(std::launch::deferred,printThead);//如果换成这行 主线程不等子线程 直接退出，子线程根本没有被执行 必须调用future的wait或者get才会被执行 cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl;//执行到这里主线程不会退出 printThead线程会被强制执行完成 默认策略是std::launch::async } -----输出----- 主线程id:0x113812dc0 is running 主线程id:0x113812dc0 is ready to exit 线程id:0x700006f48000 is running ...sleeping 线程id:0x700006f48000 is ready to exit ////main函数可以改为如下的： int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; std::future\u0026lt;bool\u0026gt; result=std::async(std::launch::deferred,printThead); cout\u0026lt;\u0026lt;\u0026#34;调用future的get方法 开始启动printThread线程执行 同时主线程阻塞等待\u0026#34;\u0026lt;\u0026lt;endl; cout\u0026lt;\u0026lt;\u0026#34;printThread线程执行结果为:\u0026#34;\u0026lt;\u0026lt;result.get()\u0026lt;\u0026lt;endl;//调用get方法 或者wait方法 主线程会阻塞等待 可以控制主线程执行时机 cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } std::packaged_task bool printThead(){ cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; sleep(5);//休眠一秒钟 cout\u0026lt;\u0026lt;\u0026#34;线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; return true; } int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; std::packaged_task\u0026lt;bool()\u0026gt; myThreadPackage(printThead);//打包一个线程执行入口函数 thread t1(std::ref(myThreadPackage));//用打包好的入口函数 启动一个线程 t1.join(); // t1.detach();//detach也没有 调用了get就会阻塞等待的 std::future\u0026lt;bool\u0026gt; result=myThreadPackage.get_future();//得到返回结果 cout\u0026lt;\u0026lt;\u0026#34;printThread线程执行结果为:\u0026#34;\u0026lt;\u0026lt;result.get()\u0026lt;\u0026lt;endl; cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } 也可以包装任何可执行对象 下面是lambda表达式\nstd::packaged_task\u0026lt;bool()\u0026gt; myThreadPackage( [](){ ....... return true; } ); std::packaged_task也可以直接调用 相当于函数调用 而不是启动线程\nStd::promise 类模版 保存线程计算结果 void promiseTest(std::promise\u0026lt;int\u0026gt; \u0026amp;result,int num){ cout\u0026lt;\u0026lt;\u0026#34;线程promiseTest id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; num+=5; sleep(3); result.set_value(num); cout\u0026lt;\u0026lt;\u0026#34;线程promiseTest id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit \u0026#34;\u0026lt;\u0026lt;endl; } int main(){ cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is running\u0026#34;\u0026lt;\u0026lt;endl; std::promise\u0026lt;int\u0026gt; myResult; thread t1(promiseTest,std::ref(myResult),999); t1.join(); std::future\u0026lt;int\u0026gt; result=myResult.get_future(); cout\u0026lt;\u0026lt;\u0026#34;线程运算结果为:\u0026#34;\u0026lt;\u0026lt;result.get()\u0026lt;\u0026lt;endl; cout\u0026lt;\u0026lt;\u0026#34; 主线程id:\u0026#34;\u0026lt;\u0026lt;std::this_thread::get_id()\u0026lt;\u0026lt;\u0026#34; is ready to exit\u0026#34;\u0026lt;\u0026lt;endl; } -----输出结果------- 主线程id:0x107f2bdc0 is running 线程promiseTest id:0x70000a5e8000 is running 线程promiseTest id:0x70000a5e8000 is ready to exit 线程运算结果为:1004 主线程id:0x107f2bdc0 is ready to exit 原子操作std::atomic int count=0; void myTest(){ for (int i=0;i\u0026lt;100000;++i){ count++; } } int main(){ thread t1(myTest); thread t2(myTest); t1.join(); t2.join(); } //count++执行的时候是有多条执行的，又因为cpu缓存的不可见性，多核cpu可能出现最后不是200000的情况，也就是count++变成了不是原子操作，所谓原子操作就是这个操作要么因此全部执行完毕，要么一个也别执行。 //c++11增加了auomic模版 支持变量的原子操作,解决上述问题可以加互斥锁，但效率不高，可以使用原子操作类模版。 std::atomic\u0026lt;int\u0026gt; count=0;就不会出现问题了 但不是所有的操作都支持原子操作，比如count++没问题 但是 count=count+1就不支持了，使用时候要查看api说明 原子操作的赋值需要使用API特定的方法 Std::thread和std::async区别 thread创建线程 如果资源紧张，可能创建失败，可能系统崩溃 Std::async 可能还是在主线程进行的，更方便的 thread_local ​\tthread_local定义的变量会在每个线程保存一份副本，而且互不干扰，在线程退出的时候自动摧毁\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;thread\u0026gt; #include \u0026lt;chrono\u0026gt; thread_local int g_k = 0; void func1(){ while (true){ ++g_k; } } void func2(){ while (true){ std::cout \u0026lt;\u0026lt; \u0026#34;func2 thread ID is : \u0026#34; \u0026lt;\u0026lt; std::this_thread::get_id() \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; \u0026#34;func2 g_k = \u0026#34; \u0026lt;\u0026lt; g_k \u0026lt;\u0026lt; std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } int main(){ std::thread t1(func1); std::thread t2(func2); t1.join(); t2.join(); return 0; } ----在func1()对g_k循环加1操作，在func2()每个1000毫秒输出一次g_k的值----- func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 func2 g_k = 0 func2 thread ID is : 15312 .....可以看出func2()中的g_k始终保持不变 经典IO模型实现 PPC、TPC Process Per Connection、Process Per Connection 整体思路总结及网搜图解 连接来时创建线程 主进程/主线程负责创建监听socket、启动监听\n对每一个新的client 创建对应的处理进程/线程(完成read\u0026mdash;\u0026gt;business process\u0026mdash;\u0026gt;send)\n多进程需要close掉connfd，多线程不需要 预先创建号处理进程或者线程 提前创建好多个处理进程/线程\n连接进来分配到预先创建好的处理进程/线程\n注意accept惊群(TCP_IP文章有讲解)，解决方案(自己加accept_mutex锁、新一点的内核直接设置SO_REUSEPORT选项也是可以的内核会随机分配、\u0026hellip;.) 总结 优点：开发简单 缺点: 能给管理的连接数太少了，因为1个进程/线程 管理一个client套接字，而单机硬件条件限制(cpu、内存)，所以同时管理的任务进程和线程是有限了(进程上下文切换太重，线程上千级别往往调度也是非常消耗时间了)，所以想要高并发，ppc和tpc往往直接可以pass了。 Reactor思想 每个进程/线程通过IO多路复用技术，实现一个进程管理多个连接(共用一个阻塞对象 比如epollfd),应用只阻塞在epollfd上。当连接有数据可以处理的时候，通过系统调用让内核通知应用，然后进程/线程就从阻塞状态返回，进行业务处理。 reactor处理思想大多也会有一个分发的思想存在(Dispatch),或则叫做拆分，也就将listen、accept、(send read)、business process分开，连接有数据可以处理，就分发给对应的handler处理，当然handler可以再拆出worker线程池等等。 往设计模式的Observe模式上理解，觉得也可以，当消息来了，就通知对应的进程/线程来处理(当然处理可以再次拆分) 讨论一些具体reactor模式实现的时候 里面的reactor一般是指负责监听和分发任务的角色 单Reactor+单线程处理(整体就一个线程)\u0026mdash;redis redis就是一个典型的代表 下面是redis这个io模型的简单理解 整体思路： 线程创建socket启动监听 注册epoll epoll_wait等待客户端连接或者请求数据，拿到所有可以处理的fd，然后顺序遍历处理即可 如果是accept事件 就也注册到epoll 如果是connfd的请求 就读取请求\u0026mdash;\u0026gt;处理\u0026mdash;\u0026gt;回包 总结 优点：\n模型简单，因为就一个线程 不用考虑并发 易开发，前面IO复用里面也有epoll的例子就是这个套路 适合短耗时业务(主业务处理逻辑不能太耗时，否则必然影响新连接的建议或者其他客户端的处理等待) 缺点：\n性能会有瓶颈的 只有一个线程，无法利用多核cpu优势 因为是顺序处理请求，遇到长耗时的客户端请求，会延迟所有业务，这也是redis禁用耗时命令的原因 redis因为处理全在内存，单个请求处理非常快，所以适合这么处理，redis据说可以有10W的QPS吞吐量\n单Reactor+单队列+业务进程/线程池\u0026mdash;thrift0.10.0 nonblocking server 整体思路 主线程启动套接字监听 注册epoll ，有新的客户端连接就也注册到epoll\n主线程epoll_wait 监控请求数据到达\n主线程从connfd客户端套接字read请求数据 入请求队列\n子线程从队列取出请求，进行business process，处理完成应答报文还是交由主线程send给client(clientfd在主线程hold)\n客户端套接字可以想象成一个队列 reactor将这个队列通过IO多路复用转换成了真正的业务请求数据队列。 业务线程池线程池，从队列中拿到数据进行真正的业务处理，将结果返回Handler。Handler收到响应结果后，send结果给客户端 thrift0.10.0版本中 nonblocking server 使用这种模型 总结 优点： 多线程加快了真正业务数据处理的速度，这个是很有必要的，因为业务处理大都是比较耗时的(跟数据库打交道，远程RPC等等) 可能出现的缺点： 性能瓶颈很大可能出现在请求队列，因为主线程写和worker线程读是线程并发的，所以需要锁(多进程一样)，要采用高性能的读写锁 单 Reactor+N队列+N线程\u0026mdash;memcached 整体思路 主线程创建socket监听，同时accept，将accept的connfd采用Robin轮询的方式，扔给worker线程的队列 每个worker线程 采用IO多路复用技术从自己的队列中取出connfd，然后read\u0026mdash;\u0026gt;business process\u0026ndash;\u0026gt;send 每个worker线程的队列存放的是connfd，我理解这种其实也算是主从Reactor模式(master Reactor只accept，然后扔给多个subReactor) 总结 优点：解决单队列的性能瓶颈，而且这里的多队列里面放的connfd，不存在上面多个线程同时读一个队列的锁竞争性能消耗 缺点：负载均衡可能导致有些队列很忙，有些队列很闲。比如队列1里面放的客户端，发送请求比较频繁，队列2里面放的客户端发送请求比较空闲。 主进程管理+多进程(accept+epoll_wait+处理)\u0026ndash;nginx 整体思路 主进程先创建socket 启动监听，然后fork出一堆worker进程（ 主进程不accept 只是管理多个worker进程 比如动态增加worker之类的） 每个worker进程 同时accept客户端连接(accept_mutex锁解决惊群问题)，也就是每个worker自己抢客户，同时处理自己抢到的多个客户(epoll_wait) worker进程根据自身负载情况，选择性地不去accept新fd，从而实现负载均衡 总结 优点： 某个worker进程挂掉不会影响整个服务 是由worker主动实现负载均衡的，这种负载均衡方式比由master来处理更简单 缺点： 多进程模型编程相对复杂，开发有一定难度 进程的开销是比线程更多的 主从多reactor多线程处理 整体思路： 主线程创建socket 启动监听，启动accept线程池(可以是1个或者多个) 这个线程池只负责accept客户端连接 每个accept线程 接收客户端连接之后 将connfd传给subReactor线程池中的某个线程 subReactor线程池处理思路: 每个subReactor线程都会处理用epoll的方式监控多个connfd 负责处理read 和write事件 业务处理转发给worker线程池 worker线程池处理： 接受请求处理事件 进行业务处理(计算、数据库处理之类的) 处理完成交给subReactor线程回报给client 总结： 这种模式理论上是性能最完美的模型，相对就是开发难度相对较大，可以使用一些现成的框架降低开发难度(Java.Netty、c++改造libevent等等)\nunix编程API 转载 1.字节序函数 #include \u0026lt;netinet.h\u0026gt; uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t host32bitvalue); 返回：网络字节序值\nuint16_t ntohs(uint16_t net16bitvalue); uint32_t ntohl(uint32_t net32bitvalue); 返回：主机字节序值\n一个测试本机字节序的程序，可参见见unpv12e：intro/byteorder.c。\n2.字节操作函数 #include \u0026lt;strings.h\u0026gt; void bzero(void *dest, size_t nbytes); void bcopy(const void *src, void *dest, size_t nbytes); int bcmp(const void *ptr1, const void *ptr2, size_t nbytes); 返回：0—相等，非0—不相等\n#include \u0026lt;string.h\u0026gt; void *memset(void *dest, int c, size_t len); void *memcpy(void *dest, void *src, size_t nbytes); int memcmp(const void *ptr1, const void *ptr2, size_t nbytes); 返回：0—相同，\u0026gt;0或\u0026lt;0—不相同；进行比较操作时，假定两个不相等的字节均为无符号字符（unsigned char）。\n3.地址转换函数 #include \u0026lt;arpa/inet.h\u0026gt; int inet_aton(const char *strptr, struct in_addr *addrptr); 返回：1—串有效，0—串有错。\nin_addr_t inet_addr(const char *strptr); 返回：若成功，返回32为二进制的网络字节序地址；若有错，则返回INADDR_NONE。\nchar *inet_ntoa(struct in_addr inaddr); 返回：指向点分十进制数串的指针。\nint inet_pton(int family, const char *strptr, void *addrptr); 返回：1—成功；0—输入不是有效的表达格式，-1—出错。\nconst char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); 返回：指向结果的指针—成功，NULL—失败。\n说明：\ninet_aton函数的指针若为空，则函数仍然执行输入串的有效性检查，但不存储任何结果。 inet_addr的缺陷：出错返回值INADDR_NONE等于255.255.255.255（IPv4的有限广播地址），所以该函数不能处理此地址。 尽量使用inet_aton，不使用inet_addr。 inet_ntoa函数的执行结果放在静态内存中，是不可重入的。 参数family可以是AF_INET，也可以是AF_INET6，若参数family不被支持，则出错，errno置为EAFNOSUPPORT。 指针addrptr是结构指针。 len指定目标的大小，避免缓冲区溢出。如果len太小，则返回一个空指针，errno置为ENOSPC。为有助于规定该大小，有如下定义： #include \u0026lt;netinet.h\u0026gt; #define INET_ADDRSTRLEN 16 /*fro IPv4 dotted-decimal */ #define INET6_ADDRSTRLEN 46 /*for IPv6 hex string */ inet_ntop函数的参数strptr不能为空指针，成功时，此指针即是函数的返回值。 实现IPv4版本的inet_pton和inet_ntop的程序，参见：unpv12e：libfree/inet_pton_ipv4.c和libfree/inet_ntop_ipv4.c。\n4.readn、writen和readline 函数原型如下： ssize_t readn(int filedes, void *buff, size_t nbytes); ssize-t writen(int filedes, void *buff, size_t nbytes); ssize_t readline(int filedes, void *buff, size_t maxlen); 返回：读写字节数，-1—出错。\n实现程序见：unpv12e：lib/readn.c、lib/writen.c、lib/readline1.c和lib/readline.c。\n5.测试描述符类型 #include \u0026lt;sys/stat.h\u0026gt; int isfdtype( int fd, int fdtype); 返回：1—是指定类型，0—不是指定类型，-1—出错。\n要测试是否为套接口描述子，fdtype应设为S_IFSOCK。\n该函数的一个实现程序，参见unpv12e：lib/isfdtype.c\n6.socket函数 #include \u0026lt;sys/socket.h\u0026gt; int socket(int family, int type, int protocol); 返回：非负描述字—成功，-1—出错。\nfamily指定协议族，有如下取值：\nAF_INET IPv4协议 AF_INET6 IPv6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接口 AF_KEY 密钥套接口 type指定套接口类型：\nSOCK_STREAM 字节流套接口 SOCK_DGRAM 数据报套接口 SOCK_RAW 原始套接口 protocol一般设为0，除非用在原始套接口上。\n并非所有family和type的组合都是有效的。\nAF_LOCAL等于早期的AF_UNIX。\n7.connect函数 #include \u0026lt;sys/socket.h\u0026gt; int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); 返回：0—成功，-1—出错。\nsockfd是socket函数返回的套接口描述字，servaddr和addrlen是指向服务器的套接口地址结构指针和结构大小。\n在调用connect之前不必非得调用bind函数。\n如果是TCP，则connect激发TCP的三路握手过程，在阻塞情况下，只有在连接建立成功或出错时该函数才返回， 出错情况：\n没有收到SYN分节的响应，在规定时间内经过重发仍无效，则返回ETIMEDOUT； 如果对SYN分节的响应是RST，表示服务器在指定端口上没有相应的服务，返回ECONNREFUSED； 如果发出 SYN在中间路由器上引发一个目的地不可达ICMP错误，在规定时间内经过重发仍无效，则返回EHOSTUNREACH或ENETUNREACH错误。 注意：如果connect失败，则套接口将不能再使用，必须关闭，不能对此套接口再调用函数connect。\n8.bind函数 #include \u0026lt;sys/socket.h\u0026gt; int bind(int sockfd, const struct sockaddr *maddr, socklen_t addrlen); 返回：0—成功，-1—出错。\n进程可以把一个特定的IP地址捆绑到他的套接口上，但此IP地址必须是主机的一个接口。\n对于IPv4，通配地址是INADDR_ANY，其值一般为0；使用方法如下： struct sockaddr_in servaddr; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);\n对于IPv6，方法如下： struct sockaddr_in6 serv; serv.sin6_addr = in6addr_any; （系统分配变量in6addr_any并将其初始化为常值IN6ADDR_ANY_INIT。）\n如果让内核选择临时端口，注意的是bind并不返回所选的断口值，要得到一个端口，必须使用getsockname函数。\nbind失败的常见错误是EADDRINUSE（地址已使用）。\n9.listen函数 #include \u0026lt;sys/socket.h\u0026gt; int listen(int sockfd, int backlog); 返回：0—成功，-1—出错。\nlisten把未连接的套接口转化为被动套接口，指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。\n参数backlog曾经规定为监听套接口上的未完成连接队列和已完成连接队列总和的最大值，但各个系统的定义方法都不尽相同；历史上常把backlog置为5，但对于繁忙的服务器是不够的；backlog的设置没有一个通用的方法，依情况而定，但不要设为0。\n10.accept函数 #include \u0026lt;sys/socket.h\u0026gt; int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 返回：非负描述字—OK，-1—出错。\naccept从已完成连接队列头返回下一个连接，若已完成连接队列为空，则进程睡眠（套接口为阻塞方式时）。\n参数cliaddr和addrlen返回连接对方的协议地址，其中addrlen是值-结果参数，调用前addrlen所指的整数值要置为cliaddr所指的套接口结构的长度，返回时由内核修改。\naccept成功执行后，返回一个连接套接口描述字。\n如果对客户的协议地址没有兴趣，可以把cliaddr和addrlen置为空指针。\n11.close函数 #include \u0026lt;unistd.h\u0026gt; int close(int sockfd); 返回：0—OK，-1—出错。\nTCP套接口的close缺省功能是将套接口做上“已关闭”标记，并立即返回到进程。这个套接口描述字不能再为进程使用，但TCP将试着发送已排队待发的任何数据，然后按正常的TCP连接终止序列进行操作。\nclose把描述字的访问计数减1，当访问计数仍大于0时，close并不会引发TCP的四分组连接终止序列。若确实要发一个FIN，可以用函数shutdown。\n12.getsockname和getpeername #include \u0026lt;sys/socket.h\u0026gt; int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen); int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen); 返回：0—OK，-1—出错。\ngetsockname函数返回与套接口关联的本地协议地址。\ngetpeername函数返回与套接口关联的远程协议地址。\naddrlen是值-结果参数。\n使用场合：\n在不调用bind的TCP客户，当connect成功返回后，getsockname返回分配给此连接的本地IP地址和本地端口号； 在以端口号为0调用bind后，使用getsockname返回内核分配的本地端口号； getsockname可用来获取某套接口的地址族； 在捆绑了通配IP地址的TCP服务器上，当连接建立后，可以使用getsockname获得分配给此连接的本地IP地址； 当一个服务器调用exec启动后，他获得客户身份的唯一途径是调用getpeername函数。 13.select函数 #include \u0026lt;sys/select.h\u0026gt; #include \u0026lt;sys/time.h\u0026gt; int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); 返回：准备好描述字的正数目，0—超时，-1—出错。\n结构timeval的定义： struct timeval { long tv_sec; /* seconds / long tv_usec; / microseconds */ };\ntimeout取值的三种情况：\n永远等下去：仅在有一个描述字准备好I/O时才返回，设置timeout为空指针； 等待固定时间：在有一个描述字准备好I/O时返回，但不超过由timeout参数所指定的秒数和微秒数； 根本不等待：检查描述字后立即返回，将timeout中的秒数和微秒数都设置为0。 在等待过程中，若进程捕获了信号并从信号处理程序返回，等待一般被中断，为了可移植性，必须准备好select返回EINTR错误。\ntimeout的值在返回时并不会被select修改（const标志）。\nreadset、writeset、exceptset指定我们要让内核测试读、写和异常条件所需的描述字。\n当前支持的异常条件有两个：\n套接口带外数据的到达； 控制状态信息的存在，可从一个已置为分组方式的伪终端主端读到。 描述字集的使用： 数据类型：fd_set； void FD_ZERO(fd_set *fdset); void FD_SET(int fd, fd_set *fdset); void FD_CLR(int fd, fd_set *fdset); void FD_ISSET(int fd, fd_set *fdset);\n参数maxfdp1指定被测试的描述字个数，它的值是要被测试的最大描述字加1。描述字0，1，2，…，maxfdp1-1都被测试。\nreadset、writeset、exceptset是值-结果参数，select修改三者所指的描述字集。所以，每次调用select时，我们都要将所有描述字集中关心的位置为1。\n套接口准备好读的条件：\n套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度的当前值。对这样的套接口的读操作将不阻塞并返回一个大于0的值（即准备好读入的数据量）。可以用套接口选项SO_RCVLOWAT来设置低潮限度，对于TCP和UDP，缺省值为1； 连接的读这一半关闭（接收了FIN的TCP连接）。对这样的套接口读操作将不阻塞并且返回0（即文件结束符）； 套接口是一个监听套接口且已完成的连接数为非0； 有一个套接口错误待处理。对这样的套接口读操作将不阻塞且返回一个错误，errno设置成明确的错误条件。这些待处理错误也可以通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。 套接口准备好写的条件：\n套接口发送缓冲区中的可用字节数大于等于套接口发送缓冲区低潮限度的当前值，且或者（1）套接口已连接，或者（2）套接口不要求连接（如UDP套接口）。可以用套接口选项SO_SNDLOWAT来设置此低潮限度，对于TCP和UDP，缺省值为2048； 连接的写这一半关闭。对这样的套接口写将产生信号SIGPIPE； 有一个套接口错误待处理。对这样的套接口写操作将不阻塞且返回一个错误，errno设置成明确的错误条件。这些待处理错误也可以通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。 如果一个套接口存在带外数据或者仍处于带外标记，那它有异常条件待处理。\n一个套接口出错时，它被select标记为既可读又可写。\n14.shutdown函数 #include \u0026lt;sys/socket.h\u0026gt; int shutdown(int sockfd, int howto); 返回：0—成功，-1—失败。\n函数的行为依赖于参数howto的值：\nSHUT_RD：关闭连接的读这一半，不再接收套接口中的数据且留在套接口缓冲区中的数据都作废。进程不能再对套接口任何读函数。调用此函数后，由TCP套接口接收的任何数据都被确认，但数据本身被扔掉。 SHUT_WR：关闭连接的写这一半，在TCP场合下，这称为半关闭。当前留在套接口发送缓冲区中的数据都被发送，后跟正常的TCP连接终止序列。此半关闭不管套接口描述字的访问计数是否大于0。进程不能再执行对套接口的任何写函数。 SHUT_RDWR：连接的读这一半和写这一半都关闭。这等效于调用shutdown两次：第一次调用时用SHUT_RD，第二次调用时用SHUT_WR。\n15.pselect函数 #include \u0026lt;sys/select.h\u0026gt; #include \u0026lt;signal.h\u0026gt; #include \u0026lt;time.h\u0026gt; int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask); 返回：准备好描述字的个数，0—超时，-1—出错。\npselect是Posix.1g发明的。相对select的变化：\npselect使用结构timespec： struct timespec { time_t tv_sec; /* seconds / long tv_nsec; / nanoseconds */ }; 新结构中的tv_nsec规定纳秒数。 pselect增加了第六个参数：指向信号掩码的指针。允许程序禁止递交某些信号。 16.poll函数 #include \u0026lt;poll.h\u0026gt; int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); 返回：准备好描述字的个数，0—超时，-1—出错。\n第一个参数是指向一个结构数组的第一个元素的指针，每个数组元素都是一个pollfd结构： struct pollfd { int fd; /* descriptor to check / short events; / events of interest on fd / short revents; / events that occurred on fd */ };\n要测试的条件由成员events规定，函数在相应的revents成员中返回描述字的状态（一个描述字有两个变量：一个为调用值，一个为结果）。\n第二个参数指定数组中元素的个数。\n第三个参数timeout指定函数返回前等待多长时间，单位是毫秒。可能值如下：\nINFTIM，永远等待； 0，立即返回，不阻塞； \u0026gt;0，等待指定数目的毫秒数。 标志的范围：\n常量 能作为events的输入吗？ 能作为revents的结果吗？ 解释 POLLIN yes yes 普通或优先级带数据可读 POLLRDNORM yes yes 普通数据可读 POLLRDBAND yes yes 优先级带数据可读 POLLPRI yes yes 高优先级数据可读 POLLOUT yes yes 普通或优先级带数据可写 POLLWRNORM yes yes 普通数据可写 POLLWRBAND yes yes 优先级带数据可写 POLLERR yes 发生错误 POLLHUP yes 发生挂起 POLLNVAL yes 描述字不是一个打开的文件 图可分为三部分：处理输入的四个常值；处理输出的三个常值；处理错误的三个常值。\npoll识别三个类别的数据：普通（normal）、优先级带（priority band）、高优先级（high priority）。术语来自流的概念。\n返回条件：\n所有正规TCP数据和UDP数据都被认为是普通数据； TCP的带外数据被认为是优先级带数据； 当TCP连接的读这一半关闭时（如接收了一个FIN），这也认为是普通数据，且后续的读操作将返回0； TCP连接存在错误既可以认为是普通数据，也可以认为是错误（POLLERR）。无论哪种情况，后续的读操作将返回-1，并将errno置为适当的值，这就处理了诸如接收到RST或超时等条件； 在监听套接口上新连接的可用性既可认为是普通数据，也可以认为是优先级带数据，大多数实现都将其作为普通数据考虑。 如果不关心某个特定的描述字，可将其pollfd结构的fd成员置为一个负值，这样就可以忽略成员events，且返回时将成员revents的值置为0。 poll没有select存在的最大描述字数目问题。但可移植性select要好于poll。\n17.getsockopt和setsockopt #include \u0026lt;sys/socket.h\u0026gt; int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); 返回：0—OK，-1—出错。\nsockfd必须是一个打开的套接口描述字；level（级别）指定系统中解释选项的代码：普通套接口代码或特定于协议的代码）；optval是一个指向变量的指针；此变量的大小由最后一个参数决定。\n对于某些套接口选项，什么时候进行设置或获取是有差别的。下面的套接口选项是由TCP已连接套接口从监听套接口继承来的：\nSO_DEBUG； SO_DONTROUTE； SO_KEEPALIVE； SO_LINGER； SO_OOBINLINE； SO_RCVBUF； SO_SNDBUF。 如果想在三路握手完成时确保这些套接口选项中的某一个是给已连接套接口设置的，我们必须先给监听套接口设置此选项。\n18.套接口选项列表 level Optname get set 说明 标志 数据类型 SOL_SOCKET SO_BROADCAST y y 允许发送广播数据报 y int SO_DEBUG y y 使能调试跟踪 y int SO_DONTROUTE y y 旁路路由表查询 y int SO_ERROR y 获取待处理错误并消除 int SO_KEEPALIVE y y 周期性测试连接是否存活 y int SO_LINGER y y 若有数据待发送则延迟关闭 linger{} SO_OOBINLINE y y 让接收到的带外数据继续在线存放 y int SO_RCVBUF y y 接收缓冲区大小 int SO_SNDBUF y y 发送缓冲区大小 int SO_RCVLOWAT y y 接收缓冲区低潮限度 int SO_SNDLOWAT y y 发送缓冲区低潮限度 int SO_RCVTIMEO y y 接收超时 timeval{} SO_SNDTIMEO y y 发送超时 timeval{} SO_REUSEADDR y y 允许重用本地地址 y int SO_REUSEPORT y y 允许重用本地地址 y int SO_TYPE y 取得套接口类型 int SO_USELOOPBACK y y 路由套接口取得所发送数据的拷贝 y int IPPROTO_IP IP_HDRINCL y y IP头部包括数据 y int IP_OPTIONS y y IP头部选项 见后面说明 IP_RECVDSTADDR y y 返回目的IP地址 y int IP_RECVIF y y 返回接收到的接口索引 y int IP_TOS y y 服务类型和优先权 int IP_TTL y y 存活时间 int IP_MULTICAST_IF y y 指定外出接口 in_addr{} IP_MULTICAST_TTL y y 指定外出TTL u_char IP_MULTICAST_LOOP y y 指定是否回馈 u_char IP_ADD_MEMBERSHIP y 加入多播组 ip_mreq{} IP_DROP_MEMBERSHIP y 离开多播组 ip_mreq{} IPPROTO_ICMPV6 ICMP6_FILTER y y 指定传递的ICMPv6消息类型 icmp6_filter{} IPPROTO_IPV6 IPV6_ADDRFORM y y 改变套接口的地址结构 int IPV6_CHECKSUM y y 原始套接口的校验和字段偏移 int IPV6_DSTOPTS y y 接收目标选项 y int IPV6_HOPLIMIT y y 接收单播跳限 y int IPV6_HOPOPTS y y 接收步跳选项 y int IPV6_NEXTHOP y y 指定下一跳地址 y sockaddr{} IPV6_PKTINFO y y 接收分组信息 y int IPV6_PKTOPTIONS y y 指定分组选项 见后面说明 IPV6_RTHDR y y 接收原路径 y int IPV6_UNICAST_HOPS y y 缺省单播跳限 int IPV6_MULTICAST_IF y y 指定外出接口 in6_addr{} IPV6_MULTICAST_HOPS y y 指定外出跳限 u_int IPV6_MULTICAST_LOOP y y 指定是否回馈 y u_int IPV6_ADD_MEMBERSHIP y 加入多播组 ipv6_mreq{} IPV6_DROP_MEMBERSHIP y 离开多播组 ipv6_mreq{} IPPROTO_TCP TCP_KEEPALIVE y y 控测对方是否存活前连接闲置秒数 int TCP_MAXRT y y TCP最大重传时间 int TCP_MAXSEG y y TCP最大分节大小 int TCP_NODELAY y y 禁止Nagle算法 y int TCP_STDURG y y 紧急指针的解释 y int 详细说明：\nSO_BROADCAST 使能或禁止进程发送广播消息的能力。只有数据报套接口支持广播，并且还必须在支持广播消息的网络上（如以太网、令牌环网等）。\n如果目的地址是广播地址但此选项未设，则返回EACCES错误。\nSO_DEBUG 仅仅TCP支持。当打开此选项时，内核对TCP在此套接口所发送和接收的所有分组跟踪详细信息。这些信息保存在内核的环形缓冲区内，可由程序trpt进行检查。\nSO_DONTROUTE 此选项规定发出的分组将旁路底层协议的正常路由机制。\n该选项经常由路由守护进程（routed和gated）用来旁路路由表（路由表不正确的情况下），强制一个分组从某个特定接口发出。\nSO_ERROR 当套接口上发生错误时，源自Berkeley的内核中的协议模块将此套接口的名为so_error的变量设为标准的UNIX Exxx值中的一个，它称为此套接口的待处理错误（pending error）。内核可立即以以下两种方式通知进程：\n如果进程阻塞于次套接口的select调用，则无论是检查可读条件还是可写条件，select都返回并设置其中一个或所有两个条件。 如果进程使用信号驱动I/O模型，则给进程或进程组生成信号SIGIO。 进程然后可以通过获取SO_ERROR套接口选项来得到so_error的值。由getsockopt返回的整数值就是此套接口的待处理错误。so_error随后由内核复位为0。\n当进程调用read且没有数据返回时，如果so_error为非0值，则read返回-1且errno设为so_error的值，接着so_error的值被复位为0。如果此套接口上有数据在排队，则read返回那些数据而不是返回错误条件。\n如果进程调用write时so_error为非0值，则write返回-1且errno设为so_error的值，随后so_error也被复位。\nSO_KEEPALIVE 打开此选项后，如果2小时内在此套接口上没有任何数据交换，TCP就会自动给对方发一个保持存活探测分节，结果如下：\n对方以期望的ACK响应，则一切正常，应用程序得不到通知； 对方以RST响应，套接口的待处理错误被置为ECONNRESET，套接口本身则被关闭； 对方对探测分节无任何响应，经过重试都没有任何响应，套接口的待处理错误被置为ETIMEOUT，套接口本身被关闭；若接收到一个ICMP错误作为某个探测分节的响应，则返回相应错误。 此选项一般由服务器使用。服务器使用它是为了检测出半开连接并终止他们。\nSO_LINGER 此选项指定函数close对面向连接的协议如何操作（如TCP）。缺省close操作是立即返回，如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。\nSO_LINGER选项用来改变此缺省设置。使用如下结构： struct linger { int l_onoff; /* 0 = off, nozero = on / int l_linger; / linger time */ };\n有下列三种情况：\nl_onoff为0，则该选项关闭，l_linger的值被忽略，等于缺省情况，close立即返回； l_onoff为非0，l_linger为0，则套接口关闭时TCP夭折连接，TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方，而不是通常的四分组终止序列，这避免了TIME_WAIT状态； l_onoff 为非0，l_linger为非0，当套接口关闭时内核将拖延一段时间（由l_linger决定）。如果套接口缓冲区中仍残留数据，进程将处于睡眠状态，直到（a）所有数据发送完且被对方确认，之后进行正常的终止序列（描述字访问计数为0）或（b）延迟时间到。此种情况下，应用程序检查close的返回值是非常重要的，如果在数据发送完并被确认前时间到，close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据（和FIN）已由对方TCP确认，它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的，它将不等待close完成。 l_linger的单位依赖于实现，4.4BSD假设其单位是时钟滴答（百分之一秒），但Posix.1g规定单位为秒。\n让客户知道服务器已经读其数据的一个方法时：调用shutdown（SHUT_WR）而不是调用close，并等待对方close连接的本地（服务器）端。\nSO_OOBINLINE 此选项打开时，带外数据将被保留在正常的输入队列中（即在线存放）。当发生这种情况时，接收函数的MSG_OOB标志不能用来读带外数据。\nSO_RCVBUF和SO_SNDBUF 每个套接口都有一个发送缓冲区和一个接收缓冲区，使用这两个套接口选项可以改变缺省缓冲区大小。\n当设置TCP套接口接收缓冲区的大小时，函数调用顺序是很重要的，因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户，SO_RCVBUF选项必须在connect之前设置；对于服务器，SO_RCVBUF选项必须在listen前设置。\nTCP套接口缓冲区的大小至少是连接的MSS的三倍，而必须是连接的MSS的偶数倍。\nSO_RCVLOWAT和SO_SNDLOWAT 每个套接口有一个接收低潮限度和一个发送低潮限度，他们由函数select使用。这两个选项可以修改他们。\n接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据量，对于一个TCP或UDP套接口，此值缺省为1。发送低潮限度是让select返回“可写”而在套接口发送缓冲区中必须有的可用空间，对于TCP套接口，此值常为2048。\nSO_RCVTIMEO和SO_SNDTIMEO 使用这两个选项可以给套接口设置一个接收和发送超时。通过设置参数的值为0秒和0微秒来禁止超时。缺省时两个超时都是禁止的。\n接收超时影响5个输入函数：read、readv、recv、recvfrom和recvmsg；发送超时影响5个输出函数：write、writev、send、sendto和sendmsg。\nSO_REUSEADDR和SO_REUSEPORT SO_REUSEADDR提供如下四个功能：\nSO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口，即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现，若不设置此选项，则bind时将出错。 SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例，只要每个实例捆绑一个不同的本地IP地址即可。对于TCP，我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。 SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上，只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。 SO_REUSEADDR允许完全重复的捆绑：当一个IP地址和端口绑定到某个套接口上时，还允许此IP地址和端口捆绑到另一个套接口上。一般来说，这个特性仅在支持多播的系统上才有，而且只对UDP套接口而言（TCP不支持多播）。 SO_REUSEPORT选项有如下语义：\n此选项允许完全重复捆绑，但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才性。 如果被捆绑的IP地址是一个多播地址，则SO_REUSEADDR和SO_REUSEPORT等效。 使用这两个套接口选项的建议：\n在所有TCP服务器中，在调用bind之前设置SO_REUSEADDR套接口选项； 当编写一个同一时刻在同一主机上可运行多次的多播应用程序时，设置SO_REUSEADDR选项，并将本组的多播地址作为本地IP地址捆绑。 SO_TYPE 该选项返回套接口的类型，返回的整数值是一个诸如SOCK_STREAM或SOCK_DGRAM这样的值。\nSO_USELOOPBACK 该选项仅用于路由域（AF_ROUTE）的套接口，它对这些套接口的缺省设置为打开（这是唯一一个缺省为打开而不是关闭的SO_xxx套接口选项）。当此套接口打开时，套接口接收在其上发送的任何数据的一个拷贝。\n禁止这些回馈拷贝的另一个方法是shutdown，第二个参数应设为SHUT_RD。\nIP_HDRINCL 如果一个原始套接口设置该选项，则我们必须为所有发送到此原始套接口上的数据报构造自己的IP头部。\nIP_OPTIONS 设置此选项允许我们在IPv4头部中设置IP选项。这要求掌握IP头部中IP选项的格式信息。\nIP_RECVDSTADDR 该选项导致所接收到的UDP数据报的目的IP地址由函数recvmsg作为辅助数据返回。\nIP_RECVIF 该选项导致所接收到的UDP数据报的接口索引由函数recvmsg作为辅助数据返回。\nIP_TOS 该选项使我们可以给TCP或UDP套接口在IP头部中设置服务类型字段。如果我们给此选项调用getsockopt，则放到外出IP数据报头部的TOS字段中的当前值将返回（缺省为0）。还没有办法从接收到的IP数据报中取此值。\n可以将TOS设置为如下的值：\nIPTOS_LOWDELAY：最小化延迟 IPTOS_THROUGHPUT：最大化吞吐量 IPTOS_RELIABILITY：最大化可靠性 IPTOS_LOWCOST：最小化成本 IP_TTL 用次选项，可以设置和获取系统用于某个给定套接口的缺省TTL值（存活时间字段）。与TOS一样，没有办法从接收到的数据报中得到此值。\nICMP6_FILTER 可获取和设置一个icmp6_filter结构，他指明256个可能的ICMPv6消息类型中哪一个传递给在原始套接口上的进程。\nIPV6_ADDRFORM 允许套接口从IPv4转换到IPv6，反之亦可。\nIPV6_CHECKSUM 指定用户数据中校验和所处位置的字节偏移。如果此值为非负，则内核将（1）给所有外出分组计算并存储校验和；（2）输入时检查所收到的分组的校验和，丢弃带有无效校验和的分组。此选项影响出ICMPv6原始套接口外的所有IPv6套接口。如果指定的值为-1（缺省值），内核在此原始套接口上将不给外出的分组计算并存储校验和，也不检查所收到的分组的校验和。\nIPV6_DSTOPTS 设置此选项指明：任何接收到的IPv6目标选项都将由recvmsg作为辅助数据返回。此选项缺省为关闭。\nIPV6_HOPLIMIT 设置此选项指明：接收到的跳限字段将由recvmsg作为辅助数据返回。\nIPV6_HOPOPTS 设置此选项指明：任何接收到的步跳选项都将由recvmsg作为辅助数据返回。\nIPV6_NEXTHOP 这不是一个套接口选项，而是一个可指定个sendmsg的辅助数据对象的类型。此对象以一个套接口地址结构指定某个数据报的下一跳地址。\nIPV6_PKTINFO 设置此选项指明：下面关于接收到的IPv6数据报的两条信息将由recvmsg作为辅助数据返回：目的IPv6地址和到达接口索引。\nIPV6_PKTOPTIONS 大多数IPv6套接口选项假设UDP套接口使用recvmsg和sendmsg所用的辅助数据在内核与应用进程间传递信息。TCP套接口使用IPV6_PKTOPTIONS来获取和存储这些值。\nIPV6_RTHDR 设置此选项指明：接收到的IPv6路由头部将由recvmsg作为辅助数据返回。\nIPV6_UNICAST_HOPS 类似于IPv4的IP_TTL，它的设置指定发送到套接口上的外出数据报的缺省跳限，而它的获取则返回内核将用于套接口的跳限值。为了从接收到的IPv6数据报中得到真实的跳限字段，要求使用IPV6_HOPLIMIT套接口选项。\nTCP_KEEPALIVE 它指定TCP开始发送保持存活探测分节前以秒为单位的连接空闲时间。缺省值至少为7200秒，即2小时。该选项仅在SO_KEEPALIVE套接口选项打开时才有效。\nTCP_MAXRT 它指定一旦TCP开始重传数据，在连接断开之前需经历的以秒为单位的时间总量。值0意味着使用系统缺省值，值-1意味着永远重传数据。\nTCP_MAXSEG 允许获取或设置TCP连接的最大分节大小（MSS）。返回值是我们的TCP发送给另一端的最大数据量，他常常就是由另一端用SYN分节通告的MSS，除非我们的TCP选择使用一个比对方通告的MSS小的值。如果此选项在套接口连接之前取得，则返回值为未从另一端收到的MSS选项的情况下所用的缺省值。\nTCP_NODELAY 如果设置，此选项禁止TCP的Nagle算法。缺省时，该算法是使能的。\nNagle算法的目的是减少WAN上小分组的数目。\nNagle算法常常与另一个TCP算法联合使用：延迟ACK（delayed ACK）算法。\n解决多次写导致Nagle算法和延迟ACK算法负面影响的方法：\n使用writev而不是多次write； 合并缓冲区，对此缓冲区使用一次write； 设置TCP_NODELAY选项，继续调用write多次，这是最不可取的解决方法。 TCP_STDURG 它影响对TCP紧急指针的解释。\n19.处理套接口的fcntl函数 #include \u0026lt;fcntl.h\u0026gt; int fcntl(int fd, int cmd, … /* arg */); 返回：依赖于参数cmd—成功，-1—失败。\n函数fcntl提供了如下关于网络编程的特性：\n非阻塞I/O：通过用F_SETFL命令设置O_NONBLOCK文件状态标志来设置套接口为非阻塞型。 信号驱动I/O：用F_SETFL命令来设置O_ASYNC文件状态标志，这导致在套接口状态发生变化时内核生成信号SIGIO。 F_SETOWN命令设置套接口属主（进程ID或进程组ID），由它来接收信号SIGIO和SIGURG。SIGIO在设置套接口为信号驱动I/O型时生成，SIGURG在新的带外数据到达套接口时生成。 F_GETOWN命令返回套接口的当前属主。 注意事项：\n设置某个文件状态标志时，先取得当前标志，与新标志路逻辑或后再设置标志。 信号SIGIO和SIGURG与其他信号不同之处在于，这两个信号只有在已使用命令F_SETOWN给套接口指派了属主后才会生成。F_SETOWN命令的整参数arg既可以是一个正整数，指明接收信号的进程ID，也可以是一个负整数，它的绝对值是接收信号的进程组ID。 当一个新的套接口由函数socket创建时，他没有属主，但是当一个新的套接口从一个监听套接口创建时，套接口属主便由已连接套接口从监听套接口继承而来。 20.gethostbyname函数 #include \u0026lt;netdb.h\u0026gt; struct hostent *gethostbyname(const char *hostname); 返回：非空指针—成功，空指针—出错，同时设置h_errno。\n函数返回的非空指针指向的结构如下： struct hostent { char *h_name; /*规范主机名 */ char *h_aliases; / 别名列表 / int h_addrtype; / AF_INET or AF_INET6 / int h_length; / 地址长度 */ char *h_addr_list; / IPv4或IPv6地址结构列表 */ }; #define h_addr h_addr_list[0];\n按照DNS的说法，gethostbyname执行一个对A记录的查询或对AAAA记录的查询，返回IPv4或IPv6地址。\nh_addr的定义是为了兼容，在新代码中不应使用。\n返回的h_name称为主机的规范（canonical）名字。当返回IPv6地址时，h_addrtype被设置为AF_INET6，成员h_length被设置为16。\ngethostbyname的特殊之处在于：当发生错误时，他不设置errno，而是将全局整数h_errno设置为定义在头文件\u0026lt;netdb.h\u0026gt;中的下列常值中的一个：\nHOST_NOT_FOUND； TRY_AGAIN； NO_RECOVERY； NO_DATA（等同于NO_ADDRESS）。 有函数hstrerror（），它将h_errno的值作为唯一的参数，返回一个指向相应错误说明的const char *型指针。\nDNS小常识： DNS中的条目称为资源记录RR（resource record），仅有少数几类RR会影响我们的名字与地址转换：\nA：A记录将主机名映射为32位的IPv4地址； AAAA：“四A”记录将主机名映射为128位的IPv6地址； PTR：PTR记录（称为“指针记录”）将IP地址映射为主机名； MX：MX记录指定一主机作为某主机的“邮件交换器”。 CNAME：CNAME代表“canonical name（规范名字）”，其常见的用法是为常用服务如ftp和www指派一个CNAME记录。 21.gethostbyname2函数 #include \u0026lt;netdb.h\u0026gt; struct hostent *gethostbyname2(const char *hostname, int family); 返回：非空指针—成功，空指针—出错，同时设置h_errno。\n该函数允许指定地址族，其他与gethostbyname相似。\n22.gethostbyaddr函数 #include \u0026lt;netdb.h\u0026gt; struct hostent *gethostbyaddr(const char *addr, size_t len, int family); 返回：非空指针—成功，空指针—出错，同时设置h_error。\n函数根据一个二进制的IP地址并试图找出相应于此地址的主机名，我们关心的是规范主机名h_name。\n参数addr不是char *类型，而是一个真正指向含有IPv4或IPv6地址的结构in_addr或in6_addr的指针；len是该结构的大小，对于IPv4是4，对于IPv6是16；family或为AF_INET或为AF_INET6。\n按照DNS的说法，该函数查询PTR记录。\n23.uname函数 #include \u0026lt;sys/utsname.h\u0026gt; int uname(struct utsname *name); 返回：非负值—成功，-1—失败。\n返回当前主机的名字，存放在如下的结构里： #define UTS_NAMESIZE 16 #define UTS_NODESIZE 256 struct utsname { char sysname[UTS_NAMESIZE]; char nodename[UTS_NODESIZE]; char release[UTS_NAMESIZE]; char version[UTS_NAMESIZE]; char machine[UTS_NAMESIZE]; };\n该函数经常与gethostbyname一起用来确定本机的IP地址：先调用uname获得主机名字，然后调用gethostbyname得到所有的IP地址。\n获得本机IP地址的另一个方法是ioctl的命令SIOCGIFCONF。\n24.gethostname函数 #include \u0026lt;unistd.h\u0026gt; int gethostname(char *name, size_t namelen); 返回：0—成功，-1—失败。\n返回当前主机的名字。name是指向主机名存储位置的指针，namelen是此数组的大小，如果有空间，主机名以空字符结束。\n主机名的最大大小通常是头文件\u0026lt;sys/param.h\u0026gt;定义的常值MAXHOSTNAMELEN。\n25.getservbyname函数 #include \u0026lt;netdb.h\u0026gt; struct servent *getservbyname(const char *servname, const char *protoname); 返回：非空指针—成功，空指针—失败。\n函数返回如下结构的指针： struct servent { char *s_name; char **s_aliases; int s_port; char *s_proto; };\n服务名servname必须指定，如果还指定了协议（protoname为非空指针），则结果表项必须有匹配的记录。如果没有指定协议名而服务支持多个协议，则返回哪个端口是依赖于实现的。\n结构中的端口号是以网络字节序返回的，所以在将它存储在套接口地址结构时，绝对不能调用htons。\n26.getservbyport函数 #include \u0026lt;netdb.h\u0026gt; struct servent *getservbyport(int port, const char *protname); 返回：非空指针—成功，空指针—出错。\nport必须为网络字节序。例如： sptr = getservbyport(htons(53), “udp”);\n27.recv和send #include \u0026lt;sys/socket.h\u0026gt; ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags); ssize_t send(int sockfd, void *buf, size_t nbytes, int flags); 返回：成功返回读入或写出的字节数，出错返回-1。\n前三个参数与read和write相同，参数flags的值或为0，或由以下的一个或多个常值逻辑或构成：\nflags 描述 recv send MSG_DONTROUTE 不查路由表 y MSG_DONTWAIT 本操作不阻塞 y y MSG_OOB 发送或接收带外数据 y y MSG_PEEK 查看外来的消息 y MSG_WAITALL 等待所有数据 y 下面说明每个标志的作用：\nMSG_DONTROUTE：这个标志告诉内核目的主机在直接连接的本地网络上，不要查路由表。这是对提供这种特性的SO_DONTROUTE套接口选项的补充。该标志可以对单个输出操作提供这种特性，而套接口选项则针对某个套接口上的所有输出操作。 MSG_DONTWAIT：这个标志将单个I/O操作设为非阻塞方式，而不需要在套接口上打开非阻塞标志，执行I/O操作，然后关闭阻塞标志。 MSG_OOB：用send时，这个标志指明发送的是带外数据，用recv时，该标志指明要读的是带外数据而不是一般数据。 MSG_PEEK：这个标志可以让我们查看可读的数据，在recv或recvfrom后系统不会将这些数据丢弃。 MSG_WAITALL：由4.3BSD Reno引入，他告诉内核在没有读到请求的字节数之前不使读操作返回。如果系统支持这个标志，则可以去掉readn函数。即使设定了该标志，如果发生如下情况：（1）捕获了一个信号；（2）连接被终止；（3）在套接口上发生错误，这个函数返回的字节数仍会比请求的少。 28.readv和writev #include \u0026lt;sys/uio.h\u0026gt; ssize_t readv(int filedes, const struct iovec *iov, int iovcnt); ssize_t writev(int filedes, const struct iovec *iov, int iovcnt); 返回：读到或写出的字节数，出错返回-1。\nreadv和writev可以让我们在一个函数调用中读或写多个缓冲区，这些操作被称为分散读和集中写。\niovec结构定义如下： struct iovec { void iov_base; / starting address of buffer / size_t iov_len; / size of buffer */ };\n在具体的实现中对iovec结构数组的元素个数有限制，4.3BSD最多允许1024个，而Solaris2.5上限是16。Posix.1g要求定义一个常值IOV_MAX，而且它的值不小于16。\nreadv和writev可用于任何描述字。writev是一个原子操作，可以避免多次写引发的Nagle算法。\n29.readmsg和writemsg #include \u0026lt;sys/socket.h\u0026gt; ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags); 返回：成功时为读入或写出的字节数，出错时为-1。\n这两个函数是最通用的套接口I/O函数，可以用recvmsg代替read、readv、recv和recvfrom，同样，各种输出函数都可以用sendmsg代替。\n参数msghdr结构的定义如下： struct msghdr { void msg_name; / protocol address / socklen_t msg_namelen; / size of protocol address */ struct iovec msg_iov; / scatter/gather array / size_t msg_iovlen; / elements in msg_iov */ void msg_control; / ancillary data; must be aligned for a cmsghdr structure / socklen_t msg_controllen; / length of ancillary data / int msg_flags; / flags returned by recvmsg() */ };\n该结构源自4.3BSD Reno，也是Posix.1g中所说明的，有些系统仍使用一种老的msghdr结构，此种结构中没有msg_flags成员，而且 msg_control和msg_controllen成员分别被叫做msg_accrights和msg_accrightslen。老系统中支持的唯一一种辅助数据形式是文件描述字（称为访问权限）的传递。\nmsg_name和msg_namelen成员用于未经连接的套接口，他们与 recvfrom和sendto的第五和第六个参数类似：msg_name指向一个套接口地址结构，如果不需要指明协议地址，msg_name应被设置为空指针，msg_namelen对sendmsg是一个值，而对recvmsg是一个值-结果参数。\nmsg_iov和msg_iovlen成员指明输入或输出的缓冲区数组。\nmsg_control和msg_controllen指明可选的辅助数据的位置和大小，msg_controllen对recvmsg是一个值-结果参数。\nmsg_flags只用于revmsg，调用recvmsg时，flags参数被拷贝到msg_flags成员，而且内核用这个值进行接收处理，接着它的值会根据recvmsg的结果而更新，sendmsg会忽略msg_flags成员，因为它在进行输出处理时使用flags参数。\n内核检查的flags和返回的msg_flags如下表所示：\n标志 在send flags、 sendto flags、 sendmsg flags中检查 在recv flags、 recvfrom flags、 recvmsg flags中检查 在recvmsg msg_flags 中返回 MSG_DONTROUTE y MSG_DONTWAIT y y MSG_PEEK y MSG_WAITALL y MSG_EOR y y MSG_OOB y y y MSG_BCAST y MSG_MCAST y MSG_TRUNC y MSG_CTRUNC y 前四个标志只检查不返回，下两个标志既检查又返回，最后四个只返回。返回的六个标志含义如下：\nMSG_BCAST：当收到的数据报是一个链路层的广播或其目的IP地址为广播地址时，将返回此标志。 MSG_MCAST：当收到的数据报是链路层的多播时，将返回该标志。 MSG_TRUNC：这个标志在数据报被截断时返回。 MSG_CTRUNC：这个标志在辅助数据被截断时返回。 MSG_EOR：如果返回的数据不是一个逻辑记录的结尾，该标志被清位，反之则置位。TCP不使用这个标志，因为它是一种字节流协议。 MSG_OOB：这个标志不是为TCP的带外数据返回的，它用于其他协议族（譬如OSI协议等）。 具体的实现可能会在msg_flags中返回一些输入的flags的标志，所以我们应该只检查那些感兴趣的标志的值。\n30.socketpair函数 #include \u0026lt;sys/socket.h\u0026gt; int socketpair(int family, int type, int protocol, int sockfd[2]); 返回：成功返回0，出错返回-1。\nfamily必须为AF_LOCAL，protocol必须为0，type可以是SOCK_STREAM或SOCK_DGRAM。新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回。\n这两个描述字相互连接，没有名字，即没有涉及隐式bind。\n以SOCK_STREAM作为type调用所得到的结果称为流管道（stream pipe）。这与一般的UNIX管道类似，但流管道是全双工的，两个描述字都是可读写的。\n31.套接口ioctl函数 #include \u0026lt;unistd.h\u0026gt; int ioctl(int fd, int request, … /* void *arg */ ); 返回：成功返回0，出错返回-1。\n第三个参数总是一个指针，但指针的类型依赖于request。\nioctl和网络有关的请求可分为如下6类：\n类别 request 描述 数据类型 套接口 SIOCATMARK 在带外标志上吗 int SIOCSPGRP 设置套接口的进程ID或进程组ID int SIOCGPGRP 获取套接口的进程ID或进程组ID int 文件 FIONBIO 设置/清除非阻塞标志 int FIOASYNC 设置/清除异步I/O标志 int FIONREAD 获取接收缓冲区中的字节数 int FIOSETOWN 设置文件的进程ID或进程组ID int FIOGETOWN 获取文件的进程ID或进程组ID int 接口 SIOCGIFCONF 获取所有接口的列表 struct ifconf SIOCSIFADDR 设置接口地址 struct ifreq SIOCGIFADDR 获取接口地址 struct ifreq SIOCSIFFLAGS 设置接口标志 struct ifreq SIOCGIFFLAGS 获取接口标志 struct ifreq SIOCSIFDSTADDR 设置点到点地址 struct ifreq SIOCGIFDSTADDR 获取点到点地址 struct ifreq SIOCGIFBRDADDR 获取广播地址 struct ifreq SIOCSIFBRDADDR 设置广播地址 struct ifreq SIOCGIFNETMASK 获取子网掩码 struct ifreq SIOCSIFNETMASK 设置子网掩码 struct ifreq SIOCGIFMETRIC 获取接口的测度（metric） struct ifreq SIOCSIFMETRIC 设置接口的测度（metric） struct ifreq SIOCxxx （有很多，依赖于实现） ARP SIOCSARP 创建/修改ARP项 struct arpreq SIOCGARP 获取ARP项 struct arpreq SIOCDARP 删除ARP项 struct arpreq 路由 SIOCADDRT 增加路径 struct rtentry SIOCDELRT 删除路径 struct rtentry 流 I_xxx (1)套接口操作 SIOCATMARK：如果套接口的读指针当前在带外标志上，则通过第三个参数指向的整数返回一个非零值，否则返回零。Posix.1g用sockatmark代替了这种请求。 SIOCGPGRP：通过第三个参数指向的整数返回为接收来自这个套接口的SIGIO或SIGURG信号而设置的进程ID或进程组ID。这和fcntl的F_GETOWN相同。 SIOCSPGRP：用第三个参数指向的整数设置进程ID或进程组ID以接收这个套接口的SIGIO或SIGURG信号。这和fcntl的F_SETOWN相同。 (2)文件操作 FIONBIO：套接口的非阻塞标志会根据第三个参数指向的值是否为零而清除或设置。等价于fcntl的F_SETFL设置/清除O_NONBLOCK标志。 FIOASYNC：根据第三个参数指向的值是否为零决定清除或接收套接口上的异步I/O信号。等价于fcntl的F_SETFL设置和清除O_AYNC标志。 FIONREAD：在第三个参数指向的整数中返回套接口接收缓冲区中当前的字节数。 FIOSETOWN：在套接口上等价于SIOCSPGRP。 FIOGETOWN：在套接口上等价于SIOCGPGRP。 (3)接口配置 SIOCGIFCONF：从内核中获取系统中配置的所有接口。它使用了结构ifconf，ifconf又使用了ifreq结构。\n结构定义如下： struct ifconf { int ifc_len; /* size of buffer, value-result / union { caddr_t ifcu_buf; / input from user-\u0026gt;kernel */ struct ifreq ifcu_req; / return from kernel-\u0026gt;user */ }ifc_ifcu; }; #define ifc_buf ifc_ifcu.ifcu_buf #define ifc_req ifc_ifcu.ifcu_req #define IFNAMSIZ 16\nstruct ifreq { char ifr_name[IFNAMSIZ]; union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; short ifru_flags; int ifru_metric; caddr_t ifru_data; }ifr_ifru; }; #define ifr_addr ifr_ifru.ifru_addr #define ifr_dstaddr ifr_ifru.ifru_dstaddr #define ifr_broadaddr ifr_ifru.broadaddr #define ifr_flags ifr_ifru.ifru_flags #define ifr_metric ifr_ifru.ifru_metric #define ifr_data ifr_ifru.ifru_data\n在调用ioctl之前分配一个缓冲区和一个ifconf结构，然后初始化后者，iotctl的第三个参数指向ifconf结构。\n一个实现获取所有接口的程序，可参见unpv12e：lib/get_ifi_info.c\n(4)接口操作 SIOCGIFCONF：从内核中获取系统中配置的所有接口。 (5)ARP高速缓存操作 (6)路由表操作 http://www.cnblogs.com/riky/archive/2006/11/24/570713.aspx\n转自：http://blog.chinaunix.net/space.php?uid=20564848\u0026amp;do=blog\u0026amp;id=73226\n进程间通信IPC Inter-Process Communication 信号Signal 什么是信号 信号在现实世界就是用来传递的某种信息的一种手段，比如一个眼神，一个声音、一个约定的手势等等 计算机世界信号是软件层次上对中断机制的一种模拟。 信号是异步的，进程不用做什么操作来等待信号到达，也不清楚信号什么时候能够到达，用来异步通知一个进程某种事情发生了，进程通过实现注册某些函数，来异步等待相应信号发生的时候做相应的处理。 信号的来源 硬件来源 (比如我们按下了键盘上的某些键【Control+C之类的】 或者某些硬件出现了故障等等) 软件来源 通过kill, raise,alarm,setitimer,sigqueue,abort系统函数 向特定进程发送信号 一些非常运算操作 比如除以0等等 收到信号如何处理 忽略信号 也就是不做任何处理 SIGKILL、SIGSTOP不能忽略 捕捉信号，定义自己的信号处理函数，当信号来了，执行相应处理 执行缺省操作，不走上面2个步骤，默认就是这个，linux对每个信号都有默认操作 相关系统函数说明 kill函数\n#include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;signal.h\u0026gt; int kill(pid_t pid,int signo) 对指定的进程发送什么信息。 pid\u0026gt;0 进程 ID 为 pid 的进程； pid=0 同一个进程组的进程； pid\u0026lt;0 pid!=-1进程组 ID 为 -pid 的所有进程； pid=-1 除发送进程自身外，所有进程 ID 大于1的进程。 raise函数\n#include \u0026lt;signal.h\u0026gt; int raise(int signo) 向进程本身发送信号，参数为即将发送的信号值。 调用成功返回 0；否则，返回 -1 sigqueue函数\n#include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;signal.h\u0026gt; int sigqueue(pid_t pid, int sig, const union sigval val) 调用成功返回 0；否则，返回 -1。 第一个参数是指定接收信号的进程 ID，第二个参数确定即将发送的信号，第三个参数是一个联合数据结构 union sigval，指定了信号传递的参数，sigqueue() 比 kill() 传递了更多的附加信息，但 sigqueue() 只能向一个进程发送信号，而不能发送信号给一个进程组 alarm函数\n#include \u0026lt;unistd.h\u0026gt; unsigned int alarm(unsigned int seconds) 为 SIGALRM 信号而设，在指定的时间 seconds 秒后，将向进程本身发送 SIGALRM 信号，又称为闹钟时间。 进程调用 alarm 后，任何以前的 alarm() 调用都将无效。 如果参数 seconds 为零，那么进程内将不再包含任何闹钟时间。 返回值，如果调用 alarm 前，进程中已经设置了闹钟时间，则返回上一个闹钟时间的剩余时间，否则返回 0。 setitimer函数\n#include \u0026lt;sys/time.h\u0026gt; int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); 比 alarm功能强大，支持3种类型的定时器： ITIMER_REAL： 设定绝对时间；经过指定的时间后，内核将发送SIGALRM信号给本进程； ITIMER_VIRTUAL 设定程序执行时间；经过指定的时间后，内核将发送SIGVTALRM信号给本进程； ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和，经过指定的时间后，内核将发送ITIMER_VIRTUAL信号给本进程； abort函数\n#include \u0026lt;stdlib.h\u0026gt; void abort(void); 向进程发送 SIGABORT 信号，默认情况下进程会异常退出，当然可定义自己的信号处理函数。 即使 SIGABORT 被进程设置为阻塞信号，调用 abort() 后，SIGABORT 仍然能被进程接收。该函数无返回值。 如何注册信号处理 确定信号值及进程针对该信号值的动作之间的映射关系，即进程将要处理哪个信号；该信号被传递给进程时，将执行何种操作\nsignal函数\n#include \u0026lt;signal.h\u0026gt; void (signal(int signum, void (handler))(int)))(int); -------或者-------- #include \u0026lt;signal.h\u0026gt; typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler)); 第一个参数指定信号的值，第二个参数指定针对前面信号值的处理，可以忽略该信号（参数设为 SIG_IGN）； 可以采用系统默认方式处理信号(参数设为 SIG_DFL)； 也可以自己实现处理方式(参数指定一个函数地址)。 如果 signal() 调用成功，返回最后一次为安装信号 signum 而调用signal() 时的 handler值；失败则返回 SIG_ERR。 sigaction函数\n#include \u0026lt;signal.h\u0026gt; int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)); sigaction函数 用于改变进程接收到特定信号后的行为。 该函数的第一个参数为信号的值，可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号（为这两个信号定义自己的处理函数，将导致信号安装错误）。 第二个参数是指向结构 sigaction 的一个实例的指针，在结构sigaction 的实例中，指定了对特定信号的处理，可以为空，进程会以缺省方式对信号处理； 第三个参数 oldact 指向的对象用来保存原来对相应信号的处理，可指定 oldact 为 NULL。如果把第二、第三个参数都设为NULL，那么该函数可用于检查信号的有效性。 第二个参数最为重要，其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。 缺点 能够传递的信息是有限的，不能传递更为复杂的信息。 文件 file 这种应该是最简单 最容易理解的一种通信方式 一个进程往文件写信息，另外一个进程读信息，也就完成了通信过程。 通信本身没有任何顺序控制，可能没有写完，另外一个进程就读到了，另外一个进程本身可能也能够写 可以通过信号来控制有序 文件通信没有访问规则 文件访问的速度是很慢的，所以一般不常用 管道 pipe/named pipe pipe\n半双工 数据流向是单一的，如果双方通信时 需要建立2个pipe 使用范围只能是父子进程/兄弟进程 在内存中的文件系统 一个进程写入输入到管道 可以被另外一端的进程读出，写入的内容每次都添加到管道缓冲区的尾部，相应的另外一端进程每次都从缓冲区的头部读取。 #include \u0026lt;unistd.h\u0026gt; int pipe(int fd[2]) 一端只能用于读，由描述字 fd[0] 表示，称其为管道读端 一端则只能用于写，由描述字 fd[1] 来表示，称其为管道写端 试图从管道写端读取数据，或者向管道读端写入数据都将导致错误发生 fd创建后 fd[0]/fd[1]就可以当作普通的文件描述符使用了 比如close read write等 Named pipe\n可以在任意进程间使用来完成通信工作 会在文件系统创建一个真实存在的文件，但实际是将其映射到内存中的一个特殊区域 文件本身可以设置相应的权限控制 #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;sys/stat.h\u0026gt; int mkfifo(const char * pathname, mode_t mode) 该函数的第一个参数是一个普通的路径名，也就是创建后 FIFO 的名字。 第二个参数与打开普通文件的 open() 函数中的mode 参数相同。 如果 mkfifo 的第一个参数是一个已经存在的路径名时，会返回EEXIST 错误，所以一般典型的调用代码首先会检查是否返回该错误，如果确实返回该错误，那么只要调用打开 FIFO 的函数就可以了。一般文件的 I/O 函数都可以用于 FIFO，如 close、read、write 等等。 管道在进程间通信中使用的比较频繁，尤其是父子多进程间通信 用的非常多\n共享内存 shm 什么是共享内存 顾名思义就是多个进程可以访问同一片内存空间，共享内存允许多个进程共享一个存储区，因为数据不用来回的复制，所以是是最快的IPC形式，这个共享内存是独立与 所有进程空间之外的。\n进程对于共享内存的主要使用可能有以下几点\n向内核提交申请 创建一个共享内存区域 申请使用一个已经存在的共享内存区域 申请释放某一个共享内存区域 可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射）机制实现，也可以通过系统V共享内存机制实现\n应用接口和原理很好理解，但内部实现机制复杂，往往为了安全通信，要引入信号灯，锁等同步机制共同使用\n创建及使用 mmap()映射一个普通文件实现共享内存\n通过shm共享内存机制创建(每个共享内存区域对应特殊文件系统shm中的一个文件)\nint shmget(key_t key, size_t size, int shmflg); //返回值是共享内存的标号shmid int shmid = shmget(key, 256, IPC_CREAT | IPC_EXCL | 0755)； key_t 是一个 long 类型，是 IPC 资源外部约定的 key (关键)值，通过 key 值映射对应的唯一存在的某一个 IPC 资源 通过 key_t 的值就能够判断某一个对应的共享内存区域在哪，是否已经创建等等 一个 key 值只能映射一个共享内存区域，但同时还可以映射一个信号量，一个消息队列资源，于是就可以使用一个 key 值管理三种不同的资源 --------------------------------------------------------------------------- 共享内存的控制信息可以通过 shmctl() 方法获取，会保存在struct_shmid_ds 结构体中 int shmctl(int shmid, int cmd, struct shmid_ds *buf) cmd：看执行什么操作(1、获取共享内存信息；2、设置共享内存信息；3、删除共享内存)。 ----------------------------------------------------------------------------- void * shmat(int shmid, const void *shmaddr, int shmflg); 将这个内存区域映射到本进程的虚拟地址空间 int shmdt(const void *shmaddr); 取消共享内存映射 信号量semaphore 概念 不同进程间/同一进程的不同线程间的一种同步手段 为了解决访问共享资源时候的冲突问题 访问规则 ​\tsem：表示的是一种共享资源的个数，对共享资源的访问规则\n​\t1）用一种数量单位去标识某一种共享资源的个数。\n​\t2）当有进程需要访问对应的共享资源的时候，则需要先查看申请，根据当前资源对应的可用数量进行申请。\n​\t3）资源的管理者（也就是操作系统内核）就使用当前的资源个数减去要申请的资源的个数。如果结果 \u0026gt;=0 表示有可用资源，允许该进程的继续访问；否则表示资源不可用，通知进程（暂停或立即返回）。\n​\t4）资源数量的变化就表示资源的占用和释放。占用：使得可用资源减少；释放：使得可用资源增加。\n相关API //创建信号量集 int semid = semget(key_t key, int nsems, int semflg) 信号量 ID 事实上是信号量集合的ID，一个ID 对应的是一组信号量，此时就使用信号量ID 设置整个信号量集合 这个时候操作分两种： （1）针对信号量集合中的一个信号量进行设置；信号量集合中的信号量是按照数组的方式被管理起来的，从而可以直接使用信号的数组下标来进行访问。 （2）针对整个信号量集和进行统一的设置 ----------------------------------------------------------------------------- int semctl(int semid, int semnum, int cmd, ...) 如果 cmd 是 GETALL、SETALL、GETVAL、SETVAL...的话，则需要提供第四个参数。第四个参数是一个共用体，这个共用体在程序中必须的自己定义（作用：初始化资源个数），定义格式如下： union semun{ int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; ----------------------------------------------------------------------------- //semop()方法。(op：operator操作) int semop(int semid, struct sembuf *sops, unsigned nsops); 第二个参数需要借助结构体 struct sembuf： struct sembuf{ unsigned short sem_num; /* semaphore number 数组下标 */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags 默认0*/ }； 通过下标直接对其信号量 sem_op 进行加减即可。 特点 如果有进程通过信号量申请共享资源，而且此时资源个数已经小于0，则此时对于该进程有两种可能性：等待资源，不等待。\n如果此时进程选择等待资源，则操作系统内核会针对该信号量构建进程等待队列，将等待的进程加入到该队列之中。\n如果此时有进程释放资源则会：\n(1)、先将资源个数增加；\n(2)、从等待队列中抽取第一个进程；\n(3)、根据此时资源个数和第一个进程需要申请的资源个数进行比较，结果大于 0，则唤醒该进程；结果小于 0，则让该进程继续等待。\n所以一般结合信号量的操作和共享内存使用来达到进程间的通信\n消息队列 Message 概念 消息队列就是一个消息的链表。可以把消息看作一个记录，具有特定的格式以及特定的优先级\n对消息队列有写权限的进程可以向中按照一定的规则添加新消息；对消息队列有读权限的进程则可以从消息队列中读走消息\n消息队列是随内核持续的；克服了信号承载信息量少，管道只能承载无格式字节流以及缓冲区大小受限等缺点\n消息队列是随内核持续的，只有在内核重起或者显示删除一个消息队列时，该消息队列才会真正被删除。因此系统中记录消息队列的数据结构（struct ipc_ids msg_ids）位于内核中，系统中的所有消息队列都可以在结构msg_ids中找到访问入口\n结构 ​\t消息队列就是一个消息的链表。每个消息队列都有一个队列头，用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息，包括消息队列键值、用户ID、组ID、消息队列中消息数目等等，甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息，也可以设置其中的某些信息。\n​\tstruct ipc_ids msg_ids是内核中记录消息队列的全局数据结构；struct msg_queue是每个消息队列的队列头\n全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员：struct kern_ipc_perm\nstruct kern_ipc_perm 能够与具体的消息队列对应起来是因为在该结构中，有一个 key_t 类型成员 key，而 key 则唯一确定一个消息队列\nstruct kern_ipc_perm{ //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构； key_t key; //该键值则唯一对应一个消息队列 uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; mode_t mode; unsigned long seq; } 管道中的数据没有分割为一个个独立单元 字节流上是连续的，但消息队列是数据分成了一个个独立的单元，每个独立单元称谓消息体，每一个消息体都是固定大小的存储区域，字节流上是不连续的\n相关API int msgget(key_t key, int msgflg); //创建消息队列 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//发送 在发送消息的时候，是在消息体结构体中指定，当前的消息发送到消息队列集合中的哪一个消息队列上 消息体结构体中就必须包含一个 type 值，type 值是long类型，而且还必须是结构体的第一个成员。而结构体中的其他成员都被认为是要发送的消息体数据 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//接收 无论是 msgsnd() 发送还是 msgrcv()接收时，只要操作系统内核发现新提供的 type 值对应的消息队列集合中的消息队列不存在，则立即为其创建该消息队列 注意事项 为了能够顺利的发送与接收，发送方与接收方需要约定规则\n同样的消息体结构体； 发送方与接收方在发送和接收的数据块儿大小上要与消息结构体的具体数据部分保持一致， 否则将不会读出正确的数据。 如果结构体成员有指针 不会将指针指向空间中的数据发送 只是发送指针本身的值。\n数组作为消息结构体成员是可以的 因为整个数组空间都在消息结构体中\nSocket通信 unix域套接字 其它网络套接字 ","permalink":"https://www.becool.vip/posts/tech/%E9%AB%98%E5%B9%B6%E5%8F%91/","summary":"\u003ch1 id=\"高并发性能相关指标或术语回顾\"\u003e高并发性能相关指标或术语回顾\u003c/h1\u003e\n\u003ch2 id=\"连接相关\"\u003e连接相关\u003c/h2\u003e\n\u003cp\u003e服务端能保持，管理，处理多少客户端的连接\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e活跃连接数：所有ESTABLISHED状态的TCP连接，某个瞬时，这些连接正在传输数据。如果您采用的是长连接的情况，一个连接会同时传输多个请求。也可以间接考察后端服务并发处理能力，注意不同于并发量。\u003c/li\u003e\n\u003cli\u003e非活跃连接数：表示除ESTABLISHED状态的其它所有状态的TCP连接数。\u003c/li\u003e\n\u003cli\u003e并发连接数：所有建立的TCP连接数量。=活跃连接数+非活跃连接数。\u003c/li\u003e\n\u003cli\u003e新建连接数：在统计周期内，从客户端连接到服务器端，新建立的连接请求的平均数。主要考察应对 突发流量或从正常到高峰流量的能力。如：秒杀、抢票场景。\u003c/li\u003e\n\u003cli\u003e丢弃连接数：每秒丢弃的连接数。如果连接服务器做了连接熔断处理，这部分数据即熔断的连接\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e在linux上socket连接体现上就是文件描述符，高并发中相关的参数一定要调优。\u003c/p\u003e","title":"网络高并发"},{"content":" 想说的话:\n每个不曾起舞的日子，都是对生命的辜负 特别喜欢这个翻译\n像疯子一样追求自己的梦想，遇到不开心也希望自己无比淡定\n多向小孩子学习 他们身上有太多可贵的品质 长大后反而丢了\n其实还有很多 不过就先这么着吧\n喜欢的事情:\n跑步 希望一直跑下去\n听歌 旅游\n看电影 电视剧\n希望以后有很多\n联系方式\n邮箱 18929579649@163.com 353175775@qq.com 微信 QQ ","permalink":"https://www.becool.vip/about/","summary":"\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e想说的话:\u003c/p\u003e\n\u003cp\u003e每个不曾起舞的日子，都是对生命的辜负 特别喜欢这个翻译\u003c/p\u003e\n\u003cp\u003e像疯子一样追求自己的梦想，遇到不开心也希望自己无比淡定\u003c/p\u003e\n\u003cp\u003e多向小孩子学习 他们身上有太多可贵的品质 长大后反而丢了\u003c/p\u003e\n\u003cp\u003e其实还有很多 不过就先这么着吧\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e喜欢的事情:\u003c/p\u003e\n\u003cp\u003e跑步 希望一直跑下去\u003c/p\u003e\n\u003cp\u003e听歌 旅游\u003c/p\u003e\n\u003cp\u003e看电影 电视剧\u003c/p\u003e\n\u003cp\u003e希望以后有很多\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e联系方式\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e邮箱\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"mailto:18929579649@163.com\"\u003e18929579649@163.com\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"mailto:353175775@qq.com\"\u003e353175775@qq.com\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e微信\n\u003cul\u003e\n\u003cli\u003e\n\u003cimg src=\"/img/wechat.jpg\" style=\"zoom:25%;\" /\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eQQ\n\u003cul\u003e\n\u003cli\u003e\n\u003cimg src=\"/img/qq.jpg\" style=\"zoom:25%;\" /\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"关于我"},{"content":"一些概念回顾 缓存穿透 概念： 缓存没有数据，而且数据库也没有数据。\n​\t当这种情况大量出现或被恶意攻击时，接口的访问全部透过Redis访问数据库，而数据库中也没有这些数据，我们称这种现象为\u0026quot;缓存穿透\u0026quot;。缓存穿透会穿透Redis的保护，提升底层数据库的负载压力，同时这类穿透查询没有数据返回也造成了网络和计算资源的浪费。\n一般解决方案 如果Redis内不存在该数据，则通过布隆过滤器判断数据是否在底层数据库内； 如果布隆过滤器告诉我们该key在底层库内不存在，则直接返回null给客户端即可，避免了查询底层数据库的动作； 如果布隆过滤器告诉我们该key极有可能在底层数据库内存在，那么将查询下推到底层数据库即可 布隆过滤器有误判率，虽然不能完全避免数据穿透的现象，但已经可以将绝大部份的的穿透查询给屏蔽在Redis层了，极大的降低了底层数据库的压力，减少了资源浪费。 缓冲击穿 概念 一般是指缓存没有但是数据库有的数据。 比如热点数据失效，导致大量合法热点请求打向数据库，此时数据库压力山大 一般解决方案： 延长热点key的过期时间或者设置永不过期，如排行榜，首页等一定会有高并发的接口； 利用互斥锁保证同一时刻只有一个客户端可以查询底层数据库的这个数据，一旦查到数据就缓存至Redis内，避免其他大量请求同时穿过Redis访问底层数据库。 缓存雪崩 概念 缓存雪崩时缓存击穿的加强版，大量的key几乎同时过期，然后大量并发查询穿过redis击打到底层数据库上，此时数据库层的负载压力会骤增，我们称这种现象为\u0026quot;缓存雪崩\u0026quot;。\n一般解决方案 缓存预热 ​\t缓存预热如字面意思，当系统上线时，缓存内还没有数据，如果直接提供给用户使用，每个请求都会穿过缓存去访问底层数据库，\n如果并发大的话，很有可能在上线当天就会宕机，因此我们需要在上线前先将数据库内的热点数据缓存至Redis内再提供出去使用，\n这种操作就成为\u0026quot;缓存预热\u0026quot;。缓存预热的实现方式有很多，比较通用的方式是写个批任务，在启动项目时或定时去触发将底层数据库内的热点数据加载到缓存内。\n缓存更新 ​\t缓存服务（Redis）和数据服务（底层数据库）是相互独立且异构的系统，在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据，因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。如何解决并发场景下更新操作的双写一致是缓存系统的一个重要知识点。\n第二步操作异常：缓存和数据的操作顺序中，第二个动作报错。如数据库被更新， 此时失效缓存的时候出错，缓存内数据仍是旧版本； 缓存更新的设计模式有四种：\nCache aside：查询：先查缓存，缓存没有就查数据库，然后加载至缓存内；更新：先更新数据库，然后让缓存失效；或者先失效缓存然后更新数据库；\nRead through：在查询操作中更新缓存，即当缓存失效时，Cache Aside 模式是由调用方负责把数据加载入缓存，而 Read Through 则用缓存服务自己来加载；\nWrite through：在更新数据时发生。当有数据更新的时候，如果没有命中缓存，直接更新数据库，然后返回。如果命中了缓存，则更新缓存，然后由缓存自己更新数据库；\nWrite behind caching：俗称write back，在更新数据的时候，只更新缓存，不更新数据库，缓存会异步地定时批量更新数据库；\nCache aside：\n为了避免在并发场景下，多个请求同时更新同一个缓存导致脏数据，因此不能直接更新缓存而是另缓存失效。\n先更新数据库后失效缓存：并发场景下，推荐使用延迟失效（写请求完成后给缓存设置1s过期时间），在读请求缓存数据时若redis内已有该数据（其他写请求还未结束）则不更新。当redis内没有该数据的时候（其他写请求已另该缓存失效），读请求才会更新redis内的数据。这里的读请求缓存数据可以加上失效时间，以防第二步操作异常导致的不一致情况。\n先失效缓存后更新数据库：并发场景下，推荐使用延迟失效（写请求开始前给缓存设置1s过期时间），在写请求失效缓存时设置一个1s延迟时间，然后再去更新数据库的数据，此时其他读请求仍然可以读到缓存内的数据，当数据库端更新完成后，缓存内的数据已失效，之后的读请求会将数据库端最新的数据加载至缓存内保证缓存和数据库端数据一致性；在这种方案下，第二步操作异常不会引起数据不一致，例如设置了缓存1s后失效，然后在更新数据库时报错，即使缓存失效，之后的读请求仍然会把更新前的数据重新加载到缓存内。\n推荐使用先失效缓存，后更新数据库，配合延迟失效来更新缓存的模式； 四种缓存更新模式的优缺点：\nCache Aside：实现起来较简单，但需要维护两个数据存储，一个是缓存（Cache），一个是数据库（Repository）； Read/Write Through：只需要维护一个数据存储（缓存），但是实现起来要复杂一些； Write Behind Caching：与Read/Write Through 类似，区别是Write Behind Caching的数据持久化操作是异步的，但是Read/Write Through 更新模式的数据持久化操作是同步的。优点是直接操作内存速度快，多次操作可以合并持久化到数据库。缺点是数据可能会丢失，例如系统断电等。 缓存本身就是通过牺牲强一致性来提高性能，因此使用缓存提升性能，就会有数据更新的延迟性。这就需要我们在评估需求和设计阶段根据实际场景去做权衡了。 缓存降级 ​\t缓存降级是指当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，即使是有损部分其他服务，仍然需要保证主服务可用。可以将其他次要服务的数据进行缓存降级，从而提升主服务的稳定性。\n降级的目的是保证核心服务可用，即使是有损的。如去年双十一的时候淘宝购物车无法修改地址只能使用默认地址，这个服务就是被降级了，这里阿里保证了订单可以正常提交和付款，但修改地址的服务可以在服务器压力降低，并发量相对减少的时候再恢复。\n降级可以根据实时的监控数据进行自动降级也可以配置开关人工降级。是否需要降级，哪些服务需要降级，在什么情况下再降级，取决于大家对于系统功能的取舍。\nCAP CAP定理是加州大学的计算机科学家 Eric Brewer 在 1998年提出，也就是下面的3个英文单词， 原始的CAP理论有非常精准的定义，讨论的范围其实也有非常有局限性，现在大家讨论的CAP其实跟原始的有不少地方是不一致，下面讨论的也是被大家讨论的一些内容。\nCAP更多的是关注数据，而不是整个系统，实际项目中往往要处理各种数据，有些数据要求更多的一致性，有些数据要求更多的可用性，如果武断的说要CP还是AP往往是比较难的。\nConsistency 一致性 (读写数据的一致性)\n简单理解就是多个数据实例的数据要保证一致，这里又可以用很多个粒度，比如强一致性，最终一致性等等\n实际数据同步时候往往不能忽略网络延迟，相同机房可能几毫秒同步完成，远距离的跨机房可能要几十毫秒甚至更多，所以有些非常关键的数据要求必须一致的时候往往把P扔了，只能单点写入，其它节点做备份。\nAvailability 可用性(服务高可用性)\n客户端发送一个请求，必须给出正确回应，所以说起高可用，往往不是单点的，是多节点的服务，因为单点如果出现故障则整个系统挂掉，根本没法向外提供服务 对应上面的图来说 也就是往往有G1 G2 往往还会有Gn等多个实例提供服务 Partition tolerance 分区容错性\n分布式系统，正常情况下多个节点之间是可以互通的，但不是讨论的范围，P讨论的如果出现故障，节点之间不能互通，所以整个网络就被分成了多块区域，而数据就是分散在这些不能连通的区域中，这个称为分区。容错的意思就是分区了(不能互相连通)也要能给提供服务，让客户端正常访问，不能出现单点故障。 为什么要P非要在C和A之间做取舍\n拿上面的G1 G2例子来说，如果必须A：要求必须实现一致性，则网络不通G2就要停止对外提供服务\n因为要C，G1改为v1 G2也必须改为v1,但是如果说P 也就是G1不能给G2发消息的情况(网络不通)，G2无法实现数据同步而保持一致性，所以这个时候就要让G2停止对外提供服务，因为数据不一致了，不能对外提供服务。\n如果G2不能对外提供服务，此时G1挂掉了，就没有节点能给对外提供服务了，也就是没有高可用了 也就是没有了A\n反过来说也是一样的 如果就是要A，也就是不要一致性了，必须保证服务高可用，G1挂掉了 G2数据虽然数据不是最新的(存在数据不一致)，也必须让G2对外提供服务，所以这个时候就是舍弃了C(放弃数据一致性)\n实际项目往往不是非常粗暴的舍弃A或者C，这里的舍弃往往只是非常短时间的舍弃。因为讨论舍弃C/A的前提是P出错了，也就是节点之间不能互通了，分区出现故障了，这个时间往往是比较短暂的，等分区故障恢复后往往就可以CA了。另外针对不同的数据往往也会选择不同的策略。\n比如一般用户账号数据(id 密码)往往是要求一致性更多点，但其它一些信息数据(昵称、兴趣)等可能要求可用性更多些\nACID ACID 是数据库管理系统为了保证事务的正确性而提出来的一个理论，ACID 包含四个约束，下面我来解释一下。\nAtomicity（原子性）\n一个事务中的所有操作，要么全部完成，要么全部不完成，不会在中间某个环节结束。事务在执行过程中发生错误，会被回滚到事务开始前的状态，就像这个事务从来没有执行过一样。\nConsistency（一致性）\n在事务开始之前和事务结束以后，数据库的完整性没有被破坏。\nIsolation（隔离性）\n数据库允许多个并发事务同时对数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别，包括读未提交（Read uncommitted）、读提交（read committed）、可重复读（repeatable read）和串行化（Serializable）。\nDurability（持久性）\n事务处理结束后，对数据的修改就是永久的，即便系统故障也不会丢失。\n可以看到，ACID 中的 A（Atomicity）和 CAP 中的 A（Availability）意义完全不同，而 ACID 中的 C 和 CAP 中的 C 名称虽然都是一致性，但含义也完全不一样。ACID 中的 C 是指数据库的数据完整性，而 CAP 中的 C 是指分布式节点中的数据一致性。再结合 ACID 的应用场景是数据库事务，CAP 关注的是分布式系统数据读写这个差异点来看，其实 CAP 和 ACID 的对比就类似关公战秦琼，虽然关公和秦琼都是武将，但其实没有太多可比性。\nBASE BASE 是指基本可用（Basically Available）、软状态（ Soft State）、最终一致性（ Eventual Consistency），核心思想是即使无法做到强一致性（CAP 的一致性就是强一致性），但应用可以采用适合的方式达到最终一致性。\n基本可用（Basically Available）\n分布式系统在出现故障时，允许损失部分可用性，即保证核心可用。\n这里的关键词是“部分”和“核心”，具体选择哪些作为可以损失的业务，哪些是必须保证的业务，是一项有挑战的工作。例如，对于一个用户管理系统来说，“登录”是核心功能，而“注册”可以算作非核心功能。因为未注册的用户本来就还没有使用系统的业务，注册不了最多就是流失一部分用户，而且这部分用户数量较少。如果用户已经注册但无法登录，那就意味用户无法使用系统。例如，充了钱的游戏不能玩了、云存储不能用了……这些会对用户造成较大损失，而且登录用户数量远远大于新注册用户，影响范围更大。\n软状态（Soft State）\n允许系统存在中间状态，而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。\n最终一致性（Eventual Consistency）\n系统中的所有数据副本经过一定时间后，最终能够达到一致的状态。\n这里的关键词是“一定时间” 和 “最终”，“一定时间”和数据的特性是强关联的，不同的数据能够容忍的不一致时间是不同的。举一个微博系统的例子，用户账号数据最好能在 1 分钟内就达到一致状态。因为用户在 A 节点注册或者登录后，1 分钟内不太可能立刻切换到另外一个节点，但 10 分钟后可能就重新登录到另外一个节点了，而用户发布的最新微博，可以容忍 30 分钟内达到一致状态。因为对于用户来说，看不到某个明星发布的最新微博，用户是无感知的，会认为明星没有发布微博。“最终”的含义就是不管多长时间，最终还是要达到一致性的状态。\nBASE 理论本质上是对 CAP 的延伸和补充，更具体地说，是对 CAP 中 AP 方案的一个补充。前面在剖析 CAP 理论时，提到了其实和 BASE 相关的两点：\nCAP 理论是忽略延时的，而实际应用中延时是无法避免的。 这一点就意味着完美的 CP 场景是不存在的，即使是几毫秒的数据复制延迟，在这几毫秒时间间隔内，系统是不符合 CP 要求的。因此 CAP 中的 CP 方案，实际上也是实现了最终一致性，只是“一定时间”是指几毫秒而已。\nAP 方案中牺牲一致性只是指分区期间，而不是永远放弃一致性。 这一点其实就是 BASE 理论延伸的地方，分区期间牺牲一致性，但分区故障恢复后，系统应该达到最终一致性\n分布式锁 Mysql锁 方式 ​\t使用数据库的唯一索引特性，获取锁就是插入一条记录(唯一索引保证只能插入一次)，释放锁就是删除这条记录\n优点 ​\t使用非常简单 开发难度很低\n缺点 锁没有失效时间 如果应用拿到锁之后挂掉，导致锁无法正常释放，那么其它应用就再也拿不到锁，业务无法开展。\n没有拿到锁的应用需要不停的检查锁是否已经释放(记录是否存在)，这样比较浪费cpu资源，也会给数据库压力\n如果搞一个监控程序监视锁，控制超时时间，超过时间就强制删除锁，一来这里的超时时间不好控制，而来监控程序也可能挂掉 还是存在锁无法释放的风险。\nRedis分布式锁 方式1__setnx key value \u0026gt; setnx lock:codehole true //使用setnx(set if not exists) 指令。在redis里面占坑 谁占到了就是抢锁成功 OK ... do something critical ... \u0026gt; del lock:codehole\t//使用del 命令释放锁 (integer) 1 ------------------------问题-------------------- 1. 中间的处理逻辑如果出现异常，比如程序死掉了，会导致无法调用del命令，也就是锁无法释放 2. 应用可以强制抢锁，因为谁都可以上来直接del 这本身是不安全了 3. 锁释放了 其它应用不知道，所以只能不停的查询锁释放还在，浪费资源 方式2__set key value ex timeout nx \u0026gt; set lock:codehole true ex 5 nx //setnx升级版 加上了设置超时时间 而且redis支持这2个功能为原子操作 过了一定时间应用不释放 redis强制释放锁 ... do something critical ... \u0026gt; del lock:codehole ------------------------问题-------------------- 1. 超时时间不好设置 如果过长 会导致一旦持有锁的应用挂掉了 很长一段时间 业务无法继续 2. 超时时间太短 业务逻辑还没有执行完成，锁就被redis强制释放，其它应用就会重新持有这把锁，这样就没法保证临界区的代码严格串行 ---比如秒杀商品超买问题 3. 应用可以强制抢锁，因为谁都可以上来直接del 这本身是不安全了(应用超时后业务逻辑处理完毕 也会误删锁 下面有例子) 4. 锁释放了 其它应用不知道，所以只能不停的查询锁释放还在，浪费资源 时间线 线程1 线程2 线程3 时刻1 执行 setnx mylock val1 加锁 执行 setnx mylock val2 加锁 执行 setnx mylock val2 加锁 时刻2 加锁成功 加锁失败 加锁失败 时刻3 执行任务\u0026hellip; 尝试加锁\u0026hellip; 尝试加锁\u0026hellip; 时刻4 任务继续（锁超时，自动释放了） setnx 获得了锁（因为线程1的锁超时释放了） 仍然尝试加锁\u0026hellip; 时刻5 任务完毕，del mylock 释放锁 执行任务中\u0026hellip; 获得了锁（因为线程1释放了线程2的） 方式3__set lockKey randomNum ex timeout nx 删除的时候匹配随机数 其它应用误删锁 大致逻辑如下： 1. 线程1 准备释放锁 ， 锁的key 为 mylock 锁的 value 为 thread1_magic_num 2. 查询当前锁 current_value = get mylock 3. 判断 if current_value == thread1_magic_num -- \u0026gt; 是 我（线程1）的锁 else -- \u0026gt;不是 我（线程1）的锁 4. 是我的锁就释放，否则不能释放（而是执行自己的其他逻辑）。 因为上述的逻辑直接使用redis命令无法做到原子性 所以直接使用比较危险 可以用lua脚本实现(redis执行lua脚本是原子的 要么都成功 要么都失败) if redis.call(\u0026#39;get\u0026#39;, lockKey) == randomNum加锁时候的随机数 then return redis.call(\u0026#39;del\u0026#39;, lockKey) else return 0 end 问题： 超时时间还是不好设置的问题，还是无法解决逻辑执行时间长，导致锁超时被redis强制释放，其它线程抢锁成功，同时执行临界区代码的问题 方式4: redlock ​\t上述3种方式都有一个共同的问题 就是如果redis是单点的，不是集群的，一旦redis节点挂点就不能够对外提供服务了。\n如果是集群的话，例如redis sentinel集群中，我们具有多台redis，他们之间有着主从的关系，例如一主二从，set命令对应的数据写到主库，然后同步到从库。当我们申请一个锁的时候，对应就是一条命令 setnx mykey myvalue ，在redis sentinel集群中，这条命令先是落到了主库。假设这时主库down了，而这条数据还没来得及同步到从库，sentinel将从库中的一台选举为主库了。这时，我们的新主库中并没有mykey这条数据，若此时另外一个client执行 setnx mykey hisvalue , 也会成功，即也能得到锁。这就意味着，此时有两个client获得了锁。虽然这个情况发生的记录很小，只会在主从failover的时候才会发生，大多数情况下、大多数系统都可以容忍，但是不是所有的系统都能容忍这种瑕疵。\n原理： 为了解决故障转移情况下的缺陷，Antirez 发明了 Redlock 算法，使用redlock算法，需要多个redis实例，加锁的时候，它会想多半节点发送 setex mykey myvalue 命令，只要过半节点成功了，那么就算加锁成功了。释放锁的时候需要想所有节点发送del命令。这是一种基于【大多数都同意】的一种机制。感兴趣的可以查询相关资料。在实际工作中使用的时候，我们可以选择已有的开源实现，python有redlock-py，java 中有Redisson redlock。\nredlock确实解决了上面所说的“不靠谱的情况”。但是，它解决问题的同时，也带来了代价。你需要多个redis实例，你需要引入新的库 代码也得调整，性能上也会有下降。\n总结 ​\t总体来讲方式3用的比较多，相对比较安全，但要根据实际业务场景适当设置超时时间，过长过短都不好。\n​ 应用一定要捕捉异常，如果出现异常一定要释放锁。\n​\t如果因为业务逻辑执行过长，redis超时强制删除了锁，自己释放锁的时候发现不是自己的锁了，也要根据实际情况做相应的处理，比如回滚之类的。\nZookeeper分布式锁 Zookeeper节点类型 永久性节点(有序和无序)\n不会因为会话结束或者超时而消失，\n有序的话 会在节点名的后面加一个数字后缀，并且是有序的，例如生成的有序节点为 /lock/node-0000000000，它的下一个有序节点则为 /lock/node-0000000001，依次类推\n临时性节点(有序和无序)\n如果会话结束或者超时就会消失。\n​\nZookeeper有节点变化通知其它客户端的特性 客户端可以针对某一个zk节点(目录 但可以有数据)设置监控事件(可以是节点变更、删除等等)和对应处理方法 当对应的zk节点发生变化 会通知客户端进行影响处理 利用临时有序节点和事件通知特性实现分布式锁 创建一个锁目录 /lock； 在 /lock 下创建临时的且有序的子节点\nClient1对应的子节点为/lock/lock-0000000000,Clinet2为/lock/lock-0000000001 \u0026hellip;..\n每个client都查询目录下的所有节点列表 判断自己释放为数字最小的节点\n3.1 是则加锁成功 进行业务处理，处理完毕 删除对应lock下的子节点\n3.2 不是最小节点 则监听自己的前一个节点 等待前一个节点处理完毕后通知自己\n如果持有锁的节点死掉了 会话超时临时节点会被zk删除 也就是释放了锁\nredis 5种数据类型 ​\t内部使用一个redisObject对象来表示所有的key和value，redisObject最主要的信息如上图所示：type代表一个value对象具体是何种数据类型，encoding是不同数据类型在redis内部的存储方式，比如：type=string代表value存储的是一个普通字符串，那么对应的encoding可以是raw或者是int，如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的，当然前提是这个字符串本身可以用数值表示，比如:\u0026ldquo;123\u0026rdquo; \u0026ldquo;456\u0026quot;这样的字符串。\nstring 字符串类型 最基本的数据类型，可以保存字符串、数字、二进制，本身是二进制安全的。\n常用操作命令 保存为字符串操作相关：\nset key value #设置字符串key的值为value append key value #在原先字符串后面追加 strlen key #查看key这个字符串的长度 setrange key offset value #Overwrite part of a string at key starting at the specified offset 改变字符串某一位的值 getrange key start end #Get a substring of the string stored at a key 获取子串 setnx key value 如果不存在则设置kv 存在不设置 SET key value [expiration EX seconds|PX milliseconds] [NX|XX] #设置kv 并且设置超时时间 往往用在分布式锁 NX表示如果存在则不能设置 不覆盖 mset mget一次设置多个kv 一次获取多个kv 保存为数字的操作相关\nincr key # Increment the integer value of a key by one INCRBY key increment #Increment the integer value of a key by the given amount DECR key #Decrement the integer value of a key by one DECRBY key decrement # Decrement the integer value of a key by the given number 保存为二进制位的相关操作\nsetbit key offset value #Sets or clears the bit at offset in the string value stored at key #offset如果超过本身长度 会动态的扩 非offset的位置都是0 如果要get的话 可能出现乱码 BITCOUNT key [start end] #Count set bits in a string 这里的start end指的是字节 不是每个自己里面的offset BITOP operation destkey key [key ...] #位操作 127.0.0.1:6379\u0026gt; setbit k1 1 1 127.0.0.1:6379\u0026gt; setbit k1 7 1 127.0.0.1:6379\u0026gt; get k1 k1的二进制位 0100 0001 \u0026#34;A\u0026#34; 127.0.0.1:6379\u0026gt; setbit k2 1 1 127.0.0.1:6379\u0026gt; setbit k2 6 1 127.0.0.1:6379\u0026gt; get k2 k1的2的二进制位 0100 0010 \u0026#34;B\u0026#34; 想执行 k1\u0026amp;k2 127.0.0.1:6379\u0026gt; bitop and result k1 k2 (integer) 1 127.0.0.1:6379\u0026gt; get result \u0026#34;@\u0026#34; result是@ 对应二进制位 0100 0000 bitop and|or|xor|not 应用场景 session共享 kv缓冲 计数器：微博数、粉丝数 小文件系统 key：文件名 value: 具体数据 bitmap的一些使用场景： 布隆过滤器 记录一个人一年的登录情况 可以用365个位表示 哪一天登陆了就设置为1 等等 统计活跃用户数之类的 每天一个key 记录每一天每个用户登陆请求(映射到每一位) 然后每一天的key通过或运算 list ​\tredis实现的是双向循环链表 所以非常容易实现分布式队列或者分布式栈，也就是字符串链表，插入本身是有顺序的，第一个插入，取出第一个还是这个元素。\n常用操作命令 LPUSH key value [value ...] # Prepend one or multiple values to a list 每次都从左边头部压入链表 可以一次压入多个元素 LPOP key # Remove and get the first element in a list 从左边出弹出一个元素 对应的右端操作就是 RPUSH RPOP 只是每次都从右端操作 LRANGE key start stop #Get a range of elements from a list 获取某一个区间的元素列表 最右端支持为下标-1的下标计数 LTRIM key start stop #Trim a list to the specified range 删除 【start,stop】左边和右边的元素 应用场景 分布式队列和栈\n将redis用作日志容器器 多个client将日志写入redis list，搞一个进程读取list写入磁盘\n取最新的N个数据操作 比如最新的10条评论 最新登陆的10个用户id列表 超过这个范围的从数据库获取\n/把当前登录人添加到链表里 ret = r.lpush(\u0026#34;login:last_login_times\u0026#34;, uid) //保持链表只有N位 ret = redis.ltrim(\u0026#34;login:last_login_times\u0026#34;, 0, N-1) //获得前N个最新登陆的用户Id列表 last_login_list = r.lrange(\u0026#34;login:last_login_times\u0026#34;, 0, N-1) 分布式任务分发器\n多个任务派发进程 将单个任务请求写入redis list 多个任务处理进程 从list中pop取任务处理 hash ​\t存放的value本身是是个hashmap 也就是一个string 类型的 field 和 value 的映射表\n​\t对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了，既不需要重复存储数据，也不会带来序列化和并发修改控制的问题。\n​\t实现方式：上面已经说到Redis Hash对应Value内部实际就是一个HashMap，实际这里会有2种不同实现，这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储，而不会采用真正的HashMap结构，对应的value redisObject的encoding为zipmap，当成员数量增大时会自动转成真正的HashMap，此时encoding为ht\n常用操作命令 127.0.0.1:6379\u0026gt; hset infoid name hkt age 20 #key:infoid 对应value为map[name:hkt,age:20] HSET key field value\t#这里的infoid可以理解成c++语言的map名字 field理解为map的key value就是map[key]的值 127.0.0.1:6379\u0026gt; hget infoid name #获取infoid这个hashmap对应key为name的值 string name=infoid[name] \u0026#34;hkt\u0026#34; 127.0.0.1:6379\u0026gt; hget infoid age #获取infoid这个hashmap对应key为age的值 int age=infoid[age] \u0026#34;20\u0026#34; 127.0.0.1:6379\u0026gt; hset infoid age 30 #修改infoid这个hashmap对应key为age的值 infoid[age]=30 127.0.0.1:6379\u0026gt; hget infoid age \u0026#34;30\u0026#34; 127.0.0.1:6379\u0026gt; hgetall infoid #获取infoid这个hashmap所有的key和value 现实项目不常用 因为比较耗时 1) \u0026#34;name\u0026#34; 2) \u0026#34;hkt\u0026#34; 3) \u0026#34;age\u0026#34; 4) \u0026#34;30\u0026#34; 127.0.0.1:6379\u0026gt; hkeys infoid #获取infoid这个hashmap对应的所有key 1) \u0026#34;name\u0026#34; 2) \u0026#34;age\u0026#34; 127.0.0.1:6379\u0026gt; hvals infoid #获取infoid这个hashmap对应的所有value 1) \u0026#34;hkt\u0026#34; 2) \u0026#34;30\u0026#34; 应用场景 聚集一些相关的数据做缓存(频繁访问不怎么修改的，但彼此有相关 比如用户信息 粉丝 关注等等) 商品详情页的多个tab页面也可以缓存到hash中 set ​\t集合、无序(插入也是没有顺序)、没有重复 ，底层就是hashmap 只是value为null,可以支持数学上的交集、并集、查集操作\n常用命令 127.0.0.1:6379\u0026gt; sadd set1 a b c d 添加元素 a b c d到集合set1 (integer) 4 127.0.0.1:6379\u0026gt; sadd set2 b c d e (integer) 4 127.0.0.1:6379\u0026gt; scard set1 #获取set1集合数量 (integer) 4 127.0.0.1:6379\u0026gt; smembers set1 #获取集合set1中所有的成员 1) \u0026#34;b\u0026#34; 2) \u0026#34;a\u0026#34; 3) \u0026#34;d\u0026#34; 4) \u0026#34;c\u0026#34; 127.0.0.1:6379\u0026gt; sinter set1 set2 #取2个集合的交集 1) \u0026#34;b\u0026#34; 2) \u0026#34;d\u0026#34; 3) \u0026#34;c\u0026#34; 127.0.0.1:6379\u0026gt; sdiff set1 set2 #取2个集合的差集 set1有 set2没有 1) \u0026#34;a\u0026#34; 127.0.0.1:6379\u0026gt; sdiff set2 set1 #取2个集合的差集 set2有 set1没有 1) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; sunion set1 set2 #取2个集合的并集 不保存到redis 1) \u0026#34;b\u0026#34; 2) \u0026#34;a\u0026#34; 3) \u0026#34;d\u0026#34; 4) \u0026#34;c\u0026#34; 5) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; sunionstore set3 set1 set2 #取2个集合的并集 保存到redis (integer) 5 127.0.0.1:6379\u0026gt; smembers set3 1) \u0026#34;b\u0026#34; 2) \u0026#34;a\u0026#34; 3) \u0026#34;d\u0026#34; 4) \u0026#34;c\u0026#34; 5) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; sismember set1 a #是否在某个集合当中 (integer) 1 127.0.0.1:6379\u0026gt; srandmember set3 #随机返回集合中的某一个元素 \u0026#34;c\u0026#34; 127.0.0.1:6379\u0026gt; srandmember set3 3 #随机返回集合中的多个元素 如果大于集合总个数 也只能返回集合总个数 1) \u0026#34;b\u0026#34; 2) \u0026#34;a\u0026#34; 3) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; srandmember set3 -7 #随机返回集合中的多个元素 如果大于集合个数 会出现重复 但个数是你所要求的 1) \u0026#34;a\u0026#34; 2) \u0026#34;b\u0026#34; 3) \u0026#34;a\u0026#34; 4) \u0026#34;a\u0026#34; 5) \u0026#34;c\u0026#34; 6) \u0026#34;e\u0026#34; 7) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; spop set1 #随机删除一个元素 \u0026#34;d\u0026#34; 127.0.0.1:6379\u0026gt; srem set1 b #指定删除某一个元素 (integer) 1 应用场景 分布式去重 向redis某个set里面不断扔数据就可以了 分布式并查集计算 案例：在微博中，可以将一个用户所有的关注人存在一个集合中，将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作，可以非常方便的实现如共同关注、共同喜好、二度好友等功能，对上面的所有集合操作，你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中 随机抽奖 随机验证码等等 zset ​\t集合、不重复、有序，sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序，如果需要一个有序的不重复的集合可以选择zset。和Set相比，Sorted Set关联了一个double类型权重参数score，使得集合中的元素能够按score进行有序排列，redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复\n​\t实现方式：Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序，HashMap里放的是成员到score的映射，而跳跃表里存放的是所有的成员，排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率，并且在实现上比较简单\n常用命令 127.0.0.1:6379\u0026gt; zadd zset 100 hkt 99 wxl 98 hyx #往zset有序集合添加3个内容 hkt 100分 wxl 99分 hyx 98分 (integer) 3 127.0.0.1:6379\u0026gt; zrange zset 0 -1 #列出全部元素 也可以加上withscores就会把分数返回 1) \u0026#34;hyx\u0026#34; 2) \u0026#34;wxl\u0026#34; 3) \u0026#34;hkt\u0026#34; 127.0.0.1:6379\u0026gt; zscore zset hkt #获取某个成员的分数 \u0026#34;200\u0026#34; ZREM key member #删除某个成员 ZREVRANGE key start stop # with scores ordered from high to low 也就是将序 默认是升序 127.0.0.1:6379\u0026gt; zpopmin zset #弹出最小的元素 1) \u0026#34;hyx\u0026#34; 2) \u0026#34;98\u0026#34; 127.0.0.1:6379\u0026gt; zpopmax zset #弹出最大的元素 1) \u0026#34;hkt\u0026#34; 2) \u0026#34;200\u0026#34; 127.0.0.1:6379\u0026gt; zrangebyscore zset 60 80 withscores #列出某个分数范围的元素集合 1) \u0026#34;dsds\u0026#34; 2) \u0026#34;77\u0026#34; zincrby zset 10 hkt #给hkt加10分 zincrby zset -10 hkt #给hkt减10分 127.0.0.1:6379\u0026gt; zrank zset hkt #从小到大升序 查看排名情况 (integer) 3 127.0.0.1:6379\u0026gt; zrevrank zset hkt #从大到小 降序查看排名情况 (integer) 0 应用场景 排行榜 带权重的消息队列 常用命令 nc localhost 6379 #如果没有cli脚本 可以直接使用nc命令与redis交互 keys *\t#列出所有的key 生产一般不用 help @generic #查看帮助 flushall #删除所有数据 help @string #查看字符串的相关本地方法帮助文档 help @list help @hash help @set help @sorted_set 127.0.0.1:6379\u0026gt; type k1 #查看类型 string 127.0.0.1:6379\u0026gt; object encoding k1 #查看编码 \u0026#34;raw\u0026#34; ","permalink":"https://www.becool.vip/posts/tech/%E5%88%86%E5%B8%83%E5%BC%8F/","summary":"\u003ch1 id=\"一些概念回顾\"\u003e一些概念回顾\u003c/h1\u003e\n\u003ch2 id=\"缓存穿透\"\u003e缓存穿透\u003c/h2\u003e\n\u003ch3 id=\"概念\"\u003e概念：\u003c/h3\u003e\n\u003cp\u003e缓存没有数据，而且数据库也没有数据。\u003c/p\u003e\n\u003cp\u003e​\t当这种情况大量出现或被恶意攻击时，接口的访问全部透过Redis访问数据库，而数据库中也没有这些数据，我们称这种现象为\u0026quot;缓存穿透\u0026quot;。缓存穿透会穿透Redis的保护，提升底层数据库的负载压力，同时这类穿透查询没有数据返回也造成了网络和计算资源的浪费。\u003c/p\u003e","title":"分布式"},{"content":"冒泡排序 介绍 ​ 冒泡排序是一种比较简单的排序，之所以叫冒泡，是因为在两两比较的过程中较大的数就像冒泡一样被换到后面。详细解释：依次比较相邻的两个数，前面的数大于后面的数，则交换，将较大的数挪动到后面\n第1轮: 比较1 \u0026ndash; N 经过依次相邻两两比较交换 最大的数则放到了最后 第2轮: 比较1 \u0026ndash;N-1 经过依次相邻两两比较交换 第2大的数则放到了N-1的位置 第N-1轮:比较1 \u0026ndash;\t2\t前2个数两两比较交换 整个过程完成 代码 BubbleSort ```go func BubbleSort(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } //外层循环控控制每轮循环两两比较的最大下标 第1次为N-1 最后一次为1(也就是最前面的2个元素) for endPos := len(a) - 1; endPos \u003e 0; endPos-- { //内层循环完成两两比较交换 for i := 0; i \u003c endPos; i++ { if a[i] \u003e a[i+1] { a[i], a[i+1] = a[i+1], a[i] } } } } ``` 时间复杂度 ​ O($N^2$)\n稳定性 ​ 稳定 因为如果2个数相等 则他们的相对位置 并没有发生改变\n优化 ​ 看内层循环 如果并没有发生数据交换 则证明所有数据已经排序完成，这个时候直接结束即可 加一个标志判断即可\nBubbleSortOpt ```go func BubbleSort(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } isChg := false //外层循环控控制每轮循环两两比较的最大下标 第1次为N-1 最后一次为1(也就是最前面的2个元素) for endPos := len(a) - 1; endPos \u003e 0; endPos-- { //内层循环完成两两比较交换 for i := 0; i \u003c endPos; i++ { if a[i] \u003e a[i+1] { a[i], a[i+1] = a[i+1], a[i] isChg = true } } if !isChg { //如果内层循环没有发生数据交换 则表明所有数据都已经排序完成 直接退出循环即可 break } } } ``` 网搜图解 ​\t摘自: https://www.cnblogs.com/onepixel/p/7674659.html\n插入排序 介绍 ​ 插入排序顾名思义就是将一个待排序的元素，插入到一组已经排好序的元素中，如果形象比喻下，可以想象一下打牌，拿起来第一张牌自然就是排好序的，拿起第二张则跟第一张进行比较，插入到合适的位置。接下来拿第三张 跟前面2张已经排好序的比较，插入合适的位置，依次类推，拿完所有的牌，顺序自然也排好了。\n​ 将待排序的元素分为有序区和无序区，按照顺序每次从无序区拿一个元素，插入插入到有序区，直到所有无序区的元素都插入有序区，整个排序过程结束。第一次有序区为第1个元素，无序区为第2\u0026mdash;N个元素，拿出第2个元素插入到有序区。\n代码 InsertSort ```go func InsertSort(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } //j为无序区的第一个元素 对应下标从1开始，每次后移一个位置 for j := 1; j \u003c len(a); j++ { //内层循环完成比较插入 倒序依次跟有序区的元素进行比较，如果小于有序区的元素 则交换 for i := j; i \u003e 0; i-- { if a[i] \u003c a[i-1] { a[i], a[i-1] = a[i-1], a[i] } } } } ``` 时间复杂度 ​ O($N^2$)\n算法稳定性 ​ 稳定 没有改变两个相等元素的相对位置\n优化 ​ 上面代码内层循环在查找待插入位置时是倒序逐个比较的，在查找待插入位置时候是可以优化的，采用二分查找可以有效减少比较次数，但优化后的插入算法则变为不稳定的\nInsertSortOpt ```go //BinSerachInsertIndex 二分查找在a数组 begin到end区间 key元素的插入位置 func BinSerachInsertIndex(a []int, begin int, end int, key int) int { pos := -1 //需要插入的位置 for begin \u003c= end { mid := begin + (end-begin)/2 if a[mid] == key { //如果等于key 则找到位置 pos = mid + 1 break } else if a[mid] \u003c key { begin = mid + 1 } else { end = mid - 1 } } if pos == -1 { pos = begin } return pos } func InsertSortOpt(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } for j := 1; j \u003c len(a); j++ { begin, end, key := 0, j-1, a[j] //找到插入的位置 pos := BinSerachInsertIndex(a, begin, end, key) //将pos到end区间的元素逐个后移 for index := j; index \u003e pos; index-- { a[index] = a[index-1] } //插入待排序元素 a[pos] = key } } ``` 网搜图解 ​\t摘自:https://www.cnblogs.com/onepixel/p/7674659.html\n归并排序 介绍 ​\tMergeSort 合并两个有序的序列为1的大的有序的序列，最典型的归并排序可以分2个大的步骤：\n1 采用递归思想 将一个大的序列:二分为大致平均的子序列，然后针对每个子序列都再递归二分(最后每个子序列长度都为1)\n2 两两子序列合并为有序序列 直到所有子序列合并完成\n​\t整体归并排序也用到了很重要的分治思想，也就是将大的问题分为小的问题 逐个解决\n代码 MergeSort ```go func MergeSort(a []int, left int, right int) { //校验 if len(a) \u003c 2 || left \u003c 0 || right \u003e len(a) || left \u003e= right { return } mid := left + (right-left)/2 //数组中间位置 MergeSort(a, left, mid) //左边归并排序 MergeSort(a, mid+1, right) //右边递归排序 MergeSlice(a, left, mid, right) //合并2个子序列为大的有序序列 } func MergeSlice(a []int, left int, mid int, right int) { //先生成1个辅助空间 长度 容量都是right-left+1 help := make([]int, right-left+1, right-left+1) helpIndex := 0 //help数组起始位置 填入一个数值 往后移动一位 //定义2个下标 开始分别指向2个子区间的最开始位置 然后逐个遍历 LIndex := left RIndex := mid + 1 for LIndex \u003c= mid \u0026\u0026 RIndex \u003c= right { if a[LIndex] \u003c= a[RIndex] { //左边区间数值较小 左边进辅助空间 help[helpIndex] = a[LIndex] LIndex++ } else { help[helpIndex] = a[RIndex] RIndex++ } helpIndex++ //不管左边区间进辅助还是右边区间 辅助数组下标下移一个位置 因为必定进了一个数 } for LIndex \u003c= mid { //如果遍历完成 左边区间还有数没放进辅助数组 那就说明剩下的左边区间数较大 依次cp进辅助 help[helpIndex] = a[LIndex] LIndex++ helpIndex++ } for RIndex \u003c= right { //如果遍历完成 左边区间还有数没放进辅助数组 那就说明剩下的左边区间数较大 依次cp进辅助 help[helpIndex] = a[RIndex] RIndex++ helpIndex++ } //辅助空间已经排好序 覆盖填回原数组 for i := 0; i \u003c helpIndex; i++ { a[left+i] = help[i] } } ``` 时间复杂度 ​\tO( NLogN)\n算法稳定性 ​\t稳定\n优化 规模较小的时候 不用归并，改为插排 ​\t递归其实非常消耗性能 规模较小的时候可以不再递归 较少递归调用次数\nMergeSortOpt ```go func MergeSortOpt(a []int, left int, right int) { //一个数 为空 下标不合法 拆分完成 if len(a) \u003c 2 || left \u003c 0 || right \u003e len(a) || left \u003e= right { return } if left+20 \u003e= right {//这里增加几行代码 规模较小 改为插排 InsertSort(a[left : right+1]) return } mid := left + (right-left)/2 //数组中间位置 MergeSort(a, left, mid) //左边归并排序 MergeSort(a, mid+1, right) //右边递归排序 MergeSlice(a, left, mid, right) //合并2个子序列为大的有序序列 } ``` 检查合并前两个数组是否已经有序 没有必要再调用合并了 MergeSortOpt2 ```go func MergeSortOpt2(a []int, left int, right int) { //一个数 为空 下标不合法 拆分完成 if len(a) \u003c 2 || left \u003c 0 || right \u003e len(a) || left \u003e= right { return } if left+20 \u003e= right {//这里增加几行代码 规模较小 改为插排 InsertSort(a[left : right+1]) return } mid := left + (right-left)/2 //数组中间位置 MergeSort(a, left, mid) //左边归并排序 MergeSort(a, mid+1, right) //右边递归排序 if a[mid]\u003c=a[mid+1]{//如果2个子序列本身已经有序 无需再合并 return } MergeSlice(a, left, mid, right) //合并2个子序列为大的有序序列 } ``` 网搜图解 选择排序 介绍 ​\t每轮都选择一个极值(最大或者最小)放到数组的某一端，其实也是分为有序区和无序区，刚开始全是无序区，\n第1轮 遍历N个数 挑选极值放到数组最左侧 有序区有1个数\n第2轮 遍历剩下的N-1个数，挑选极值放入数组第2个位置，也就是依次放入有序区\n\u0026hellip;\n直到剩下最后一个元素 这个元素自然是整个数组的极值 整个数组排序完成\n代码 SelectSort ```go func SelectSort(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } for j := 0; j \u003c len(a)-1; j++ {//控制每轮循环 遍历比较的元素个数 min := j\t//min记录最小元素下标 for i := j + 1; i \u003c len(a); i++ { if a[min] \u003e a[i] { min = i } } a[j], a[min] = a[min], a[j] //将最小元素依次放入有序区 } } ``` 时间复杂度 ​\tO($N^2$)\n算法稳定性 ​\t不稳定 会改变两个相等元素本身的相对位置 如 (7) 2 4 8 3 4 [7] 1 第一轮下来(7)会跑到最后\n优化 ​\t修改内层循环，每一轮遍历 不仅找到最小下标 也要找到最大下标 最小放数组左边，最大放数组右边，减少循环次数，当然外层循环条件也要修改，最开始无序区为整个数组 每一轮下来 数组两端2个元素变为有序，有序区从两端往中间扩大，直到所有元素都为有序\nSelectSortOpt ```go func SelectSortOPT(a []int) { if len(a) \u003c 2 { //一个数或者为空 不用排序 return } //刚开始left right分别为数组最小和最大下标 每轮循环left和rignt分别放置最小和最大值 //终止条件为left==right 每轮循环后left右移 right左移 for left, right := 0, len(a)-1; left \u003c right; left, right = left+1, right-1 { minIndex, maxIndex := left, right for i := left; i \u003c= right; i++ { if a[i] \u003c a[minIndex] { //找到最小值下标 minIndex = i } if a[i] \u003e a[maxIndex] { //找到最大值下标 maxIndex = i } } a[left], a[minIndex] = a[minIndex], a[left]//最小的放当前无序区最左边 if left == maxIndex { //如最大下标就是刚开始的最小下标 因为已经交换到了minIndex位置 所以最大下标也要跟着修改 maxIndex = minIndex } a[maxIndex], a[right] = a[right], a[maxIndex]//最大值放到当前无序区最右边 } } ``` 网搜图解 ​\t摘自： https://www.cnblogs.com/onepixel/p/7674659.html\n堆排序 介绍 二叉堆介绍 堆排序是借助堆这种数据结构进行排序，又分为最大堆和最小堆。堆也分很多种，这里用二叉堆，下面从网上找到的2张图展示下最大堆和最小堆。\n​\t最大堆 所有父节点都**\u0026gt;=两个子节点 最小堆 所有父节点都\u0026lt;=**两个子节点\n​\t最大堆 可用于升序排序 最小堆可用于降序排序\n​\t二叉堆实现方式不止一种，这里选择最简单的数组实现，下图展示二叉堆如何用数组存放以及父子节点关系如何对应到数组下标关系。\n堆排序大致过程 首选遍历数组 构建二叉堆(数组实现)\n交换堆头尾两个元素，也就是数组头尾元素，最大值放到了数组最后一个元素。因为根节点发生变化\n所以重新堆化，范围不包括最后一个元素，最后一个元素相当于已经输出排序完成，为最大值。\n对于重新堆化的前面N-1个元素 循环执行第2步 直到输出所有堆节点 完成最终排序\n代码 最大堆 MaxHeapSort ```go func MaxHeapSort(a []int) { size:=len(a)//数组长度 if size \u003c 2 { return } for i := 0; i \u003c len(a); i++ {//遍历数组 构建堆 MaxHeapInsert(a, i) } for size \u003e 0 { a[0], a[size-1] = a[size-1], a[0] //将当前堆顶也就是最大值放到最后 把最后的元素换到堆顶 然后重塑堆 size-- MaxHeapify(a, 0, size) } } func MaxHeapInsert(a []int, index int) { //如果插入节点大于父节点 则需要向上调整 先跟父节点交换 然后再比较上面的父节点 for parentIndex := (index - 1) / 2; a[index] \u003e a[parentIndex]; index, parentIndex = parentIndex, (index-1)/2 { a[index], a[parentIndex] = a[parentIndex], a[index] } } //大堆 重新堆化过程 func MaxHeapify(a []int, index int, size int) { for maxIndex := -1; maxIndex != index; { maxIndex = index leftIndex := 2*index + 1 rightIndex := 2*index + 2 //求当前节点 左孩子 右孩子中最大值对应的下标 if leftIndex \u003c size \u0026\u0026 a[maxIndex] \u003c a[leftIndex] { maxIndex = leftIndex } if rightIndex \u003c size \u0026\u0026 a[maxIndex] \u003c a[rightIndex] { maxIndex = rightIndex } if maxIndex != index { a[index], a[maxIndex] = a[maxIndex], a[index] //跟左孩子、右孩子中最大的交换 index = maxIndex maxIndex = -1 } } } ``` 最小堆 MinHeapSort ```go func MinHeapSort(a []int) { if len(a) \u003c 2 { return } for i := 0; i \u003c len(a); i++ { MinHeapInsert(a, i) } size := len(a) for size \u003e 0 { a[0], a[size-1] = a[size-1], a[0] //将当前堆顶也就是最大值放到最后 把最后的元素换到堆顶 然后重塑堆 size-- MinHeapify(a, 0, size) } } //MinHeapInsert 创建大堆 数组实现 index为要插入的元素下标 //节点下标为i 对应左孩子为2*i+1 右边孩子为2*i+2 //节点下标为i 对应父节点为(i-1)/2 func MinHeapInsert(a []int, index int) { parentIndex := (index - 1) / 2 for a[index] \u003c a[parentIndex] { //如果插入节点小于父节点 则需要向上调整 先跟父节点交换 然后再比较上面的父节点 a[index], a[parentIndex] = a[parentIndex], a[index] index = parentIndex parentIndex = (index - 1) / 2 } } //MinHeapify 下标index发生了变化 重塑堆 一路向下调整 如果两个孩子中有一个比自己小 则交换 然后继续往下调整找到比自己小的孩子 然后跟其交换 //节点下标为i 对应左孩子为2*i+1 右边孩子为2*i+2 //节点下标为i 对应父节点为(i-1)/2 func MinHeapify(a []int, index int, size int) { for minIndex := -1; minIndex != index; { minIndex = index leftIndex := 2*index + 1 rightIndex := 2*index + 2 //求当前节点 左孩子 右孩子中最小值对应的下标 if leftIndex \u003c size \u0026\u0026 a[minIndex] \u003e a[leftIndex] { minIndex = leftIndex } if rightIndex \u003c size \u0026\u0026 a[minIndex] \u003e a[rightIndex] { minIndex = rightIndex } if minIndex != index { a[index], a[minIndex] = a[minIndex], a[index] //跟左孩子、右孩子中最小的交换 index = minIndex minIndex = -1 } } } ``` ## 时间复杂度 ​\tO(NlogN)\n算法稳定性 ​\t不稳定\n优化 ​\t当前实现的就是原地堆排序，没有使用额外的辅助空间，暂无好的优化思路，待补充\n网搜图解 希尔排序 介绍 ​\t希尔排序是直接插入排序的优化版本，由一个叫shell的人提出来的，核心思想是按照步长分组，然后每组分组插排，然后缩短步长分组，继续每组插排，最后步长为1，变为直接插排。\n​\t关于步长及缩短步长如何选择，有很多种方案，可以直接分半，然后再除以2 最后为1，这里采用的Knuth序列，也就是按照下面的规律递增\ngap=1\u0026mdash;\u0026ndash;\u0026raquo;gap=3*gap+1\n代码 ShellSort ```go func ShellSort(a []int) { //步长采用knuth序列 变化规律为 h=1 ---\u003e h = 3*h+1 h := 1 for h \u003c= len(a)/3 { h = 3*h + 1 } //控制gap递减 最后变为1 for gap := h; gap \u003e 0; gap = (gap - 1) / 3 { //控制分组 for j := gap; j \u003c len(a); j++ { //每组进行直接插排 for i := j; i \u003e gap-1; i = i - gap { if a[i] \u003c a[i-gap] { a[i], a[i-gap] = a[i-gap], a[i] } } } } } ``` 时间复杂度 O($N^3/2$)\n算法稳定性 ​\t不稳定\n优化 ​\t待补充\n网搜图解 快速排序 介绍 ​\t快速排序主要用到了分治和递归思想，跟归并排序差不多，快速排序一般要选择一个基准值(pivot),然后将小于这个基准的放左边，大于这个基准的放右边，基准值放那边无所谓，这样一轮下来，数组分成了2个区域，左边区域比右边区域小，然后对2个区域用递归的方法继续快排。\n​\t这里的快速用荷兰国旗问题分成了3个区域，\u0026lt;pivot | ==pivot | \u0026gt;pivot 然后递归 \u0026lt;pivot 和\u0026gt;pivot的区域 继续分区快排序\n​\t关于基准值的选取可以有很多种，可以随机选取，可以最前面的，可以最后面的，这里采用的是最常见(选取最末端元素)\n代码 QuickSort ```go func QuickSort(a []int, left int, right int) { if len(a) \u003c 2 || left \u003e= right { return } base := a[right] //基准选取最末端元素 equalArea := PartitionIntSlice(a, left, right, base) QuickSort(a, left, equalArea[0]-1) //递归快排小于区间 QuickSort(a, equalArea[1]+1, right) //递归快排大于区间 } //PartitionIntSlice 给定一个数组，左边界left 右边界right 比较基准base //返回一个2个数值的int数组 该数组第一个值为等于base的开始位置 第二个值为等于base的结束位置 //所以下标小于该数组第一个值的区间都小于base 下标大于数组第二个值的区间都大于base func PartitionIntSlice(a []int, left int, right int, base int) [2]int { l := left - 1 //l为小于区间的结束下标 刚开始指向最小下标左边 r := right + 1 //l为大于区间的开始下标 刚开始指向最大下标右边 cur := left //当前遍历的数设置为整个区间最左边 for cur \u003c r { if a[cur] \u003c base { //如果当前数小于基数 当前数和小于区间的下一个数交换 小于区间扩一个 a[cur], a[l+1] = a[l+1], a[cur] l++ cur++ } else if a[cur] \u003e base { //如果当前数大于基数 则cur下标++ a[cur], a[r-1] = a[r-1], a[cur] r-- } else { //当前数跟基数相等 不变 cur++ } } return [2]int{l + 1, r - 1} } ``` 时间复杂度 ​\tO(NlogN)\n算法稳定性 ​\t不稳定\n优化 ​\t可以选择双轴快排序，也就是选择2个base(不相同,相同的话就又变成了荷兰国旗) 分区为 \u0026lt;minbase | minbase\u0026lt;= \u0026amp;\u0026amp; \u0026lt;=maxBase | \u0026gt;maxbase\n代码后续补充\n网搜图解 计数排序 介绍 ​\t计数排序的应用场景比较清晰，也是桶排序的一种。明确的知道一个数组有N的整数，量比较大，但是数据范围比较小 都是[0,MAX), 然后创建一个计数数组，长度为MAX,计数数组值都初始化为0，然后遍历原数组，将原数组的值和计数数组的下标对应起来，比如原数组某个元素值为1，则计数数组下标为1的元素加1，表示1的元素出现过一次，这个步骤可以叫做入桶。然后顺序遍历计数数组，如果该下标的元素出现过(也就是值\u0026gt;0)，数组元素值为多少，则该下标出桶多少次，依次填回原数组即可。\n​\n代码 CountingSort ```go func CountSort(a []int, max int) { if len(a) \u003c 2 { return } count := make([]int, max, max) //创建计数的桶 for i := 0; i \u003c len(a); i++ { count[a[i]]++ } indexOfa := 0 for i := 0; i \u003c len(count); i++ { for count[i] \u003e 0 { a[indexOfa] = i indexOfa++ count[i]-- } } } ``` 时间复杂度 O(N)\n算法稳定性 ​\t直接计数排序本身是不稳定的，如果采用累加计数数组，然后倒序遍历原数组结合累加计数数组 则可以实现成稳定的，下面优化版本给出了一个稳定版本\n优化 ​\t分桶方法可以有很多种，比如0号桶 存放0-9数据 1号桶存放10-19等等都是可以的，每个桶可以再放一个数组 然后对于这个数组进行快排或者插排之类的\n如果某个桶数量太大，可以针对这个桶继续分桶等等 这里不再赘述，后续有兴趣再补充。\n这里列出一个稳定版本的计数排序\nCountSortStable ```go //CountSortStable 桶排序的一种 应用场景 知道一个数组有N个整数 并且范围都是[0 ,MAX) //也就是量大 但是数据范围比较小 稳定版本 采用累加计数数组+倒序遍历原数组 func CountSortStable(a []int, max int) { if len(a) \u003c 2 { return } count := make([]int, max, max) //创建桶 for i := 0; i \u003c len(a); i++ { count[a[i]]++ } //累加计数数组 从下标1开始 其值等于count[i]+count[i-1] for i := 1; i \u003c len(count); i++ { count[i] += count[i-1] //记录原数组元素在原数组出现的最后一个位置 } //然后倒序遍历原数组 这里要用到一个附加数组 help := make([]int, len(a), len(a)) for k := len(a) - 1; k \u003e= 0; k-- { count[a[k]]-- lastIndex := count[a[k]] //这里为了代码好理解 多写一行 help[lastIndex] = a[k] } for i := 0; i \u003c len(help); i++ { a[i] = help[i] } } ``` 网搜图解 基数排序 介绍 ​\t基数排序也是桶排序的一种，主要思想是按照低优先级先排序 然后再按照高优先级再排序，最后完成排序。\n比如整数排序，先按照个位排序，再按照十位排序 再按照百位、千位排序，可以参看图解，比较一目了然\n代码 RadixSort ```go //GetMax 返回数组中的最大值 func GetMax(a []int) int { max := a[0] for i := 1; i \u003c len(a); i++ { if a[i] \u003e max { max = a[i] } } return max } //radixSort 传入按照什么基数排序 1 个位 10十位 100百位... func radixSort(a []int, radix int) { help := make([]int, len(a), len(a)) //无论是个位、十位、百位... 都只有0-9 10个数字 所以准备10个桶 bucket := make([]int, 10, 10) for i := 0; i \u003c len(a); i++ { radixNum := (a[i] / radix) % 10 //得到某个基数位的数字 比如345 传入radix是1 也就是个位数也就是3 bucket[radixNum]++ } //这个for循环完成 也就完成了个位数桶计数 比如bucket[1]=3 也就是个位数是1的数字有3个 for j := 1; j \u003c len(bucket); j++ { bucket[j] += bucket[j-1] } //这个for循环完成 桶计数含义发生改变 bucket[1]=3表示个位数\u003c=1的数字有3个 //倒序遍历原数组 按照基数位排序后输出到辅助数组 for k := len(a) - 1; k \u003e= 0; k-- { bucket[(a[k]/radix)%10]-- help[bucket[(a[k]/radix)%10]] = a[k] } for i := 0; i \u003c len(a); i++ { a[i] = help[i] } } //RadixSort 基数排序 先按照个位排序 再按照10位排序 再按照百位排序 ... func RadixSort(a []int) { max := GetMax(a) for radix := 1; max/radix \u003e 0; radix *= 10 { radixSort(a, radix) //依次按照个位 十位 百位 ...排序 } } ``` 时间复杂度 ​\tO(X*2N) 这里的X 主要是指分了多少个基数 比如个位、十位、百位 那X=3 对于每个基数 内部都至少需要2N的时间复杂度\n算法稳定性 ​\t上面实现的是稳定的 就是采用累加计数 然后倒序遍历数组的方法\n优化 ​\t待补充\n网搜图解 桶排序 介绍 计数排序和基数排序是最常见的2种桶排序思想，不是基于比较的排序思想，桶排序的前提假设大致如下： 假设原数据是大值均匀分布的 量也比较大 在原数据上建立1个函数映射关系 将原数据映射到有限个数的桶上 然后针对每个桶再想办法排序(比如插排、快排等) 最后按照桶顺序依次输出桶里的元素 就完成了整个排序 代码 ​\t这里不写代码了\n时间复杂度 ​\t去掉常数项就是O(N)\n算法稳定性 可以做到稳定\n优化 待补充\n网搜图解 待补充\n","permalink":"https://www.becool.vip/posts/tech/%E6%8E%92%E5%BA%8F_%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/","summary":"\u003ch1 id=\"冒泡排序\"\u003e冒泡排序\u003c/h1\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e​    冒泡排序是一种比较简单的排序，之所以叫冒泡，是因为在两两比较的过程中较大的数就像冒泡一样被换到后面。详细解释：依次比较相邻的两个数，前面的数大于后面的数，则交换，将较大的数挪动到后面\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e第1轮: 比较1 \u0026ndash; N \t\t经过依次相邻两两比较交换 最大的数则放到了最后\u003c/li\u003e\n\u003cli\u003e第2轮: 比较1 \u0026ndash;N-1       经过依次相邻两两比较交换 第2大的数则放到了N-1的位置\u003c/li\u003e\n\u003cli\u003e第N-1轮:比较1 \u0026ndash;\t2\t\t前2个数两两比较交换     整个过程完成\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"代码\"\u003e代码\u003c/h2\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eBubbleSort\u003c/summary\u003e\n```go\nfunc BubbleSort(a []int) {\n\tif len(a) \u003c 2 { //一个数或者为空 不用排序\n\t\treturn\n\t}\n\t//外层循环控控制每轮循环两两比较的最大下标 第1次为N-1 最后一次为1(也就是最前面的2个元素)\n\tfor endPos := len(a) - 1; endPos \u003e 0; endPos-- {\n\t\t//内层循环完成两两比较交换\n\t\tfor i := 0; i \u003c endPos; i++ {\n\t\t\tif a[i] \u003e a[i+1] {\n\t\t\t\ta[i], a[i+1] = a[i+1], a[i]\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\u003c/details\u003e\n\u003ch2 id=\"时间复杂度\"\u003e时间复杂度\u003c/h2\u003e\n\u003cp\u003e​    O($N^2$)\u003c/p\u003e","title":"排序_数据结构与算法"},{"content":"搭建hugo静态博客记录 1 安装Hugo 我这里是imac所以就直接用brew 其它操作系统也很简单 google\nbrew install hugo 2 初始化站点目录 先cd到你想放置的磁盘目录 然后执行一下命令即可 会在当前目录创建站点名称同名目录\n$ hugo new site blog $ cd blog 3 安装主题并修改 现在hugo主题商店挺多，这里选择了相对干净的PaperMod 如果没有git环境 可自行百度安装\n$ git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1 config.yml 配置文件主要修改项\nbaseURL: http://heketong.github.io/ # 绑定的域名 languageCode: zh # en-us title: 疯子爱淡定 theme: hugo-PaperMod # 主题名字，和themes文件夹下的一致 enableInlineShortcodes: true enableEmoji: true # 允许使用 Emoji 表情，建议 true enableRobotsTXT: true # 允许爬虫抓取到搜索引擎，建议 true hasCJKLanguage: true # 自动检测是否包含 中文日文韩文 如果文章中使用了很多中文引号的话可以开启 buildDrafts: false buildFuture: false buildExpired: false googleAnalytics: # 谷歌统计 # Copyright: Sulv paginate: 15 # 首页每页显示的文章数 summaryLength: 140 # 文章概览的自字数，默认70 minify: disableXML: true # minifyOutput: true permalinks: # 访问博客时网址的显示形式 post: \u0026#34;/:title/\u0026#34; # post: \u0026#34;/:year/:month/:day/:title/\u0026#34; defaultContentLanguage: zh # 最顶部首先展示的语言页面 defaultContentLanguageInSubdir: false menu: main: - identifier: search name: 🔍 搜索 url: search weight: 1 - identifier: home name: 🏠 主页 url: / weight: 2 - identifier: posts name: 📚 文章 url: posts weight: 3 - identifier: tags name: 🧩 标签 url: tags weight: 15 - name: 🧩 分类 url: /categories/ - identifier: archives name: ⏱️ 时间轴 url: archives/ weight: 20 - identifier: about name: 🙋🏻‍♂️ 关于 url: about weight: 50 - identifier: links name: 🤝 友链 url: links weight: 60 outputs: home: - HTML - RSS - JSON # 这里的参数会被代码以 .Site.Params 的形式读取 params: env: production # to enable google analytics, opengraph, twitter-cards and schema. description: \u0026#34;\u0026#34; author: becool # author: [\u0026#34;Me\u0026#34;, \u0026#34;You\u0026#34;] # multiple authors defaultTheme: auto # defaultTheme: light or dark disableThemeToggle: false DateFormat: \u0026#34;2006-01-02\u0026#34; ShowShareButtons: true ShowReadingTime: true # disableSpecialistPost: true displayFullLangName: true ShowPostNavLinks: true ShowBreadCrumbs: true ShowCodeCopyButtons: true hideFooter: false # 隐藏页脚 ShowWordCounts: true VisitCount: true ShowLastMod: true #显示文章更新时间 ShowToc: true # 显示目录 TocOpen: true # 自动展开目录 extendCodeBackground: false # 代码块是否自动横向展开 comments: true profileMode: enabled: true title: Be Cool subtitle: \u0026#34;每个不曾起舞的日子,都是对生命的辜负.\u0026lt;br/\u0026gt;👇联系方式\u0026#34; imageUrl: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; imageTitle: imageWidth: 150 imageHeight: 150 buttons: - name: 🧱 音乐 url: posts/music - name: 👨🏻‍💻 技术 url: posts/tech - name: 📕 阅读提升 url: posts/read - name: 🏖 生活 url: posts/life socialIcons: - name: github title: View Source on Github url: \u0026#34;https://github.com/heketong\u0026#34; - name: QQ url: \u0026#34;img/qq.jpg\u0026#34; - name: WeChat url: \u0026#34;img/wechat.jpg\u0026#34; - name: email url: \u0026#34;mailto:18929579649@163.com\u0026#34; - name: RSS url: \u0026#34;index.xml\u0026#34; label: # 左上角图标 text: \u0026#34;疯子爱淡定\u0026#34; icon: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; iconHeight: 35 analytics: google: SiteVerificationTag: \u0026#34;\u0026#34; assets: favicon: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; favicon16x16: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; favicon32x32: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; apple_touch_icon: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; safari_pinned_tab: \u0026#34;img/heketong_profile_photo.JPG\u0026#34; disableFingerprinting: true # 禁用指纹 cover: hidden: false # hide everywhere but not in structured data hiddenInList: false # hide on list pages and home hiddenInSingle: false # hide on single page fuseOpts: # 搜索配置 isCaseSensitive: false shouldSort: true location: 0 distance: 1000 threshold: 0.4 minMatchCharLength: 0 keys: [ \u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34; ] StartYear: 2020 # 底部开始年份 FilingNo: 粤ICP备2022127626号-1 # 底部备案号 Reward: true #打赏是否开启 # 打赏微信图片地址， # 可用绝对和相对地址，相对地址的图片需放在static/img下， # 填写格式为img/wechat_pay.png WechatPay: img/wechatpayimg.JPG Alipay: img/alipayimg.JPG #打赏支付宝图片地址 # twikoo评论 twikoo: version: 1.5.8 # 填写twikoo版本号 id: # 填写自己的twikoo id region: # 环境地域，默认为 ap-shanghai，如果您的环境地域不是上海，需传此参数，请看twikoo官方文档 taxonomies: category: categories tag: tags series: series markup: goldmark: renderer: unsafe: true # HUGO 默认转义 Markdown 文件中的 HTML 代码，如需开启的话 highlight: # anchorLineNos: true codeFences: true guessSyntax: true # lineNos: true noClasses: true tabWidth: 4 style: monokai # codeFences：代码围栏功能，这个功能一般都要设为 true 的，不然很难看，就是干巴巴的-代码文字，没有颜色。 # guessSyntax：猜测语法，这个功能建议设置为 true, 如果你没有设置要显示的语言则会自动匹配。 # hl_Lines：高亮的行号，一般这个不设置，因为每个代码块我们可能希望让高亮的地方不一样。 # lineNoStart：行号从编号几开始，一般从 1 开始。 # lineNos：是否显示行号，我比较喜欢显示，所以我设置的为 true. # lineNumbersInTable：使用表来格式化行号和代码,而不是 标签。这个属性一般设置为 true. # noClasses：使用 class 标签，而不是内嵌的内联样式 privacy: vimeo: disabled: false simple: true twitter: disabled: false enableDNT: true simple: true instagram: disabled: false simple: true youtube: disabled: false privacyEnhanced: true services: instagram: disableInlineCSS: true twitter: disableInlineCSS: true 4 写markdown文章 $ hugo new posts/create_hugo_blog /Users/ketonghe/blog/content/posts/create_hugo_blog 用markdown编辑器编辑文章\n5 发布预览 $ hugo server -D | ZH +------------------+----+ Pages | 13 Paginator pages | 0 Non-page files | 0 Static files | 9 Processed images | 0 Aliases | 6 Sitemaps | 1 Cleaned | 0 Total in 91 ms Watching for changes in /Users/ketonghe/blog/{archetypes,content,data,layouts,static,themes} Watching for config changes in /Users/ketonghe/blog/config.yml Environment: \u0026#34;development\u0026#34; Serving pages from memory Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop 接下来就可以在本地浏览器 输入http://localhost:1313/ 访问了\n这个主题支持站内搜索 还不错\n6 添加评论支持 https://becool.vip/posts/tech/hugo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0tkikoo%E8%AF%84%E8%AE%BA/\n7 生成静态页面 这里可以用github的action自动部署\nname: CI #自动化的名称 on: push: # push的时候触发 branches: # 那些分支需要触发 - master jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: submodules: true fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#34;latest\u0026#34; - name: Build Web run: hugo - name: Deploy Web uses: peaceiris/actions-gh-pages@v4 with: PERSONAL_TOKEN: ${{ secrets.ACTIONS_DEPLOY_KEY }} EXTERNAL_REPOSITORY: heketong/heketong.github.io PUBLISH_BRANCH: master PUBLISH_DIR: ./public ","permalink":"https://www.becool.vip/posts/tech/create_hugo_blog/","summary":"\u003ch1 id=\"搭建hugo静态博客记录\"\u003e搭建hugo静态博客记录\u003c/h1\u003e\n\u003ch2 id=\"1-安装hugo\"\u003e1 安装Hugo\u003c/h2\u003e\n\u003cp\u003e我这里是imac所以就直接用brew 其它操作系统也很简单 google\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebrew install hugo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"2-初始化站点目录\"\u003e2 初始化站点目录\u003c/h2\u003e\n\u003cp\u003e先cd到你想放置的磁盘目录 然后执行一下命令即可 会在当前目录创建站点名称同名目录\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ hugo new site blog                                       \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cd blog\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"3-安装主题并修改\"\u003e3 安装主题并修改\u003c/h2\u003e\n\u003cp\u003e现在hugo主题商店挺多，这里选择了相对干净的PaperMod  如果没有git环境 可自行百度安装\u003c/p\u003e","title":"hugo搭建博客"},{"content":"相关历史及分成模型 历史介绍 1969年 美国担心敌人会摧毁自己的网络，所以国防部高级研究计划局（Advanced Research Projects Agency，ARPA）下决心要建立一个高可用的网络，即使部分线路或者交换机的故障不会导致整个网络的瘫痪，于是有了后来的ARPANET（Advanced Research Project Agency Network）\u0026mdash;最早只是一个简单的分组交换网 经过不断发展，原始的ARPANET慢慢变成了多个网络互联，逐步促成了互联网的出现。 1983年TCP/IP 协议成为 ARPANET 上的标准协议，现在互联网世界这么繁荣都得意与TCP/IP协议，当然任何一个行业越是繁荣昌盛，就越是有良好的协议标准，接口标准，TCP/IP就是网际互联中最流行的协议标准，也正是因为其流行，互联网才能越发发达。 分层模型 一般都会提到7层 4层 或者 5层，下面给出一张图做个简单对比 ​\t左边是ISO/OSI的7层模型，分的更细，一般我们常说的右边的4层，咱们从上到下分层说\n应用层 Application Layer 应用层的本质是规定了应用程序之间如何相互传递报文，处理特定的应用程序细节\n我们常说的FTP、HTTP、SMTP、NFS、SNMP、Telnet都是应用层的协议，就拿HTTP来说：\n规定了报文的类型 是请求报文还是应答报文 每段报文具体什么语法 有多少段，怎么才算结束(\\r\\n) 进程应该以什么样的顺序发送或者接收报文 \u0026hellip;\u0026hellip;\u0026hellip;. 大部分应用层协议都有RFC文档定义(Request for Comments，缩写：RFC,由互联网工程任务组（IETF）发布的一系列备忘录。文件收集了有关互联网相关信息，以及UNIX和互联网社群的软件文件，以编号排定。目前RFC文件是由互联网协会（ISOC）赞助发行)\n传输层 Transport Layer 主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议族中，有两个互不相同的传输协议：TCP（传输控制协议）和UDP（用户数据报协议）。\nTCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层，确认接收到的分组，设置发送最后确认分组的超时时钟等。\n由于传输层TCP提供了高可靠性的端到端的通信，因此应用层可以忽略所有这些细节。为了提供可靠的服务，TCP采用了超时重传、发送和接收端到端的确认分组等机制。\nUDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机，但并不保证该数据报能到达另一端。一个数据报是指从发送方传输到接收方的一个信息单元（例如，发送方指定的一定字节数的信息）。UDP协议任何必需的可靠性必须由应用层来提供。\n端口 这里必须提下端口的概念，主要为了区分一台主机上不同的应用程序，比如80端口一般就是http应用的端口，21一般是ftp应用的端口，知道了端口就知道数据包到时候要送给哪个应用程序 网络层/网际层/网络互联层 Internet Layer 网络互连层提供了主机到主机的通信，将传输层产生的的数据包封装成分组数据包发送到目标主机，并提供路由选择的能力。\n处理分组在网络中的活动，例如分组的选路。\n在TCP/IP协议族中，网络层协议包括IP协议（网际协议），\nICMP协议（Internet互联网控制报文协议），\nIGMP协议（Internet组管理协议）。\nIP是一种网络层协议，提供的是一种不可靠的服务，它只是尽可能快地把分组从源结点送到目的结点，但是并不提供任何可靠性保证。同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互联网中进行传输。\nICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。\nIGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个主机。\n链路层/网络接口层/网络访问层 Network Access Layer 也就是上图右边的网络接口层和硬件层，通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆（或其他任何传输媒介）的物理接口细节。ARP（地址解析协议）和RARP（逆地址解析协议）是某些网络接口（如以太网和令牌环网）使用的特殊协议，用来转换IP层和网络接口层使用的地址。\n以太网、Wifi、蓝牙工作在这一层，网络访问层提供了主机连接到物理网络需要的硬件和相关的协议\n为什么要分层 分层的本质是通过分离关注点而让复杂问题简单化，通过分层可以做到：\n各层独立：限制了依赖关系的范围，各层之间使用标准化的接口，各层不需要知道上下层是如何工作的，增加或者修改一个应用层协议不会影响传输层协议 灵活性更好：比如路由器不需要应用层和传输层，分层以后路由器就可以只用加载更少的几个协议层 易于测试和维护：提高了可测试性，可以独立的测试特定层，某一层有了更好的实现可以整体替换掉 能促进标准化：每一层职责清楚，方便进行标准化 抓个包瞄一瞄 这里抓一个http报文看看 直接后台 curl http://www.baidu.com 用到工具 Wireshark wireshark设置只抓百度服务器的报文 后台运行curl $ curl http://www.baidu.com \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;!--STATUS OK--\u0026gt;\u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;meta http-equiv=content-type content=text/html;charset=utf-8\u0026gt;\u0026lt;meta http-equiv=X-UA-Compatible content=IE=Edge\u0026gt;\u0026lt;meta content=always name=referrer\u0026gt;\u0026lt;link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css\u0026gt;\u0026lt;title\u0026gt;百度一下，你就知道\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body link=#0000cc\u0026gt; \u0026lt;div id=wrapper\u0026gt; \u0026lt;div id=head\u0026gt; \u0026lt;div class=head_wrapper\u0026gt; \u0026lt;div class=s_form\u0026gt; \u0026lt;div class=s_form_wrapper\u0026gt; \u0026lt;div id=lg\u0026gt; \u0026lt;img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;form id=form name=f action=//www.baidu.com/s class=fm\u0026gt; \u0026lt;input type=hidden name=bdorz_come value=1\u0026gt; \u0026lt;input type=hidden name=ie value=utf-8\u0026gt; \u0026lt;input type=hidden name=f value=8\u0026gt; \u0026lt;input type=hidden name=rsv_bp value=1\u0026gt; \u0026lt;input type=hidden name=rsv_idx value=1\u0026gt; \u0026lt;input type=hidden name=tn value=baidu\u0026gt;\u0026lt;span class=\u0026#34;bg s_ipt_wr\u0026#34;\u0026gt;\u0026lt;input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;span class=\u0026#34;bg s_btn_wr\u0026#34;\u0026gt;\u0026lt;input type=submit id=su value=百度一下 class=\u0026#34;bg s_btn\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div id=u1\u0026gt; \u0026lt;a href=http://news.baidu.com name=tj_trnews class=mnav\u0026gt;新闻\u0026lt;/a\u0026gt; \u0026lt;a href=http://www.hao123.com name=tj_trhao123 class=mnav\u0026gt;hao123\u0026lt;/a\u0026gt; \u0026lt;a href=http://map.baidu.com name=tj_trmap class=mnav\u0026gt;地图\u0026lt;/a\u0026gt; \u0026lt;a href=http://v.baidu.com name=tj_trvideo class=mnav\u0026gt;视频\u0026lt;/a\u0026gt; \u0026lt;a href=http://tieba.baidu.com name=tj_trtieba class=mnav\u0026gt;贴吧\u0026lt;/a\u0026gt; \u0026lt;noscript\u0026gt; \u0026lt;a href=http://www.baidu.com/bdorz/login.gif?login\u0026amp;amp;tpl=mn\u0026amp;amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb\u0026gt;登录\u0026lt;/a\u0026gt; \u0026lt;/noscript\u0026gt; \u0026lt;script\u0026gt;document.write(\u0026#39;\u0026lt;a href=\u0026#34;http://www.baidu.com/bdorz/login.gif?login\u0026amp;tpl=mn\u0026amp;u=\u0026#39;+ encodeURIComponent(window.location.href+ (window.location.search === \u0026#34;\u0026#34; ? \u0026#34;?\u0026#34; : \u0026#34;\u0026amp;\u0026#34;)+ \u0026#34;bdorz_come=1\u0026#34;)+ \u0026#39;\u0026#34; name=\u0026#34;tj_login\u0026#34; class=\u0026#34;lb\u0026#34;\u0026gt;登录\u0026lt;/a\u0026gt;\u0026#39;);\u0026lt;/script\u0026gt; \u0026lt;a href=//www.baidu.com/more/ name=tj_briicon class=bri style=\u0026#34;display: block;\u0026#34;\u0026gt;更多产品\u0026lt;/a\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div id=ftCon\u0026gt; \u0026lt;div id=ftConw\u0026gt; \u0026lt;p id=lh\u0026gt; \u0026lt;a href=http://home.baidu.com\u0026gt;关于百度\u0026lt;/a\u0026gt; \u0026lt;a href=http://ir.baidu.com\u0026gt;About Baidu\u0026lt;/a\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;p id=cp\u0026gt;\u0026amp;copy;2017\u0026amp;nbsp;Baidu\u0026amp;nbsp;\u0026lt;a href=http://www.baidu.com/duty/\u0026gt;使用百度前必读\u0026lt;/a\u0026gt;\u0026amp;nbsp; \u0026lt;a href=http://jianyi.baidu.com/ class=cp-feedback\u0026gt;意见反馈\u0026lt;/a\u0026gt;\u0026amp;nbsp;京ICP证030173号\u0026amp;nbsp; \u0026lt;img src=//www.baidu.com/img/gs.gif\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 观察Wireshark抓包情况 我们针对编号4的http请求报文 点击下面的详细简单看看\nhttp应用层 起始行（start line），起始行根据是请求报文还是响应报文分为「请求行」和「响应行」。这个例子中起始行是GET / HTTP/1.1，表示这是一个 GET 请求，请求的 URL 为/，协议版本为HTTP 1.1，起始行最后会有一个空行CRLF（\\r\\n)与下面的首部分隔开 首部（header），首部采用形如key:value的方式，比如常见的User-Agent、ETag、Content-Length都属于 HTTP 首部，每个首部直接也是用空行分隔 可选的实体（entity），实体是 HTTP 真正要传输的内容，比如下载一个图片文件，传输的一段 HTML等 传输层 传输层两端的端口 代表两端的程序\n网际互联 TCP概述 面向连接 面向连接的协议要求正式发送数据之前需要通过3次「握手」建立一个逻辑连接，结束通信时也是通过有序的四次挥手来断开连接 可靠的 IP 是一种无连接、不可靠的协议：它尽最大可能将数据报从发送者传输给接收者，但并不保证包到达的顺序会与它们被传输的顺序一致，也不保证包是否重复，甚至都不保证包是否会达到接收者。\nTCP 要想在 IP 基础上构建可靠的传输层协议，必须有一个复杂的机制来保障可靠性。 主要有下面几个方面：\n对每个包提供校验和 每个 TCP 包首部中都有两字节用来表示校验和，防止在传输过程中有损坏。如果收到一个校验和有差错的报文，TCP 不会发送任何确认直接丢弃它，等待发送端重传 包的序列号解决了接收数据的乱序、重复问题 接收端会根据序列号排序 去重 超时重传 TCP 发送数据后会启动一个定时器，等待对端确认收到这个数据包。如果在指定的时间内没有收到 ACK 确认，就会重传数据包，然后等待更长时间，如果还没有收到就再重传，在多次重传仍然失败以后，TCP 会放弃这个包。 流量控制、拥塞控制 待补充 面向字节流的协议 流的含义就是没有边界，所以应用要自己界定边界\n假设你调用 2 次 write 函数往 socket 里依次写 500 字节、800 字节。write 函数只是把字节拷贝到内核缓冲区，最终会以多少条报文发送出去是不确定的，如下图所示：\n简单解释：\n情况 1：分为两条报文依次发出去 500 字节 和 800 字节数据 情况 2：两部分数据合并为一个长度为 1300 字节的报文，一次发送 情况 3：第一部分的 500 字节与第二部分的 500 字节合并为一个长度为 1000 字节的报文，第二部分剩下的 300 字节单独作为一个报文发送 情况 4：第一部分的 400 字节单独发送，剩下100字节与第二部分的 800 字节合并为一个 900 字节的包一起发送。 情况 N：还有更多可能的拆分组合 上面出现的情况取决于诸多因素：路径最大传输单元 MTU、发送窗口大小、拥塞窗口大小等。\n当接收方从 TCP 套接字读数据时，它是没法得知对方每次写入的字节是多少的。接收端可能分2 次每次 650 字节读取，也有可能先分三次，一次 100 字节，一次 200 字节，一次 1000 字节进行读取\n全双工的协议 在 TCP 中发送端和接收端可以是客户端/服务端，也可以是服务器/客户端，通信的双方在任意时刻既可以是接收数据也可以是发送数据，每个方向的数据流都独立管理序列号、滑动窗口大小、MSS 等信息 概述总图： ​\tTcp头部 示意图 源端口号、目标端口号 用wireshare抓包 点击tcp报文 就会出现源端口号( Src Port)和目标端口号(Dst Port), TCP报文是没有ip地址的，ip地址要去IP报文查看，示意图如下：\nTCP连接4元组 源IP、源端口、目标IP、目标端口。一个4元组可以唯一标示一个tcp连接 序列号 Seq Tcp是面向字节流的协议，通过tcp协议传输的字节流都为其分配了序列号，序列号指的是本报文第一个字节的序列号\n序列号+报文长度就可以确定传输的是哪一段数据\n在SYN报文中 序列号用于交换彼此的初始序列号，在其它报文中，序列号用于保证包的顺序 因为IP层不保证包的顺序，所以这个保证主要是tcp层靠序列号保证的，如果发送方发送的是四个报文序列号分别是1、2、3、4，但到达接收方的顺序是 2、4、3、1，接收方就可以通过序列号的大小顺序组装出原始的数据 连接三次握手 初始化序列号(Initial Sequence Number) ​\t在建立连接之初，通信双方都会各自选择一个序列号，称之为初始序列号。在建立连接时，通信双方通过 SYN 报文交换彼此的 ISN，如下图所示。\n服务端的2、3两步一般合并发送，所以就变成了下面的三次握手过程\n确认号 Ack Ack的含义是告知对方 小于此Ack号的所有字节都已经收到，期望对方下次开始从Ack字节开始发送报文吧。\n关于确认号有几个注意点：\n不是所有的包都需要确认的 不是收到了数据包就立马需要确认的，可以延迟一会再确认 ACK 包本身不需要被确认，否则就会无穷无尽死循环了 确认号永远是表示小于此确认号的字节都已经收到 校验和 checksum 每个 TCP 包首部中都有两字节用来表示校验和，防止在传输过程中有损坏。如果收到一个校验和有差错的报文，TCP 不会发送任何确认直接丢弃它，等待发送端重传。\nTCP Flags 我们通常所说的 SYN、ACK、FIN、RST 其实只是把 flags 对应的 bit 位置为 1 而已，这些标记可以组合使用，比如 SYN+ACK，FIN+ACK 等\n最常见的有下面这几个：\nSYN（Synchronize）：用于发起连接数据包同步双方的初始序列号\nACK（Acknowledge）：确认数据包\nRST（Reset）：这个标记用来强制断开连接，通常是之前建立的连接已经不在了、包不合法、或者实在无能为力处理\nFIN（Finish）：通知对方我发完了所有数据，准备断开连接，后面我不会再发数据包给你了。\nPSH（Push）：告知对方这些数据包收到以后应该马上交给上层应用，不能缓存起来\n窗口大小 windows size 可以看到用于表示窗口大小的\u0026quot;Window Size\u0026quot; 只有 16 位，可能 TCP 协议设计者们认为 16 位的窗口大小已经够用了，也就是最大窗口大小是 65535 字节（64KB）。就像网传盖茨曾经说过：“640K内存对于任何人来说都足够了”一样。\n自己挖的坑当然要自己填，因此TCP 协议引入了「TCP 窗口缩放」选项 作为窗口缩放的比例因子，比例因子值的范围是 0 ~ 14，其中最小值 0 表示不缩放，最大值 14。比例因子可以将窗口扩大到原来的 2 的 n 次方，比如窗口大小缩放前为 1050，缩放因子为 7，则真正的窗口大小为 1050 * 128 = 134400，如下图所示\nwireshark可以抓包看到窗口大小。\n值得注意的是，窗口缩放值在三次握手的时候指定，如果抓包的时候没有抓到 SYN 包，wireshark 是不知道真正的窗口缩放值是多少的\n可选项、填充 格式: 种类(Kind) 1byte,长度(Length)1 byte,值(value)\n常用的可选项：\nMSS：最大段大小选项，是 TCP 允许的从对方接收的最大报文段 SACK：选择确认选项 Window Scale：窗口缩放选项 MSS抓包示意图\nMTU、MSS MTU(Maximum Transmission Unit) 最大传输单元 MTU是在数据链路层限制的，数据链路层按照帧传输，每一帧因为协议限制也有大小限制，所以IP层不能把一个太大的包直接塞给链路层，这个限制叫做MTU。 以太网帧格式 除去14字节头和4字节CRC校验 有效数据荷载范围为 46\u0026mdash;\u0026gt;1500,这个就是以太网的MTU 假设以太网MTU为1500 传输100Kb数据，至少需要(100*1024/1500)=69帧 不同的数据链路层对应的MTU是不同的，可以通过netstat -i查看对应网卡的mtu，普通以太网一般是1500 IP分段 IP头格式\nIPv4数据报最大大小为65535字节，远远大于以太网的MTU，有些网络还会开启巨帧模式(Jumbo Frame)，可以得到9000字节。所以当一个IP数据报大于MTU时，IP层就会把报文切割为多个小的片段(小于MTU)，从而使得这些报文能够在数据链路层传输。\n​ 一个大包\u0026mdash;\u0026ndash;\u0026gt;拆成多个小包 放入数据链路层\n上面图片有个分片偏移量，就是用来表示该分段在原始数据报文中的位置，如果用wireshark抓报要乘以8. 抓包看下IP分段 ping -s 3000 www.baidu.com\nman ping可以看到ping -s命令会增加8个字节的ICMP都，所以其实是发送3008字节\nwireshark抓包截图\n第一个包\n​ ​\t这个包是 IP 分段包的第一个分片，More fragments: Set表示这个包是 IP 分段包的一部分，还有其它的分片包，Fragment offset: 0表示分片偏移量为 0，IP 包的 payload 的大小为 1480，加上 20 字节的头部正好是 1500.\n第2个包\n同样More fragments处于 set 状态，表示后面还有其它分片，Fragment offset: 185这里并不是表示分片偏移量为 185，wireshark 这里显示的时候除以了 8，真实的分片偏移量为 185 * 8 = 1480\n第3个包\n可以看到More fragments处于 Not set 状态，表示这是最后一个分片了。Fragment offset: 370表示偏移量为 370 * 8 = 2960，包的大小为 68 - 20（IP 头部大小） = 48\n3个包简单汇总示意\n有一种攻击就是IP fragment attack，一直传More fragments = 1的包，导致接收方一直缓存分片，从而可能导致接收方内存耗尽。\n路径MTU 一个包从发送端到最后的接收端要经过各种各样的网络，每个网络的MTU都可能不一样，所以整个通道上最小的MTU就称为路径MTU(Path MTU) Tcp最大段大小 MSS(Max Segment Size) tcp为了避免被分片，会主动将数据分割成小段后，然后再交给网络层，最大的分段大小称为MSS。\nMSS=MTU-sizeof(ip header)-sizeof(tcp header)\n以太网中TCP的MSS=1500-20(ip header)-20(tcp header)=1460\nsocket选项 TCP_MAXSEG TCP 有一个 socket 选项 TCP_MAXSEG，可以用来设置此次连接的 MSS，如果设置了这个选项，则 MSS 不能超过这个值\nint tcp_maxseg = mss; socklen_t tcp_maxseg_len = sizeof(tcp_maxseg); // 设置 TCP_MAXSEG 选项 if ((err = setsockopt(server_fd, IPPROTO_TCP, TCP_MAXSEG, \u0026amp;tcp_maxseg, tcp_maxseg_len)) \u0026lt; 0) { error_quit(\u0026#34;set TCP_MAXSEG failed, code: %d\\n\u0026#34;, err); } 端口Port 端口主要用来区别一个主机上的各个应用程序，一台主机最大允许65536个端口号 分层结构中 每一层都有一个唯一标示 tcp是端口，ip层是ip地址，链路层为mac地址 熟知端口号(well-known port) ​\t知端口号由专门的机构由 IANA 分配和控制，范围为 0~1023。为了能让客户端能随时找到自己，服务端程序的端口必须要是固定的。很多熟知端口号已经被用就分配给了特定的应用，比如 HTTP 使用 80端口，HTTPS 使用 443 端口，ssh 使用 22 端口。 访问百度http://www.baidu.com/，其实就是向百度服务器之一（163.177.151.110）的 80 端口发起请求.\n​\t在linux上如果要监听这些端口 需要Root权限，熟知端口又是也被称为保留端口\n已登机的端口(registered port) ​\t已登记的端口不受 IANA 控制，不过由 IANA 登记并提供它们的使用情况清单。它的范围为 1024～49151。\n为什么是 49151 这样一个魔数？ 其实是取的端口号最大值 65536 的 3/4 减 1 （49151 = 65536 * 0.75 - 1）。可以看到已登记的端口占用了大约 75% 端口号的范围。\n已登记的端口常见的端口号有：\nMySQL：3306 Redis：6379 MongoDB：27017 熟知和已登机端口可以在iana官网查到\n临时端口(ephemeral port) ​\t如果应用程序没有调用 bind() 函数将 socket 绑定到特定的端口上，那么 TCP 和 UDP 会为该 socket 分配一个唯一的临时端口。IANA 将 49152～65535 范围的端口称为临时端口（ephemeral port）或动态端口（dynamic port），也称为私有端口（private port），这些端口可供本地应用程序临时分配端口使用。\n不同的操作系统实现会选择不同的范围分配临时端口，在 Linux 上能分配的端口范围由 /proc/sys/net/ipv4/ip_local_port_range 变量决定，一般 Linux 内核端口范围为 32768~60999\ncat /proc/sys/net/ipv4/ip_local_port_range 32768 60999 在需要主动发起大量连接的服务器上（比如网络爬虫、正向代理）可以调整 ip_local_port_range 的值，允许更多的可用端口。\n调用bind函数 但不指定端口 也会分配临时端口\n调用connect函数也会分配临时端口\n临时端口可能耗尽，届时会创建应用失败\n修改临时端口范围 ketonghe@ubuntu:~$ sudo sysctl -w net.ipv4.ip_local_port_range=\u0026#34;50001 50001\u0026#34; net.ipv4.ip_local_port_range = 50001 50001 启动一个客户端连接 ketonghe@ubuntu:~$ netstat -anpl|grep 8787 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 192.168.3.66:8787 0.0.0.0:* LISTEN 81014/./server tcp 0 0 192.168.3.66:8787 192.168.3.66:50001 ESTABLISHED 81014/./server tcp 0 0 192.168.3.66:50001 192.168.3.66:8787 ESTABLISHED 81016/./client 再启动一个客户端连接\nketonghe@ubuntu:~/code$ ./client connect error,procedure will exit: Cannot assign requested address 没有临时端口 报错 ------调用系统命令 strace 查看程序执行的系统调用 就会发现connect报错 EADDRNOTAVAIL strace ./client|grep connect .... socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 connect(3, {sa_family=AF_INET, sin_port=htons(8787), sin_addr=inet_addr(\u0026#34;192.168.3.66\u0026#34;)}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address) dup(2) = 4 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) fstat(4, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0 write(4, \u0026#34;connect error,procedure will exi\u0026#34;..., 67connect error,procedure will exit: Cannot assign requested address ) = 67 close(4) = 0 exit_group(0) = ? +++ exited with 0 +++ 端口相关命令 nc telnet 可以查看对应端口是否打开(是否有对应应用已经启动并占用这个端口)\ntelnet 10.211.55.12 6379 Trying 10.211.55.12... Connected to 10.211.55.12. Escape character is \u0026#39;^]\u0026#39;. ------------------- nc -v 10.211.55.12 6379 Ncat: Connected to 10.211.55.12:6379 telnet 10.211.55.12 6380 Trying 10.211.55.12... telnet: connect to address 10.211.55.12: Connection refused nc -v 127.0.0.1 1234 nc: connectx to 127.0.0.1 port 1234 (tcp) failed: Connection refused netstat lsof 查看端口被什么进程占用\nketonghe@ubuntu:~$ netstat -ltpn | grep 8787 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 192.168.3.66:8787 0.0.0.0:* LISTEN 80550/./server ketonghe@ubuntu:~$ sudo lsof -n -P -i:8787 [sudo] password for ketonghe: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME server 80550 ketonghe 3u IPv4 1098588 0t0 TCP 192.168.3.66:8787 (LISTEN) ---- 其中 -n 表示不将 IP 转换为 hostname，-P 表示不将 port number 转换为 service name，-i:port 表示端口号为 22 的进程 找到进程监听的端口号\nps找到进程id\nketonghe@ubuntu:~$ sudo netstat -atpn | grep 80550 tcp 0 0 192.168.3.66:8787 0.0.0.0:* LISTEN 80550/./server ------------------------------------------------------------- sudo lsof -n -P -p 80550 不grep过滤 会显示占用的文件具柄 lsof: WARNING: can\u0026#39;t stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs Output information may be incomplete. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME server 80550 ketonghe cwd DIR 8,1 4096 1184987 /home/ketonghe/code server 80550 ketonghe rtd DIR 8,1 4096 2 / server 80550 ketonghe txt REG 8,1 13792 1184981 /home/ketonghe/code/server server 80550 ketonghe mem REG 8,1 2030544 132349 /lib/x86_64-linux-gnu/libc-2.27.so server 80550 ketonghe mem REG 8,1 170960 132345 /lib/x86_64-linux-gnu/ld-2.27.so server 80550 ketonghe 0u CHR 136,0 0t0 3 /dev/pts/0 server 80550 ketonghe 1u CHR 136,0 0t0 3 /dev/pts/0 server 80550 ketonghe 2u CHR 136,0 0t0 3 /dev/pts/0 server 80550 ketonghe 3u IPv4 1098588 0t0 TCP 192.168.3.66:8787 (LISTEN) server 80550 ketonghe 4u a_inode 0,14 0 11406 [eventpoll] ketonghe@ubuntu:~$ sudo lsof -n -P -p 80550|grep TCP //只看TCP协议 lsof: WARNING: can\u0026#39;t stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs Output information may be incomplete. server 80550 ketonghe 3u IPv4 1098588 0t0 TCP 192.168.3.66:8787 (LISTEN) ------------------------------------------------------------- ketonghe@ubuntu:/proc/80550/fd$ l /proc/80550/fd total 0 lrwx------ 1 ketonghe ketonghe 64 Aug 3 21:54 4 -\u0026gt; \u0026#39;anon_inode:[eventpoll]\u0026#39;//这个为epollfd lrwx------ 1 ketonghe ketonghe 64 Aug 3 21:54 3 -\u0026gt; \u0026#39;socket:[1098588]\u0026#39;//监听fd 1098588为socket的inode lrwx------ 1 ketonghe ketonghe 64 Aug 3 21:54 2 -\u0026gt; /dev/pts/0\t//stderr lrwx------ 1 ketonghe ketonghe 64 Aug 3 21:54 1 -\u0026gt; /dev/pts/0\t//stdout lrwx------ 1 ketonghe ketonghe 64 Aug 3 21:54 0 -\u0026gt; /dev/pts/0 //stdin ketonghe@ubuntu:/proc/80550/fd$ cat /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm-\u0026gt;when retrnsmt uid timeout inode 0: 0100007F:A281 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 46378 1 0000000000000000 100 0 0 10 0 1: 4203A8C0:2253 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1098588 1 0000000000000000 100 0 0 10 0 2: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 36001 1 0000000000000000 100 0 0 10 0 3: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 1097236 1 0000000000000000 100 0 0 10 0 4: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 980905 1 0000000000000000 100 0 0 10 0 5: 4203A8C0:0016 0703A8C0:E447 01 00000000:00000000 02:0009FF18 00000000 0 0 1100461 4 0000000000000000 20 4 31 10 -1 ------可以看到inode为1098588的套接字 也可以看到本地地址 远端地址 st为0A 表示正在监听 为TCP_LISTEN状态 端口安全 redis无密码登录被黑\n端口小结 三次握手 经典3次握手示意图 三次握手最重要的是交换彼此的ISN(初始化序列号)，初始序列号的计算要查看内核，可以先掌握变化规律\n客户端c先发送SYN报文 tcpflags只有SYN置位 报文不携带数据，但也占用一个序列号，因为需要服务端确认Ack\n凡消耗序列号的tcp报文段，一定要对方确认，如果对方不Ack，发送端则会一直重传到指定次数为止 服务端s收到客户端c的SYN后，将SYN和ACK两个flag都置位，SYN是告诉客户端自己的初始化序列号是多少，Ack为ISN(c)+1是为了告诉\u0026lt;Ack的报文都已经收到，下次再发请从Ack编号开始，这里的SYN也需要对方(客户端)确认 所以这里也消耗一个序列号\n客户端c发送最后一个ACK，因为不需要对方再次确认 所以就不消耗序列号\n初始化序列号(Initial Sequence Number) ​\tISN并不是从0开始的，wireshark可能默认显示的相对序列号，这个linux是有固定的算法，总是是动态的，主要是安全考虑。\n如果知道了连接的ISN，比较容易构造一个RST包，将连接直接关闭。如果是动态的，比较难构造RST socket也支持 SO_REUSEADDR端口重用，如果ISN固定 收到一个包就不知道是老包重传的还是新的连接包，保证2个连接的ISN不会串包 三次握手状态变化 对于客户端而言：\n初始的状态是处于 CLOSED 状态。CLOSED 并不是一个真实的状态，而是一个假想的起点和终点。 客户端调用 connect 以后会发送 SYN 同步报文给服务端，然后进入 SYN-SENT 阶段，客户端将保持这个阶段直到它收到了服务端的确认包。 如果在 SYN-SENT 状态收到了服务端的确认包，它将发送确认服务端 SYN 报文的 ACK 包，同时进入 ESTABLISHED 状态，表明自己已经准备好发送数据。 对于服务端而言：\n初始状态同样是 CLOSED 状态 在执行 bind、listen 调用以后进入 LISTEN状态，等待客户端连接。 当收到客户端的 SYN 同步报文以后，会回复确认同时发送自己的 SYN 同步报文，这时服务端进入 SYN-RCVD 阶段等待客户端的确认。 当收到客户端的确认报文以后，进入ESTABLISHED 状态。这时双方可以互相发数据了 发送syn包 对方没有回复会怎么样 客户端会处于SYN-SENT状态一段时间，会尝试重发SYN包 多少次有开关\nketonghe@ubuntu:~/code$ cat /proc/sys/net/ipv4/tcp_syn_retries 6 6次重试（65s = 1s+2s+4s+8s+16s+32s)以后放弃重试，connect 调用返回 -1，调用超时，如果是真实客户端 packetdrill 构造SYN_SENT 状态的连接\nketonghe@ubuntu:~/packetdrill/pkt$ cat a.pkt // 新建一个 server socket +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 // 客户端 connect +0 connect(3, ..., ...) = -1 --------执行发包 sudo /home/ketonghe/packetdrill/gtests/net/packetdrill/packetdrill a.pkt -----tcpdump抓包 ketonghe@ubuntu:~/code$ sudo tcpdump -i any port 8080 -nn -U -vvv -w test.pcap [sudo] password for ketonghe: tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes Got 7------重发6次 然后超时 6次重试（65s = 1s+2s+4s+8s+16s+32s)以后放弃重试，connect 调用返回 -1，调用超时 TCP同时打开 以其中一方为例，记为 A，另外一方记为 B\n最初的状态是CLOSED A 发起主动打开，发送 SYN 给 B，然后进入SYN-SENT状态 A 还在等待 B 回复的 ACK 的过程中，收到了 B 发过来的 SYN，what are you 弄啥咧，A 没有办法，只能硬着头皮回复SYN+ACK，随后进入SYN-RCVD A 依旧死等 B 的 ACK 好不容易等到了 B 的 ACK，对于 A 来说连接建立成功 TCP自连接 假设一个客户端想要连接的端口是50001\n客户端启动的时候系统临时端口只有50001 也就是自己连接自己\n这个时候就会出现自连接现象。\n作为客户端发送SYN (最后发给了自己) 作为服务端收到自己发送的SYN包 以为对方想连接 所以回复SYN+ACK(回复给了自己) 自己收到自己发送的SYN和ACK 以为是对方回复的，认为握手成功，进入ESTABLISHED状态 模拟自连接 准备客户端程序 int cliSocket=socket(AF_INET,SOCK_STREAM,0); servaddr.sin_addr.s_addr = inet_addr(\u0026#34;192.168.3.66\u0026#34;); servaddr.sin_port = htons(50001); //连接服务器 if( 0 != connect(cliSocket,(struct sockaddr *)\u0026amp;servaddr,sizeof(servaddr))){ perror(\u0026#34;connect error,procedure will exit\u0026#34;); return 0; } char readBuffer[1024]; char writeBuffer[1024]=\u0026#34;hello srv,i\u0026#39;m from client!\u0026#34;; if( write(cliSocket,writeBuffer,strlen(writeBuffer) ) \u0026lt;0){ perror(\u0026#34;write error! procedure will exit!\u0026#34;); return 0; }//连接上服务端之后 直接发送一条消息 memset(readBuffer,0,sizeof(readBuffer)); memset(writeBuffer,0,sizeof(writeBuffer)); //从connSocket中读取数据 直到读取不到未知 while( read(cliSocket,readBuffer,sizeof(readBuffer))\u0026gt;0){ cout\u0026lt;\u0026lt;\u0026#34;srv: \u0026#34;\u0026lt;\u0026lt;readBuffer\u0026lt;\u0026lt;endl; cin\u0026gt;\u0026gt;writeBuffer; if( write(cliSocket,writeBuffer,strlen(writeBuffer) ) \u0026lt;0){ perror(\u0026#34;write error! procedure will exit!\u0026#34;); return 0; } memset(readBuffer,0,sizeof(readBuffer)); memset(writeBuffer,0,sizeof(writeBuffer)); } cout\u0026lt;\u0026lt;\u0026#34;srv is close,chat will exit\u0026#34;\u0026lt;\u0026lt;endl; close(cliSocket); return 0; 修改系统临时端口 只剩下50001 ketonghe@ubuntu:~/code$ sudo sysctl -w net.ipv4.ip_local_port_range=\u0026#34;50001 50001\u0026#34; net.ipv4.ip_local_port_range = 50001 50001 ketonghe@ubuntu:~/code$ netstat -anpl|grep 50001 ---检查没人占用 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) 开启tcpdump抓包 sudo tcpdump -i any port 50001 -nn -U -vvv -w tcpSelfConn.pcap 查看netstat 发现自己连接上自己了 ketonghe@ubuntu:~/code$ netstat -anpl|grep 50001 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 192.168.3.66:50001 192.168.3.66:50001 ESTABLISHED 86363/./client client程序运行输出 ketonghe@ubuntu:~/code$ ./client srv: hello srv,i\u0026#39;m from client! aaaaaaa srv: aaaaaaa ^C wireshark查看抓包 自连接可能发生的真实场景 你写的业务系统 B 会访问本机服务 A，服务 A 监听了 50001 端口 业务系统 B 的代码写的稍微比较健壮，增加了对服务 A 断开重连的逻辑 如果有一天服务 A 挂掉比较长时间没有启动，业务系统 B 开始不断 connect 重连 系统 B 经过一段时间的重试就会出现自连接的情况 这时服务 A 想启动监听 50001 端口就会出现地址被占用的异常，无法正常启动 自连接的危害 自连接的进程占用了端口，导致真正需要监听端口的服务进程无法监听成功 自连接的进程看起来 connect 成功，实际上服务是不正常的，无法正常进行业务数据通信(自己发给自己的) 如何避免自然连接 让服务监听的端口与客户端随机分配的端口不可能相同即可\n出现自连接的时候，主动关掉连接\n判断是否是自连接的逻辑是判断源 IP 和目标 IP 是否相等，源端口号和目标端口号是否相等 如果都相等 直接close掉\n断开四次挥手 最常见的断开4次挥手 客户端调用 close 方法，执行「主动关闭」，会发送一个 FIN 报文给服务端，从这以后客户端不能再发送数据给服务端了，客户端进入FIN-WAIT-1状态。FIN 报文其实就是将 FIN 标志位设置为 1,此时客户端需要等待服务端的ACK和FIN包，close发送的FIN包可以携带数据，也可以不携带，但都消耗序列号，因为需要对方确认。发送FIN后不能再发送数据，单可以接受服务端的数据。也就是半关闭状态(half\u0026ndash;close) 服务端收到客户端的FIN包，立马发送ACK给客户端，然后自己进入CLOSE_WAIT状态 客户端收到服务端的ACK后 客户端进入FIN_WAIT2状态(等待服务端发送FIN) 这个时候还可以收数据。 服务端确认没有数据发送了 就发送FIN包，然后进入LAST_ACK状态(最后等待客户端发送ACK)，这个时候服务端也不能再发送数据了 客户端收到服务端的FIN，自己也发送ACK给服务端，然后客户端进入TIME_WAIT状态(等待2个MSL后进入CLOSED状态) 服务端收到客户端最后的ACK 进入CLOSED状态 4次挥手是否可以变成3次 如果服务端收到客户端的FIN包后 确认自己没有数据要发送 也是可以将服务端的ACK和FIN包一起发送的(延迟ACK的时候也可能出现这种情况) 客户端服务端同时关闭 以客户端为例\n最初客户端和服务端都处于 ESTABLISHED 状态 客户端发送 FIN 包，等待对端对这个 FIN 包的 ACK，随后进入 FIN-WAIT-1 状态 处于FIN-WAIT-1状态的客户端还没有等到 ACK，收到了服务端发过来的 FIN 包 收到 FIN 包以后客户端会发送对这个 FIN 包的的确认 ACK 包，同时自己进入 CLOSING 状态 继续等自己 FIN 包的 ACK 处于 CLOSING 状态的客户端终于等到了ACK，随后进入TIME-WAIT 在TIME-WAIT状态持续 2*MSL，进入CLOSED状态 ​\t发送FIN包后进入FIN_WAIT1状态(等待对方发送发送ACK和FIN)，如果这个时候对方不发送ACK，先收到对方发送的FIN，就知道对方不会发送数据了 也就进入了CLOSING状态。\n​\t这个时候还要等待对方发送ACK，如果收到了ACK就进入TIME_WAIT状态(等待2个MSL进入CLSOSED状态)\n​\t服务端也是一样的。\n​\tA方发送FIN包之前，如果有收到对方B的FIN包则只有B会进入TIME_WAIT，否则会出现双方都TIME_WAIT状态\nTCP头部时间戳选项(TCP Timestamps Option，TSopt) TCP协议里面有选项(Options)、填充(Padding),其中TSopt也就是时间戳选项也是一个比较重要的选项\nTimestamps 选项最初是在 RFC 1323 中引入的，这个 RFC 的标题是 \u0026ldquo;TCP Extensions for High Performance\u0026rdquo;，在这个 RFC 中同时提出的还有 Window Scale、PAWS 等机制\n组成部分说明 在 Wireshark 抓包中，常常会看到 TSval 和 TSecr 两个选项，值得注意的是第二个选项 TSecr 不是 secrets 的意思，而是 \u0026ldquo;TS Echo Reply\u0026rdquo; 的缩写，TSval 和 TSecr 是 TCP 选项时间戳的一部分。\nTCP Timestamps Option 由四部分构成：类别（kind）、长度（Length）、发送方时间戳（TS value）、回显时间戳（TS Echo Reply）。时间戳选项类别（kind）的值等于 8，用来与其它类型的选项区分。长度（length）等于 10。两个时间戳相关的选项都是 4 字节\n是否使用时间戳选项实在连接的SYN包时候确定的，当然需要双方都支持才行，只要有一方没有回复就都不再发送时间戳选项\ncurl github.com抓包结果\n发送方发送数据时，将一个发送时间戳 1734581141 放在发送方时间戳TSval中\n接收方收到数据包以后，将收到的时间戳 1734581141 原封不动的返回给发送方，放在TSecr字段中，同时把自己的时间戳 3303928779 放在TSval中\n后面的包以此类推\nTSVal就是发送的时间 TSecr是本次发送的包是回复的对方哪个时间点发送的报文\n解决问题 Timestamps 选项的提出初衷是为了解决两个问题：\n1、两端往返时延测量（RTTM）\n​\t因为知道了一个报文的发送时间和收到应答时间 就可以准确的确定中间经过的时间\n2、序列号回绕（PAWS）\n内核会为每个连接维护一个 ts_recent 值，记录最后一次通信的的 timestamps 值，如果收到的报文的时间戳小于ts_recent ，则会直接丢弃。\n时间戳选项也可能造成RST 三次握手中的第二步，如果服务端回复 SYN+ACK 包中的 TSecr 不等于握手第一步客户端发送 SYN 包中的 TSval，客户端在对 SYN+ACK 回复 RST。示例包如下所示：\n​\nTCP11种状态变迁图 11状态变迁图 半连接队列、全连接队列、backlog 基本概念解读 当服务端调用listen函数之后，tcp状态有CLOSE变为LISTEN，同时内核创建了2个队列\n半连接队列(Incomplete connection queue) 又称SYN队列 全连接队列(Incomplete connection queue) 又称Accept队列 当客户端调用connect函数后 内核发送SYN包给服务端，服务端收到后会 回复ACK和自己的SYN，服务器的状态会由listen变为SYN_RCVD（SYN Received），此时会将这条连接信息放入 SYN半连接队列，存储的是“inbound SYN packets”，入境的SYN包\n服务端回复SYN+ACK后等待客户端回复ACK，也会开启定时器，如果超时收不到客户端的ACK，就会重发SYN+ACK,重发次数为配置参数，tcp_synack_retries，一旦收到客户端的ACK，服务端就尝试将它加入全连接队列(Accept Queue)\u0026mdash;除非队列满了\n半连接队列大小计算 与三个内容有关系\n服务端调用listen函数传入的backlog 系统变量net.ipv4.tcp_max_syn_backlog 默认128 系统变量 net.core.somaxconn 具体计算逻辑：\n1. nr_table_entries= min(backlog、net.ipv4.tcp_max_syn_backlog、net.core.somaxconn) 取3者中最小值 赋值给nr_table_entries 2. nr_table_entries=max(nr_table_entries,8) //跟8比较取最大值 3. nr_table_entries + 1 向上取求最接近的最大 2 的指数次幂 nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); 4. 通过 for 循环找不大于 nr_table_entries 最接近的 2 的对数值 结果为max_qlen_log for (lopt-\u0026gt;max_qlen_log = 3; (1 \u0026lt;\u0026lt; lopt-\u0026gt;max_qlen_log) \u0026lt; nr_table_entries; lopt-\u0026gt;max_qlen_log++); 5. 半连接队列大小为\t2^max_qlen_log 例子：\n在系统参数不修改的情形，盲目调大 listen 的 backlog 对最终半连接队列的大小不会有影响。 在 listen 的 backlog 不变的情况下，盲目调大 somaxconn 和 max_syn_backlog 对最终半连接队列的大小不会有影响 如果半连接队列满了会怎么办 当半连接队列溢出时，Server 收到了新的发起连接的 SYN：\n如果不开启 net.ipv4.tcp_syncookies：直接丢弃这个 SYN 如果开启net.ipv4.tcp_syncookies 如果全连接队列满了，并且 qlen_young 的值大于 1：丢弃这个 SYN 否则，生成 syncookie 并返回 SYN/ACK 包 全连接队列大小计算 backlog 和 somaxconn 中的较小值 关于ss命令 ss -lnt | grep :9090 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 51 50 *:9090 *:* 处于 LISTEN 状态的 socket，Recv-Q 对应 sk_ack_backlog，表示当前 socket 的完成三次握手等待用户进程 accept 的连接个数，Send-Q 对应 sk_max_ack_backlog，表示当前 socket 全连接队列能最大容纳的连接数 对于非 LISTEN 状态的 socket，Recv-Q 表示 receive queue 的字节大小，Send-Q 表示 send queue 的字节大小 backlog多大才合适 你如果的接口处理连接的速度要求非常高，或者在做压力测试，很有必要调高这个值 如果业务接口本身性能不好，accept 取走已建连的速度较慢，那么把 backlog 调的再大也没有用，只会增加连接失败的可能性 Nginx 和 Redis 默认的 backlog 值等于 511，Linux 默认的 backlog 为 128。\n全连接队列满了怎么办 默认情况下，全连接队列满以后，服务端会忽略客户端的 ACK，随后会重传SYN+ACK，也可以修改这种行为，这个值由/proc/sys/net/ipv4/tcp_abort_on_overflow决定。\ntcp_abort_on_overflow 为 0 表示三次握手最后一步全连接队列满以后 server 会丢掉 client 发过来的 ACK，服务端随后会进行重传 SYN+ACK。 tcp_abort_on_overflow 为 1 表示全连接队列满以后服务端直接发送 RST 给客户端。 但是回给客户端 RST 包会带来另外一个问题，客户端不知道服务端响应的 RST 包到底是因为「该端口没有进程监听」，还是「该端口有进程监听，只是它的队列满了」\nSYN flood攻击 客户端大量伪造 IP 发送 SYN 包，服务端回复的 ACK+SYN 去到了一个「未知」的 IP 地址，势必会造成服务端大量的连接处于 SYN_RCVD 状态，而服务器的半连接队列大小也是有限的，如果半连接队列满，也会出现无法处理正常请求的情况。\n如何发现或者监控SYN Flood 监控日常活跃连接数 如果大幅增加而且大量SYN_RECV状态的连接 可能就是有问题 如何防范及应急 net.ipv4.tcp_max_syn_backlog调大 同时listen时候的backlog也调大，目的是增大SYN半连接队列内存\n减少服务端SYN+ACK重试次数 应急时候可以将/proc/sys/net/ipv4/tcp_synack_retries改为0 因为真正的客户端必经还以重发SYN。\n开启tcp_syncookies\nSYN Cookie 的原理是基于「无状态」的机制，服务端收到 SYN 包以后不马上分配为 Inbound SYN分配内存资源，而是根据这个 SYN 包计算出一个 Cookie 值，作为握手第二步的序列号回复 SYN+ACK，等对方回应 ACK 包时校验回复的 ACK 值是否合法，如果合法才三次握手成功，分配连接资源\n如果发现攻击后能给确定非法ip 也可以直接iptable限制这些ip 效果非常明显\n外围增加F5等负载均衡利器，只有真正连接上的请求才转发给服务器\nTCP Fast Open TFO TCP快速打开 ​\tTFO 是在原来 TCP 协议上的扩展协议，它的主要原理就在发送第一个 SYN 包的时候就开始传数据了，\n不过它要求当前客户端之前已经完成过「正常」的三次握手。快速打开分两个阶段：请求 Fast Open Cookie 和 真正开始 TCP Fast Open\nTFO与非TFO的对比 优势就是后续的连接 tcp交互次数更少 效率更快 也可以有一定预防SYN Flood 代码如果要使用 要调用sendto函数 同时设置MSG_FASTOPEN flag 小结 客户端发送一个 SYN 包，头部包含 Fast Open 选项，且该选项的 Cookie 长度为 0 服务端根据客户端 IP 生成 cookie，放在 SYN+ACK 包中一同发回客户端 客户端收到 Cookie 以后缓存在自己的本地内存 客户端再次访问服务端时，在 SYN 包携带数据，并在头部包含 上次缓存在本地的 TCP cookie 如果服务端校验 Cookie 合法，则在客户端回复 ACK 前就可以直接发送数据。如果 Cookie 不合法则按照正常三次握手进行。 Address already in use SO_REUSEADDR 背景 如果服务器退出或者崩溃导致服务器先close，那么服务端就会出现TIME_WAIT状态，需要等待2个MSL才能最终释放连接\n如果这个时候立马启动服务器程序，就会出现address already in use的错误。\n并不是服务端只有处于TimeWait才有用 处于Fin_WAIT2也是可以的\n为什么通常不会在客户端出现 因为客户端每次都是随机的临时端口，所以一般不会出现\nSO_REUSEPORT 多个进程监听同一个端口 ​\t默认情况下，一个 IP、端口组合只能被一个套接字绑定，Linux 内核从 3.9 版本开始引入一个新的 socket 选项 SO_REUSEPORT，又称为 port sharding，允许多个套接字监听同一个IP 和端口组合\n​\t充分发挥多核CPU的性能，多进程处理网络请求的方式可以有下面2个方式\n主进程 + 多个 worker 子进程监听相同的端口\n多进程 + REUSEPORT\n第一种方最常用的一种模式，Nginx 默认就采用这种方式。主进程执行 bind()、listen() 初始化套接字，然后 fork 新的子进程。在这些子进程中，通过 accept/epoll_wait 同一个套接字来进行请求处理，但会带来惊群问题（thundering herd）\n惊群问题 thundering herd ​\t明明只有一块骨头只够一条小狗吃，五只小狗却一起从睡眠中醒来争抢，对于没有抢到小狗来说，浪费了很多精力\n计算机中的惊群问题指的是：多进程/多线程同时监听同一个套接字，当有网络事件发生时，所有等待的进程/线程同时被唤醒，但是只有其中一个进程/线程可以处理该网络事件，其它的进程/线程获取失败重新进入休眠 惊群问题带来的是 CPU 资源的浪费和锁竞争的开销。根据使用方式的不同，Linux 上的网络惊群问题分为 accept 惊群和 epoll 惊群两种，linux内核2.6 版本中引入了 WQ_FLAG_EXCLUSIVE 选项解决了 accept 调用的惊群问题(非epoll 多进程直接accept) epoll惊群 ​\tepoll 典型的工作模式是父进程执行 bind、listen 以后 fork 出子进程，使用 epoll_wait 等待事件发生，模式如下图所示：\n示例代码 int main(void) { // ... sock_fd = create_and_bind(\u0026#34;9090\u0026#34;); listen(sock_fd, SOMAXCONN); epoll_fd = epoll_create(1); event.data.fd = sock_fd; event.events = EPOLLIN; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, \u0026amp;event); events = calloc(MAXEVENTS, sizeof(event)); for (int i = 0; i \u0026lt; 4; i++) { if (fork() == 0) { while (1) { int n = epoll_wait(epoll_fd, events, MAXEVENTS, -1); printf(\u0026#34;return from epoll_wait, pid is %d\\n\u0026#34;, getpid()); sleep(2); for (int j = 0; j \u0026lt; n; j++) { if ((events[i].events \u0026amp; EPOLLERR) || (events[i].events \u0026amp; EPOLLHUP) || (!(events[i].events \u0026amp; EPOLLIN))) { close(events[i].data.fd); continue; } else if (sock_fd == events[j].data.fd) { struct sockaddr sock_addr; socklen_t sock_len; int conn_fd; sock_len = sizeof(sock_addr); conn_fd = accept(sock_fd, \u0026amp;sock_addr, \u0026amp;sock_len); if (conn_fd == -1) { printf(\u0026#34;accept failed, pid is %d\\n\u0026#34;, getpid()); break; } printf(\u0026#34;accept success, pid is %d\\n\u0026#34;, getpid()); close(conn_fd); } } } } } 查看进程打开具柄情况\nls -l /proc/24735/fd lrwx------. 1 ya ya 64 Jan 28 06:20 0 -\u0026gt; /dev/pts/2 lrwx------. 1 ya ya 64 Jan 28 06:20 1 -\u0026gt; /dev/pts/2 lrwx------. 1 ya ya 64 Jan 28 00:10 2 -\u0026gt; /dev/pts/2 lrwx------. 1 ya ya 64 Jan 28 06:20 3 -\u0026gt; \u0026#39;socket:[72919]\u0026#39; listen套接字 lrwx------. 1 ya ya 64 Jan 28 06:20 4 -\u0026gt; \u0026#39;anon_inode:[eventpoll]\u0026#39; epollfd 为了表示打开文件，linux 内核维护了三种数据结构，分别是：\n内核为每个进程维护了一个其打开文件的「描述符表」（file descriptor table），我们熟知的 fd 为 0 的 stdin 就是属于文件描述符表。 内核为所有打开文件维护了一个系统级的「打开文件表」（open file table），这个打开文件表存储了当前文件的偏移量，状态信息和对 inode 的指针等信息，父子进程的 fd 可以指向同一个打开文件表项。 最后一个是文件系统的 inode 表（i-node table） 经过 for 循环的 fork，会生成 4 个子进程，这 4 个子进程会继承父进程的 fd。在这种情况下，对应的进程文件描述符表、打开文件表和 inode 表的关系如下图所示：\n子进程的 epoll_wait 等待同一个底层的 open file table 项，当有事件发送时，会通知到所有的子进程\n开启一个客户端连接\nreturn from epoll_wait, pid is 25410 return from epoll_wait, pid is 25411 return from epoll_wait, pid is 25409 return from epoll_wait, pid is 25412 accept success, pid is 25410 accept failed, pid is 25411 accept failed, pid is 25409 accept failed, pid is 25412 可以看到当有新的网络事件发生时，阻塞在 epoll_wait 的多个进程同时被唤醒。在这种情况下，epoll 的惊群还是存在，有不少的措施可以解决 epoll 的惊群。Nginx 为了处理惊群问题，在应用层增加了 accept_mutex 锁\n当然也可以使用SO_REUSEPORT 选项\nSO_REUSEPORT 选项 如果不加一个选项 同一个server程序 只能启动一次，否则机会提示失败\n但加了这个选项 可以多个进程监听同一个端口\nint optval = 1; setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, \u0026amp;optval, sizeof(optval)); 启动2个服务器进程 查看端口情况\nketonghe@ubuntu:~/code$ ss -tlnpe | grep -i 8787 LISTEN 0 5 192.168.3.66:8787 0.0.0.0:* users:((\u0026#34;server\u0026#34;,pid=88296,fd=3)) uid:1000 ino:1215520 sk:34b \u0026lt;-\u0026gt; LISTEN 0 5 192.168.3.66:8787 0.0.0.0:* users:((\u0026#34;server\u0026#34;,pid=88294,fd=3)) uid:1000 ino:1215512 sk:34c \u0026lt;-\u0026gt; ketonghe@ubuntu:~/code$ netstat -anp|grep 8787 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 192.168.3.66:8787 0.0.0.0:* LISTEN 88296/./server tcp 0 0 192.168.3.66:8787 0.0.0.0:* LISTEN 88294/./server ss命令介绍\n-t, --tcp 显示 TCP 的 socket -l, --listening 只显示 listening 状态的 socket，默认情况下是不显示的。 -n, --numeric 显示端口号而不是映射的服务名 -p, --processes 显示进程名 -e, --extended 显示 socket 的详细信息 客户端连接来了分给谁？ ​\tlinux4.5、4.6版本引入了SO_REUSEPORT group概念，在查找匹配的 socket 时，就不用遍历整条冲突链，对于设置了 SO_REUSEPORT 选项的 socket 经过二次哈希找到对应的 SO_REUSEPORT group\n简单理解可以说是随机的。\nSO_REUSEPORT与安全性 试想下面的场景，你的进程进程监听了某个端口，不怀好意的其他人也可以监听相同的端口来“窃取”流量信息，这种方式被称为端口劫持（port hijacking）。SO_REUSEPORT 在安全性方面的考虑主要是下面这两点。\n1、只有第一个启动的进程启用了 SO_REUSEPORT 选项，后面启动的进程才可以绑定同一个端口。 2、后启动的进程必须与第一个进程的有效用户ID（effective user ID）匹配才可以绑定成功\nSO_REUSEPORT 的应用 SO_REUSEPORT 带来了两个明显的好处：\n实现了内核级的负载均衡\n支持滚动升级（Rolling updates）\n步骤如下所示。\n新启动一个新版本 v2 ，监听同一个端口，与 v1 旧版本一起处理请求。 发送信号给 v1 版本的进程，让它不再接受新的请求 等待一段时间，等 v1 版本的用户请求都已经处理完毕时，v1 版本的进程退出，留下 v2 版本继续服务 SO_LINGER选项 TimeWait状态 MSL Max Segment lifetime MSL（报文最大生存时间）是 TCP 报文在网络中的最大生存时间。这个值与 IP 报文头的 TTL 字段有密切的关系。\nIP 报文头中有一个 8 位的存活时间字段（Time to live, TTL）如下图。 这个存活时间存储的不是具体的时间，而是一个 IP 报文最大可经过的路由数，每经过一个路由器，TTL 减 1，当 TTL 减到 0 时这个 IP 报文会被丢弃。\n从上面可以看到 TTL 说的是「跳数」限制而不是「时间」限制，尽管如此我们依然假设最大跳数的报文在网络中存活的时间不可能超过 MSL 秒。Linux 的套接字实现假设 MSL 为 30 秒，因此在 Linux 机器上 TIME_WAIT 状态将持续 60秒\n为什么要TIME_WAIT状态 避免当前关闭连接与后续连接混淆（让旧连接的包在网络中消逝） ​\t数据报文可能在发送途中延迟但最终会到达，因此要等老的“迷路”的重复报文段在网络中过期失效，这样可以避免用相同源端口和目标端口创建新连接时收到旧连接姗姗来迟的数据包，造成数据错乱。\n假设客户端 10.211.55.2 的 61594 端口与服务端 10.211.55.10 的 8080 端口一开始建立了一个 TCP 连接。\n假如客户端发送完 FIN 包以后不等待直接进入 CLOSED 状态，老连接 SEQ=3 的包因为网络的延迟。过了一段时间相同的 IP 和端口号又新建了另一条连接，这样 TCP 连接的四元组就完全一样了。恰好 SEQ 因为回绕等原因也正好相同，那么 SEQ=3 的包就无法知道到底是旧连接的包还是新连接的包了，造成新连接数据的混乱。\nTIME_WAIT 等待时间是 2 个 MSL，已经足够让一个方向上的包最多存活 MSL 秒就被丢弃，保证了在创建新的 TCP 连接以后，老连接姗姗来迟的包已经在网络中被丢弃消逝，不会干扰新的连接\n确保可靠实现TCP全双工终止连接 关闭连接的四次挥手中，最终的 ACK 由主动关闭方发出，如果这个 ACK 丢失，对端（被动关闭方）将重发 FIN，如果主动关闭方不维持 TIME_WAIT 直接进入 CLOSED 状态，则无法重传 ACK，被动关闭方因此不能及时可靠释放\n如果上述情况没有TIME_WAIT等待 直接CLOSED\n​\t主动关闭方如果马上进入 CLOSED 状态，被动关闭方这个时候还处于LAST-ACK状态，主动关闭方认为连接已经释放，\n端口可以重用了，如果使用相同的端口三次握手发送 SYN 包，会被处于 LAST-ACK状态状态的被动关闭方返回一个 RST，三次握手失败\n为什么要2个MSL 1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端 1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达 2MS = 去向 ACK 消息最大存活时间（MSL) + 来向 FIN 消息的最大存活时间（MSL）\nTIME_WAIT的问题 在一个非常繁忙的服务器上，如果有大量 TIME_WAIT 状态的连接会怎么样呢？\n连接表无法复用 socket 结构体内存占用 连接表无法复用 因为处于 TIME_WAIT 的连接会存活 2MSL（60s），意味着相同的TCP 连接四元组（源端口、源 ip、目标端口、目标 ip）在一分钟之内都没有办法复用，通俗一点来讲就是“占着茅坑不拉屎”。\n在一台 Linux 机器上，端口最多是 65535 个（ 2 个字节）。如果客户端与服务器通信全部使用短连接，不停的创建连接，接着关闭连接，客户端机器会造成大量的 TCP 连接进入 TIME_WAIT 状态，很有可能出现端口不够用的情况\n应对TIME_WAIT tcp_tw_reuse 选项 缓解紧张的端口资源，一个可行的方法是重用“浪费”的处于 TIME_WAIT 状态的连接，当开启 net.ipv4.tcp_tw_reuse 选项时，处于 TIME_WAIT 状态的连接可以被重用。下面把主动关闭方记为 A， 被动关闭方记为 B，它的原理是：\n如果主动关闭方 A 收到的包时间戳比当前存储的时间戳小，说明是一个迷路的旧连接的包，直接丢弃掉\n如果因为 ACK 包丢失导致被动关闭方还处于LAST-ACK状态，并且会持续重传 FIN+ACK。这时 A 发送SYN 包想三次握手建立连接，此时 A 处于SYN-SENT阶段。当收到 B 的 FIN 包时会回以一个 RST 包给 B，B 这端的连接会进入 CLOSED 状态，A 因为没有收到 SYN 包的 ACK，会重传 SYN，后面就一切顺利了。\nRST创建的几种情况 在tcp协议中，RST表示复位，用来异常的关闭连接，发送 RST 关闭连接时，不必等缓冲区的数据都发送出去，直接丢弃缓冲区中的数据，连接释放进入CLOSED状态。而接收端收到 RST 段后，也不需要发送 ACK 确认。 端口未监听 这种情况很常见，比如 web 服务进程断电挂掉或者未启动，客户端使用 connect 建连，都会出现 \u0026ldquo;Connection Reset\u0026rdquo; 或者\u0026quot;Connection refused\u0026quot; 错误\n这样机制可以用来检测对端端口是否打开，发送 SYN 包对指定端口，看会不会回复 SYN+ACK 包。如果回复了 SYN+ACK，说明监听端口存在，如果返回 RST，说明端口未对外监听，如下图所示\n如果调用了close函数，设置SO_LINGER为true 也会出现close后丢掉缓冲区 直接发送RST包 重置连接，立马进入CLOSED状态\n如果RST中途丢失了怎么办 如果另外一方没有收到RST会重试一定次数后 再次放弃。\nBroken Pipe和Connection reset by peer Broken pipe 与 Connection reset by peer 错误在网络编程中非常常见，出现的前提都是连接已关闭。\nConnection reset by peer 这个错误很好理解，就是对方已经关闭连接了\nBroken pipe出现时机是：在一个RST的套接字中继续写数据，因为连接已经关闭了，再写数据，内核就直接返回这个异常。\n当一个进程向某个已收到 RST 的套接字执行写操作时，内核向该进程发送一个 SIGPIPE 信号。该信号的默认行为是终止进程，因此进程一般会捕获这个信号进行处理。不论该进程是捕获了该信号并从其信号处理函数返回，还是简单地忽略该信号，写操作都将返回 EPIPE 错误（也就Broken pipe 错误）,这也是 Broken pipe 只在写操作中出现的原因\n重传相关： 重传示意 Ack延迟确认 ​\t如果发送 5000 个字节的数据包，因为 MSS 的限制每次传输 1000 个字节，分 5 段传输\n包1 (序列号1 长度1000) 包2 (序列号1001 长度1000) 包3 (序列号2001 长度1000) 包4 (序列号3001 长度1000) 包2 (序列号4001 长度1000) 数据包 1 发送的数据正常到达接收端，接收端回复 ACK 1001，表示 seq 为1001之前的数据包都已经收到，下次从1001开始发。\n数据包 2因为某些原因未能到达服务端，其他包(3,4,5)正常到达，这时接收端也不能 ack 3 4 5 数据包，因为数据包 2 还没收到，接收端只能回复 ack 1001\n第 2 个数据包重传成功以后服务器会回复5001，表示seq 为 5001 之前的数据包都已经收到了,这就是Ack延迟确认\n简单示意图如下：\n快速重传机制和SACK ​\t网络协议设计者们想到了一种方法：「快速重传」\n快速重传的含义是：当发送端收到 3 个或以上重复 ACK，就意识到之前发的包可能丢了，于是马上进行重传，不用傻傻的等到超时再重传\n​\t这就引入了另外一个问题，发送 3、4、5 包收到的全部是 ACK=1001，快速重传解决了一个问题: 需要重传。因为除了 2 号包，3、4、5 包也有可能丢失，那到底是只重传数据包 2 还是重传 2、3、4、5 所有包呢？\n收到 3 号包的时候在 ACK 包中告诉发送端：喂，小老弟，我目前收到的最大连续的包序号是 1000（ACK=1001），[1:1001]、[2001:3001] 区间的包我也收到了 收到 4 号包的时候在 ACK 包中告诉发送端：喂，小老弟，我目前收到的最大连续的包序号是 1000（ACK=1001），[1:1001]、[2001:4001] 区间的包我也收到了 收到 5 号包的时候在 ACK 包中告诉发送端：喂，小老弟，我目前收到的最大连续的包序号是 1000（ACK=1001），[1:1001]、[2001:5001] 区间的包我也收到了 这样发送端就清楚知道只用重传 2 号数据包就可以了，数据包 3、4、5已经确认无误被对端收到。这种方式被称为 SACK（Selective Acknowledgment）\n​\t大值流程如下：\n打开单个包的详情，在 ACK 包的 option 选项里，包含了 SACK 的信息，如下图：\n隔多久重传？ 经典方法（适用 RTT 波动较小的情况） ​\t一个最简单的想法就是取平均值，比如第一次 RTT 为 500ms，第二次 RTT 为 800ms，那么第三次发送时，各让一步取平均值 RTO 为 650ms。经典算法的思路跟取平均值是一样的，只不过系数不一样而已。\n​\t经典算法引入了「平滑往返时间」（Smoothed round trip time，SRTT）的概念：经过平滑后的RTT的值，每测量一次 RTT 就对 SRTT 作一次更新计算。\nSRTT = ( α * SRTT ) + ((1- α) * RTT) 标准方法（Jacobson / Karels 算法） 传统方法最大的问题是RTT 有大的波动时，很难即时反应到 RTO 上，因为都被平滑掉了。标准方法对 RTT 的采样增加了一个新的因素\nSRTT = (1 - α) * SRTT + α * RTT RTTVAR = (1 - β) * RTTVAR + β * (|RTT-SRTT|) RTO= µ * SRTT + ∂ * RTTVar 重传二义性与 Karn / Partridge 算法 前面的算法都很精妙，但是有一个最基本的问题还没解决，如何重传情况下计算 RTT，下面列举了三种常见的场景\nKarn / Partridge 算法就是为了解决重传二义性的。它的思路也是很奇特，解决问题的最好办法就是不解决它：\n既然不能确定 ACK 包到底对应重传包还是非重传包，那这次就忽略吧，这次重传的 RTT 不会被用来更新 SRTT 及后面的 RTO 只有当收到未重传过的某个请求的 ACK 包时，才更新 SRTT 等变量并重新计算RTO 仅仅有上面的规则是远远不够的，放弃掉重传那次不管看起来就像遇到危险把头埋在沙子里的鸵鸟。如果网络抖动，倒是突然出现大量重传，但这个时候 RTO 没有更新，就很坑了，本身 RTO 就是为了自适应网络延迟状况的，结果出问题了没有任何反应。这里 Karn 算法采用了出现重传就将 RTO 翻倍的方法，这就是我们前面看到过的指数级退避（Exponential backoff）。这种方式比较粗暴，但是非常简单。\n滑动窗口 背景 从socket的角度看TCP，大概如上面所示，TCP 会把要发送的数据放入发送缓冲区（Send Buffer)，接收到的数据放入接收缓冲区（Receive Buffer），应用程序会不停的读取接收缓冲区的内容进行处理。\n流量控制做的事情就是，如果接收缓冲区已满，发送端应该停止发送数据。那发送端怎么知道接收端缓冲区是否已满呢？\n为了控制发送端的速率，接收端会告知客户端自己接收窗口（rwnd），也就是接收缓冲区中空闲的部分。\nTCP 在收到数据包回复的 ACK 包里会带上自己接收窗口的大小，接收端需要根据这个值调整自己的发送策略。\n抓包中的win是什么？ 这里的Win是告诉对方，自己接收窗口的大小。这里的Win=29312是告诉对方自己当前能接收数据最大为29312，对方收到以后，会把自己的「发送窗口」限制在 29312 大小之内。如果自己的处理能力有限，导致自己的接收缓冲区满，接收窗口大小为 0，发送端应该停止发送数据。\nTCP包状态分类 粉色部分#1 (Bytes Sent and Acknowledged)：表示已发送且已收到 ACK 确认的数据包。 蓝色部分#2 (Bytes Sent but Not Yet Acknowledged)：表示已发送但未收到 ACK 的数据包。发送方不确定这部分数据对端有没有收到，如果在一段时间内没有收到 ACK，发送端需要重传这部分数据包。 绿色部分#3 (Bytes Not Yet Sent for Which Recipient Is Ready)：表示未发送但接收端已经准备就绪可以接收的数据包（有空间可以接收） 黄色部分#4 (Bytes Not Yet Sent，Not Ready to Receive)：表示还未发送，且这部分接收端没有空间接收 发送窗口(send window)与可用窗口(usable window) 首先这两个概念都是站在发送数据方说的，需要考虑接收方的Win窗口大小。\n发送窗口是 TCP 滑动窗口的核心概念，它表示了在某个时刻一端能拥有的最大未确认的数据包大小（最大在途数据），发送窗口是发送端被允许发送的最大数据包大小，其大小等于上图中 #2 区域和 #3 区域加起来的总大小，说白了就是发送数据方最多能发送多少数据(包括已发送但是没有被Ack的数据)\n可用窗口是发送端还能发送的最大数据包大小，它等于发送窗口的大小减去在途数据包大小，是发送端还能发送的最大数据包大小，对应于上图中的 #3 号区域。说白了就是发送方当前实际最多能发送多少数据(发送窗口大小减掉已发送但未Ack的大小)\n窗口的左边界表示成功发送并已经被接收方确认的最大字节序号，窗口的右边界是发送方当前可以发送的最大字节序号，滑动窗口的大小等于右边界减去左边界，如下图：\n​\t当上图中的可用区域的6个字节（46~51）发送出去，可用窗口区域减小到 0，这个时候除非收到接收端的 ACK 数据，否则发送端将不能发送数据。\n利用packetdrill模拟滑动窗口用光 脚本 --tolerance_usecs=100000 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 // 禁用 nagle 算法 +0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 +0 bind(3, ..., ...) = 0 +0 listen(3, 1) = 0 // 三次握手 +0 \u0026lt; S 0:0(0) win 20 \u0026lt;mss 1000\u0026gt; +0 \u0026gt; S. 0:0(0) ack 1 \u0026lt;...\u0026gt; +.1 \u0026lt; . 1:1(0) ack 1 win 20 +0 accept(3, ..., ...) = 4 // 演示已经发送并 ACK 前 31 字节数据 +.1 write(4, ..., 15) = 15 +0 \u0026lt; . 1:1(0) ack 16 win 20 +.1 write(4, ..., 16) = 16 +0 \u0026lt; . 1:1(0) ack 32 win 20 +0 write(4, ..., 14) = 14 +0 write(4, ..., 6) = 6 +.1 \u0026lt; . 1:1(0) ack 52 win 20 +0 `sleep 1000000` 解析如下：\n一开始我们禁用了 Nagle 算法以便后面可以连续发送包。 三次握手以后，客户端声明自己的窗口大小为 20 字节 通过两次发包和确认前 31 字节的数据 发送端发送(32,46)部分的 14 字节数据，滑动窗口的可用窗口变为 6 发送端发送(46,52)部分的 6 字节数据，滑动窗口的可用窗口变为 0，此时发送端不能往接收端发送任何数据了，除非有新的 ACK 到来 接收端确认(32,52)部分 20 字节的数据，可用窗口重现变为 20. wireshark抓包 1\u0026mdash;\u0026gt;3 为三次握手 客户端声明自己Win为20(缓冲区最多存放20字节 也就是服务端的发送窗口为20) 4为服务端发送15字节 然后5为客户端ack这15自己已经收到 6为服务端发送16字节 然后7为客户端ack这16自己已经收到 8为服务端发送14字节，然后客户端没有ack(所以服务端的可用窗口由20\u0026mdash;-\u0026gt;6) 9为服务端再次发送6个字节 把可用窗口用光 这个时候 不能再发送数据了 10为客户端回复ack，8、9发送的20字节都已经收到，这个是Win又变为20，也就是告诉服务端可以继续发送数据了 抓包显示的 TCP Window Full不是一个 TCP 的标记，而是 wireshark 智能帮忙分析出来的，表示包的发送方已经把对方所声明的接收窗口耗尽了，三次握手中客户端声明自己的接收窗口大小为 20，这意味着发送端最多只能给它发送 20 个字节的数据而无需确认，在途字节数最多只能为 20 个字。\n滑动窗口变化示意图 利用packetdrill模拟滑动窗口为0 继续发包会出现什么情况 --tolerance_usecs=100000 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 +0 bind(3, ..., ...) = 0 +0 listen(3, 1) = 0 +0 \u0026lt; S 0:0(0) win 4000 \u0026lt;mss 1000\u0026gt; +0 \u0026gt; S. 0:0(0) ack 1 \u0026lt;...\u0026gt; // 三次握手确定客户端接收窗口大小为 360 +.1 \u0026lt; . 1:1(0) ack 1 win 360 +0 accept(3, ..., ...) = 4 // 第一步：往客户端（接收端）写 140 字节数据 +0 write(4, ..., 140) = 140 // 第二步：模拟客户端回复 ACK，接收端滑动窗口减小为 260 +.01 \u0026lt; . 1:1(0) ack 141 win 260 // 第四步：服务端（发送端）接续发送 180 字节数据给客户端（接收端） +0 write(4, ..., 180) = 180 // 第五步：模拟客户端回复 ACK，接收端滑动窗口减小到 80 +.01 \u0026lt; . 1:1(0) ack 321 win 80 // 第七步：服务端（发送端）继续发送 80 字节给客户端（接收端） +0 write(4, ..., 80) = 80 // 第八步：模拟客户端回复 ACK，接收端滑动窗口减小到 0 +.01 \u0026lt; . 1:1(0) ack 401 win 0 // 这一步很重要，写多少数据没关系，一定要有待发送的数据。如果没有待发的数据，不会进行零窗口探测 // 这 100 字节数据实际上不会发出去 +0 write(4, ..., 100) = 100 +0 `sleep 1000000` wireshark抓包结果 No = 8 的包，发送端发送 80 以后，自己已经把接收端声明的接收窗口大小耗尽了，wireshark 帮我们把这种行为识别为了 TCP Window Full。 No = 9 的包，是接收端回复的 ACK，携带了 win=0，wireshark 帮忙把这个包标记为了 TCP Zero window No = 10 ~ 25 的包就是我们前面提到的TCP Zero Window Probe(零窗口探测包)，但是 wireshark 这里识别这个包为了 Keep-Alive，之所以被识别为Keep-Alive 是因为这个包跟 Keep-Alive 包很像。这个包的特点是：一个长度为 0 的 ACK 包，Seq 为当前连接 Seq 最大值减一。因为发出的探测包一直没有得到回应，所以会一直发送端会一直重试。重试的策略跟前面介绍的超时重传的机制一样，时间间隔遵循指数级退避，最大时间间隔为 120s，重试了 16，总共花费了 16 分钟 利用零窗口探测搞死服务器 有等待重试的地方就有攻击的可能，上面的零窗口探测也会成为攻击的对象。试想一下，一个客户端利用服务器上现有的大文件，向服务器发起下载文件的请求，在接收少量几个字节以后把自己的 window 设置为 0，不再接收文件，\n服务端就会开始漫长的十几分钟时间的零窗口探测，如果有大量的客户端对服务端执行这种攻击操作，那么服务端资源很快就被消耗殆尽。\nTCP window full 与 TCP zero window 这两者都是发送速率控制的手段，\nTCP Window Full 是站在发送端角度说的，表示在途字节数等于对方接收窗口的情况，此时发送端不能再发数据给对方直到发送的数据包得到 ACK。 TCP zero window 是站在接收端角度来说的，是接收端接收窗口满，告知对方不能再发送数据给自己。 TCP拥塞控制 滑动窗口只关注了发送端和接收端自身的情况，并没有考虑整个网络的情况，所以就有了拥塞控制\nTCP的拥塞控制主要涉及3个处理机制\n慢启动(Slow Start) 拥塞避免(Congestion Avoidance) 快速重传(Fast Retransmit)、快速恢复(Fast Recovery) 为了实现这三个机制，每个TCP链接都有2个核心状态值\n拥塞窗口(Congestion Window)\u0026mdash;cwnd 慢启动阀值(Slow Start Threshold)\u0026mdash;ssthresh 拥塞窗口 （Congestion Window，cwnd） 拥塞窗口指的是在收到对端 ACK 之前自己还能传输的最大 MSS 段数。\n接收窗口（rwnd）是接收端的限制，是接收端还能接收的数据量大小\n拥塞窗口（cwnd）是发送端的限制，是发送端在还未收到对端 ACK 之前还能发送的数据量大小\n前面滑动窗口讲的发送窗口是单纯不考虑网络的情况，考虑网络，真正的发送窗口大小 = 「接收端接收窗口大小」 与 「发送端自己拥塞窗口大小」 两者的最小值\n发送端能发送多少数据，取决于两个因素：\n对方能接收多少数据（接收窗口） 自己为了避免网络拥塞主动控制不要发送过多的数据（拥塞窗口） TCP不会在双方数据交互过程中，交换彼此的cwnd，这个值是维护在本地内存中的一个值。\n拥塞控制的核心就是控制拥塞窗口的大小变化(cwnd)\n慢启动Slow Start 所谓慢启动指的是 刚开始拥塞窗口很小，不至于让网络瘫痪，然后测试看看是否丢跑，如果对端及时Ack了，说明网络ok，慢慢增加拥塞窗口，多发送数据，持续这个过程，直到拥塞避免阶段(拥塞窗口达到慢启动阀值)。\n大致过程如下：\n第一步，三次握手以后，双方通过 ACK 告诉了对方自己的接收窗口（rwnd）的大小，之后就可以互相发数据了 第二步，通信双方各自初始化自己的「拥塞窗口」（Congestion Window，cwnd）大小。 第三步，cwnd 初始值较小时，每收到一个 ACK，每经过一个 RTT，cwnd 变为之前的两倍。 linux现在默认初始cwnd为10(根据 Google 的研究，90% 的 HTTP 请求数据都在 16KB 以内，约为 10 个 TCP 段。再大比如 16，在某些地区会出现明显的丢包，因此 10 是一个比较合理的值)\n因此可以得到拥塞窗口到达N所花费时间为:\ntime(cwnd到达N)=RTT * (log ( N/initcwnd) )//这里假设RTT稳定 假设RTT为50ms，客户端和服务端的接收窗口为65535字节（64KB），初始拥塞窗口为10个MSS。 这里的N我们要先换算为MSS段数 MSS按照1460算 N=65535/1460=45 所以到达65536字节，也就是到达64K的吞吐量需要的时间为： 50ms * log(45/10)=50ms*2.12=106ms,如果RTT更小 那么这个时间也会相应变小 甚至可以忽略 慢启动阈值（Slow Start Threshold，ssthresh） 慢启动拥塞窗口（cwnd）肯定不能无止境的指数级增长下去，否则拥塞控制就变成了「拥塞失控」了，它的阈值称为「慢启动阈值」（Slow Start Threshold，ssthresh），这是文章开头介绍的拥塞控制的第二个核心状态值。ssthresh 就是一道刹车，让拥塞窗口别涨那么快。\n当 cwnd \u0026lt; ssthresh 时，拥塞窗口按指数级增长（慢启动） 当 cwnd \u0026gt;= ssthresh 时，拥塞窗口按线性增长（拥塞避免） 拥塞避免（Congestion Avoidance） 当 cwnd \u0026gt; ssthresh 时，拥塞窗口进入「拥塞避免」阶段，在这个阶段，每一个往返 RTT，拥塞窗口大约增加 1 个 MSS 大小，直到检测到拥塞为止(三次重复Ack)。\n与慢启动的区别在于\n慢启动的做法是 RTT 时间内每收到一个 ACK，也就是每经过 1 个 RTT，cwnd 翻倍 拥塞避免的做法保守的多，每经过一个RTT 才将拥塞窗口加 1，不管期间收到多少个 ACK 快速重传与快速恢复 当发送方收到3次或者更多次重复的Ack，就认为网络已经轻度拥塞了，这个时候会进行快速重传(只传SACK里面没有的包)，同时进入快速恢复阶段：\n拥塞阈值 ssthresh 降低为 cwnd 的一半：ssthresh = cwnd / 2 拥塞窗口 cwnd 设置为 ssthresh 拥塞窗口线性增加 (直到再次出现重复Ack，然后再次进行快速重发和快速恢复 如此循环) 现在的initcwnd已经不是1了 上图还是1\nNagle 算法 if there is new data to send if the window size \u0026gt;= MSS and available data is \u0026gt;= MSS send complete MSS segment now else if there is unconfirmed data still in the pipe enqueue data in the buffer until an acknowledge is received else send data immediately end if end if end if Nagle 算法的作用是减少小包在客户端和服务端直接传输，一个包的 TCP 头和 IP 头加起来至少都有 40 个字节，如果携带的数据比较小的话，那就非常浪费了。就好比开着一辆大货车运一箱苹果一样 Nagle 算法在通信时延较低的场景下意义不大。在 Nagle 算法中 ACK 返回越快，下次数据传输就越早 假设 RTT 为 10ms 且没有延迟确认，那么你敲击键盘的间隔大于 10ms 的话就不会触发 Nagle 的条件：只有接收到所有的在传数据的 ACK 后才能继续发数据，也即如果所有的发出去的包 ACK 都收到了，就不用等了。如果你想触发 Nagle 的停等（stop-wait）机制，1s 内要输入超过 100 个字符。因此如果在局域网内，Nagle 算法基本上没有什么效果 关闭Nagle算法 TCP_NODELAY选项设置为true\n小结 Nagle 算法，这个算法可以有效的减少网络上小包的数量。Nagle 算法是应用在发送端的，简而言之就是，对发送端而言：\n当第一次发送数据时不用等待，就算是 1byte 的小包也立即发送 后面发送数据时需要累积数据包直到满足下面的条件之一才会继续发送数据： 数据包达到最大段大小MSS 接收端收到之前数据包的确认 ACK 不过 Nagle 算法是时代的产物，可能会导致较多的性能问题，尤其是与ack延迟确认一起使用的时候。很多组件为了高性能都默认禁用掉了这个特性\nNagle 算法出现的时候网络带宽都很小，当有大量小包传输时，很容易将带宽占满，出现丢包重传等现象。因此对 ssh 这种交互式的应用场景，选择开启 Nagle 算法可以使得不再那么频繁的发送小包，而是合并到一起，代价是稍微有一些延迟。现在的 ssh 客户端已经默认关闭了 Nagle 算法。\nAck延迟确认 delayed ack 不是每个数据包都对应一个 ACK 包，因为可以合并确认。 也不是接收端收到数据以后必须立刻马上回复确认包 如果收到一个数据包以后暂时没有数据要分给对端，它可以等一段时间（Linux 上是 40ms）再确认。如果这段时间刚好有数据要传给对端，ACK 就可以随着数据一起发出去了。如果超过时间还没有数据要发送，也发送 ACK，以免对端以为丢包了。这种方式成为「延迟确认」\n这个原因跟 Nagle 算法其实一样，回复一个空的 ACK 太浪费了。\n如果接收端这个时候恰好有数据要回复客户端，那么 ACK 搭上顺风车一块发送。 如果期间又有客户端的数据传过来，那可以把多次 ACK 合并成一个立刻发送出去 如果一段时间没有顺风车，那么没办法，不能让接收端等太久，一个空包也得发。 什么场景立马回复Ack 如果接收到了大于一个frame 的报文，且需要调整窗口大小 处于 quickack 模式（tcp_in_quickack_mode） 收到乱序包（We have out of order data.） 其它情况一律采用Delayed ack\npingpong就是有来有回 一般是R-W-R-W\u0026hellip; 延迟确认出现的最多的场景是 W-W-R（写写读） Delayed ack例子 服务端 readLine 有返回非空字符串（读到\\n 或 \\r）就把字符串原样返回给客户端\npublic class DelayAckServer { private static final int PORT = 8888; public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(PORT)); System.out.println(\u0026#34;Server startup at \u0026#34; + PORT); while (true) { Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); int i = 1; while (true) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = reader.readLine(); if (line == null) break; System.out.println((i++) + \u0026#34; : \u0026#34; + line); outputStream.write((line + \u0026#34;\\n\u0026#34;).getBytes()); } } } } 客户端分两次调用 write 方法，模拟 http 请求的 header 和 body。第二次 write 包含了换行符（\\n)，然后测量 write、write、read 所花费的时间\npublic class DelayAckClient { public static void main(String[] args) throws IOException { Socket socket = new Socket(); socket.connect(new InetSocketAddress(\u0026#34;server_ip\u0026#34;, 8888)); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String head = \u0026#34;hello, \u0026#34;; String body = \u0026#34;world\\n\u0026#34;; for (int i = 0; i \u0026lt; 10; i++) { long start = System.currentTimeMillis(); outputStream.write((\u0026#34;#\u0026#34; + i + \u0026#34; \u0026#34; + head).getBytes()); // write outputStream.write((body).getBytes()); // write String line = reader.readLine(); // read System.out.println(\u0026#34;RTT: \u0026#34; + (System.currentTimeMillis() - start) + \u0026#34;: \u0026#34; + line); } inputStream.close(); outputStream.close(); socket.close(); } } 运行结果:\njavac DelayAckClient.java; java -cp . DelayAckClient RTT: 1: #0 hello, world RTT: 44: #1 hello, world RTT: 46: #2 hello, world RTT: 44: #3 hello, world RTT: 42: #4 hello, world RTT: 41: #5 hello, world RTT: 41: #6 hello, world RTT: 44: #7 hello, world RTT: 44: #8 hello, world RTT: 44: #9 hello, world 除了第一次，剩下的 RTT 全为 40 多毫秒。这刚好是 Linux 延迟确认定时器的时间 40ms .\n对包逐个分析一下 1 ~ 3：三次握手 4 ~ 9：第一次 for 循环的请求，也就是 W-W-R 的过程\n4：客户端发送 \u0026ldquo;#0 hello, \u0026quot; 给服务端 5：因为服务端只收到了数据还没有回复过数据，tcp 判断不是 pingpong 的交互式数据，属于 quickack 模式，立刻回复 ACK 6：客户端发送 \u0026ldquo;world\\n\u0026rdquo; 给服务端 7：服务端因为还没有回复过数据，tcp 判断不是 pingpong 的交互式数据，服务端立刻回复 ACK 8：服务端读到换行符，readline 函数返回，会把读到的字符串原样写入到客户端。TCP 这个时候检测到是 pingpong 的交互式连接，进入延迟确认模式 9：客户端收到数据以后回复 ACK 10 ~ 14：第二次 for 循环\n10：客户端发送 \u0026ldquo;#1 hello, \u0026quot; 给服务端。服务端收到数据包以后，因为处于 pingpong 模式，开启一个 40ms 的定时器，奢望在 40ms 内有数据回传 11：很不幸，服务端等了 40ms 定期器到期都没有数据回传，回复确认 ACK 同时取消 pingpong 状态 12：客户端发送 \u0026ldquo;world\\n\u0026rdquo; 给服务端 13：因为服务端不处于 pingpong 状态，所以收到数据立即回复 ACK 14：服务端读到换行符，readline 函数返回，会把读到的字符串原样写入到客户端。这个时候又检测到收发数据了，进入 pingpong 状态。 从第二次 for 开始，后面的数据包都一样了。 整个过程包交互图如下：\nNagle算法+Dealyed Ack 上面结果其实是默认开启了Nagle算法的，如果将其关闭 每次RTT都差不多，几乎为0.\n禁用 Nagle 的情况下，不用等一个包发完再发下一个，而是几乎同时把两次写请求发送出来了。服务端收到带换行符的包以后，立马可以返回结果，ACK 可以捎带过去，就不会出现延迟 40ms 的情况\nTCP的keepalive 背景 络故障或者系统宕机都将使得对端无法得知这个消息。如果应用程序不发送数据，可能永远无法得知该连接已经失效。假设应用程序是一个 web 服务器，客户端发出三次握手以后故障宕机或被踢掉网线，对于 web 服务器而已，下一个数据包将永远无法到来，但是它一无所知。TCP 不会采用类似于轮询的方式来询问：小老弟你有什么东西要发给我吗？\n这一个情况就是如果在未告知另一端的情况下通信的一端关闭或终止连接，那么就认为该条TCP连接处于半打开状态。 这种情况发现在通信的一方的主机崩溃、电源断掉的情况下。 只要不尝试通过半开连接来传输数据，正常工作的一端将不会检测出另外一端已经崩溃。 那么有一段将一直有一个ESTABLISHED状态的连接 占用资源 linux tcp keepalive相关参数 // 多少秒 没有数据包交互发送keepalive探测包 默认 ketonghe@ubuntu:~/code$ cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 //每次keepalive探测时间间隔 单位秒 ketonghe@ubuntu:~/code$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 //最多探测多少次 ketonghe@ubuntu:~/code$ cat /proc/sys/net/ipv4/tcp_keepalive_probes 9 为什么大部分应用程序都没有开启 keepalive 选项 现在大部分应用程序）都没有开启 keepalive 选项，一个很大的原因就是默认的超时时间太长了，从没有数据交互到最终判断连接失效，需要花 2.1875 小时（7200 + 75 * 9），显然太长了。但如果修改这个值到比较小，又违背了 keepalive 的设计初衷（为了检查长时间死连接）\n生产实践 一般都有心跳包：比如服务端针对每个连接计时，如果长时间没有收到客户端的数据就直接close掉这个连接。客户端如果想要维持长连接就间隔一定时间发送心跳包。 TCP的定时器 连接建立定时器（connection establishment） 当发送端发送 SYN 报文想建立一条新连接时，会开启连接建立定时器，如果没有收到对端的 ACK 包将进行重传。\n时间变化规律为: 间隔 1s、2s、4s、8s、16s、32s。。。。重传次数为配置:\n/proc/sys/net/ipv4/tcp_syn_retries 默认6\n重传定时器（retransmission） 如果在发送数据包的时候没有收到 ACK 呢？这就是这里要讲的第二个定时器重传定时器。重传定时器在时间是动态计算的，取决于 RTT 和重传的次数。\n/proc/sys/net/ipv4/tcp_retries2 配置最大重传次数 默认15\n延迟 ACK 定时器 TCP 收到数据包以后在没有数据包要回复时，不马上回复 ACK。这时开启一个定时器，等待一段时间看是否有数据需要回复。如果期间有数据要回复，则在回复的数据中捎带 ACK，如果时间到了也没有数据要发送，则也发送 ACK。linux很多默认40ms，这个可以参见上面的delayed ack章节\n坚持计时器（persist timer） 专门为零窗口探测而准备的，我们都知道 TCP 利用滑动窗口来实现流量控制，当接收端 B 接收窗口为 0 时，发送端 A 此时不能再发送数据，发送端A此时开启 Persist 定时器，超时后发送一个特殊的报文给接收端看对方窗口是否已经恢复，这个特殊的报文只有一个字节\n保活定时器（keepalive timer） 通信以后一段时间有再也没有传输过数据，怎么知道对方是不是已经挂掉或者重启了呢？于是 TCP 提出了一个做法就是在连接的空闲时间超过 2 小时，会发送一个探测报文，如果对方有回复则表示连接还活着，对方还在，如果经过几次探测对方都没有回复则表示连接已失效，客户端会丢弃这个连接。\nFIN_WAIT_2 定时器 四次挥手过程中，主动关闭的一方收到 ACK 以后从 FIN_WAIT_1 进入 FIN_WAIT_2 状态等待对端的 FIN 包的到来，FIN_WAIT_2 定时器的作用是防止对方一直不发送 FIN 包，防止自己一直傻等。这个值由/proc/sys/net/ipv4/tcp_fin_timeout 决定\nTIME_WAIT 定时器 TIME_WAIT存在的意义有两个：\n可靠的实现 TCP 全双工的连接终止（处理最后 ACK 丢失的情况） 避免当前关闭连接与后续连接混淆（让旧连接的包在网络中消逝） 一些工具使用 telnet //检查端口是否打开 telnet [domainname or ip] [port] ------------------------------------------------------------------------------------- //发送http请求 telnet www.baidu.com 80 GET / HTTP/1.1 Host: www.baidu.com ------------------------------------------------------------------------------------- //连接redis 执行命令 $ telnet 127.0.0.1 6379 Trying 127.0.0.1... Connected to localhost. Escape character is \u0026#39;^]\u0026#39;. set hello world +OK get hello $5 world netcat ------------------------------------------------------------------------------------- //快速启动监听 或者聊天 nc -l 9090 //快速启动进程 监听9090端口 l为监听listen的意思 nc localhost 9090 //连接本机的9090端口 接下来2端就可以聊天了 ------------------------------------------------------------------------------------- //可以跟telnet一样 发送http请求 $ nc www.baidu.com 80 GET / HTTP/1.1 host: www.baidu.com HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: no-cache Connection: keep-alive Content-Length: 14615 .... ------------------------------------------------------------------------------------- //用unix管道的形式 发送http请求 echo -ne \u0026#34;GET / HTTP/1.1\\r\\nhost:www.baidu.com\\r\\n\\r\\n\u0026#34; | nc www.baidu.com 80 echo 的 -n 参数很关键，echo 默认会在输出的最后增加一个换行，加上 -n 参数以后就不会在最后自动换行了。 执行上面的命令，可以看到也返回了百度的首页 html ------------------------------------------------------------------------------------- //查看远端端口是否打开 -z 参数表示不发送任何数据包，tcp 三次握手完后自动退出进程。有了 -v 参数则会输出更多详细信息（verbose） nc -zv [host or ip] [port] $ nc -zv www.baidu.com 80 Connection to www.baidu.com port 80 [tcp/http] succeeded! ------------------------------------------------------------------------------------- //也可以访问redis nc localhost 6379 ... 然后就可以执行命令了 $ echo get hello | nc localhost 6379 //也可以这样直接执行一个命令 $5 world ------------------------------------------------------------------------------------- netstat //参数说明 -a 列出所有套接字 包括各种协议 各种状态的 -t 列出tcp相关的 -u 列出udp相关的 -l 列出处于监听状态的 -n 禁用端口和ip映射 -p 显示进程 -i 显示所有网卡信息 ------------------------------------------------------------------------------------- //查看某个状态处于某个状态的连接 同时打印相关进程信息 ketonghe@ubuntu:~/code$ netstat -tnp|grep 8787|grep xxx (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 192.168.3.66:8787 192.168.3.66:43072 ESTABLISHED 88296/./server tcp 0 0 192.168.3.66:43072 192.168.3.66:8787 ESTABLISHED 88303/./client ------------------------------------------------------------------------------------- //统计某个端口处于各个状态的连接个数 ketonghe@ubuntu:~/code$ netstat -ant | grep 8787|awk \u0026#39;{print $6}\u0026#39; | sort | uniq -c | sort -n 2 LISTEN 4 ESTABLISHED tcpdump //常用参数说明 另外一般都要root权限 -i 指定网卡 tcpdump -i any就是所有网卡 ifconfig可以查看网卡信息 -i en1就是只监控en1网卡的 host 指定主机 sudo tcpdump -i en1 host 10.211.55.2 //监控en1网卡 主机是xxx的信息 这里没有指定src dst所以可以是源ip或者目标ip sudo tcpdump -i any src 10.211.55.10//只抓取主机xx发出的包 sudo tcpdump -i any dst 10.211.55.1//只抓去主机xx收到的包 port选项 指定端口 sudo tcpdump -i any dst port 80 //只抓取80端口收到的包 tcpdump portrange 21-23 //抓取 21 到 23 区间所有端口的流量 限定端口范围 -n 禁止ip映射 显示真实ip -nn 显示真实ip和端口 都不映射 sudo tcpdump -i any -nn udp //过滤协议 只查看udp -A 用 ASCII 打印报文内容，比如常用的 HTTP 协议传输 json 、html 文件等都可以用这个选项 sudo tcpdump -i any -nn port 80 -A 输出如下： 11:04:25.793298 IP 183.57.82.231.80 \u0026gt; 10.211.55.10.40842: Flags [P.], seq 1:1461, ack 151, win 16384, length 1460 HTTP/1.1 200 OK Server: Tengine Content-Type: application/javascript -X 命令，用来同时用 HEX 和 ASCII 显示报文内容。 sudo tcpdump -i any -nn port 80 -X 11:33:53.945089 IP 36.158.217.225.80 \u0026gt; 10.211.55.10.45436: Flags [P.], seq 1:1461, ack 151, win 16384, length 1460 0x0000: 4500 05dc b1c4 0000 8006 42fb 249e d9e1 E.........B.$... 0x0010: 0ad3 370a 0050 b17c 3b79 032b 8ffb cf66 ..7..P.|;y.+...f 0x0020: 5018 4000 9e9e 0000 4854 5450 2f31 2e31 P.@.....HTTP/1.1 0x0030: 2032 3030 204f 4b0d 0a53 6572 7665 723a .200.OK..Server: 0x0040: 2054 656e 6769 6e65 0d0a 436f 6e74 656e .Tengine..Conten 0x0050: 742d 5479 7065 3a20 6170 706c 6963 6174 t-Type:.applicat -s 选项 限制包大小 当包体很大，可以用 -s 选项截取部分报文内容，一般都跟 -A 一起使用。查看每个包体前 500 字节可以用下面的命令 sudo tcpdump -i any -nn port 80 -A -s 500 -s 0 就是显示包的所有内容 -c 选项 抓取固定个数的报文 可以抓取 number 个报文后退出。在网络包交互非常频繁的服务器上抓包比较有用，可能运维人员只想抓取 1000 个包来分析一些网络问题，就比较有用了 sudo tcpdump -i any -nn port 80 -c 5 -w 报文输出到文件 sudo tcpdump -i any port 80 -w test.pcap 生成的 pcap 文件就可以用 wireshark 打开进行更详细的分析了 也可以加上 -U 强制立即写到本地磁盘。性能较差 -S 显示绝对的序列号 不加都是默认从0开始 也就是相对的 相关布尔运算使用 tcpdump 真正强大的是可以用布尔运算符and（或\u0026amp;\u0026amp;）、or（或||）、not（或!）来组合出任意复杂的过滤器 抓取 ip 为 10.211.55.10 到端口 3306 的数据包 sudo tcpdump -i any host 10.211.55.10 and dst port 3306 抓取源 ip 为 10.211.55.10，目标端口除了22 以外所有的流量 sudo tcpdump -i any src 10.211.55.10 and not dst port 22 复杂分组问题 如果要抓取：来源 ip 为 10.211.55.10 且目标端口为 3306 或 6379 的包，按照前面的描述，我们会写出下面的语句 sudo tcpdump -i any src 10.211.55.10 and (dst port 3306 or 6379) ----上面这个是错误的 因为有特殊符号() 解决办法是使用单引号把复杂的组合条件包起来 sudo tcpdump -i any \u0026#39;src 10.211.55.10 and (dst port 3306 or 6379)\u0026#39; ---这个是正确的 如果想显示所有的 RST 包，要如何来写 tcpdump 的语句呢？答案如下: tcpdump \u0026#39;tcp[13] \u0026amp; 4 != 0\u0026#39; tcp[13] 表示 tcp 头部中偏移量为 13 字节，如上图中红色框的部分，\n!=0 表示当前 bit 置 1，即存在此标记位，跟 4 做与运算是因为 RST 在 TCP 的标记位的位置在第 3 位(00000100)\n如果想过滤 SYN + ACK 包，那就是 SYN 和 ACK 包同时置位（00010010），写成 tcpdump 语句就是\ntcpdump \u0026#39;tcp[13] \u0026amp; 18 != 0\u0026#39; tcpdump输出解读 A机器用nc -l 8080启动一个 tcp 的服务器，然后启动 tcpdump 抓包（sudo tcpdump -i any port 8080 -nn -A ）\nB机器 用 nc 10.211.55.10 8080进行连接，然后输入\u0026quot;hello, world\u0026quot;回车 过一段时间 ctrl-c结束连接 整个抓包过程如下\n1 16:46:22.722865 IP 10.211.55.5.45424 \u0026gt; 10.211.55.10.8080: Flags [S], seq 3782956689, win 29200, options [mss 1460,sackOK,TS val 463670960 ecr 0,nop,wscale 7], length 0 2 16:46:22.722903 IP 10.211.55.10.8080 \u0026gt; 10.211.55.5.45424: Flags [S.], seq 3722022028, ack 3782956690, win 28960, options [mss 1460,sackOK,TS val 463298257 ecr 463670960,nop,wscale 7], length 0 3 16:46:22.723068 IP 10.211.55.5.45424 \u0026gt; 10.211.55.10.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 463670960 ecr 463298257], length 0 4 16:46:25.947217 IP 10.211.55.5.45424 \u0026gt; 10.211.55.10.8080: Flags [P.], seq 1:13, ack 1, win 229, options [nop,nop,TS val 463674184 ecr 463298257], length 12 hello world 5 16:46:25.947261 IP 10.211.55.10.8080 \u0026gt; 10.211.55.5.45424: Flags [.], ack 13, win 227, options [nop,nop,TS val 463301481 ecr 463674184], length 0 6 16:46:28.011057 IP 10.211.55.5.45424 \u0026gt; 10.211.55.10.8080: Flags [F.], seq 13, ack 1, win 229, options [nop,nop,TS val 463676248 ecr 463301481], length 0 7 16:46:28.011153 IP 10.211.55.10.8080 \u0026gt; 10.211.55.5.45424: Flags [F.], seq 1, ack 14, win 227, options [nop,nop,TS val 463303545 ecr 463676248], length 0 8 16:46:28.011263 IP 10.211.55.5.45424 \u0026gt; 10.211.55.10.8080: Flags [.], ack 2, win 229, options [nop,nop,TS val 463676248 ecr 463303545], length 0 第 1~3 行是 TCP 的三次握手的过程\n第 1 行 中，第一部分是这个包的时间（16:46:22.722865），显示到微秒级。接下来的 \u0026ldquo;10.211.55.5.45424 \u0026gt; 10.211.55.10.8080\u0026rdquo; 表示 TCP 四元组：包的源地址、源端口、目标地址、目标端口，中间的大于号表示包的流向。接下来的 \u0026ldquo;Flags [S]\u0026rdquo; 表示 TCP 首部的 flags 字段，这里的 S 表示设置了 SYN 标志，其它可能的标志有 F：FIN 标志 R：RST 标志 P：PSH 标志 U：URG 标志 . ：没有标志，ACK 情况下使用 接下来的 \u0026ldquo;seq 3782956689\u0026rdquo; 是 SYN 包的序号。需要注意的是默认的显示方式是在 SYN 包里的显示真正的序号，在随后的段中，为了方便阅读，显示的序号都是相对序号。\n接下来的 \u0026ldquo;win 29200\u0026rdquo; 表示自己声明的接收窗口的大小\n接下来用[] 包起来的 options 表示 TCP 的选项值，里面有很多重要的信息，比如 MSS、window scale、SACK 等\n最后面的 length 参数表示当前包的长度\n第 2 行是一个 SYN+ACK 包，如前面所说，SYN 包中包序号用的是绝对序号，后面的 win = 28960 也声明的发送端的接收窗口大小。 从第 3 行开始，后面的包序号都用的是相对序号了。第三行是客户端 B 向服务端 A 发送的一个 ACK 包。注意这里 win=229，实际的窗口并不是 229，因为窗口缩放（window scale） 在三次握手中确定，后面的窗口大小都需要乘以 window scale 的值 2^7（128），比如这里的窗口大小等于 229 * 2^7 = 229 * 128 = 29312 第 4 行是客户端 B 向服务端 A 发送\u0026quot;hello world\u0026quot;字符串，这里的 flag 为P.,表示 PSH+ACK。发送包的 seq 为 1:13，长度 length 为 12。窗口大小还是 229 * 128\n第 5 行是服务端 A 收到\u0026quot;hello world\u0026quot;字符串以后回复的 ACK 包，可以看到 ACK 的值为 13，表示序号为 13 之前的所有的包都已经收到，下次发包从 13 开始发\n第 6 行是客户端 B 执行 Ctrl+C 以后nc 客户端准备退出时发送的四次挥手的第一个 FIN 包，包序号还是 13，长度为 0\n第 7 行是服务端 A 对 B 发出的 FIN 包后，也同时回复 FIN + ACK，因为没有往客户端传输过数据包，所以这里的 SEQ 还是 1\n第 8 行是客户端 A 对 服务端 B 发出的 FIN 包回复的 ACK 包\n当然可以用-w选项 输出到文件 然后用wireshark分析就更方便了 wireshark 图形化界面抓包工具，对应命令行版本是 tshark 用的比较少\n抓包过滤 ​\t抓包的过程很耗 CPU 和内存资源而且大部分情况下我们不是对所有的包都感兴趣，因此可以只抓取满足特定条件的包，丢弃不感兴趣的包，比如只想抓取 ip 为172.18.80.49 端口号为 3306 的包，可以输入host 172.18.80.49 and port 3306，语法跟tcpdump差不多\n可以在抓包之前设置\n显示结果过滤 Display fliter 显示过滤可以算是 wireshark 最常用的功能了，与抓包过滤不一样的是，显示过滤不会丢弃包的内容，不符合过滤条件的包被隐藏起来，方便我们阅读。\n过滤的方式常见的有以下几种：\n协议、应用过滤器（ip/tcp/udp/arp/icmp/ dns/ftp/nfs/http/mysql) 字段过滤器（http.host/dns.qry.name） 比如我们只想看 http 协议报文，在过滤器中输入 http 即可\n字段过滤器可以更加精确的过滤出想要的包，比如我们只想看锤科网站t.tt域名的 dns 解析，可以输入dns.qry.name == t.tt\n再比如，我只想看访问锤科的 http 请求，可以输入http.host == t.tt\n可以随便找一个 dns 的查询，找到查询报文，展开详情里面的内容，然后鼠标选中想过滤的字段，最下面的状态码就会出现当前 wireshark 对应的查看条件，比如下图中的dns.qry.name\n常用的查询条件有： tcp 相关过滤器 tcp.flags.syn==1：过滤 SYN 包 tcp.flags.reset==1：过滤 RST 包 tcp.analysis.retransmission：过滤重传包 tcp.analysis.zero_window：零窗口 http 相关过滤器 http.host==t.tt：过滤指定域名的 http 包 http.response.code==302：过滤http响应状态码为302的数据包 http.request.method==POST：过滤所有请求方式为 POST 的 http 请求包 http.transfer_encoding == \u0026ldquo;chunked\u0026rdquo; 根据transfer_encoding过滤 http.request.uri contains \u0026ldquo;/appstock/app/minute/query\u0026rdquo;：过滤 http 请求 url 中包含指定路径的请求 通信延迟常用的过滤器 http.time\u0026gt;0.5：请求发出到收到第一个响应包的时间间隔，可以用这个条件来过滤 http 的时延 tcp.time_delta\u0026gt;0.3：tcp 某连接中两次包的数据间隔，可以用这个来分析 TCP 的时延 dns.time\u0026gt;0.5：dns 的查询耗时 wireshakr 所有的查询条件在这里可以查到：https:/ /www.wireshark.org/docs/dfref/\n比较预算符 wireshark 支持比较运算符和逻辑运算符。这些运算符可以灵活的组合出强大的过滤表达式。\n等于：== 或者 eq 不等于：!= 或者 ne 大于：\u0026gt; 或者 gt 小于：\u0026lt; 或者 lt 包含 contains 匹配 matches 与操作：AND 或者 \u0026amp;\u0026amp; 或操作：OR 或者 || 取反：NOT 或者 ! 比如想过滤 ip 来自 10.0.0.10 且是 TCP 协议的数据包：\nip.addr == 10.0.0.10 and tcp 协议分层解释 Frame：物理层的数据帧 Ethernet II：数据链路层以太网帧头部信息 Internet Protocol Version 4：互联网层IP包头部信息 Transmission Control Protocol：传输层的数据段头部信息，此处是TCP协议 Hypertext Transfer Protocol：应用层 HTTP 的信息 跟踪 TCP 数据流（Follow TCP Stream） 在实际使用过程中，跟踪 TCP 数据流是一个很高频的使用。我们通过前面介绍的那些过滤条件找到了一些包，大多数情况下都需要查看这个 TCP 连接所有的包来查看上下文。\n这样就可以查看整个连接的所有包交互情况了，如下图所示，三次握手、数据传输、四次挥手的过程一目了然\n解密HTTPS包 随着 https 和 http2.0 的流行，https 正全面取代 http，这给我们抓包带来了一点点小困难。Wireshark 的抓包原理是直接读取并分析网卡数据。 下图是访问 www.baidu.com 的部分包截图，传输包的内容被加密了。\n要想让它解密 HTTPS 流量，要么拥有 HTTPS 网站的加密私钥，可以用来解密这个网站的加密流量，但这种一般没有可能拿到。要么某些浏览器支持将 TLS 会话中使用的对称加密密钥保存在外部文件中，可供 Wireshark 解密流量。 在启动 Chrome 时加上环境变量 SSLKEYLOGFILE 时，chrome 会把会话密钥输出到文件。\nSSLKEYLOGFILE=/tmp/SSLKEYLOGFILE.log /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome wireshark 可以在Wireshark -\u0026gt; Preferences... -\u0026gt; Protocols -\u0026gt; SSL打开Wireshark 的 SSL 配置面板，在(Pre)-Master-Secret log filename选项中输入 SSLKEYLOGFILE 文件路径。\n抓包的一些应用实战(转载) JDBC批量插入问题排查 几年前遇到过一个问题，使用 jdbc 批量插入，插入的性能总是上不去，看代码又查不出什么结果。代码简化以后如下：\npublic static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName(\u0026#34;com.mysql.jdbc.Driver\u0026#34;); String url = \u0026#34;jdbc:mysql://localhost:3306/test?useSSL=false\u0026#34;; Connection connection = DriverManager.getConnection(url, \u0026#34;root\u0026#34;, \u0026#34;\u0026#34;); PreparedStatement statement = connection.prepareStatement(\u0026#34;insert into batch_insert_test(name)values(?)\u0026#34;); for (int i = 0; i \u0026lt; 10; i++) { statement.setString(1, \u0026#34;name#\u0026#34; + System.currentTimeMillis() + \u0026#34;#\u0026#34; + i); statement.addBatch(); } statement.executeBatch(); } 通过 wireshark 抓包，结果如下\n可以看到 jdbc 实际上是发送了 10 次 insert 请求，既不能降低网络通信的成本，也不能在服务器上批量执行。\n单步调试，发现调用到了executeBatchSerially\n看源码发现跟connection.getRewriteBatchedStatements()有关，当等于 true 时，会进入批量插入的流程，等于 false 时，进入逐条插入的流程。\n修改 sql 连接的参数，增加rewriteBatchedStatements=true\n// String url = \u0026#34;jdbc:mysql://localhost:3306/test?useSSL=false\u0026#34;; String url = \u0026#34;jdbc:mysql://localhost:3306/test?useSSL=false\u0026amp;rewriteBatchedStatements=true\u0026#34;; wireshark 抓包情况如下，可以确认批量插入生效了\nrewriteBatchedStatements 参数将\ninsert into batch_insert_test(name)values(\u0026#39;name#1554175696958#0\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#1\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#2\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#3\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#4\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#5\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#6\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#7\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#8\u0026#39;) insert into batch_insert_test(name)values(\u0026#39;name#1554175696958#9\u0026#39;) 改写为真正的批量插入\ninsert into batch_insert_test(name)values (\u0026#39;name#1554175696958#0\u0026#39;),(\u0026#39;name#1554175696958#1\u0026#39;), (\u0026#39;name#1554175696958#2\u0026#39;),(\u0026#39;name#1554175696958#3\u0026#39;), (\u0026#39;name#1554175696958#4\u0026#39;),(\u0026#39;name#1554175696958#5\u0026#39;), (\u0026#39;name#1554175696958#6\u0026#39;),(\u0026#39;name#1554175696958#7\u0026#39;), (\u0026#39;name#1554175696958#8\u0026#39;),(\u0026#39;name#1554175696958#9\u0026#39;) 思考 我们经常会用很多第三方的库，这些库我们一般没有精力把每行代码都读通读透，遇到问题时，抓一些包也许就可以很快确定问题的所在，这就是抓包网络分析的魅力所在。\nNginx 频繁出现 OOM问题排查 在最近的一次百万长连接压测中，32C 128G 的四台 Nginx 频繁出现 OOM(内存耗尽)\n现象描述 这是一个 websocket 百万长连接收发消息的压测环境，客户端 jmeter 用了上百台机器，经过四台 Nginx 到后端服务，简化后的部署结构如下图所示。\n在维持百万连接不发数据时，一切正常，Nginx 内存稳定。在开始大量收发数据时，Nginx 内存开始以每秒上百 M 的内存增长，直到占用内存接近 128G，woker 进程开始频繁 OOM 被系统杀掉。32 个 worker 进程每个都占用接近 4G 的内存。dmesg -T 的输出如下所示\n[Fri Mar 13 18:46:44 2020] Out of memory: Kill process 28258 (nginx) score 30 or sacrifice child [Fri Mar 13 18:46:44 2020] Killed process 28258 (nginx) total-vm:1092198764kB, anon-rss:3943668kB, file-rss:736kB, shmem-rss:4kB work 进程重启后，大量长连接断连，压测就没法继续增加数据量\n排查过程分析 拿到这个问题，首先查看了 Nginx 和客户端两端的网络连接状态，使用 ss -nt 命令可以在 Nginx 看到大量 ESTABLISH 状态连接的 Send-Q 堆积很大，客户端的 Recv-Q 堆积很大。Nginx 端的 ss 部分输出如下所示 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 792024 1.1.1.1:80 2.2.2.2:50664 ... 如果不是listen套接字 这里的Send-Q就是发送缓冲区的大小 说明nginx发给客户端的缓冲区太大了 在 jmeter 客户端抓包偶尔可以看到较多零窗口，如下所示。 到了这里有了一些基本的方向，首先怀疑的就是 jmeter 客户端处理能力有限，有较多消息堆积在中转的 Nginx 这里。\n为了验证想法，想办法 dump 一下 nginx 的内存看看。因为在后期内存占用较高的状况下，dump 内存很容易失败，这里在内存刚开始上涨没多久的时候开始 dump。\n首先使用 pmap 查看其中任意一个 worker 进程的内存分布，这里是 4199，使用 pmap 命令的输出如下所示。\npmap -x 4199 | sort -k 3 -n -r 00007f2340539000 475240 461696 461696 rw--- [ anon ] ... 随后使用 cat /proc/4199/smaps | grep 7f2340539000 查找某一段内存的起始和结束地址，如下所示。\ncat /proc/3492/smaps | grep 7f2340539000 7f2340539000-7f235d553000 rw-p 00000000 00:00 0 随后使用 gdb 连上这个进程，dump 出这一段内存。\ngdb -pid 4199 dump memory memory.dump 0x7f2340539000 0x7f235d553000 随后使用 strings 命令查看这个 dump 文件的可读字符串内容，可以看到是大量的请求和响应内容。\n这样坚定了是因为缓存了大量的消息导致的内存上涨。随后看了一下 Nginx 的参数配置\nlocation / { proxy_pass http://xxx; proxy_set_header X-Forwarded-Url \u0026#34;$scheme://$host$request_uri\u0026#34;; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Cookie $http_cookie; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 512M; client_body_buffer_size 64M; proxy_connect_timeout 900; proxy_send_timeout 900; proxy_read_timeout 900; proxy_buffer_size 64M; proxy_buffers 64 16M; proxy_busy_buffers_size 256M; proxy_temp_file_write_size 512M; } 可以看到 proxy_buffers 这个值设置的特别大。可以模拟重现：\n解决方案 因为要支持上百万的连接，针对单个连接的资源配额要小心又小心。一个最快改动方式是把 proxy_buffering 设置为 off，如下所示。\nproxy_buffering off; 经过实测，在压测环境修改了这个值以后，以及调小了 proxy_buffer_size 的值以后，内存稳定在了 20G 左右，没有再飙升过.\n后面可以开启 proxy_buffering，调整 proxy_buffers 的大小可以在内存消耗和性能方面取得更好的平衡\nNginx 的 buffering 机制设计的初衷确实是为了解决收发两端速度不一致问题的，没有 buffering 的情况下，数据会直接从后端服务转发到客户端，如果客户端的接收速度足够快，buffering 完全可以关掉。但是这个初衷在海量连接的情况下，资源的消耗需要同时考虑进来，如果有人故意伪造比较慢的客户端，可以使用很小的代价消耗服务器上很大的资源。 其实这是一个非阻塞编程中的典型问题，接收数据不会阻塞发送数据，发送数据不会阻塞接收数据。如果 Nginx 的两端收发数据速度不对等，缓冲区设置得又过大，就会出问题了。 思考 有过程中一些辅助的判断方法，比如通过 strace、systemtap 工具跟踪内存的分配、释放过程，这里没有展开，这些工具是分析黑盒程序的神器。\n除此之外，在这次压测过程中还发现了 worker_connections 参数设置不合理导致 Nginx 启动完就占了 14G 内存等问题，这些问题在没有海量连接的情况下是比较难发现的。\n最后，底层原理是必备技能，调参是门艺术。\n","permalink":"https://www.becool.vip/posts/tech/tcp_ip/","summary":"\u003ch1 id=\"相关历史及分成模型\"\u003e相关历史及分成模型\u003c/h1\u003e\n\u003ch2 id=\"历史介绍\"\u003e历史介绍\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e1969年 美国担心敌人会摧毁自己的网络，所以国防部高级研究计划局（Advanced Research Projects Agency，ARPA）下决心要建立一个高可用的网络，即使部分线路或者交换机的故障不会导致整个网络的瘫痪，于是有了后来的ARPANET（Advanced Research Project Agency Network）\u0026mdash;最早只是一个简单的分组交换网\u003c/li\u003e\n\u003cli\u003e经过不断发展，原始的ARPANET慢慢变成了多个网络互联，逐步促成了互联网的出现。\u003c/li\u003e\n\u003cli\u003e1983年TCP/IP 协议成为 ARPANET 上的标准协议，现在互联网世界这么繁荣都得意与TCP/IP协议，当然任何一个行业越是繁荣昌盛，就越是有良好的协议标准，接口标准，TCP/IP就是网际互联中最流行的协议标准，也正是因为其流行，互联网才能越发发达。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"分层模型\"\u003e分层模型\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e一般都会提到7层 4层 或者 5层，下面给出一张图做个简单对比\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/ISO-OSI%e6%a8%a1%e5%9e%8b%e4%b8%8eTCPIP%e5%88%86%e5%b1%82%e6%a8%a1%e5%9e%8b%e5%af%b9%e6%af%94.png\" alt=\"\"  /\u003e\n\u003c/p\u003e","title":"TCP/IP"}]