[{"content":"","date":"2024-05-14","externalUrl":null,"permalink":"/categories/cache/","section":"Categories","summary":"","title":"Cache","type":"categories"},{"content":"","date":"2024-05-14","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2024-05-14","externalUrl":null,"permalink":"/categories/concurrent/","section":"Categories","summary":"","title":"Concurrent","type":"categories"},{"content":"","date":"2024-05-14","externalUrl":null,"permalink":"/post/","section":"Posts","summary":"","title":"Posts","type":"post"},{"content":" 一个基于共享内存，多线程多进程并发安全，key value 变长 的 LRU 缓存。 内部基于 TLSF 内存分配器, 和 LevelDB 的 LRUCache 代码实现 # 一，介绍 # 一个基于共享内存，多线程多进程并发安全，key value 变长 的 LRU 缓存。 内部基于 TLSF 内存分配器, 和 LevelDB 的 LRUCache 代码\n基于 TLSF 内存分配器。存入多少数据就占用多少内存。释放掉的 key/value 等内存空间自动回收重用。\n基于 LevelDB 的 LRUCache ，读操作和写操作可以并发执行，内部通过读引用计数实现。\n读操作直接返回指向共享内存的指针，零拷贝。\n支持 CompareAndSwap CAS 乐观锁操作\n多进程多线程并发读写 ShmLRUCache 时, 如果个别进程异常退出，则其他进程会在读写的时候检测到， 会删除共享内存文件将当前进程退出，等待进程被重启之后重建新的共享内存使用。利用 pthread mutex 的 robust list 实现\n支持设置 TTL 过期时间，达到过期时间的 key 会在哈希桶 Lookup 访问过程中被淘汰删除。\n开发了配套工具 dump_shm_lru_cache ，可以在运行时只读打印共享内存中的所有 key value\n二，使用方法 # 暂不开源，没有使用方法 # 常用共享内存相关工具命令：\ntop 共享内存都算入 top 看到的 SHR 即共享内存这一列，同时也算入 RES 即驻留内存这一列。\ndu -sh /dev/shm/* 查看共享内存文件实际占用多少物理内存\nlsof -p $pid |egrep 'NAME|/dev/shm' 查看进程 $pid 实际打开了哪些共享内存文件\npmap -X $pid |egrep 'Rss|finder_' 查看进程的共享内存文件 finder_* 都映射到哪些地址段\nls -lhS /dev/shm/* 查看共享内存文件最大允许的大小。注意不是实际占用大小。\n三，性能测试，固定使用 4 进程 * 4 线程 = 16 线程 # 下面的性能测试工具代码也和代码在同一个目录下，可以随时自己编译自己复现。\n1. 纯读，96% 命中率时 550W QPS，50% 命中率时 640W QPS # 2. 覆盖写, 43 WQPS # 3. 读写并发进行 ( 4x4=16 个线程读，同时另外有 4x4=16 个线程写)，此时读性能 420W QPS，写性能 41W QPS # 4. 典型业务逻辑“先查询如果 miss 就写”，77% 命中率的情况下，400 W QPS # 四，几个方面的关键设计 # 1. 为什么不搞无锁？ # 因为无锁只能对 8/16 字节的简单内存操作实现原子性，而想实现变长就要搞个主流一点的 best fit 类的内存分配器算法，这类算法都有复杂的管理数据结构，比如由多个链表，bitmap，红黑树 等组成的复合数据结构，其上的操作基本不可能实现成无锁。\n2. 怎么应对多进程下某些进程的异常退出？ # 背景：pthread_mutex 有个 robust list 功能 (PTHREAD_MUTEX_ROBUST) ，其语义是“当多个进程 attach 到同一块共享内存，并且其中一个进程在加锁进入临界区之后被 kill ，则其他进程在下次 pthread_mutex_lock 时会收到特殊返回码 EOWNERDEAD”。\n基于 robust list，可以把 “被 kill ” 分为 2 种来做应对：\n在临界区内被 kill，这种情况通过 EOWNERDEAD 可以检测到，直接删掉共享内存文件，重启进程就行。\n在临界区外被 kill， 只要保证所有修改数据结构的操作都在临界区中，则这种情况不会影响到 lru cache 的数据一致性。\n3. 在每个进程映射到同样的起始虚拟地址，还是不同的起始虚拟地址即搞 boost::interprocess::offset_ptr 那种？ # 这里选择了多进程映射到同样起始地址，同样起始地址的好处是里面的数据结构可以用裸指针，代码实现简单。\n背景是因为我发现 X86-64 平台上 48bit 地址空间非常巨大，pmap 看到其实大多数地址空间是没有使用的，目前是从 90TB 这个虚拟地址开始尝试分配，如果冲突就后移。并且这里利用了新一点 Linux kernel 的 MAP_FIXED_NOREPLACE 选项，可以避免覆盖已有的内存映射。\n4. 起始虚拟地址是怎么分配出来的？ # 创建 shm 文件的时候进行分配，读 /proc/self/maps 获知本进程的地址空间分布，从中找到从 90TB 开始并且没有被占用的空洞，来进行 mmap，如果冲突了即地址空间已经被占用了，再次尝试。\n其他进程 attach 已经存在的 shm 文件的时候，有个 CheckAttach 的检查逻辑，如果发现本进程的地址空间被占了，会返回 kMMap2BaseAddrConflicted , 然后 Remove 删掉共享内存文件。（Linux 下进程打开文件后，如果被 deleted， 还是可以正常读写的，不受影响。）\n这里的过程就有点像个活锁，经过多次互相删除后，最终所有进程会达成统一，在相同的虚拟地址上映射同一个共享内存文件。\n不同的 shm_name 文件，会分配出不同的起始地址。\n5. 怎么限制内存使用？ # 目前有 2 种限制策略：总条数限制，和总字节数限制。当达到限制时，会进行淘汰，有 2 种淘汰策略：LRU 和 TTL。\n6. 用什么内存分配器算法？ # 找了目前业界比较成熟的一个嵌入式分配器 TLSF 直接复用。\n7. LRU Cache 数据结构怎么实现？ # 直接把 LevelDB 中的经典 LRU Cache 代码复制出来做改造，该实现的一大亮点是搞了个引用计数，使得读写可以并发进行。并且我直接继承了其很多单元测试，用来保证代码正确性。\n8. 节省内存的技巧 # 使用了 varint 7bit 编码保存 key/value 的长度 。\nLRU 中节点的三个指针，换算成到基地址的 offset，并考虑了 align 和 48bit 地址空间后，用位运算压缩到了每个指针 35bit 。\n五. 和业界类似项目对比 # 1. boost::interprocess # (1). shm_lru_cache 在 robust list 检测到 corrupted 后，能定制 多进程 rpc 环境下的恢复策略 : _exit()。\n(2). shm 上 robust list 的 mutex 的实现，需要自己实现多进程对 OWNER DIED 状态的发现机制。\n(3). shm_allocator 实现了 stateless allocator 无状态的分配器，用于各种库更省内存，比如各种带 Alloc 模板参数的 std 类，容器等。 boost::interprocess 的 allocator 是有状态的。\n2. memcached # (1). 没有网络层，因此不可能有网络层的故障\n(2). 性能大幅度提升，\n3. libshmcache # https://github.com/happyfish100/libshmcache\nshm_lru_cache 利用了 robust list 实现的 corrupted 检测，这才是严密的。\nTLSF 有 merge/absorb 逻辑，能适应 value 长度的各种变化。\n4. lmdb/mdbx # 设计用途不同，lmdb/mdbx 是 B+ 树有序 map 数据结构 key-value 存储引擎。性能有限。\n5. redis # “Redis 6.0 主备实例测试数据”：\nhttps://support.huaweicloud.com/pwp-dcs/dcs_10_0001.html#section2\nhttps://redis.io/docs/management/optimization/benchmarks/\nGET/SET 大概是 15W QPS，当然 redis 是单核还有网络层而且是文件持久化的，适用场景有点不一样。\n6. dragonfly # https://github.com/dragonflydb/dragonfly/blob/main/README.zh-CN.md#- 基准测试\u0026gt;\n7. golang 的各种 cache # 这些都不是基于共享内存的，而且性能也有差距\n","date":"2024-05-14","externalUrl":null,"permalink":"/post/2024/05/14/shm-concurrent-lru-cache/","section":"Posts","summary":"一个基于共享内存，多线程多进程并发安全，key value 变长 的 LRU 缓存。 内部基于 TLSF 内存分配器, 和 LevelDB 的 LRUCache 代码实现 # 一，介绍 # 一个基于共享内存，多线程多进程并发安全，key value 变长 的 LRU 缓存。 内部基于 TLSF 内存分配器, 和 LevelDB 的 LRUCache 代码\n","title":"shm_lru_cache 共享内存 并发 变长 LRU 缓存 ","type":"post"},{"content":"","date":"2024-05-14","externalUrl":null,"permalink":"/","section":"Tech Explorer","summary":"","title":"Tech Explorer","type":"page"},{"content":"","date":"2020-11-10","externalUrl":null,"permalink":"/categories/btree/","section":"Categories","summary":"","title":"Btree","type":"categories"},{"content":"","date":"2020-11-10","externalUrl":null,"permalink":"/categories/hashmap/","section":"Categories","summary":"","title":"Hashmap","type":"categories"},{"content":" 1. 背景 # 后台开发很常见一大类需求是 线程安全 高性能 容器数据结构 开源的 https://github.com/greg7mdp/parallel-hashmap parallel-hashmap 是对 Google 的 abseil-cpp 库的改进，可供开发中直接使用。\n参考官网的英文文档，简单翻译介绍如下：\n[TOC]\n2. 概览 # parallel-hashmap 提供了一组卓越的 hash map 实现， 和 可以替代 std::map 和 std::set 的 btree 实现，并有如下特点:\nHeader only: 无需链接。直接 include 头文件即可用。\ndrop-in replacement for std::unordered_map, std::unordered_set, std::map and std::set。 原样替换，api 完全兼容标准 stl 容器 。\n要求支持 C++11 的编译器 , 并且提供 C++14 和 C++17 的 API (例如 try_emplace)\nVery efficient, 明显比编译器默认提供的 unordered map/set 快， 也比 boost 的实现快，比 sparsepp 快。这里有个 benchmark ： https://martin.ankerl.com/2019/04/01/hashmap-benchmarks-01-overview/ ，大概 比 std::unorder_map 就是 insert快1倍，find 快3倍\nMemory friendly: 低内存占用，尽管略微高于 sparsepp 。 参考 abseil 的数据，https://abseil.io/about/design/btree 目前 64位模式下， libstdc++ 实现的 std::set\u0026lt;int32_t\u0026gt; 对插入的每个value，分配 40 字节， 而 absl::btree_set\u0026lt;int32_t\u0026gt; 对每个 value 分配 ~4.3 至 ~5.1 字节\n支持 heterogeneous lookup\n很容易前向声明 forward declare: 只需要在头文件中 include phmap_fwd_decl.h 来声明 Parallel Hashmap 容器就行 [注: 目前这种对包含指针作为 key 的哈希表不适用。]\nDump/load 特性: 当一个 flat 哈希表存储了 std::trivially_copyable 的数据时, 表可以被 dump 到磁盘文件，并作为一个简单的数组高效地 restore 恢复，并且过程中不需要进行 hash 计算。这通常比逐个元素地序列化到磁盘快 10倍，但是会额外占用 10% - 60% 的磁盘空间。 见 examples/serialize.cc. (flat hash map/set only)\nTested 在这些平台上经过了测试： Windows (vs2015 \u0026amp; vs2017, vs2019, Intel compiler 18 and 19), linux (g++ 4.8.4, 5, 6, 7, 8, clang++ 3.9, 4.0, 5.0) 和 MacOS (g++ and clang++) - click on travis and appveyor icons above for detailed test status.\n自动支持 boost 的 hash_value() 方法，用来提供哈希函数 (见 examples/hash_value.h). 并且提供 std::pair 和 std::tuple 的哈希函数实现.\nnatvis visualization support in Visual Studio (hash map/set only)\n3. 快速 和 内存友好 # 作者有个介绍文章 讲解了 Parallel Hashmap 的设计和好处.\n总结起来说，Google absl 的 flat_hash_map 由于采用 2倍的内存增长 resize 策略，所以有个内存占用的尖峰 peak， 在尖峰时刻，需要3倍的临时内存占用，来把旧的 bucket 里面的数据移动到 新的2倍大的 bucket 里面。\n文章提出的想法就是，把一个大的 flat_hash_map 拆分成2的倍数个比如 16个，这样内存峰值就只有原来的 1/16 了。\n并且这种拆分还便于进行并发。\n本库提供的 hashmap 和 btree 基于 Google 在 Abseil 库中开源的实现。 hashmap 使用了 closed hashing，就是 value 直接存成一个内存数组，避免内存间接访问。通过使用并发的 SSE2 指令，这些 hashmap 可以允许一次并发查找 16 个槽位，即使当 hashmap 的填充率已经达到 87.5% ，也可以保持快速。\n重要: 本仓库借鉴了 abseil-cpp 仓库的代码, 做了修改，并且可能和原版本行为不同。本仓库是独立工作，像其他开源项目一样不提供保证。请访问 abseil-cpp 获取官方的 Abseil 库。\n4. 安装 # 无需安装，代码里面直接 include 就行。\n5. 例子 # #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;parallel_hashmap/phmap.h\u0026gt; using phmap::flat_hash_map; int main() { // Create an unordered_map of three strings (that map to strings) flat_hash_map\u0026lt;std::string, std::string\u0026gt; email = { { \u0026#34;tom\u0026#34;, \u0026#34;tom@gmail.com\u0026#34;}, { \u0026#34;jeff\u0026#34;, \u0026#34;jk@gmail.com\u0026#34;}, { \u0026#34;jim\u0026#34;, \u0026#34;jimg@microsoft.com\u0026#34;} }; // Iterate and print keys and values for (const auto\u0026amp; n : email) std::cout \u0026lt;\u0026lt; n.first \u0026lt;\u0026lt; \u0026#34;\u0026#39;s email is: \u0026#34; \u0026lt;\u0026lt; n.second \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; // Add a new entry email[\u0026#34;bill\u0026#34;] = \u0026#34;bg@whatever.com\u0026#34;; // and print it std::cout \u0026lt;\u0026lt; \u0026#34;bill\u0026#39;s email is: \u0026#34; \u0026lt;\u0026lt; email[\u0026#34;bill\u0026#34;] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 6. 提供的各种哈希表及其优缺点 # 头文件 parallel_hashmap/phmap.h 提供下列 8 种 哈希表 :\nphmap::flat_hash_set phmap::flat_hash_map phmap::node_hash_set phmap::node_hash_map phmap::parallel_flat_hash_set phmap::parallel_flat_hash_map phmap::parallel_node_hash_set phmap::parallel_node_hash_map 头文件 parallel_hashmap/btree.h 提供一下基于 btree 的有序容器 ordered containers:\nphmap::btree_set phmap::btree_map phmap::btree_multiset phmap::btree_multimap btree 容器是直接移植了 Abseil，因此应该是 Abseil 的表现一样，除了细微不同（例如支持 std::string_view 而不是 absl::string_view，并且有前向声明）\n当 btree 被修改，value 可能在内存中被移动。这意味着当 btree 容器被修改时，指向 btree 容器中的 value 的指针或者迭代器会失效。 这和 std::map / std::set 显著不同, std 容器提供指针稳定性的保证。 \u0026lsquo;flat\u0026rsquo; hash map 和 set 也会有这种失效。\n全部的类型和模板参数可以在这个头文件看到： parallel_hashmap/phmap_fwd_decl.h , 这个头文件也可以用于前向声明 Parallel Hashmap 库。\n哈希容器的关键设计点\n名字包含 flat 的哈希表内部会移动 key 和 value，所以如果在外部包含了指向 flat 哈希表中元素的指针，当哈希表被修改时，指针可能会变成野指针。而名字包含 node 的哈希表内部不会移动 key 和 value，可以用在这种场合。\nflat 系列哈希表内存占用更少，并且通常比 node 系列哈希表更快，所以尽可能使用 flat系列。当然，例外情况是 value 很大（比如大于 100字节），在内存中移动时很慢的话，此时应使用 node 系列。\nparallel 系列的哈希表。当你需要在少数个哈希表中存储非常大量的 value 时， 应该优先用 parallel 系列 哈希表。而如果你需要在大量 哈希表中，每个存储相对少量的元素时，优先用非 parallel系列的哈希表。\nparallel 系列哈希表的好处是:\na. 减少了 resize 时候内存占用的峰值。\nb. 可以打开多线程支持 (由于内部拆分成多个子哈希表，所以可以借此实现并发安全。)\nbtree 系列容器中的关键设计\nBtree 系列容器是有序容器，可以作为 std::map 和 std::set 的替代。他们在每个树节点中存储多个value，因此更缓存友好，并且内存占用显著更低。\n通常都应该优先用 Btree 容器，来代替 STL 中默认的红黑树容器。也有例外：\n要求指针稳定性，或者迭代器稳定性。 value 类型巨大，在内存中移动起来比较昂贵。 当不需要顺序的时候， 通常哈希表容器是比 btree容器 更好的选择。\n7. 对 Abseil\u0026rsquo;s 哈希表的改动 # 默认哈希，从 absl::hash 改成了 std::hash。 可以通过定义宏PHMAP_USE_ABSL_HASH改变。\nerase(iterator) 和 erase(const_iterator) 都返回指向被删除的元素的下一个元素的迭代器，和 std::unordered_map 一样. 并提供了一个非标准的 void _erase(iterator) ，用于不需要返回 value 的场合。\n没有提供 absl::string_view 这种新类型。 std::hash\u0026lt;\u0026gt; 支持的所有类型 phmap 都支持 (如果编译器提供了 std::string_view 那 phmap 当然也支持).\nAbseil 哈希表内部会随机初始化一个哈希种子，这样迭代顺序就是非确定性的，当哈希表被用于面向用户的 web 服务场合的时候，这可以用于阻止 Denial Of Service 攻击，但是这使得调试变困难了。 phmap 哈希表默认不会实现这种随机初始化，但可以通过在 include phmap.h 之前 定义宏 #define PHMAP_NON_DETERMINISTIC 1 来打开这种随机初始化 (参考 raw_hash_set_test.cc).\n和 Abseil 哈希表不一样, 我们内部做了 哈希值的混合。这在用户提供的哈希函数熵分布比较差的时候， 可以避免哈希表出现严重性能下降。这代理的性能开销是非常低的，并且在使用 不完美 的哈希函数的时候， 也能提供稳定的性能。\n8. 内存使用 # type memory usage additional peak memory usage when resizing flat tables node tables parallel flat tables parallel node tables size() 是容器中的 value 数量，通过 size() 方法返回。 load_factor() 是比例: size() / bucket_count(). 从 0.4375 (刚 resize 之后) 到 0.875 (在 resize 之前) 之间波动. bucket 数组的大小每次 resize 都 double。 value 9 来自 sizeof(void *) + 1, 由于对 bucket 数组 中的每一个 entry， node 哈希表存储一个指针加一个字节的元数据， flat 哈希表存储 value， 加每个value 一个字节的元数据，直接存储在 bucket 数组中，因此得到 sizeof(C::value_type) + 1. resize 时候的额外峰值内存占用，取决于旧的 bucket 数组的大小（是新bucket数组大小的 0.5倍），就是需要被复制到新bucket数组的 value，并在复制完成后，老的 value 会被释放。 parallel 哈希表,当用模板参数 N=4 时, 创建 16 个 submap. 当哈希值均匀分布，并在单线程模式下, 任何时间点都只有一个 submap 做 resize，因此比值约等于 0.03 即 0.5 / 16 9. 哈希表容器的 Iterator 迭代器失效 # 规则和 std::unordered_map 相同。\nOperations Invalidated All read only operations, swap, std::swap Never clear, rehash, reserve, operator= Always insert, emplace, emplace_hint, operator[] Only if rehash triggered erase Only to the element erased 10. btree 容器的 Iterator 失效 # 不同于 std::map 和 std::set, 任何修改操作都可能使得存活的迭代器失效。\nOperations Invalidated All read only operations, swap, std::swap Never clear, operator= Always insert, emplace, emplace_hint, operator[] Yes erase Yes 11. Example 2 - 为用户自定义类提供 hash 函数 # 为了使用 flat_hash_set 或者 flat_hash_map，自定义类需要提供一个 哈希函数。可以通过以下2种方法之一实现：\n通过 HashFcn 模板参数提供一个 hash 函数\n使用 boost 的话，可以在自定义类中加一个 hash_value() friend 函数.\n例子自己看官方文档吧， 就不贴了。\n12. 线程安全性 # Parallel Hashmap 容器遵循 C++ 标准库的线程安全规则。具体地:\n单个 phmap 哈希表从多个线程读，是线程安全的。例如，给定一个哈希表 A，从 thread 1 和 thread 2 并发读是安全的。\n如果单个哈希表在被一个线程写，在任何线程进行的，对该哈希表的读写操作，都是不安全的，需要被保护。例如，给定哈希表 A， 如果 thread 1 在写 A， 比如避免 thread 2 在同时读或者写 A。\n不同线程对同一种 type 的不同实例，并发进行读写，是安全的。例如，给定相同类型的哈希表 A 和 B , 在 thread 1 中写 A， 并且在 thread 2 中读 B ，是安全的。\nparallel 系列的哈希表，可以通过提供一个 synchronization type (例如 std::shared_mutex, boost::shared_mutex, std::mutex, ) 作为模板的最后一个参数， 变成读写操作内部线程安全的。因为内部是在 submap 子哈希表上进行了加锁，可以获得一种较大的并发水平。读操作可以通过 if_contains() 安全地进行, 通过持有 submap 的锁，并把 value 的引用传给回调函数。类似地， 用 modify_if 或 try_emplace_l可以进行安全的写操作。但是要注意，标准 API 返回的迭代器或者引用并没有被 mutex 保护，所以当另一个线程可能修改哈希表时，不能可靠地使用他们。\n如果使用各种 mutex 类型的例子，包括 boost::mutex, boost::shared_mutex 和 absl::Mutex 可以参考 examples/bench.cc (推荐使用 C++17 的 std::shared_mutex ，因为实测性能最好)\nparallel_系列容器的线程安全的函数有：\ninsert() emplace() lazy_emplace() erase() merge() swap() rehash() find() bucket_count() has_element() if_contains() modify_if() try_emplace_l() 多线程的例子，例如\n#include \u0026lt;assert.h\u0026gt; #include \u0026lt;stdint.h\u0026gt; #include \u0026lt;map\u0026gt; #include \u0026lt;shared_mutex\u0026gt; #include \u0026lt;thread\u0026gt; #include \u0026lt;unordered_map\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026#34;parallel-hashmap/parallel_hashmap/phmap.h\u0026#34; // 替代：std::unordered_map\u0026lt;uint64_t, uint32_t\u0026gt; typedef phmap::parallel_flat_hash_map\u0026lt;uint64_t, uint32_t, phmap::priv::hash_default_hash\u0026lt;uint64_t\u0026gt;, phmap::priv::hash_default_eq\u0026lt;uint64_t\u0026gt;, std::allocator\u0026lt;std::pair\u0026lt;const uint64_t, uint32_t\u0026gt;\u0026gt;, 4, std::shared_mutex\u0026gt; MapT; int main() { MapT index; const static uint32_t key_num = 10000; const static int thread_num = 10; for (uint64_t key = 0; key \u0026lt; key_num; ++key) { index.try_emplace_l( key, [](uint32_t\u0026amp; val) { val = 0; }, 0); } std::vector\u0026lt;std::thread\u0026gt; thread_list; for (int i = 0; i \u0026lt; thread_num; ++i) { thread_list.push_back(std::thread([\u0026amp;]() { for (uint64_t key = 0; key \u0026lt; key_num * 10; ++key) { index.modify_if(key, [](uint32_t\u0026amp; val) { ++val; }); } })); } for (auto\u0026amp; t : thread_list) { t.join(); } uint32_t hit_num = 0; for (uint64_t key = 0; key \u0026lt; key_num * 10; ++key) { index.if_contains(key, [\u0026amp;](const uint32_t\u0026amp; val) { assert(thread_num == val); ++hit_num; }); } assert(hit_num == key_num); return 0; } 我的博客即将同步至腾讯云+社区，邀请大家一同入驻：(https://cloud.tencent.com/developer/support-plan?invite_code=3qw4g1gk0xgkc)\n","date":"2020-11-10","externalUrl":null,"permalink":"/post/2020/11/10/parallel-hashmap-btree-fast-multi-thread-intro/","section":"Posts","summary":"1. 背景 # 后台开发很常见一大类需求是 线程安全 高性能 容器数据结构 开源的 https://github.com/greg7mdp/parallel-hashmap parallel-hashmap 是对 Google 的 abseil-cpp 库的改进，可供开发中直接使用。\n","title":"开源库 parallel-hashmap 介绍：高性能 线程安全 内存友好的哈希表 和 btree","type":"post"},{"content":"","date":"2020-06-14","externalUrl":null,"permalink":"/categories/algorithm/","section":"Categories","summary":"","title":"Algorithm","type":"categories"},{"content":"","date":"2020-06-14","externalUrl":null,"permalink":"/categories/search/","section":"Categories","summary":"","title":"Search","type":"categories"},{"content":" 1. IR-book 经典教材中的思路 # Faster postings list intersection via skip pointers 通过跳指针，实现更快的 posting list 求交 https://nlp.stanford.edu/IR-book/html/htmledition/faster-postings-list-intersection-via-skip-pointers-1.html\n首先有个最 trival 的算法，两个 posting list，各一个指针，谁不匹配，就把谁右移一个，\n跳表方法，思路类似 skip list，是要在建索引的时候，额外构建 skip list，这样就不是右移一个，而是按照跳表来右移。\n2. lemire 教授的 SIMDCompressionAndIntersection 库中有多种算法 # https://github.com/lemire/SIMDCompressionAndIntersection/blob/master/src/intersection.cpp\n可以看到实现了这些求交算法，并且还有个 benchmark 程序 benchintersection 可以做对比\n\u0026ldquo;simd\u0026rdquo; \u0026ldquo;galloping\u0026rdquo; \u0026ldquo;scalar\u0026rdquo; \u0026ldquo;v1\u0026rdquo; \u0026ldquo;v3\u0026rdquo; \u0026ldquo;simdgalloping\u0026rdquo; \u0026ldquo;simd_avx2\u0026rdquo; \u0026ldquo;v1_avx2\u0026rdquo; \u0026ldquo;v3_avx2\u0026rdquo; \u0026ldquo;simdgalloping_avx2\u0026rdquo; \u0026ldquo;highlyscalable_intersect_SIMD\u0026rdquo; \u0026ldquo;lemire_highlyscalable_intersect_SIMD\u0026rdquo; 其中根据输入数据的规模，会选用不同的算法，并且还有 avx2 指令集的优化算法。\n其中提到的 sse 优化的 fast-intersection-sorted-lists-sse https://highlyscalable.wordpress.com/2012/06/05/fast-intersection-sorted-lists-sse/\n3. \u0026ldquo;The magic of Algorithms! \u0026quot; 中的算法 # Chap. 6, Algorithm 6.1 Intersection based on Mutual Partitioning\nhttp://didawiki.di.unipi.it/doku.php/magistraleinformaticanetworking/ae/ae2019/start#books_notes_etc\npdf 是 https://www.dropbox.com/s/dqttqigvuf4ydkd/main.pdf?dl=0\n思路很简单，就是嵌套的 binary search，实测效果不错，而且实现简单，可以直接递归实现。\n我实测效果还不错，提了个 issue https://github.com/lemire/SIMDCompressionAndIntersection/pull/29\n4. List Intersection for Web Search: Algorithms, Cost Models, and Optimizations # http://www.vldb.org/pvldb/vol12/p1-kim.pdf 微软的文章，搞了个 cost model， 来评估各种 list 求交集算法的性能，但是不开源。\n本文提出一种基于代价的模型，来估算各种算法组合的代价，并使用代价模型来搜索代价最低的算法，得到的计划通常是 2-way 算法的一个组合， 并且比传统的 2-way 和 k-way 算法性能都好。\n并列举了常见的 List Intersection 算法： 2-way:\n2-Gallop 2-Merge 2-SIMD k-way:\nk-Gallop k-Merge 2-way vs. k-way 本文断言，对 k-list 求交，通过 k-1 次重复 2-way 求交算法，比通过单次 k-way 求交算法，更快。\n结论，通过本文 cost model optimizer 规划出来的 2-way 求交计算，通常比 k-way 求交更快。\nhttps://arxiv.org/pdf/1401.6399.pdf\nWe demonstrated that combining unpacking and differential coding resulted in faster decompression speeds, which were approximately 30 % better than the best speeds reported previously [3]. To match the performance of these fast compression schemes, we additionally vectorized and optimized the intersection of posting lists. To this end, we introduced a family of algorithms exploiting commonly available SIMD instructions (V1, V3 and SIMD GALLOPING). They are often twice as fast as the best non-SIMD algorithms. Then, we used our fast SIMD routines for decompression and posting\nEfficient set intersection for inverted indexing\nhttp://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.415.2636\u0026rep=rep1\u0026type=pdf\nIntersection in integer inverted indices https://pdfs.semanticscholar.org/db30/9935b512ef4d0c8941c96e987ad70400ebde.pdf\nlookup 算法最快，非常简单，两层存储，按位分成两层。 首层直接定位，二层直接遍历。 二层可以压缩。\n","date":"2020-06-14","externalUrl":null,"permalink":"/post/2020/06/14/posting-list-intersection-algorithms/","section":"Posts","summary":"1. IR-book 经典教材中的思路 # Faster postings list intersection via skip pointers 通过跳指针，实现更快的 posting list 求交 https://nlp.stanford.edu/IR-book/html/htmledition/faster-postings-list-intersection-via-skip-pointers-1.html\n","title":"倒排索引求交算法相关资料调研","type":"post"},{"content":"","date":"2020-06-04","externalUrl":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux","type":"categories"},{"content":"","date":"2020-06-04","externalUrl":null,"permalink":"/categories/server/","section":"Categories","summary":"","title":"Server","type":"categories"},{"content":"一，问题背景 # 多进程之间需要传输大量数据的时候，比如多进程 RPC 框架的进程之间通信，常用共享内存队列。\n但是共享内存队列难免会有 入队+出队 2次 memcpy 。\n而且要变长共享内存队列，如果支持多生产者进程+多消费者进程 ，就要处理线程安全方面的问题， 比较麻烦。\nprocess_vm_readv() , process_vm_writev() 是 Linux 3.2 新增的 syscall，用于在多个进程的地址空间之间，高效传输大块数据。\nhttps://www.man7.org/linux/man-pages/man2/process_vm_readv.2.html\nhttps://github.com/open-mpi/ompi/blob/master/opal/mca/btl/sm/btl_sm_get.c#L96\n在此， 我提个设想，可以用 process_vm_readv 实现一个多进程内存队列，相比之下，优势是：\n在处理 多线程/多进程 并发时，更简单 省掉一次 memcpy。 下面 demo 代码演示了这个思路。 为了方便，直接用 posix mq 来传输 iovec 数组，也没有考虑多路复用。 真实项目可能需要 eventfd 之内的机制来做 notify。\n实测了下，3个进程加起来有 5.5 GB/秒 的速度。\n二，代码 # #include \u0026lt;assert.h\u0026gt; #include \u0026lt;fcntl.h\u0026gt; #include \u0026lt;mqueue.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;sys/mman.h\u0026gt; #include \u0026lt;sys/stat.h\u0026gt; #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;sys/uio.h\u0026gt; #include \u0026lt;sys/wait.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; struct MemMessage { pid_t pid = 0; vector\u0026lt;iovec\u0026gt; mem_vec; public: MemMessage() {} string DebugString() const { ostringstream oss; oss \u0026lt;\u0026lt; \u0026#34;pid=\u0026#34; \u0026lt;\u0026lt; pid; for (const auto\u0026amp; mem : mem_vec) { oss \u0026lt;\u0026lt; \u0026#34; iov={\u0026#34; \u0026lt;\u0026lt; mem.iov_base \u0026lt;\u0026lt; \u0026#34;,\u0026#34; \u0026lt;\u0026lt; mem.iov_len \u0026lt;\u0026lt; \u0026#34;}\u0026#34;; } return oss.str(); } string ToString() const { pid_t from_pid = getpid(); string buff; buff.append((const char*)\u0026amp;from_pid, sizeof(from_pid)); for (const auto\u0026amp; mem : mem_vec) { buff.append((const char*)\u0026amp;mem, sizeof(mem)); } return buff; } bool FromString(const char* buff, size_t buff_len) { size_t pos = 0; if (buff_len \u0026lt; sizeof(pid)) { return false; } pid = *((pid_t*)buff); pos += sizeof(pid); for (; pos + sizeof(iovec) \u0026lt;= buff_len; pos += sizeof(iovec)) { mem_vec.resize(mem_vec.size() + 1); auto\u0026amp; iov = mem_vec.back(); memcpy(reinterpret_cast\u0026lt;char*\u0026gt;(\u0026amp;iov), buff + pos, sizeof(iov)); } return true; } void CopyToMem(string\u0026amp; buff) { size_t sum = 0; for (const auto\u0026amp; iov : mem_vec) { sum += iov.iov_len; } buff.resize(sum, \u0026#39;\\0\u0026#39;); iovec local; local.iov_base = \u0026amp;buff[0]; local.iov_len = buff.size(); auto nread = process_vm_readv(pid, \u0026amp;local, 1, \u0026amp;mem_vec[0], mem_vec.size(), 0); // assert(nread \u0026gt;= 0); if (nread \u0026lt;= 0) { cout \u0026lt;\u0026lt; getpid() \u0026lt;\u0026lt; \u0026#34; process_vm_readv from=\u0026#34; \u0026lt;\u0026lt; DebugString() \u0026lt;\u0026lt; \u0026#34; sum=\u0026#34; \u0026lt;\u0026lt; sum \u0026lt;\u0026lt; \u0026#34; len=\u0026#34; \u0026lt;\u0026lt; nread \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;pid=\u0026#34; \u0026lt;\u0026lt; pid \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; strerror(errno) \u0026lt;\u0026lt; endl; } } }; class MemQueue { mqd_t m_mq; struct mq_attr m_attr; public: int Init(const string\u0026amp; queue_name, bool read_write = false) { memset(\u0026amp;m_attr, 0, sizeof(m_attr)); m_attr.mq_flags = 0; m_attr.mq_maxmsg = 10; m_attr.mq_msgsize = 4096; const int oflag = read_write ? O_WRONLY | O_CREAT : O_RDONLY; m_mq = mq_open(queue_name.c_str(), oflag, S_IRWXU, \u0026amp;m_attr); // m_mq = mq_open(queue_name.c_str(), oflag); cout \u0026lt;\u0026lt; getpid() \u0026lt;\u0026lt; \u0026#34; mq_open=\u0026#34; \u0026lt;\u0026lt; queue_name \u0026lt;\u0026lt; \u0026#34; oflag=\u0026#34; \u0026lt;\u0026lt; oflag \u0026lt;\u0026lt; \u0026#34; ret=\u0026#34; \u0026lt;\u0026lt; m_mq \u0026lt;\u0026lt; endl; } int PushMemMsg(const MemMessage\u0026amp; msg) { string buff = msg.ToString(); return mq_send(m_mq, buff.c_str(), buff.size(), 0); } bool PopMemMsg(MemMessage\u0026amp; msg) { string buff(m_attr.mq_msgsize, \u0026#39;\\0\u0026#39;); unsigned prio = 0; const auto ret = mq_receive(m_mq, \u0026amp;buff[0], buff.size(), \u0026amp;prio); if (ret \u0026lt; 0) { return false; } buff.resize(ret); msg.FromString(buff.data(), buff.size()); return true; } }; const static string queue_name = \u0026#34;/test_queue\u0026#34;; enum STATUS { STATUS_WAITING = 0, STATUS_RUNNING = 1, STATUS_EXIT = 2 }; struct ControlCenter { int proc_status; }; ControlCenter* control = nullptr; void child_proc() { const auto ret = fork(); assert(ret \u0026gt;= 0); if (ret != 0) { return; } while (STATUS_WAITING == control-\u0026gt;proc_status) { usleep(1000); } MemQueue q; q.Init(queue_name); size_t bytes = 0; while (STATUS_RUNNING == control-\u0026gt;proc_status) { MemMessage msg; if (q.PopMemMsg(msg)) { string buff; msg.CopyToMem(buff); bytes += buff.size(); } else { usleep(1000); } } cout \u0026lt;\u0026lt; getpid() \u0026lt;\u0026lt; \u0026#34; bytes=\u0026#34; \u0026lt;\u0026lt; bytes \u0026lt;\u0026lt; \u0026#34; exit\u0026#34; \u0026lt;\u0026lt; endl; exit(0); } int main(int argc, char* argv[]) { control = (ControlCenter*)mmap(NULL, sizeof(*control), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); control-\u0026gt;proc_status = STATUS_WAITING; const static int proc_num = 3; for (int i = 0; i \u0026lt; proc_num; ++i) { child_proc(); } MemQueue q; q.Init(queue_name, true); control-\u0026gt;proc_status = STATUS_RUNNING; string str(1024 * 1024 * 10, \u0026#39;a\u0026#39;); for (int i = 0; i \u0026lt; 10000; ++i) { MemMessage msg; for (int t = 0; t \u0026lt; 3; ++t) { size_t base = rand() % str.size(); size_t size = rand() % (str.size() - base); msg.mem_vec.emplace_back(iovec{\u0026amp;str[base], size}); } const auto ret = q.PushMemMsg(msg); // cout \u0026lt;\u0026lt; getpid() \u0026lt;\u0026lt; \u0026#34; PushMemMsg=\u0026#34; \u0026lt;\u0026lt; msg.DebugString() \u0026lt;\u0026lt; \u0026#34; ret=\u0026#34; \u0026lt;\u0026lt; ret \u0026lt;\u0026lt; endl; // sleep(1); } control-\u0026gt;proc_status = STATUS_EXIT; for (int i = 0; i \u0026lt; proc_num; ++i) { int status = 0; wait(\u0026amp;status); } return 0; }","date":"2020-06-04","externalUrl":null,"permalink":"/post/queue-use-process_vm_readv/","section":"Posts","summary":"一，问题背景 # 多进程之间需要传输大量数据的时候，比如多进程 RPC 框架的进程之间通信，常用共享内存队列。\n但是共享内存队列难免会有 入队+出队 2次 memcpy 。\n而且要变长共享内存队列，如果支持多生产者进程+多消费者进程 ，就要处理线程安全方面的问题， 比较麻烦。\nprocess_vm_readv() , process_vm_writev() 是 Linux 3.2 新增的 syscall，用于在多个进程的地址空间之间，高效传输大块数据。\nhttps://www.man7.org/linux/man-pages/man2/process_vm_readv.2.html\nhttps://github.com/open-mpi/ompi/blob/master/opal/mca/btl/sm/btl_sm_get.c#L96\n在此， 我提个设想，可以用 process_vm_readv 实现一个多进程内存队列，相比之下，优势是：\n在处理 多线程/多进程 并发时，更简单 省掉一次 memcpy。 下面 demo 代码演示了这个思路。 为了方便，直接用 posix mq 来传输 iovec 数组，也没有考虑多路复用。 真实项目可能需要 eventfd 之内的机制来做 notify。\n实测了下，3个进程加起来有 5.5 GB/秒 的速度。\n","title":"用 process_vm_readv() 实现多进程内存队列","type":"post"},{"content":"","date":"2020-04-20","externalUrl":null,"permalink":"/categories/cpp/","section":"Categories","summary":"","title":"Cpp","type":"categories"},{"content":"一，介绍 # 比赛题目很简单：构造一个程序，在 stdout 上打印出自身的 MD5，程序越短越好。按最终程序文件大小字节数排名，文件越小，排名越靠前。 只能使用 ld-linux-x86-64.so, libc.so, libdl.so, libgcc_s.so, libm.so, libstdc++.so 。 禁止了 socket, shmget, fork, execvc 等 syscall 。\n汇编高手如云，本人只做到 752 字节，只拿到 27 名。 但忙活好几天，学到不少东西，也有苦劳，还是值得记录一下。\n基本是纯 C 实现，没有动用汇编。\n二，用过的一些思路和资源 # 基于这个 MD5 实现改造 https://gist.github.com/creationix/4710780\nhttps://www.nayuki.io/page/fast-md5-hash-implementation-in-x86-assembly\n参照 musl 和 rt0 ，去除对 glibc 依赖\nmusl libc https://www.musl-libc.org/ rt0 : https://github.com/lpsantil/rt0 三，没有成功的一些思路 # 思路，利用内核的 MD5 计算代码： grep -i md5 linux-5.6.2 只找到 2处，\nTCP_MD5SIG ， CONFIG_TCP_MD5SIG : \u0026ldquo;TCP: MD5 Signature Option support (RFC2385)\u0026rdquo; 一个冷门特性，看起来通过 socket 才能执行。\nAF_ALG Linux 的 crypto api，通过 socket 形式暴露 api， 但是 socket 被禁用了。 https://www.chronox.de/libkcapi.html\n构造碰撞，hardcode 直接 print 出来\nhttps://github.com/corkami/collisions\nhttps://www.rogdham.net/2017/03/12/gif-md5-hashquine.en\nhttps://github.com/Rogdham/gif-md5-hashquine\nhttp://www.win.tue.nl/hashclash/ 尝试了 fastcoll_v1 ，但是 fastcoll 构造1个字节的冲突要 2 block = 128 字节 ，则 16 字节就要 2048 字节，还不如 C 语言直接算。\n通过某些 ipc 机制，绕过评判。 试了简单的 shmget, 不成功。\n三. 代码 # 后续借鉴思路，有优化\n#define SYS_write 1 #define SYS_exit 60 inline long syscall3(long n, long a0, long a1, long a2) { long ret; __asm__ volatile(\u0026#34;syscall\u0026#34; : \u0026#34;=a\u0026#34;(ret) : \u0026#34;a\u0026#34;(n), \u0026#34;D\u0026#34;(a0), \u0026#34;S\u0026#34;(a1), \u0026#34;d\u0026#34;(a2) : \u0026#34;rcx\u0026#34;, \u0026#34;r11\u0026#34;, \u0026#34;memory\u0026#34;); return (ret); } inline long syscall1(long n, long a0) { long ret; __asm__ volatile(\u0026#34;syscall\u0026#34; : \u0026#34;=a\u0026#34;(ret) : \u0026#34;a\u0026#34;(n), \u0026#34;D\u0026#34;(a0) : \u0026#34;rcx\u0026#34;, \u0026#34;r11\u0026#34;, \u0026#34;memory\u0026#34;); return (ret); } static int write(int f, const char* d, int l) { int ret = syscall3(SYS_write, f, (long)(d), l); return (ret); } void _exit(int r) { syscall1(SYS_exit, r); } typedef unsigned uint32_t; typedef unsigned char uint8_t; typedef unsigned long size_t; #define NULL 0 // In words #define memcpy(dest, src, n) \\ for (size_t i = 0; i \u0026lt; n; ++i) { \\ ((char*)dest)[i] = ((const char*)src)[i]; \\ } // leftrotate function definition #define LEFTROTATE(x, c) (((x) \u0026lt;\u0026lt; (c)) | ((x) \u0026gt;\u0026gt; (32 - (c)))) static void md5_hash(uint8_t* initial_msg, const size_t initial_len, uint32_t hash[4]) { // Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating // r specifies the per-round shift amounts const static uint8_t r[] = { 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21, }; // Use binary integer part of the sines of integers (in radians) as constants// Initialize variables: uint32_t k[64]; for (int i = 0; i \u0026lt; 64; ++i) { double f = 1.0 + i; asm(\u0026#34;fsin;\\n\\t\u0026#34; \u0026#34;fabs;\\n\\t\u0026#34; : \u0026#34;=t\u0026#34;(f) : \u0026#34;0\u0026#34;(f)); f *= 4294967296.0; k[i] = (uint32_t)f; } // Pre-processing: adding a single 1 bit // append \u0026#34;1\u0026#34; bit to message /* Notice: the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte.[37] */ // Pre-processing: padding with zeros // append \u0026#34;0\u0026#34; bit until message length in bit ≡ 448 (mod 512) // append length mod (2 pow 64) to message const int new_len = ((((initial_len + 8) / 64) + 1) * 64) - 8; // Message (to prepare) uint8_t* msg = initial_msg msg[initial_len] = 128; // write the \u0026#34;1\u0026#34; bit uint32_t bits_len = 8 * initial_len; // note, we append the len memcpy(msg + new_len, \u0026amp;bits_len, 4); // in bits at the end of the buffer // Process the message in successive 512-bit chunks: // for each 512-bit chunk of message: for (int offset = 0; offset \u0026lt; new_len; offset += 64) { // break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15 uint32_t* w = (uint32_t*)(msg + offset); // Initialize hash value for this chunk: uint32_t a = hash[0]; uint32_t b = hash[1]; uint32_t c = hash[2]; uint32_t d = hash[3]; // Main loop: uint32_t i; for (i = 0; i \u0026lt; 64; i++) { uint32_t f, g; if (i \u0026lt; 16) { f = (b \u0026amp; c) | ((~b) \u0026amp; d); g = i; } else if (i \u0026lt; 32) { f = (d \u0026amp; b) | ((~d) \u0026amp; c); g = (5 * i + 1) % 16; } else if (i \u0026lt; 48) { f = b ^ c ^ d; g = (3 * i + 5) % 16; } else { f = c ^ (b | (~d)); g = (7 * i) % 16; } uint32_t temp = d; d = c; c = b; b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i / 4 / 4 * 4 + i % 4]); a = temp; } // Add this chunk\u0026#39;s hash to result so far: hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; } } static char xdigits(char c) { if ((c \u0026gt;= 0) \u0026amp;\u0026amp; (c \u0026lt;= 9)) { return \u0026#39;0\u0026#39; + c; } return \u0026#39;a\u0026#39; + (c - 10); } void _start(void) __attribute__((noreturn)); void _start(void) { uint32_t hash[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; char* addr = (char*)0x400000; const size_t len = 688; md5_hash(addr, len, hash); unsigned char* md = (unsigned char*)\u0026amp;hash[0]; char buff[32]; for (int i = 0; i \u0026lt; 32; ++i) { unsigned x = md[(i \u0026gt;\u0026gt; 1)]; x \u0026gt;\u0026gt;= (i \u0026amp; 1 ? 0 : 4); buff[i] = xdigits(x \u0026amp; 15); } write(1, \u0026amp;buff[0], sizeof(buff)); _exit(0); } CFLAGS=\u0026#34;-static -Os -Wl,--omagic -D__RT0_WITH_FASTER_SYSCALL__ -ffunction-sections -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib -nostdinc -Wl,--build-id=none -fomit-frame-pointer -fno-stack-protector -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-unroll-loops -fmerge-all-constants -fno-ident -mfpmath=sse -mfancy-math-387 -finline-functions-called-once -I ./ \u0026#34; gcc $CFLAGS print_my_md5.c -S -fverbose-asm gcc $CFLAGS print_my_md5.s -o print_my_md5 strip -R .comment print_my_md5 strip -R .bss print_my_md5 ls -l ./print_my_md5 ./ELFkickers/sstrip/sstrip print_my_md5 ./print_my_md5 echo md5sum ./print_my_md5 ls -l ./print_my_md5","date":"2020-04-20","externalUrl":null,"permalink":"/post/shortest-print-self-md5-contest/","section":"Posts","summary":"一，介绍 # 比赛题目很简单：构造一个程序，在 stdout 上打印出自身的 MD5，程序越短越好。按最终程序文件大小字节数排名，文件越小，排名越靠前。 只能使用 ld-linux-x86-64.so, libc.so, libdl.so, libgcc_s.so, libm.so, libstdc++.so 。 禁止了 socket, shmget, fork, execvc 等 syscall 。\n汇编高手如云，本人只做到 752 字节，只拿到 27 名。 但忙活好几天，学到不少东西，也有苦劳，还是值得记录一下。\n基本是纯 C 实现，没有动用汇编。\n","title":"构造最短程序打印自身的 MD5","type":"post"},{"content":"一，背景介绍 # ElasticSearch 是由 Lucene 包装上分布式复制一致性算法等附加功能，构成的开源搜索引擎系统。\n近两年在业界热度大增，主要有 3 种应用场景：\n全文搜索引擎 NOSQL 数据库 日志分析数据库 ELK 很多垂直领域搜索需求，都可以基于 ElasticSearch 来设计架构。\nElasticSearch 能大幅度提升相关业务的迭代开发速度，实现类似 sql 数据库增删改查一样的快速开发。 并在相对高 qps 的在线业务中，保证毫秒级的延迟，提供极高的可用性和稳定性。\n经过持续的研读官方文档，调研业界经验，并在实践中应用反思后，总结出一套架构方案。供参考，欢迎意见建议。\n二，架构方案 # 一个 ElasticSearch 集群 cluster ，配套：\n转发代理 proxy 队列 1. 转发代理 proxy # proxy 的功能是：\n协议转换 ，做 rpc 协议如 protobuf 和 ElasticSearch REST json 之间的协议转换， 对文档更新等请求，定义了 rpc 协议，便于队列做各种处理。 引入 RPC 框架成熟的负载均衡，容灾，故障屏蔽等功能，到对 ES 的 RPC 中， 比如如果单机 ES 进程挂了，通过返回码让调用方自动换机器重试。 统一监控告警系统，监控各种请求失败，延迟分布等，并监控 ElasticSearch java 进程状态，集群状态 转发文档更新请求给本机的队列 。用队列做削峰填谷，自动合并批量，做限流。 提供双写能力，便于索引升级切换 proxy 到本机 ES 做了 http 连接池，避免频繁的 HTTP tcp 建连接。 2. 队列 # 队列 实现了 出队限流，请求合并，削峰填谷 3个功能。\n在实际业务中，常常会定期做文档全量更新，会出现短时间内写请求高峰，\n如果直接写 ES，请求高峰时，经常出现 ES write 线程池占满，导致部分写请求失败。\n另外部分业务每次请求只更新1个文档，导致 ES cpu 高，影响 ES 的写性能，不符合官方推荐做法。\n为此，引入队列:\n配置限制了出队的 QPS ，确保集中高峰被抹平，以匀速稳定地写入 ES，彻底消除了更新失败。\n并用配置自动把多个请求合并成批量 （比如 5000个文档一个批量），优化了 ES 的写入性能。\n请求高峰中超出配置 QPS 的请求，队列自动暂存在文件中，随后处理，保证了 ES 服务平稳。\n3. 其他工具 # 另外，繁荣的 ES 开源生态中，周边工具非常丰富便捷， 我们常用的两种周边工具：kinana 和 bin/elasticsearch-sql-cli，极其方便快捷，大幅度提升了开发效率。\n三，搜索应用开发优化指南 # 垂直搜索系统的在线检索部分，一般流程如下\nES 用来实现 召回和粗排环节 ，和部分自动补全环节。\n基于 ES 开发的优点：\nES/Lucene 的 Query DSL 极其强大全面灵活，业务逻辑代码大幅度简化，开发简单便捷，业务迭代开发速度大大提高。 有商业公司维护的高质量官方文档， 网上也有海量资料，新人几天就可以上手，快速形成生产力，提升团队效率。 成熟稳定，就目前经验看没有遇到过 bug 业务如果扩展，后续伸缩性，扩展性，分 shard ，多副本等，都有比较成熟方案。 1. query DSL 语法 # 基于 ES 的开发，首先需要学习常见的几种 query，\nES 的 query 简单分成 4 类:\nterm query，对单个词的 query，包括 term/terms/range/exists/missing/ids/regexp 等 full text query ，全文检索query，对多个词（即句子）的query，包括 match/multi_match/common 等 compound query 复合 query，包括 bool/dis_max/function_score 等 match_all ，简单匹配所有文档 建议先学习 term/match/range/bool ，就可以实现大部分业务逻辑。\n网上资料较多，就不转述了。\n可以先看看这些中文资料，在 test 环境的 kibana 做做实验，快速上手：\nhttps://www.elastic.co/guide/cn/elasticsearch/guide/current/search-in-depth.html\nhttps://my.oschina.net/yumg/blog/637409\nhttps://www.cnblogs.com/yjf512/p/4897294.html\n当然最好的还是官方英文文档：\nhttps://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html\n2. 分词 # 中文搜索的一个核心议题，就是分词。\nElasticSearch 常用的中文分词是 ik analyzer。ik 是开箱即用，便于小型业务快速开发的。\n但是作为对分词可定制性要求较高的业务，我们实际测试，发现 ik analyzer :\n不支持本地自定义词典文件的热加载； 无法针对不同 index 配置不同自定义词典； 另外对一些分词的 bad case ，比如没有正确切分的词，没法简单 fix。 因此推荐不用 ik ，而是在更新文档和搜索的时候，在外部做分词，然后用空格拼起来，传给 ES 做索引/搜索。这种方案中，在 ES mapping 中配置成 whitespace 分词器。\n外部分词可以用 cppjieba 等，索引分词还可以合并多种分词算法结果提高召回率。\n对 cppjieba ，我之前做过内存优化，将内存优化到了 1/100。\n另外，索引之前，也有必要做 UTF8 的 normalize，全角转半角，英文大小写统一，和英文的词干提取， mapping 中常用\n\u0026#34;cjk_width\u0026#34;, \u0026#34;lowercase\u0026#34;, \u0026#34;porter_stem\u0026#34; 这些filter\n具体可以参考已有业务代码。\n3.关系型搜索 # 实际开发遇到典型的 one-many 关系型数据上的 query，\n比如在某业务中，就遇到这种逻辑，经过调研发现常见有 4 种方案：\n分开2 个 index : one + many ，分开2次串行 search， 问题: 需要2次延迟大\n反范式，完全展开，one 的数据追加到每一个 many 文档中， 问题：数据量变大，更新 one 需要用 _update_by_query 如果 one 数据更新频繁，可能导致大量写操作\nnested ，比如 one 嵌套 many 子文档 问题是：nested 嵌套文档更新需要更新整个 root 文档，即要把整个 one 文档 和含有的 many 文档 select 出来，修改，再写回。 对热门 one 文档， 更新会操作大量数据，并发写还可能 data race。\njoin, has_parent, has_child 把 one 和 many 的所有字段合并到一个 index 中， one 和 many 分别独立更新。\n经过实际数据测试 join field 方案， 发现当 one:many = 1:1000万 时， 延迟在 5ms 可以接受，因此目前采用了这种方案。\n当然，官方文档指出 join 性能是会慢的，后续也有待实践检验。\n4. 粗排相关性打分 # Lucene 从 2016年的 6.0 版本开始，默认的相关性算法切换成了 bm25 ， bm25 是一种调整过的 tf idf 算法。\n这里可以做一简单举例介绍，更深入的介绍可以参见下面文章，以及官方文档：\nhttps://farer.org/2018/09/10/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch/\nhttps://www.cnblogs.com/richaaaard/p/5254988.html\nES 的 explain 对 bm25 算分的过程有详尽的解释，推荐自行实验。\nhttps://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html\n4.1. BM25 例解 # 比如某业务的真实数据中，我们在所有文档的 title 这个 field 搜索 “牛奶 ” 这个词，\nexplain 可以看到，这个 bm25 分数的是这样得来的：\nsum( weight(title:牛奶 in 77341) [PerFieldSimilarity] ) ， weight(title:牛奶 in 77341) [PerFieldSimilarity] = idf * tfNorm 首先，如果某 field 被多个 term 命中，分别算每个 term 的分数 (PerFieldSimilarity)，然后求和，本例子只有1个 term “牛奶”。\n每个 term 的分数 PerFieldSimilarity\nPerFieldSimilarity = idf * tfNorm\n而 idf 表征词的重要程度，与具体文档无关。\nidf = log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))\n其中 docFreq 就是本shard 中，有多少个文档含有 “牛奶”， docCount 就是本shard 一共有多少个文档。\ntfNorm = (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) termFreq=1.0 k1=1.2 b=0.75 avgFieldLength=14.456173 fieldLength=2 //比如如果 title 是 \u0026#34;牛奶 醪糟\u0026#34; ，那就是 2个 term freq 即该 field 中，“牛奶”这个词出现了几次 k1 和 b 都是固定常数。 fieldLength 是当前文档的当前field ，一共有多少个 term。 avgFieldLength 即本 shard 中的所有文档的本 field 的 fieldLength 的平均值\n4.2. BM25 shard 调整 # 实际业务发现，当 index 内文档太少(比如 10w 量级就算少) 时 ， 有的词在多个 shard 内，词频分布会出现严重不均匀，可能会导致 bm25 分数产生较大偏差，\n实践中的解决办法：\nSearch 的 url_parameters 参数填 “\u0026amp;search_type=dfs_query_then_fetch” 减少 shard 数 4.3. BM25 similarity 参数调整 # 如上计算过程可见，bm25 中的 b 参数，是用来给短文档做加权的，即 b 越大，越倾向于给短文档更高的 score， 实际中，和算法同学一起分析后，发现针对我们的某业务，不应该对短文本有太高偏向，所以我们把 b 调整成了 0.3 ， 实测发现解决了一批 bad case，用户体验有明显改善。\n四，性能优化 # 计算机程序的性能取决于数据结构和算法， ES/Lucene 中主要有几种数据结构:\nFST Posting List ，著名的倒排索引，PForDelta 压缩，支持 SkipList 方式跳跃 BKD Tree，用来实现 int 和 geo 查询 DocValues , 以 DocID 为 Key 的列存储 https://zhuanlan.zhihu.com/p/47951652\nhttps://www.elastic.co/blog/elasticsearch-query-execution-order\n更深入的理解，我目前也在探索中。\n在垂直搜索引擎业务中，用户对延迟非常敏感，一般业界经验认为，良好的用户体验应该是在 ** 200毫秒 ** 内返回搜索结果， 这就意味着 ES 延迟最好控制在 100毫秒之内。\n经过我们实际业务发现，决定 ES 延迟的因素主要有：\n内存是否足够， page cache 是否 cache 了检索过程用到的文件数据 具体 query 的优化，类比 mysql query 优化 1. page cache 内存优化 # page cache 是决定 ES 延迟的首要因素，用作在线检索服务的 ES ， 实际中在线检索的代码路径不能有硬盘 io 访问 (实践证明， SSD也不行)\n当 ES 用作在线垂直搜索引擎时，\n《查询亿级数据毫秒级返回！牛逼哄哄的ElasticSearch是如何做到的？》 https://zhuanlan.zhihu.com/p/68706615\n《ElasticSearch在数十亿级别数据下，如何提高查询效率？》 https://zhuanlan.zhihu.com/p/60458049\n实践中，某 index 发现延迟非常高，达到了 1-2秒，用户体验很差。 调查发现，iostat 看下 io util 很高，经常到 80% 90%，单机索引数据文件是 page cache 可用内存的 4倍， 于是降低了副本数，单机数据量减少到 page cache 可用内存2倍后， 硬盘 io 降到了 0 ，延迟一下降低到了 150ms 。\n2. int 字段查询优化 # 业务中常会有一些 int 型的字段，存一些枚举性质的值。 在 10亿以上文档的情况下，实际发现有的会出性能问题。\n比如 比如前述业务有1个 int 类型的 filter 字段，实际只有 {0,1} 2种取值，\n借助 ES 的 profile ，我们发现搜索 query 93% 的耗时在 filter 字段的 PointInSetQuery 中，\n随后发现，针对该业务，只需要返回 filter 为 0 的文档，于是我们在更新文档时，发现 filter 非0 的文档，直接把所有字段都清空，并随后在 query 中去掉了 filter 字段的过滤。\n之后发现耗时从 150ms 降到了 20ms。\n3. 副本数 replicas num # 确定副本数的思路：\n副本数越小越好。越小，单机数据越少，文件被 cache 的比例越高，性能越好。 副本太少会影响可用性。因此必须大于最大能容忍故障单机个数 max_failures 。 综合起来就是：\nmax(max_failures, ceil(num_nodes / num_primaries) - 1).\nnum_primaries 是 primary shards 的数量，就是一个 index 有多少个 shards，一般都 \u0026gt; num_nodes\nreplicas_might_help_with_throughput_but_not_always\n对数据量特别少的 index，可以每台机都存一个副本 \u0026ldquo;auto_expand_replicas\u0026rdquo;: \u0026ldquo;0-all\u0026rdquo;,\n4. 多 SSD # 在 elasticsearc.yml 的 path.data 配置多个路径，ES 会自动把 shard 均分到多个路径上，如果有多个硬盘，可以充分利用多设备的 io 带宽，当然对在线业务意义不大。\n5. 内存配置 # 最开始我们使用 16G 内存机型， 后来发现出现大量 Elasticsearch Data too large Error 错误，随后发现，解决办法就是换到 64G 内存机型，\n改 jvm.options 加大 jvm 的 heap 解决，从 10G 加大到 30G 解决 -Xms30g -Xmx30g\n需要注意的是，不建议大于 32G，避免 jvm 的指针压缩优化失效。 可以看 ES 的启动 log 确定\n[2019-05-22T12:29:16,961][INFO ][o.e.e.NodeEnvironment ] [node_xxx] heap size [29.7gb], compressed ordinary object pointers [true] https://www.elastic.co/cn/blog/a-heap-of-trouble\n6. refresh_interval # 如网上众多文章所说， refresh_interval 一般都设成了 30秒。\n一些参考资料：\n滴滴 Elasticsearch 多集群架构实践 https://cloud.tencent.com/developer/article/1405404 京东到家订单中心 Elasticsearch 演进历程 https://cloud.tencent.com/developer/article/1377194 从平台到中台：Elaticsearch在蚂蚁金服的实践经验 https://new.qq.com/omn/20190117/20190117A09ADY.html 美团点评旅游搜索召回策略的演进 https://tech.meituan.com/2017/06/16/travel-search-strategy.html eBay 的 Elasticsearch 性能调优实践 https://www.infoq.cn/article/elasticsearch-performance-tuning-practice-at-ebay 有赞搜索系统的架构演进 https://tech.youzan.com/search-tech-1/ 有赞搜索引擎实践(工程篇) https://tech.youzan.com/search-engine1/ 全面理解搜索Query：当你在搜索引擎中敲下回车后，发生了什么？ https://zhuanlan.zhihu.com/p/112719984 elasticsearch初学终极教程: 从零到一 https://kalasearch.cn/blog/elasticsearch-tutorial/ ","date":"2020-03-29","externalUrl":null,"permalink":"/post/elasticsearch-dev-arch/","section":"Posts","summary":"一，背景介绍 # ElasticSearch 是由 Lucene 包装上分布式复制一致性算法等附加功能，构成的开源搜索引擎系统。\n近两年在业界热度大增，主要有 3 种应用场景：\n全文搜索引擎 NOSQL 数据库 日志分析数据库 ELK 很多垂直领域搜索需求，都可以基于 ElasticSearch 来设计架构。\nElasticSearch 能大幅度提升相关业务的迭代开发速度，实现类似 sql 数据库增删改查一样的快速开发。 并在相对高 qps 的在线业务中，保证毫秒级的延迟，提供极高的可用性和稳定性。\n经过持续的研读官方文档，调研业界经验，并在实践中应用反思后，总结出一套架构方案。供参考，欢迎意见建议。\n","title":"基于 ElasticSearch 开发垂直搜索系统","type":"post"},{"content":"1. 问题 # 一个 C++ 程序，如果 throw 了 exception ，但是又没有 catch，那么一般会产生 coredump， 问题是，在 gcc 4.x 版本产生的 coredump 文件中，没有 throw 时候的堆栈信息，导致不知道是哪里 throw 的，没法查问题。\n原因是 gcc 4.x 的 /libstdc++-v3/src/c++11/thread.cc:92 里面有个 catch(\u0026hellip;)，所以 stack unwind 了，就没了 throw 时候的 stack 。\nvoid * execute_native_thread_routine(){ try { ... }catch(...){ std::terminate(); } } https://abcdabcd987.com/libstdc++-bug/\n一个解决办法是可以升级 GCC 7 ，或者可以用更简单的办法：\n2.代码 hook __cxa_throw ，让 coredump 带上堆栈 # 一个解决办法是通过改代码，hook __cxa_throw() 让每次生成的 coredump 都带上堆栈：\nhttps://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/cxxabi.h#L616\n// Throw the exception. void __cxa_throw(void*, std::type_info*, void (_GLIBCXX_CDTOR_CALLABI *) (void *)) __attribute__((__noreturn__)); __cxa_throw() 是 libstdc++/libc++ 用于实现 throw 的函数。\nhttps://libcxxabi.llvm.org/spec.html\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;stdexcept\u0026gt; #include \u0026lt;thread\u0026gt; extern \u0026#34;C\u0026#34; { //加这3行代码，通过 hook __cxa_throw，直接 abort，可以避免 stack unwind。 void __cxa_throw(void* ex, void* info, void (*dest)(void*)) { ::abort(); } } void func(){ throw std::runtime_error(\u0026#34;die\u0026#34;); } int main() { std::thread t(func); t.join(); return 0; } 效果如下\n(gdb) bt #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 #1 0x00007ffff7264801 in __GI_abort () at abort.c:79 #2 0x0000555555554f53 in __cxa_throw (ex=0x7ffff0000ba0, info=0x555555756ce0 \u0026lt;typeinfo for std::runtime_error@@GLIBCXX_3.4\u0026gt;, dest=0x7ffff7af4140 \u0026lt;std::runtime_error::~runtime_error()\u0026gt;) at test.cpp:6 #3 0x0000555555554f8f in func () at test.cpp:10 #4 0x0000555555555371 in std::__invoke_impl\u0026lt;void, void (*)()\u0026gt; (__f=@0x555555769e78: 0x555555554f53 \u0026lt;func()\u0026gt;) at /usr/include/c++/7/bits/invoke.h:60 #5 0x000055555555517e in std::__invoke\u0026lt;void (*)()\u0026gt; (__fn=@0x555555769e78: 0x555555554f53 \u0026lt;func()\u0026gt;) at /usr/include/c++/7/bits/invoke.h:95 #6 0x000055555555584c in std:: thread::_Invoker\u0026lt;std::tuple\u0026lt;void (*)()\u0026gt; \u0026gt;::_M_invoke\u0026lt;0ul\u0026gt; (this=0x555555769e78) at /usr/include/c++/7/thread:234 #7 0x0000555555555808 in std:: thread::_Invoker\u0026lt;std::tuple\u0026lt;void (*)()\u0026gt; \u0026gt;::operator() (this=0x555555769e78) at /usr/include/c++/7/thread:243 #8 0x00005555555557d8 in std:: thread::_State_impl\u0026lt;std:: thread::_Invoker\u0026lt;std::tuple\u0026lt;void (*)()\u0026gt; \u0026gt; \u0026gt;::_M_run (this=0x555555769e70) at /usr/include/c++/7/thread:186 #9 0x00007ffff7b096ef in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #10 0x00007ffff761c6db in start_thread (arg=0x7ffff6e85700) at pthread_create.c:463 #11 0x00007ffff734588f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 3. gdb catch throw # 如果是对已有的二进制，或者已经在运行的进程：\ngdb 里面输入 catch throw 然后运行，gdb 就会在任何 throw 的时候暂停，即可看到 throw 时候的栈。\n","date":"2020-03-29","externalUrl":null,"permalink":"/post/cpp-throw-coredump-with-backtrace/","section":"Posts","summary":"1. 问题 # 一个 C++ 程序，如果 throw 了 exception ，但是又没有 catch，那么一般会产生 coredump， 问题是，在 gcc 4.x 版本产生的 coredump 文件中，没有 throw 时候的堆栈信息，导致不知道是哪里 throw 的，没法查问题。\n原因是 gcc 4.x 的 /libstdc++-v3/src/c++11/thread.cc:92 里面有个 catch(…)，所以 stack unwind 了，就没了 throw 时候的 stack 。\nvoid * execute_native_thread_routine(){ try { ... }catch(...){ std::terminate(); } } https://abcdabcd987.com/libstdc++-bug/\n一个解决办法是可以升级 GCC 7 ，或者可以用更简单的办法：\n","title":"怎样让 C++ 中 throw exception 产生的 coredump 带上栈","type":"post"},{"content":"可以对抗输入数据模式的排序算法。\nquick sort 在输入数据接近有序的情况下，效率不高。\nPattern-defeating quicksort (pdqsort) is a novel sorting algorithm that combines the fast average case of randomized quicksort with the fast worst case of heapsort, while achieving linear time on inputs with certain patterns. pdqsort is an extension and improvement of David Mussers introsort. All code is available for free under the zlib license.\npdqsort 结合了随机化的 quicksort 的快速的平均性能，和 heapsort 在最坏情况下的性能，并且在某些特定的输入模式下，可以实现线性的性能。 pdqsort 是 David Mussers 的 introsort 的一个扩展。\nhttps://github.com/orlp/pdqsort\nSingle Thread Algorithms This table provides a brief description of the single thread algorithms of the library.\nClickHouse/contrib/pdqsort/\nhttps://www.boost.org/doc/libs/1_67_0/libs/sort/doc/html/sort/single_thread.html\nAlgorithm Stable Additional memory Best, average, and worst case method spreadsort no key_length N, Nsqrt(LogN), min(NlogN, Nkey_length) Hybrid radix sort pdqsort no Log N N, NLogN, NLogN Comparison operator spinsort yes N / 2 N, NLogN, NLogN Comparison operator flat_stable_sort yes size of the data / 256 + 8K N, NLogN, NLogN Comparison operator spreadsort is an extremely fast hybrid radix sort algorithm, designed and developed by Steven Ross. pdqsort is a improvement of the quick sort algorithm, designed and developed by Orson Peters. spinsort is a stable sort that is fast with random or nearly sorted data, designed and developed by Francisco Tapia. flat_stable_sort is a stable sort that uses very little additional memory (around 1% of the size of the data), providing 80% - 90% of the speed of spinsort, designed and developed by Francisco Tapia. ","date":"2020-03-03","externalUrl":null,"permalink":"/post/2020/03/03/pdqsort-qsort/","section":"Posts","summary":"可以对抗输入数据模式的排序算法。\nquick sort 在输入数据接近有序的情况下，效率不高。\nPattern-defeating quicksort (pdqsort) is a novel sorting algorithm that combines the fast average case of randomized quicksort with the fast worst case of heapsort, while achieving linear time on inputs with certain patterns. pdqsort is an extension and improvement of David Mussers introsort. All code is available for free under the zlib license.\n","title":"pdqsort-qsort","type":"post"},{"content":"一，问题背景 # 很多时候，我们需要确保进程只有一个实例运行。\n有几种方法：\nhttp://stackoverflow.com/questions/2964391/preventing-multiple-process-instances-on-linux\nhttp://stackoverflow.com/questions/5339200/how-to-create-a-single-instance-application-in-c-or-c\nhttps://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication/src\n比较常规的做法，是对一个文件加文件锁 flock，比如对 pid 文件 flock( LOCK_EX|LOCK_NB )\n但是这种方法有些弊端：\n如果文件被 mv 或者 rm，是会被绕过的。 如果磁盘故障比如磁盘满，目录没有写权限，会失败。 二，abstract namespace unix socket # http://linux.die.net/man/7/unix\nunix socket 有3种：\n基于文件的 socketpair 创建的，匿名的 abstract namespace 的，Linux特有 Linux 下， AF_UNIX socket 支持一种特殊的 abstract namespace unix socket 。\n相比 普通的基于文件系统的 unix socket，abstract namespace unix socket ：\n没有磁盘文件 进程挂了以后自动删除，无残留文件 无需担心与 文件系统上的文件冲突，不需要关心文件系统上的绝对路径是否存在的问题 在 lsof 的结果里面看起来，就是有一些 类似 @test_abstract_ns 这样的 文件项\n代码中使用也很简单， abstract namespace unix socket 在 bind 之前，sockaddr_un.sun_path[0] 设成 0x0 即可。\n三，代码 # 于是我用 abstract unix socket 实现了一个 SysSem 工具类（ 一个 system 范围的 semaphore ）， 用来：\n让一个程序只启动一个实例。 让 x 进程等待 y 进程执行完 yyy 操作后，才能执行 xxx 操作。 特点：\n多进程/线程 并发安全。 当持有的进程被 kill ，OS自动释放，无残留。 没有磁盘文件，没有文件意外被删的各种情况。 不占用 tcp/udp 端口。 简单，不到 60行代码。 #include \u0026lt;sys/socket.h\u0026gt; #include \u0026lt;sys/un.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;string\u0026gt; // // a semaphore with system scope. // // 1. no race conditions between Post() / GetValue() , better than flock(). // 2. when a running process be killed, automatically release all. // 3. no file on disk, no accidently delete . // 4. no tcp/udp socket, no confliction, no port consumption. // class SysSem { public: SysSem() : _fd(-1) { memset(\u0026amp;_addr, 0, sizeof(_addr)); } ~SysSem(); void Init(std::string id); bool Post(); bool GetValue(); const char* GetID() const; private: struct sockaddr_un _addr; int _fd; }; void SysSem::Init(std::string id) { _addr.sun_family = AF_UNIX; const size_t len = std::min(id.size(), sizeof(_addr.sun_path) - 2); // 2 = start null and end null byte // abstract namespace socket address , _addr.sun_path[0] is a null byte (\u0026#39;\\0\u0026#39;) memcpy(_addr.sun_path + 1, id.c_str(), len); // memcpy(_addr.sun_path + 0, id.c_str(), len); } const char* SysSem::GetID() const { return \u0026amp;_addr.sun_path[1]; } SysSem::~SysSem() { if (_fd \u0026gt;= 0) { ::close(_fd); _fd = -1; } } bool SysSem::Post() { _fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (_fd \u0026lt; 0) { return false; } if ((0 != ::bind(_fd, (struct sockaddr*)\u0026amp;_addr, sizeof(_addr))) || (0 != listen(_fd, 65536))) { return false; } return true; } bool SysSem::GetValue() { const int clientFD = ::socket(AF_UNIX, SOCK_STREAM, 0); if (clientFD \u0026lt; 0) { return false; } const bool ret = (0 == ::connect(clientFD, (struct sockaddr*)\u0026amp;_addr, sizeof(_addr))); ::close(clientFD); return ret; } #include \u0026lt;assert.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; int main(int argc, char** argv) { if (argc != 3) { fprintf(stderr, \u0026#34;usage: %s abstract-path post/get\\n\u0026#34;, argv[0]); exit(1); } SysSem inst; inst.Init(argv[1]); if (0 == strcasecmp(argv[2], \u0026#34;post\u0026#34;)) { assert(inst.Post()); SysSem check; check.Init(argv[1]); assert(check.GetValue()); printf(\u0026#34;ok, i am the only one under %s. running ...\\n\u0026#34;, inst.GetID()); pause(); } else if (0 == strcasecmp(argv[2], \u0026#34;get\u0026#34;)) { assert(inst.GetValue()); printf(\u0026#34;a process is running under %s. \\n\u0026#34;, inst.GetID()); } else { printf(\u0026#34;unknown cmd \\n\u0026#34;); } return 0; }","date":"2020-03-03","externalUrl":null,"permalink":"/post/2020/03/03/abstract-unix-socket-single-instance/","section":"Posts","summary":"一，问题背景 # 很多时候，我们需要确保进程只有一个实例运行。\n有几种方法：\nhttp://stackoverflow.com/questions/2964391/preventing-multiple-process-instances-on-linux\nhttp://stackoverflow.com/questions/5339200/how-to-create-a-single-instance-application-in-c-or-c\nhttps://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication/src\n比较常规的做法，是对一个文件加文件锁 flock，比如对 pid 文件 flock( LOCK_EX|LOCK_NB )\n但是这种方法有些弊端：\n如果文件被 mv 或者 rm，是会被绕过的。 如果磁盘故障比如磁盘满，目录没有写权限，会失败。 二，abstract namespace unix socket # http://linux.die.net/man/7/unix\nunix socket 有3种：\n基于文件的 socketpair 创建的，匿名的 abstract namespace 的，Linux特有 Linux 下， AF_UNIX socket 支持一种特殊的 abstract namespace unix socket 。\n相比 普通的基于文件系统的 unix socket，abstract namespace unix socket ：\n没有磁盘文件 进程挂了以后自动删除，无残留文件 无需担心与 文件系统上的文件冲突，不需要关心文件系统上的绝对路径是否存在的问题 ","title":"用 abstract unix socket 实现进程单实例运行","type":"post"},{"content":"","date":"2019-11-29","externalUrl":null,"permalink":"/categories/python/","section":"Categories","summary":"","title":"Python","type":"categories"},{"content":"一，问题背景 # 随着深度学习的广泛应用，在搜索引擎/推荐系统/机器视觉等业务系统中，越来越多的深度学习模型部署到线上服务。\n机器学习模型在离线训练时，一般要将输入的数据做特征工程预处理，再输入模型在 TensorFlow PyTorch 等框架上做训练。\n1.常见的特征工程逻辑 # 常见的特征工程逻辑有：\n分箱/分桶 离散化 log/exp 对数/幂等 math numpy 常见数学运算 特征缩放/归一化/截断 交叉特征生成 分词匹配程度计算 字符串分隔匹配判断tong 缺省值填充等 数据平滑 onehot 编码，hash 编码等 这些特征工程代码，当然一般使用深度学习最主要的语言 python 实现。\n二，业务痛点 # 离线训练完成，模型上线部署后，同样要用 C++ 重新实现 这些 python 的特征工程逻辑代码。\n我们发现，“用 C++ 重新实现” 这个步骤，给实际业务带来了大量的问题：\n繁琐，费时费力，极容易出现 python 和 C++ 代码不一致 不一致会直接影响模型在线上的效果，导致大盘业务指标不如预期，产生各种 bad case 不一致难以发现，无法测试，无法监控，经常要靠用户投诉反馈，甚至大盘数据异常才能发现 1. 业界方案 # 针对这些问题，我调研了这些业界方案：\n《推荐系统中模型训练及使用流程的标准化》 https://www.infoq.cn/article/2E6LCqb1GeqFRAjkkjX3\n《自主研发、不断总结经验，美团搜索推荐机器学习平台》 https://cloud.tencent.com/developer/article/1357309\n《京东电商推荐系统实践》 https://www.infoq.cn/article/1OkKmb_gEYNR3YqC9RcW\n“模型线上线下一致性问题对于模型效果非常重要，我们使用特征日志来实时记录特征，保证特征的一致性。这样离线处理的时候会把实时的用户反馈，和特征日志做一个结合生成训练样本，然后更新到模型训练平台上，平台更新之后在推送到线上，这样整个排序形成了一个闭环。”\n总结起来，有几种思路：\n在线特征存储起来给离线用 在线 C++ 代码编译成 so 导出给离线用 根据一份配置生成离线和在线代码 提取公共代码，加强代码复用，等软件工程手段，减少不一致 2. 自动翻译方案 # (1) .已有方案的缺点 # 但这些思路都有各种缺点：\n所有在线请求的所有特征，这个存储量数据量很大 算法改代码需要等待后台开发，降低了算法同学的工作效率 特征处理代码的复杂度转移到配置文件中，不一定能充分表达，而且配置格式增加学习成本 就这边真实离线特征处理代码来看，大部分代码都无法抽取出公共代码做复用。 (2). 翻译器 # 回到问题出发点考虑，显而易见，这个问题归根结底就是需要一个 “ python 到 c++ 的翻译器 ” 。\n那其实 “翻译器 Transpiler ” ，和编译器解释器类似，也是个古老的热门话题了，比如 WebAssembly, CoffeeScript ，Babel , Google Closure Compiler，f2c\n于是一番搜索，发现 python 到 C++ 的翻译器也不少，其中 Pythran 是新兴比较热门的开源项目。\n于是一番尝试后，借助 pythran，我们实现了：\n一条命令 全自动把 Python 翻译成等价 C++ 严格等价保证改写，彻底消除不一致 完全去掉重新实现 这块工作量，后台开发成本降到 0 ，彻底解放生产力 算法同学继续使用纯 python，开发效率无影响，** 无学习成本 ** 并能推广到其他需要 python 改写成后台 C++ 代码 的业务场景，解放生产力 三，pythran 的使用流程 # (1). 安装 # 一条命令安装：\npip3 install pythran (2). 写 Python 代码 # 下面这个 python demo，是 pythran 官方 demo\nimport math import numpy as np def zero(n, m): return [[0]*n for col in range(m)] #pythran export matrix_multiply(float list list, float list list) def matrix_multiply(m0, m1): new_matrix = zero(len(m0),len(m1[0])) for i in range(len(m0)): for j in range(len(m1[0])): for k in range(len(m1)): new_matrix[i][j] += m0[i][k]*m1[k][j] return new_matrix #pythran export arc_distance(float[], float[], float[], float[]) def arc_distance(theta_1, phi_1, theta_2, phi_2): \u0026#34;\u0026#34;\u0026#34; Calculates the pairwise arc distance between all points in vector a and b. \u0026#34;\u0026#34;\u0026#34; temp = (np.sin((theta_2-theta_1)/2)**2 + np.cos(theta_1)*np.cos(theta_2) * np.sin((phi_2-phi_1)/2)**2) distance_matrix = 2 * np.arctan2(np.sqrt(temp), np.sqrt(1-temp)) return distance_matrix #pythran export dprod(int list, int list) def dprod(l0,l1): \u0026#34;\u0026#34;\u0026#34;WoW, generator expression, zip and sum.\u0026#34;\u0026#34;\u0026#34; return sum(x * y for x, y in zip(l0, l1)) #pythran export get_age(int ) def get_age(age): if age \u0026lt;= 20: age_x = \u0026#39;0_20\u0026#39; elif age \u0026lt;= 25: age_x = \u0026#39;21_25\u0026#39; elif age \u0026lt;= 30: age_x = \u0026#39;26_30\u0026#39; elif age \u0026lt;= 35: age_x = \u0026#39;31_35\u0026#39; elif age \u0026lt;= 40: age_x = \u0026#39;36_40\u0026#39; elif age \u0026lt;= 45: age_x = \u0026#39;41_45\u0026#39; elif age \u0026lt;= 50: age_x = \u0026#39;46_50\u0026#39; else: age_x = \u0026#39;50+\u0026#39; return age_x (3). Python 转成 C++ # 一条命令完成翻译\npythran -e demo.py -o demo.hpp (4). 写 C++ 代码调用 # pythran/pythonic/ 目录下是 python 标准库的 C++ 等价实现，翻译出来的C++ 代码需要 include 这些头文件\n写个 C++ 代码调用\n#include \u0026#34;demo.hpp\u0026#34; #include \u0026#34;pythonic/numpy/random/rand.hpp\u0026#34; #include \u0026lt;iostream\u0026gt; using std::cout; using std::endl; int main() { pythonic::types::list\u0026lt;pythonic::types::list\u0026lt;double\u0026gt;\u0026gt; m0 = { {2.0, 3.0}, {4.0, 5.0} }, m1 = { {1.0, 2.0}, {3.0, 4.0} }; cout \u0026lt;\u0026lt; m0 \u0026lt;\u0026lt; \u0026#34;*\u0026#34; \u0026lt;\u0026lt; m1 \u0026lt;\u0026lt; \u0026#34;\\n=\\n\u0026#34; \u0026lt;\u0026lt; __pythran_demo::matrix_multiply()(m0, m1) \u0026lt;\u0026lt; endl \u0026lt;\u0026lt; endl; auto theta_1 = pythonic::numpy::random::rand(3), phi_1 = pythonic::numpy::random::rand(3), theta_2 = pythonic::numpy::random::rand(3), phi_2 = pythonic::numpy::random::rand(3); cout \u0026lt;\u0026lt; \u0026#34;arc_distance \u0026#34; \u0026lt;\u0026lt; theta_1 \u0026lt;\u0026lt; \u0026#34;,\u0026#34; \u0026lt;\u0026lt; phi_1 \u0026lt;\u0026lt; \u0026#34;,\u0026#34; \u0026lt;\u0026lt; theta_2 \u0026lt;\u0026lt; \u0026#34;,\u0026#34; \u0026lt;\u0026lt; phi_2 \u0026lt;\u0026lt; \u0026#34;\\n=\\n\u0026#34; \u0026lt;\u0026lt; __pythran_demo::arc_distance()(theta_1, phi_1, theta_2, phi_2) \u0026lt;\u0026lt; endl \u0026lt;\u0026lt; endl; pythonic::types::list\u0026lt;int\u0026gt; l0 = {2, 3}, l1 = {4, 5}; cout \u0026lt;\u0026lt; \u0026#34;dprod \u0026#34; \u0026lt;\u0026lt; l0 \u0026lt;\u0026lt; \u0026#34;,\u0026#34; \u0026lt;\u0026lt; l1 \u0026lt;\u0026lt; \u0026#34;\\n=\\n\u0026#34; \u0026lt;\u0026lt; __pythran_demo::dprod()(l0, l1) \u0026lt;\u0026lt; endl \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;get_age 30 = \u0026#34; \u0026lt;\u0026lt; __pythran_demo::get_age()(30) \u0026lt;\u0026lt; endl \u0026lt;\u0026lt; endl; return 0; } (5). 编译运行 # g++ -g -std=c++11 main.cpp -fopenmp -march=native -DUSE_XSIMD -I /usr/local/lib/python3.6/site-packages/pythran/ -o pythran_demo ./pythran_demo 四，pythran 的功能与特性 # (1). 介绍 # 按官方定义，Pythran 是一个 AOT (Ahead-Of-Time - 预先编译) 编译器。 给科学计算的 python 加注解后，pythran 可以把 python 代码变成接口相同的原生 python 模块，大幅度提升性能。\n并且 pythran 也可以利用 OpenMP 多核和 SIMD 指令集。\n支持 python 3 和 Python 2.7 。\npythran 的 manual 挺详细： https://pythran.readthedocs.io/en/latest/MANUAL.html\n(2). 功能 # pythran 并不支持完整的python， 只支持 python 语言特性的一个子集:\npolymorphic functions 多态函数(翻译成 C++ 的泛型模板函数) lambda list comprehension 列表推导式 map, reduce 等函数 dictionary, set, list 等数据结构 exceptions 异常 file handling 文件处理 部分 numpy 不支持的功能：\nclasses 类 polymorphic variables 可变类型变量 (3). 支持的数据类型和函数 # pythran export 可以导出函数和全局变量。 支持导出的数据类型，BNF 定义是：\nargument_type = basic_type | (argument_type+) # this is a tuple | argument_type list # this is a list | argument_type set # this is a set | argument_type []+ # this is a ndarray, C-style | argument_type [::]+ # this is a strided ndarray | argument_type [:,...,:]+ # this is a ndarray, Cython style | argument_type [:,...,3]+ # this is a ndarray, some dimension fixed | argument_type:argument_type dict # this is a dictionary basic_type = bool | byte | int | float | str | None | slice | uint8 | uint16 | uint32 | uint64 | uintp | int8 | int16 | int32 | int64 | intp | float32 | float64 | float128 | complex64 | complex128 | complex256 可以看到基础类型相当全面，支持各种 整数，浮点数，字符串，复数\n复合类型支持 tuple, list, set, dict, numpy.ndarray 等，\n对应 C++ 代码的类型实现在 pythran/pythonic/include/types/ 下面，可以看到比如 dict 实际就是封装了一下 std::unordered_map https://pythran.readthedocs.io/en/latest/SUPPORT.html 可以看到支持的 python 基础库，其中常用于机器学习的 numpy 支持算比较完善。\n五，pythran 的基本原理 # 和常见的编译器/解释器类似， pythran 的架构是分成 3 层：\npython 代码解析成抽象语法树 AST 。用 python 标准库自带的的 ast 模块实现 代码优化。 在 AST 上做优化，有多种 transformation pass，比如 deadcode_elimination 死代码消除，loop_full_unrolling 循环展开 等。还有 Function/Module/Node 级别的 Analysis，用来遍历 AST 供 transformation 利用。 后端，实现代码生成。目前有2个后端，Cxx / Python， Cxx 后端可以把 AST 转成 C++ 代码（ Python 后端用来调试）。 目前看起来 ，pythran 还欠缺的：\n字符串处理能力欠缺，缺少 str.encode()/str.decode() 对 utf8 的支持 缺少正则表达式 regex 支持 缺少 json 支持 看文档要自己加也不麻烦，看业务需要可以加。\n六，相关文章 # 《京东电商推荐系统实践》 https://www.infoq.cn/article/1OkKmb_gEYNR3YqC9RcW\n《自主研发、不断总结经验，美团搜索推荐机器学习平台》 https://cloud.tencent.com/developer/article/1357309\n《推荐系统中模型训练及使用流程的标准化》 https://www.infoq.cn/article/2E6LCqb1GeqFRAjkkjX3\nnumba http://numba.pydata.org\n","date":"2019-11-29","externalUrl":null,"permalink":"/post/2019/11/29/pthran-py-to-cpp-translater-in-feature-engineering/","section":"Posts","summary":"一，问题背景 # 随着深度学习的广泛应用，在搜索引擎/推荐系统/机器视觉等业务系统中，越来越多的深度学习模型部署到线上服务。\n机器学习模型在离线训练时，一般要将输入的数据做特征工程预处理，再输入模型在 TensorFlow PyTorch 等框架上做训练。\n1.常见的特征工程逻辑 # 常见的特征工程逻辑有：\n分箱/分桶 离散化 log/exp 对数/幂等 math numpy 常见数学运算 特征缩放/归一化/截断 交叉特征生成 分词匹配程度计算 字符串分隔匹配判断tong 缺省值填充等 数据平滑 onehot 编码，hash 编码等 这些特征工程代码，当然一般使用深度学习最主要的语言 python 实现。\n二，业务痛点 # 离线训练完成，模型上线部署后，同样要用 C++ 重新实现 这些 python 的特征工程逻辑代码。\n我们发现，“用 C++ 重新实现” 这个步骤，给实际业务带来了大量的问题：\n繁琐，费时费力，极容易出现 python 和 C++ 代码不一致 不一致会直接影响模型在线上的效果，导致大盘业务指标不如预期，产生各种 bad case 不一致难以发现，无法测试，无法监控，经常要靠用户投诉反馈，甚至大盘数据异常才能发现 ","title":"Python 自动翻译成 C++ ，彻底保证离线在线特征一致","type":"post"},{"content":"一，问题背景 # 中文分词应用比较广泛的开源算法，是 jieba 结巴分词，结巴分词较高性能的实现是 C++ 版本的 CppJieba : https://github.com/yanyiwu/cppjieba\n在实际使用 CppJieba 的过程中，我们发现 CppJieba 的内存占用比较高。\n比如对一个 76W 词 大小 11MB 的词典 ，加载 2份 （比如为了支持平滑改动用户词典）就需要耗费 505MB内存。\n这对一些多进程的后台服务，浪费大量内存，难以接受，因此这里希望削减内存耗费。\n经过初步调查，确定改进方法，然后动手改造，最终把 505MB 缩减到了 4.7MB ，实现了 99% 内存降低。\n此处也有 issue 讨论 https://github.com/yanyiwu/cppjieba/issues/3\n代码在 https://github.com/byronhe/cppjieba 。\n二，实现过程 # 二.1 查内存分布 # 第一步先用 jemalloc 的 memory profiler 工具查看内存耗费在哪里，\n改一下 CppJieba 的 test/demo.cpp， 链接 jemalloc，编译成二进制 jieba_test 然后设置环境变量 export MALLOC_CONF=\u0026quot;prof:true,prof_prefix:mem_prof/mem_profile_je.out,lg_prof_interval:20,lg_prof_sample:20\u0026quot; 然后 mkdir mem_prof， 并运行测试程序 jeprof \u0026ndash;pdf ./jieba_test mem_prof/mem_profile_je.out.xxx.heap \u0026gt; mem_profile.pdf 打开 mem_profile.pdf ，就可以看到内存分布了\n二.2 优化方案 # 显而易见，内存主要耗费在:\nTrie.hpp 中的 Trie 树构建 KeywordExtractor.hpp 加载 idf 词典文件。 因此方案:\n1. Double Array Trie 代替 cppjieba::Trie # 引入 Double Array Trie (简称 DAT ,https://github.com/s-yata/darts-clone) , 代替 Trie.hpp 中的简单内存 Trie，并把 darts 生成的 DAT 保存到文件中，在启动时，如果已经有和词典对应的 DAT ，直接 mmap() attach 上去，即可启动。\n经过实测发现，75万词词典，dart-clone 生成的 DAT 文件，大小只有 24MB，而且可以 mmap 挂载，多进程共享。\n2. KeywordExtractor # KeywordExtractor 是个不常用功能，直接改成支持传入空的 idfPath 和 stopWordPath, 此时不加载数据即可。\n二.3 其他问题 # 1. 支持热更新，保证词典和DAT一致 # 这里一个问题是，词典可能热更新，那怎么知道 DAT 文件和当前词典的内容对应？\n我的做法是，对 默认词典文件+自定义词典文件，用文件内容算 MD5，写入 DAT 文件头部，这样打开 DAT 文件发现 MD5 不一致，就知道 DAT文件过时了，即可重建 DAT 。\n实测发现算 MD5 还是很快的，启动时间都在 1秒 左右。\n2. 代码清理 # 另外，清理了一下代码，删掉了 Unicode.hpp 中的无用代码。 清理了 FullSegment.hpp HMMSegment.hpp MixSegment.hpp MPSegment.hpp QuerySegment.hpp 等中的重复代码。\n3. 不兼容改动 # 由于 Double Array Trie 无法支持动态插入词，删除 InsertUserWord() 方法 FullSegment.hpp 中 maxId 的计算有 bug，做了 fix。 为了节省内存，改成允许传入空的 idfPath 和 stopWordPath 。 会生成 Double Array Trie 临时文件，临时文件名默认会自动生成，也可以传 dict_cache_path 指定 改成自定义词典中重复的词，保留权重最大的。 删除 Unicode.hpp 中的无用代码 整体改造后，代码量比原来减少 100 多行。\n上线后效果显著。\n当内存降低到 2-3MB 的水平后，这意味着 75W 词这种规模的大词典，可以用在手机环境。\n比如可以在 ios 或者 Android 上做 中文/英文的切词， 这意味着可能在客户端实现体验相当良好的搜索引擎。\nios 上也有可用于中文的分词器 CFStringTokenizer ，但貌似不开源。\n","date":"2019-11-25","externalUrl":null,"permalink":"/post/2019/11/25/cppjieba-darts-DAT-memory_optimize/","section":"Posts","summary":"一，问题背景 # 中文分词应用比较广泛的开源算法，是 jieba 结巴分词，结巴分词较高性能的实现是 C++ 版本的 CppJieba : https://github.com/yanyiwu/cppjieba\n在实际使用 CppJieba 的过程中，我们发现 CppJieba 的内存占用比较高。\n比如对一个 76W 词 大小 11MB 的词典 ，加载 2份 （比如为了支持平滑改动用户词典）就需要耗费 505MB内存。\n这对一些多进程的后台服务，浪费大量内存，难以接受，因此这里希望削减内存耗费。\n经过初步调查，确定改进方法，然后动手改造，最终把 505MB 缩减到了 4.7MB ，实现了 99% 内存降低。\n此处也有 issue 讨论 https://github.com/yanyiwu/cppjieba/issues/3\n代码在 https://github.com/byronhe/cppjieba 。\n","title":"用 DAT 重实现 CppJieba 中文分词算法，降低 99% 内存消耗","type":"post"},{"content":"分词是中文搜索的重要环节，目前分词算法已经比较成熟，分词错误的主要是由于未登录词。\n因此发现业务领域语料库中的新词，减少未登录词，对改善搜索引擎的用户体验有重要意义。\n新词发现的一种常用算法，是 matrix67 大神 2012 年提出的 《互联网时代的社会语言学：基于SNS的文本数据挖掘》 https://www.matrix67.com/blog/archives/5044\n其主要思路，是统计语料中出现的所有 ngram 子字符串的凝固度，自由度，信息熵。\n算法中需要统计所有 ngram 子字符串的 左熵右熵，实现该算法时，一般以子字符串为 key，用哈希表来存。\n但随着语料库变大时，内存消耗变大，\n比如之前的 python 版本实现，对 200MB 的语料，就需要约 30G 内存来存哈希表，\n导致单机内存不足无法运行，而且对这样规模的语料库，算法需要跑一两天才能出结果。\n这里我应用一些工程实现方面的技巧， 把用哈希表统计左熵右熵的计算，拆分成多个子哈希表，分批计算，并利用多核并行，大幅度优化了算法的性能。\n最终实现了 GB 大小语料上的新词发现，并把运行时间缩减到了 30 分钟左右 。\nhttps://github.com/hankcs/HanLP/wiki/%E6%96%B0%E8%AF%8D%E8%AF%86%E5%88%AB\n","date":"2019-09-18","externalUrl":null,"permalink":"/post/2019/09/18/newwords_discovery/","section":"Posts","summary":"分词是中文搜索的重要环节，目前分词算法已经比较成熟，分词错误的主要是由于未登录词。\n因此发现业务领域语料库中的新词，减少未登录词，对改善搜索引擎的用户体验有重要意义。\n新词发现的一种常用算法，是 matrix67 大神 2012 年提出的 《互联网时代的社会语言学：基于SNS的文本数据挖掘》 https://www.matrix67.com/blog/archives/5044\n其主要思路，是统计语料中出现的所有 ngram 子字符串的凝固度，自由度，信息熵。\n算法中需要统计所有 ngram 子字符串的 左熵右熵，实现该算法时，一般以子字符串为 key，用哈希表来存。\n但随着语料库变大时，内存消耗变大，\n比如之前的 python 版本实现，对 200MB 的语料，就需要约 30G 内存来存哈希表，\n导致单机内存不足无法运行，而且对这样规模的语料库，算法需要跑一两天才能出结果。\n这里我应用一些工程实现方面的技巧， 把用哈希表统计左熵右熵的计算，拆分成多个子哈希表，分批计算，并利用多核并行，大幅度优化了算法的性能。\n","title":"GB 规模语料上的高性能新词发现算法","type":"post"},{"content":"http://giflib.sourceforge.net/whatsinagif/index.html\nhttp://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html https://gif.ski/ gifski 可以从视频生成高质量的 gif\n浓缩的才是精华：浅析 GIF 格式图片的存储和压缩 https://cloud.tencent.com/developer/article/1004763\n用av1视频替代 gif https://www.singhkays.com/blog/its-time-replace-gifs-with-av1-video\n\u0026ldquo;Replace animated GIFs with video for faster page loads\u0026rdquo; : https://web.dev/replace-gifs-with-videos/\nhttps://web.dev/efficient-animated-content/\nhttp://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html\n每帧一个调色板，就可以消除噪点。 https://medium.com/@Peter_UXer/small-sized-and-beautiful-gifs-with-ffmpeg-25c5082ed733\nif 本身的算法比较古老压缩率低下，和 h264/h265/vp9 之类的编码算法是没法比的，比如用 h264/VP9 mp4/webm 视频代替 gif，大概可以压缩到原大小的 1/5 ~ 1/10\n\u0026ldquo;Replace animated GIFs with video for faster page loads\u0026rdquo; : https://web.dev/replace-gifs-with-videos/\n《页面加载性能之video替换GIF》: https://www.bilibili.com/read/cv6905954\n\u0026ldquo;用av1视频替代 gif \u0026quot; https://www.singhkays.com/blog/its-time-replace-gifs-with-av1-video\ngifsicle https://www.lcdf.org/gifsicle/\n查看 gif 文件结构:\ngifsicle -I input.gif gcc -pthread -DHAVE_CONFIG_H quantize.c merge.c optimize.c gifunopt.c gifwrite.c gifsicle.c clp.c support.c giffunc.c xform.c gifread.c fmalloc.c -I ../include/ -I ../ -lm # gcc -pthread -DHAVE_CONFIG_H quantize.c merge.c optimize.c gifunopt.c gifwrite.c gifsicle.c clp.c support.c giffunc.c xform.c gifread.c fmalloc.c -I ../include/ -I ../ -lm function scale(){ input=$1 time ffmpeg -hide_banner -y -i $input -b 568k -r 20 -vf \u0026#39;fps=20,scale=w=162:h=-1:flags=lanczos,palettegen \u0026#39; ${input}.palettegen.png time ffmpeg -hide_banner -y -i $input -i ${input}.palettegen.png -r 15 -lavfi \u0026#39;fps=15,scale=w=162:h=-1:flags=lanczos[x];[x][1:v]paletteuse\u0026#39; $2 rm -f ${input}.palettegen.png } time ffmpeg -threads 1 -hide_banner -y -i 987.gif -vf \u0026#39;scale=w=162:h=-1:flags=lanczos \u0026#39; 987.gif.162.lanczos.gif scale 987.gif 987.gif.162.palettegen.gif gifsicle 987.gif --resize 162x_ -o 987.gif.162.gifsicle.gif #ffmpeg -y -i 987.gif -filter_complex \u0026#34;[0:v] fps=15,scale=w=720:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1\u0026#34; avengers3.gif #ffmpeg -y -i 987.gif -filter_complex \u0026#34;[0:v]scale=w=162:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1\u0026#34; 987.gif.162.ffmpeg_single.gif ffmpeg -y -i 987.gif -filter_complex \u0026#34;[0:v]scale=w=162:h=-1:flags=lanczos,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1\u0026#34; 987.gif.162.ffmpeg_single.gif time convert 987.gif -resize 162x1620 987.162.gif ","date":"2019-08-26","externalUrl":null,"permalink":"/post/2019/08/26/gif-resize-algorithm-resources/","section":"Posts","summary":"http://giflib.sourceforge.net/whatsinagif/index.html\nhttp://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html https://gif.ski/ gifski 可以从视频生成高质量的 gif\n浓缩的才是精华：浅析 GIF 格式图片的存储和压缩 https://cloud.tencent.com/developer/article/1004763\n用av1视频替代 gif https://www.singhkays.com/blog/its-time-replace-gifs-with-av1-video\n“Replace animated GIFs with video for faster page loads” : https://web.dev/replace-gifs-with-videos/\nhttps://web.dev/efficient-animated-content/\nhttp://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html\n每帧一个调色板，就可以消除噪点。 https://medium.com/@Peter_UXer/small-sized-and-beautiful-gifs-with-ffmpeg-25c5082ed733\n","title":"gif 缩放算法及相关资料","type":"post"},{"content":"","date":"2019-08-26","externalUrl":null,"permalink":"/categories/image/","section":"Categories","summary":"","title":"Image","type":"categories"},{"content":"","date":"2019-08-26","externalUrl":null,"permalink":"/categories/video/","section":"Categories","summary":"","title":"Video","type":"categories"},{"content":"借助 ext4 文件系统的 打洞 功能，可以实现一个消息队列 https://gist.github.com/CAFxX/571a1558db9a7b393579\nfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, length); Punching holes in files\nhttps://lwn.net/Articles/415889/\n怎样对大文件的中间部分进行增删？\n首先，如果复制/写文件的后半段的话，肯定是可以实现的，但是这样不“高效”。 所以这里的需求是 “怎样在大文件中间 insert 或者 delete 一段数据而不用读写文件的后半部分”。\n由于 unix 的历史，并没有直接提供这种 syscall 。\n如果用 3.15 之后的 linux kernel ，在 ext4 (only for extent-based files) 和 XFS 文件系统上， http://man7.org/linux/man-pages/man1/fallocate.1.html http://man7.org/linux/man-pages/man2/fallocate.2.html\nfallocate 这个 syscall 新增了 FALLOC_FL_COLLAPSE_RANGE (delete 一段) 和 FALLOC_FL_INSERT_RANGE （insert 一段）这两个选项，可以实现在文件系统 block size 边界对齐的 insert/delete 。\n","date":"2019-08-10","externalUrl":null,"permalink":"/post/2019/08/10/message-queue-using-hole-punching/","section":"Posts","summary":"借助 ext4 文件系统的 打洞 功能，可以实现一个消息队列 https://gist.github.com/CAFxX/571a1558db9a7b393579\nfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, length); Punching holes in files\nhttps://lwn.net/Articles/415889/\n怎样对大文件的中间部分进行增删？\n首先，如果复制/写文件的后半段的话，肯定是可以实现的，但是这样不“高效”。 所以这里的需求是 “怎样在大文件中间 insert 或者 delete 一段数据而不用读写文件的后半部分”。\n","title":"借助 ext4 文件系统的 打洞 功能实现消息队列","type":"post"},{"content":"","date":"2017-03-08","externalUrl":null,"permalink":"/categories/http2/","section":"Categories","summary":"","title":"Http2","type":"categories"},{"content":"","date":"2017-03-08","externalUrl":null,"permalink":"/categories/network/","section":"Categories","summary":"","title":"Network","type":"categories"},{"content":"Proxygen 的整体架构 一个 HTTPSession 对应一个 tcp 连接。\nHTTPSession 中包含HTTPCodec ，HTTPCodec用来在 HTTPMessage(Request/Response) 和 字节流之间做转换（就是解析/序列化）。\n一个 HTTPTransaction 对应一个 HTTP2 的Stream ，也就是一次 Req/Resp Handler 是业务逻辑处理的基类。\n其中 HTTP2 部分由几个类构成：\ncodec/HTTPCodec.h codec/HTTP2Framer.cpp codec/HTTP2Codec.cpp , HTTPParallelCodec.cpp codec/Compress/HeaderCodec.cpp,HPACKCodec.cpp session/HTTPSession.cpp session/HTTPTransacion.cpp session/HTTP2PriorityQueue.cpp 1. HTTPCodec.h # 定义了 HTTPCodec 这个基类，定义了用来在 内部的 HTTP Request/Response 和 字节流之间做转换的公共接口，是HTTP1.1/SPDY/HTTP2 的公共基类。具体的HTTP2/HTTP1.1等各种协议的编解码，实现在 HTTP1.1/SPDY/HTTP2 等子类中。\n1) HTTPCodec # HTTPCodec 是 HTTP1.1/ HTTP2 接口的超集，即HTTP1.1也被当成 HTTP2 实现\ni. 首先所有的 子类都要支持 StreamID，HTTP1.1 由于有KeepAlive，看成有多个Stream。 ii. onIngress()，传入输入数据，驱动解析。 iii. generateHeader()/ generateBody() /generateChunkHeader() /generateGoaway()/ generatePingRequest()/ generatePriority() 等，是 http1.1/http2 的超集。HTTP1xCodec.cpp 是没有实现HTTP2 的这些Frame 的generateXXX函数。HTTP1.1\n2) PriorityQueue，优先级队列。 # Session/HTTP2PriorityQueue.cpp 实现了这个接口类\n3) HTTPCodec::Callback 类 # 解析的过程中，需要通知 HTTPCodec 的使用者做一些操作，因此有个 接口类 HTTPCodec::Callback，HTTPCodec 的使用者实现 Callback 的子类，传给 HTTPCodec 的子类，HTTPCodec 的子类在解析过程中解析出来的各种消息都回调这个callback。 Callback 的主要方法有:\nonMessageBegin() onHeadersComplete() // HeaderList 接收完 onBody() // Body 开始 onMessageComplete() //Message 整个接收完 onWindowUpdate() // 收到 WindowUpdate Frame onPriority()， //收到 Priority Frame等 顾名思义，处理HTTPMessage 开始事件，收到各种 HTTP2 Frame 等等。\n2. HTTP2Framer.cpp # 10种Frame 的 解析/序列化 工具函数，parseXXXFrame, writeXXXFrame，最底层。供HTTP2Codec 使用。\n3. HTTP2Codec # Framing Layer，即处理 HTTPMesage , HTTP2 Frame 和 输入/输出字节流之间的 解析/序列化。实现了HTTPParallelCodec(HTTP2和SPDY的公共基类，HTTPCodec 的子类)，HeaderCodec两个接口类\n1) 输入字节流的解析的时候，做: # i. HEADERS Frame， # 做各种 Header 的变换处理。\nii. PRIORITY # 4. compress/HeaderCodec.cpp # HeaderCodec 是 Header List 的编解码器。HeaderCodec解码的结果通过 HeaderCodec::StreamingCallback接口类通知给使用者（即HTTP2Codec）。HeaderCodec::StreamingCallback主要有个 onHeader(name, value) 接口(HTTP2Codec实现了)。\n5. compress/HPACKCodec.cpp # HPACK保存 int \u0026ndash;\u0026gt; string 的映射，通过发送 int， 代替 string 来压缩。\nHPACK编码，主要需要实现动态表，静态表，Huffman encoding，Header序列化/解析。\nHPACKCodec 主要有 encode(vector)/decode(Buffer) 两个接口。重要成员变量：\nHPACKEncoder encoder_; HPACKDecoder decoder_; 实际的 动态表/静态表 逻辑，是一个HeaderTable，主要数据结构就是vector + ，\nHPACKDecoder 里面的动态表对应的是 HPACKContext，先 peek 一个字节，判断是否用了HPACKDecodeBuffer来解码\nInteger比特编解码，literal编解码，都实现在 HPACKDecodeBuffer / HPACKEncodeBuffer里面。\nHuffman encoding 实际使用的是 256位的trie，不是2位的，为了优化性能。\n在 HeaderTable.cpp 中，可以看到，实际的 vector 是当成一个 环形队列来用的， 实际是 把 RFC 中规定的 [ 1 \u0026mdash; n ] 的区间，逆向映射到 这个环形队列中，即 index 1 是，对应到 环形队列的 尾，index n 是对应到环形队列的头。 这样，按照 RFC，增加 Header 进来的时候，应该是加在 1 前面，并把所有的已有 Header 往后挪1个位置，实现上就可以不用做 “挪动”，而且已有的 name\u0026ndash;\u0026gt;index 的映射也不用改。这是一种实现上的小技巧。\n并用 一个\nunordered_map\u0026lt;Header.name , list\u0026lt;index\u0026gt; \u0026gt; 的形式存储 Header.name 到一组 vector 中的 index 的映射，这是由于 有 相同 name ，对应多个 value 的情况，而且要支持 “查找 name+value 匹配” 和 ”查找 name 匹配“ 两种查找。\n6. session/HTTP2PriorityQueue.cpp # 每一个 Stream都 依赖一个 Stream 或者 Stream 0。 一个 Stream A 依赖 Stream B ，那么 A 就是 B 的子节点。形成依赖树。 Stream 0 是树的根节点。\nHTTP2PriorityQueue实现了PriorityQueue接口类，实现了 addPriorityNode(StreamID id, StreamID parent) 接口\n主要的逻辑实现在 Node这个内部类里面。\nPriorityQueue 的数据成员，主要是一个 unorder_map\u0026lt;streamID, Node\u0026gt;\nNode内部的数据成员：\nNode { HTTP2PriorityQueue\u0026amp; queue_; //属于哪个 PriorityQueue Node *parent_{nullptr}; //树中的父节点 HTTPCodec::StreamID id_{0}; // uint16_t weight_{16}; //权重 HTTPTransaction *txn_{nullptr}; bool isPermanent_{false}; bool enqueued_{false}; //enqueued 表示在 有输出数据的队列中 uint64_t totalEnqueuedWeight_{0}; uint64_t totalChildWeight_{0}; std::list\u0026lt;std::unique_ptr\u0026lt;Node\u0026gt;\u0026gt; children_; // 子节点 std::list\u0026lt;std::unique_ptr\u0026lt;Node\u0026gt;\u0026gt;::iterator self_; folly::IntrusiveListHook enqueuedHook_; //一个侵入式链表 folly::IntrusiveList\u0026lt;Node, \u0026amp;Node::enqueuedHook_\u0026gt; enqueuedChildren_; // } 有输出数据等待发送 (pending Egress) 的 HTTPTransaction ，会加入\nsignalPendingEgress() ， 通知 HTTP2PriorityQueue 某一个 Stream 产生了输出数据。 内部实现是把 这 Stream加入\nclearPendingEgress()， 通知一个 Stream 发完了 输出数据。\naddOrUpdatePriorityNode()，\nnextEgress() ， 获取有输出数据等待发送的 HTTPTransaction 的列表，列表的每个元素是 pair\u0026lt; HTTPTransaction * ，double weight \u0026gt; ，按照 weight 从大到小排列，列表中的所有weight 加起来等于1。\n7. session/HTTPTransaction.cpp # HTTPTransaction 表示一次 Req／Resp，HTTPTransaction 需要和 HTTPSession 交互写入 HTTPMessage 所以提出了Transport 这个概念，这样依赖关系就是单向的，没有 HTTPTransaction – HTTPSession 之间不会产生双向依赖。实际处理业务的代码，定义成一个个 Handler，需要与Handler 交互，\n1) HTTPTransaction::Transport # 是 HTTPTransaction 的下层为 HTTPTransaction 提供的输出HTTPMessage 服务。(下层具体指的是 HTTPSession)。\nTransport 的方法有：sendHeaders()/sendBody()/pauseIngress()/resumeIngress()/ sendPriority() / 等\n2) HTTPTransactionHandler # 是一个个业务Handler 的基类，Handler的方法有:onHeadersComplete()/onBody()/onChunk/onTrailers/onEgressPaused()/ onEgressResumed等，就是解析出 HTTP Message的各个部分，或者流控发现无法再写了时候的通知。\ni. HTTPTransaction::Handler 有好几种,业务Handler 比如直接生成 ErrorPage 的 HTTPDirectResponseHandler就是生成 404 页面的 Handler，在其内部，实现 onHeadersComplete 方法，在这个方法内生成 回包数据，然后调用 HTTPTransaction 的 sendHeaders/sendBody 等方法发回给客户端。\n3) HTTPTransactionIngressSM # 由于 HTTPTransaction 要调用 Handler 的好几种方法，内部要记录当前已经处理到哪一步了，不能允许在任意时机，任意来的数据都触发回调，所以搞了两个状态机，HTTPTransactionIngressSM/ HTTPTransactionEgressSM 来明确地规定 回调可以触发的顺序。\n4) HTTPTransaction 的重要成员变量： # streamId, handler_ , transport_ ,egressState_ , ingressState_, recvWindow_ , sendWindow_, HTTP2PrioprityQueue::Handle queueHande_,\n8. session/HTTPSession.cpp # HTTPSession 针对 HTTP2/HTTP1.1/SPDY的超集的，HTTPSession是一个 HTTPCodec::Callback（接受解析出来的各种 Frame），是 HTTPTransaction::Callback（供HTTPTransaction发送输出 HTTP Message）,是 FlowControlFilter::Callback（接受输出状态打开/关闭的通知）。\n1) 重要成员变量： # { folly::IOBufQueue readBuf_, folly::IOBufQueue writeBuf_, map\u0026lt;StreamID, HTTPTransaction\u0026gt;transactions_, HTTP2PriorityQueue queue, FlowControlFilter connFlowControl_, }\t2) getNextToSend() # 是做发送的，可以看到，proxygen 是按相对权重来分派带宽的。txnEgressQueue_.nextEgress() 取出当前可发送的 HTTPTransaction + 相对权重的列表，然后把可发送字节数 按照相对权重分给各个HTTPTransaction。\n9. 细节 # 1) IOBufQueue # 多处用到的基础类：IOBufQueue，Zero-Copy，引用计数。一个 Bufffer Chain，设计类似 linux kernel 的sk_buff, BSD 的 mbuf 。\nhttps://github.com/facebook/folly/blob/master/folly/io/IOBuf.h\n* An IOBuf is a pointer to a buffer of data. * * IOBuf objects are intended to be used primarily for networking code, and are * modelled somewhat after FreeBSD\u0026#39;s mbuf data structure, and Linux\u0026#39;s sk_buff structure. * IOBuf objects facilitate zero-copy network programming, by allowing multiple * IOBuf objects to point to the same underlying buffer of data, using a * reference count to track when the buffer is no longer needed and can be freed. 2) HTTPHeaders # HTTPHeaders 有个性能优化，用了 静态完美hash函数，把常用的 83个 Header 各自唯一地hash 成1 字节，HeaderList 的查找使用 汇编实现的memchr。\nhttps://github.com/facebook/proxygen/blob/master/proxygen/lib/http/HTTPHeaders.h\n* Headers are stored as Name/Value pairs, in the order they are received on * the wire. We hash the names of all common HTTP headers (using a static * perfect hash function generated using gperf from HTTPCommonHeaders.gperf) * into 1-byte hashes (we call them \u0026#34;codes\u0026#34;) and only store these. We search * them using memchr, which has an x86_64 assembly implementation with * complexity O(n/16) ;) ","date":"2017-03-08","externalUrl":null,"permalink":"/post/Proxygen-http2-code-analysis/","section":"Posts","summary":"Proxygen 的整体架构 一个 HTTPSession 对应一个 tcp 连接。\nHTTPSession 中包含HTTPCodec ，HTTPCodec用来在 HTTPMessage(Request/Response) 和 字节流之间做转换（就是解析/序列化）。\n一个 HTTPTransaction 对应一个 HTTP2 的Stream ，也就是一次 Req/Resp Handler 是业务逻辑处理的基类。\n","title":"Proxygen http2 代码分析","type":"post"},{"content":"TrafficServer 是Apache基金会的 HTTP/HTTP2 代理服务器。\nTrafficServer 的 HTTP2 部分主要的代码在 :\ntrafficserver/proxy/http2/HTTP2.h, HTTP2.cc trafficserver/proxy/http2/Http2Stream.h, Http2Stream.cc trafficserver/proxy/http2/Http2ClientSession.h, Http2ClientSession.cc trafficserver/proxy/http2/Http2ConnectionState.h, Http2ConnectionState.cc trafficserver/proxy/http2/ Http2DependencyTree.h, Http2DependencyTree.cc trafficserver/proxy/http2/HPACK.h, HPACK.cc trafficserver/proxy/http2/HuffmanCodec.h, HuffmanCodec.cc 1. trafficserver/proxy/http2/HTTP2.h, HTTP2.cc # 10种Frame 的struct定义，解析/序列化函数，HeaderList转换函数，一些常量定义\n2. trafficserver/proxy/http2/Http2ClientSession.h, Http2ClientSession.cc # Http2ClientSession 处理 io event 事件，解析 FrameHeader，\n调用顺序：\nHttp2ClientSession::main_event_handler() --\u0026gt; state_read_connection_preface() --\u0026gt; state_start_frame_read() --\u0026gt; state_process_frame_read() 解析出一个完整的Frame --\u0026gt; do_complete_frame_read() --\u0026gt; Http2ConnectionState::main_event_handler(HTTP2_SESSION_EVENT_RECV ) 3. trafficserver/proxy/http2/Http2ConnectionState.h, Http2ConnectionState.cc # Http2ConnectionState，对应一个 HTTP2 连接，代码基本等价于 proxygen 的 HTTPSession，主要的成员变量：\n{ 双链表\u0026lt;Http2Stream\u0026gt; //略挫，有个find_stream 用的遍历，改map好点。 Http2ClientSession， HpackHandle 2个， DependencyTree， Http2ConnectionSettingsclient_settings server_settings，连接的几个 Settings 配置项，一个int数组 } 主要的方法：各个 rcv_xxx_frame() 和 send_xxx_frame(0 Http2ConnectionState::main_event_handler(HTTP2_SESSION_EVENT_RECV )里面，解析输入的各种 Frame\nrcv_data_frame, // HTTP2_FRAME_TYPE_DATA rcv_headers_frame, // HTTP2_FRAME_TYPE_HEADERS rcv_priority_frame, // HTTP2_FRAME_TYPE_PRIORITY rcv_rst_stream_frame, // HTTP2_FRAME_TYPE_RST_STREAM rcv_settings_frame, // HTTP2_FRAME_TYPE_SETTINGS rcv_push_promise_frame, // HTTP2_FRAME_TYPE_PUSH_PROMISE rcv_ping_frame, // HTTP2_FRAME_TYPE_PING rcv_goaway_frame, // HTTP2_FRAME_TYPE_GOAWAY rcv_window_update_frame, // HTTP2_FRAME_TYPE_WINDOW_UPDATE rcv_continuation_frame, // HTTP2_FRAME_TYPE_CONTINUATION 还有发送各类 frame 的接口，send_data_frames()/send_headers_frame() / send_push_promise_frame() / send_rst_stream_frame() 等。\n具体地：\nrcv_data_frame : # find_stream,然后检查stream的state是不是OPEN/HALF_CLOSE_LOCAL，去除 padding，处理END_STREAM flag，检查本端流量控制 window_size ，保存buffer，更新本地的window，发回 Connection 和 Stream Level 的WindowUpdate Frame。\nrcv_headers_frame： # find_stream/create_stream，处理padding，处理PRIORITY flag添加到 PriorityTree 里面，如果 Header Block 结束了（即有END_HEADERS FLAG），那就http2_decode_header_blocks() 做HPACK 解压缩，调用 Http2Stream 处理解析出的HeaderList\nrcv_priority_frame: # find_stream，reprioritize，重新调整树。\nrcv_rst_stream_frame # rcv_settings_frame: # parse检查收到的各个配置项，更新到client_settings中，并发送SETTINGS_ACK\nrcv_push_promise_frame 等 # 包括 rcv_ping_frame\nrcv_goaway_frame:\nrcv_window_update_frame\n比较简单，略。\nrcv_continuation_frame # 类似 rcv_headers_frame\nsend_data_frames_depends_on_priority # 取了 DependencyTree的top()，\nsend_data_frames 等 # send_a_data_frame\nsend_headers_frame\nsend_push_promise_frame\nsend_rst_stream_frame\nsend_settings_frame\nsend_ping_frame\nsend_goaway_frame\nsend_window_update_frame\n比较简单，就是检查字段合法性，序列化，然后发送。\n4. trafficserver/proxy/http2/Http2Stream.h, Http2Stream.cc # 对应实现HTTP2 的Stream，重要的成员变量有：\nHttp2StreamState _state;//实现IDLE，OPEN，RESERVED，HALF_CLOSE等的切换。\nDependencyTree::Node *priority_node;// Priority Tree中的节点指针。\n5. trafficserver/proxy/http2/Http2DependencyTree.h, Http2DependencyTree.cc # Http2DependencyTree::Node 表示一个Stream，成员变量有：\n{ bool actived; //是否有输出数据 bool queued; //是否在 actived 队列中 uint32_t id; uint32_t weight; uint32_t point; //queue中的节点按照这个 point 做优先级排序 Node *parent; //父节点 DLL\u0026lt;Node\u0026gt; children; //子节点的双列表 PriorityQueueEntry\u0026lt;Node *\u0026gt; *entry; // PriorityQueue\u0026lt;Node *\u0026gt; *queue; //有输出数据的所有子节点的队列，按point 优先级降序排列 Http2Stream *t; } Http2DependencyTree 中有个 Node * root 指向 树的根节点。\nHttp2DependencyTree的主要方法有：\nNode *find(uint32_t id);\nDFS 递归遍历树，查找一个Stream。\nNode *add(uint32_t parent_id, uint32_t id, uint32_t weight, bool exclusive, T t); 就是插入 parent 的 children 链表中，兼做 exclusive 处理。\nvoid remove(Node *node); 从parent 的 queue 中移除自己，把自己的 queue 全转给 parent，把自己的 children 全转给 parent\nvoid reprioritize(uint32_t new_parent_id, uint32_t id, bool exclusive); 重排优先级，就是移动节点\nNode *top(); 返回整个树中，point 最高的叶子节点。递归实现。\nvoid activate(Node *node); 往上遍历，如果当前节点没有 在 parent 的 queue中，加入\nvoid deactivate(Node *node, uint32_t sent); 从parent 的queue中去除自己。\nvoid update(Node *node, uint32_t sent);\n这里使用 Weighted Fair Queue (WFQ) Scheduling 来调度 Stream 之间的优先级。\nHttp2ConnectionState::send_data_frames_depends_on_priority() 中，是直接取了 树的 top() 节点，\n6. trafficserver/proxy/http2/HPACK.h, HPACK.cc # 实现 HPACK 压缩/解压缩\n7. trafficserver/proxy/http2/HuffmanCodec.h, HuffmanCodec.cc # 实现静态的 Huffman 表，接口简单，只有4个函数，，\n是实现成 二叉树，相比proxygen估计效率会低一些。\n","date":"2017-03-08","externalUrl":null,"permalink":"/post/TrafficServer-http2-code-analysis/","section":"Posts","summary":"TrafficServer 是Apache基金会的 HTTP/HTTP2 代理服务器。\nTrafficServer 的 HTTP2 部分主要的代码在 :\ntrafficserver/proxy/http2/HTTP2.h, HTTP2.cc trafficserver/proxy/http2/Http2Stream.h, Http2Stream.cc trafficserver/proxy/http2/Http2ClientSession.h, Http2ClientSession.cc trafficserver/proxy/http2/Http2ConnectionState.h, Http2ConnectionState.cc trafficserver/proxy/http2/ Http2DependencyTree.h, Http2DependencyTree.cc trafficserver/proxy/http2/HPACK.h, HPACK.cc trafficserver/proxy/http2/HuffmanCodec.h, HuffmanCodec.cc ","title":"TrafficServer http2 代码分析","type":"post"},{"content":"","date":"2015-09-06","externalUrl":null,"permalink":"/categories/crypto/","section":"Categories","summary":"","title":"Crypto","type":"categories"},{"content":"","date":"2015-09-06","externalUrl":null,"permalink":"/categories/tls/","section":"Categories","summary":"","title":"Tls","type":"categories"},{"content":"最近发现密码学很有意思，刚好还和工作有点关系，就研究了一下，本文是其中一部分笔记和一些思考。\n密码学理论艰深，概念繁多，本人知识水平有限，错误难免，如果您发现错误，请务必指出，非常感谢!\n本文禁止转载\n本文目标：\n学习鉴赏TLS协议的设计，透彻理解原理和重点细节 跟进一下密码学应用领域的历史和进展 整理现代加密通信协议设计的一般思路 本文有门槛，读者需要对现代密码学有清晰而系统的理解，建议花精力补足背景知识再读。本文最后的参考文献里有一些很不错的学习资料。\n一 . TLS协议的设计目标： # 1. 密码学的方法论 # 密码学和软件开发不同，软件开发是工程，是手艺，造轮子是写代码的一大乐趣。软件开发中常常有各种权衡，一般难有明确的对错，一般还用建筑来比拟软件的结构，设计的优雅被高度重视。\n密码学就不一样了。密码学是科学，不是工程，有严格的技术规范，严禁没有经过学术训练者随意创造。要求严谨的理论建模，严密的数学证明。很少有需要权衡的地方，正确就是正确，错误就是错误。又由于密码学过去在军事上的重要价值，各国政府一直投入大量人力物力财力，不断深入强化己方的算法，破解对手的算法，所以密码学就是一种残酷的军备竞赛。\n密码学有很多的陷阱（下文会介绍几个），设计使用密码学的协议或者软件，是极其容易出错，高风险的专业活动，单纯的码农背景是做不了的。本着不作死就不会死的伟大理念，首先推荐读者尽可能使用 TLS 这种标准化，开源，广泛使用，久经考验，高性能的协议。本文也只是整理一点粗浅的科普常识，读完这篇文章，并不能使读者具有设计足够安全的密码学协议的能力。\n密码学经过几十年的军备竞赛式发展，已经发展出大量巧妙而狡猾的攻击方法，我们使用的算法，都是在所有已知的攻击方法下都无法攻破的，由于我们大多数码农并没有精力去了解最前沿的攻击方法，所以我们其实并没有能力去评价一个加密算法，更没有能力自己发明算法。所以最好跟着业界的主流技术走，肯定不会有大错。\n现代密码学近20年进展迅猛，现在搞现代密码学研究的主要都是数学家，在这个领域里面以一个码农的知识背景，已经很难理解最前沿的东西，连正确使用加密算法都是要谨慎谨慎再谨慎的。一个码农，能了解密码学基本概念，跟进密码学的最新应用趋势，并正确配置部署TLS这种协议，就很不错了。\n密码学算法很难被正确地使用，各种细节非常容易出错。 例如：\n1.大多数码农都听说过aes，可是大多数都不了解细节，比如：aes应该用哪种模式？应该用哪种padding？IV/nonce应该取多少bit？IV/nonce应该怎么生成？ key size应该选多大？key应该怎么生成？应不应该加MAC？MAC算法的选择？MAC和加密应该怎么组合？ 2.大多数知道RSA的码农分不清 RSASSA-PKCS1-v1_5 ，RSAES-OAEP 和 RSASSA-PSS 3.更多错误参见 这个stackoverflow问答，强烈推荐仔细阅读 密码学算法很难被正确地实现(代码实现过程中会引入很多漏洞，比如HeartBleed，比如各种随机数生成器的bug，时间侧通道攻击漏洞)\n不能一知半解，绝对不能在一知半解的情况下就动手设计密码学协议。犹如“盲人骑瞎马，夜班临深池”。\n不能闭门造车，密码学相关协议和代码一定要开源，采用大集市式的开发，接受peer review，被越多的人review，出漏洞的可能越小（所以应该尽可能使用开源组件）\n2. TLS的设计目标 # TLS的设计目标是构建一个安全传输层（Transport Layer Security ），在基于连接的传输层（如tcp）之上提供：\n密码学安全 (1). 保密， message privacy (保密通过加密encryption实现，所有信息都加密传输，第三方无法窃听 ) (2). 完整性， message integrity（ 通过MAC校验机制，一旦被篡改，通信双方会立刻发现 ） (3). 认证， mutual authentication （双方认证,双方都可以配备证书，防止身份被冒充 ） 互操作，通用性 （ 根据公开的rfc，任何符合rfc的软件实现都可以互操作，不受限于任何专利技术） 可扩展性 ( 通过扩展机制 tls_ext可以添加功能，有大量的新功能，都是通过扩展添加的) 高效率 （通过session cache，恰当部署cache之后，tls的效率很高） 请认准这几个目标，在后文中，会逐一实现。\n3. TLS的历史 # 1995: SSL 2.0, 由Netscape提出，这个版本由于设计缺陷，并不安全，很快被发现有严重漏洞，已经废弃。 1996: SSL 3.0. 写成RFC，开始流行。目前(2015年)已经不安全，必须禁用。 1999: TLS 1.0. 互联网标准化组织ISOC接替NetScape公司，发布了SSL的升级版TLS 1.0版. 2006: TLS 1.1. 作为 RFC 4346 发布。主要fix了CBC模式相关的如BEAST攻击等漏洞 2008: TLS 1.2. 作为RFC 5246 发布 。增进安全性。目前(2015年)应该主要部署的版本，请确保你使用的是这个版本 2015之后: TLS 1.3，还在制订中，支持0-rtt，大幅增进安全性，砍掉了aead之外的加密方式 由于SSL的2个版本都已经退出历史舞台了，所以本文后面只用TLS这个名字。 读者应该明白，一般所说的SSL就是TLS。\n二. TLS协议的原理 # 1. 自顶向下，分层抽象 # 构建软件的常用方式是分层，把问题域抽象为多层，每一层的概念定义为一组原语，上一层利用下一层的组件构造实现，并被上一层使用，层层叠叠即成软件。\n例如在编程语言领域中，汇编语言为一层，在汇编上面是C/C++等静态编译语言，C/C++之上是python/php/lua等动态类型脚本语言层，之上常常还会构造领域特定的DSL 在网络架构中，以太网是一层，其上是ip协议的网络层，ip之上是tcp等传输层，tcp之上是http等应用层 密码学通信协议也是分层构造得到。大致可以这么分层：\n最底层是基础算法原语的实现，例如: aes , rsa， md5, sha256，ecdsa, ecdh 等（举的例子都是目前的主流选择，下同）\n其上是选定参数后，符合密码学里标准分类的算法，包括块加密算法，签名算法，非对称加密算法，MAC算法等，例如： aes-128-cbc-pkcs7，rsaes-oaep ，rsassa-pkcs1-v1_5, hmac-sha256，ecdsa-p256，curve25519 等\n再其上，是把多种标准算法组合而成的半成品组件，例如：对称传输组件例如 aes-128-cbc + hmac-sha256，aes-128-gcm，认证密钥协商算法: rsassa-OAEP + ecdh-secp256r1，数字信封：rsaes-oaep + aes-cbc-128 + hmac-sha256 ，文件密码加密存储组件：pbkdf2+aes-128-cbc-hmac-sha256，密钥扩展算法 PRF-sha256 等\n再其上，是用各种组件拼装而成的各种成品密码学协议/软件，例如：tls协议，ssh协议，srp协议，gnupg文件格式，iMessage协议，bitcoin协议等等\n第1层，一般程序员都有所了解，例如rsa，简直路人皆知; md5 被广泛使用(当然，也有广泛的误用) 第2层，各种莫名其妙的参数，一般很让程序员摸不着头脑，需要深入学习才能理清。 第3层，很多程序员自己造的轮子，往往说白了就是想重复实现第3层的某个组件而已。 第4层，正确地理解，使用，部署这类成熟的开放协议，并不是那么容易。很多的误用来源于不理解，需要密码学背景知识，才能搞懂是什么，为什么，怎么用。\n最难的是理论联系实际。面对一个一团乱麻的实际业务问题，最难的是从中抽象分析出其本质密码学问题，然后用密码学概念体系给业务建模。在分析建模过程中，要求必须有严密的，体系化的思考方式。不体系化的思考方式会导致疏漏，或者误用。\n第2层中，密码学算法，常见的有下面几类：\n块加密算法 block cipher: AES, Serpent, 等 流加密算法 stream cipher: RC4，ChaCha20 等 Hash函数 hash funtion:MD5，sha1，sha256，sha512 , ripemd 160，poly1305 等 消息验证码函数 message authentication code: HMAC-sha256，AEAD 等 密钥交换 key exchange: DH，ECDH，RSA，PFS方式的（DHE，ECDHE）等 公钥加密 public-key encryption: RSA，rabin-williams 等 数字签名算法 signature algorithm:RSA，DSA，ECDSA (secp256r1 , ed25519) 等 密码衍生函数 key derivation function: TLS-12-PRF(SHA-256) , bcrypto，scrypto，pbkdf2 等 随机数生成器 random number generators: /dev/urandom 等 每个类别里面的都有几个算法不断竞争，优胜劣汰，近几十年不断有老的算法被攻破被淘汰，新的算法被提出被推广。这一块话题广，水很深，内容多，陷阱也多，后续byron会翻译整理一系列文章，分享一下每一类里面个人收集的资料。 在此推荐一下 开源电子书crypto101，讲的很透彻，而且很易读)\n设计一个加密通信协议的过程，就是自顶向下，逐步细化，挑选各类组件，拼装成完整协议的过程\n3. TLS CipherSuite # 从上述分层的角度看，TLS大致是由3个组件拼成的：\n1.对称加密传输组件，例如aes-128-gcm(这几个例子都是当前2015年最主流的选择); 2.认证密钥协商组件，例如rsa-ecdhe; 3.密钥扩展组件，例如TLS-PRF-sha256 这些组件可以再拆分为5类算法，在TLS中，这5类算法组合在一起，称为一个CipherSuite： authentication (认证算法) encryption (加密算法 ) message authentication code (消息认证码算法 简称MAC) key exchange (密钥交换算法) key derivation function （密钥衍生算法)\nTLS协议设计之初就考虑到了这每一类算法的演变，所以没有定死算法，而是设计了一个算法协商过程，来允许加入新的算法( 简直是软件可扩展性设计的典范！)，协商出的一个算法组合即一个CipherSuite TLS CipherSuite 在 iana 集中注册，每一个CipherSuite分配有 一个2字节的数字用来标识 ，可以在 iana的注册页面 查看\niana注册页面截图： 在浏览器中，就可以查看当前使用了什么 CipherSuite，在地址栏上，点击一个小锁的标志，就可以看到了。 服务器端支持的CipherSuite列表，如果是用的openssl，可以用 openssl ciphers -V | column -t 命令查看，输出如： 例如其中这一行(这个是目前的主流配置):\n0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD 表示： 名字为ECDHE-RSA-AES128-GCM-SHA256 的CipherSuite ，用于 TLSv1.2版本，使用 ECDHE 做密钥交换，使用RSA做认证，使用AES-128-gcm做加密算法，MAC由于gcm作为一种aead模式并不需要，所以显示为aead，使用SHA256做PRF算法。\n可以参考 man 1 ciphers\n要注意的是，由于历史兼容原因，tls标准，和openssl的tls实现中，有一些极度不安全的CipherSuite，一定要禁用，比如：\nEXP , EXPORT 一定要禁用。EXPORT表示上世纪美国出口限制弱化过的算法，早已经被攻破，TLS的FREAK 攻击就是利用了这类坑爹的算法。 eNULL, NULL 一定要禁用。NULL表示不加密！默认是禁用的。 aNULL ： 一定要禁用。表示不做认证(authentication) ，也就是说可以随意做中间人攻击。 ADH 一定要禁用。表示不做认证的 DH 密钥协商。 上面是举个例子，读者不要自己去研究怎么配置，这太容易搞错。 请按照mozilla官方给出的这个权威文档，复制粘贴就好了。\nCipherSuite的更多解释，配置方法等，可以参考byron之前写的一篇文章 SSL/TLS CipherSuite 介绍\n4. 协议分层 # TLS是用来做加密数据传输的，因此它的主体当然是一个对称加密传输组件。为了给这个组件生成双方共享的密钥，因此就需要先搞一个认证密钥协商组件，故，TLS协议自然分为：\n做对称加密传输的record协议 ，the record protocol 做认证密钥协商的handshake协议，the handshake protocol 还有3个很简单的辅助协议：\nchangecipher spec 协议，the changecipher spec protocol, 用来通知对端从handshake切换到record协议(有点冗余，在TLS1.3里面已经被删掉了) alert协议，the alert protocol, 用来通知各种返回码， application data协议， The application data protocol，就是把http，smtp等的数据流传入record层做处理并传输。 这种 认证密钥协商 + 对称加密传输 的结构，是绝大多数加密通信协议的通用结构，在后文的更多协议案例中，我们可以看到该结构一再出现。\n这5个协议中： record协议在tcp流上提供分包， 图片来自网络： 其它的: handshake protocol, alert protocol, changeCipherSpec protocol, application data protocol都封装在record protocol的包里，然后在tcp上传输（此处以tcp举例，也有可能是udp，或者随便什么ipc机制等）\n下文分别介绍，内容主要是翻译自 RFC5246，RFC5077，RFC4492\n5. record 协议 # record协议做应用数据的对称加密传输，占据一个TLS连接的绝大多数流量，因此，先看看record协议 图片来自网络: Record 协议 \u0026ndash; 从应用层接受数据，并且做:\n分片，逆向是重组 生成序列号，为每个数据块生成唯一编号，防止被重放或被重排序 压缩，可选步骤，使用握手协议协商出的压缩算法做压缩 加密，使用握手协议协商出来的key做加密/解密 算HMAC，对数据计算HMAC，并且验证收到的数据包的HMAC正确性 发给tcp/ip，把数据发送给 TCP/IP 做传输(或其它ipc机制)。 1. SecurityParameters # record层的上述处理，完全依据下面这个SecurityParameters里面的参数进行：\nstruct { ConnectionEnd entity; PRFAlgorithm prf_algorithm; BulkCipherAlgorithm bulk_cipher_algorithm; CipherType cipher_type; uint8 enc_key_length; uint8 block_length; uint8 fixed_iv_length; uint8 record_iv_length; MACAlgorithm mac_algorithm; uint8 mac_length; uint8 mac_key_length; CompressionMethod compression_algorithm; opaque master_secret[48]; opaque client_random[32]; opaque server_random[32]; } SecurityParameters; record 层使用上面的SecurityParameters生成下面的6个参数（不是所有的CipherSuite都需要全部6个，如果不需要，那就是空）:\nclient write MAC key server write MAC key client write encryption key server write encryption key client write IV server write IV 当handshake完成，上述6个参数生成完成之后，就可以建立连接状态，连接状态除了上面的SecurityParameters，还有下面几个参数，并且随着数据的发送/接收，更新下面的参数：\ncompression state : 当前压缩算法的状态。\ncipher state : 加密算法的当前状态，对块加密算法比如aes，包含密码预处理生成的轮密钥(感谢温博士指出) \u0026ldquo;round key\u0026rdquo;，还有IV等；对于流加密，包含能让流加密持续进行加解密的状态信息\nsequence number : 每个连接状态都包含一个sequence number，并且读和写状态有不同的sequence number。当连接开始传输数据时，sequence number必须置为0. sequence number 是uint64类型的，并且不得超过 $ 2^{64}-1$ 。s. Sequence number不得回绕。如果一个TLS实现无法避开回绕一个sequence number，必须进行重协商。sequence number在每个record被发送时都增加1。并且传输的第1个Record必须使用0作为sequence number。\n此处有几个问题值得思考：\n(1). 为什么MAC key , encryption key, IV 要分别不同？\n在密码学中，对称加密算法一般需要encryption key，IV两个参数，MAC算法需要MAC key参数，因此这3个key用于不同的用途。 当然，不是所有的算法都一定会用到这3个参数，例如新的aead型算法，就不需要MAC key。\n(2). 为什么client和server要使用不同的key 如果TLS的双方使用相同的key，那么当使用stream cipher加密应用数据的时候，stream cipher的字节流在两个方向是一样的，如果攻击者知道TLS数据流一个方向的部分明文（比如协议里面的固定值），那么对2个方向的密文做一下xor，就能得到另一个方向对应部分的明文了。\n还有，当使用 aead 比如 aes-gcm 做加密的时候，aead标准严格要求，绝对不能用相同的 key+nonce 加密不同的明文，故如果TLS双方使用相同的key，又从相同的数字开始给nonce递增，那就不符合规定，会直接导致 aes-gcm 被攻破。\n参考: http://crypto.stackexchange.com/questions/2878/separate-read-and-write-keys-in-tls-key-material\n2. record层分段 # 如上图所示，对要发送的数据流，首先分段，分段成如下格式：\nstruct { uint8 major; uint8 minor; } ProtocolVersion; enum { change_cipher_spec(20), alert(21), handshake(22), application_data(23), (255) } ContentType; struct { ContentType type; ProtocolVersion version; uint16 length; opaque fragment[TLSPlaintext.length]; } TLSPlaintext; version字段 : ，定义当前协商出来的TLS协议版本，例如 TLS 1.2 version 是 { 3, 3 }\nlength字段 : 即长度，tls协议规定length必须小于 $2^{14}$，一般我们不希望length过长，因为解密方需要收完整个record，才能解密，length过长会导致解密方需要等待更多的rtt，增大latency，破坏用户体验，参考 Web性能权威指南 TLS那一章。\ntype字段 : ，用来标识当前record是4种协议中的哪一种，\nrecord压缩 TLS协议定义了可选的压缩，但是，由于压缩导致了 2012 年被爆出CRIME攻击，BREACH攻击，所以在实际部署中，一定要禁用压缩。 http://www.unclekevin.org/?p=640 http://www.freebuf.com/articles/web/5636.html 3. record层的密码学保护 # record层的密码学保护:\n经过处理后的包格式定义如下：\nstruct { ContentType type; ProtocolVersion version; uint16 length; select (SecurityParameters.cipher_type) { case stream: GenericStreamCipher; case block: GenericBlockCipher; case aead: GenericAEADCipher; } fragment; } TLSCiphertext; TLS协议设计目标中的 1.保密(encryption) 2.完整性(authentication) ，和防重放就在这里实现。 实现方式有3类：\nBlock Cipher (CBC mode of operation) + HMAC：例如 aes-128-cbc+hmac-sha256 Stream Cipher (RC4) + HMAC Authenticated-Encryption using block cipher (GCM/CCM 模式)：例如 aes-128-gcm 1.Block Cipher+HMAC 和 2.Stream Cipher + HMAC 的各类算法目前（2015年）都已经爆出各种漏洞(后文解释)，目前最可靠的是 3.Authenticated-Encryption 类的算法，主要就是aes-gcm，下一代的TLS v1.3干脆只保留了3.Authenticated-Encryption，把1和2直接禁止了(所以。。。你真的还要继续用aes-cbc吗？)。\nGCM模式是AEAD的，所以不需要MAC算法。 GCM模式是AEAD的一种，AEAD 的 作用类似于 Encrypt-then-HMAC ，例如 Sha256 + Salt + AES + IV\n此处需要介绍一个陷阱。 在密码学历史上，出现过3种加密和认证的组合方式：\nEncrypt-and-MAC MAC-then-Encrypt Encrypt-then-MAC 在TLS协议初定的那个年代，人们还没意识到这3种组合方式的安全性有什么差别，所以TLS协议规定使用 2.MAC-then-Encrypt，即先计算MAC，然后把 \u0026ldquo;明文+MAC\u0026rdquo; 再加密(块加密或者流加密)的方式，做流加密+MAC，和块加密+MAC。 但是，悲剧的是，近些年，人们发现 MAC-then-Encrypt 这种结构导致了 很容易构造padding oracle 相关的攻击，例如这在TLS中，间接形成被攻击者利用，这间接导致了 BEAST 攻击 , Lucky 13攻击 (CVE-2013-0169), 和 POODLE 攻击 (CVE-2014-3566).\n目前因此，学术界已经一致同意： Encrypt-then-MAC 才是最安全的! tls使用的是 MAC-then-Encrypt 的模式，导致了一些问题。 具体比较，参见： http://cseweb.ucsd.edu/~mihir/papers/oem.pdf https://www.iacr.org/archive/crypto2001/21390309.pdf http://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac https://news.ycombinator.com/item?id=4779015 http://tozny.com/blog/encrypting-strings-in-android-lets-make-better-mistakes/\n鉴于这个陷阱如此险恶，学术界有人就提出了，干脆把Encrypt和MAC直接集成为一个算法，在算法内部解决好安全问题，不再让码农选择，避免众码农再被这个陷阱坑害，这就是AEAD（Authenticated-Encryption With Additional data）类的算法，GCM模式就是AEAD最重要的一种。\n4. record层的密码学保护\u0026ndash;MAC # TLS record 层 MAC的计算方法：\nMAC(MAC_write_key, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment); 其中的seq_num是当前record的 sequence number，每条record都会++， 可以看到把 seq_num，以及record header里面的几个字段也算进来了，这样解决了防重放问题，并且保证record的任何字段都不能被篡改。\n算完MAC，格式如下：\nstream-ciphered struct { opaque content[TLSCompressed.length]; opaque MAC[SecurityParameters.mac_length]; } GenericStreamCipher; 然后根据SecurityParameters.cipher_type，选择对应的对称加密算法进行加密，分类解说如下：\n5. record层的密码学保护\u0026ndash;stream cipher # stream cipher: 算stream cipher，stream cipher的状态在连续的record之间会复用。 stream cipher的主力是RC4，但是目前RC4已经爆出多个漏洞，所以实际中基本不使用流加密算法，详情请见：\nhttps://tools.ietf.org/html/rfc7457#section-2.5\n[FreeBuf] RC4加密已不再安全，破解效率极高\nhttp://www.imperva.com/docs/HII_Attacking_SSL_when_using_RC4.pdf\n6. record层的密码学保护\u0026ndash; CBC block cipher # CBC模式块加密 TLS目前靠得住的的块加密cipher也不多，基本就是AES（最靠谱，最主流），Camellia，SEED，（3DES，IDEA之类已经显得老旧，DES请禁用），加密完的格式如下：\nstruct { opaque IV[SecurityParameters.record_iv_length]; block-ciphered struct { opaque content[TLSCompressed.length]; opaque MAC[SecurityParameters.mac_length]; uint8 padding[GenericBlockCipher.padding_length]; uint8 padding_length; }; } GenericBlockCipher; 这个值得说道说道，因为我们码农平常在业界还能看到很多用AES-CBC的地方，其中的几个参数：\nIV ： 要求**必须用密码学安全的伪随机数生成器(CSPRNG)**生成，并且必须是不可预测的，在Linux下，就是用用/dev/urandom，或者用 openssl 库的 RAND_bytes()。 注意：TLS 在 1.1版本之前，没有这个IV字段，前一个record的最后一个block被当成下一个record的IV来用，然后粗大事了，这导致了 BEAST攻击。 所以，TLS1.2改成了这样。 (还在使用CBC的各位，建议关注一下自己的IV字段是怎么生成出来的。如果要用，做好和TLS1.2的做法保持一致)。\n其中 SecurityParameters.record_iv_length 一定等于 SecurityParameters.block_size. 例如 AES-256-CBC的 IV 一定是16字节长的，因为AES 128/192/256 的block size都是16字节。\npadding\n使用CBC常用的PKCS 7 padding（在block size=16字节这种情况下，和pkcs 5的算法是一回事，java代码里面就可以这么用这个case里，和pkcs 5的结果是一样的） padding_length 就是PKCS 7 padding的最后一个字节 注意2个险恶的陷阱：\n实现的代码必须在收到全部明文之后才能传输密文，否则可能会有BEAST攻击 实现上，根据MAC计算的时间，可能进行时间侧通道攻击，因此必须确保\u0026ndash;运行时间和padding是否正确无关。 7. record层的密码学保护\u0026ndash; AEAD cipher # AEAD 到了我们重点关注的AEAD，AEAD是新兴的主流加密模式，是目前最重要的模式，其中主流的AEAD模式是 aes-gcm-128/aes-gcm-256/chacha20-poly1305\nAEAD加密完的格式是：\nstruct { opaque nonce_explicit[SecurityParameters.record_iv_length]; aead-ciphered struct { opaque content[TLSCompressed.length]; }; } GenericAEADCipher; AEAD ciphers的输入是: key，nonce, 明文,和 \u0026ldquo;additional data\u0026rdquo;. key是 client_write_key 或者 the server_write_key. 不需要使用 MAC key.\n每一个AEAD算法都要指定不同的nonce构造算法，并指定 GenericAEADCipher.nonce_explicit 的长度. 在TLS 1.2中，规定很多情况下，可以按照rfc5116 section 3.2.1的技术来做。其中record_iv_length是nonce的显式部分的长度，nonce的隐式部分从key_block作为 client_write_iv和 and server_write_iv得出，并且把显式部分放在 GenericAEAEDCipher.nonce_explicit 里.\n在TLS 1.3 draft中，做了更改:\n规定 AEAD算法的 nonce的长度规定为 max(8 bytes, N_MIN)，即如果N_MIN比8大，就用N_MIN; 如果比8小，就用8。\n并且规定 N_MAX小于8字节的AEAD不得用于TLS。\n规定TLS AEAD中每条record的nonce通过下面的方法构造出来： 64bit的sequence number的右侧填充0，直到长度达到iv_length。然后把填充过的sequence number和静态的 client_write_iv或 server_write_iv （根据发送端选择）做异或(XOR)。异或完成后，得到的 iv_length 的nonce就可以做每条record的nonce用了。\nAEAD输入的明文就是 TLSCompressed.fragment (记得上面的介绍吗？AEAD是MAC和encrypt的集成，所以输入数据不需要在算MAC了).\nAEAD输入的additional_data 是:\nadditional_data = seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length; \u0026ldquo;+\u0026rdquo; 表示字符串拼接。 可以看到，此处类似上面的MAC计算，算入了seq_num来防重放，type,version,length等字段防止这些元数据被篡改。\nAEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext, additional_data) 解密+验证完整性：\nTLSCompressed.fragment = AEAD-Decrypt(write_key, nonce, AEADEncrypted, additional_data) 如果解密/验证完整性失败,就回复一条 fatal bad_record_mac alert 消息.\naes-gcm的iv长度，nonce长度，nonce构成等，后续再深入探讨。\n8. record层的密码学保护\u0026ndash; Key扩展 # Key 扩展\nTLS握手生成的master_secret只有48字节，2组encryption key, MAC key, IV加起来，长度一般都超过48，(例如 AES_256_CBC_SHA256 需要 128字节),所以，TLS里面用1个函数，来把48字节延长到需要的长度，称为PRF：\nkey_block = PRF(SecurityParameters.master_secret, \u0026#34;key expansion\u0026#34;, SecurityParameters.server_random + SecurityParameters.client_random); 然后，key_block像下面这样被分割：\nclient_write_MAC_key[SecurityParameters.mac_key_length] server_write_MAC_key[SecurityParameters.mac_key_length] client_write_key[SecurityParameters.enc_key_length] server_write_key[SecurityParameters.enc_key_length] client_write_IV[SecurityParameters.fixed_iv_length] server_write_IV[SecurityParameters.fixed_iv_length] TLS使用HMAC结构，和在CipherSuite中指定的hash函数（安全等级起码是SHA256的水平） 来构造PRF，\n首先定义P_hash，把(secret,seed)扩展成无限长的字节流：\nP_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret, A(3) + seed) + ... 其中\u0026quot;+\u0026ldquo;表示字符串拼接。 A() 定义为:\nA(0) = seed A(i) = HMAC_hash(secret, A(i-1)) TLS的 PRF 就是把 P_hash 应用在secret上:\nPRF(secret, label, seed) = P_\u0026lt;hash\u0026gt;(secret, label + seed) 其中 label 是一个协议规定的，固定的 ASCII string.\n要注意的是，TLS 1.3里面已经废弃了这种方式，改为使用更靠谱的 HKDF，HKDF 也是 html5的WebCryptoAPI的标准算法之一。\n5. handshake 协议 # handshake protocol重要而繁琐。\nTLS 1.3对握手做了大修改，下面先讲TLS 1.2，讲完再介绍一下分析TLS 1.3.\n1.handshake的总体流程 # handshake protocol用于产生给record protocol使用的SecurityParameters。 在handshake中:\n客户端和服务器端协商TLS协议版本号和一个CipherSuite， 认证对端的身份（可选，一般如https是客户端认证服务器端的身份）， 并且使用密钥协商算法生成共享的master secret。 步骤如下：\n交换Hello消息，协商出算法，交换random值，检查session resumption.\n交换必要的密码学参数，来允许client和server协商出premaster secret。\n交换证书和密码学参数，让client和server做认证，证明自己的身份。\n从premaster secret和交换的random值 ，生成出master secret。\n把SecerityParameters提供被record层。\n允许client和server确认对端得出了相同的SecurityParameters，并且握手过程的数据没有被攻击者篡改。\nHandshake的结果是在双方建立相同的Session，Session 包含下列字段：\nsession identifier session id，用来唯一标识一个session，在session 恢复的时候，也要用到 peer certificate 对端的 X509v3 格式证书. 如果不需要认证对端的身份，就为空。 compression method 压缩算法，一般被禁用 cipher spec CipherSuite，如上文介绍，包含： 用于生成key的pseudorandom function (PRF) , 块加密算法例如AES, MAC算法 (例如 HMAC-SHA256). 还包括一个 mac_length字段，在后文的we握手协议介绍 master secret 48字节的，client和server共享密钥。 is resumable 一个标志位，用来标识当前session是否能被恢复。 以上字段，随后被用于生成 record层的SecurityParameters，多个连接可以通过握手协议的session恢复功能来复用同一个session。\n握手协议使用 非对称加密/密钥协商/数字签名 3类算法， 因此要求读者对这3类算法概念清晰，能准确区分。 在此澄清一下,： 非对称的算法分为3类: ，\n非对称加密，有：RSAES-PKCS1-v1_5，RSAES-OAEP ,Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5等 非对称密钥协商，有：DH，DHE，ECDH，ECDHE 等 非对称数字签名：RSASSA-PKCS1-v1_5，RSASSA-PSS，ECDSA，DSA，ED25519 等 另外，非对称加密算法，可以当作密钥协商算法来用，所以 RSAES-PKCS1-v1_5，RSAES-OAEP 也可以当作密钥协商算法来用。\n插播一段 RSA：\nRSA的实际工程应用，要遵循PKCS#1 标准，见 https://www.ietf.org/rfc/rfc3447\n其中的 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 是使用RSA算法的两种不同scheme（体制）。 RSAES表示 RSA Encryption schemes，即非对称加密， RSAES有：RSAES-OAEP，RSAES-PKCS1-v1_5两种，其中RSAES-OAEP更新更安全\nRSASSA表示 Signature schemes with appendix，即appendix模式(appendix和recovery的区别请参看密码学教材)的非对称数字签名算法。 RSASSA有： RSASSA-PSS, RSASSA-PKCS1-v1_5 两种， 其中RSASSA-PSS更新更安全\nRSA还有一个缺陷，就是很容易被时间侧通道攻击，所以现在的RSA实现都要加 blinding ，后文有介绍。\n可以看到，RSA是一种很特殊的算法，既可以当非对称加密算法使用，又可以当非对称数字签名使用。这一点很有迷惑性，其实很多用RSA的人都分不清自己用的是RSA的哪种模式。\n相比之下，ECC(椭圆曲线)这一块的算法就很清晰，ECDSA只能用作数字签名，ECDH只能用作密钥交换。\n分清楚 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 有什么用涅？\nPKCS#1规范解释：\nA generally good cryptographic practice is to employ a given RSA key pair in only one scheme. This avoids the risk that vulnerability in one scheme may compromise the security of the other, and may be essential to maintain provable security.\nFIPS PUB 186-3 美国标准规定：\nAn RSA key pair used for digital signatures shall only be used for one digital signature scheme (e.g., ANS X9.31, RSASSA-PKCS1 v1.5 or RSASSA-PSS; see Sections 5.4 and 5.5). In addition, an RSA digital signature key pair shall not be used for other purposes (e.g., key establishment).\n一对密钥只做一个用途，要么用作非对称加解密，要么用作签名验证，别混着用！ 一对密钥只做一个用途，要么用作非对称加解密，要么用作签名验证，别混着用！ 一对密钥只做一个用途，要么用作非对称加解密，要么用作签名验证，别混着用！\n这个要求，决定了一个协议的 PFS（前向安全性），在斯诺登曝光NSA的“今日捕获，明日破解”政策后，越发重要。\nhttps://news.ycombinator.com/item?id=5942534\nhttp://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html\nhttps://lwn.net/Articles/572926/\nhttps://www.eff.org/deeplinks/2014/04/why-web-needs-perfect-forward-secrecy\nhttp://www.wired.com/2013/10/lavabit_unsealed\nPFS反映到密钥协商过程中，就是：\n不要使用RSA做密钥协商，一定只用RSA做数字签名。 不要把ECDH的公钥固定内置在客户端做密钥协商 后文可以看到这一原则在 TLS 1.3, QUIC，Apple的iMessage等协议中一再贯彻。\n非对称RSA/ECC这个话题比较大了，后面有空再写文章吧，读者可以先看一下参考资料，里面有清晰的介绍。\n插播结束，继续TLS。\n由于设计的时候，就要考虑兼容性，而且实际历史悠久，所以TLS协议90年代曾经使用的一些算法，现在已经被破解了，例如有的被发现漏洞(rc4)，有的密钥长度过短(例如曾经美帝有出口限制，限制RSA 在512比特以下，对称加密密钥限制40比特以下，后来2005年限制被取消)，但是考虑到兼容，现在的TLS实现中，还是包含了这种已经被破解的老算法的代码。这样，如果攻击者可以干扰握手过程，诱使client和server使用这种已经被破解的算法，就会威胁TLS协议的安全，这被称为“降级攻击”。\n为了在握手协议解决降级攻击的问题，TLS协议规定：client发送ClientHello消息，server必须回复ServerHello消息，否则就是fatal error，当成连接失败处理。ClientHello和ServerHello消息用于建立client和server之间的安全增强能力，ClientHello和ServerHello消息建立如下属性：\nProtocol Version Session ID Cipher Suite Compression Method. 另外，产生并交换两个random值 ClientHello.random 和 ServerHello.random\n密钥协商使用四条： server的Certificate，ServerKeyExchange，client的Certificate，ClientKeyExchange 。TLS规定以后如果要新增密钥协商方法，可以订制这4条消息的数据格式，并且指定这4条消息的使用方法。密钥协商得出的共享密钥必须足够长，当前定义的密钥协商算法生成的密钥长度必须大于46字节。\n在hello消息之后，server会把自己的证书在一条Certificate消息里面发给客户端(如果需要做服务器端认证的话，例如https)。 并且，如果需要的话，server会发送一条ServerKeyExchange消息，（例如如果服务器的证书只用做签名，不用做密钥交换，或者服务器没有证书）。client对server的认证完成后，server可以要求client发送client的证书，如果这是协商出来的CipherSuite允许的。下一步，server会发送ServerHelloDone消息，表示握手的hello消息部分已经结束。然后server会等待一个client的响应。如果server已经发过了CertificateRequest消息，client必须发送Certificate消息。然后发送ClientKeyExchange消息，并且这条消息的内容取决于ClientHello和ServerHello消息协商的算法。如果client发送了有签名能力的证书，就显式发送一个经过数字签名的CertificateVerify消息，来证明自己拥有证书私钥。\n然后，client发送一个ChangeCipherSpec消息，并且client拷贝待定的Cipher Spec到当前的Cipher Spec。然后client立即用新算法+新key+新密钥 发送Finished消息。收到后，server发送自己的ChangeCipherSpec消息，作为响应，并且拷贝待定的Cipher Spec到当前的Cipher Spec。此时，握手就完成了，client和server可以开始交换应用层数据（如下图所示）。应用层数据不得在握手完成前发送。\n引用一个来自网络的图片： Client Server ClientHello --------\u0026gt; ServerHello Certificate* ServerKeyExchange* CertificateRequest* \u0026lt;-------- ServerHelloDone Certificate* ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------\u0026gt; [ChangeCipherSpec] \u0026lt;-------- Finished Application Data \u0026lt;-------\u0026gt; Application Data Figure 1. Message flow for a full handshake * 表示可选的消息，或者根据上下文在某些情况下会发送的消息。Indicates optional or situation-dependent messages that are not always sent. 注：为了帮助解决管道阻塞的问题，ChangeCipherSpec是一个独立的TLS protocol content type，并不是一个握手消息。\nTLS的完整握手过程，要进行RSA/ECDH/ECDSA等非对称计算，非对称计算是很慢的。关于非对称的性能： 例如在2015年的服务器cpu： Intel(R) Xeon(R) CPU E3-1230 V2 @ 3.30GHz 上， 使用如下命令测试：\nopenssl speed rsa2048 openssl speed ecdsap256 openssl speed ecdhp256 openssl speed aes-128-cbc openssl speed -evp aes-128-cbc 结果如下表：\n算法 性能 性能 RSA-2048 私钥运算 723.7 次/秒 公钥运算 23505.8 次/秒 256 bit ecdsa (nistp256) 签名 8628.4 次/秒 验证 2217.0 次/秒 256 bit ecdh (nistp256) ECDH协商 2807.8 次/秒 aes-128-cbc 加密 121531.39 K/秒 aes-128-cbc 使用aesni硬件加速 加密 683682.13 K/秒 注：非对称的单位是 次/秒，这是由于非对称一般只用于处理一个block， 对称的单位是 K/秒，因为对称一般用于处理大量数据流，所以单位和流量一样。 可以给非对称的 次/秒 乘以 block size ，就可以和对称做比较了。例如rsa-2048，723.7*2048/8/1024=185.2672 K/秒 ， 故 RSA-2048 私钥运算性能 是aes-128-cbc 的 $1.5/1000$。是aesni的 $2.6/10000$。\n如上，性能数据惨不忍睹， 简直不能忍！！！\n有鉴于此，TLS从设计之初，就采用了万能手段\u0026ndash;加cache，有2种cache手段：session id，和session ticket。把握手的结果直接cache起来，绕过握手运算。\n当client和server决定恢复一个之前的session，或复用一个已有的session时(可以不用协商一个新的SecurityParameters)，消息流程如下：\n客户端使用要被恢复的session，发送一个ClientHello，把Session ID包含在其中。server在自己的session cache中，查找客户端发来的Session ID，如果找到，sever把找到的session 状态恢复到当前连接，然后发送一个ServerHello，在ServerHello中把Session ID带回去。然后，client和server都必须ChangeCipherSpec消息，并紧跟着发送Finished消息。这几步完成后，client和server 开始交换应用层数据（如下图所示）。如果server在session cache中没有找到Session ID，那server就生成一个新的session ID在ServerHello里给客户端，并且client和server进行完整的握手。\n流程图如下：\nClient Server ClientHello --------\u0026gt; ServerHello [ChangeCipherSpec] \u0026lt;-------- Finished [ChangeCipherSpec] Finished --------\u0026gt; Application Data \u0026lt;-------\u0026gt; Application Data Figure 2. Message flow for an abbreviated handshake 3. handshake 协议外层结构 # 从消息格式来看，TLS Handshake Protocol 在 TLS Record Protocol 的上层. 这个协议用于协商一个session的安全参数。 Handshake 消息（例如ClientHello，ServerHello等） 被包装进 TLSPlaintext结构里面，传入TLS record层，根据当前session 状态做处理，然后传输。\n如下：\nenum { hello_request(0), client_hello(1), server_hello(2), certificate(11), server_key_exchange (12), certificate_request(13), server_hello_done(14), certificate_verify(15), client_key_exchange(16), finished(20), (255) } HandshakeType; struct { HandshakeType msg_type; /* handshake type */ uint24 length; /* bytes in message */ select (HandshakeType) { case hello_request: HelloRequest; case client_hello: ClientHello; case server_hello: ServerHello; case certificate: Certificate; case server_key_exchange: ServerKeyExchange; case certificate_request: CertificateRequest; case server_hello_done: ServerHelloDone; case certificate_verify: CertificateVerify; case client_key_exchange: ClientKeyExchange; case finished: Finished; case session_ticket: NewSessionTicket; /* NEW */ } body; } Handshake; TLS协议规定，handshake 协议的消息必须按照规定的顺序发，收到不按顺序来的消息，当成fatal error处理。也就是说，TLS协议可以当成状态机来建模编码。\n下面按照消息发送必须遵循的顺序，逐个解释每一条握手消息。\nhandshake协议的外层字段，见这个抓包：\n4. handshake \u0026ndash; ClientHello，ServerHello，HelloRequest # Hello消息有3个：ClientHello, ServerHello，HellloRequest 逐个说明：\n4.1 Client Hello # 当客户端第一次连接到服务器时，第一条message必须发送ClientHello。 另外，rfc里规定，如果客户端和服务器支持重协商，在客户端收到服务器发来的HelloRequest后，也可以回一条ClientHello，在一条已经建立的连接上开始重协商。(重协商是个很少用到的特性。)\n消息结构：\nstruct { uint32 gmt_unix_time; opaque random_bytes[28]; } Random; opaque SessionID\u0026lt;0..32\u0026gt;; uint8 CipherSuite[2]; enum { null(0), (255) } CompressionMethod; struct { ProtocolVersion client_version; Random random; SessionID session_id; CipherSuite cipher_suites\u0026lt;2..2^16-2\u0026gt;; CompressionMethod compression_methods\u0026lt;1..2^8-1\u0026gt;; select (extensions_present) { case false: struct {}; case true: Extension extensions\u0026lt;0..2^16-1\u0026gt;; }; } ClientHello; Random 其中： gmt_unix_time 是 unix epoch时间戳。 random_bytes 是 28字节的，用密码学安全随机数生成器 生成出来的随机数。\n密码学安全的随机数生成，这是个很大的话题，也是一个大陷阱，目前最好的做法就是用 /dev/urandom，或者openssl库的 RAND_bytes()\n历史上，恰好就在SSL的random_bytes这个字段，NetScape浏览器早期版本被爆出过随机数生成器漏洞。 被爆菊的随机数生成器使用 pid + 时间戳 来初始化一个seed，并用MD5(seed)得出结果。 见 http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html， 建议读者检查一下自己的随机数生成器。\nclient_version 客户端支持的最高版本号。 random 客户端生成的random。 ClientHello.session_id 唯一标识一个session，用来做session cache。如果为空，表示不做复用，要求服务器生成新的session。 session_id的来源有：\n之前的协商好的连接的session_id 当前连接的session_id 当前也在使用中的另一条连接的session_id 其中第三种允许不做重新握手，就同时建立多条独立的安全连接。这些独立的连接可能顺序创建，也可以同时创建。一个SessionID当握手协商的Finished消息完成后，就合法可用了。存活直到太旧被移除，或者session 关联的某个连接发生fatal error。SessionID的内容由服务器端生成。\n注：由于SessionID的传输是不加密，不做MAC保护的，服务器不允许把私密信息发在里面，不能允许伪造的SessionID在服务器造成安全问题。（握手过程中的数据，整体是受Finished消息的保护的）\nClientHello.cipher_suites字段，包含了客户端支持的CipherSuite的列表，按照客户端希望的优先级排序，每个CipherSuite有2个字节，每个CipherSuite由：一个密钥交换算法，一个大量数据加密算法(需要制定key length参数)，一个MAC算法，一个PRF 构成。服务器会从客户端发过来的列表中选择一个；如果没有可以接受的选择，就返回一个 handshake failure 的 alert，并关闭连接。如果列表包含服务器不认识，不支持，或者禁用的CipherSuite，服务器必须忽略。 如果SessionID不为空，则cipher_suites里面起码要包含客户端cache的session里面的那个CipherSuite\ncompression_methods，类似地，ClientHello里面包含压缩算法的列表，按照客户端优先级排序。当然，如前介绍，服务器一般禁用TLS的压缩。\ncompression_methods 后面可以跟一组扩展(extensions)， extensions都是可选的，比较有用的扩展如： SNI, session ticket，ALPN，OCSP 等，后文介绍。\n客户端发送了ClientHello后，服务器端必须回复ServerHello消息，回复其他消息都会导致 fatal error 关闭连接。\n4.2 Server Hello # 当收到客户端发来的ClientHello后，正常处理完后，服务器必须回复ServerHello。\n消息结构：\nstruct { ProtocolVersion server_version; Random random; SessionID session_id; CipherSuite cipher_suite; CompressionMethod compression_method; select (extensions_present) { case false: struct {}; case true: Extension extensions\u0026lt;0..2^16-1\u0026gt;; }; } ServerHello; server_version 服务器选择 ClientHello.client_version 和 服务器支持的版本号 中的最小的。 random 服务器生成的random，必须确保和客户端生成的random没有关联。 session_id 服务器为本连接分配的SessionID。如果ClientHello.session_id不为空，服务器会在自己的本地做查找。 如果找到了匹配，并且服务器决定复用找到的session建立连接，服务器应该把ClientHello.session_id同样的 session id填入ServerHello.session_id，这表示恢复了一个session，并且双方会立即发送Finished消息。 否则，回复一个和ClientHello.session_id不同的Serverhello.session_id，来标识新session。服务器可以回复一个空的session_id，来告诉客户端这个session不要cache，不能恢复。 如果一个session 被恢复了，那必须恢复成之前协商的session里面的 CipherSuite。要注意的是，并不要求服务器一定要恢复session， 服务器可以不做恢复。 在实践中，session cache在服务器端要求key-value形式的存储，如果tls服务器不止一台的话，就有一个存储怎么共享的问题，要么存储同步到所有TLS服务器的内存里，要么专门搞服务来支持存储，并使用rpc访问， 无论如何，都是很麻烦的事情，相比之下，后文要介绍的session ticket就简单多了，所以一般优先使用session ticket。\ncipher_suite 服务器选定的一个CipherSuite。如果是恢复的session，那就是session里的CipherSuite。 compression_method 跟上面类似。 extensions 扩展列表。要注意的是，ServerHello.extensions 必须是 ClientHello.extensions的子集。 4.3 Hello Extensions # The extension 的格式是:\nstruct { ExtensionType extension_type; opaque extension_data\u0026lt;0..2^16-1\u0026gt;; } Extension; enum { signature_algorithms(13), (65535) } ExtensionType; 其中:\n\u0026ldquo;extension_type\u0026rdquo; 标识是哪一个扩展类型。\n\u0026ldquo;extension_data\u0026rdquo; 一坨二进制的buffer，扩展的数据体，各个扩展自己做解析。\nextension_type 只能出现一次，ExtensionType之间不指定顺序。\nextensions 可能在新连接创建时被发送，也可能在要求session恢复的时候被发送。所以各个extension都需要规定自己再完整握手和session恢复情况下的行为。 这些情况比较琐碎而微妙，具体案例要具体分析。\n4.4 Hello Request # 服务器任何时候都可以发送 HelloRequest 消息。\nHelloRequest的意思是，客户端应该开始协商过程。客户端应该在方便的时候发送ClientHello。服务器不应该在客户端刚创建好连接后，就发送HelloRequest，此时应该让客户端发送ClientHello。\n客户端收到这个消息后，可以直接忽略这条消息。 服务器发现客户端没有响应HelloRequest后，可以发送fatal error alert。\n消息结构：\nstruct { } HelloRequest; HelloRequest不包含在握手消息的hash计算范围内。\n5. handshake \u0026ndash; Server Certificate # 当服务器确定了CipherSuite后，根据CipherSuite里面的认证算法，如果需要发送证书给客户端，那么就发送 Server Certificate消息给客户端。Server Certificate总是在ServerHello之后立即发送，所以在同一个RTT里。\nServer Certificate里面包含了服务器的证书链。\n消息结构：\nopaque ASN.1Cert\u0026lt;1..2^24-1\u0026gt;; struct { ASN.1Cert certificate_list\u0026lt;0..2^24-1\u0026gt;; } Certificate; certificate_list 证书列表，发送者的证书必须是第一个，后续的每一个证书都必须是前一个的签署证书。根证书可以省略 证书申请的时候，一般会收到好几个证书，有的需要自己按照这个格式来拼接成证书链。\n如果服务器要认证客户端的身份，那么服务器会发送Certificate Request消息，客户端应该也以 这条Server Certificate消息的格式回复。\n服务器发送的证书必须：\n证书类型必须是 X.509v3。除非明确地协商成别的了(比较少见，rfc里提到了例如 OpenPGP格式)。\n服务器证书的公钥，必须和选择的密钥交换算法配套。\n密钥交换+认证算法 配套的证书中公钥类型 RSA / RSA_PSK RSA 公钥；证书中必须允许私钥用于加密 （即如果使用了X509V3规定的key usage扩展， keyEncipherment比特位必须置位） 这种用法没有前向安全性，因此在 TLS 1.3中被废弃了 DHE_RSA / ECDHE_RSA RSA 公钥；证书中必须允许私钥用于签名(即如果使用了X509V3规定的key usage扩展， digitalSignature比特位必须置位)，并且允许server key exchange消息将要使用的签名模式(例如 PKCS1_V1.5 ，OAEP等)和hash算法(例如sha1, sha256等) DHE_DSS DSA 公钥; 历史遗留产物，从来没有被大规模用过，安全性差，废弃状态。证书必须允许私钥用于签名，必须允许server key exchange消息中使用的hash算法。 DH_DSS / DH_RSA Diffie-Hellman 公钥; 要求key usage里面的keyAgreement比特位必须置位。 这种用法没有前向安全性，因此在 TLS 1.3中被废弃了 ECDH_ECDSA / ECDH_RSA 能做 ECDH 用途的公钥；公钥必须使用 客户端支持的ec曲线和点格式。这种用法没有前向安全性，因此在 TLS 1.3中被废弃了 ECDHE_ECDSA ECDSA用途的公钥；证书必须运输私钥用作签名，必须允许server key exchange消息里面要用到的hash算法。公钥必须使用客户端支持的ec曲线和点格式。 \u0026ldquo;server_name\u0026rdquo; 和 \u0026ldquo;trusted_ca_keys\u0026rdquo; 扩展用于引导证书选择。 其中有5种是ECC密钥交换算法：ECDH_ECDSA, ECDHE_ECDSA, ECDH_RSA, ECDHE_RSA, ECDH_anon。 ECC（椭圆曲线）体制相比RSA，由于公钥更小，性能更高，所以在移动互联网环境下越发重要。 以上ECC的5种算法都用ECDH来计算premaster secret， 仅仅是ECDH密钥的生命周期和认证算法不同。 其中只有 ECDHE_ECDSA 和 ECDHE_RSA 是前向安全的。\n如果客户端在ClientHello里提供了 \u0026ldquo;signature_algorithms\u0026rdquo; 扩展，那么服务器提供的所有证书必须用 \u0026ldquo;signature_algoritms\u0026quot;中提供的 hash/signature算法对 之一签署。要注意的是，这意味着，一个包含某种签名算法密钥的证书，可能被另一种签名算法签署（例如，一个RSA公钥可能被一个ECDSA公钥签署）。(这在TLS1.2和TLS1.1中是不一样的，TLS1.1要求所有的算法都相同。)注意这也意味着DH_DSS,DH_RSA,ECDH_ECDSA,和ECDH_RSA 密钥交换不限制签署证书的算法。固定DH证书可能使用\u0026quot;signature_algorithms\u0026quot;扩展列表中的 hash/签名算法对 中的某一个签署。名字 DH_DSS, DH_RSA, ECDH_ECDSA, 和 ECDH_RSA 只是历史原因，这几个名字的后半部分中指定的算法，并不会被使用，即DH_DSS中的DSS并不会被使用，DH_RSA中并不会使用RSA做签名，ECDH_ECDSA并不会使用ECDSA算法。。。 如果服务器有多个证书，就必须从中选择一个，一般根据服务器的外网ip地址，SNI中指定的hostname，服务器配置来做选择。如果服务器只有一个证书，那么要确保这一个证书符合这些条件。 要注意的是，存在一些证书使用了TLS目前不支持的 算法组合。例如，使用 RSASSA-PSS签名公钥的证书（即证书的SubjectPublicKeyInfo字段是id-RSASSA-PSS）。由于TLS没有给这些算法定义对应的签名算法，这些证书不能在TLS中使用。 如果一个CipherSuite指定了新的TLS密钥交换算法，也会指定证书格式和要求的密钥编码方法。\n6. handshake \u0026ndash; Server Key Exchange # 服务器会在 server Certificate 消息之后，立即发送 Server Key Exchange消息。 （如果协商出的CipherSuite不需要做认证，即anonymous negotiation，会在ServerHello之后立即发送Server Key Exchange消息）\n只有在server Certificate 消息没有足够的信息，不能让客户端完成premaster的密钥交换时，服务器才发送 server Key Exchange， 主要是对前向安全的几种密钥协商算法，列表如下：\nDHE_DSS DHE_RSA DH_anon ECDHE_ECDSA ECDHE_RSA ECDH_anon 对下面几种密钥交换方法，发送ServerKeyExchange消息是非法的：\nRSA DH_DSS DH_RSA ECDH_ECDSA ECDH_RSA 需要注意的是，ECDH和ECDSA公钥的数据结构是一样的。所以，CA在签署一个证书的时候，可能要使用 X.509 v3 的 keyUsage 和 extendedKeyUsage 扩展来限定ECC公钥的使用方式。\nServerKeyExchange传递足够的信息给客户端，来让客户端交换premaster secret。一般要传递的是：一个 Diffie-Hellman 公钥，或者一个其他算法(例如RSA)的公钥。\n在TLS实际部署中，我们一般只使用这4种：ECDHE_RSA, DHE_RSA, ECDHE_ECDSA，RSA\n其中RSA密钥协商（也可以叫密钥传输）算法，由于没有前向安全性，在TLS 1.3里面已经被废除了。参见： Confirming Consensus on removing RSA key Transport from TLS\t1.3 消息格式：\nenum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa, ec_diffie_hellman } KeyExchangeAlgorithm; struct { opaque dh_p\u0026lt;1..2^16-1\u0026gt;; opaque dh_g\u0026lt;1..2^16-1\u0026gt;; opaque dh_Ys\u0026lt;1..2^16-1\u0026gt;; } ServerDHParams; /* Ephemeral DH parameters */ dh_p Diffie-Hellman密钥协商计算的大质数模数。 dh_g Diffie-Hellman 的生成元， dh_Ys 服务器的Diffie-Hellman公钥 (g^X mod p). struct { opaque point \u0026lt;1..2^8-1\u0026gt;; } ECPoint; enum { explicit_prime (1), explicit_char2 (2), named_curve (3), reserved(248..255) } ECCurveType; struct { ECCurveType curve_type; select (curve_type) { case named_curve: NamedCurve namedcurve; }; } ECParameters; struct { ECParameters curve_params; ECPoint public; //ECDH的公钥 } ServerECDHParams; struct { select (KeyExchangeAlgorithm) { case dh_anon: ServerDHParams params; case dhe_dss: case dhe_rsa: ServerDHParams params; digitally-signed struct { opaque client_random[32]; opaque server_random[32]; ServerDHParams params; } signed_params; case ec_diffie_hellman: ServerECDHParams params; Signature signed_params; case rsa: case dh_dss: case dh_rsa: struct {} ; /* message is omitted for rsa, dh_dss, and dh_rsa */ /* may be extended, e.g., for ECDH -- see [TLSECC] */ }; } ServerKeyExchange; params 服务器的密钥交换参数。 signed_params 对需要认证的（即非anonymous的）密钥交换，对服务器的密钥交换参数的数字签名。 ECParameters 结构比较麻烦，其中ECCurveType是支持3种曲线类型的，可以自行指定椭圆曲线的多项式系数，基点等参数。但是，我们基本不会用到这种功能，因为一般部署都是使用 NamedCurve，即参数已经预先选定，各种密码学库普遍都支持的一组曲线，其中目前用的最广的是 secp256r1 （还被称为 P256，或 prime256v1）\nNamedCurve 列表中比较重要的曲线(在TLS1.3中，只保留了这几条曲线。)，定义如下：\nenum { ... secp256r1 (23), secp384r1 (24), secp521r1 (25), reserved (0xFE00..0xFEFF), (0xFFFF) } NamedCurve; ECDHE_RSA 密钥交换算法的 SignatureAlgorithm 是 rsa 。 ECDHE_ECDSA 密钥交换算法的 SignatureAlgorithm 是 ecdsa。\n如果客户端提供了 \u0026ldquo;signature_algorithms\u0026rdquo; 扩展， 则签名算法和hash算法必须是列在扩展中的算法。 要注意的是，这个地方可能有不一致，例如客户端可能提供了 DHE_DSS 密钥交换，但是 \u0026ldquo;signature_algorithms\u0026quot;扩展中没有DSA算法，在这类情况下，为了正确地协商，服务器必须确保满足自己选择的CipherSuite满足 \u0026ldquo;signature_algorithms\u0026rdquo; 的限制。这不优雅，但是是为了把对原来的CipherSuite协商的设计的改动减到最小，而做的妥协。\n并且，hash和签名算法，必须和服务器的证书里面的公钥兼容。\n7. handshake \u0026ndash; Certificate Request # TLS规定了一个可选功能：服务器可以认证客户端的身份，这通过服务器要求客户端发送一个证书实现，服务器应该在ServerKeyExchange之后立即发送CertificateRequest消息。\n消息结构：\nenum { rsa_sign(1), dss_sign(2), rsa_fixed_dh(3),dss_fixed_dh(4), rsa_ephemeral_dh_RESERVED(5),dss_ephemeral_dh_RESERVED(6), fortezza_dms_RESERVED(20), ecdsa_sign(64), rsa_fixed_ecdh(65), ecdsa_fixed_ecdh(66), (255) } ClientCertificateType; opaque DistinguishedName\u0026lt;1..2^16-1\u0026gt;; struct { ClientCertificateType certificate_types\u0026lt;1..2^8-1\u0026gt;; SignatureAndHashAlgorithm supported_signature_algorithms\u0026lt;2^16-1\u0026gt;; DistinguishedName certificate_authorities\u0026lt;0..2^16-1\u0026gt;; } CertificateRequest; certificate_types 客户端可以提供的证书类型。 rsa_sign 包含RSA公钥的证书。\ndss_sign 包含DSA公钥的证书。\nrsa_fixed_dh 包含静态DH公钥的证书。\ndss_fixed_dh 包含静态DH公钥的证书。\nsupported_signature_algorithms : 服务器支持的 hash/signature 算法的列表。\ncertificate_authorities : 服务器可以接受的CA(certificate_authorities)的 distinguished names 的列表 DER编码格式.\n这些 distinguished names 可能为root CA或者次级CA指定了想要的 distinguished name ，因此，这个消息可以用来描述已知的root，或者希望的授权空间。 如果 certificate_authorities 列表是空的，那么客户端可以发送任何适当的 ClientCertificateType 类型的证书，如果没有别的限制的话。\ncertificate_types 和 supported_signature_algorithms 字段的交叉选择很复杂。 certificate_types 这个字段从SSLv3时代就定义了，但是一直都没有详细定义，其大多数功能都被 supported_signature_algorithms 代替了。 有如下规则：\n客户端提供的任何证书，必须用一个supported_signature_algorithms 中出现过的 hash/signature 算法对 签名.\n客户端提供的末端证书必须提供一个和 certificate_types 兼容的key。 如果这个key是一个签名key，那必须能和 supported_signature_algorithms 中提供的某个 hash/signature 算法对配合使用。\n由于历史原因，某些客户端证书类型的名字，包含了证书的签名算法，例如，早期版本的TLS中， rsa_fixed_dh 意思是一个被RSA算法签署，并且包含一个固定DH密钥的证书。在TLS1.2中，这个功能被 supported_signature_algorithms 淘汰，并且证书类型不再限制用来签署证书的算法。例如，如果服务器发送了 dss_fixed_dh 证书类型，和 { {sha1, dsa}, {sha1,rsa} } 签名类型，客户端可以回复一个 包含静态DH密钥，用RSA-sha1签署的证书。\n如果协商出来的是匿名CipherSuite，服务器不能要求客户端认证。\n8. handshake \u0026ndash; Server Hello Done # 在 ServerHello和相关消息已经处理结束后，服务器发送ServerHelloDone。在发送ServerHelloDone后，服务器开始等待客户端的响应。\nServerHelloDone消息表示，服务器已经发送完了密钥协商需要的消息，并且客户端可以开始客户端的密钥协商处理了。\n收到ServerHelloDone后，客户端应该确认服务器提供了合法的证书，并且确认服务器的ServerHello消息里面的参数是可以接受的。\n消息格式：\nstruct { } ServerHelloDone; 9. handshake \u0026ndash; Client Certificate # ClientCertificate消息是客户端收到ServerHelloDone后，可以发送的第一条消息。仅当服务器要求了一个证书的情况下，客户端才发送ClientCertificate消息，如果没有可用的合适证书，客户端必须发送一条不包含任何证书的ClientCertificate消息（即 certificate_list 结构长度为0）。\n如果客户端没有发送任何证书，服务器自行决定，可以放弃要求客户端认证，继续握手；或者发送一条 fatal handshake_failure的alert消息，断开连接。并且，如果证书链的某些方面是不能接受的（比如证书没有被可信任的CA签署），服务器可以自行决定，是继续握手（放弃要求客户端认证），或者发送一条fatal的alert。\n客户端证书使用和ServerCertificate相同的结构发送。\nClientCertificate把客户端的证书链发送给服务器。服务器会使用证书链来验证CertificateVerify 消息（如果使用基于签名的客户端认证），或者来计算premaster secret（对于非短暂的 DH）。证书必须和协商出来的CipherSuite的密钥交换算法配套，并和任何协商的扩展配套。\n尤其是：\n证书必须是X.509v3 类型的。 客户端的末级证书的公钥必须和CertificateRequest里列出的证书类型兼容。 客户端证书类型 证书公钥类型 rsa_sign RSA公钥；证书必须允许公钥用于certificateVerify消息中的数字签名和hash算法 dss_sign DSA 公钥；证书必须允许密钥使用CertificateVerify中的hahs函数做签名； ecdsa_sign 可以用作 ECDSA 的公钥；证书必须允许 公钥用 CertificateVerify中的hash函数做签名；公钥必须使用服务器支持的曲线，和点格式； rsa_fixed_dh / dss_fixed_dh Diffie-Hellman 公钥; 必须使用和服务器key相同的参数。 rsa_fixed_ecdh / ecdsa_fixed_ecdh 可以用作 ECDH 的公钥。必须和服务器的公钥使用同样的曲线，同样的点格式 如果 certificate_authorities 列表不是空的，客户端证书链中的某一个证书必须是CA中的某一个签署的。 证书必须使用 服务器可以接受的 hash/signature 算法对。 类似于Server Certificate，有一些证书目前无法在TLS中使用。\n10. handshake \u0026ndash; Client Key Exchange # 客户端必须在客户端的Certificate消息之后，立即发送ClientKeyExchange消息。 或者必须在ServerHelloDone后立即发送ClientKeyExchange消息。\nClientKeyExchange消息中，会设置premaster secret，通过发送 RSA公钥加密premaster secret的密文，或者发送允许双方得出相同的premaster secret的Diffie-Hellman参数。\n当客户端使用短暂的 Diffie-Hellman 密钥对时，ClientKeyExchange包含客户端的 Diffie-Hellman 公钥。如果客户端发送一个包含静态 Diffie-Hellman 指数的证书（比如，在使用固定DH的客户端认证），那么这条消息必须被发送，并且必须为空。\n消息结构： 消息的选择取决于选择的密钥交换算法。\nstruct { select (KeyExchangeAlgorithm) { case rsa: EncryptedPreMasterSecret; case dhe_dss: case dhe_rsa: case dh_dss: case dh_rsa: case dh_anon: ClientDiffieHellmanPublic; case ec_diffie_hellman: ClientECDiffieHellmanPublic; } exchange_keys; } ClientKeyExchange; (1). RSA 加密的 Premaster Secret 消息 # 如果用RSA做密钥协商和认证，客户端生成 48字节的 premaster secret，使用服务器证书里面的公钥加密，然后把密文EncryptedPreMasterSecret发送给服务器，结构定义如下：\nstruct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret; client_version 客户端支持的最新协议版本号，这个字段用来检测中间人版本回退攻击。T random 46 字节的，安全生成的随机值。 struct { public-key-encrypted PreMasterSecret pre_master_secret; } EncryptedPreMasterSecret; pre_master_secret 这个随机值由客户端生成，用于生成master secret。 注：PreMasterSecret里面的 client_version 是 ClientHello.client_version，而不是协商的到的版本号，这个特性用来阻止版本回退攻击。不幸的是，有些不正确的老的代码使用了协商得到的版本号，导致检查client_version字段的时候，和正确的实现无法互通。\n客户端实现必须在PreMasterSecret中发送正确的版本号。如果 ClientHello.client_version 的版本号是 TLS 1.1 或者更高，服务器实现必须如下检查版本号。如果版本号是 TLS 1.0 或者更早，服务器必须检查版本号，但是可以通过配置项关闭检查。\n要注意的是，如果版本号检查失败了，PreMasterSecret 应该像下面描述的那样填充成随机数。\nTLS中的RSA使用的是 PKCS1-V1.5 填充( PKCS1-V1.5也是openssl库RSA的默认填充方式)。Bleichenbacher 在1998年发表了一种针对 PKCS1-V1.5 的选择密文攻击， Klima在2003年发现 PKCS1-V1.5 中 PreMasterSecret 版本号检查的一个侧通道攻击。只要TLS 服务器暴露一条特定的消息是否符合PKCS1-V1.5格式，或暴露PreMasterSecret解密后结构是否合法，或版本号是否合法，就可以用上面2种方法攻击。\nKlima 还提出了完全避免这类攻击的方法：对格式不正确的消息，版本号不符的情况，要做出和完全正确的RSA块一样的响应，要让客户端区分不出这3种情况。 具体地说，要如下：\n生成 46 字节的密码学安全随机值 R 解密消息，获得明文 M 如果 PKCS#1 填充不正确，或者 PreMasterSecret 消息的长度不是48字节，则 pre_master_secret = ClientHello.client_version || R 或者如果 ClientHello.client_version \u0026lt;= TLS 1.0，并且明确禁止了版本号检查，则 pre_master_secret = ClientHello.client_version || M[2..47] 注意：明确地用 ClientHello.client_version 构造 pre_master_secret 时，当客户端在原来的 pre_master_secret 中发送了错误的 客户端版本值时，会产生一个不合法的 master_secret 。\n另一种解决问题的方法是，把版本号不符，当成 PKCS-1 格式错误来对待，并且完全随机填充 premaster secret。\n生成 48 字节的密码学安全随机值 R 解密 PreMasterSecret 恢复出明文 M 如果 PKCS#1 填充不正确，或者消息的长度不是48字节，则 pre_master_secret = R 或者如果 ClientHello.client_version \u0026lt;= TLS 1.0，并且 明确禁止了版本号检查，则 pre_master_secret = M 或者如果 M[0..1] != CleintHello.client_version pre_master_secret = R 或者 pre_master_secret = M 尽管实践中，还没有发现针对这种结构的攻击，Klima 在论文中描述了几种理论上的攻击方式，因此推荐上述的第一种结构。\n在任何情况下，一个 TLS 服务器绝对不能在:1. 处理 RSA 加密的 premaster 消息失败, 2.或者版本号检查失败 时产生alert消息。当遇到这两种情况时，服务器必须用随机生成的 premaster 值继续握手。服务器可以把造成失败的真实原因log下来，用于调查问题，但是必须小心确保不能把这种信息泄漏给攻击者（比如通过时间侧通道，log文件，或者其它通道等泄漏）。\nRSAES-OAEP 加密体制，更能抵抗 Bleichenbacher 发表的攻击，然而，为了和早期的TLS版本最大程度保持兼容，TLS 仍然规定使用 RSAES-PKCS1-v1_5 体制。只要遵守了上面列出的建议，目前还没有 Bleichenbacher 的变化形式能攻破 TLS 。\n实现的时候要注意：公钥加密的数据用 字节数组 \u0026lt;0..2^16-1\u0026gt; 的形式表示。因此，ClientKeyExchange中的 RSA加密的PreMasterSecret 前面有2个字节用来表示长度。这2个字节在使用RSA做密钥协商时，是冗余的，因为此时 EncryptedPreMasterSecret 是 ClientKeyExchange 中的唯一字段，因此可以无歧义地得出 EncryptedPreMasterSecret 的长度。因此更早的 SSLv3 规范没有明确规定 public-key-encrypted 数据的编码格式，因此有一些SSLv3的实现没有包含 长度字段，这些实现直接把 RSA 加密的数据放入了 ClientKeyExchange消息里面。 TLS规范要求 EncryptedPreMasterSecret 字段包含长度字段。因此得出的结果会和一些 SSLv3 的实现不兼容。实现者从 SSLv3 升级到 TLS 时，必须修改自己的实现，以接受并且生成带长度的格式。如果一个实现要同时兼容 SSLv3 和 TLS，那就应该根据协议版本确定自己的行为。\n注意：根据 Boneh 等在2003年USENIX Security Symposium上发表的论文 \u0026ldquo;Remote timing attacks are practical\u0026rdquo;，针对 TLS RSA密钥交换的远程时间侧通道攻击，是实际可行的，起码当客户端和服务器在同一个LAN里时是可行的。因此，使用静态 RSA 密钥的实现，必须使用 RSA blinding，或者Boneh论文中提到的，其他抵抗时间侧通道攻击的技术。\nopenssl中的RSA blinding，参见：http://linux.die.net/man/3/rsa_blinding_on\n(2). 客户端 Diffie-Hellman 公钥 # 这条消息把客户端的 Diffie-Hellman 公钥 ( Yc ) 发送给服务器。\nYc的编码方式由 PublicValueEncoding 决定。\n消息的结构：\nenum { implicit, explicit } PublicValueEncoding; implicit 如果客户端已经发送了一个包含合适的 DH 公钥的证书（即 fixed_dh 客户端认证方式），那么Yc已经隐式包含了，不需要再发送。这种情况下，ClientKeyExchange消息必须发送，并且必须是空的。 explicit 表示Yc需要发送。 struct { select (PublicValueEncoding) { case implicit: struct { }; case explicit: opaque dh_Yc\u0026lt;1..2^16-1\u0026gt;; } dh_public; } ClientDiffieHellmanPublic; dh_Yc 客户端的 Diffie-Hellman 公钥 Yc. (3). 客户端 EC Diffie-Hellman 公钥 # struct { select (PublicValueEncoding) { case implicit: struct { }; case explicit: ECPoint ecdh_Yc; } ecdh_public; } ClientECDiffieHellmanPublic; Diffie-Hellman 推广到椭圆曲线群上，就是 EC Diffie-Hellman ，简称 ECDH，其它的计算，和一般的 DH 计算类似。\nECDH 是目前最重要的密钥协商算法 http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html\n11. handshake \u0026ndash; Cerificate Verify # 当需要做客户端认证时，客户端发送CertificateVerify消息，来证明自己确实拥有客户端证书的私钥。这条消息仅仅在客户端证书有签名能力的情况下发送（就是说，除了含有固定 Diffie-Hellman 参数的证书以外的证书）。CertificateVerify必须紧跟在ClientKeyExchange之后发送。\n消息结构： Structure of this message:\nstruct { digitally-signed struct { opaque handshake_messages[handshake_messages_length]; } } CertificateVerify; 此处， handshake_messages 表示所有发送或者接收的握手消息，从client hello开始，一直到CertificateVerify之前的所有消息，包括handshake消息的type和length字段，这是之前所有握手结构体的拼接。要注意，这要求双方在握手过程中，都得缓存所有消息，或者在握手过程中，用每一种可能的hash算法计算到CeritificateVerify为止的hash值。\nsignature中用的hash和签名算法必须是 CertificateRequest 的 supported_signature_algorithms 中的某一种。另外，hash和签名算法必须和客户端的证书的算法兼容。 RSA公钥可能被用于任何允许的hash函数，只要遵循证书中的限制。\n12. handshake \u0026ndash; Finished # 在 ChangeCipherSpec 消息之后，应该立即发送 Finished 消息，来确认密钥交换和认证过程已经成功了。ChangeCipherSpec 必须在其它握手消息和 Finished 消息之间。\nFinished 消息是第一条用刚刚协商出来的参数保护的消息。接收方必须确认Finished消息的内容是正确的。一旦某一方发送了，并且确认了对端发来的Finished消息，就可以开始在连接上发送和接收应用数据了。\n消息结构：\nstruct { opaque verify_data[verify_data_length]; } Finished; verify_data PRF(master_secret, finished_label,Hash(handshake_messages)) [0..verify_data_length-1]; finished_label 对客户端发的Finished消息来说，固定是字符串 \u0026#34;client finished\u0026#34;. 对服务器发的Finished消息来说，固定是字符串 \u0026#34;server finished\u0026#34;. Hash表示握手消息的hash。hash函数是前文 PRF 的hash 函数。或者 CipherSuite 规定的用于 Finished 计算的hash函数。\n在TLS的之前版本中，verify_data 总是 12 字节。在TLS 1.2中，这取决于CipherSuite。如果CipherSuite没有显式规定 verify_data_length ，就当成12字节处理。将来的CipherSuite可能会规定别的长度，但是不能小于12字节。\nFinished 消息必须跟在 ChangeCipherSpec 消息之后，如果顺序错乱，就是 fatal error.\nhandshake_message 的内容包含从 ClientHello开始，直到 本条Finished之前的所有消息，只包含handshake层的消息体，不包含record层的几个消息头字段。包括CertificateVerify 消息。同时，对客户端和服务器来说，handshake_message 的内容不同， 后发送者必须包含前发送者的 Finished 消息。\n注意：ChangeCipherSpec 消息，alert，和其它的record 类型不是握手消息，不包含在 hash计算中。同时，HelloRequest 消息也不算在内。\n13. handshake \u0026ndash; NewSessionTicket # SessionTicket 定义在 RFC5077 标准里面，2008年发布。\nSessionTicket是一种不需要服务器端状态的，恢复TLS session的方式。 SessionTicket可以用于任何CipherSuite。 TLS 1.0, TLS 1.1, TLS 1.2 都适用。\n在下面这些场景下，尤其有用：\n用户量巨大，session id的方式耗费服务器内存过多 服务器希望长时间缓存session 服务器有多台，不希望服务器间有共享状态 服务器内存不足 客户端在 ClientHello中设置一个 SessionTicket 扩展来标识自己支持 SessionTicket。如果客户端本地没有存之前收到的ticket，就把这个扩展设为空。\n如果服务器希望使用 SessionTicket 机制，服务器把本地的 session 状态存入一个ticket中，ticket会被加密，并被MAC保护，无法篡改，加密和算MAC用的key只有服务器知道。 加密并MAC过的ticket用 NewSessionTicket 消息分发给客户端，NewSessionTicket 消息应该在 ChangeCipherSpec 消息之前，在服务器验证通过客户端的Finished消息之后发送。\nClient Server ClientHello (empty SessionTicket extension)-------\u0026gt; ServerHello (empty SessionTicket extension) Certificate* ServerKeyExchange* CertificateRequest* \u0026lt;-------- ServerHelloDone Certificate* ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------\u0026gt; NewSessionTicket [ChangeCipherSpec] \u0026lt;-------- Finished Application Data \u0026lt;-------\u0026gt; Application Data Figure 1: Message flow for full handshake issuing new session ticket 客户端把收到的ticket和master secret等其它与当前session有关的参数一起，缓存起来。 单客户端希望恢复会话时，就把ticket包含在 ClientHello 的 SessionTicket 扩展中发给服务器。 服务器收到后，解密ticket，算MAC确认ticket没有被篡改过，然后从解密的内容里面，获取session 状态，用来恢复会话。如果服务器成功地验证了ticket，可以在 ServerHello 之后返回一个 NewSessionTicket 消息来更新ticket。\n显然，这种情况下，相比完整握手，可以省掉1个RTT。如下图：\nClient Server ClientHello (SessionTicket extension) --------\u0026gt; ServerHello (empty SessionTicket extension) NewSessionTicket [ChangeCipherSpec] \u0026lt;-------- Finished [ChangeCipherSpec] Finished --------\u0026gt; Application Data \u0026lt;-------\u0026gt; Application Data Figure 2: Message flow for abbreviated handshake using new session ticket 如果服务器不能，或者不想使用客户端发来的ticket，那服务器可以忽略ticket，启动一个完整的握手流程。\n如果服务器此时不希望下发新的ticket，那就可以不回复 SessionTicket 扩展，或者不回复 NewSessionTicket 消息。 此时除了 ClientHello里面的 SessionTicket扩展，就和一般的TLS流程一样了。\n如果服务器拒绝收到的ticket，服务器可能仍然希望在完整的握手之后，下发新的ticket。 此时流程和全新 ticket 生成下发的区别，就是ClientHello的SessionTicket不是空的。\nNewSessionTicket 消息 服务器在握手过程中，发ChangeCipherSpec之前发送NewSessionTicket消息。 如果服务器在ServerHello中包含了一个SessionTicket扩展，那就必须发送NewSessionTicket消息。 如果服务器没有包含SessionTicket扩展，那绝对不能发送NewSessionTicket消息。 如果服务器在包含了SessionTicket扩展之后，不想发送ticket，那可以发送一个长度为0的NewSessionTicket消息。\n在完整握手的情况下，客户端必须在确认服务器的Finished消息正确之后，才能认为NewSessionTicket 里面的ticket合法。\n服务器可以NewSessionTicket消息中更新 ticket。\nticket_lifetime_hint 字段包含一个服务器的提示，提示客户端本ticket应该存多长时间就失效。单位是秒，网络字节序。当时间到期时，客户端应该删掉ticket和关联的状态。客户端也可以提前删除。服务器端也可以提前认为ticket失效。\nstruct { uint32 ticket_lifetime_hint; opaque ticket\u0026lt;0..2^16-1\u0026gt;; } NewSessionTicket; SessionTicket 和 Session ID 之间的关系比较繁琐。感兴趣的自行去看RFC吧。\n对于客户端来说，ticket就是一块二进制buffer，客户端并不管里面的内容。所以ticket具体怎么加密加MAC服务器可以为所欲为，无需顾及客户端的感受。\nRFC5077中推荐了一种ticket的加密保护方法： 服务器使用2个key，一个 aes-128-cbc的key，一个 HMAC-SHA-256 的key。\nticket的格式像这样：\nstruct { opaque key_name[16]; opaque iv[16]; opaque encrypted_state\u0026lt;0..2^16-1\u0026gt;; opaque mac[32]; } ticket; 其中，key_name 用来标识一组key，这样服务器端就可以使用多组key。\n加密过程，首先随机生成IV，然后用 aes-128-cbc 加密 session 的序列化结果， 然后用 HMAC-SHA-256 对 key_name,IV,encrypted_data 的长度（2字节），encrypted_data 计算MAC。 最好把各个字段填入上面ticket结构体。 显然，此处是 Encrypt-then-MAC的方式，是最安全的。\n实际在openssl 中的session，用asn1格式序列化保存了下面这些字段：\ntypedef struct ssl_session_asn1_st { ASN1_INTEGER version; ASN1_INTEGER ssl_version; ASN1_OCTET_STRING cipher; ASN1_OCTET_STRING master_key; ASN1_OCTET_STRING session_id; ASN1_OCTET_STRING session_id_context; ASN1_INTEGER time; ASN1_INTEGER timeout; ASN1_INTEGER verify_result; ASN1_OCTET_STRING tlsext_hostname; ASN1_INTEGER tlsext_tick_lifetime; ASN1_OCTET_STRING tlsext_tick; } SSL_SESSION_ASN1; 6. ChangeCipherSpec 协议 # ChangeCipherSpec用来通知对端，开始启用协商好的Connection State做对称加密，内容只有1个字节。 这个协议是冗余的，在TLS 1.3里面直接被删除了。\nchangeCipherSpec协议抓包： 7. Alert 协议 # 一种返回码机制，简单\nenum { warning(1), fatal(2), (255) } AlertLevel; struct { AlertLevel level; AlertDescription description; } Alert; 其中level是等级，不同等级要求不同的处理。\n其中有一种：close_notify，用来通知对端，我不会再发送更多数据了。这个可以让对端主动close fd，这样可以减少我方tcp timewait状态的socket 量。\nalert协议： 8. application data协议 # application data协议，就是把应用数据直接输入record层，做分段，算MAC，加密，传输。 抓包举例如下：\n8. TLS协议的安全分析 # 安全分析，重中之重，也是大家最关心的。\n安全分析的第一步是建立攻击模型，TLS的攻击模式是：\n攻击者有充足的计算资源 攻击者无法得到私钥，无法得到客户端和服务器内存里面的密钥等保密信息 攻击者可以抓包，修改包，删除包，重放包，篡改包。 这个模型其实就是密码学里面一般假定的攻击模型。\n好了，在这个模型下，TLS的安全性分析如下：\n1. 认证和密钥交换 的安全性 # TLS有三种认证模式：双方认证，服务器认证，无认证。 只要包含了有服务器端认证，就可以免疫 man-in-the-middle 攻击。但是完全匿名的会话是可以被 MITM 攻击的。匿名的服务器不能认证客户端。\n密钥交换的目的，是产生一个只有通信双方知道的共享密钥 pre_master_secret 。pre_master_secret 用来生成 master_secret 。 master_secret 用来生成 Finished 消息，加密key，和MAC key。通过发送正确的Finished消息，双方可以证明自己知道正确的 pre_master_key。\n1. 匿名密钥交换 # 匿名密钥交换是一种历史遗留的不安全方式。 匿名密钥交换缺失认证(Authentication)，所以绝大多数场景下，我们应该禁用这种方式。\n2. RSA 密钥交换和认证 # 当使用RSA的时候，合并了密钥交换 和 服务器端认证。 RSA公钥包含在服务器证书中。要注意的是，一旦服务器证书的RSA私钥泄露，之前用该证书保护的所有流量都会变成可以破解的，即没有前向安全性(Perfect Forward Secrecy)。 需要前向安全性的TLS用户，应该使用 DHE 或者 EC TLS users desiring Perfect Forward Secrecy should use DHE 类的CcipherSuite。这样，如果私钥泄露，只需要更换私钥和证书就行，不会有更大的损失。\nRSA密钥交换和认证的安全性基于，在验证了服务器的证书之后，客户端用服务器的公钥加密一个 pre_master_secret 。成功地解密 pre_master_secret 并产生正确地 Finished 消息之后，就可以确信服务器拥有证书对应的私钥。\n如果使用了客户端认证，通过 CertificateVerify 消息来认证客户端。客户端会签署一个之前所有握手消息的hash值，这些握手消息包括 服务器的证书，ServerHello.random 。其中服务器证书确保客户端签署了和本服务器有关的绑定(即不能重放和别的服务器的握手)，ServerHello.random 确保签名和当前握手流程绑定(即不能重放)。\n3. Diffie-Hellman 密钥交换和认证 # 当使用 DH 密钥交换的时候，服务器:\n或者发送包含固定 DH参数的证书 或者发送一组临时DH参数，并用 ECDSA/RSA/DSA 证书的私钥签署。而且在签署之前，临时DH参数和 hello.random 都参与hash计算，来确保攻击者不能重放老的签名值。 无论如何，客户端都可以通过验证证书，或者验证签名，来确保收到的DH参数确实来自真正的服务器。\n如果客户端有一个包含固定 Diffie-Hellman 参数的证书，则证书包含完成密钥交换所需的参数。要注意的是，这种情况下，客户端和服务器每次都会协商出相同的 DH 结果(就是 pre_master_secret)。 为了尽可能减少 pre_master_secret 存在在内存里面的时间，当不再需要的时候，尽快将其清除，pre_master_secret 应该尽早转换成 master_secret 的形式。 为了进行密钥交换，客户端发送的 Diffie-Hellman 参数必须和服务器发送的兼容。\n如果客户端有一个标准的 DSA 或者 RSA 证书，或者 客户端没有被认证，那么客户端在ClientKeyExchange中发送一组临时参数，或者可选地发送一个CertificateVerify消息来证明自己的身份。\n如果相同的 DH 密钥对，被多次用于握手协商，不管是由于客户端或服务器使用了固定DH密钥的证书，还是服务器在重用 DH 密钥，都必须小心避免 small subgroup 攻击。实现都必须遵循 rfc2785 中的标准。\n最简单避免 small subgroup 攻击的方法是使用一种 DHE CipherSuite，并且每次都握手都生成一个新的 DH 私钥 X。如果选择了一个合适的base（例如2），g^X mod p 的计算可以非常快，因而性能开销会最小化。并且每次都使用一个新的DH密钥，可以提供前向安全性。当使用 DHE 类的CipherSuite时，实现必须每次握手都生成一个全新的DH私钥（即 X ）。\n由于TLS允许服务器提供任意的 DH 群，客户端必须确认服务器提供的DH 群的大小适合本地策略。 客户端必须确认 DH 公共指数有足够的大小。 如果DH群已知的话，客户端做简单比对就行了，因此服务器可以使用一个已知的群，来方便客户端的检查。\n2. 版本回退攻击 # 由于 TLS 历史上出现过多个版本，服务器端实现可能会兼容多个版本的协议，而像 SSL 2.0 这样的版本是有严重安全问题的，因此攻击者可能会尝试诱骗客户端和服务器，来使TLS连接回退到 SSL 2.0这种老版本。\nTLS 对此的解决办法，就是PreMasterSecret里面包含版本号。\n3. 针对握手过程的攻击 # 攻击者可能会尝试影响握手过程，来使双方选择不安全的加密算法。\n对这种攻击的解决办法是，如果握手消息被篡改了，那么在Finished消息里，客户端和服务器都会计算 握手消息的hash，如果攻击者篡改了握手消息，那么双方得出的hash就不一样，这样Finished消息就会验证不过。就会发现攻击。\n4. 针对 Resuming Sessions 的攻击 # 当使用 session resuming的时候，会产生新的 ClientHello.random 和 ServerHello.random ，并和session的 master_secret 一同被hash。只要master_secret没有泄漏，并且PRF中用来生成加密key和MAC key的hash算法是安全的，连接就是安全的，并且独立于前一个连接(被恢复的前一个连接)。\n只有在客户端和服务器都同意的情况下，才会做session resuming。只要有任意一方怀疑 session 泄漏，或者证书过期/被吊销，就可以要求对端做完整的握手。 一个session的生命周期建议定位24小时。由于如果攻击者获得了 master_secret 就可以在session ID过期之前伪装成被泄漏者，所以要加一个生命期限制。 运行在不安全环境的应用程序，不应该把session ID写入持久存储。\n5. 针对应用数据保护的攻击 # master_secret 和 ClientHello.random 及 ServerHello.random 一起做 hash，来生成每个连接唯一的加密key和MAC key（就算是session resuming得到的连接，也是不同的）。\n在CBC和stream cipher的情况下， 发送出去的数据，在发送前用MAC保护，来避免消息重放，避免篡改。 MAC根据 MAC key，序列号，消息长度，消息内容，固定字符串算出。 消息类型字段（content type）是必须的，来确保握手消息，ChangeCipherSpec消息，应用数据消息不会被混淆。 序列号用来确保删除包或者打乱包顺序的攻击无法得逞。 由于序列号是64位的，可以认为不会回绕。 从一方发给对端的消息，不能被插入对端发来的字节流中，这是用于两端使用不同的 MAC key。 类似地，server write key 和 client write key相互独立。因此stream cipher的key只使用了一次，避免了类似问题。\n如果攻击者获取了加密key，那么就可以解密所有的消息。 类似地，泄漏MAC key，会使攻击者可以篡改消息。\nAEAD就简单了。\n6. 显式 IV的安全性 # 如前文所述，TLS 1.0是把前一条消息的最后一个block，当作下一条消息的第一个IV的，这促成了2004年公开的 BEAST 攻击，后来就改成这种显式IV的更安全的方式了。\n7. 加密和MAC组合模式的安全性 # 前文介绍CBC和AEAD时已有分析，此处略过。\n8. DOS 攻击下的安全性 # TLS容易遭受某些 DoS 攻击。例如，攻击者创建很多TCP连接，就可以让服务器忙于做 RSA 解密计算。然而，由于TLS运行在TCP之上，只要操作系统TCP栈的 SYN-ACK里seqnum是随机的，攻击者就无法隐藏自己的ip，这样就可以和一般的TCP连接一样做DOS防御。\n由于TLS运行在TCP上，每个独立的连接都可能遭受一系列DOS攻击。尤其是，攻击者可以伪造RST包，来中断一条TCP+TLS连接。或者伪造部分TLS记录，导致连接阻塞挂起。不过这些攻击都是任何TCP协议都有问题，不是TLS特有的。\n9.Session Ticket 的安全分析 # Ticket必须: 1.有MAC （即 authenticated，不可篡改），2.加密（即保密）。\n下面分析在各种攻击方法下的安全性。\n1. 无效的Session # TLS协议要求当发现错误的时候，把TLS session变为无效。\n这不会影响到ticket的安全性。\n2. 窃取 Tickets # 攻击者或者中间人，可能会窃取到ticket，并且尝试用来和server建立会话。 然而，由于ticket是加密过的，并且攻击者不知道密钥，窃取到的ticket无法使攻击者恢复会话。 TLS服务器必须使用强加密和MAC算法，来保护ticket。\n3. 伪造 Tickets # 一个恶意用户可能会伪造，或者篡改一个ticket，来恢复一个会话，来延长ticket的生命周期，或者假装成另一个用户。\n然而，由于服务器使用了强的校验保护算法，比如带密码的 HMAC-SHA1 ，因此无法得逞。\n4. DoS 攻击 # 推荐ticket 格式中的 key_name 字段帮助服务器有效地拒绝不是自己签发的票据。 因此，一个攻击者可能发送大量的ticket，让服务器忙于验证ticket。 然而，只要服务器使用了高效的加密和MAC算法，就不会有问题。（现实中，加密和MAC算法效率都极高，这根本不是问题）\n5. 加密 Ticket 的key 的管理 # 加密ticket的key的管理，推荐的做法：\nkey 应该用密码学安全的随机数生成器生成，按照RFC4086。 key 和加密算法最少应该是 128 比特安全程度的。 key 除了加密和解密ticket以外，不应该有其他用途。 key 应该定期更换 当ticket格式更换，或者算法更换时，应该更换key 6. Ticket 的有效期 # TLS服务器控制ticket的生命周期。服务器根据配置来决定可以接受的ticket生命周期。 ticket的生命周期可能会长于24小时。TLS客户端可能会接受到一个ticket生命周期的提示，当然，客户端本地的策略最终决定ticket保存多久。\n7. 其他的 Ticket 格式和分发方法 # 如果没使用推荐的ticket格式，那必须小心地分析方案的安全性。尤其是，如果保密数据比如保密密钥传输给了客户端，那必须用加密方式传输，来防止泄露或篡改。\n8. Identity Privacy, Anonymity, and Unlinkability # ticket的加密和加MAC，就保证了敏感信息不会泄露。\n由于在ticket解密之前的TLS握手，无法隐藏客户端的特征，因此中间人可能根据相同的ticket被复用，发现相同的ticket属于相同的用户。TLS对这种情况不提供保证。\n9. TLS扩展: # https://tools.ietf.org/html/rfc6066\n几个比较重要的TLS扩展：\nServer Name Indication (SNI) 由于在SNI提出之前，tls握手过程中没有字段标明客户端希望连接服务器端的哪个域名，这样如果一个服务器端口上有多个域名，服务器就无法给出正确的证书。随着ipv4地址空间紧张，这个问题越发突出。因此提出了SNI。\nTLSEXT_ALPN 上层协议协商，就是在握手过程中，标明TLS里面是什么协议，例如 http2就是 h2\nMaximum Fragment Length Negotiation 主要用于嵌入式环境，需要客户端发送。\nSession Ticket Session Ticket，就是把session cache加密后存入客户端，这样服务器端就不需要任何存储了。\nTLSEXT_SAFE_RENEGOTIATION 重协商\nCertificate Status Request: OCSP ，OCSP 主要是为了减少客户端查询 证书撤销列表（Ceritificate Revoke List)的网络调用，而提出的。\n10. TLS的配套：PKI体系 # 1. X.509 证书 # X.509是PKI的一个标准，其中内容包括：\n公钥证书 证书撤销列表，CRL 证书路径验证算法（CA/证书 链的格式） X.509使用ASN.1语法做序列化/反序列化\nASN1 就是一个数据序列化/反序列化格式，跟 protobuf 差不多，可以算作竞争对手。\nDER 就是用 ASN1 序列化某些数据结构的格式。\nPEM 就是 DER做base64，加上一些其他字段。\n证书链，以一个或多个CA证书开头的证书的列表，其中:\n每一个证书的 Issuer 和下一个证书的 Subject 相同 每一个证书都被下一个证书的私钥签署 最后一个证书是 根证书(\u0026ldquo;root CA\u0026rdquo;)，在TLS握手中不会被发送 证书里面包含公钥，和其它一些字段（比如证书用途，有效期，签发者等等） x509.v3证书的字段： mozilla的ca证书列表 https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/\nhttps://www.apple.com/certificateauthority/ca_program.html 苹果对CA提的要求：\n1.CA必须取得完整的 WebTrust for Certification Authorities audit （WebTrust CA审计：http://www.webtrust.org/） 2.你的root CA证书必须为apple平台的用户提供广泛的商业价值。例如，一个组织内内部使用的证书不能被接受为root证书。 3.你签的证书必须含有可以公开访问的CRL地址。\nWebtrust审计介绍： Webtrust是由世界两大著名注册会计师协会（美国注册会计师协会，AICPA和加拿大注册会计师协会，CICA）制定的安全审计标准，主要对申请对象的系统及业务运作逻辑安全性、保密性等共计七项内容进行近乎严苛的审查和鉴证。只有通过Webtrust国际安全审计认证，才有可能成为全球主流浏览器根信任的证书签发机构。\nhttps://www.geotrust.com/ 的网站上右下角，有个图标: 点开就可以看到 KPMG 对 geotrust 公司的 webtrust 审计报告: https://cert.webtrust.org/SealFile?seal=1567\u0026file=pdf\n2011年 荷兰CA公司DigiNotar颁发假google，Facebook，微软证书被发现，后发现被入侵，导致该公司破产。 http://www.cnbeta.com/articles/154375.htm\nhttps://news.ycombinator.com/item?id=530600 CA公司签署一个证书的成本是0 。 CA公司的主要成本构成：审核 ，验证CSR成本，支持成本，法律成本(保险费用，担保费用)。 要进入各个浏览器的根证书列表，CA公司每年必须过 WebTrust 年度审计，是很大的开销。 一些浏览器厂商还会对植入根证书列表的CA收费。 基础设施开销，CRL 和 OCSP 服务器成本。 验证CSR：就是提交证书申请后，CA要做多项验证，越是高级的证书（比如EV）验证越麻烦。不固定开销，有些要花费很多人力和时间来完成。 法律开销：CA公司得买保险，保险费跑不了。 CA链费用：新开的CA公司要等5-10年，才会被普遍信任，才能广泛进入根证书链。要想加快点，就得给别的大牌CA公司掏钱，买次级证书。\n2.现有PKI体系暴露出的问题 # http://googleonlinesecurity.blogspot.com/2015/03/maintaining-digital-certificate-security.html\nhttps://blog.mozilla.org/security/2015/04/02/distrusting-new-cnnic-certificates/\nhttps://www.dfn-cert.de/dokumente/workshop/2013/FolienSmith.pdf\nhttps://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf\n解决方案：\n1. public key pin # https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning\n2. HSTS # http://www.chromium.org/hsts 收录进chrome的默认HSTS列表：\thttps://hstspreload.appspot.com/\n11. TLS协议历史上出现过的漏洞，密码学常见陷阱 # 1. TLS的漏洞 # 漏洞分析很耗时间，这里总结一些资料，有兴趣的自己看吧。\n虽然TLS的设计已经尽可能的严密，但是随着技术进步的滚滚车轮，历史上还是出现过很多漏洞， 可以参看这个rfc，做了总结：\nSummarizing Known Attacks on Transport Layer Security (TLS) and Datagram TLS (DTLS)\n还有这个文档： The Sorry State Of SSL\nhttp://hyperelliptic.org/internetcrypto/OpenSSLPresentation.pdf\nTLS 协议最近一些年被爆出过的设计缺陷，尤其是在用的最多的 AES-CBC 和 RC4 上。\nAES-CBC 发现了:\npadding oracle 攻击 BEAST 攻击 Lucky 13 攻击 TIME 攻击 POODLE攻击 2013 年, AlFardan发表了对 RC4 的一个攻击分析，展示如何恢复 RC4 传输的连接上的数据。这种恢复攻击利用了RC4的一些已知弱点，例如RC4最初的一些字节的显著统计特征。\n最近几年，TLS的代码实现引起了安全研究者的关注，这导致了新漏洞不断发现。 2014年，OpenSSL库爆出了好几个漏洞，例如 HeartBleed，还有 CVE-2014-6321 ( Microsoft SChannel 的实现漏洞)等.\nTLS的问题:\n• 很多问题是由于TLS使用了一些“史前时代”的密码学算法(- Eric Rescorla) • CBC 和 Mac-Pad-then-Encrypt • RSA-PKCS#1v1.5 的 RSA padding • RC4 的任何使用 • 很蠢的设计：临时 RSA 密钥协商，GOST 类CipherSuite，Snap Start 等 • 可怕的向后兼容要求，导致迟迟不能废弃一些老算法。\nThe Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software\nhttp://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html\nhttps://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf\nWhy Eve and Mallory Love Android An Analysis of Android SSL (In)Security\n2. 密码学常见陷阱 # 先举几个加密协议被破解的例子，给大家助兴：\n人人网使用256比特RSA加密登录密码，3分钟可破 Flickr length extension attack 漏洞 分析whatsapp协议缺陷的一个文章 卫星电话的私有gmr-1/gmr-2加密算法被逆向并破解 http://cryptofails.blogspot.ca/2013/07/cakephp-using-iv-as-key.html http://cryptofails.blogspot.ca/2013/07/saltstack-rsa-e-d-1.html 网上有一些资料，有兴趣自己看吧:\nhttps://www.schneier.com/essays/archives/1998/01/security_pitfalls_in.html https://www.schneier.com/essays/archives/1999/03/cryptography_the_imp.html http://www.lauradhamilton.com/10-cryptography-mistakes-amateurs-make http://www.cryptofails.com/ http://cryptofails.blogspot.ca/ 密码学常见应用错误 http://security.stackexchange.com/questions/2202/lessons-learned-and-misconceptions-regarding-encryption-and-cryptology\n不要自己发明加密算法。Don\u0026rsquo;t roll your own crypto. 不要使用不带MAC的加密 Don\u0026rsquo;t use encryption without message authentication. 在拼接多个字符串做hash之前，要特别小心 Be careful when concatenating multiple strings, before hashing. 要特别小心使用的随机数生成器，确保有足够的熵 Make sure you seed random number generators with enough entropy. 不要重用 nonce 或者。IV Don\u0026rsquo;t reuse nonces or IVs. 加密和MAC不要使用同样的key，非对称加密和签名不要使用相同的key Don\u0026rsquo;t use the same key for both encryption and authentication. Don\u0026rsquo;t use the same key for both encryption and signing. 不要使用ECB模式做对称加密 Don\u0026rsquo;t use a block cipher with ECB for symmetric encryption Kerckhoffs定律，一个密码学系统的安全性必须建立在密码保密的基础上，其他都是公开的。Kerckhoffs\u0026rsquo;s principle: A cryptosystem should be secure even if everything about the system, except the key, is public knowledge 不要把用户产生的密码作为加密的key。Try to avoid using passwords as encryption keys. 在密码学协议中，任何2条消息的密文都不应该一样。In a cryptographic protocol: Make every authenticated message recognisable: no two messages should look the same 不要把相同的key用在通信的2个方向上。Don\u0026rsquo;t use the same key in both directions. 不要使用不安全的key长度。Don\u0026rsquo;t use insecure key lengths. \u0026hellip; 13. 下一代TLS: TLS 1.3 # tls 1.3的草案在 http://tlswg.github.io/tls13-spec/ 相比tls 1.2, 1.3改动巨大，这些改动对加密通信协议的一般设计也有重要启发。\nTLS 1.3 的改动 值得关注的重大改进有：\n0-RTT支持 1-RTT握手支持 改为使用HKDF做密钥拓展 彻底禁止RC4 彻底禁止压缩 彻底禁止aead以外的其他算法 去除aead的显式IV 去除了AEAD的AD中的长度字段 去除ChangeCipherSpec 去除重协商 去除静态RSA和DH密钥协商 移动互联网兴起之后，rtt延迟变得更重要，可以看到，tls 1.3 的各项改进，主要就是针对移动互联网场景的。\nTLS 1.3 去掉了 ChangeCipherSpec ，这样record之上有3个协议：handshake，alert，application data\n1. record层的密码学保护的改动 # 由于只保留了aead，所以不需要MAC key了。\naead的具体参数用法也有调整，前文有。\nKDF 换成了标准的HKDF，有2种 tls_kdf_sha256, tls_kdf_sha384\n2.handshake协议的改动 # 鉴于session ticket如此之好用，简直人见人爱，所以 TLS 1.3 直接把session ticket内置了，并改名叫 PSK\n要注意的是，此 PSK 和 tls 1.2中一个很生僻的psk(见 rfc4279 )并不是一回事。\n综合考虑了 session resuming ，session ticket后， TLS 1.3 提出了3种handshake模式：\nDiffie-Hellman （ 包含 DH 和 ECDH 两种，下文说到 ECDH 的地方，请自行脑补成 \u0026ldquo;ECDH/DH\u0026rdquo;）. A pre-shared symmetric key (PSK) ，预先共享的对称密钥，此处用统一的模型来处理session resuming 和 rfc4279的psk A combination of a symmetric key and Diffie-Hellman ，前两者合体 3.1-RTT 握手 # 首先，TLS 1.2 的握手有2个rtt，第一个rtt是 ClientHello/ServerHello，第二个rtt是ClientKeyExchange/ServerKeyExchange， 之所以KeyExchange要放在第二个rtt，是由于tls1.2要支持多种密钥交换算法，和各种不同参数(比如 DH还是ECDH还是RSA，ECDHE用什么曲线，DH用什么群生成元，用什么模数，等等)，这些算法和参数都依赖第一个rtt去协商出来， TLS1.3大刀阔斧地砍掉了各种自定义DH群，砍掉了ECDH的自定义曲线，砍掉了RSA协商，密钥协商的算法只剩下不多几个，而且其实大家实际应用中基本都用 ECDH P-256，也没啥人用别的，所以干脆让客户端缓存服务器上一次用的是啥协商算法，把 KeyExchange直接和入第一个rtt，客户端在第一个rtt里直接就用缓存的这个算法发KeyExchange的公钥，如果服务器发现客户端发上来的算法不对，那么再告诉正确的，让客户端重试好了。 这样，就引入了 HelloRetryRequest 这个消息。\n这样，基本没有副作用，就可以降到 1-RTT。 这是TLS 1.3 的完整握手。\n显然，如果一个协议只有一种密钥协商算法，比如定死为 ECDH P-256，那一定可以做到 1-RTT\n4. 有副作用的 0-RTT握手 # 0-RTT应该是受Google的QUIC协议的启发， 如果服务器把自己的 ECDH 公钥长期缓存在客户端，那么客户端就可以用缓存里的ECDHE公钥，构造一个电子信封，在第一个RTT里，直接就发送应用层数据了。 这个长期缓存在客户端的ECDH公钥，称为 半静态 ECDH 公钥（ semi-static (EC)DH share ） ECDH公钥通过 ServerConfiguration 消息发送给客户端。\n这个0-rtt优化是有副作用的：\n0-RTT发送的应用数据没有前向安全性。 跨连接可以重放0-RTT里的应用数据（任何服务器端无共享状态的协议，都无法做到跨连接防重放） 如果服务器端 半静态 ECDH公钥对应的私钥泄露了，攻击者就可以伪装成客户端随意篡改数据了。 服务器在 ServerConfiguration 消息里把半静态 ECDH 公钥发送给客户端。 ServerConfiguration 值得关注一下：\nstruct { opaque configuration_id\u0026lt;1..2^16-1\u0026gt;; uint32 expiration_date; NamedGroup group; opaque server_key\u0026lt;1..2^16-1\u0026gt;; EarlyDataType early_data_type; ConfigurationExtension extensions\u0026lt;0..2^16-1\u0026gt;; } ServerConfiguration; 其中的 expiration_date 是本 ServerConfiguration 最后的有效期限。 这个值绝对不允许大于7天。 客户端绝对不允许存储 ServerConfiguration 大于7天，不管服务器怎么填这个值。\n0-RTT 中的应用数据，放在 EarlyDataIndication 中发送，\nTLS 1.3 还特意给 EarlyDataIndication 定义了一种 ContentType : early_handshake （共四种 alert(21), handshake(22), application_data(23), early_handshake(25) ）\n5. Resumption 和 PSK # TLS 1.3 里面，把session resumption/session ticket 恢复出来的key，和 psk (rfc4279)， 统一在一个 handshake PSK 模式下处理。\nPSK CipherSuite可以 把PSK和ECDHE结合起来用，这样是有前向安全性的。 也可以仅仅使用PSK，这样就没有前向安全性。\n6. Key Schedule 过程的改动 # TLS 1.3 中，综合考虑的 session ticket的各种情况后，提出了 ES，SS 两个概念，统一处理密钥协商的各种情况。 在各种handshake模式下，ES和SS的取值来源不同。\nEphemeral Secret (ES) 每个连接新鲜的 ECDHE 协商得出的值。凡是从 ES 得出的值，都是前向安全的（当然，在 PSK only模式下，不是前向安全的）。 Static Secret (SS) 从静态，或者半静态key得出的值。例如psk，或者服务器的半静态 ECDH 公钥。 在各种 handshake 模式下：\nKey Exchange Static Secret (SS) Ephemeral Secret (ES) (EC)DHE (完整握手) Client ephemeral w/ server ephemeral Client ephemeral w/ server ephemeral (EC)DHE (w/ 0-RTT) Client ephemeral w/ server static Client ephemeral w/ server ephemeral PSK Pre-Shared Key Pre-shared key PSK + (EC)DHE Pre-Shared Key Client ephemeral w/ server ephemeral 如上表所示： 完整的 1-RTT握手的时候， SS 和 ES 都是用的 ephemeral key ，这样是一定有前向安全性的。 使用 0-RTT 的握手的时候，使用客户端的 ephemeral key 和 服务器端的半静态 ECDH 公钥生成 SS， 纯 PSK，这种场景完全没有前向安全性，应该避免。 PSK +　ECDHE，这种场景比较有意思，SS是用的Pre-Shared Key，没有前向安全性，ES 用的 ephemeral key，有前向安全性。 可以看到，相比 TLS 1.2 的 session ticket，TLS 1.3 中 的 PSK + ECDHE，是结合了 ES 的，这样就有了前向安全性，相对更安全。\n和 TLS 1.2 不同的是，TLS 1.3的 master_secret 是使用 ES和SS 两个得出的。\nHKDF-Expand-Label(Secret, Label, HashValue, Length) = HKDF-Expand(Secret, Label + \u0026#39;\\0\u0026#39; + HashValue, Length) 1. xSS = HKDF(0, SS, \u0026#34;extractedSS\u0026#34;, L) 2. xES = HKDF(0, ES, \u0026#34;extractedES\u0026#34;, L) 3. master_secret = HKDF(xSS, xES, \u0026#34;master secret\u0026#34;, L) 4. finished_secret = HKDF-Expand-Label(xSS, \u0026#34;finished secret\u0026#34;, handshake_hash, L) Traffic Key Calculation\n加密流量用的key，在 TLS 1.3 里面称为 Traffic Key，由于多引入了一种ContentType，在不同的ContentType下，Traffic Key 并不相同。 如下表：\nRecord Type Secret Label Handshake Hash Early data xSS \u0026ldquo;early data key expansion\u0026rdquo; ClientHello Handshake xES \u0026ldquo;handshake key expansion\u0026rdquo; ClientHello\u0026hellip; ServerKeyShare Application master secret \u0026ldquo;application data key expansion\u0026rdquo; All handshake messages but Finished 要关注的是， Early Data 的 Traffic Key 是用 xSS 算出来的。也就是说，是用 Pre-Shared Key决定的。因此是没有前向安全性的。\n在一个TLS 连接中，究竟是用哪种 handshake 模式，是由 CipherSuite 协商决定的。\n三. TLS协议的代码实现 # TLS的主要实现：\nOpenSSL libressl boringssl(Google) libressl s2n(Amazon) nss(Mozilla) polarssl botan gnutls(gpl) cyassl go.crypto openssl 的 tls 协议实现有 6W 行，libressl 3.68W行， polarssl 1.29 W行， Botan 1.13 W行\nopenssl是其中代码最糟糕的（没有之一）。 openssl提供了的api都太过于底层，api设计的也很费解，而且严重匮乏文档。 请参考 《令人作呕的OpenSSL》\n不幸的是，OpenSSL是用的最广泛的，是事实上的标准。\nboringssl Google’s OpenSSL fork by Adam Langley (@agl__)\nhttps://github.com/sweis/crypto-might-not-suck\n四. TLS协议的部署与优化 # 这个方面网上的文章还是不少的，本文就简略一点。\n全站https时代正在到来!， 移动互联网对人们生活的介入越来越深人，用户越来越多的隐私数据和支付数据通过网络传输，人们的隐私意识安全意识不断提高；运营商流量劫持，强行插入广告越来越引起反感。因此，各互联网大厂都开始切换到https。\n例如，2015年3月百度全站切换到https，百度运维部的介绍文章：《全站 https 时代的号角 : 大型网站的 https 实践系列》。\n不久后淘宝切了全站https，https://www.taobao.com/ http://velocity.oreilly.com.cn/2015/index.php?func=session\u0026id=8\n国外：由Snowden爆料，美国人发现NSA在大范围深度地监听互联网; 还有openssl连续被爆多个严重安全漏洞。之后近2年，各种加密通信协议，软件，项目开始热门，各大厂商开始关注密码协议，做数据加密，信息安全。(openssl资助，pfs被重视，)\nGoogle的性能数据：\n“In January this year (2010), Gmail switched to using HTTPS for everything by default. .. In order to do this we had to deploy no additional machines and no special hardware. On our production frontend machines, SSL accounts for \u0026lt; 1% of the CPU load, \u0026lt; 10 KB of memory per connection, and \u0026lt; 2% of network overhead…\nIf you stop reading now you only need to remember one thing: SSL is not computationally expensive any more.”\n\u0026ndash; Overclocking SSL blog post by Adam Langley (Google https://www.imperialviolet.org/2010/06/25/overclocking-ssl.html )\ngoogle的优化： https://bit.ly/gottls https://www.imperialviolet.org/2010/06/25/overclocking-ssl.html https://istlsfastyet.com/ https://www.ssllabs.com/downloads/SSL_TLS_Deployment_Best_Practices.pdf http://chimera.labs.oreilly.com/books/1230000000545/ch04.html\nbaidu的经验： http://op.baidu.com/2015/04/https-index/\naws的配置 http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-https-load-balancers.html\n可以参考byron之前给出的一个介绍nginx配置的文章 Nginx下配置高性能，高安全性的https TLS服务，本人提供售后咨询服务，哈哈。\nCipherSuite配置(Mozilla的权威配置) https://wiki.mozilla.org/Security/Server_Side_TLS\nhardenedlinux的这个文档：SSL/TLS部署最佳实践v1.4: http://hardenedlinux.org/jekyll/update/2015/07/28/ssl-tls-deployment-1.4.html\n全站切https，值得关注的一个点是cdn切https，如果cdn资源不使用cdn提供商的域名的话，之前会有私钥必须得交给cdn提供商的安全风险，但是幸运的是cloudflare提出了keyless ssl方案，解决了这个问题 https://github.com/cloudflare/keyless，cdn切https应该可以借鉴。\n有时候我们会用wireshark之类的工具抓包，来调试http协议，但是切换到https后，都变成二进制密文了，直接抓包是行不通了，那怎么调试协议呢？ 有个简单的解决办法：可以在浏览器配置 SSLKEYLOGFILE 环境变量，然后在wireshark里查看https的明文数据， 参考 https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format\n五. 更多的加密通信协议case：QUIC，iMessage，TextSecure, otr, ios HomeKit，libsodium # 时间有限，下面有些协议就没有做详细的分析了，读者自己去看吧。\n1. QUIC # QUIC = TCP+TLS+SPDY https://www.chromium.org/quic\n其中的 crypto design文档是本文关注的。\nhttp://network.chinabyte.com/162/13361162.shtml http://blog.chromium.org/2015/04/a-quic-update-on-googles-experimental.html 截止2015.04，从Chrome到Google server的流量的大概50% 是走的QUIC协议，而且还在不断增加。 据说减少了YouTube的30%的卡顿。\nhttps://github.com/devsisters/libquic\nQUIC值得借鉴的地方有：crypto算法选择，0-RTT的实现方法，证书压缩省流量\nQUIC的crypto算法选择： 密钥交换算法只有2种：\n// Key exchange methods const QuicTag kP256 = TAG(\u0026#39;P\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;5\u0026#39;, \u0026#39;6\u0026#39;); // ECDH, Curve P-256 const QuicTag kC255 = TAG(\u0026#39;C\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;5\u0026#39;, \u0026#39;5\u0026#39;); // ECDH, Curve25519 对称加密只使用AEAD，并且只有2种：\n// AEAD algorithms const QuicTag kNULL = TAG(\u0026#39;N\u0026#39;, \u0026#39;U\u0026#39;, \u0026#39;L\u0026#39;, \u0026#39;N\u0026#39;); // null algorithm const QuicTag kAESG = TAG(\u0026#39;A\u0026#39;, \u0026#39;E\u0026#39;, \u0026#39;S\u0026#39;, \u0026#39;G\u0026#39;); // AES128 + GCM-12 const QuicTag kCC12 = TAG(\u0026#39;C\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;); // ChaCha20 + Poly1305 证书类型2种，RSA证书， 和 RSA/ECDSA双证书\n// Proof types (i.e. certificate types) const QuicTag kX509 = TAG(‘X’, ‘5’, ‘0’, ‘9’); // X.509 certificate, all key types const QuicTag kX59R = TAG(‘X’, ‘5’, ‘9’, ‘R’); // X.509 certificate, RSA keys only handshake的结果是为了协商出来下面这些参数：\n// Parameters negotiated by the crypto handshake. struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters { // Initializes the members to 0 or empty values. QuicCryptoNegotiatedParameters(); ~QuicCryptoNegotiatedParameters(); QuicTag key_exchange; QuicTag aead; std::string initial_premaster_secret; std::string forward_secure_premaster_secret; // subkey_secret is used as the PRK input to the HKDF used for key extraction. std::string subkey_secret; CrypterPair initial_crypters; CrypterPair forward_secure_crypters; // Normalized SNI: converted to lower case and trailing \u0026#39;.\u0026#39; removed. std::string sni; std::string client_nonce; std::string server_nonce; // hkdf_input_suffix contains the HKDF input following the label: the // ConnectionId, client hello and server config. This is only populated in the // client because only the client needs to derive the forward secure keys at a // later time from the initial keys. std::string hkdf_input_suffix; // cached_certs contains the cached certificates that a client used when // sending a client hello. std::vector\u0026lt;std::string\u0026gt; cached_certs; // client_key_exchange is used by clients to store the ephemeral KeyExchange // for the connection. scoped_ptr\u0026lt;KeyExchange\u0026gt; client_key_exchange; // channel_id is set by servers to a ChannelID key when the client correctly // proves possession of the corresponding private key. It consists of 32 // bytes of x coordinate, followed by 32 bytes of y coordinate. Both values // are big-endian and the pair is a P-256 public key. std::string channel_id; // Used when generating proof signature when sending server config updates. bool x509_ecdsa_supported; // Used to generate cert chain when sending server config updates. std::string client_common_set_hashes; std::string client_cached_cert_hashes; }; 可以看到：QUIC内置支持sni 而且区分 initial_premaster_secret 和 forward_secure_premaster_secret。\n先这样吧，后续再分析。\n2. apple ios iMessage # iOS Security Guide : https://www.apple.com/business/docs/iOS_Security_Guide.pdf\nApple 的 iMessage系统的密码学安全机制设计，端到端加密，前向安全(PFS)，签名使用ECDSA P-256，非对称加密使用RSA 1280 bit，苹果自己维护一个 用户名\u0026ndash;》公钥 的目录服务。\niMessage在注册时，给每个用户生成一对 RSA-1280 密钥用作非对称加密，一对 NIST P-256 ECDSA 密钥用作签名，2个私钥本地保存，公钥上传给Apple的目录服务器(IDS)。\n当要发送消息的时候，根据接收方的用户名，从IDS里面找到RSA公钥 和 APNS 地址。然后随机生成 128 比特密钥，用 AES-CTR-128 加密要发送的消息，用接收方的 RSA 1280 公钥，使用 OAEP 填充加密 128比特aes密钥。然后拼接 aes密文和rsa密文，对结果使用发送方的 ECDSA 私钥，用sha1算一次数字签名。 然后把aes密文，rsa密文，数字签名拼接起来，发给 APNS 投递给接收方。\n如果要发送大文件，就生成一个key，用 aes-ctr-256 加密文件，并计算一个sha1，然后把key和sha1 放入消息里面发送。\nApple iMessage is a messaging service for iOS devices and Mac computers. iMessage supports text and attachments such as photos, contacts, and locations.Apple does not log messages or attachments, and their contents are protected by end-to-end encryption so no one but the sender and receiver can access them. Apple cannot decrypt the data. \u0026hellip; When a user turns on iMessage on a device, the device generates two pairs of keys for use with the service: an RSA 1280-bit key for encryption and an ECDSA 256-bit key on the NIST P-256 curve for signing. The private keys for both key pairs are saved in the device’s keychain and the public keys are sent to Apple’s directory service (IDS), where they are associated with the user’s phone number or email address, along with the device’s APNs address. \u0026hellip;\nUsers start a new iMessage conversation by entering an address or name. If the user enters a name, the device first utilizes the user’s Contacts app to gather the phone numbers and email addresses associated with that name, then gets the public keys and APNs addresses from the IDS. The user’s outgoing message is individually encrypted for each of the receiver’s devices. The public RSA encryption keys of the receiving devices are retrieved from IDS. For each receiving device, the sending device generates a random 128-bit key and encrypts the message with it using AES in CTR mode. This per-message AES key is encrypted using RSA-OAEP to the public key of the receiving device. The combination of the encrypted message text and the encrypted message key is then hashed with SHA-1, and the hash is signed with ECDSA using the sending device’s private signing key. The resulting messages, one for each receiving device, consist of the encrypted message text, the encrypted message key, and the sender’s digital signature. They are then dispatched to the APNs for delivery. Metadata, such as the timestamp and APNs routing information, is not encrypted. Communication with APNs is encrypted using a forwardsecret TLS channel.\nAPNs can only relay messages up to 4 KB or 16 KB in size, depending on iOS version. If the message text is too long, or if an attachment such as a photo is included, the attachment is encrypted using AES in CTR mode with a randomly generated 256-bit key and uploaded to iCloud. The AES key for the attachment, its URI (Uniform Resource Identifier), and a SHA-1 hash of its encrypted form are then sent to the recipient as the contents of an iMessage, with their confidentiality and integrity protected through normal iMessage encryption,\n3. apple ios HomeKit # iOS Security Guide : https://www.apple.com/business/docs/iOS_Security_Guide.pdf\nApple的HomeKit，是 WWDC2014 上提出的 iot 智能家居开发平台 （iot啊，目前最火的概念啊，各种高大上啊）。 可以看到 HomeKit 作为一个全新的协议， 抛弃了历史遗留算法，直接采用了目前最先进的算法\nHomeKit 密码学安全机制的设计： 使用Ed25519做 公钥签名/验证，使用 SRP(3072 bit) 做来在iOS设备和HomeKit配件之间交换密码并做认证，使用 ChaCha20-Poly1305做对称加密， 使用HKDF-SHA512做密钥拓展。每个session的开始用Station-to-Station 协议做密钥协商和认证， 随后使用Curve25519做密钥协商，生成共享key。\nHomeKit provides a home automation infrastructure that utilizes iCloud and iOS security to protect and synchronize private data without exposing it to Apple.\n\u0026hellip; HomeKit identity and security are based on Ed25519 public-private key pairs. An Ed25519 key pair is generated on the iOS device for each user for HomeKit, which becomes his or her HomeKit identity. It is used to authenticate communication between iOS devices, and between iOS devices and accessories.\n\u0026hellip; Communication with HomeKit accessories HomeKit accessories generate their own Ed25519 key pair for use in communicating with iOS devices. To establish a relationship between an iOS device and a HomeKit accessory, keys are exchanged using Secure Remote Password (3072-bit) protocol, utilizing an 8-digit code provided by the accessory’s manufacturer and entered on the iOS device by the user, and then encrypted using ChaCha20-Poly1305 AEAD with HKDF-SHA-512-derived keys. The accessory’s MFi certification is also verified during setup. When the iOS device and the HomeKit accessory communicate during use, each authenticates the other utilizing the keys exchanged in the above process. Each session is established using the Station-to-Station protocol and is encrypted with HKDF-SHA-512 derived keys based on per-session Curve25519 keys. This applies to both IP-based and Bluetooth Low Energy accessories.\n4. TextSecure # TextSecure是一个端到端im加密通信协议，由WhisperSystem公司设计，目前whatsapp和WhisperSystem公司有合作，看网上资料，2014年11月开始，whatsapp已经开始使用TextSecure协议来做端到端加密(消息来源: https://whispersystems.org/blog/whatsapp/ http://www.wired.com/2014/11/whatsapp-encrypted-messaging/)。\nTextSecure V2 协议： https://github.com/WhisperSystems/TextSecure/wiki/ProtocolV2 https://github.com/trevp/axolotl/wiki https://whispersystems.org/blog/advanced-ratcheting/\nThe TextSecure encrypted messaging protocol 是otr的一个衍生协议，主要有2个不同点: 1.ECDSA代替DSA 2.某些数据结构压缩\n5. otr 协议 # 标准文档见：https://otr.cypherpunks.ca/Protocol-v3-4.0.0.html\nopen kullo协议 https://www.kullo.net/protocol/\nChoice of algorithms Whenever we write about symmetric or asymmetric encryption or signatures, we mean the following algorithms, modes and parameters:\nsymmetric encryption: AES-256 in GCM mode asymmetric encryption: RSA-4096 with OAEP(SHA-512) padding asymmetric signatures: RSA-4096 with PSS(SHA-512) padding\n6. libsodium/NaCL # libsodium/NaCL 值得重点介绍，大力推广 。 新的没有兼容包袱的系统，都值得考虑用 NaCL来代替 openssl。 libsodium是对NaCL的封装，NaCL大有来头，作者 DJB 是密码学领域的权威人物，chacha20，Curve25519 的作者 。 没有历史包袱的项目，强烈建议使用 libsodium/NaCL。\n这篇文章介绍了NaCL和openssl相比的各方面改进 http://cr.yp.to/highspeed/coolnacl-20120725.pdf https://cryptojedi.org/peter/data/tenerife-20130121.pdf http://nacl.cr.yp.to/\n7. Tox.im # 一款实用NaCL的端到端加密im https://github.com/irungentoo/toxcore/blob/master/docs/updates/Crypto.md\n8. CurveCP # CurveCP值得重点介绍， http://curvecp.org/security.html\nCurveCP的安全考量： Confidentiality and integrity server authentication? client authentication? replay attacks? man-in-the-middle attacks? passive forward secrecy? active forward secrecy? against traffic analysis? internet destination, exact timing, and approximate length of each packet that you send.\nAvailability availability, i.e., to make denial-of-service attacks more difficult. Blind amplification Unauthenticated memory consumption CPU consumption\nEfficiency CPU overhead Network overhead without packet loss Latency without packet loss\nDecongestion\n9. tcpcrypt # http://tcpcrypt.org/\n10.noise # https://github.com/trevp/noise/wiki\n11.tcpcrypt # http://tcpcrypt.org/\n12. netflix MSL # http://techblog.netflix.com/2014/10/message-security-layer-modern-take-on.html\nhttp://www.infoq.com/cn/news/2014/11/netflix-safe-communication\n12.Amazon KMS 密钥管理服务 白皮书 # https://d0.awsstatic.com/whitepapers/KMS-Cryptographic-Details.pdf\n值得注意和借鉴的点：\n对称加密算法选择了 AES-GCM-256 数字签名有2种：ECDSA，RSA， ECDSA 的曲线选择了 secp384r1 （P384），hash 算法选择了 SHA384 RSA 选择2048位，签名体制选择 RSASSA-PSS，hash 算法选择了 SHA256 密钥协商，使用ECDH，选择曲线 secp384r1 (P384)，有2种用法 one-pass ECDH. ECDHE 电子信封加密，KMS内置了电子信封。 电子信封就是，你预先知道对方的长期公钥，你有一个消息要发送给对方，所以你生成一个随机的msgKey,然后 ciphertext = Encrypt(msgKey, message), 并且用对方的公钥加密 msgKey： encKey = Encrypt(k, msgKey), 最后把(encKey, ciphertext) 发给对方，这样，只有公钥对应私钥的拥有者才能打开信封。典型应用比如 OpenPGP。\n其中的 one-pass ECDH，大概意思是： 发起方有一对长期使用的签名密钥对，发起方生成一对临时的 ECDH 密钥，用自己的长期签名密钥签署 临时ECDH公钥。对端有一对长期 ECDH 密钥，收到发起方发来的 ECDH 公钥后，验证签名，并且用自己的长期ECDH私钥和收到的公钥协商出共享密钥。 整个过程中，只是用了一对临时ECDH密钥，2对长期密钥。\nECDHE就是比较典型的ECDHE了，和TLS用法一样：双方都持有一对长期使用的签名密钥对，并拥有对方的签名公钥，然后分别生成一对临时ECDH密钥，用自己的签名私钥签署ECDH公钥，把得出的签名和ECDH公钥发给对方， 双方收到对方的ECDH公钥后，验证签名，通过后用对方的ECDH公钥和自己的ECDH私钥协商出共享密钥。DONE。\n白皮书中还举了几个例子，\n六. TLS协议给我们的启发 \u0026ndash; 现代加密通信协议设计 # 在看了这么多的分析和案例之后，我们已经可以归纳出加密通信协议设计的普遍问题，和常见设计决策，\n设计决策点：\n四类基础算法 加密/MAC/签名/密钥交换 如何选择？ 对称加密目前毫无疑问应该直接用aead，最佳选择就是 aes-128-gcm/aes-256-gcm/chacha20-poly1305了 数字签名/验证方案，如果是移动互联网，应该考虑直接放弃 RSA，考虑 P-256 的 ECDSA 公钥证书，或者更进一步的 ed25519 公钥证书。 密钥交换算法，目前最佳选择就是 curve25519，或者 P-256。\n对称加密算法+认证算法，如何选择？或者直接用aead？\n签名算法如何选择？RSA or ECDSA or Ed25519？\n考虑将来的算法调整，要加版本号机制吗？ 建议是加上，起码在密钥协商的步骤，要加上版本号。便于将来更新算法。\nRSA用作密钥交换是一个好的选择吗？考虑PFS 建议直接放弃RSA，RSA服务器端性能比ECDSA更差，签名更大费流量，而且没有前向安全性，给私钥保管带来更大风险。\n自建PKI，是个好的选择吗？crl如何解决？ 自建PKI可以做到更安全，比如简单的客户端内置数字签名公钥。可是当需要紧急吊销一个证书的时候，只能通过紧急发布新版客户端来解决。\n必须用糟糕的openssl吗？or something better？crypto++,botan, nacl/libsodium, polarssl？libsodium: ed25519+curve2519+chacha20+poly1305\n重放攻击如何解决？某种seq？或者nonce如何生成？\n握手过程被中间人篡改的问题怎么解决？\n性能：私钥运算的cpu消耗可以承受吗？加上某种cache？ 要解决私钥运算的高cpu消耗，必然就需要 session ticket/session id 这种cache机制。显然session ticket 更好\n延迟：密钥协商需要几个rtt？最少多少？加上cache后？和tcp对比如何\nTLS的性能(主要指服务器cpu消耗)还有空间可以压榨吗？我能设计一个性能更牛逼的吗？\n七. 附录：密码学基础概念 # 本文已经很长了，基础概念的内容更多，再展开介绍就太长了，下面就列一下点，贴一下参考资料，就先这样,以后再说吧。\n当然，最好的资料是下面列的书。\n1. 块加密算法 block cipher # AES 等\n《AES后分组密码的研究现状 及发展趋势》 http://www.ccf.org.cn/resources/1190201776262/2010/04/15/019026.pdf\naead的介绍（作者是大神） https://www.imperialviolet.org/2015/05/16/aeads.html\n3种组合方式之争 http://www.thoughtcrime.org/blog/the-cryptographic-doom-principle/\nCBC模式+MAC-then-encrypt的padding oracle 攻击, tls POODLE 漏洞 http://drops.wooyun.org/papers/3194 https://defuse.ca/blog/recovering-cbc-mode-iv-chosen-ciphertext.html\n128 bit 和 256 bit key size之争 https://www.schneier.com/blog/archives/2009/07/another_new_aes.html\nnist 对 aes gcm 的技术标准，官方权威文档： http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf\n一个gcm的调用范例 https://github.com/facebook/conceal/blob/master/native/crypto/gcm_util.c\nDES 1天之内破解DES（2008年） http://www.sciengines.com/company/news-a-events/74-des-in-1-day.html\niPhone 5S开始，A7芯片也有了aes硬件指令 (ARMv8 指令集)，有825%的性能提升： http://www.anandtech.com/show/7335/the-iphone-5s-review/4\n2. 流加密算法 stream cipher # RC4，ChaCha20 等\n序列密码发展现状 http://www.ccf.org.cn/resources/1190201776262/2010/04/15/019018.pdf\nrc4 : http://www.rc4nomore.com/\n[RC4加密已不再安全，破解效率极高（含视频）] http://www.freebuf.com/news/72622.html\n3. Hash函数 hash funtion # MD5，sha1，sha256，sha512 , ripemd 160，poly1305 等\nMD5被碰撞: http://natmchugh.blogspot.com/2014/10/how-i-created-two-images-with-same-md5.html\nhttp://blog.avira.com/md5-the-broken-algorithm/\n4. 消息验证码函数 message authentication code # HMAC-sha256，AEAD 等\n为什么要用MAC http://www.happybearsoftware.com/you-are-dangerously-bad-at-cryptography.html\nFlickr的漏洞案例： http://netifera.com/research/flickr_api_signature_forgery.pdf\nhttp://www.ietf.org/rfc/rfc2104.txt\n5. 密钥交换 key exchange # DH，ECDH，RSA，PFS方式的（DHE，ECDHE）等\nhttps://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/\n关于 前向安全性( Perfect Forward Secrecy ) http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html\nhttp://www.cryptopp.com/wiki/Elliptic_Curve_Cryptography\ngoogle对openssl里面的椭圆曲线的优化： http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37376.pdf\nhttp://www.math.brown.edu/~jhs/Presentations/WyomingEllipticCurve.pdf\nripple从nistp256k1曲线迁移到ed25519 https://ripple.com/uncategorized/curves-with-a-twist/\nopenssh 6.5 开始支持 ed25519, curve25519, chacha20-poly1305 http://www.openssh.org/txt/release-6.5\n6. 公钥加密 public-key encryption # RSA，rabin-williams 等\nRSA入门必读（斯坦福，普渡的课件）： http://crypto.stanford.edu/~dabo/courses/cs255_winter07/rsa.ppt https://engineering.purdue.edu/kak/compsec/NewLectures/Lecture12.pdf\nPKCS1 标准，应用RSA必读： https://www.ietf.org/rfc/rfc3447\nRSA 的公钥为什么比AES的key长？ http://crypto.stackexchange.com/questions/8687/security-strength-of-rsa-in-relation-with-the-modulus-size\nhttp://cryptofails.blogspot.ca/2013/07/saltstack-rsa-e-d-1.html\n使用什么padding？ OAEP，为什么不要用PKCS V1.5\nhttp://stackoverflow.com/questions/2991603/pkcs1-v2-0-encryption-is-usually-called-oaep-encryption-where-can-i-confirm-i\nhttp://crypto.stackexchange.com/questions/12688/can-you-explain-bleichenbachers-cca-attack-on-pkcs1-v1-5 http://en.wikipedia.org/wiki/Adaptive_chosen-ciphertext_attack\nPKCS #1 \u0026ndash; #15标准协议官方网站： http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/public-key-cryptography-standards.htm http://arxiv.org/pdf/1207.5446v1.pdf\nblinding 一种实现上的技术，用来解决 timing 侧通道攻击的问题 https://en.wikipedia.org/wiki/Blinding_(cryptography) http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf\nTwenty Years of Attacks on the RSA Cryptosystem: http://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf\n电子信封(digital envelope) http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/what-is-a-digital-envelope.htm\n在openssl的evp接口中有直接支持： https://wiki.openssl.org/index.php/EVP_Asymmetric_Encryption_and_Decryption_of_an_Envelope\n7. 数字签名算法 signature algorithm # RSA，DSA，ECDSA (secp256r1 , ed25519) 等\n三大公钥体制：RSA，DSA，ECDSA RSA目前是主流，占据绝大多数市场份额 DSA已经被废弃 ECDSA是未来的趋势，例如bitcoin就用ECDSA https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet/ https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/\n8. 密码衍生函数 key derivation function # TLS-12-PRF(SHA-256) , bcrypto，scrypto，pbkdf2 等\nhkdf： http://tools.ietf.org/html/rfc5869 https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/\n9. 随机数生成器 random number generators # /dev/urandom 等\n现代密码学实践指南[2015年]\n八. 参考文献： # TLS/SSL 相关RFC及标准 # TLS 1.2 TLS 1.3 draft specification AES GCM for TLS ECC cipher suites for TLS TLS extensions Application-Layer Protocol Negotiation Extension X.509 PKI X.509 PKI and CRLs 美国国家标准局NIST 的密码学标准 NIST SP 800-90A nsa 的 SuiteB 密码学标准 TLS on wikipedia 协议分析文章 # http://www.root.org/talks/TLS_Design20071129_2.pdf 20 Years of SSL/TLS Research An Analysis of the Internet\u0026rsquo;s Security Foundation https://www.slideshare.net/yassl/securing-data-in-transit http://security.stackexchange.com/questions/20803/how-does-ssl-tls-work SSL/TLS in Detail SSL/TLS The Sorry State Of SSL What\u0026rsquo;s the matter with TLS? http://blog.csdn.net/CaesarZou/article/details/9331993 \u0026lt;tools.ietf.org/html/rfc4210\u0026gt;) X.509 PKI and CRLs Layman\u0026rsquo;s Guide to ASN.1 实际部署调优相关 # https://bit.ly/gottls https://istlsfastyet.com/ https://www.imperialviolet.org/ https://letsencrypt.org/ http://chimera.labs.oreilly.com/books/1230000000545/ch04.to/crypto.html RSA Conference 2015 : New Trends In Cryptographic Algorithm Suites Used For TLS Communications 密码学相关 # Stanford Cryptography open course crypto101，一本很棒的开源电子书 现代密码学理论与实践 - 毛文波 现代密码学:原理与协议 - Katz and Lindell \u0026ldquo;Modern Crypto: 15 Years of Advancement in Cryptography\u0026rdquo; \u0026ndash; 2015 defcon 大会Steve Weis 演讲 强烈建议不要看90年代的书，普遍严重过时，比如《应用密码学：协议、算法与C源程序（原书第2版）》 DJBs crypto page DJBs entropy attacks Cryptographic Right Answers http://www.slideshare.net/yassl/securing-data-in-transit Schneier 关于密码学2010年现状的评述 http://security.stackexchange.com/questions/2202/lessons-learned-and-misconceptions-regarding-encryption-and-cryptology http://chargen.matasano.com/chargen/2009/7/22/if-youre-typing-the-letters-a-e-s-into-your-code-youre-doing.html http://kodu.ut.ee/~swen/publications/articles/laur-thesis-binded.pdf https://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/study-on-cryptographic-protocols https://github.com/sweis/crypto-might-not-suck Cryptographic Best Practices in the Post-Snowden Era Crypto War 52 Things People Should Know To Do Cryptography http://bristolcrypto.blogspot.com/ https://www.schneier.com/ https://www.imperialviolet.org/2015/05/16/aeads.html https://crypto.stanford.edu/~dabo/cryptobook/draft_0_2.pdf http://saweis.net/pdfs/weis-modern-crypto-defcon-2015.pdf 相关开源项目 # GoTLS - go语言自己搞的 TLS 协议实现\nOpenSSL - 事实上的标准\nLibreSSL - OpenBSD搞的OpenSSL的分支，代码可读性大大提高\nBoringSSL - Google Security team 维护的OpenSSL分支\nNSS - Mozilla 维护的TLS协议实现\ns2n - Amazon搞的tls协议实现\nMiTLS , TLS Attacks\nNaCL and libsodium\nspiped\n","date":"2015-09-06","externalUrl":null,"permalink":"/post/2015/09/06/tls-protocol-analysis-and-crypto-protocol-design/","section":"Posts","summary":"最近发现密码学很有意思，刚好还和工作有点关系，就研究了一下，本文是其中一部分笔记和一些思考。\n密码学理论艰深，概念繁多，本人知识水平有限，错误难免，如果您发现错误，请务必指出，非常感谢!\n本文禁止转载\n本文目标：\n学习鉴赏TLS协议的设计，透彻理解原理和重点细节 跟进一下密码学应用领域的历史和进展 整理现代加密通信协议设计的一般思路 本文有门槛，读者需要对现代密码学有清晰而系统的理解，建议花精力补足背景知识再读。本文最后的参考文献里有一些很不错的学习资料。\n","title":"TLS协议分析 与 现代加密通信协议设计","type":"post"},{"content":"问题背景 # https://timyang.net/programming/load-average/\n2015.08.13 高可用架构群 Load 编程比赛，Tim 在群征集一段代码使 load average 最高\n多线程写几个 while 循环的方法太 trivial 了，就不提了。\n下面是 byronhe 的两个解：\n1. 究极版之 stap 大法 # vagrant@vagrant-ubuntu-trusty-64:~$ cat loadavg.stap probe kernel.function(\u0026#34;get_avenrun\u0026#34;) { $avenrun[0]= ($1\u0026lt;\u0026lt;11); $avenrun[1]=$avenrun[0]; $avenrun[2]=$avenrun[1]; } vagrant@vagrant-ubuntu-trusty-64:~$ sudo stap -g loadavg.stap $(((1\u0026lt;\u0026lt;(64-11))-1)) 效果如下:\nhttps://github.com/torvalds/linux/blob/master/fs/proc/loadavg.c\nhttps://github.com/torvalds/linux/blob/master/kernel/sched/loadavg.c\n已经到达内核变量可以表示的最大值了，所以是究极版。嘿嘿嘿。\n《用systemtap来修改下linux内核变量的值》 http://blog.yufeng.info/archives/102\n《Install SystemTap in Ubuntu 14.04 》 http://blog.jeffli.me/blog/2014/10/10/install-systemtap-in-ubuntu-14-dot-04/\n2. 用 O_SYNC 制造 uninterruptible 进程 # 利用 O_SYNC 制造同步请求，使得进程进入 D 状态，增大 loadavg\n#!/usr/bin/env python import os import sys def load_add_1(): fd=os.open(\u0026#34;test.txt\u0026#34;,os.O_CREAT|os.O_RDWR|os.O_SYNC, 0644) for i in xrange(10000*100): os.write(fd,\u0026#34; \u0026#34;*100) sys.exit(0) for i in xrange(10): if os.fork() == 0: load_add_1() 《Linux Load Averages: Solving the Mystery》 深度解析 loadavg 的历史根源演变和意义\nhttp://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html\nWhen load averages first appeared in Linux, they reflected CPU demand, as with other operating systems. But later on Linux changed them to include not only runnable tasks, but also tasks in the uninterruptible state (TASK_UNINTERRUPTIBLE or nr_uninterruptible). This state is used by code paths that want to avoid interruptions by signals, which includes tasks blocked on disk I/O and some locks. You may have seen this state before: it shows up as the \u0026ldquo;D\u0026rdquo; state in the output ps and top. The ps(1) man page calls it \u0026ldquo;uninterruptible sleep (usually IO)\u0026rdquo;.\nOn Linux, load averages are (or try to be) \u0026ldquo;system load averages\u0026rdquo;, for the system as a whole, measuring the number of threads that are working and waiting to work (CPU, disk, uninterruptible locks). Put differently, it measures the number of threads that aren\u0026rsquo;t completely idle. Advantage: includes demand for different resources.\n解释： linux 下的 loadavg ，表示 runnable 进程数，加上 uninterruptible 状态的进程数。 uninterruptible 状态出现在进程不希望被信号打断时，比如阻塞在磁盘 IO 上，或者某些锁上。 就是有时候会在 ps 或者 top 中看到的 \u0026ldquo;D\u0026rdquo; 状态。\n因此 linux 下的 load average，不是整个系统对各种资源的需求，不仅包含对 cpu 的需求，也包含了对 磁盘 iops 等资源的需求，是一种广义的负载。\n","date":"2015-08-13","externalUrl":null,"permalink":"/post/2015/08/13/linux-loadavg-competation/","section":"Posts","summary":"问题背景 # https://timyang.net/programming/load-average/\n2015.08.13 高可用架构群 Load 编程比赛，Tim 在群征集一段代码使 load average 最高\n多线程写几个 while 循环的方法太 trivial 了，就不提了。\n下面是 byronhe 的两个解：\n","title":"Linux LoadAvg 编程比赛","type":"post"},{"content":"本文介绍目前现代密码学的最先进技术， 前半部分主要翻译自 《Cryptographic Right Answers》，附上收集的资料，和byron个人的理解。\n密码学理论艰深，概念繁多，本人知识水平有限，错误难免，如果您发现错误，请务必指出，非常感谢!\n下文分类介绍在各种适用场景下，你应该使用的现代密码学算法 # 1. 加密数据 : # 按照优先级，应该选择:\n(1) 首选 NaCl库，或者libsodium库，使用里面的crypto_secretbox()/crypto_secretbox_open() 函数 (2) Chacha20-Poly1305 算法 (3) AES-GCM 算法\n适用场景:当你需要避免把明文数据在网络上传输的时候。\n以上3种算法，都是AEAD类的算法，AEAD是2015年最好的选择。 其中的(2)和(3)在结构上类似：一个流加密模式的算法，配合一个多项式结构的MAC。 (2)是一个流加密算法，配合一个为通用cpu优化的MAC算法， 对密码学库的实现者来说，Poly1305也比GCM更容易安全地实现。 AES-GCM是工业标准(TLS目前主要用的就是AES-GCM)，现代CPU通常都有专门为AES-GCM设计的硬件指令，但是在没有硬件指令支持的CPU上(比如32位的arm)，(3)性能低于(2)。\n此外，应该\n避免AES-CBC(说来话长，后文有解释) 避免AES-CTR 避免64bit块大小的块加密算法\u0026ndash;(说的就是你\u0026ndash;BlowFish) 避免OFB模式 不要使用RC4，RC4已经被攻破 2. 对称密钥长度 : # 选择使用256bit长度的密钥\n适用场景：只要你在使用密码学，你就应该注意对称密钥长度\n请记住：不要把对称加密（如AES）的key长度，和非对称加密(如RSA)的key长度搞混淆了，对称加密的key通常比非对称加密的key短多了。\n下表对比了相同安全程度时，不同算法的密钥长度，单位:bit\nSymmetric ECC DH/DSA/RSA 80 163 1024 112 233 2048 128 283 3072 192 409 7680 256 571 15360 此外，应该\n避免使用巨大key的算法(使用远大于256的key，只能说明使用者没有安全概念) 避免把多个加密算法串联叠加起来使用，这并没有什么卵用 避免128bit以下的key长度(比如，哥们求你别再提DES这种56bit密钥的古董了) 3. 对称签名: # 应该选择 HMAC 类的算法\n适用场景：安全加固一个API，如各种开放API的调用方认证\n如果对一个API，你需要做认证(authenticating)，但是不需要做加密(encrypting)，记得千万不要自己发明算法，你自己发明的MAC算法基本都有安全漏洞，如果不信，请Google一下 \u0026ldquo;长度扩展攻击\u0026rdquo; 长度扩展攻击 Flickr的漏洞案例\n同时，必须要注意的是，要使用一个常数时间字符串对比算法（这个地方和码农的常识完全相反，请务必留意）\n此外，应该\n避免自行设计的“带密码的hash”结构，你的设计基本都是有安全漏洞的 避免HMAC-MD5，避免HMAC-SHA1，使用HMAC-SHA256, HMAC-SHA512等 避免复杂的多项式MAC 避免加密hash值的结构 避免CRC 4. Hashing/HMAC 算法 # 应该选择SHA2类的算法:: SHA-256, SHA-384, SHA-512, SHA-512/256\n优先使用 SHA-512/256，SHA-512/256这个算法把 SHA-512 的512bit输出截短到256bit，避开了length extension 攻击。 同时，目前SHA-2是很安全可靠的，你不需要升级到SHA-3.\n此外，应该\n避免SHA-1 避免MD5 避免MD6 5. 随机ID # 应该使用256 bit的随机值\n一定要使用 /dev/urandom，请认准这个\n此外，应该\n避免用户空间的随机数生成器如:havaged,prngs,egd,等 避免/dev/random 6. 密码处理 # 按照优先级顺序,选择：\nscrypt bcrypt 如果以上2个都没有，那就用PBKDF2 此外，应该\n避免直接SHA-2 避免直接SHA-1 避免直接MD5 7. 非对称加密 # 应该使用NaCl库\n适用场景：当你需要加密消息，发给陌生人，并且对方异步接收消息，做离线解密时。这是一个很窄的应用案例，这种用法有个名字叫电子信封(digital envelope)，典型比如gpg加密文件后发送。\n这条是几条之中最难做正确的，不要使用底层的密码学库，比如OpenSSL或者BouncyCastle。\n你应该停止使用RSA，并且切换到椭圆曲线类体制，原因是：\n对RSA的攻击能力的进步 \u0026mdash; 定义在传统质数域上的乘法运算(应用包括DH,DSA,ElGamal等)，要比椭圆曲线域上的乘法运算快得多。这是由于质数域上数域筛法（number field sieve,NFS）的进展，而在椭圆曲线域上，没有NFS这类算法。 RSA (和DH) 或迫使你考虑“向后兼容性”，而椭圆曲线体制没有这种兼容性包袱。TLS最近的几个安全漏洞，部分愿意也是由于这种向后兼容性，导致已经被破解的陈旧算法存在 RSA在一般场景中，都是直接用公钥做非对称加密，这种用法丧失了前向安全性(Perfect Forward Secrecy)。而椭圆曲线就不提倡，也很难这样使用，这样你就不会害死自己了。 在椭圆曲线体制下，保证正确性和安全性的重任，主要由密码学家承担，密码学家会提供一组曲线参数，在某一性能水平下，针对安全性和性能做优化。这样程序员不容易误用而害死自己。在RSA体制下，正好相反，程序员必须提供参数来保证正确性和安全性，就算是RSA-OAEP这种很好的设计，程序员也必须知道怎么提供参数，这样程序员很容易搞错。 如果你必须使用RSA，一定要使用RSA-OAEP with SHA256，指数使用 65537\n避免 RSA-PKCS1v15 避免 ElGamal 避免 RSA 8. 非对称签名 # 应该使用NaCl，Ed25519，或者RFC6979\n应用场景：如果你在设计一种新的比特币，或者一个给Ruby Gems或者Vagrant imges文件签名的系统，或者数字版权保护系统(DRM)，其中一系列的文件需要离线做认证； 或者你在设计一个加密消息传输层\n上一条的内容在此处全部适用。\n在10+年做付费软件安全评估的工作经历中，我只有屈指可数的几次，遇到使用RSA-PSS的用户，RSA-PSS是一个学术界的推荐算法。\n过去10年，非对称签名最主要的应用场景是比特币，和前向安全的密钥协商（TLS协议里面的ECDHE）。 其中最主要的算法全都是基于椭圆曲线体制的。务必警惕新出现的使用RSA签名的系统，很有可能有问题。\n在过去几年中，业界有一种趋势：放弃传统DSA签名，改为难以误用的确定性签名体制，其中的EdDSA(不要和ECDSA搞混了喂！)和RFC6979是最好的例子。这种趋势的主要是受到2010年索尼PlayStation 3的 ECDSA私钥被破解事件的影响，在这个案例中，索尼公司的码农错误地把一个随机数重复使用来做ECDSA签名，形成了漏洞，使得破解者据此直接把私钥算出来了。确定性签名体制在设计中不再依赖随机数生成器，因此彻底避开此类误用。所以你应该优先使用确定性签名体制。\n避免RSA-PKCS1v15，避免RSA，避免ECDSA，避免DSA 特别要避免常规的DSA和ECDSA 9. Diffie-Hellman 密钥交换 # 应该使用NaCl，Curve25519，或者DH-2048\n适用场景:如果你在设计加密消息传输系统，并且无法使用固定对称密码\n这是很棘手的一条，主要考量如下：\n如果你能使用NaCl库，那就使用NaCl库。你甚至不需要管NaCl是什么。 如果你能使用一个可信赖的第三方库，那就使用Curve25519，这是一条现代的ECDH曲线，有丰富的开源代码，性能经过高度优化，被彻底地安全分析过。并且Curve25519即将进入TLS 1.3版本标准。 但是绝对不要自己实现Curve25519，也绝对不要自己移植Curve25519的C代码 如果你不能使用第三方ECDH库，但是可以使用DH库，那就使用DH-2048，使用1个标准的2048 bit的群。 但是不要使用传统的DH，如果你需要协商DH参数，或者和其他实现互操作 如果你一定要做握手协商，或者和旧软件互操作，那么考虑使用NIST P-256, NIST P-256 有广泛的软件支持。 写死在代码里的DH-2048参数，比NIST P-256更安全。NIST P-256比协商出来的DH更安全。 但是，由于NIST P-256的实现有一些陷阱，所以一定要谨慎选择可信赖的，广泛使用使的第三方库 P-256 可能是NIST曲线中最安全的，不要使用P-224。 DH（密钥协商）算法确实很难用，但是它很重要。\n避免，传统常规的 DH, SRP, J-PAKE 握手和协商 避开任何只使用了块加密算法和srand(time())的密钥协商模式（肯定有漏洞） 10. 网站安全 # 应该使用OpenSSL，或者Google的BoringSSL，或者直接使用 AWS的 ELB\n此处网站安全，指的是让网站支持HTTPS协议。 如果你不能把这个任务交给Amazon的云服务去做，把难题留给Amazon去解决，那么OpenSSL目前仍然是正确选择。\n避免不常见的TLS库，例如polarssl，GnuTLS，MatrixSSL等 11. 客户端-服务器结构的应用程序的安全： # 应该使用TLS\n适用场景：如果你以为自己理解了前面关于公钥加密的介绍。。。\n通常，在你设计了自己的RSA协议之后的1至18个月，你肯定会发现，你犯了某个错误，使你的协议没有任何安全性。 比如Salt Stack，Salt Stack的协议使用了 e=1 的RSA 公钥。。。\n听起来，TLS有下面这些黑历史：\nThe Logjam DH negotiation attack The FREAK export cipher attack The POODLE CBC oracle attack The RC4 fiasco The CRIME compression attack The Lucky13 CBC padding oracle timing attack The BEAST CBC chained IV attack Heartbleed Renegotiation Triple Handshakes Compromised CAs 但是，你仍然应该使用TLS做传输协议，因为：\n这些漏洞中的大部分，仅仅是针对浏览器的，因为他们依赖受害者执行攻击者控制的JavaScript脚本，这些JavaScript脚本生成重复的明文，或特定的明文。\n这些漏洞中的大部分，其影响都可以被减轻，只需要你在代码和配置里面写死 TLS v1.2, ECDHE，和 AES-GCM就行。这听起来很棘手，但是这远远没有你自己设计使用ECDHE和AES-GCM的传输协议棘手。\n在一个自定义的传输协议的场景中，你并不需要依赖CA，你可以用一个自签名证书，嵌入到你的客户端里面。\n不要自己设计加密传输协议，这是极其困难而易错的工程难题\n使用TLS，但是不要使用默认配置\n12. 在线备份 # 应该使用Tarsnap\n#名词解释\n本文的内容比较新，相关中文资料极少，因此文中的名词对读者可能有点陌生，故byron这里介绍一下文中提到的一些名词：\n1. NaCl库: # http://nacl.cr.yp.to/ 是密码学学术权威 Daniel J. Bernstein教授 设计的一个密码学算法库，2008年发开始公布。NaCl的特点是：api简洁而易用，高性能，高安全性，主要用于网络通信，加密，解密，签名等，NaCl提供了构建高层密码学工具的核心功能。\n2. libsodium库: # https://download.libsodium.org/doc/ libsodium是对NaCl库的一个分支，进一步改进接口易用性，和可移植性。\n3. AEAD: # https://www.imperialviolet.org/2014/02/27/tlssymmetriccrypto.html AEAD的概念: 在通常的密码学应用中，Confidentiality (保密) 用加密实现，Message authentication (消息认证) 用MAC实现。这两种算法的配合方式，引发了很多安全漏洞，过去曾经有3种方法：1. Encrypt-and-MAC 2.MAC-then-Encrypt 3.Encrypt-then-MAC ，后来发现，1和2都是有安全问题的，所以，2008年起， 逐渐提出了“用一个算法在内部同时实现cipher+MAC”的idea，称为AEAD(Authenticated encryption with additional data)。 在AEAD这种概念里，cipher+MAC 被 一个AEAD算法替换。 http://en.wikipedia.org/wiki/Authenticated_encryption\n4. ChaCha20-poly1305 # ChaCha20-poly1305是一种AEAD，提出者是Daniel J. Bernstein教授，针对移动互联网优化，目前Google对移动客户端的所有流量都使用ChaCha20-Poly1305\n5. AES-GCM # AES-GCM是一种AEAD，是目前TLS的主力算法，互联网上https流量的大部分依赖使用AES-GCM。\n6. AES-GCM和ChaCha20-Poly1305的性能对比测试结果: # Chip AES-128-GCM speed ChaCha20-Poly1305 speed OMAP 4460 24.1 MB/s 75.3 MB/s Snapdragon S4 Pro 41.5 MB/s 130.9 MB/s Sandy Bridge Xeon (AESNI) 900 MB/s 500 MB/s 7. AES-CBC # 关于AES-CBC，在AES-GCM流行之前，TLS主要依赖AES-CBC，而由于历史原因，TLS在设计之初固定选择了MAC-then-Encrypt结构，AES-CBC和MAC-then-encrypt结合，为选择密文攻击(CCA)创造了便利条件，TLS历史上有多个漏洞都和CBC模式有关：\nThe POODLE CBC oracle attack:参考: 1.POODLE的一个分析 2.openssl的分析 3.乌云的文章 The CRIME compression attack: The Lucky13 CBC padding oracle timing attack: The BEAST CBC chained IV attack: 8. SHA2 # http://en.wikipedia.org/wiki/SHA-2\n9. Curve25519 # http://cr.yp.to/ecdh.html Curve25519 是目前最高水平的 Diffie-Hellman函数，适用于广泛的场景，由Daniel J. Bernstein教授设计。由于NIST P-256的设计过程不透明，有来历不明的参数，被广泛怀疑有后门，所以设计了Curve25519，Curve25519的设计过程完全公开，没有任何来历不明的参数。 部署情况：http://ianix.com/pub/curve25519-deployment.html\n10. Ed25519 # http://ed25519.cr.yp.to/ Ed25519是一个数字签名算法，\n签名和验证的性能都极高， 一个4核2.4GHz 的 Westmere cpu，每秒可以验证 71000 个签名 安全性极高，等价于RSA约3000-bit 签名过程不依赖随机数生成器，不依赖hash函数的防碰撞性，没有时间通道攻击的问题 并且签名很小，只有64字节，公钥也很小，只有32字节。 部署情况：http://ianix.com/pub/ed25519-deployment.html 11. 前向安全性 # 前向安全性( Perfect Forward Secrecy ) http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html 前向安全性指的是，如果攻击者抓取并保存流量，那么将来私钥泄露后，攻击者也无法利用泄露的私钥解密这些流量。\n12. Diffie-Hellman 密钥交换 # http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange 在任何一本密码学教材里面都会重点介绍的\n13. constant time compare # 针对Timing attack，http://en.wikipedia.org/wiki/Timing_attack （这种攻击真是脑洞大开！） 当一个算法的运行时间和输入数据有关的时候，可以根据运行时间这一信息，破解出密钥等。 典型的，比如要验证一个对称签名，如果你用了C库里面的memcmp()，那你就会被timing attack方式攻击。 因此，涉及到密码学数据的memcmp，必须要用运行时间和输入无关的函数，比如OpenSSL库里面的CRYPTO_memcmp()\n","date":"2015-06-05","externalUrl":null,"permalink":"/post/2015/06/05/modern-crypto/","section":"Posts","summary":"本文介绍目前现代密码学的最先进技术， 前半部分主要翻译自 《Cryptographic Right Answers》，附上收集的资料，和byron个人的理解。\n密码学理论艰深，概念繁多，本人知识水平有限，错误难免，如果您发现错误，请务必指出，非常感谢!\n下文分类介绍在各种适用场景下，你应该使用的现代密码学算法 # 1. 加密数据 : # 按照优先级，应该选择:\n(1) 首选 NaCl库，或者libsodium库，使用里面的crypto_secretbox()/crypto_secretbox_open() 函数 (2) Chacha20-Poly1305 算法 (3) AES-GCM 算法\n适用场景:当你需要避免把明文数据在网络上传输的时候。\n以上3种算法，都是AEAD类的算法，AEAD是2015年最好的选择。 其中的(2)和(3)在结构上类似：一个流加密模式的算法，配合一个多项式结构的MAC。 (2)是一个流加密算法，配合一个为通用cpu优化的MAC算法， 对密码学库的实现者来说，Poly1305也比GCM更容易安全地实现。 AES-GCM是工业标准(TLS目前主要用的就是AES-GCM)，现代CPU通常都有专门为AES-GCM设计的硬件指令，但是在没有硬件指令支持的CPU上(比如32位的arm)，(3)性能低于(2)。\n","title":"现代密码学实践指南[2015年]","type":"post"},{"content":"下文以nginx为例，介绍如何部署一个高性能，高安全性的https服务器。\n并附送一个优化出来的openssl编译脚本，可以编译出一个高性能，高安全性的openssl库，您可以直接复制粘贴使用。\n此处直接给出实践指导，后续再写文章解释tls协议的这些原理细节。\nnginx下https配置的优化点，主要有:\nsession ticket session id cache ocsp stapling http KeepAlive ECDHE等ciphersuite优化 openssl 编译优化 一， nginx https的配置 # 先贴一下nginx配置，如下。\n是根据mozilla的权威文档 ,和生成工具(选择 nginx，intermediate ) 生成的配置为基础，加入session ticket等配置的结果\nserver { listen 443 ssl; # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate ssl_certificate /path/to/signed_cert_plus_intermediates; ssl_certificate_key /path/to/private_key; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_ticket_key /etc/nginx/conf.d/tls_session_ticket.key; ssl_session_tickets on; keepalive_timeout 75s; # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits ssl_dhparam /path/to/dhparam.pem; # intermediate configuration. tweak to your needs. ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers \u0026#39;ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA\u0026#39;; ssl_prefer_server_ciphers on; # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=15768000; # OCSP Stapling --- # fetch OCSP records from URL in ssl_certificate and cache them ssl_stapling on; ssl_stapling_verify on; ## verify chain of trust of OCSP response using Root CA and Intermediate certs ssl_trusted_certificate /path/to/signed_cert_plus_intermediates; resolver 114.114.114.114; .... } 二， 其中的几个配置项 # 1. /path/to/signed_cert_plus_intermediates # 是你的证书链，就是ca签署之后发给你的文件，是一串这样的内容\n-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- ... -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- 2. /path/to/private_key # 是你的私钥，一定要保密，最好chown+chmod限制只有http server可以读取！内容大致是：\n-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- 3. /path/to/dhparam.pem # 是dhe密钥协商的参数，生成方法：\nopenssl dhparam -out dhparam.pem 2048 4. /etc/nginx/conf.d/tls_session_ticket.key # 是用来加解密session ticket的密码文件，这个文件的安全等级应该和私钥一样，最好chmod+chown限制只有http server能读取。 生成方法:\nopenssl rand 48 \u0026gt; tls_session_ticket.key 最好定期（比如2天一次）轮换，配置文件里把新的key文件放在最前面，旧的在下面，如下\nssl_session_ticket_key current.key; ssl_session_ticket_key previous.key; 5. ssl_trusted_certificate /path/to/signed_cert_plus_intermediates # ssl_trusted_certificate，是用来验证ocsp响应的各个ca证书+中级证书，和信任的ca根证书列表。当用来验证ocsp响应的时候，应该配置为你的ca根证书+和中级ca证书的列表，此处可以简单和ssl_certificate使用同一个证书列表文件。\n其中当需要使用tls的客户端认证的时候（大多数https server都用不到客户端认证），需要指定信任的ca根证书列表文件, 这个文件在centos里面是/etc/ssl/certs/ca-bundle.trust.crt 这个文件。\n配置完成之后，使用ssllabs的这个在线检查工具https://www.ssllabs.com/ssltest/analyze.html 做检查，里面提出的警告，都需要改正。\n三， openssl编译优化 # 主流的https server，都是依赖openssl来提供tls协议，openssl本身代码复杂，概念繁多，最近对这方面做了研究，下面是一个深入分析过后的最优化编译配置，可以编译出一个高性能，高安全性的openssl版本，您可以直接复制粘贴使用。\nopenssl请使用最新的1.0.2a版本，这个版本有intel针对ecdh p256曲线的优化，编译脚本见本文末尾。\n编译好之后，需要让nginx使用我们编译的这个openssl库，在centos下，可以用LD_LIBRARY_PATH的办法：\nsudo vim /usr/lib/systemd/system/nginx.service 在其中添加一行Environment，如下：\n[Service] ... Environment=LD_LIBRARY_PATH=/usr/local/bin/openssl-1.0.2a_64_build/lib/ ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf ... 其中的路径，请自行修改。\n还需要创建软连接，解决so库版本号的问题，如下\nln -s libcrypto.so.1.0.0 libcrypto.so.10 ln -s libssl.so.1.0.0 libssl.so.10 脚本中包含了一个cloudflare的patch，增加chacha20_poly1305 cipher，使用了这个patch之后，可以使用cloudflare的ciphers配置\nhttps://github.com/byronhe/modern-crypto-toolbox/blob/master/build_openssl.sh\n","date":"2015-05-08","externalUrl":null,"permalink":"/post/2015/05/08/https-config-optimize-in-nginx/","section":"Posts","summary":"下文以nginx为例，介绍如何部署一个高性能，高安全性的https服务器。\n并附送一个优化出来的openssl编译脚本，可以编译出一个高性能，高安全性的openssl库，您可以直接复制粘贴使用。\n此处直接给出实践指导，后续再写文章解释tls协议的这些原理细节。\nnginx下https配置的优化点，主要有:\nsession ticket session id cache ocsp stapling http KeepAlive ECDHE等ciphersuite优化 openssl 编译优化 一， nginx https的配置 # 先贴一下nginx配置，如下。\n是根据mozilla的权威文档 ,和生成工具(选择 nginx，intermediate ) 生成的配置为基础，加入session ticket等配置的结果\n","title":"nginx下配置高性能，高安全性的https TLS服务","type":"post"},{"content":"本文翻译了Colin Percival 于2010年在bsdcan的演讲ppt\n原文标题：Everything you need to know about cryptography in 1 hour\n演讲时间：May 13, 2010\n原文在 https://www.bsdcan.org/2010/schedule/attachments/135_crypto1hr.pdf\n演讲视频在 https://www.youtube.com/watch?v=jzY3m5Kv7Y8 ( 请自行翻墙 )\nColin Percival 是密码学方面的专家，FreeBSD项目的安全长官，Tarsnap在线备份服务的创始人，scrypt密钥衍生算法的作者，致力于改进软件中密码学的应用，向程序员传播密码学的正确使用。\n为什么要 cryptography in 1 hour： # 因为有例如下面这些错误的使用案例：\nGoogle keyczar(时间通道攻击)\nSSL( session renegotiation)\nAmazon AWS 签名方法 (可能碰撞的签名算法)\nFlicker API 签名(hash length-extension) 错误地使用了错误的工具\nIntel Hyper Threading (architectual side channel) 不常用的环境\nWEP,WPA,GSM\u0026hellip;（多种漏洞）\n密码学被攻破通常是由于：\n愚蠢\n使用了错误的工具，或者以错误的方式使用它们\n不常用的环境\n经验智慧：不要亲自写密码学代码！ # 使用SSL做传输 使用GPG保护静止数据 \u0026ldquo;如果你在输入A-E-S到你的代码里，你已经开始犯错\u0026rdquo; \u0026ndash; Tomas Ptacek\n现实：如果你无论如何都要写密码学代码，你最好知道你在干什么\n现实：大多数应用程序只需要众所周知的标准模式的一个子集，而这是很容易保证正确的\n55分钟后，你应该：\n知道在99%的情况下应该怎么做\n知道一些常见的错误在哪里\n知道当你在做一些非标准的事情的时候，你真的需要咨询一个密码学专家\n密码学做保护，并对抗一些攻击，但不能对抗所有攻击： # 贿赂，盗窃，敲诈\n监狱刑罚\n攻击人通常比攻击数据更贵\n攻击人通常比攻击数据更危险\n数据被拷打时不会开记者招待会来控诉\n密码学的目的是强迫美国政府拷打你\n希望他们认为你的信息不是这么重要\n密码学有3个主要目的：加密 Encryption ，*认证 Authentication *，识别 Identification # 加密阻止恶意者读取你的数据 认证阻止恶意者在不被发现的情况下改动你的数据 识别阻止恶意者假装你 有时认证和识别在一个步骤中进行：“这条消息自从我写下它之后没有被篡改”和“我是Colin” 可以替换成“这条消息自从Colin写下它之后没有被改过”\n在大多数case里，你需要把2个，或者更多的密码学组件合起来使用\n概念定义 # plaintext是我们关心的数据 ciphertext是恶意者可以得到的数据 key用于在这两者之间做转换，有时候我们需要多个key 对称密码学就是 ，使用相同的key，来把plaintext转换成ciphertext，和把ciphertext转换成 plaintext\n非对称密码学就是，这两个方向的转换使用不同的key\n理想的密码学组件并不存在，但是如果一个密码学组件被识别出不理想，一般就认为它是被攻破的\nhashing # 一个理想的hash函数H(x)是一个函数映射，把任意长度的输入映射成n-bit的输出， 保证：\n抵抗碰撞\n单向\n抵抗碰撞意味着需要 2^(n/2)的时间来找到2个输入，使得它们的hash相同\n单向意味着给定hash后，需要2^n的时间来找到一个输入，使得它的hash是给定hash\n没有保证任何其他性质\n特别是： 知道H(x)可能允许一个攻击者计算某些y的H(y)\nhashing 实践： # 使用 SHA-256\n考虑切换到SHA-3，在未来5-10年内（2010年算起）\n使用一个hash，如果你可以安全地分布 H(x)，并且想验证不安全地收到的x\u0026rsquo;实际上等于x\n不要使用：MD2 MD4 MD5 SHA-1 RIPEMD\n不要把 FreeBSD-8.0-RELEASE-amd64-disc1.iso和CHECKSUM.SHA256放到同一个FTP 服务器上，还以为自己干了件有用的事情\n不要尝试把一个hash函数当成对称签名用\n对称认证 # 对称认证是通过提供一个消息认证码（MAC，message authentication code）来进行的。\n一个理想的 MAC fk(x)使用一个key，来把任意长度的输入映射到n-bit的输出，确保需要 2^n 的时间，一个攻击者才能在得到任意(x, fx(x))对的情况下，生成任意(y,fk(y))对。 有时MAC被称作随机函数\n与hashing不同，知道fk(x)不能使你对另一个key计算出 fk(y)，\nFlicker的API使用了hashing来认证API请求，他们实际需要的是 MAC\n对称认证 实践 # 使用 HMAC-SHA256\n确保同样的输入，HMAC-SHA256不会生成不同的消息\nAmazon和Flicker都做错了\n不要使用 CBC-MAC\n理论上安全，但是把你的块加密算法暴露给了攻击者\n不要使用 Poly1305\n太新，除非你是Daniel Bernstein(Poly 1305的发明者)，否则你基本不可能做出一个\n安全和正确的实现\n不要 当验证一个签名的时候，通过timing side channel的形式泄露信息\nSide channel 攻击 # 一个side channel 是攻击者除了ciphertext以外可以获取信息的任何方式\n密码系统是以它们的数学设计被定义的，但是side channel 是由于密码系统的人工实现导致的。\n最常见的side channel是timing \u0026ndash; 你花费多长时间来 加密/解密/签名/验证 一个消息\n其他side channel包括：电磁辐射，电力消耗，和微架构特征（例如L1缓存替换，在支持 HyperThreading的Intel CPU上）\nSide channel 攻击 实践 # 咨询一个密码学专家，如果你计划使恶意者可以物理接触到任何密码设备\n咨询一个密码学专家，如果你计划允许恶意者在你允许密码学的物理硬件上运行代码(虚拟化系统)\n咨询一个密码学专家，如果你计划发布一个以新方式泄露信息的CPU\nintel可能做错了 不要写其运行时间泄露信息的代码\nTiming attacks # 避免 依赖于key，或者依赖于plaintext的表查找\n不要有依赖于key，或者依赖于plaintext的分支(if, for, while , foo?bar:baz 等代码指令)\n不要想这种代码：\nfor(int i=0;i\u0026lt;MACLEN;i++){ if(MAC-computed[i] != MAC-received[i]) return MAC_IS_BAD ; return MAC_IS_GOOD; 应该这样写\nfor(int i=0;i\u0026lt;MACLEN;i++){ x |= (MAC-computed[i] - MAC-received[i]); return x?MAC_IS_BAD:MAC_IS_GOOD; Google Keyczar就做错了\n块加密 # 对称加密通常构建在块加密算法的基础之上\n一个理想的块加密算法使用一个key来双向地把n-bit的输入x转换成n-bit的输出Ek(x)，这样当 知道(x,Ek(x))时，对任意的(x\u0026rsquo;,k\u0026rsquo;)!=(x,k)，你没法以大于2^-n的概率猜出(x\u0026rsquo;,Ek\u0026rsquo;(x\u0026rsquo;))\n有时被称为“随机排列”\n通常我们关心的是Ek(x)不透露出 当x!=x\u0026rsquo;时Ek(x\u0026rsquo;)的信息。\n如果一个攻击者可以通过观察一个块加密算法怎么处理不同key，来获取有用信息，该块加密算法 被认为可被 key相关攻击 （key-related attack）攻破。\n块加密 实践： # 使用 AES-256\nAES-256有related key攻击漏洞，但是当你把其它事情做对时，这并不是问题\nAES-128理论上足够强壮，但是块加密算法很难没有side channel地正确实现 ，\n并且key里面的额外bit 在key的一些bit泄露之后是有用的。\n不要使用 blowfish\n想都不要想使用DES\n避免 triple-des(3des)\n不要使用块加密算法\u0026quot;raw\u0026quot;，替代地，在一个已经建立的操作模式中使用它(?)。\n块加密算法操作模式 # 一个块加密算法操作模式告诉你怎么使用一个块加密算法来保护数据流\n在很多case中，plaintext需要被填充到块大小的整数倍，块密码算法操作模式会 告诉你怎么做到这一点\n流行的模式有: ECB ，CBC , CFB , OFB，CTR，IAPM，CCM，EAX，GCM \u0026hellip;\n大多数模式只提供了加密，但也有一些提供了认证\n实践： 使用：CTR模式\n不要使用同时提供加密和认证的模式\n想都不要想使用ECB\n使用一个MAC（例如HMAC-SHA256）来认证你的加密数据\n如果你认为你不需要这么做，咨询一个密码学专家，他会告诉你你为什么是错的。\n验证你的加密数据的真实性，在你揭秘数据之前。\n非对称加密 # 一个非对称加密模式使用一个签名key来把plaintext转换成ciphertext，使用一个认证码来把 ciphertext转化成plaintext或者“错误的签名”\n签名key不能从认证key算出，但是认证key可以从签名key算出\nciphertext通常由 plaintext加一个签名构成\n如果一个攻击者接触到了验证key，并且可以说服你签名任意的plaintext，那就可以认为这个 非对称加密系统被攻破了。\n非对称认证 # 使用 RSASSA-PSS（RSA签名，使用Probabilistic Signature Scheme padding）\n使用 2048-bit的RSA key，使用65537作为公钥的公共幂，和SHA256\n不要使用 PKCS v1.5 padding\n想都不要想 使用不带消息padding的RSA(即RAW)\n可能避免： DSA\n可能避免：椭圆曲线签名模式\n想都不要想使用相同的RSA key既做认证，又做加密\n非对称加密 # 非对称加密类似于非对称签名，除了方向相反，使用公钥把plaintext转成ciphertext，但是要 使用私钥把ciphertext转成plaintext\n一个\n大多数非对称加密模式都限制能加密的消息大小到一个很低的数字以下。\n非对称加密 # 使用 RSAES-OAEP（RSA加密，使用Optimal Asymmeric Encryption Padding）\n使用2048-bit的RSA key，以65537为幂底数，SHA256和MGF1-SHA256\n不要使用 PCKS v1.5 padding\n不要使用不带消息padding的RSA\n生成一个随机key，用这个key使用对称加密加密你的消息，然后使用非对称加密算法加密你的key\n小心避免RSAES-OAEP的timing攻击通道\n密码 # 密码通常被直接用于识别，但是也可以被用于加密或者认证。\n实践：\n尽可能避免密码\n使用一个key继承函数，来尽快把密码转成key。\n使用PBKDF2，如果你跟随流行趋势\n使用scrypt，如果你希望2^8倍地比严肃的攻击者更安全\n想都不要想把用户的密码存储在服务器上。绝对不能，就算它们被加密了也不能。\nSSL # SSL是一个可怕的系统\nSSL的复杂性导致它很难别安全地实现\nSSL给了攻击者很多攻击选项\nSSL要求你决定你想信任什么CA\n你相信中国政府吗？\n不幸的是，SSL是唯一可行的选项\n实践：\n随客户端分发非对称签名key，并且使用这个key开始你的整个密码系统\n使用SSL加密你的网站，Email，和其他公开的标准面向Internet的服务器。\n认真地想想你信任哪些CA。\n奇奇怪怪地部分： # 咨询一个密码学专家，如果\n你的密码学硬件设备，攻击者可以物理接触到。（例如smartcards）\n你想使用最少的电力消耗（例如在手机上）\n你需要处理尽可能大的数据流量（例如 10Gbps的 IPSec 隧道）\n你需要传输尽可能少的bit（例如和一个核潜艇的通信）\n你想忽视我在这个talk中提出的任何建议\nQA\n","date":"2015-04-12","externalUrl":null,"permalink":"/post/2015/04/12/translate-Everything-you-need-to-know-about-cryptgraphy-in-1-hour/","section":"Posts","summary":"本文翻译了Colin Percival 于2010年在bsdcan的演讲ppt\n原文标题：Everything you need to know about cryptography in 1 hour\n演讲时间：May 13, 2010\n原文在 https://www.bsdcan.org/2010/schedule/attachments/135_crypto1hr.pdf\n演讲视频在 https://www.youtube.com/watch?v=jzY3m5Kv7Y8 ( 请自行翻墙 )\nColin Percival 是密码学方面的专家，FreeBSD项目的安全长官，Tarsnap在线备份服务的创始人，scrypt密钥衍生算法的作者，致力于改进软件中密码学的应用，向程序员传播密码学的正确使用。\n","title":"【翻译】密码学一小时必知","type":"post"},{"content":"","date":"2015-04-12","externalUrl":null,"permalink":"/categories/translate/","section":"Categories","summary":"","title":"Translate","type":"categories"},{"content":"一，简介 # jump consistent hash是一种一致性哈希算法, 此算法零内存消耗，均匀分配，快速，并且只有5行代码。\n此算法适合使用在分shard的分布式存储系统中 。\n此算法的作者是 Google 的 John Lamping 和 Eric Veach，论文原文在 http://arxiv.org/ftp/arxiv/papers/1406/1406.2294.pdf\n完整代码：\nint32_t JumpConsistentHash(uint64_t key, int32_t num_buckets) { int64_t b = -1, j = 0; while (j \u0026lt; num_buckets) { b = j; key = key * 2862933555777941757ULL + 1; j = (b + 1) * (double(1LL \u0026lt;\u0026lt; 31) / double((key \u0026gt;\u0026gt; 33) + 1)); } return b; } 输入是一个64位的key，和桶的数量（一般对应服务器的数量），输出是一个桶的编号。\n二，原理解释： # 下面byron根据论文的推导过程，做个翻译：\njump consistent hash的设计目标是：\n平衡性，把对象均匀地分布在所有桶中。 单调性，当桶的数量变化时，只需要把一些对象从旧桶移动到新桶，不需要做其它移动。 jump consistent hash的设计思路是：计算当bucket数量变化时，有哪些输出需要变化。\n让我们循序渐进地思考：\n记 ch(key,num_buckets) 为num_buckets时的hash函数。 当num_buckets=1时，由于只有1个桶，显而易见，对任意k，有ch(k,1)==0。 当num_buckets=2时，为了使hash的结果保持均匀，ch(k,2)的结果应该有占比1/2的结果保持为0，有1/2跳变为1。 由此，一般规律是：num_buckets从n变化到n+1后，ch(k,n+1) 的结果中，应该有占比 n/(n+1) 的结果保持不变，而有 1/(n+1) 跳变为 n+1。 因此，我们可以用一个随机数生成器，来决定每次要不要跳变，并且让这个随机数生成器的状态仅仅依赖于key。就得到下面这个初步代码：\nint ch(int key, int num_buckets) { random.seed(key) ; int b = 0; // This will track ch(key, j +1) . for (int j = 1; j \u0026lt; num_buckets; j ++) { if (random.next() \u0026lt; 1.0/(j+1) ) b = j ; } return b; } 显而易见，这个算法是O(n)的。同时我们可以发现，大多数情况下b=j 是不会执行的，而且随着 j 越来越大，这个概率越来越低。 那么有没有办法根据一个随机数，直接得出下一个跳变的 j ，降低时间复杂度呢？\nok,请把你的大脑切换到概率论模式。\n我们可以把 ch(key,bum_buckets) 看做一个随机变量，\n上述算法，追踪了桶编号的的跳变过程，我们记上一个跳变结果是b，假设下一个结果以一定概率是 j ，那么从b+1到j-1，这中间的多次增加桶都不能跳变。 对于在区间 (b, j) 内的任意整数 i ，j是下一个结果的概率可以记为:\n$$P(j\u003e=i) = P(ch(k,i)=ch(k,b+1))$$其中 $ch(k,i)=ch(k,b+1)$ 意即从 $b+1$ 到 $i$ 的过程中，连续多次增加桶的时候都没有跳变，这个概率也就是连续多次不跳变事件概率的乘积，因此：\n$$ P(j\u003e=i) = P(ch(k,b+1)=ch(k,b+2)) * $$ $$ P(ch(k,b+2)=ch(k,b+3)) * $$ $$ P(ch(k,b+3)=ch(k,b+4)) * $$ $$ ...... * $$ $$ P(ch(k,i-1)=ch(k,i)) $$由于单次不跳变的概率：\n$$P( ch(k,i)=ch(k,i+1) ) = i/(i+1)$$所以连续多次不跳变的概率\n$$P(j\u003e=i) = (b+1)/(b+2) * (b+2)/(b+3) * ... * (i-1)/i$$前后项分子分母相互抵消，得到：\n$$P(j\u003e=i) = (b+1)/i$$意即：$j\u003e=i$ 的概率为 $(b+1)/i$\n此时，我们取一个在 [0,1] 区间均匀分布的随机数 $r$，规定 $r\u003c(b+1)/i$，就有 $j\u003e=i$， 所以有 $i\u003c(b+1)/r$，这样就得到了i的上界是 $(b+1)/r$，由于对任意的i都要有$j\u003e=i$，所以 $j=floor( (b+1)/r )$，这样我们用一个随机数 $r$ 得到了 $j$ 。\n因此，代码可以改进为：\nint ch(int key, int num_buckets) { random. seed(key) ; int b = -1; // bucket number before the previous jump int j = 0; // bucket number before the current jump while(j\u0026lt;num_buckets){ b=j; double r=random.next(); // 0\u0026lt;r\u0026lt;1.0 j = floor( (b+1) /r); } return b; } 这个算法的时间复杂度，可以假设每次r都取0.5，则可以认为每次 $j=2*j$，因此时间复杂度为 $O(log(n))$ 。\n此处需要一个均匀的伪随机数生成器，论文中使用了一个64位的线性同余随机数生成器。\n需要指出的是：不像割环法，jump consistent hash不需要对 key 做 hash ，这是由于 jump consistent hash 使用内置的伪随机数生成器，来对每一次 key 做再 hash，（byron的理解：所以结果分布的均匀性与输入key的分布无关，由伪随机数生成器的均匀性保证）。\n三，各项指标对比分析 # consistent hash的概念出自David Karger的论文，经典并且应用广泛的割环法即出自这篇论文：http://www.ra.ethz.ch/cdstore/www8/data/2181/pdf/pd1.pdf\nKarger提出2种实现:\n\u0026ldquo;version A\u0026rdquo;，用 std::map\u0026lt;uint64_t, int32_t\u0026gt;表示key的hash 到桶id的映射。 \u0026ldquo;version B\u0026rdquo;，用 vector\u0026lt;pair\u0026lt;uint64_t, int32_t\u0026gt; \u0026gt;存储，vector事先排好序，用二分查找。 这两种实现的查找时间复杂度也都是O(log(n))\njump consistent hash的论文中，用jump consistent hash和Karger的割环算法做了对比，结果如下：\n1. key分布的均匀性 # 直接从论文中摘录如下表格：\n从标准差(Standard Error)这一列可见，jump consistent hash的均匀性要胜过割环法。\n并且显而易见，jump consistent hash，当 扩/缩容 时，跳变key数量已经是理论最少值 1/n。\n2. 执行耗时 # 下面是论文中的执行耗时对比图，其中k=1000。\n3. 内存占用对比 # 显而易见，请自行脑补\n4. 初始化耗时对比 # 显而易见，请自行脑补\n四，相关链接 # jump consistent hash 适合节点数只增加的业务场景，比如存储系统分 shard。\n如果节点数会随机减少，比如 web caching 场景下服务器节点随机故障。\n假设节点 i ( 0 \u0026lt;= i \u0026lt; num_buckets ) 故障，此时如果简单地把 num_buckets 减 1，就会导致编号在 i 之后的节点上的数据全部错位到前一个节点。 因此 jump consistent hash 不适合节点随机故障的场景。\n在 Hacker News上面的讨论：https://news.ycombinator.com/item?id=8136408\n这个算法最早在Google的guava库里面开源：https://github.com/google/guava/blob/master/guava/src/com/google/common/hash/Hashing.java#L392\n","date":"2015-03-13","externalUrl":null,"permalink":"/post/2015/03/13/jump_consistent_hash/","section":"Posts","summary":"一，简介 # jump consistent hash是一种一致性哈希算法, 此算法零内存消耗，均匀分配，快速，并且只有5行代码。\n此算法适合使用在分shard的分布式存储系统中 。\n此算法的作者是 Google 的 John Lamping 和 Eric Veach，论文原文在 http://arxiv.org/ftp/arxiv/papers/1406/1406.2294.pdf\n完整代码：\nint32_t JumpConsistentHash(uint64_t key, int32_t num_buckets) { int64_t b = -1, j = 0; while (j \u003c num_buckets) { b = j; key = key * 2862933555777941757ULL + 1; j = (b + 1) * (double(1LL \u003c\u003c 31) / double((key \u003e\u003e 33) + 1)); } return b; } 输入是一个64位的key，和桶的数量（一般对应服务器的数量），输出是一个桶的编号。\n","title":"【翻译/介绍】jump consistent hash 零内存消耗，均匀，快速，简洁，来自Google的一致性哈希算法","type":"post"},{"content":"本文是关于SSL/TLS的 CipherSuite 的信息摘录，翻译。如有疑问，欢迎指出。\n一，CipherSuite的概念 # CipherSuite 这个名词目前没看到有好的中文翻译，个人觉得翻译成“加密算法套件”比较合适。Cipher泛指是密码学的加密算法，例如 aes, rsa, ecdh 等。 tls是由各类基础算法，作为原语组合而成。 一个CipherSuite是4个算法的组合：\n1个authentication (认证)算法 1个encryption(加密)算法 1个message authentication code (消息认证码 简称MAC)算法 1 个key exchange(密钥交换)算法 二，CipherSuite的注册管理 # TLS Cipher Suite 在 iana 集中注册，每一个CipherSuite分配有 一个2字节的数字用来标识 ： 可以在 iana的网页查看： https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 例如:\n0x00,0x2F\tTLS_RSA_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x30\tTLS_DH_DSS_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x31\tTLS_DH_RSA_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x32\tTLS_DHE_DSS_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x33\tTLS_DHE_RSA_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x34\tTLS_DH_anon_WITH_AES_128_CBC_SHA\tY\t[RFC5246] 0x00,0x35\tTLS_RSA_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x36\tTLS_DH_DSS_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x37\tTLS_DH_RSA_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x38\tTLS_DHE_DSS_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x39\tTLS_DHE_RSA_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x3A\tTLS_DH_anon_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 0x00,0x3B\tTLS_RSA_WITH_NULL_SHA256\tY\t[RFC5246] 0x00,0x3C\tTLS_RSA_WITH_AES_128_CBC_SHA256\tY\t[RFC5246] 0x00,0x3D\tTLS_RSA_WITH_AES_256_CBC_SHA256\tY\t[RFC5246] 0x00,0x3E\tTLS_DH_DSS_WITH_AES_128_CBC_SHA256\tY\t[RFC5246] 0x00,0x3F\tTLS_DH_RSA_WITH_AES_128_CBC_SHA256\tY\t[RFC5246] 例如其中的：\n0x00,0x37\tTLS_DH_RSA_WITH_AES_256_CBC_SHA\tY\t[RFC5246] 这一条， 0x00,0x37 这个 value 就是 TLS_DH_RSA_WITH_AES_256_CBC_SHA 这个 Cipher Suite的数字，rfc5246 定义了这个CipherSuite的具体实现。\n三，CipherSuite在现实环境的应用 # tls/ssl 一共出过 5个版本：ssl2/ssl3/tls1.0/tls1.1/tls1.2 ，ssl2/ssl3这两个版本漏洞太多，请务必禁用。\ntls1.2，当前(2015年)最新的tls协议，定义在：rfc5246\ntls协议的实现有多种，如openssl, gnutls, nss, libressl, cyassl, polarssl, botan等等。\nopenssl的代码算是其中最混乱的，但是也是最久经考验的。 ( 请参见此打脸文： http://blog.csdn.net/dog250/article/details/24552307 )\n个人觉得polarssl和botan的架构最清晰，代码风格清新可爱，便于学习理解协议（但是不建议在生产环境下用，例如polarssl功能尚有欠缺）。\n1.服务器端： # 说说openssl，openssl 实现了以上列表中的大部分( 摒弃了不安全的一些CipherSuite)。\nopenssl 支持的 cipher 列表，可以用 openssl ciphers -V | column -t 命令查看 输出如：\n0xC0,0x30 - ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD 0xC0,0x2C - ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD 0xC0,0x28 - ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384 0xC0,0x24 - ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384 0xC0,0x14 - ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1 0xC0,0x0A - ECDHE-ECDSA-AES256-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1 0x00,0xA5 - DH-DSS-AES256-GCM-SHA384 TLSv1.2 Kx=DH/DSS Au=DH Enc=AESGCM(256) Mac=AEAD 0x00,0xA1 - DH-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH/RSA Au=DH Enc=AESGCM(256) Mac=AEAD 0x00,0x9F - DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(256) Mac=AEAD 0x00,0x6B - DHE-RSA-AES256-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AES(256) Mac=SHA256 0x00,0x69 - DH-RSA-AES256-SHA256 TLSv1.2 Kx=DH/RSA Au=DH Enc=AES(256) Mac=SHA256 0x00,0x68 - DH-DSS-AES256-SHA256 TLSv1.2 Kx=DH/DSS Au=DH Enc=AES(256) Mac=SHA256 0x00,0x39 - DHE-RSA-AES256-SHA SSLv3 Kx=DH Au=RSA Enc=AES(256) Mac=SHA1 0x00,0x37 - DH-RSA-AES256-SHA SSLv3 Kx=DH/RSA Au=DH Enc=AES(256) Mac=SHA1 每一行里：\n第1个字段是 这个CipherSuite的value， 第2个字段是 CipherSuite 的名字 ， 第3个字段是本Cipher Suite是用于哪个 SSL/TLS版本的协议， 第4个字段 Kx 是 key exchange 算法 第5个字段 Au 是 authentication 算法 第6个字段Enc是 encryptation算法 第7个字段 Mac 是 MAC 算法，用于创建消息摘要 例如:\n0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256 这一行，表示：\n名字为 ECDH-ECDSA-AES128-SHA256 的CipherSuite ，用于 TLSv1.2版本， 使用 ECDH做密钥交换， 使用ECDSA做认证， 使用AES-128做加密算法， 使用SHA256做MAC算法。 一个CipherSuite，在 openssl 库里用这个结构体表示:\n/* used to hold info on the particular ciphers used */ struct ssl_cipher_st { int valid; const char *name; /* text name */ unsigned long id; /* id, 4 bytes, first is version */ /* * changed in 0.9.9: these four used to be portions of a single value * \u0026#39;algorithms\u0026#39; */ unsigned long algorithm_mkey; /* key exchange algorithm */ unsigned long algorithm_auth; /* server authentication */ unsigned long algorithm_enc; /* symmetric encryption */ unsigned long algorithm_mac; /* symmetric authentication */ unsigned long algorithm_ssl; /* (major) protocol version */ unsigned long algo_strength; /* strength and export flags */ unsigned long algorithm2; /* Extra flags */ int strength_bits; /* Number of bits really used */ int alg_bits; /* Number of bits for algorithm */ }; 以上4个算法，对应其中的下面四行：\nunsigned long algorithm_mkey; /* key exchange algorithm */ unsigned long algorithm_auth; /* server authentication */ unsigned long algorithm_enc; /* symmetric encryption */ unsigned long algorithm_mac; /* symmetric authentication */ 2，客户端： # 在浏览器里面，可以这样查看当前使用的CipherSuite: Firefox下打开gmail，点击地址栏左侧锁图标\n3. 如何指定CipherSuite # 在tls中，选择CipherSuite的方法是通过cipher list\n格式和用法见：https://www.openssl.org/docs/apps/ciphers.html\nnginx里面的配置项是 cipher_list\ncipher list 的格式是：\n一个cipher list 包含一个或者多个由冒号分隔的cipher string( 逗号和空格也可以接受但不常用)。\n一个cipher string可以是下列形式之一:\n(1).可以由单个cipher suite构成，例如 RC4-SHA。\n(2).它可以表示含有某个特定算法的cipher列表，或者一种特定类型的cipher suite。例如， SHA1表示所有使用摘要算法SHA1的cipher suite, SSLV3表示所有SSL V3算法。\n(3).cipher suite的列表,可以使用加号+ 合并到一个单一的cipher string里面。这被作为一个逻辑且操作。例如，SHA1+DES表示所有包含了 SHA，并且包含了DES的算法。\n(4).每一个cipher string可以在前面加上字符 !,-,或者+\n如果加了!，那么这种cipher永久从列表里面删除，就算后边显式添加进来也不行。\n如果加了-，那么cipher中的一些或者全部可以在后面的选项里面加回来。\n如果加了+，那么cipher被移动到列表的最后，这个选项不增加任何cipher，只是把匹配的cipher移动到最后。\n如果没有上述字符，那么字符串被解析成一个cipher list，追加到当前配置列表的后面。如果cipher list 中的某些cipher已经存在了，就忽略该cipher。\n(5).另外，cipher string @STRENGTH 可以用在任何点，用来把当前cipher list按照加密算法key长度排序。\n当前建议的配置参数可以看看mozilla的这个文档：https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations\n四，CipherSuite的进化与现状 # 随着密码学的发展，硬件性能的提高，加密和破解的不断对抗博弈，常用的算法也在不断进化，旧的算法被破解，新的算法诞生。\nCipherSuite的当前流行趋势：\nauthentication (认证)算法 ：常见的有 RSA/DSA/ECDSA 3种，目前最主流的是人民群众喜闻乐见，妇孺皆知的RSA ( 2048 bit及以上)， （ECDSA 是新兴趋势，例如gmail，facebook都在迁移到ECDSA，当然目前用的还不多，DSA 由于只能提供1024bit，已经没啥人敢用）。\n加密算法：主流趋势是使用 aes，128/256 bit都可以，加密模式的趋势是使用gcm，cbc由于被发现有 BEAST 攻击等，比较难以正确使用，至于ecb模式，请勿使用。加密算法 还有RC4（不建议使用），3DES（不建议使用），Camellia(貌似日本人搞的) ，DES(已经被淘汰)等，\nmessage authentication code (消息认证码 简称MAC)算法 ，主流有 sha256,sha384,sha1,等。tls中使用了HMAC模式，而不是原始的 sha256,sha1等。google已经在淘汰MD5了。（gcm是一种特殊的称为aead的加密模式，不需要配合MAC。）\nkey exchange(密钥交换)算法：主流有两种：DH和ECDH，自从斯诺登爆料了NSA的https破解方案以后，现在的 key exchange(密钥交换)算法，普遍流行 PFS，把DH, ECDH变成 DHE，ECDHE 。\nmozilla目前推荐的 cipher list：\nECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK mozilla的优先级选择考虑：\n1.ECDHE+AESGCM最先选，目前没有已知漏洞。\n2.PFS ciphersuite优先，其中ECDHE优先于DHE\n3.SHA256优先于SHA1。完全禁用MD5。\n4.AES 128优先于AES 256。这个问题有一些讨论。\n5.在向后兼容模式中，AES优先于3DES。\n6.完全禁止RC4。3DES只用于兼容老版本。\ncloudflare的ssl cipher list配置：\nhttps://github.com/cloudflare/sslconfig/blob/master/conf\ngoogle的一篇文章解释当前cipher suite的流行趋势 http://googleonlinesecurity.blogspot.com.au/2013/11/a-roster-of-tls-cipher-suites-weaknesses.html\ngoogle在密码学方面的最新进展可以在这个博客追踪：http://googleonlinesecurity.blogspot.com/\n","date":"2015-01-23","externalUrl":null,"permalink":"/post/2015/01/23/ssl_tls_ciphersuite_intro/","section":"Posts","summary":"本文是关于SSL/TLS的 CipherSuite 的信息摘录，翻译。如有疑问，欢迎指出。\n一，CipherSuite的概念 # CipherSuite 这个名词目前没看到有好的中文翻译，个人觉得翻译成“加密算法套件”比较合适。Cipher泛指是密码学的加密算法，例如 aes, rsa, ecdh 等。 tls是由各类基础算法，作为原语组合而成。 一个CipherSuite是4个算法的组合：\n1个authentication (认证)算法 1个encryption(加密)算法 1个message authentication code (消息认证码 简称MAC)算法 1 个key exchange(密钥交换)算法 ","title":"SSL/TLS CipherSuite 介绍","type":"post"},{"content":"在单元测试中，我们需要提供业务逻辑的mock版本， 当业务逻辑实现为C++的virtual function时，这是很容易的，我们只需要写一个子类， 实现virtual function就行了，Google 的 gmock就针对这种情况设计。\n可是，如果遗留代码中有一般C函数，非virtual的类成员函数，模板函数，inline函数，如何提供mock版本呢？\n下面的代码用一点trick实现了上述函数的运行时mock。\n原理是，在运行时，修改目标函数的机器码，改为jmp到mock版本的函数中。\n实现如下：\n#include \u0026lt;stdint.h\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;sys/mman.h\u0026gt; #include \u0026#34;patch_elf.h\u0026#34; using namespace std; int print_op(void * addr,int leng){ unsigned char * op=(unsigned char *) addr; cout\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;\u0026#34;addr:\u0026#34;\u0026lt;\u0026lt;addr\u0026lt;\u0026lt;\u0026#34; code:\u0026#34;\u0026lt;\u0026lt;endl; for(int i=0;i\u0026lt;leng;++i){ cout\u0026lt;\u0026lt;\u0026#34;0x\u0026#34;\u0026lt;\u0026lt;hex\u0026lt;\u0026lt;(unsigned int)op[i]\u0026lt;\u0026lt;\u0026#34; \u0026#34;; } cout\u0026lt;\u0026lt;endl; return 0; } int patch_func(void * original,void * mock){ /* cout\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34; \u0026lt;\u0026lt;endl\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; ,i am going to patch \u0026#34;\u0026lt;\u0026lt;original\u0026lt;\u0026lt;\u0026#34; to \u0026#34;\u0026lt;\u0026lt;mock \u0026lt;\u0026lt;endl; */ //rax 用于保存函数调用的返回值，所以可以占用 //4010e1: b8 20 0c 40 00 mov $0x400c20,%eax //4010e6: ff e0 jmpq *%rax uint32_t addr=(uint32_t)(uint64_t)mock; const int code_len=7; char inject_code[code_len]={0xb8,0x00,0x00,0x00,0x00,0xff,0xe0}; memcpy(\u0026amp;inject_code[1],(char*)\u0026amp;addr,4); //print_op(inject_code,code_len); //接下来，把inject_code复制到original这个位置 //print_op(original,100); //首先，要改掉内存的权限，增加写权限 const size_t length = sysconf(_SC_PAGESIZE); void * code_addr= (void*) ( ( (long) original/length)*length ); int ret=mprotect(code_addr, length, PROT_READ | PROT_WRITE | PROT_EXEC); if ( 0!=ret ) { cerr\u0026lt;\u0026lt;\u0026#34;mprotect failed! ret=\u0026#34;\u0026lt;\u0026lt;ret\u0026lt;\u0026lt;endl; } //修改代码 memcpy( original,inject_code,code_len); //再去掉写权限 ret=mprotect(code_addr, length, PROT_READ | PROT_EXEC); if ( 0!=ret ) { cerr\u0026lt;\u0026lt;\u0026#34;mprotect failed! ret=\u0026#34;\u0026lt;\u0026lt;ret\u0026lt;\u0026lt;endl; } //print_op(original,100); //cout\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34; // \u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl; return 0; } 测试了\n1.一般函数 2.inline函数 3.一般成员函数 4.模板函数\n并在 ：32位，64位; -O2, -O0，参数下编译\n除了 inline函数没办法，其它的都有效\n#pragma once #include \u0026lt;iostream\u0026gt; #include \u0026lt;cstdio\u0026gt; using namespace std; class ST1{ public: uint32_t a; uint64_t b; char c[200]; double d; ST1 * e; ST1():a(0),b(0),d(0),e(0){ c[0]=0; } //类的成员函数 int member_func(); int member_func_mock(); }; int member_func_extern(ST1 * st); //一般函数 int original_func(ST1 * para1,ST1 para2,void * para3); int mock_func(ST1 * para1,ST1 para2,void * para3); //int ref_func(ST1 * para1,ST1 para2,void * para3); class Base{ private: uint32_t b; ST1 st; public: uint32_t a; Base():b(0),a(0){} }; //inline 函数 inline int inline_func(int a,int b){ int c=a+b+ 0x1111 * a + b/0x1111; printf(\u0026#34;%s %d\\n\u0026#34;,__func__,c); return c; } inline int inline_func_mock(int a,int b){ int c=a+b+100; printf(\u0026#34;%s %d\\n\u0026#34;,__func__,c); return c; } //模板函数 template \u0026lt;typename T\u0026gt; uint32_t get_member_a(T \u0026amp; t){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; a=\u0026#34;\u0026lt;\u0026lt;t.a\u0026lt;\u0026lt;endl; return t.a; } template \u0026lt;typename T\u0026gt; uint32_t get_member_b(T \u0026amp; t){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; b=\u0026#34;\u0026lt;\u0026lt;t.b\u0026lt;\u0026lt;endl; return t.b; } #include \u0026lt;stdint.h\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026#34;func.h\u0026#34; using namespace std; int original_func(ST1 * para1,ST1 para2,void * para3){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34;\\tcalled! \u0026#34; \u0026lt;\u0026lt;\u0026#34; a+a \u0026#34;\u0026lt;\u0026lt;para1-\u0026gt;a+para2.a \u0026lt;\u0026lt;\u0026#34; b+b \u0026#34;\u0026lt;\u0026lt;para1-\u0026gt;b+para2.b \u0026lt;\u0026lt;\u0026#34; c+c \u0026#34;\u0026lt;\u0026lt;para1-\u0026gt;c\u0026lt;\u0026lt;para2.c \u0026lt;\u0026lt;\u0026#34; d+d \u0026#34;\u0026lt;\u0026lt;para1-\u0026gt;d+para2.d \u0026lt;\u0026lt;\u0026#34; e+e \u0026#34;\u0026lt;\u0026lt;para1-\u0026gt;e\u0026lt;\u0026lt;para2.e \u0026lt;\u0026lt;para3 \u0026lt;\u0026lt;endl; return 0; } int mock_func(ST1 * para1,ST1 para2,void * para3){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34;\\tcalled!\u0026#34; \u0026lt;\u0026lt;endl; return 0; } int ref_func(ST1 * para1,ST1 para2,void * para3){ return mock_func(para1,para2,para3); } int ST1::member_func(){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; called! \u0026#34; \u0026lt;\u0026lt;\u0026#34; a=\u0026#34;\u0026lt;\u0026lt; this-\u0026gt;a \u0026lt;\u0026lt;\u0026#34; b=\u0026#34;\u0026lt;\u0026lt; this-\u0026gt;b \u0026lt;\u0026lt;\u0026#34; c=\u0026#34;\u0026lt;\u0026lt; this-\u0026gt;c \u0026lt;\u0026lt;\u0026#34; d=\u0026#34;\u0026lt;\u0026lt; this-\u0026gt;d \u0026lt;\u0026lt;endl; return 0; } int ST1::member_func_mock(){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; called! i do nothing.\u0026#34; \u0026lt;\u0026lt;endl; return 0; } int member_func_extern(ST1 * st){ cout\u0026lt;\u0026lt;__func__\u0026lt;\u0026lt;\u0026#34; called! i am not member function.\u0026#34; \u0026lt;\u0026lt;endl; return 0; } #include \u0026lt;stdint.h\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;sys/mman.h\u0026gt; #include \u0026#34;func.h\u0026#34; #include \u0026#34;patch_elf.h\u0026#34; using namespace std; int test(){ ST1 s1,s2; char str[]=\u0026#34;hello\u0026#34;; s1.a=s1.b=s1.d=1; s1.e=NULL; s2.a=s2.b=s2.d=2; s1.e=NULL; cout\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34;\u0026lt;\u0026lt;endl; //mock original_func，替换成mock_func original_func(\u0026amp;s1,s2,\u0026amp;str[0]); patch_func((void*)\u0026amp;original_func, (void*)\u0026amp;mock_func); original_func(\u0026amp;s1,s2,\u0026amp;str[0]); cout\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34;\u0026lt;\u0026lt;endl; //mock inline 函数貌似不行 int a=s1.a, b=s1.b; inline_func(a,b); patch_func( (void*) \u0026amp;inline_func, (void*) \u0026amp;inline_func_mock); inline_func(a,b); cout\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34;\u0026lt;\u0026lt;endl; s1.member_func(); patch_func( (void*) \u0026amp;ST1::member_func, (void*) \u0026amp;ST1::member_func_mock); s1.member_func(); patch_func( (void*) \u0026amp;ST1::member_func, (void*) \u0026amp;member_func_extern); s1.member_func(); cout\u0026lt;\u0026lt;\u0026#34;----------------------------------------------------------------------\u0026#34;\u0026lt;\u0026lt;endl; get_member_a(s1); patch_func( (void*) \u0026amp; get_member_a\u0026lt;ST1\u0026gt; , (void*) \u0026amp; get_member_b\u0026lt;ST1\u0026gt;); get_member_a(s1); return 0; } int main(){ test(); return 0; } 可以参考\nsubhook: https://github.com/Zeex/subhook Microsoft Detours: https://github.com/microsoft/detours MinHook: https://github.com/TsudaKageyu/minhook PolyHook: https://github.com/stevemk14ebr/PolyHook_2_0 https://www.codeproject.com/Articles/21414/Powerful-x86-x64-Mini-Hook-Engine ","date":"2014-03-27","externalUrl":null,"permalink":"/post/2014/03/27/mock-c-plus-plus-function-for-unit-test/","section":"Posts","summary":"在单元测试中，我们需要提供业务逻辑的mock版本， 当业务逻辑实现为C++的virtual function时，这是很容易的，我们只需要写一个子类， 实现virtual function就行了，Google 的 gmock就针对这种情况设计。\n可是，如果遗留代码中有一般C函数，非virtual的类成员函数，模板函数，inline函数，如何提供mock版本呢？\n下面的代码用一点trick实现了上述函数的运行时mock。\n","title":"mock C++ function for unit test","type":"post"},{"content":"近日需要给服务器做备份，在网上找到了一个现成的金山快盘的api：https://github.com/deren/python-kuaipan （感谢！）\n试了一下，遂决定备份到金山快盘上。\n备份脚本考虑了以下几个方面的要求：\n1.上传之前一定要加密，对比之后，使用了openssl的aes-256-cbc加密算法\n2.增量备份，最开始尝试用duplicity,写了一个duplicity的backend，然后发现，好复杂！猛然发现，linux上的tar就有 增量备份功能，于是直接用了tar\n3.分卷，KuaiPan有文件大小限制，此处用了split把tar文件分卷\n代码放在 https://github.com/byronhe/kuaipan_backup\n","date":"2014-02-08","externalUrl":null,"permalink":"/post/2014/02/08/kuaipan-backup-script/","section":"Posts","summary":"近日需要给服务器做备份，在网上找到了一个现成的金山快盘的api：https://github.com/deren/python-kuaipan （感谢！）\n试了一下，遂决定备份到金山快盘上。\n","title":"一个使用KuaiPan备份linux服务器的脚本","type":"post"},{"content":"简单几行 python 代码实现一个 http 代理\n#!/usr/local/bin/python # -*- coding: utf-8 -*- import base64 import select import socket import httplib import os import sys #base64.encodestring(\u0026#34;das\\120xsdada\\s\u0026#34;) def usage (): print \u0026#34;connect_to_http_proxy %s\u0026#34; print \u0026#34;usage: connect_to_http_proxy \u0026amp;lt;desthost\u0026amp;gt; \u0026amp;lt;destport\u0026amp;gt;\u0026#34; if len(sys.argv) != 3: usage() print sys.argv sys.exit() proxy_host=\u0026#34;web-proxy-domain.com\u0026#34; proxy_port=\u0026#34;8080\u0026#34; dest_host=\u0026#34;\u0026#34; dest_port=\u0026#34;\u0026#34; dest_host, dest_port=sys.argv[1:] sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect((proxy_host,int(proxy_port))) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) req=\u0026#34;CONNECT \u0026#34;+dest_host+\u0026#34;:\u0026#34;+dest_port + \u0026#34; HTTP/1.0\\r\\n\\r\\n\u0026#34; sock.send(req) resp=sock.recv(100000) if resp.find(\u0026#34;200\u0026#34;) \u0026amp;lt; 0: print req,resp exit(-1) ep=select.epoll() ep.register(sock.fileno(),select.EPOLLIN) ep.register(sys.stdin.fileno(),select.EPOLLIN) while True: events=ep.poll(1,2) for fd,event in events: if event \u0026amp; select.EPOLLERR: sys.exit(0) if event \u0026amp; select.EPOLLIN and fd==sock.fileno(): os.write(sys.stdout.fileno(),sock.recv(1024*100)) if event \u0026amp; select.EPOLLIN and fd==sys.stdin.fileno(): os.write(sock.fileno(),sys.stdin.read()) #conn=httplib.HTTPConnection(proxy_host+\u0026#34;:\u0026#34;+proxy_port) #conn.request(\u0026#34;CONNECT\u0026#34;,dest_host+\u0026#34;:\u0026#34;+dest_port) #res=conn.getresponse() #print res.read() #if res.status != 200: # print res.status,res.reason # sys.exit()","date":"2013-11-18","externalUrl":null,"permalink":"/post/2013/11/18/a-http-proxy-script/","section":"Posts","summary":"简单几行 python 代码实现一个 http 代理\n","title":"a http proxy script","type":"post"},{"content":"libpcap驱动了tcpdump,和wireshark这类抓包工具.提供了高度灵活的包过滤语言. 据wikipedia,高性能的包过滤最早是在bsd上作为一个问题被解决,被称为bpf,在内核实现了一个解释器,进行包匹配,用户态提供一个字符设备, linux作为后来者,支持与bsd基本相同的packet filter,称为lpf,不同的是,linux是通过在一个raw socket来支持包过滤的,通过setsockopt来SO_ATTACH_FILTER,挂载过滤器. strace 可知,libpcap实际上进行了如下syscall:\nsocket(PF_PACKET, SOCK_RAW, 768) = 59 setsockopt(59, SOL_SOCKET, SO_ATTACH_FILTER, \u0026#34;\\1\\0\\0\\0\\0\\0\\0\\0\\250\\327Vc\\375\\177\\0\\0\u0026#34;, 16) = 0 libpcap的api文档和demo代码可以参见\nhttp://www.tcpdump.org/pcap3_man.html http://www.tcpdump.org/sniffex.c 参考文档了demo,我写了一个小的sniffer,\n如下:\nclass Sniffer{ public: Sniffer():handle(NULL),cap_exp(\u0026#34;\u0026#34;),net_if(\u0026#34;any\u0026#34;),exp_compiled(false){} ~Sniffer(){ if(handle){ pcap_freecode(\u0026amp;filter_code); } if(exp_compiled){ pcap_close(handle); } } string help(){ string h(\u0026#34;avaliable net_interface:\u0026#34;); pcap_if_t* alldev=NULL; if(0==pcap_findalldevs(\u0026amp;alldev, errbuf)){ for(pcap_if_t * dev =alldev;NULL!=dev;dev=dev-\u0026gt;next){ h+=dev-\u0026gt;name; h+=\u0026#34; \u0026#34;; } pcap_freealldevs(alldev); } char * default_if=pcap_lookupdev(errbuf); if(default_if){ h+=\u0026#34; default interface:\u0026#34;; h+=default_if; } return h; } string err(){ return pcap_geterr(handle); } bool configure(const string \u0026amp; net_interface,const string \u0026amp; exp){ if(net_interface!=\u0026#34;\u0026#34;) net_if=net_interface; cap_exp=exp; if(0!=pcap_lookupnet(net_if.data(),\u0026amp;netp,\u0026amp;maskp,errbuf)){ return false; } handle=pcap_create(net_if.data(),errbuf); if(NULL==handle){ return false; } if(0!=pcap_activate(handle)){ return false; } //ignore:? //pcap_set_snaplen //pcap_set_promisc //pcap_set_rfmon //pcap_set_timeout //pcap_set_buffer_size //pcap_set_tstamp_type //only cap ethernet packet if(DLT_EN10MB!=pcap_datalink(handle)){ return false; } if (0!=pcap_compile(handle, \u0026amp;filter_code, cap_exp.data(), 0, maskp)) { return false; } exp_compiled=true; if (0!=pcap_setfilter(handle, \u0026amp;filter_code)) { return false; } } bool loop(int pkg_num=-1){ typedef void (*pcap_handler)(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes); pcap_loop(handle,pkg_num,\u0026amp;(Sniffer::pcap_callback),(u_char*)this); } static void pcap_callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes){ Sniffer * p_this=(Sniffer*) user; p_this-\u0026gt;dispatch(h,bytes); } private: void dispatch(const struct pcap_pkthdr *h, const u_char *bytes){ got_packet(NULL,h,bytes); } private: pcap_t *handle; // packet capture handle string net_if; //e.g. \u0026#34;eth0\u0026#34; string cap_exp; //e.g \u0026#34;tcp and dst port 80\u0026#34; bool exp_compiled; struct bpf_program filter_code; // compiled filter program (expression) bpf_u_int32 netp; bpf_u_int32 maskp; char errbuf[PCAP_ERRBUF_SIZE]; };","date":"2013-08-04","externalUrl":null,"permalink":"/post/2013/08/04/libpcap-sniffer/","section":"Posts","summary":"libpcap驱动了tcpdump,和wireshark这类抓包工具.提供了高度灵活的包过滤语言. 据wikipedia,高性能的包过滤最早是在bsd上作为一个问题被解决,被称为bpf,在内核实现了一个解释器,进行包匹配,用户态提供一个字符设备, linux作为后来者,支持与bsd基本相同的packet filter,称为lpf,不同的是,linux是通过在一个raw socket来支持包过滤的,通过setsockopt来SO_ATTACH_FILTER,挂载过滤器. strace 可知,libpcap实际上进行了如下syscall:\nsocket(PF_PACKET, SOCK_RAW, 768) = 59 setsockopt(59, SOL_SOCKET, SO_ATTACH_FILTER, \"\\1\\0\\0\\0\\0\\0\\0\\0\\250\\327Vc\\375\\177\\0\\0\", 16) = 0 libpcap的api文档和demo代码可以参见\nhttp://www.tcpdump.org/pcap3_man.html http://www.tcpdump.org/sniffex.c 参考文档了demo,我写了一个小的sniffer,\n","title":"libpcap试玩","type":"post"},{"content":"","date":"2012-07-25","externalUrl":null,"permalink":"/categories/science/","section":"Categories","summary":"","title":"Science","type":"categories"},{"content":"目标：实现一个算法，模拟在一个封闭二维区域，圆形小球朝给定方向坠落的过程，实现二维区域的紧密填充。\n像下面这样：\n难点，及其简单解决：\n1.如何把粒子移动尽可能远？\n图中的粒子i，能往下移动多远？一般情况,碰撞？边界？\n一个简单解法：\n注意如下事实：判断两个粒子是否重叠，判断粒子是否和边 界线重叠，都是十分容易的。\n据此定义函数 f (r) 如下\n考虑把粒子往前推的过程，最开始 f (r) = 1，当推进到一个临界值后，f (r) = 0,\n因此，f (r) 的函数图像是：\n代码如下：\n//找出一个点，在一个方向上最远可以前进多远，限于一步之内，该点可以不属于这个mesh，如果不能前进，返回false bool most_advance(Point* p,double direc_x,double direc_y,Mesh *mesh,double \u0026amp;x,double \u0026amp;y,double \u0026amp;best){ //二分法求根。 if(!(p-\u0026gt;can_move)) return false; double low_radio=0.0; double high_radio=1.0;//mesh-\u0026gt;x_num + mesh-\u0026gt;y_num; best=low_radio; bool at_least_one_success=false; double mid; double step=mesh-\u0026gt;get_step(); Point new_point; while(fabs(low_radio-high_radio)\u0026gt;0.000001){ mid=(low_radio+high_radio)/2; new_point.x=p-\u0026gt;x + direc_x * step * mid; new_point.y=p-\u0026gt;y + direc_y * step * mid; bool result=mesh-\u0026gt;can_move_point(p,new_point); if(result){ low_radio=mid; best=max(best,mid); at_least_one_success=true; }else{ high_radio=mid; } //cout\u0026lt;\u0026lt;\u0026#34;mid=\u0026#34;\u0026lt;\u0026lt;mid\u0026lt;\u0026lt;\u0026#34; best=\u0026#34;\u0026lt;\u0026lt;best\u0026lt;\u0026lt;\u0026#34; result=\u0026#34;\u0026lt;\u0026lt;result\u0026lt;\u0026lt;endl; } if(!at_least_one_success) return false; x=p-\u0026gt;x + direc_x * step * best; y=p-\u0026gt;y + direc_y * step * best; return true; }","date":"2012-07-25","externalUrl":null,"permalink":"/post/2012/07/25/simulate-object-falling/","section":"Posts","summary":"目标：实现一个算法，模拟在一个封闭二维区域，圆形小球朝给定方向坠落的过程，实现二维区域的紧密填充。\n像下面这样：\n难点，及其简单解决：\n1.如何把粒子移动尽可能远？\n图中的粒子i，能往下移动多远？一般情况,碰撞？边界？\n一个简单解法：\n","title":"圆形坠落模拟算法设计","type":"post"},{"content":"本文译自 “Deriving the Y Combinator in 7 Easy Steps“，\n原文链接：http://igstan.ro/posts/2010-12-01-deriving-the-y-combinator-in-7-easy-steps.html\n在没有原生递归支持的语言中，Y结合子（Y Combinator）是一种实现递归的方式（事实上，它更常被作为一种锻炼程序思维的方式）。要实现Y结合子，要求这种语言支持匿名函数。\n此处，我选择JavaScript来推导Y结合子，从递归阶乘函数的定义开始，一步一步进行变换。\nStep 1 # 最初的实现，使用JavaScript内建的递归机制。\nvar fact = function (n) { if (n \u0026amp;lt; 2) return 1; return n * fact(n - 1); }; Step 2 # 获得递归的最简单方法是什么？我们可以定义一个函数，它接受它自身作为参数，并且用这个参数作为参数，调用这个参数。当然，这是一个无限循环，会导致栈溢出。\n(function (f) { f(f); })(function (f) { f(f); }); 我们的阶乘函数套用上面的模板，再做点改动，阶乘函数接受一个我们还不知道的参数，所以我们要的是返回一个接受该参数的函数。然后这个函数可以被用于计算阶乘。同时，这可以让我们的阶乘函数不会无限循环下去。\nvar fact = (function (f) { return function (n) { // 终止条件 if (n \u0026amp;lt; 2) return 1; //因为f返回一个函数，所以这有一个双重调用。 return n * f(f)(n - 1); }; })(function (f) { return function (n) { // 终止条件 if (n \u0026amp;lt; 2) return 1; // 因为f返回一个函数，所以这有一个双重调用。 return n * f(f)(n - 1); }; }); Step 3 # 此时，我们的代码有一些糟糕的重复，让我们把放进一个名叫recur的辅助函数里。\nvar recur = function (f) { return f(f); }; var fact = recur(function (f) { return function (n) { if (n \u0026amp;lt; 2) return 1; // 因为f返回一个函数，所以这有一个双重调用。 return n * f(f)(n - 1); }; }); Step 4 # 上面这个版本的问题是，它有两重调用（指的是f(f)(n-1)）。我们想去除它，让我们的函数跟接近于递归版本。怎么做呢？\n我们可以使用一个辅助函数，它接受一个数值参数，进行两重调用。这个trick通过把辅助函数放在可见f的作用域里，所以g可以调用f。\nvar recur = function (f) { return f(f); }; var fact = recur(function (f) { var g = function (n) { return f(f)(n); }; return function (n) { if (n \u0026amp;lt; 2) return 1; // 没有双重调用了，函数g接受一个数值参数。 return n * g(n - 1); }; }); Step 5 # 以上版本工作良好，但是它的定义太杂乱了。我们可以把它再清理为一个辅助函数，把阶乘定义尽可能分离出来。\nvar recur = function (f) { return f(f); }; var wrap = function (h) { return recur(function (f) { var g = function (n) { return f(f)(n); }; return h(g); }); }; var fact = wrap(function (g) { return function (n) { if (n \u0026amp;lt; 2) return 1; return n * g(n - 1); }; }); Step 6 # g只调用了一次，让我们把g内联进wrap里。\nvar recur = function (f) { return f(f); }; var wrap = function (h) { return recur(function (f) { return h(function (n) { return f(f)(n); }); }); }; var fact = wrap(function (g) { return function (n) { if (n \u0026amp;lt; 2) return 1; return n * g(n - 1); }; }); Step 7 # 现在，如果我们把recur也内联进wrap里，我们就得到了著名的Y结合子！\nvar Y = function (h) { return (function (f) { return f(f); })(function (f) { return h(function (n) { return f(f)(n); }); }); }; var fact = Y(function (g) { return function (n) { if (n \u0026amp;lt; 2) return 1; return n * g(n - 1); }; }); The End # 玩的开心！\n","date":"2012-04-09","externalUrl":null,"permalink":"/post/2012/04/09/translation-Y-conbinator-in-seven-steps/","section":"Posts","summary":"本文译自 “Deriving the Y Combinator in 7 Easy Steps“，\n原文链接：http://igstan.ro/posts/2010-12-01-deriving-the-y-combinator-in-7-easy-steps.html\n在没有原生递归支持的语言中，Y结合子（Y Combinator）是一种实现递归的方式（事实上，它更常被作为一种锻炼程序思维的方式）。要实现Y结合子，要求这种语言支持匿名函数。\n此处，我选择JavaScript来推导Y结合子，从递归阶乘函数的定义开始，一步一步进行变换。\nStep 1 # 最初的实现，使用JavaScript内建的递归机制。\nvar fact = function (n) { if (n \u0026lt; 2) return 1; return n * fact(n - 1); }; Step 2 # 获得递归的最简单方法是什么？我们可以定义一个函数，它接受它自身作为参数，并且用这个参数作为参数，调用这个参数。当然，这是一个无限循环，会导致栈溢出。\n(function (f) { f(f); })(function (f) { f(f); }); 我们的阶乘函数套用上面的模板，再做点改动，阶乘函数接受一个我们还不知道的参数，所以我们要的是返回一个接受该参数的函数。然后这个函数可以被用于计算阶乘。同时，这可以让我们的阶乘函数不会无限循环下去。\nvar fact = (function (f) { return function (n) { // 终止条件 if (n \u0026lt; 2) return 1; //因为f返回一个函数，所以这有一个双重调用。 return n * f(f)(n - 1); }; })(function (f) { return function (n) { // 终止条件 if (n \u0026lt; 2) return 1; // 因为f返回一个函数，所以这有一个双重调用。 return n * f(f)(n - 1); }; });","title":"[翻译]轻松7步，导出Y结合子","type":"post"},{"content":"","date":"2012-04-09","externalUrl":null,"permalink":"/categories/scheme/","section":"Categories","summary":"","title":"Scheme","type":"categories"},{"content":"一个基于约束传播的，玩具级微型计算语言的设计和简单实现。\n这个程序就是做来玩和练习的，代码是玩具级别的，用的python，基本可以正常工作了。\n先介绍应用背景：\n在流体机械设计中，通常根据性能参数进行设计，算出其它变量，但问题是，在设计过程中，需要进行变量的手工调整，例如圆整，修正到某一范围，校核等等。\n其计算模式举例如下：\n1.定义变量，如输入压力Pin=0.98，输入温度Tin=27,输入流量Qvin=400,kv2，φ2r，b2，D2，u2，qin等等。。。\n2.根据某些物理公式，算出几个新的量，如转速 n=33.9*sqrt(kv2*φ2r*b2/D2*(u2^3)/qin)\n3.把n从8296.93圆整为整数8300，\n4.重新计算b2/D2=0.06455，校核可知0.02\u0026lt;0.06455\u0026lt;0.065，符合要求\n5.根据n计算出其它新的变量，修正，校核。。。\n。。。\n观察可以发现，这种计算模式，和《计算机程序的构造与解释》中提到的约束传播系统很像，如果把一个变量看作一个对象，那么，当它位于一个公式的左侧，例如n，也就意味着，右侧变量例如kv2更新时，应该给它发送一个消息，让它重新计算自己的值，当n更新后，如果它发现有别的变量依赖于自己，它应该发消息通知它们更新自己的值。\n还可以看出，这种依赖关系形成了一个图，例如应该有一条从kv2到n的边，把n称为kv2的订阅者。\n所以这种计算模式可以用约束传播系统建模，但是此处和书里的约束传播系统有差异：此处的约束传播系统是有向图，而书里是无向图，设计成有向图主要是为了简单，无向图的消息发送顺序是难以控制的，而且构造的时候公式中的每个变量都要持有其它对象的引用，太麻烦，有向图只需要在公式左侧的那个变量哪里保存公式右侧的每个变量的引用。\n形成有向图后，每个变量的状态设为invaild，这种状态下，不会向它的会订阅者发送更新消息，收到获取值的消息时报错。\n有向图中，还有一些源点，是最初给定值的变量。\n当某个变量被赋值时，它把自己设为新值，同时向它的订阅者发送更新消息。订阅者计算自己的新值，如果和旧值相同，就沉默；否则，更新自己，通知订阅者更新。\nso，想象一下，在你的面前，虚空之中漂浮着一张有向图， 由kv2—\u0026gt;n这样的一条条边练成，当一个点被赋予值，从这点荡出一圈圈涟漪，传到它的下一级，再从更新过的点荡出新的波纹，传开，传开。。。直到所有的点都收敛，水面恢复宁静。\n好了，说代码，每一个变量都要保存它的订阅者，它的表达式，注意到，数字1.1是一种变量，变量a是一种表达式，所以代码如下：\n#!/usr/bin/env python # -*- coding: utf-8 -*- \u0026#34;\u0026#34;\u0026#34; 变量is-a表达式 数值is-a表达式 故有如下继承关系 通过env的符号表可以查到Expr的实例 \u0026#34;\u0026#34;\u0026#34; __all__ = [\u0026#39;Expr\u0026#39;,\u0026#39;Var\u0026#39;,\u0026#39;Number\u0026#39;] class Expr(object): op=\u0026#34;\u0026#34; #a function parameters=[] #Expr list desc=\u0026#34;\u0026#34; def __init__(self,op,paras,desc=\u0026#34;\u0026#34;): self.op=op self.parameters=paras self.desc=desc def get_value(self): pl=[p.get_value() for p in self.parameters] return self.op(*pl) def set_desc(self,d): self.desc=d def dump(self): pas=[] if len(self.parameters): pas=[s.dump() for s in self.parameters] pas.insert(0, \u0026#39;(\u0026#39;+self.op.__name__) return \u0026#39; \u0026#39;.join(pas) + \u0026#39;)\u0026#39; class Number(Expr): value=0.0 def __init__(self,v): self.value=v def get_value(self): return self.value def dump(self): return str(self.value) def update(self): pass class Var(Expr): name=\u0026#34;\u0026#34; desc=\u0026#34;\u0026#34; expr=None value=0.0 subscribers=[] state=\u0026#34;invaild\u0026#34; def __init__(self,name,desc=\u0026#34;\u0026#34;): self.name=name self.desc=desc self.state=\u0026#34;invaild\u0026#34; def broadcast(self): for s in self.subscribers: s.update() def update(self): self.state=\u0026#34;normal\u0026#34; new_value=self.expr.get_value() if new_value == self.value: return self.value=new_value self.broadcast() def set_expr(self,exp): self.expr=exp if isinstance(exp,Number): self.update() def set_value(self,v): self.value=v self.state=\u0026#34;normal\u0026#34; self.broadcast() def get_value(self): if self.state==\u0026#34;invaild\u0026#34;: self.update() assert self.state==\u0026#34;normal\u0026#34; return self.value def subscribe(self,subs): for sub in subs: self.subscribers.append(sub) def dump(self): expr_d=\u0026#34;\u0026#34; if self.expr: expr_d=\u0026#39; \u0026#39;+self.expr.dump() return str(self.name) +\u0026#34;=\u0026#34;+str(self.value)+expr_d#+\u0026#34; \u0026#34;+self.desc def test(): a=Var(\u0026#34;a\u0026#34;,\u0026#34;变量a\u0026#34;) b=Var(\u0026#34;b\u0026#34;,\u0026#34;变量b\u0026#34;) if __name__==\u0026#34;__main__\u0026#34;: test() 所有的变量当然是要保存到一个符号表（或称环境）里的，同时，这个环境里还要有加减乘除，sin，sqrt这样的基本运算的定义，pi，e这样的常数的定义，python的operator和math模块就够用了。\n#!/usr/bin/env python # -*- coding: utf-8 -*- import math import operator from expr import Var,Number,Expr from parser import Parser class CmdFilter(object): def __init__(self,env,input_file): self.env=env self.input_file=input_file def readline(self): while True: s=self.input_file.readline() if not s: return s if self.env.filter_cmd(s): return s class Env(object): \u0026#34;\u0026#34;\u0026#34; 求值环境，提供变量符号表和函数符号表 \u0026#34;\u0026#34;\u0026#34; symbol_table={} #存放变量 expr_table=[] #存放自由表达式 function_table={}#存放函数，对外只读 cmds=[\u0026#39;dump\u0026#39;,\u0026#39;run\u0026#39;] #env先于parser处理掉一些命令,如dump parser=None def __init__(self): self.fill_function_table() self.fill_symbol_table() self.parser=Parser(self) def dump(self): print \u0026#34;-\u0026#34;*70,\u0026#34;\\ndump all variables and expressions:\u0026#34; for k,v in self.symbol_table.items(): print k+\u0026#34;:\u0026#34;,v.get_value() print \u0026#34;\\nall checkings:\u0026#34; for e in self.expr_table: print e.get_value(),\u0026#34;=\u0026#34;,e.dump() print \u0026#34;-\u0026#34;*70 def run(self): for k,v in self.symbol_table.items(): v.update() def fill_function_table(self): #算术运算符 #1.+,-,*,/,^,=,(,) 算术运算符 self.function_table[\u0026#39;+\u0026#39;]=operator.add self.function_table[\u0026#39;-\u0026#39;]=operator.sub self.function_table[\u0026#39;*\u0026#39;]=operator.mul self.function_table[\u0026#39;/\u0026#39;]=operator.div self.function_table[\u0026#39;^\u0026#39;]=operator.pow #逻辑运算符 #2.==,\u0026gt;=,\u0026gt;,\u0026amp;lt;=,\u0026amp;lt;,!= 逻辑运算符 self.function_table[\u0026#39;==\u0026#39;]=operator.eq self.function_table[\u0026#39;\u0026gt;=\u0026#39;]=operator.ge self.function_table[\u0026#39;\u0026gt;\u0026#39;]=operator.gt self.function_table[\u0026#39;\u0026amp;lt;=\u0026#39;]=operator.le self.function_table[\u0026#39;\u0026amp;lt;\u0026#39;]=operator.lt self.function_table[\u0026#39;!=\u0026#39;]=operator.ne self.function_table[\u0026#39;sqrt\u0026#39;]=math.sqrt self.function_table[\u0026#39;sin\u0026#39;]=math.sin self.function_table[\u0026#39;cos\u0026#39;]=math.cos self.function_table[\u0026#39;tan\u0026#39;]=math.tan self.function_table[\u0026#39;asin\u0026#39;]=math.asin self.function_table[\u0026#39;acos\u0026#39;]=math.acos self.function_table[\u0026#39;atan\u0026#39;]=math.atan self.function_table[\u0026#39;exp\u0026#39;]=math.exp self.function_table[\u0026#39;pow\u0026#39;]=math.pow self.function_table[\u0026#39;factorial\u0026#39;]=math.factorial self.function_table[\u0026#39;fabs\u0026#39;]=math.fabs self.function_table[\u0026#39;ln\u0026#39;]=math.log self.function_table[\u0026#39;log10\u0026#39;]=math.log10 def fill_symbol_table(self): self.symbol_table[\u0026#39;pi\u0026#39;]=Number(math.pi) self.symbol_table[\u0026#39;e\u0026#39;] =Number(math.e) def add_expr(self,e): if e: self.expr_table.append(e) def get_function(self,name): if self.function_table.has_key(name): return self.function_table[name] else: return None def get_variable(self,name): if self.symbol_table.has_key(name): return self.symbol_table[name] else: return None def set_variable(self,name,var): self.symbol_table[name]=var def filter_cmd(self,s): s=s.strip() if s in self.cmds: fun=getattr(self,s) fun() return None return s def exec_stream(self,in_file): input_file=CmdFilter(self,in_file) self.parser.parse(input_file) import sys def test(): env=Env() env.exec_stream(sys.stdin) if __name__==\u0026#34;__main__\u0026#34;: test() 接下来，词法分析和语法分析，词法分析没有手写，也没有用flex那样的工具，直接用了一排正则表达式，挨个往下匹配，匹配上了就返回。\n严格来说，这个是不太好的，此处的词法分析原理上是不能和flex比的，flex里的多个正则表达式是合并到一个NFA里，再转化成一个DFA的，所以它的规则首先是最长优先，其次是长度相同时写在前面的优先，此处只有写在前面的优先，不太好。\n语法分析是递归下降文法分析。一行一行地分析，一行是一个token的list。\n#!/usr/bin/env python # -*- coding: utf-8 -*- from math import * from operator import * import re from expr import Var,Number,Expr \u0026#34;\u0026#34;\u0026#34; 把 a=1+b+c^2 c=2 b=c+2 这样的一行一行，分成 ID ASSIGN ADD ID ADD EQ POW 2 EOL ID ASSIGN 2 EOL ID ASSIGN ID ADD 2 EOL 这样的一行一行token, 输出是一行一个token list的形式 token分为如下几类: 1.+,-,*,/,^,=,(,),, 算术运算符 2.==,\u0026gt;=,\u0026gt;,\u0026lt;=,\u0026lt;,!= 逻辑运算符 3.[0-9]+\\.[0-9]*[Ee][+-]?[0-9]+ [0-9]+ 字面浮点数和整数 4.[a-zA-Z_]+ 变量或函数名称标识符 5.[ \\\\t\\\\n] 忽略，或结束 由于使用正则表达式直接匹配，所以和flex不同的是: 无法确保当有多个匹配项时，最长优先,因此，只能利用先后顺序解决冲突， 因而必须把==放在=前面。 \u0026#34;\u0026#34;\u0026#34; logic_op_re=re.compile(r\u0026#39;==|\u0026gt;=|\u0026gt;|\u0026lt;=|\u0026lt;|!=\u0026#39;) number_re =re.compile(r\u0026#39;[+-]?[0-9]+\\.?[0-9]*[Ee]?[+-]?[0-9]?\u0026#39;) arith_op_re=re.compile(r\u0026#39;\\+|-|\\*|/|\\^|=|\\(|\\)|,\u0026#39;) int_re =re.compile(r\u0026#39;[0-9]+\u0026#39;) id_re =re.compile(r\u0026#39;[a-zA-Z_]+\u0026#39;) blank_re =re.compile(r\u0026#39;[\\ |\\t|\\r]+\u0026#39;) comment_re =re.compile(r\u0026#39;\u0026#34;([^\u0026#34;]*)\u0026#34;\u0026#39;) other_re =re.compile(r\u0026#39;.+\u0026#39;) def scanner(line): result=[] while True: line=line.strip() if not line: return result m=re.match(logic_op_re,line) if m: result.append((\u0026#39;logic_op\u0026#39;,m.group())) line=line[m.end():] continue m=re.match(number_re ,line) if m: result.append((\u0026#39;number\u0026#39;,float(m.group()))) line=line[m.end():] continue m=re.match(arith_op_re,line) if m: result.append((\u0026#39;arith_op\u0026#39;,m.group())) line=line[m.end():] continue m=re.match(int_re ,line) if m: result.append((\u0026#39;number\u0026#39;,float(m.group()))) line=line[m.end():] continue m=re.match(id_re ,line) if m: result.append((\u0026#39;id\u0026#39;,m.group())) line=line[m.end():] continue m=re.match(comment_re ,line) if m: result.append((\u0026#39;comment\u0026#39;,m.group())) line=line[m.end():] continue m=re.match(blank_re ,line) if m: line=line[m.end():] continue m=re.match(other_re,line) if m: print \u0026#34;亲，看看你都塞了一堆什么进来呀？\\\u0026#34;\u0026#34;+m.group()+\u0026#34;\\\u0026#34; 人家好伤心的呀！\u0026#34; line=line[m.end():] return result class Parser(object): \u0026#34;\u0026#34;\u0026#34; 文法分析： \u0026#34;\u0026#34;\u0026#34; input_file=None env=None def __init__(self,env): self.env=env def parse(self,input_file): \u0026#34;\u0026#34;\u0026#34; 如入可以是sys.stdin,a file,a string 要求实现readline()方法 \u0026#34;\u0026#34;\u0026#34; self.input_file=input_file self.run() def run(self): while True: line=self.input_file.readline() if not line: return tokens=scanner(line) #把字母名称的函数标示出来 r=[] for t in tokens: if t[0]==\u0026#39;id\u0026#39; and self.env.get_function(t[1]): r.append((\u0026#39;function\u0026#39;,t[1])) else: r.append(t) tokens=r #把comment提取出来 comments=map(lambda x:x[1], filter(lambda x:x[0]==\u0026#34;comment\u0026#34;,tokens)) comments=\u0026#39; \u0026#39;.join(comments) tokens=filter(lambda x:x[0]!=\u0026#34;comment\u0026#34;,tokens) #含有=的表达式是约束方程，其它的都是expr c=tokens.count( (\u0026#39;arith_op\u0026#39;, \u0026#39;=\u0026#39;)) if c==0: #没有约束到变量的表达式 e=self.parse_expr(tokens) #e.set_desc(comments) self.env.add_expr(e) continue if c\u0026gt;1: print \u0026#34;亲，赋值一次就够了，你肿么赋值了\u0026#34;+str(c)+\u0026#34;次涅？\u0026#34; continue #c=1 if len(tokens)\u0026lt;3 or tokens[0][0]!=\u0026#39;id\u0026#39; or \\ tokens[1]!=(\u0026#39;arith_op\u0026#39;,\u0026#39;=\u0026#39;): print \u0026#34;亲，你给我的表达式格式有问题偶～:\u0026#34;+line continue var_name=tokens[0][1] var=self.env.get_variable(var_name) if var is None: var=Var(var_name,comments) self.env.set_variable(var_name,var) e=self.parse_expr(tokens[2:]) var.set_expr(e) def parse_expr(self,tokens): \u0026#34;\u0026#34;\u0026#34; token分为如下几类: 1.+,-,*,/,^,=,(,),, 算术运算符 2.==,\u0026gt;=,\u0026gt;,\u0026lt;=,\u0026lt;,!= 逻辑运算符 3.[0-9]+\\.[0-9]*[Ee][+-]?[0-9]+ [0-9]+ 字面浮点数和整数 4.[a-zA-Z_]+ 变量或函数名称标识符 5.[ \\\\t\\\\n] 忽略，或结束 BNF: expr=expr[==|\u0026gt;=|\u0026gt;|\u0026lt;=|\u0026lt;|!=]expr|expr expr=expr+expr | expr-expr expr=expr*expr | expr/expr expr=expr^expr expr=function(expr[,expr]) expr=(expr) expr=\u0026lt;float\u0026gt;|\u0026lt;var\u0026gt; \u0026#34;\u0026#34;\u0026#34; if len(tokens): expr,rest=self.parse_logic_op(tokens) return expr #能处理就处理，不能处理原样返回。 def parse_logic_op(self,tokens): left,rest=self.parse_add_sub_op(tokens) if not len(rest): return left,rest logic_op_list=[\u0026#34;==\u0026#34;,\u0026#34;\u0026gt;=\u0026#34;,\u0026#34;\u0026gt;\u0026#34;,\u0026#34;\u0026lt;=\u0026#34;,\u0026#34;\u0026lt;\u0026#34;,\u0026#34;!=\u0026#34;] if rest[0][1] not in logic_op_list: return left,rest op=self.env.get_function(rest[0][1]) right,rest=self.parse_add_sub_op(rest[1:]) return Expr(op,[left,right]),rest def parse_add_sub_op(self,tokens): left,rest=self.parse_mul_div_op(tokens) add_sub_op_list=[\u0026#34;+\u0026#34;,\u0026#34;-\u0026#34;] while len(rest) and rest[0][1] in add_sub_op_list: op=self.env.get_function(rest[0][1]) right,rest=self.parse_mul_div_op(rest[1:]) left=Expr(op,[left,right]) return left,rest def parse_mul_div_op(self,tokens): left,rest=self.parse_pow_op(tokens) mul_div_op_list=[\u0026#34;*\u0026#34;,\u0026#34;/\u0026#34;] while len(rest) and rest[0][1] in mul_div_op_list: op=self.env.get_function(rest[0][1]) right,rest=self.parse_pow_op(rest[1:]) left=Expr(op,[left,right]) return left,rest def parse_pow_op(self,tokens): left,rest=self.parse_function_op(tokens) pow_op_list=[\u0026#34;^\u0026#34;] while len(rest) and (rest[0][1] in pow_op_list): op=self.env.get_function(rest[0][1]) right,rest=self.parse_pow_op(rest[1:]) left=Expr(op,[left,right]) return left,rest def parse_function_op(self,tokens): if tokens[0][0] in [\u0026#39;number\u0026#39;,\u0026#39;id\u0026#39;]: return self.parse_float_var_op(tokens) if tokens[0][1]==\u0026#39;(\u0026#39;: return self.parse_parentheses_op(tokens) if tokens[0][0]!=\u0026#39;function\u0026#39;: return None,tokens op=self.env.get_function(tokens[0][1]) if op and tokens[1][1]==\u0026#39;(\u0026#39;: paras=[] tokens=tokens[2:] left,tokens=self.parse_add_sub_op(tokens) paras.append(left) while tokens[0][1]==\u0026#39;,\u0026#39;: left,tokens=self.parse_add_sub_op(tokens[1:]) paras.append(left) if tokens[0][1]==\u0026#39;)\u0026#39;: tokens=tokens[1:] else: print \u0026#34;bad syntax. tokens found -\u0026gt;\u0026#34;,tokens expr=Expr(op,paras) return expr,tokens else: print \u0026#34;error bad syntax -\u0026gt;\u0026#34;,tokens return None,tokens def parse_parentheses_op(self,tokens): if tokens[0][1]==\u0026#39;(\u0026#39;: left,tokens=self.parse_logic_op(tokens[1:]) if tokens[0][1]==\u0026#39;)\u0026#39;: return left,tokens[1:] return left,tokens return None,tokens def parse_float_var_op(self,tokens): if tokens[0][0] == \u0026#39;number\u0026#39;: n=Number(tokens[0][1]) return n,tokens[1:] if tokens[0][0] == \u0026#39;id\u0026#39;: var=self.env.get_variable(tokens[0][1]) if not var: var_name=tokens[0][1] var=Var(var_name,\u0026#39;\u0026#39;) self.env.set_variable(var_name,var) var=self.env.get_variable(tokens[0][1]) return var,tokens[1:] return None,tokens import StringIO from env import * def test(): s=\u0026#34;\u0026#34;\u0026#34; a=1+(b+c)^2/23.1e-10 \u0026#34;变量a\u0026#34; c=2 \u0026#34;变量c\u0026#34; \u0026#34;c是个好变量\u0026#34; b=c+2 \u0026#34;变量b\u0026#34; \u0026#34;b也是个好变量\u0026#34; \u0026#34;这是为它写的第三条注释\u0026#34; a\u0026gt;c \u0026#34;检查a是否大于c\u0026#34; a\u0026gt;=c \u0026#34;检查a是否大于等于c\u0026#34; run dump c=3 \u0026#34;change c again.\u0026#34; \u0026#34;注释也可以在前面\u0026#34; c^2\u0026gt;=sin(pow(a,b)+b) run dump \u0026#34;\u0026#34;\u0026#34; #for l in s.split(\u0026#39;\\n\u0026#39;): # scanner(l) print \u0026#34;+\u0026#34;*80 print \u0026#39;*\u0026#39;*70 print s print \u0026#39;*\u0026#39;*70 e=Env() i=StringIO.StringIO(s) e.exec_stream(i) print \u0026#34;+\u0026#34;*80 if __name__==\u0026#34;__main__\u0026#34;: test() 最后是个main.py\n#!/usr/bin/env python # -*- coding: utf-8 -*- import sys from env import Env import StringIO e=Env() e.exec_stream(sys.stdin) s=\u0026#34;\u0026#34;\u0026#34; x=-1\u0026#34;\u0026#34;\u0026#34; i=StringIO.StringIO(s) e.exec_stream(i)","date":"2012-04-09","externalUrl":null,"permalink":"/post/2012/04/09/Propagation-of-Constraints-lang-in-py/","section":"Posts","summary":"一个基于约束传播的，玩具级微型计算语言的设计和简单实现。\n这个程序就是做来玩和练习的，代码是玩具级别的，用的python，基本可以正常工作了。\n先介绍应用背景：\n在流体机械设计中，通常根据性能参数进行设计，算出其它变量，但问题是，在设计过程中，需要进行变量的手工调整，例如圆整，修正到某一范围，校核等等。\n其计算模式举例如下：\n1.定义变量，如输入压力Pin=0.98，输入温度Tin=27,输入流量Qvin=400,kv2，φ2r，b2，D2，u2，qin等等。。。\n2.根据某些物理公式，算出几个新的量，如转速 n=33.9*sqrt(kv2*φ2r*b2/D2*(u2^3)/qin)\n3.把n从8296.93圆整为整数8300，\n4.重新计算b2/D2=0.06455，校核可知0.02\u003c0.06455\u003c0.065，符合要求\n5.根据n计算出其它新的变量，修正，校核。。。\n。。。\n观察可以发现，这种计算模式，和《计算机程序的构造与解释》中提到的约束传播系统很像，如果把一个变量看作一个对象，那么，当它位于一个公式的左侧，例如n，也就意味着，右侧变量例如kv2更新时，应该给它发送一个消息，让它重新计算自己的值，当n更新后，如果它发现有别的变量依赖于自己，它应该发消息通知它们更新自己的值。\n还可以看出，这种依赖关系形成了一个图，例如应该有一条从kv2到n的边，把n称为kv2的订阅者。\n所以这种计算模式可以用约束传播系统建模，但是此处和书里的约束传播系统有差异：此处的约束传播系统是有向图，而书里是无向图，设计成有向图主要是为了简单，无向图的消息发送顺序是难以控制的，而且构造的时候公式中的每个变量都要持有其它对象的引用，太麻烦，有向图只需要在公式左侧的那个变量哪里保存公式右侧的每个变量的引用。\n形成有向图后，每个变量的状态设为invaild，这种状态下，不会向它的会订阅者发送更新消息，收到获取值的消息时报错。\n有向图中，还有一些源点，是最初给定值的变量。\n当某个变量被赋值时，它把自己设为新值，同时向它的订阅者发送更新消息。订阅者计算自己的新值，如果和旧值相同，就沉默；否则，更新自己，通知订阅者更新。\nso，想象一下，在你的面前，虚空之中漂浮着一张有向图， 由kv2—\u003en这样的一条条边练成，当一个点被赋予值，从这点荡出一圈圈涟漪，传到它的下一级，再从更新过的点荡出新的波纹，传开，传开。。。直到所有的点都收敛，水面恢复宁静。\n","title":"一个基于约束传播的微型计算语言的设计和实现","type":"post"},{"content":"代码框架来自sicp 练习2.42。算是作业吧。\n(define (enumerate-interval l r) (if (= l r) (list l) (cons l (enumerate-interval (+ l 1) r)))) ;(enumerate-interval 1 10) (define (contains? e pl) (if (null? pl) #f (or (eq? e (car pl)) (contains? e (cdr pl))))) ;检查形如(2 1 4 3 2)这样的positions list中有没有重复元素,简单办法，hash会是更好的办法。 (define (no-repeat? positions) (if (null? positions) #t (and (not (contains? (car positions) (cdr positions))) (no-repeat? (cdr positions))))) ;(no-repeat? \u0026#39;(1 2)) ;(no-repeat? \u0026#39;(1 2 1)) ;按理说应该检查一下rest-of-queens是不是length = (- k 1)... ;使用cons可以想象成把所有列往右移一列再把新的添到第一列，这样不影响safe?的判断，简单。 (define (addjoin-position new-row k rest-of-queens) (cons new-row rest-of-queens)) (define (flatmap proc seq) (foldr append () (map proc seq))) (define (safe? k positions) ;两个点在对角线上，也就是满足同一个方程x+y=n或x-y=n ;所以要判断两个点是不是在同一条“左下至右上对角线”上， ;只需要算出两个x-y，判断是否相等即可。左上至右下同理。 (define (px-y pl) (map + positions (enumerate-interval 1 k))) (define (px+y pl) (map - positions (enumerate-interval 1 k))) (and (no-repeat? positions);不在同一行 (no-repeat? (px-y positions));不在同一条“左下至右上对角线”上 (no-repeat? (px+y positions));不在同一条“左上至右下对角线”上 )) ;(safe? 1 \u0026#39;(1)) ;(safe? 2 \u0026#39;(1 2)) ;(safe? 3 \u0026#39;(1 2 3)) ;(safe? 4 \u0026#39;(3 1 4 2)) ;(safe? 4 \u0026#39;(2 4 1 3)) ;层次遍历状态空间树，剪掉每一层中不合适的分支后再扩展到下一层。 (define (queens board-size) (define empty-board ()) (define (queen-cols k) ;一个布局表示成一个list,形如(2 4 1 3),表示第一列为2，第二列为2，第三列为1... ;queens-cols 返回((1,2,3)(2,1,3)(3,2,1))这样的布局组成的list (if (= k 0) (list empty-board) (filter (lambda (positions) (safe? k positions)) (flatmap (lambda (reat-of-queens) (map (lambda (new-row) (addjoin-position new-row k reat-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (- k 1)))))) (queen-cols board-size)) ;(queens 4) (queens 8)","date":"2011-12-22","externalUrl":null,"permalink":"/post/2011/12/22/sicp-02.42-eight-queue/","section":"Posts","summary":"代码框架来自sicp 练习2.42。算是作业吧。\n","title":"sicp练习2.42 [解8皇后问题的Scheme语言实现]","type":"post"},{"content":"sicp练习2.57\n(define variable? symbol?) (define (same-variable? a b) (and (variable? a) (variable? b) (eq? a b))) (define (sum-exp? exp) (and (pair? exp) (eq? (car exp) \u0026#39;+))) (define (product-exp? exp) (and (pair? exp) (eq? (car exp) \u0026#39;*))) (define (expon-exp? exp) (and (pair? exp) (eq? (car exp )\u0026#39;**))) (define (** x n) (exp (* n (log x)))) (define (make-sum lst) (let ((num (foldl + 0 (filter number? lst))) (sym (filter (lambda (x) (not (number? x))) lst))) (if (= 0 num) (cond ((= (length sym) 0) 0) ((= (length sym) 1) (car sym)) (else (cons \u0026#39;+ sym))) (if (= (length sym) 0) num (cons \u0026#39;+ (cons num sym)))))) ;(make-sum \u0026#39;(0 0)) ;(make-sum \u0026#39;(2 -2 3 -3 a b)) ;(make-sum \u0026#39;(2 3)) ;(make-sum \u0026#39;(2 -2 3 a 4 b)) ;(make-sum \u0026#39;((+ a b) (+ b d))) ;(make-sum \u0026#39;((* a 0) (* 1 (+ 0 b x)))) ;(make-sum \u0026#39;( (* a b) ) ) ;(make-sum \u0026#39;(a b) ) (define (make-product lst) (let ((num (foldl * 1 (filter number? lst))) (sym (filter (lambda (x) (not (number? x))) lst))) (cond ((= num 0) 0) ((= num 1) (if (= (length sym) 1) (car sym) (cons \u0026#39;* sym))) (else (cons \u0026#39;* (cons num sym))) ))) ;(make-product \u0026#39;(0 1 2)) ;(make-product \u0026#39;(0 a b 1 c)) ;(make-product \u0026#39;(0.5 2 a)) ;(make-product \u0026#39;(0.5 2 a c (+ a c))) ;(make-product \u0026#39;(a b 1 3 -1 (* f va))) (define (make-expon x n) (cond ((eq? n 0) 1) ((eq? x 0) 0) (else (list \u0026#39;** x n)) )) ;(make-expon 0 \u0026#39;a) ;(make-expon 0 0) ;(make-expon \u0026#39;a 0) ;(make-expon \u0026#39;a \u0026#39;b) ;(make-expon 2 3) (define (deriv exp var) (cond ((number? exp) 0) ((variable? exp) (if (same-variable? exp var) 1 0)) ((sum-exp? exp) (make-sum (map (lambda (x) (deriv x var)) (cdr exp)))) ((product-exp? exp) (let ((first (cadr exp)) (second (make-product (cddr exp)))) (make-sum (list (make-product (list first (deriv second var))) (make-product (list (deriv first var) second )))) )) ((expon-exp? exp) (let ((base (cadr exp)) (n (caddr exp))) (make-product (list n (make-expon base (make-sum (list n -1))) (deriv base var) )) )) )) (deriv \u0026#39;(+ a (+ a a) b a) \u0026#39;a) ;4 (deriv \u0026#39;a \u0026#39;b) ;0 (deriv \u0026#39;(* a b x) \u0026#39;a) ;(* b x) (deriv \u0026#39;(* (+ (* a b) (* a c)) d) \u0026#39;a) ;(* (+ b c) d) (deriv \u0026#39;(* (+ a b c) (* a b b)) \u0026#39;a) ;(+ (* (+ a b c) (* b b)) (* a b b)) (deriv \u0026#39;(** x n) \u0026#39;x) ;(* n (** x (+ -1 n))) (deriv \u0026#39;(** (* 3 a ) n) \u0026#39;a) ;(* n (** (* 3 a) (+ -1 n)) (* 3))","date":"2011-12-22","externalUrl":null,"permalink":"/post/2011/12/22/symbol-deriv/","section":"Posts","summary":"sicp练习2.57\n","title":"符号求导，scheme实现","type":"post"},{"content":"Church计数 my code：\n;Church计数 (define zero (lambda (f) (lambda (x) x))) (define (add-1 n) (lambda (f) (lambda (x) (f ((n f) x))))) (define (show-num n)((n (lambda (x) (+ 1 x))) 0)) (define (add a b) (lambda (f) (lambda (x) ((a f) ((b f) x))))) (define one (lambda (f) (lambda (x) (f x)))) (define two (lambda (f) (lambda (x) (f (f x))))) (show-num zero) (show-num one) (show-num two) (newline) (show-num (add-1 zero)) (show-num (add-1 (add-1 zero))) (show-num (add-1 (add-1 (add-1 zero)))) (show-num (add-1 (add-1 (add-1 (add-1 zero))))) (newline) (display \u0026#34;my add\\n\u0026#34;) (show-num (add zero zero)) (show-num (add (add-1 zero) (add-1 zero))) (show-num (add (add-1 zero) (add-1 (add-1 zero)))) (define (multi a b) (lambda (f) (lambda (x) ((a (b f) ) x)))) (define (expon a b) (lambda (f) (lambda (x) (((a b) f) x)))) (show-num (multi two two)) (show-num (multi (add one two) two)) (show-num (expon two two)) (show-num (expon (expon two two) two)) Church计数，通过(lambda (f))把f作为参数，这样f就不会被求值，而f的执行次数就代表了数字。\nlambda中的形参其值是未知的，所以不会被求值。\n形参好比糖果的包装纸，要把一段代码包起来，把它放到一个匿名函数里就行了\n比如有一个函数\n(lambda (a b) (+ a b))\n要把它包装起来，可以写成\n(lambda (x) (lambda (a b) (+ a b)) )\n要剥开糖纸，传个参数就行了。\nChurch计数就是这个思想。\nshow-num用来把Church计数方式的数字转换成普通数字。\n","date":"2011-11-20","externalUrl":null,"permalink":"/post/2011/11/20/church-encoding/","section":"Posts","summary":"Church计数 my code：\n","title":"Church计数","type":"post"},{"content":"就不贴matlab代码了，图比较大。\n分别是：\n无环量流动\n有环量流动,两个滞止点\n有环量流动,一个滞止点\n有环量流动,柱面外一个滞止点\n","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/matlab-flow-field/","section":"Posts","summary":"就不贴matlab代码了，图比较大。\n分别是：\n无环量流动\n有环量流动,两个滞止点\n有环量流动,一个滞止点\n有环量流动,柱面外一个滞止点\n","title":"【旧代码】2010年用matlab做的几张理想流场的图","type":"post"},{"content":"我不喜欢fortran，\n奇怪的词法规则（竟然不用空格分开token），\nio操作竟然是语言的一部分（这种非本质的东西像C那样用库来扩展多好）\n。。。\nprogram link implicit none type node character(20) name integer id integer score type(node),pointer ::next end type integer,parameter::num=3 type(node),pointer ::students call init_all(students,num) call show_all(students) print*,\u0026#34;now,we add a student\u0026#34; call add_node(students) call show_all(students) print*,\u0026#34;now,we delete the student\u0026#34; call del_node(students) call show_all(students) contains subroutine add_node(pos) type(node),pointer ::pos,tmp allocate(tmp) tmp%name=\u0026#34;abc\u0026#34; tmp%id=1 tmp%score=100 print*,\u0026#34;please input name,id,end score of one students\u0026#34; read*,tmp%name,tmp%id,tmp%score if(associated(pos%next)) then tmp%next=\u0026gt;pos%next pos%next=\u0026gt;tmp else nullify(tmp%next) pos%next=\u0026gt;tmp end if end subroutine subroutine del_node(pos) type(node),pointer ::pos,next next=\u0026gt;pos%next if(associated(next%next)) then pos%next=\u0026gt;next%next deallocate(next) else nullify(pos%next) deallocate(next) end if end subroutine subroutine show_all(pos) type(node),pointer ::pos,tmp integer ::cnt cnt=1 print*,\u0026#34;all students are:\u0026#34; tmp=\u0026gt;pos do while(associated(tmp)) print*,cnt,\u0026#34;th \u0026#34;,tmp%name,tmp%id,tmp%score tmp=\u0026gt;tmp%next cnt=cnt+1 end do end subroutine subroutine init_all(pos,num) type(node),pointer ::pos,tmp,walk integer num integer i allocate(tmp) nullify(tmp%next) call add_node(tmp) pos=\u0026gt;tmp%next deallocate(tmp) walk=\u0026gt;pos do i=1,num-1 call add_node(walk) walk=\u0026gt;walk%next end do end subroutine end program link ","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/linked-list-in-fortran-with-pointer/","section":"Posts","summary":"我不喜欢fortran，\n奇怪的词法规则（竟然不用空格分开token），\nio操作竟然是语言的一部分（这种非本质的东西像C那样用库来扩展多好）\n","title":"【旧代码】fortran中的指针实现链表的代码","type":"post"},{"content":"思路类似 http://bazaar.launchpad.net/~lenzgr/mylvmbackup/trunk/view/head:/mylvmbackup.pl.in\n#!/bin/sh bk_to=\u0026#34;/var/mysql-backup-`date +%Y-%m-%d-%k-%M-%S |tr -d \u0026#39; \u0026#39;`\u0026#34; user_name=\u0026#34;root\u0026#34; user_pw=\u0026#34;dev\u0026#34; sql=\u0026#34; flush tables with read lock; system cp -pR /var/lib/mysql $bk_to; unlock tables; \u0026#34; mysql -u $user_name -p$user_pw -e \u0026#34;$sql\u0026#34; echo $sql tar cjf $bk_to.bz2 $bk_to rm -rf $bk_to ","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/mysql-backup-script/","section":"Posts","summary":"思路类似 http://bazaar.launchpad.net/~lenzgr/mylvmbackup/trunk/view/head:/mylvmbackup.pl.in\n#!/bin/sh bk_to=\"/var/mysql-backup-`date +%Y-%m-%d-%k-%M-%S |tr -d ' '`\" user_name=\"root\" user_pw=\"dev\" sql=\" flush tables with read lock; system cp -pR /var/lib/mysql $bk_to; unlock tables; \" mysql -u $user_name -p$user_pw -e \"$sql\" echo $sql tar cjf $bk_to.bz2 $bk_to rm -rf $bk_to","title":"【旧代码】mysql备份的shell脚本","type":"post"},{"content":"传热过程数值模拟（《传热学》实验指导书第四部分第一题，第一，第二类边界条件）\n2010年十月写的旧代码。\n第一类边界条件是给定边界温度。\n第二类是对流边界。\n区域都是如下形状的:\n-------------------------------- | | | | | | | ---------------------- | | | | | | | | | | | | | | | | --------- 用C++纯属蛋疼。\n第一类边界条件：\n/* * 等温边界 */ #include\u0026lt;iostream\u0026gt; #include\u0026lt;cmath\u0026gt; using namespace std; const double out_temp=30.0;//外边界温度 const double in_temp=0.0;//内边界温度 const double accuracy=0.00000001;//精度 const double lambda=0.53;//导热系数 const int width=15;//上部点阵宽度 const int width_bottom=4;//下部点阵宽度 const int height=4;//上部点阵高度 const int height_bottom=7;//下部点阵高度 /* //另一组参数 const int width=16; const int width_bottom=6; const int height=6; const int height_bottom=6; */ //总共点数 const int num_of_points=width*height+width_bottom*height_bottom; //单个点 class point { public: double temp;//温度 int up;//数组下标 int down; int left; int right; point(){ temp=0.0;//初始化成0摄氏度 up=down=right=left=0; } }; ostream \u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp; src_stream,const point \u0026amp; src){ src_stream\u0026lt;\u0026lt;\u0026#34;temp=\u0026#34;\u0026lt;\u0026lt;src.temp; src_stream\u0026lt;\u0026lt;\u0026#34; up=\u0026#34;\u0026lt;\u0026lt;src.up\u0026lt;\u0026lt;\u0026#34; down=\u0026#34;\u0026lt;\u0026lt;src.down; src_stream\u0026lt;\u0026lt;\u0026#34; left=\u0026#34;\u0026lt;\u0026lt;src.left\u0026lt;\u0026lt;\u0026#34; right=\u0026#34;\u0026lt;\u0026lt;src.right; return src_stream; } void print_grid(point * points){//输出网格各点的温度 cout\u0026lt;\u0026lt;endl; for(int position=0;position\u0026gt;=0;position=points[position].down){ for(int tmp=position;tmp\u0026gt;=0;tmp=points[tmp].right){ cout.width(10); cout\u0026lt;\u0026lt;points[tmp].temp; } cout\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl; } } double calc_direction(point * points,int direction,double \u0026amp;opposit_weight){ //计算给定方向邻点的温度和反方向的权重。 switch (direction) { case -1: return out_temp; break; case -2: return in_temp; break; case -3: opposit_weight*=2; return 0.0; break; default: return points[direction].temp; } } void compu_point(point * points,int now){ //根据周围四个点算出指定点的温度 double left_temp; double right_temp; double up_temp; double down_temp; double up_wight=0.25;//上方权重,默认0.25 double down_wight=0.25; double left_wight=0.25; double right_wight=0.25; left_temp=calc_direction(points,points[now].left,right_wight); right_temp=calc_direction(points,points[now].right,left_wight); up_temp=calc_direction(points,points[now].up,down_wight); down_temp=calc_direction(points,points[now].down,up_wight); points[now].temp=left_temp*left_wight+right_temp*right_wight+up_temp*up_wight+down_temp*down_wight; } void rec_walk(point * points){ // \u0026#34;左=\u0026gt;右\u0026#34;里嵌\u0026#34;上=\u0026gt;下\u0026#34;遍历计算 for(int position=0;position\u0026gt;=0;position=points[position].right) for(int tmp=position;tmp\u0026gt;=0;tmp=points[tmp].down) compu_point(points,tmp); } void heat_rate(point * points){ //计算导热量 double out_sum=0.0; double in_sum=0.0; //外边界 int pos=0; double tmp=0.0; for(;points[pos].right\u0026gt;=0;pos=points[pos].right){ tmp=out_temp - points[pos].temp ; if(points[pos].right\u0026lt;=0) out_sum += 0.5*tmp; else out_sum+=tmp; } for(pos=points[0].down;points[pos].down\u0026gt;=0;pos=points[pos].down){ tmp=out_temp - points[pos].temp ; if(points[pos].down\u0026lt;=0) out_sum += 0.5*tmp; else out_sum+=tmp; } //内边界 for(pos=num_of_points-1;points[pos].right\u0026lt;0;pos=points[pos].up){ tmp = points[pos].temp - in_temp ; if(points[pos].down\u0026lt;=0) in_sum += 0.5*tmp; else in_sum +=tmp; } for(pos=points[pos].right;points[pos].right\u0026gt;=0;pos=points[pos].right){ tmp = points[pos].temp - in_temp; if(points[pos].right\u0026lt;=0) in_sum += 0.5*tmp; else in_sum += tmp; } out_sum*=lambda; in_sum*=lambda; cout\u0026lt;\u0026lt;\u0026#34;外边界导热量=\u0026#34;\u0026lt;\u0026lt;out_sum; cout\u0026lt;\u0026lt;\u0026#34;\\n内边界导热量=\u0026#34;\u0026lt;\u0026lt;in_sum; cout\u0026lt;\u0026lt;\u0026#34;\\n误差=\u0026#34;\u0026lt;\u0026lt;(in_sum-out_sum)/in_sum*100\u0026lt;\u0026lt;\u0026#34;%\u0026#34;\u0026lt;\u0026lt;endl; } void init_grid(point * points){ //初始化网格 int position=0; //初始化最上面的边 for(int i=0;i\u0026lt;width;i++){ points[i].up=-1;//-1表示外边界，-2内边界,-3表示绝热 points[i].left=i-1; points[i].right=i+1; points[i].down=i+width; position=i; } points[0].left=-1;//等温 points[width-1].right=-3;//绝热 //上面区域 for(int i=1;i\u0026lt;height;i++){ for(int j=0;j\u0026lt;width;j++){ points[i*width+j].up =i*width+j-width; points[i*width+j].left =i*width+j-1; points[i*width+j].right=i*width+j+1; points[i*width+j].down =i*width+j+width; position=i*width+j; } points[i*width+0].left=-1; points[i*width+width-1].right=-3; } position-=width; position++; for(int j=width_bottom;j\u0026lt;width;j++){ points[position+j].down =-2;//内边界 } position+=width; //下面区域 for(int i=0;i\u0026lt;height_bottom;i++){ for(int j=0;j\u0026lt;width_bottom;j++){ if(i) points[position+i*width_bottom+j].up=position+i*width_bottom+j-width_bottom; else points[position+i*width_bottom+j].up=position+i*width_bottom+j-width; points[position+i*width_bottom+j].left=position+i*width_bottom+j-1; points[position+i*width_bottom+j].right=position+i*width_bottom+j+1; points[position+i*width_bottom+j].down=position+i*width_bottom+j+width_bottom; } points[position+i*width_bottom+0].left=-1; points[position+i*width_bottom+width_bottom-1].right=-2; } //下边界 position+=width_bottom*height_bottom; position-=width_bottom; for(int j=0;j\u0026lt;width_bottom;j++){ points[position+j].down=-3; } position+=width_bottom; } int main(){ point * points=new point[num_of_points]; init_grid(points); //初始化完。输出看看。 //for(int i=0;i\u0026lt;num_of_points;i++) // cout\u0026lt;\u0026lt;i\u0026lt;\u0026lt;\u0026#34; =\u0026gt; \u0026#34;\u0026lt;\u0026lt;points[i]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; cout\u0026lt;\u0026lt;\u0026#34;初始温度分布（最外层和最内层没有显示）：\\n\u0026#34;; print_grid(points); //开始迭代计算 double diff=out_temp; int times=0; for(;diff\u0026gt;out_temp*accuracy;times++){ //\u0026#34;上=\u0026gt;下\u0026#34; 里嵌 \u0026#34;左=\u0026gt;右\u0026#34;遍历计算 diff=0.0; for(int now=0;now\u0026lt;num_of_points;now++){ double tmp=points[now].temp; compu_point(points,now); tmp=abs(tmp-points[now].temp); diff = diff\u0026gt;tmp?diff:tmp; } } cout\u0026lt;\u0026lt;\u0026#34;\\n迭代计算\u0026#34;\u0026lt;\u0026lt;times\u0026lt;\u0026lt;\u0026#34;次得到的温度分布（最外层和最内层没有显示）：\\n\u0026#34;; print_grid(points); heat_rate(points); delete [] points; return 0; } 第二类边界条件：\n/* * 对流边界 */ #include\u0026lt;iostream\u0026gt; #include\u0026lt;cmath\u0026gt; using namespace std; const double out_temp=30.0;//外边界温度 const double out_h=10.0;//外边界对流换热系数 const double in_temp=10.0;//内边界温度 const double in_h=4 .0;//内边界对流换热系数 const double accuracy=0.00000001;//精度 const double lambda=0.53;//导热系数 const int width=15;//上部点阵宽度 const int width_bottom=4;//下部点阵宽度 const int height=4;//上部点阵高度 const int height_bottom=7;//下部点阵高度 const double delta_x=3.0/(2*width+1); const double out_Bi=out_h*delta_x/lambda;//外表面网格Bi数 const double in_Bi = in_h*delta_x/lambda;//内表面网格Bi数 /* //另一组参数 const int width=150; const int width_bottom=40; const int height=40; const int height_bottom=70; */ //总共点数 const int num_of_points=width*height+width_bottom*height_bottom; //单个点 class point { public: double temp;//温度 int up;//数组下标 int down; int left; int right; point(){ temp=0.0;//初始化成0摄氏度 up=down=right=left=0; } }; ostream \u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp; src_stream,const point \u0026amp; src){ src_stream\u0026lt;\u0026lt;\u0026#34;temp=\u0026#34;\u0026lt;\u0026lt;src.temp; src_stream\u0026lt;\u0026lt;\u0026#34; up=\u0026#34;\u0026lt;\u0026lt;src.up\u0026lt;\u0026lt;\u0026#34; down=\u0026#34;\u0026lt;\u0026lt;src.down; src_stream\u0026lt;\u0026lt;\u0026#34; left=\u0026#34;\u0026lt;\u0026lt;src.left\u0026lt;\u0026lt;\u0026#34; right=\u0026#34;\u0026lt;\u0026lt;src.right; return src_stream; } void print_grid(point * points){//输出网格各点的温度 cout\u0026lt;\u0026lt;endl; for(int position=0;position\u0026gt;=0;position=points[position].down){ for(int tmp=position;tmp\u0026gt;=0;tmp=points[tmp].right){ cout.width(10); cout\u0026lt;\u0026lt;points[tmp].temp; } cout\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl\u0026lt;\u0026lt;endl; } } void dump_all(point * points){//便于调试 for(int i=0;i\u0026lt;num_of_points;i++) cout\u0026lt;\u0026lt;i\u0026lt;\u0026lt;\u0026#34; =\u0026gt; \u0026#34;\u0026lt;\u0026lt;points[i]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } double calc_direction(point * points,int direction,double \u0026amp;opposit_weight,double \u0026amp;this_weight){ //计算给定方向邻点的温度和反方向的权重。 switch (direction) { case -1://外边界 this_weight*=out_Bi; return out_temp; break; case -2://内边界 this_weight*=in_Bi; return in_temp; break; case -3://绝热 this_weight=0.0; opposit_weight*=2; return 0.0; break; default: return points[direction].temp; } } void compu_point(point * points,int now){ //根据周围四个点算出指定点的温度 double left_temp; double right_temp; double up_temp; double down_temp; double up_wight=1;//上方权重 double down_wight=1; double left_wight=1; double right_wight=1; left_temp=calc_direction(points,points[now].left,right_wight,left_wight); right_temp=calc_direction(points,points[now].right,left_wight,right_wight); up_temp=calc_direction(points,points[now].up,down_wight,up_wight); down_temp=calc_direction(points,points[now].down,up_wight,down_wight); double Bi_sum=up_wight+down_wight+right_wight+left_wight; points[now].temp=(left_temp*left_wight+right_temp*right_wight+up_temp*up_wight+down_temp*down_wight)/Bi_sum; } void rec_walk(point * points){ // \u0026#34;左=\u0026gt;右\u0026#34;里嵌\u0026#34;上=\u0026gt;下\u0026#34;遍历计算 for(int position=0;position\u0026gt;=0;position=points[position].right) for(int tmp=position;tmp\u0026gt;=0;tmp=points[tmp].down) compu_point(points,tmp); } void heat_rate(point * points){ //计算导热量 double out_sum=0.0; double in_sum=0.0; //外边界 int pos=0; double tmp=0.0; for(;points[pos].right\u0026gt;=0;pos=points[pos].right){ tmp=out_temp - points[pos].temp ; if(points[pos].right\u0026lt;=0) out_sum += 0.5*tmp; else out_sum+=tmp; } for(pos=points[0].down;points[pos].down\u0026gt;=0;pos=points[pos].down){ tmp=out_temp - points[pos].temp ; if(points[pos].down\u0026lt;=0) out_sum += 0.5*tmp; else out_sum+=tmp; } //内边界 for(pos=num_of_points-1;points[pos].right\u0026lt;0;pos=points[pos].up){ tmp = points[pos].temp - in_temp ; if(points[pos].down\u0026lt;=0) in_sum += 0.5*tmp; else in_sum +=tmp; } for(pos=points[pos].right;points[pos].right\u0026gt;=0;pos=points[pos].right){ tmp = points[pos].temp - in_temp; if(points[pos].right\u0026lt;=0) in_sum += 0.5*tmp; else in_sum += tmp; } out_sum*=out_h*delta_x; in_sum *=in_h *delta_x; cout\u0026lt;\u0026lt;\u0026#34;外边界导热量=\u0026#34;\u0026lt;\u0026lt;out_sum; cout\u0026lt;\u0026lt;\u0026#34;\\n内边界导热量=\u0026#34;\u0026lt;\u0026lt;in_sum; cout\u0026lt;\u0026lt;\u0026#34;\\n误差=\u0026#34;\u0026lt;\u0026lt;(in_sum-out_sum)/in_sum*100\u0026lt;\u0026lt;\u0026#34;%\u0026#34;\u0026lt;\u0026lt;endl; } void init_grid(point * points){ //初始化网格 int position=0; //初始化最上面的边 for(int i=0;i\u0026lt;width;i++){ points[i].up=-1;//-1表示外边界，-2内边界,-3表示绝热 points[i].left=i-1; points[i].right=i+1; points[i].down=i+width; position=i; } points[0].left=-1;//对流 points[width-1].right=-3;//绝热 //上面区域 for(int i=1;i\u0026lt;height;i++){ for(int j=0;j\u0026lt;width;j++){ points[i*width+j].up =i*width+j-width; points[i*width+j].left =i*width+j-1; points[i*width+j].right=i*width+j+1; points[i*width+j].down =i*width+j+width; position=i*width+j; } points[i*width+0].left=-1; points[i*width+width-1].right=-3; } position-=width; position++; for(int j=width_bottom;j\u0026lt;width;j++){ points[position+j].down =-2;//内边界 } position+=width; //下面区域 for(int i=0;i\u0026lt;height_bottom;i++){ for(int j=0;j\u0026lt;width_bottom;j++){ if(i) points[position+i*width_bottom+j].up=position+i*width_bottom+j-width_bottom; else points[position+i*width_bottom+j].up=position+i*width_bottom+j-width; points[position+i*width_bottom+j].left=position+i*width_bottom+j-1; points[position+i*width_bottom+j].right=position+i*width_bottom+j+1; points[position+i*width_bottom+j].down=position+i*width_bottom+j+width_bottom; } points[position+i*width_bottom+0].left=-1; points[position+i*width_bottom+width_bottom-1].right=-2; } //下边界 position+=width_bottom*height_bottom; position-=width_bottom; for(int j=0;j\u0026lt;width_bottom;j++){ points[position+j].down=-3; } position+=width_bottom; } int main(){ point * points=new point[num_of_points]; init_grid(points); //dump_all(points); cout\u0026lt;\u0026lt;\u0026#34;初始温度分布（最外层和最内层没有显示）：\\n\u0026#34;; print_grid(points); //开始迭代计算 double diff=out_temp; int times=0; for(;diff\u0026gt;out_temp*accuracy;times++){ //\u0026#34;上=\u0026gt;下\u0026#34; 里嵌 \u0026#34;左=\u0026gt;右\u0026#34;遍历计算 diff=0.0; for(int now=0;now\u0026lt;num_of_points;now++){ double tmp=points[now].temp; compu_point(points,now); tmp=abs(tmp-points[now].temp); diff = diff\u0026gt;tmp?diff:tmp; } } cout\u0026lt;\u0026lt;\u0026#34;\\n迭代计算\u0026#34;\u0026lt;\u0026lt;times\u0026lt;\u0026lt;\u0026#34;次得到的温度分布（最外层和最内层没有显示）：\\n\u0026#34;; print_grid(points); heat_rate(points); delete [] points; return 0; }","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/heat-grid/","section":"Posts","summary":"传热过程数值模拟（《传热学》实验指导书第四部分第一题，第一，第二类边界条件）\n2010年十月写的旧代码。\n第一类边界条件是给定边界温度。\n第二类是对流边界。\n区域都是如下形状的:\n-------------------------------- | | | | | | | ---------------------- | | | | | | | | | | | | | | | | --------- 用C++纯属蛋疼。\n","title":"【旧代码】传热过程数值模拟","type":"post"},{"content":"","date":"2011-11-15","externalUrl":null,"permalink":"/categories/java/","section":"Categories","summary":"","title":"Java","type":"categories"},{"content":"小项目，练手的。\n1.使用了java.util.concurrent包里的线程池，可以飙升到满带宽，在100M带宽上，可以达到10MB/s。\n2.使用了java.nio里的channels，性能比自己缓冲有一些提高。\nimport java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.Calendar; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class HttpDownloader implements Callable { URLConnection connection; FileChannel outputChann; public static volatile int count = 0; public static void main(String[] args) throws Exception { ExecutorService poll = Executors.newFixedThreadPool(100); for (int i = 0; i \u0026amp;lt; 100; i++) { Calendar now = Calendar.getInstance(); String fileName = \u0026#34;../data/\u0026#34; + now.get(Calendar.YEAR) + \u0026#34;年\u0026#34; + (now.get(Calendar.MONTH) + 1) + \u0026#34;月\u0026#34; + now.get(Calendar.DAY_OF_MONTH) + \u0026#34;日--\u0026#34; + i + \u0026#34;.txt\u0026#34;; poll.submit(new HttpDownloader(\u0026#34;http://www.sina.com\u0026#34;, (new FileOutputStream(fileName)).getChannel())); } poll.shutdown(); long start = System.currentTimeMillis(); while (!poll.isTerminated()) { Thread.sleep(1000); System.out.println(\u0026#34;已运行\u0026#34; + ((System.currentTimeMillis() - start) / 1000) + \u0026#34;秒，\u0026#34; + HttpDownloader.count + \u0026#34;个任务还在运行\u0026#34;); } } public HttpDownloader(String url, FileChannel fileChannel) throws Exception { synchronized (HttpDownloader.class) { count++; } connection = (new URL(url)).openConnection(); this.outputChann = fileChannel; } @Override public String call() throws Exception { connection.connect(); InputStream inputStream = connection.getInputStream(); ReadableByteChannel rChannel = Channels.newChannel(inputStream); outputChann.transferFrom(rChannel, 0, Integer.MAX_VALUE); // System.out.println(Thread.currentThread().getName() + \u0026#34; completed!\u0026#34;); inputStream.close(); outputChann.close(); synchronized (HttpDownloader.class) { count--; } return null; } } ","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/java-multithread-download-webpage/","section":"Posts","summary":"小项目，练手的。\n1.使用了java.util.concurrent包里的线程池，可以飙升到满带宽，在100M带宽上，可以达到10MB/s。\n","title":"java多线程网页下载代码","type":"post"},{"content":"import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import org.postgresql.ds.PGSimpleDataSource; import oracle.jdbc.pool.OracleDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; public class DBConnection { /** * @param args * @throws SQLException */ public static void main(String[] args) throws SQLException { // TODO Auto-generated method stub MysqlDataSource mysqlDataSource = new MysqlDataSource(); // mysqlDataSource.setPassword(\u0026#34;dev\u0026#34;); // mysqlDataSource.setUser(\u0026#34;dev\u0026#34;); mysqlDataSource .setURL(\u0026#34;jdbc:mysql://localhost/forJava?user=dev\u0026amp;#038;password=dev\u0026#34;); Connection conn = mysqlDataSource.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate(\u0026#34;create table if not exists web\\n\u0026#34; + \u0026#34;(\\n\u0026#34; + \u0026#34; id int not null primary key,\\n\u0026#34; + \u0026#34; name varchar(100),\\n\u0026#34; + \u0026#34; created timestamp,\\n\u0026#34; + \u0026#34; content blob\\n\u0026#34; + \u0026#34;);\\n\u0026#34; + \u0026#34;\u0026#34;); for (int i = 0; i \u0026amp;lt; 1; i++) { stmt.executeUpdate(\u0026#34;insert into web (name,content) values (\u0026#39;HL\u0026#39;,\u0026#39;frgertrhrtnthtohioh\u0026#39;)\u0026#34;); } showResultSet(stmt.executeQuery(\u0026#34;select * from web limit 10\u0026#34;)); // Driver driver = new com.mysql.jdbc.Driver(); // driver.connect(\u0026#34;jdbc:mysql://localhost/forJava?user=dev\u0026amp;#038;password=dev\u0026#34;, // null); PGSimpleDataSource pgSimpleDataSource = new PGSimpleDataSource(); pgSimpleDataSource.setServerName(\u0026#34;localhost\u0026#34;); pgSimpleDataSource.setDatabaseName(\u0026#34;dev\u0026#34;); pgSimpleDataSource.setUser(\u0026#34;dev\u0026#34;); pgSimpleDataSource.setPassword(\u0026#34;dev\u0026#34;); conn = pgSimpleDataSource.getConnection(); // conn = // DriverManager.getConnection(\u0026#34;jdbc:postgresql://localhost/test\u0026#34;, // \u0026#34;dev\u0026#34;, \u0026#34;dev\u0026#34;); showResultSet(conn.createStatement().executeQuery(\u0026#34;select * from cities\u0026#34;)); OracleDataSource oraDataSource = new OracleDataSource(); // oraDataSource.setServerName(\u0026#34;127.0.0.1\u0026#34;); // oraDataSource.setDatabaseName(\u0026#34;HR\u0026#34;); // oraDataSource.setUser(\u0026#34;HR\u0026#34;); // oraDataSource.setPassword(\u0026#34;HR\u0026#34;); oraDataSource.setURL(\u0026#34;jdbc:oracle:thin:hr/hr@//localhost:1521/XE\u0026#34;); conn = oraDataSource.getConnection(); stmt = conn.createStatement(); stmt.execute(\u0026#34;select * from tab\u0026#34;); showResultSet(stmt.getResultSet()); stmt.execute(\u0026#34;select * from jobs\u0026#34;); showResultSet(stmt.getResultSet()); stmt.execute(\u0026#34;select * from DEPARTMENTS\u0026#34;); showResultSet(stmt.getResultSet()); } static void showResultSet(ResultSet resultSet) throws SQLException { ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); int num = resultSetMetaData.getColumnCount(); while (resultSet.next()) { for (int i = 1; i \u0026amp;lt;= num; i++) { System.out.print(resultSetMetaData.getCatalogName(i) + \u0026#34; \u0026#34; + resultSet.getString(i)); } System.out.println(); } } } 呃，在自己电脑上同时安装了mysql, postgresql,oracle,db2,sqlite的人是不是很蛋疼？\n","date":"2011-11-15","externalUrl":null,"permalink":"/post/2011/11/15/jdbc-DataSource-connect-mysql-pgsql-oracle/","section":"Posts","summary":"import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import org.postgresql.ds.PGSimpleDataSource; import oracle.jdbc.pool.OracleDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; public class DBConnection { /** * @param args * @throws SQLException */ public static void main(String[] args) throws SQLException { // TODO Auto-generated method stub MysqlDataSource mysqlDataSource = new MysqlDataSource(); // mysqlDataSource.setPassword(\"dev\"); // mysqlDataSource.setUser(\"dev\"); mysqlDataSource .setURL(\"jdbc:mysql://localhost/forJava?user=dev\u0026#038;password=dev\"); Connection conn = mysqlDataSource.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate(\"create table if not exists web\\n\" + \"(\\n\" + \" id int not null primary key,\\n\" + \" name varchar(100),\\n\" + \" created timestamp,\\n\" + \" content blob\\n\" + \");\\n\" + \"\"); for (int i = 0; i \u0026lt; 1; i++) { stmt.executeUpdate(\"insert into web (name,content) values ('HL','frgertrhrtnthtohioh')\"); } showResultSet(stmt.executeQuery(\"select * from web limit 10\")); // Driver driver = new com.mysql.jdbc.Driver(); // driver.connect(\"jdbc:mysql://localhost/forJava?user=dev\u0026#038;password=dev\", // null); PGSimpleDataSource pgSimpleDataSource = new PGSimpleDataSource(); pgSimpleDataSource.setServerName(\"localhost\"); pgSimpleDataSource.setDatabaseName(\"dev\"); pgSimpleDataSource.setUser(\"dev\"); pgSimpleDataSource.setPassword(\"dev\"); conn = pgSimpleDataSource.getConnection(); // conn = // DriverManager.getConnection(\"jdbc:postgresql://localhost/test\", // \"dev\", \"dev\"); showResultSet(conn.createStatement().executeQuery(\"select * from cities\")); OracleDataSource oraDataSource = new OracleDataSource(); // oraDataSource.setServerName(\"127.0.0.1\"); // oraDataSource.setDatabaseName(\"HR\"); // oraDataSource.setUser(\"HR\"); // oraDataSource.setPassword(\"HR\"); oraDataSource.setURL(\"jdbc:oracle:thin:hr/hr@//localhost:1521/XE\"); conn = oraDataSource.getConnection(); stmt = conn.createStatement(); stmt.execute(\"select * from tab\"); showResultSet(stmt.getResultSet()); stmt.execute(\"select * from jobs\"); showResultSet(stmt.getResultSet()); stmt.execute(\"select * from DEPARTMENTS\"); showResultSet(stmt.getResultSet()); } static void showResultSet(ResultSet resultSet) throws SQLException { ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); int num = resultSetMetaData.getColumnCount(); while (resultSet.next()) { for (int i = 1; i \u0026lt;= num; i++) { System.out.print(resultSetMetaData.getCatalogName(i) + \" \" + resultSet.getString(i)); } System.out.println(); } } } 呃，在自己电脑上同时安装了mysql, postgresql,oracle,db2,sqlite的人是不是很蛋疼？\n","title":"jdbc使用DataSource连接mysql,postgresql,oracle的代码","type":"post"},{"content":"这是2011年8月份做过的一点实验，查了MSDN等等很多资料，基本搞明白了。\n这里我不会使用visual studio的图形界面工具，作为专业人士，还是搞懂自己的工具是怎么运转的，这样比较好。\n要使用的是visual studio的命令行工具，其实和gcc那堆工具对应关系挺明显的，大致如下：\ngcc cl ar lib ld link 文件后缀对应关系:\ngcc cl .a .lib .so .dll .o .obj 新手注意：这个命令提示符是\u0026quot;开始菜单\u0026quot;\u0026ndash;\u0026raquo;\u0026ldquo;Microsoft Visual studio\u0026rdquo;\u0026ndash;\u0026raquo;\u0026ldquo;Visual Tools\u0026rdquo; 那里的提示符，其实就是设置过一些环境变量的cmd。\n先把实验代码贴出来（可以先跳过不看），共有五个文件：\n1.build.bat\n\u0026#34;\u0026#34;C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\vcvarsall.bat\u0026#34;\u0026#34; x86 cd dynamic del /f /Q * cl /LD ..\\ext.c del ext.obj cl ..\\main.c /link ext.lib del main.obj cd .. cd static del /f /Q * cl /c ..\\ext.c lib ext.obj del ext.obj cl ..\\main-static.c /link ext.lib del main*.obj main*.exp main*.lib cd .. 2.库的头文件，ext.h\n#ifndef EXT_H #define EXT_H #ifdef Import #define Dll __declspec(dllimport) #else #ifdef Export #define Dll __declspec(dllexport) #else #define Dll #endif #endif #include \u0026lt;stdio.h\u0026gt; Dll typedef struct { int i; char c; } st; Dll extern int num; Dll void fun(); Dll extern st s; #endif 3.库的实现文件,ext.c\n#define Export #include \u0026#34;ext.h\u0026#34; st s={24,\u0026#39;h\u0026#39;}; int num=0; void fun(){ printf(\u0026#34;hello,i am in lib. num=%d\\n\u0026#34;,num++); } 4.以动态链接库方式使用这个库的程序代码，main.c\n#define Import #include \u0026#34;ext.h\u0026#34; #include \u0026#34;windows.h\u0026#34; #include\u0026lt;stdio.h\u0026gt; int main(){ fun(); printf(\u0026#34;num=%d\\n\u0026#34;,num); fun(); printf(\u0026#34;num=%d\\n\u0026#34;,num); printf(\u0026#34;st i=%d c=%c \\n\u0026#34;,s.i,s.c); } 5.以静态链接库方式使用这个库的程序代码，main-static.c\n#include \u0026#34;ext.h\u0026#34; #include \u0026#34;windows.h\u0026#34; #include\u0026lt;stdio.h\u0026gt; int main(){ fun(); printf(\u0026#34;num=%d\\n\u0026#34;,num); fun(); printf(\u0026#34;num=%d\\n\u0026#34;,num); printf(\u0026#34;st i=%d c=%c \\n\u0026#34;,s.i,s.c); } 一，静态链接库 要给用户提供静态链接库，都要提供哪些文件呢？.h头文件+.lib库文件 先问个问题： 使用库的最简单方式是什么？当然是不使用库！ 不使用库谁不会啊？使用如下命令：\ncl main-static.c ext.c 在当前目录下，就会编译，链接成一个main-static.exe，执行一下，没有问题！\n再问个问题：除了exe文件，还看见了什么？有ext.obj, main-static.obj, main-static.lib ,main-static.exp .\n.obj文件是目标文件（object file）， .lib文件是静态库文件（msdn中称为导入库）， .exp文件，导出文件，辅助作用（参看这里和这里） 可以猜想，用cl直接编译生成exe的过程，是\next.c\u0026mdash;\u0026raquo;ext.obj main-static.c\u0026ndash;\u0026raquo;main-static.obj main-static.obj\u0026mdash;\u0026raquo;main-static.exp+main-static.lib ext.obj+main-static.lib+main-static.exp\u0026mdash;\u0026raquo;main-static.exe 的过程\n我尝试了改换参数顺序，即用命令\ncl ext.c main-static.c 发现main-static.exp + main-static.lib换成了ext.lib+ext.exp 这说明是cl是先把.c文件都生成为obj文件，然后把第一个命令行参数指定xxx.c的xxx.obj文件再生成为xxx.lib+xxx.exp文件 这和静态链接有什么关系呢？关系密切啊！ 把除了ext.lib 以外的所有生成文件删除，运行如下命令:\ncl main-static.c /link ext.lib 就可以看见，生成了main-static.exe，运行正常。ext.lib就是静态链接库。 其实，这就是一个静态链接的过程。 静态链接的第一步，怎么由ext.c生成obj文件？查msdn（或者 命令cl /?）可知，给cl提供/c参数就可以了，先清掉那堆文件，运行\ncl /c ext.c 成功生成ext.obj.同理可以生成main-static.obj。 严格来说编译已经完成，接下来的，都是链接过程。\n第二步，怎么从obj文件生成.lib文件（加/DEF参数，就可以同时生成.exp文件)\nlib ext.obj 就生成了ext.lib 第三步，怎么从main-static.c + ext.lib 生成exe\ncl main-static.c /link ext.lib 需要注意的是，头文件一定要可以找到。\n二，动态链接库 visual studio的动态链接分为显式链接和隐式链接两种（http://msdn.microsoft.com/zh-cn/library/253b8k2c(v=vs.80).aspx），显式链接就是自己写代码调用win32 Api加载dll文件，要使用LoadLibrary, GetProcAddress(), FreeLibrary()这些函数，这个方法几乎不需要工具，就不说了，看看msdn的例子就明白了。 以下说的都是隐式链接\nvisual studio的动态链接库使用很与众不同，竟然需要提供三种文件：.h+.lib+.dll文件，诡异的是，这个.lib文件和静态链接库里的.lib文件其实不一样！ 更诡异的是，用户链接的时候，其实不需要.dll文件！\n对比之下，linux下的gcc，若是动态链接库，需要的是.so+.h 文件（.so相当于.dll），这是显著的差异。\n参看csdn(http://msdn.microsoft.com/zh-cn/library/3y1sfaz2(v=vs.100).aspx)可知，和静态链接不同的是，动态链接需要对库的源代码进行改动，添加\n__declspec( dllimport ) 或\n__declspec( dllexport ) 补充：也可以用def文件，不过我没试过，参看 http://msdn.microsoft.com/zh-cn/library/0b9xe492.aspx 和 http://msdn.microsoft.com/zh-cn/library/28d6s79h.aspx 。\n这个时候要注意，__declspec( dllimport )和__declspec( dllexport ) 的使用是不一样的，在库里导出的符号（函数，变量等，可以用dumpbin查看）应该使用__declspec( dllexport ) ，而使用库的代码，在包含头文件后，要有这些符号的声明，要看到__declspec( dllimport )形式的声明，所以这是有区别的。在上面ext.h里，我用了一个宏Dll来统一处理。 处理完源代码，就可以编译了。\n第一步，生成obj文件，\ncl /c ext.c 第二步，由obj文件生成dll文件+lib文件，\ncl /LD ext.obj 或者也可以用\nlink /DLL ext.obj 可以看到，生成了ext.dll , ext.exp, ext.lib 三个文件，其中的ext.lib 这个文件和静态链接时生成的ext.lib文件是不一样的！而且ext.dll在main.c的链接过程中是用不着的！ （此时已经生成了dll文件，要是使用显式链接，就把dll文件弄走，自己写代码去啦），继续隐式链接 第三步，不妨移走ext.dll，然后编译\ncl main.c /link ext.lib 可以看到生成main.exe，但是运行一下，会报告找不到ext.dll，当然找不到啦，只要把ext.dll移到当前目录下（或在dll文件查找路径中的一个中）即可正常运行。\n注：dll文件查找路径参看 http://msdn.microsoft.com/zh-cn/library/7d83bc18(v=vs.80).aspx\n这个领域关注的人少，鄙人理解浅薄，错误难免，欢迎指正。\n","date":"2011-11-13","externalUrl":null,"permalink":"/post/2011/11/13/visual-studio-static-dynamic-lib-dev-tools-usage/","section":"Posts","summary":"这是2011年8月份做过的一点实验，查了MSDN等等很多资料，基本搞明白了。\n这里我不会使用visual studio的图形界面工具，作为专业人士，还是搞懂自己的工具是怎么运转的，这样比较好。\n","title":"visual studio静态，动态链接库开发工具简单使用","type":"post"},{"content":"这是前几天笔试时，考场上想出来的算法，但是算了两次都不一样，最后只好蒙了一个选项，悲催！\n问题如下：\n给定一个整数序列，例如 4,2,6,3,7,1 ，该序列有多少个递增子序列？\n我的算法如下：\n记第i个元素为arr[i] ，记以第i个元素结尾的递增子序列有N[i]个，\n则，考虑以第i+1个元素结尾的所有递增子序列，\n首先置N[i+1]=1;//表示只有第i+1个元素一个元素的子序列。 对j=1...i 如果arr[j]\u0026lt;arr[i+1] N[i+1]+=N[j] 最后结果等于N数组里所有元素之和。\n","date":"2011-10-17","externalUrl":null,"permalink":"/post/2011/10/17/inc-sub-seq-count-algo/","section":"Posts","summary":"这是前几天笔试时，考场上想出来的算法，但是算了两次都不一样，最后只好蒙了一个选项，悲催！\n问题如下：\n给定一个整数序列，例如 4,2,6,3,7,1 ，该序列有多少个递增子序列？\n","title":"递增子序列数目计算的算法","type":"post"},{"content":"问题提出背景：在非结构化三角形网格生成过程中，若采用前沿推进法，在推进过程中是不好构造三角形的（而且也没有要），最好在把所有的边都连好以后再找出所有三角形，于是提出了问题：在由三角形构成的平面无向图中如何找出所有三角形？\n网格如图：\n要注意的是，这个无向图很特殊，\n1.这个图在平面上。\n2.这个图是由三角形构成的（如果不是由三角行构成，那这个网格就没有用处了）。\n我的算法如下，伪代码表示:\nforeach(点 p in所有的点){ foreach(点 np in p的所有邻居点){ foreach(点 nnpin np的所有邻居点){ if( p,np,nnp三点两两不重合 \u0026amp;\u0026amp; p,np,nnp三点两两相连 \u0026amp;\u0026amp; p==uniqPointOfTriangle(p,np,nnp) \u0026amp;\u0026amp; uniqPointOf2Points(np,nnp)==np) ){ 输出p,np,nnp构成的三角形。 } } } } 算法的关键在于uniqPointOfTriangle( )和uniqPointOf2Points( )这两个函数。\n这两个函数的原理相同， uniqPointOfTriangle( )uniqPointOf2Points（）唯一的作用是\n它的一个性质： 输出和输入参数的顺序无关。\n如果没有这两个函数的判断，每个三角形会被输出6次，而有了这两个函数的限制后，强制在3个元素的6中排列中指定1种，\n就消除了重复。\nuniqPointOfTriangle的实现我想了一个邪恶的办法:\nstruct point * uniqPointOfTriangle(struct point * a,struct point * b ,struct point * c){ /*返回一个3个点的特征点，即不管a,b,c的次序，这个函数返回结果唯一*/ struct point * p=NULL; //我直接比较指针大小，O(∩_∩)O哈哈~ p=(a \u0026gt; b?a:b); return p \u0026gt; c?p:c; } 还有一种正常一点的办法：\n思路是：对三个点，先在x方向找出最小的点，若有一个，直接返回；若有两个，找出y方向小的那个返回。\n另外，这样输出的三角形中其内部可能有其他的点，若要消除，再加上一层过滤，去除掉那些”p有邻点在p,np,nnp三角形中的”情况即可，\n这是因为这个图由三角形构成的特殊性质，如果有在p–np–nnp中有点，假设这些点都不和p相连，那么，\n这些点和p-np, p-nnp构成的区域必然不是三角形！所以可以这样干。\n","date":"2011-08-22","externalUrl":null,"permalink":"/post/2011/08/22/find-uniqPointOfTriangle/","section":"Posts","summary":"问题提出背景：在非结构化三角形网格生成过程中，若采用前沿推进法，在推进过程中是不好构造三角形的（而且也没有要），最好在把所有的边都连好以后再找出所有三角形，于是提出了问题：在由三角形构成的平面无向图中如何找出所有三角形？\n网格如图：\n要注意的是，这个无向图很特殊，\n1.这个图在平面上。\n2.这个图是由三角形构成的（如果不是由三角行构成，那这个网格就没有用处了）。\n我的算法如下，伪代码表示:\nforeach(点 p in所有的点){ foreach(点 np in p的所有邻居点){ foreach(点 nnpin np的所有邻居点){ if( p,np,nnp三点两两不重合 \u0026\u0026 p,np,nnp三点两两相连 \u0026\u0026 p==uniqPointOfTriangle(p,np,nnp) \u0026\u0026 uniqPointOf2Points(np,nnp)==np) ){ 输出p,np,nnp构成的三角形。 } } } } 算法的关键在于uniqPointOfTriangle( )和uniqPointOf2Points( )这两个函数。\n","title":"找出平面上的特殊无向图中的所有三角形的算法","type":"post"},{"content":" （以前在bmy bbs发过一次）\n这个方法试过在64位win7和32位winxp上可用。\n1，首先安装codeblockes，必须选完全安装（Full,All plugins,…），路径可以选默认路径即 C:/Program Files/CodeBlocks\n（如果安装到别的路径，后面提到的路径请自行对应更改）,一路点yes，装完后关掉codeblocks。\n2，汉化，把locale这个文件夹拷贝到 C:/Program Files/CodeBlocks/share/CodeBlocks/下（win7下需要管理员权限），确保最后的路径是这样的： C:/Program Files/CodeBlocks/share/CodeBlocks/locale/zh_CN/codeblocks.mo\n然后打开codeblocks，如果没有自动汉化，到settings-\u0026gt;environment-\u0026gt;选左侧view，找到右侧的internationalization里找中文选上，点OK后重启codeblocks，如果汉化不成功，请检查codeblocks.mo文件是否在正确的路径。\n3，安装含有gfortran的gcc，注意：最好选择一个不带空格的路径，更不要用中文路径，如C:/gcc ,把路径记住。\n4,让codeblocks使用gfortran，在codeblocks菜单中选 “设置”-\u0026gt;“编译器和调试器”，选中“GNU GCC 编译器”，点“复制”，然后输入一个好记的名字如“my Gfotran compiler”，打开“可执行工具链”，在“编译器安装目录”里找到C:/gcc，确定。\n然后把下面的“C编译器”，“C++编译器”，“动态库链接器”都选成gfortran.exe,“调试器”选gdb.exe,其他的都不要动，确定。安装完成。\n5，文件-\u0026gt;新建-\u0026gt;项目，选“empty project”，输入项目名，保存路径等，然后，在编译器下拉框中，必须选择“my Gfotran compiler”其余默认，建好项目后，选文件-\u0026gt;新建-\u0026gt;文件，选“empty file”，点 “…”按钮，输入一个以.f90结尾的文件名，并选中debug和release，确定。就可以在打开的编辑窗口中输入代码了。在菜单上有构建，调试等功能。\n参考链接：\nhttp://wiki.codeblocks.org/index.php?title=Installing_Fortran_Compiler\nhttp://www.d2school.com/codeblocks/doc/codeblocks_setup.html codeblocks下载页面(要上外网)\nhttp://www.codeblocks.org/downloads/26\nequation solution编译的gcc\nhttp://www.equation.com/servlet/equation.cmd?fa=fortran\n汉化包 http://www.d2school.com/codeblocks/download/d2school_codeblocks_chinese_locale.7z ","date":"2011-05-12","externalUrl":null,"permalink":"/post/2011/05/12/codeblocks-with-gfortran-for-fortran-dev/","section":"Posts","summary":" （以前在bmy bbs发过一次）\n这个方法试过在64位win7和32位winxp上可用。\n1，首先安装codeblockes，必须选完全安装（Full,All plugins,…），路径可以选默认路径即 C:/Program Files/CodeBlocks\n","title":"Codeblocks配合gfortran作为fortran开发环境的配置方法","type":"post"},{"content":"用于监控apache，如果退出就自动重启apache，原理是在如果apache退出，则apache的pid文件会被close，或触发attrib事件， 只要用inotify监控这个事件，然后重启apache，再重新监控pid文件即可。\n#!/bin/bash #Author:windydays #Date : 2010/8/29 #用于监控apache，如果退出就自动重启apache，原理是在如果apache退出，则apache的pid文件会被close，或触发attrib事件， #只要用inotify监控这个事件，然后重启apache，再重新监控pid文件即可。 #使用方法：用root权限运行即可。 #停止方法：sudo pkill inotifywait. pid_file=/var/run/apache2.pid mon_log=/var/log/apache2/mon-apache.sh.log if ! [ -f $pid_file ] ; then echo \u0026#39;pid file does NOT exist! maybe it is somewhere else or service is down now.\u0026#39; \u0026gt;\u0026gt;$mon_log; exit 1 ; fi #如果apache没有出问题，会阻塞在下面这里. inotifywait -q -e close $pid_file \u0026gt;\u0026gt;$mon_log; exit_code=$?; echo \u0026#34;At $(date)\u0026#34; \u0026gt;\u0026gt;$mon_log; #如果#?不等于0说明inotifywait不正常退出。 if [ $exit_code = 0 ];then /etc/init.d/apache2 restart sleep 8; exec $0; fi 领会意思即可，这个东西没有实用价值，有一些超时问题无法解决。\n实际上还是那种每隔两分钟，就发个HTTP请求，检测apache是否还活着的方式比较稳定可靠，例如monit。\n","date":"2010-08-29","externalUrl":null,"permalink":"/post/2010/08/29/apache-monitor-inotify-script/","section":"Posts","summary":"用于监控apache，如果退出就自动重启apache，原理是在如果apache退出，则apache的pid文件会被close，或触发attrib事件， 只要用inotify监控这个事件，然后重启apache，再重新监控pid文件即可。\n","title":"一个用inotify监控apache的脚本","type":"post"},{"content":"LAMP环境的一般入侵，大致经过sql注入，上传webshell，本地提权至root，安装rootkit等步骤。\n采用如下的配置，mod_chroot和单独分区挂载可以让本地提权变得极为困难，而mod-security可以封堵一般的sql注入等应用层攻击。\n以下内容是在ubuntu10.04上实践以后总结出来的,直接上命令，就不做过多解释了，水平有限，错误在所难免，欢迎指正。\n首先确保apache,php,mysql已经正常工作,出现问题的话，查看 /var/log/apache2, /var/log/syslog, /var/log/mysql/error.log 尤其是apparmor导致的权限错误，不易发现。\n1.mod_chroot的安装，配置\n目标是chroot到/var/www。\n(1) 安装:\nsudo service apache2 stop sudo apt-get libapache2-mod-chroot sudo vi /etc/apache2/mod-available/mod_chroot.conf 内容为\n\u0026lt;IfModulemod_chroot.c\u0026gt; LoadFile /lib/libgcc_s.so.1 ChrootDir /var/www \u0026lt;/IfModule\u0026gt; sudo a2enmodmod_chroot /etc/apache2/site-enabled/000-default 中 DocumentRoot 改为 /\nsudo ln -s /var/www/var/run/apache2.pid /var/run/apache2.pid 并把\nln -s /var/www/var/run/apache2.pid /var/run/apache2.pid 加到/etc/rc.local中\nsudo mkdir /var/www/tmp #放session sudo chmod 1777tmp sudo mkdir -p /var/www/var/run/mysqld sudo ln -s / /var/www/var/www 安装好apache,静态页面和php正常后，弄mysql\nsudo service mysql stop. 改/etc/apparmor.d/usr.sbin.mysqld， 把\u0026quot;/var/run/mysqld.sock w,\u0026ldquo;那一行复制并改为 \u0026ldquo;/var/www/var/run/mysqld.sock w,\u0026rdquo; 改/etc/mysql/my.cnf 在[client],[mysqld_safe],[mysqld]每一节里socket路径改为 \u0026ldquo;socket = /var/www/var/run/mysqld/mysqld.sock\u0026rdquo; sudo servicemysql start. 一些其他问题\ndate()不正常，解决办法：\nsudo mkdir -p /var/www/usr/share /var/www/etc sudo cp -rp /usr/share/zoneinfo /var/www/usr/share/ sudo cp /etc/localtime/var/www/etc/ DNS可能出问题(没试过)\nsudo cp /etc/resolv.conf/var/www/etc/resolv.conf 找不到错误页面\nsudo cp -rp /usr/share/apache2//var/www/usr/share/ 2.mod_security的安装，配置\n安装\nsudo aptitude install libapache2-mod-security2 sudo cp/usr/share/doc/mod-security-common/examples/rules/modsecurity_crs_10_config.conf /etc/apache2/mods-enabled/mod-security.conf sudo a2enmod mod-security sudo apache2ctl stop 修改 /etc/apache2/mods-available/mod-security.conf 中的 debug_log 和 audit_log 路径到合适的位置，并添加如下两行\nInclude/usr/share/doc/mod-security-common/examples/rules/modsecurity_crs_10_global_config.conf Include/usr/share/doc/mod-security-common/examples/rules/base_rules/*.conf sudo apache2ctl start #/usr/share/doc/mod-security-common/examples/rules/目录下还有一些规则可用\n参照gentoo的ebuild中的如下内容（http://gentoo-portage.com/www-apache/mod_security/ChangeLog）\nif ! use vanilla; then mv \u0026#34;${D}\u0026#34;${APACHE_MODULES_CONFDIR}/mod_security/modsecurity_*{41_phpids,50_outbound}*/ \u0026#34;${D}\u0026#34;${APACHE_MODULES_CONFDIR}/mod_security/optional_rules || die fi modsecurity_*{41_phpids,50_outbound}*的这几个规则还是不要用比较好。\n另外，http://www.gotroot.com/tiki-index.php?page=mod_security+rules 提供一些规则\n3.另外，可以把/var/www/放在一个单独的分区上，用noexec,nosuid,nodev参数挂载,打开mysql的apparmor,可以极大增强安全性。\n参考资料： /usr/share/doc/mod-chroot-common/\nhttp://core.segfault.pl/~hobbit/mod_chroot/index.html\nhttp://server.it168.com/a2010/0714/1077/000001077357.shtml\nhttp://www.howtoforge.com/chrooting-apache2-mod-chroot-debian-etch\n/usr/share/doc/mod-security-common\n后期补充一些：\nchroot的关键是确保/var/www/ ,/var/www/var/www都存在，并且/var/www/var/www 是指向/的符号连接\n对apache chroot的过程，我的理解（如有不对，欢迎指正）是\n先初始化其他模块 chroot到/var/www 改变当前目录到/var/www(此时实际上是改变到/var/www/var/www目录下) 当http请求到来时，比如http://127.0.0.1/index.php,根据vhost的配置（如果documentroot是/，网页实际放在/var/www/下的话），apache寻找/index.php(实际上是/var/www/index.php) 另外，还有个诊断方法是查看/proc//root和/proc//cwd，一个是apache的root路径，一个是apache的当前目录（是apache的pid ），再看看apache的访问日志\n","date":"2010-08-17","externalUrl":null,"permalink":"/post/2010/08/17/apache-security-enhancement-using-mod_chroot-mod_security/","section":"Posts","summary":"LAMP环境的一般入侵，大致经过sql注入，上传webshell，本地提权至root，安装rootkit等步骤。\n采用如下的配置，mod_chroot和单独分区挂载可以让本地提权变得极为困难，而mod-security可以封堵一般的sql注入等应用层攻击。\n","title":"apache的安全增强配置(使用mod_chroot,mod_security)","type":"post"},{"content":"","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"写 C++ ，关注高性能数据结构、并发、Linux 与密码学。\n","externalUrl":null,"permalink":"/about/","section":"Tech Explorer","summary":"写 C++ ，关注高性能数据结构、并发、Linux 与密码学。\n","title":"关于","type":"page"}]