Linux 多线程服务端编程:使用 muduo C++ 网络库

截至 2022 年 5 月,共记重印 23 次,总印数 33200 册。

出版社:电子工业出版社

页数:xvi+600

ISBN:9787121192821

2013年1月第一次印刷,印数 3000 册。

2013年3月第二次印刷,印数 3000 册。

2013年7月第三次印刷,印数 3000 册。重印感言

2014年9月第四次印刷,印数 1500 册。

2015年2月第五次印刷,印数 2000 册。

2015年9月第六次印刷,印数 1500 册。

2016年4月第七次印刷,印数 1500 册。

2016年10月第八次印刷,印数 1500 册。

2017年6月第九次印刷,印数 1500 册。

2017年11月第十次印刷,印数 800 册。

2018年3月第十一次印刷,印数 800 册。

2018年5月第十二次印刷,印数 1500 册。

2018年11月第十三次印刷,印数 1000 册。

2019年3月第十四次印刷,印数 1000 册。

2019年6月第十五次印刷,印数 1000 册。

2019年11月第十六次印刷,印数 1000 册。

2020年4月第十七次印刷,印数 800 册。

2020年6月第十八次印刷,印数 800 册。

2020年9月第十九次印刷,印数 1000 册。

2013年10月,电子书在京东和亚马逊上市

2015年4月,多看电子书及繁体版上市

快捷链接: 电子书 | 试读 | 读者赞誉 | 目录 | 代码 | 购买 | 勘误表 | URL清单

看完了 W. Richard Stevens 的传世经典《UNIX 网络编程》, 能照着例子用 Sockets API 编写 echo 服务, 却仍然对稍微复杂一点的网络编程任务感到无从下手? 书中示例代码把业务逻辑和 Sockets 调用混在一起,似乎不利于将来扩展? 网络编程中的遇到一些具体问题该怎么办?例如

读过《UNIX 环境高级编程》,想用多线程来发挥多核 CPU 的效率, 但对程序该用哪种多线程模型感到一头雾水? 有没有值得推荐的适用面广的多线程 IO 模型? 互斥器、条件变量、读写锁、信号量这些底层同步原语哪些该用哪些不该用? 有没有更高级的同步设施能简化开发? 《UNIX 网络编程(第二卷)》介绍的那些琳琅满目的IPC机制到底用哪个才能兼顾开发效率与可伸缩性?

网络编程和多线程编程的基础打得差不多,开始实际做项目了,更多问题扑面而来:

这本《Linux 多线程服务端编程:使用 muduo C++ 网络库》中,作者凭借多年的工程实践经验试图解答以上疑问。当然,内容还远不止这些……

内容简介

本书主要讲述采用现代 C++ 在 x86-64 Linux 上编写多线程 TCP 网络服务程序的主流常规技术, 重点讲解一种适应性较强的多线程服务器的编程模型, 即 one loop per thread。这是在 Linux 下以 native 语言编写用户态高性能网络程序最成熟的模式, 掌握之后可顺利地开发各类常见的服务端网络应用程序。 本书以 muduo 网络库为例,讲解这种编程模型的使用方法及注意事项。

本书的宗旨是贵精不贵多。 掌握两种基本的同步原语就可以满足各种多线程同步的功能需求, 还能写出更易用的同步设施。 掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务, 编写运行于公司内网环境的分布式服务统。

作者简介

陈硕,北京师范大学硕士,擅长 C++ 多线程网络编程和实时分布式系统架构。 曾在摩根士丹利 IT 部门工作 5 年,从事实时外汇交易系统开发。 现在在美国加州硅谷某互联网大公司工作,从事大规模分布式系统的可靠性工程。 编写了开源 C++ 网络库 muduo,参与翻译了 《代码大全( 第2 版)》《C++ 编程规范(繁体版)》, 整理了《C++ Primer (第4 版)(评注版)》, 并曾多次在各地技术大会演讲。

试读样章

豆瓣页面及网上书店购买

资源

读者赞誉

国内的作品,我则高度推荐陈硕的《Linux多线程服务端编程》。 这本书的名字赶跑了不少潜在的读者,所以我要特别说明一下。 这本书是C++开发的高水平作品,与其说是教你怎么用C++写服务端开发, 不如说是教你如何以服务端开发为例子提升C++开发水平。 前面几本书都是谈标准C++自己的事情,碰到像iostream这样失败的标准组件也不得不硬着头皮介绍。 而这本书是接地气的实践结晶,告诉你面对具体问题时应怎样权衡,C++里什么好用, 什么不好用,为什么,等等。 ——孟岩

在用C++设计服务器程序方面是独一无二的佳作。 ——xjyu007

很好的C++工程实践经验,特别对指针(对象生命周期)的管理让我受益匪浅。 ——LoveU4Ever

个人认为是学习网络编程的首选, muduo的reactor设计堪称优雅. 曾经想在windows上重新实现一下, 但是最终还是放弃了, windows很难有一个像样的reactor的网络库. 同libevent相比, muduo的设计很清晰易懂, 效率也更高. ——丹尼羽沐风

作为难得的国内在C++领域浸淫多年的一线专家, 作者的这本书堪称国内Linux高性能网络服务器编程里程碑之作。 我看过一些Muduo的源码,获益良多, 无论从代码质量还是编码规范还是架构设计都是教科书级的,一定买来收藏。 ——瘦马不瘦

作为一名嵌入式工程师,@bnu_chenshuo 的书还是让我看呆了。 自己也读过C++的著作,但都无法坚持下来,太多语法糖的罗列让我感觉在读一份事不关己律师函。 muduo C++却让我真正看到了我需要的层次——操作系统与计算机科学原理,什么环境和设计会诱发何种bug以及达到什么样的性能,这是真正相通的部分。 ——我的上铺叫路遥

把@bnu_chenshuo 的大作《Linux多线程服务端编程-使用muduo C++网络库》带了本回来。 自己不搞网络编程,工作也不用Linux,代码也是半C艹半C井,写C++也是COM那一套,混用ATL、WRL和改过的STL,和此书题目几乎不搭界。 但此书内容深得我心,只有第二部分是网络库,其他全是C++和工程经验。 ——wingc

虽然不做linux和网络编程,买了一本@bnu_chenshuo 的《Linux多线程服务端编程:使用muduo C++网络库》偶尔学习学习。 书里面大量的脚注和个人见解,是我喜欢的类型。 ——老师木

《linux多线程服务端编程:使用muduo C++网络库》。 很不错的书,看得出作者是下了功夫的。 虽然自己不用C++,但是设计中的思路和对一些“坑”的见解非常值得学习。 在一些问题的阐述中,引用了不少文献并予以列出,而这些文献又是非常好的阅读材料。感谢作者。 ——狂徒大作

有过写服务器程序的经验,但是从来没有系统化过知识, 陈硕的这本书让我很好的从系统化了相关的知识, 并且了解到了之前从没有思考过的一些问题。 ——编号一百零二

里面还是很多值得一读的经验和技术总结, 如果从事linux、后台服务、网络开发中任何一种都可以从里面找到可借鉴的东西, 美中不足的是文章形有点散。 ——gongwt

相当不错的书,期待了好久了,准备多看几遍。 第9章以后的知识比较散,但都是工程实践中的精华,也值得细看。 ——dogandwolf

我对于C++仍然持谨慎态度,但作者显然已经浸淫多年、游刃有余了。 这本书的内容更多的是实践经验之谈,不是一般的教程。 对于C++,多线程,TCP/IP中的诸多陷阱都做了精辟的阐述。 附带还有设计一个C++网络库的思路与最佳实践,国内不可多得的原创精品。 ——bigship

前言(节选)

本书主要讲述采用现代 C++ 在 x86-64 Linux 上编写多线程 TCP 网络服务程序的主流常规技术,这也是我对过去 5 年编写生产环境下的多线程服务端程序的经验总结。 本书重点讲解多线程网络服务器的一种 IO 模型,即 one loop per thread。这是一种适应性较强的模型,也是 Linux 下以 native 语言编写用户态高性能网络程序最成熟的模式, 掌握之后可顺利地开发各类常见的服务端网络应用程序。 本书以 muduo 网络库为例,讲解这种编程模型的使用方法及注意事项。

muduo 是一个基于非阻塞 IO 和事件驱动的现代 C++ 网络库, 原生支持 one loop per thread 这种 IO 模型。muduo 适合开发 Linux 下的面向业务的多线程服务端网络应用程序,其中“面向业务的网络编程”的定义见附录 A。 “现代 C++”指的不是 C++11 新标准,而是 2005 年 TR1 发布之后的 C++ 语言和库。 与传统 C++ 相比,现代 C++ 的变化主要有两方面:资源管理(见第 1 章)与事件回调(见第 449 页)。

本书不是多线程编程教程(不会介绍 Pthreads 函数的基本用法,只在 §4.1 简单列举了最基本的 Pthreads 函数), 也不是网络编程教程(不会介绍 BSD Sockets API 的基本功能,只在 §6.1 简单回顾了 Sockets API 的用法), 更不是 C++ 教程(不会介绍 C++ 语法及 STL 基础知识)。 读者应该已经大致读过《现代操作系统》、《UNIX 环境高级编程》、 《UNIX 网络编程》、《C++ Primer》或与之内容相近的书籍, 熟悉基本概念,并掌握 Pthreads 和 Sockets API 的常规用法。 本书不谈 C++11,因为目前(2012 年)主流的 Linux 服务端发行版的 g++ 版本都还停留在 4.4, C++11 进入实用尚需一段时日。

本书适用的硬件环境是主流 x86-64 服务器,多路多核 CPU、几十 GB 内存、千兆以太网互联。 除了第 5 章讲诊断日志之外,本书不涉及文件 IO。

本书分为四大部分,第 1 部分“C++ 多线程系统编程”考察多线程下的对象生命期管理、线程同步方法、多线程与 C++ 的结合、高效的多线程日志等。 第 2 部分“muduo 网络库”介绍使用现成的非阻塞网络库编写网络应用程序的方法,以及 muduo 的设计与实现。 第 3 部分“工程实践经验谈”介绍分布式系统的工程化开发方法和 C++ 在工程实践中的功能特性取舍。 第 4 部分“附录”分享网络编程和 C++ 语言的学习经验。

本书的宗旨是贵精不贵多。掌握两种基本的同步原语就可以满足各种多线程同步的功能需求, 还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务, 编写运行于公司内网环境的分布式服务系统。(本书不涉及分布式存储系统,也不涉及 UDP。)

术语与排版范例

本书大量使用英文术语,甚至有少量英文引文。 设计模式的名字一律用英文,例如 Observer、Reactor、Singleton。 在中文术语不够突出时,也会使用英文,例如 class、heap、event loop、STL algorithm 等。 注意几个中文 C++ 术语:对象实体(instance) 、函数重载决议(resolution) 、模板具现化(instantiation) 、覆写(override)虚函数、提领(dereference)指针。 本书中的英语可数名词一般不用复数形式,例如两个 class,6 个 syscall; 但有时会用 (s) 强调中文名词是复数。fd 是文件描述符(file descriptor)的缩写。 “CPU 数目”一般指的是核(core)的数目。 用诸如§11.5 表示本书第 11.5 节,L42 表示上下文中出现的第 42 行代码。 JCPCC2e 等是参考文献,见书末清单。

代码

本书的示例代码以开源项目的形式发布在 GitHub 上, 地址是 http://github.com/chenshuo/recipes/http://github.com/chenshuo/muduo/ 。 本书配套页面提供全部源代码打包下载,正文中出现的类似 recipes/thread 的路径是压缩包内的相对路径, 读者不难找到其对应的 GitHub URL。

本书假定读者熟悉 diff -u 命令的输出格式,用于表示代码的改动。

本书正文中出现的代码有时为了照顾排版而略有改写,例如改变缩进规则,去掉单行条件语句前后的花括号等。 就编程风格而论,应以电子版代码为准。

联系方式

邮箱:giantchen_at_gmail.com

主页:http://chenshuo.com/book (正文和脚注中出现的 URL 可从这里找到。 )

微博:http://weibo.com/giantchen

博客:http://blog.csdn.net/Solstice

代码:http://github.com/chenshuo

陈硕

中国•香港

目录

第 1 部分 C++ 多线程系统编程
第 1 章 线程安全的对象生命期管理
1.1 当析构函数遇到多线程
1.1.1 线程安全的定义
1.1.2 MutexLock 与 MutexLockGuard
1.1.3 一个线程安全的 Counter 示例
1.2 对象的创建很简单
1.3 销毁太难
1.3.1 mutex 不是办法
1.3.2 作为数据成员的 mutex 不能保护析构
1.4 线程安全的 Observer 有多难
1.5 原始指针有何不妥
1.6 神器 shared_ptr/weak_ptr
1.7 插曲:系统地避免各种指针错误
1.8 应用到 Observer 上
1.9 再论 shared_ptr 的线程安全
1.10 shared_ptr 技术与陷阱
1.11 对象池
1.11.1 enable_shared_from_this
1.11.2 弱回调
1.12 替代方案
1.13 心得与小结
1.14 Observer 之谬
第 2 章 线程同步精要
2.1 互斥器(mutex)
2.1.1 只使用非递归的 mutex
2.1.2 死锁
2.2 条件变量(condition variable)
2.3 不要用读写锁和信号量
2.4 封装 MutexLock、MutexLockGuard、Condition
2.5 线程安全的 Singleton 实现
2.6 sleep(3) 不是同步原语
2.7 归纳与总结
2.8 借 shared_ptr 实现 copy-on-write
第 3 章 多线程服务器的适用场合与常用编程模型
3.1 进程与线程
3.2 单线程服务器的常用编程模型
3.3 多线程服务器的常用编程模型
3.3.1 one loop per thread
3.3.2 线程池
3.3.3 推荐模式
3.4 进程间通信只用 TCP
3.5 多线程服务器的适用场合
3.5.1 必须用单线程的场合
3.5.2 单线程程序的优缺点
3.5.3 适用多线程程序的场景
3.6 “多线程服务器的适用场合”例释与答疑
第 4 章 C++ 多线程系统编程精要
4.1 基本线程原语的选用
4.2 C/C++ 系统库的线程安全性
4.3 Linux 上的线程标识
4.4 线程的创建与销毁的守则
4.4.1 pthread_cancel 与 C++
4.4.2 exit(3) 在 C++ 中不是线程安全的
4.5 善用 __thread 关键字
4.6 多线程与 IO
4.7 用 RAII 包装文件描述符
4.8 RAII 与 fork()
4.9 多线程与 fork()
4.10 多线程与 signal
4.11 Linux 新增系统调用的启示
第 5 章 高效的多线程日志
5.1 功能需求
5.2 性能需求
5.3 多线程异步日志
5.4 其他方案
第 2 部分 muduo 网络库
第 6 章 muduo 网络库简介
6.1 由来
6.2 安装
6.3 目录结构
6.3.1 代码结构
6.3.2 例子
6.3.3 线程模型
6.4 使用教程
6.4.1 TCP 网络编程本质论
6.4.2 echo 服务的实现
6.4.3 七步实现 finger 服务
6.5 性能评测
6.5.1 muduo 与 Boost.Asio、libevent2 的吞吐量对比
6.5.2 击鼓传花:对比 muduo 与 libevent2 的事件处理效率
6.5.3 muduo 与 Nginx 的吞吐量对比
6.5.4 muduo 与 ZeroMQ 的延迟对比
6.6 详解 muduo 多线程模型
6.6.1 数独求解服务器
6.6.2 常见的并发网络服务程序设计方案
第 7 章 muduo 编程示例
7.1 五个简单 TCP 示例
7.2 文件传输
7.3 Boost.Asio 的聊天服务器
7.3.1 TCP 分包
7.3.2 消息格式
7.3.3 编解码器 LengthHeaderCodec
7.3.4 服务端的实现
7.3.5 客户端的实现
7.4 muduo Buffer 类的设计与使用
7.4.1 muduo 的 IO 模型
7.4.2 为什么 non-blocking 网络编程中应用层 buffer 是必需的
7.4.3 Buffer 的功能需求
7.4.4 Buffer 的数据结构
7.4.5 Buffer 的操作
7.4.6 其他设计方案
7.4.7 性能是不是问题
7.5 一种自动反射消息类型的 Google Protobuf 网络传输方案
7.5.1 网络编程中使用 Protobuf 的两个先决条件
7.5.2 根据 type name 反射自动创建 Message 对象
7.5.3 Protobuf 传输格式
7.6 在 muduo 中实现 Protobuf 编解码器与消息分发器
7.6.1 什么是编解码器(codec)
7.6.2 实现 ProtobufCodec
7.6.3 消息分发器(dispatcher)有什么用
7.6.4 ProtobufCodec 与 ProtobufDispatcher 的综合运用
7.6.5 ProtobufDispatcher 的两种实现
7.6.6 ProtobufCodec 和 ProtobufDispatcher 有何意义
7.7 限制服务器的最大并发连接数
7.7.1 为什么要限制并发连接数
7.7.2 在 muduo 中限制并发连接数
7.8 定时器
7.8.1 程序中的时间
7.8.2 Linux 时间函数
7.8.3 muduo 的定时器接口
7.8.4 Boost.Asio Timer 示例
7.8.5 Java Netty 示例
7.9 测量两台机器的网络延迟和时间差
7.10 用 timing wheel 踢掉空闲连接
7.10.1 timing wheel 原理
7.10.2 代码实现与改进
7.11 简单的消息广播服务
7.12 “串并转换”连接服务器及其自动化测试
7.13 socks4a 代理服务器
7.13.1 TCP 中继器
7.13.2 socks4a 代理服务器
7.13.3 N : 1 与 1 : N 连接转发
7.14 短址服务
7.15 与其他库集成
7.15.1 UDNS
7.15.2 c-ares DNS
7.15.3 curl
7.15.4 更多
第 8 章 muduo 网络库设计与实现
8.0 什么都不做的 EventLoop
8.1 Reactor 的关键结构
8.1.1 Channel class
8.1.2 Poller class
8.1.3 EventLoop 的改动
8.2 TimerQueue 定时器
8.2.1 TimerQueue class
8.2.2 EventLoop 的改动
8.3 EventLoop::runInLoop() 函数
8.3.1 提高 TimerQueue 的线程安全性
8.3.2 EventLoopThread class
8.4 实现 TCP 网络库
8.5 TcpServer 接受新连接
8.5.1 TcpServer class
8.5.2 TcpConnection class
8.6 TcpConnection 断开连接
8.7 Buffer 读取数据
8.7.1 TcpConnection 使用 Buffer 作为输入缓冲
8.7.2 Buffer::readFd()
8.8 TcpConnection 发送数据
8.9 完善 TcpConnection
8.9.1 SIGPIPE
8.9.2 TCP No Delay 和 TCP keepalive
8.9.3 WriteCompleteCallback 和 HighWaterMarkCallback
8.10 多线程 TcpServer
8.11 Connector
8.12 TcpClient
8.13 epoll
8.14 测试程序一览
第 3 部分 工程实践经验谈
第 9 章 分布式系统工程实践
9.1 我们在技术浪潮中的位置
9.1.1 分布式系统的本质困难
9.1.2 分布式系统是个险恶的问题
9.2 分布式系统的可靠性浅说
9.2.1 分布式系统的软件不要求 7 × 24 可靠
9.2.2 “能随时重启进程”作为程序设计目标
9.3 分布式系统中心跳协议的设计
9.4 分布式系统中的进程标识
9.4.1 错误做法
9.4.2 正确做法
9.4.3 TCP 协议的启示
9.5 构建易于维护的分布式程序
9.6 为系统演化做准备
9.6.1 可扩展的消息格式
9.6.2 反面教材:ICE 的消息打包格式
9.7 分布式程序的自动化回归测试
9.7.1 单元测试的能与不能
9.7.2 分布式系统测试的要点
9.7.3 分布式系统的抽象观点
9.7.4 一种自动化的回归测试方案
9.7.5 其他用处
9.8 分布式系统部署、监控与进程管理的几重境界
9.8.1 境界 1:全手工操作
9.8.2 境界 2:使用零散的自动化脚本和第三方组件
9.8.3 境界 3:自制机群管理系统,集中化配置
9.8.4 境界 4:机群管理与 naming service 结合
第 10 章 C++ 编译链接模型精要
10.1 C 语言的编译模型及其成因
10.1.1 为什么 C 语言需要预处理
10.1.2 C 语言的编译模型
10.2 C++ 的编译模型
10.2.1 单遍编译
10.2.2 前向声明
10.3 C++ 链接(linking)
10.3.1 函数重载
10.3.2 inline 函数
10.3.3 模板
10.3.4 虚函数
10.4 工程项目中头文件的使用规则
10.4.1 头文件的害处
10.4.2 头文件的使用规则
10.5 工程项目中库文件的组织原则
10.5.1 动态库是有害的
10.5.2 静态库也好不到哪儿去
10.5.3 源码编译是王道
第 11 章 反思 C++ 面向对象与虚函数
11.1 朴实的 C++ 设计
11.2 程序库的二进制兼容性
11.2.1 什么是二进制兼容性
11.2.2 有哪些情况会破坏库的 ABI
11.2.3 哪些做法多半是安全的
11.2.4 反面教材:COM
11.2.5 解决办法
11.3 避免使用虚函数作为库的接口
11.3.1 C++ 程序库的作者的生存环境
11.3.2 虚函数作为库的接口的两大用途
11.3.3 虚函数作为接口的弊端
11.3.4 假如 Linux 系统调用以 COM 接口方式实现
11.3.5 Java 是如何应对的
11.4 动态库接口的推荐做法
11.5 以 boost::function 和 boost::bind 取代虚函数
11.5.1 基本用途
11.5.2 对程序库的影响
11.5.3 对面向对象程序设计的影响
11.6 iostream 的用途与局限
11.6.1 stdio 格式化输入输出的缺点
11.6.2 iostream 的设计初衷
11.6.3 iostream 与标准库其他组件的交互
11.6.4 iostream 在使用方面的缺点
11.6.5 iostream 在设计方面的缺点
11.6.6 一个 300 行的 memory buffer output stream
11.6.7 现实的 C++ 程序如何做文件 IO
11.7 值语义与数据抽象
11.7.1 什么是值语义
11.7.2 值语义与生命期
11.7.3 值语义与标准库
11.7.4 值语义与 C++ 语言
11.7.5 什么是数据抽象
11.7.6 数据抽象所需的语言设施
11.7.7 数据抽象的例子
第 12 章 C++ 经验谈
12.1 用异或来交换变量是错误的
12.1.1 编译器会分别生成什么代码
12.1.2 为什么短的代码不一定快
12.2 不要重载全局 ::operator new()
12.2.1 内存管理的基本要求
12.2.2 重载 ::operator new() 的理由
12.2.3 ::operator new() 的两种重载方式
12.2.4 现实的开发环境
12.2.5 重载 ::operator new() 的困境
12.2.6 解决办法:替换 malloc()
12.2.7 为单独的 class 重载 ::operator new() 有问题吗
12.2.8 有必要自行定制内存分配器吗
12.3 带符号整数的除法与余数
12.3.1 语言标准怎么说
12.3.2 C/C++ 编译器的表现
12.3.3 其他语言的规定
12.3.4 脚本语言解释器代码
12.3.5 硬件实现
12.4 在单元测试中 mock 系统调用
12.4.1 系统函数的依赖注入
12.4.2 链接期垫片(link seam)
12.5 慎用匿名 namespace
12.5.1 C 语言的 static 关键字的两种用法
12.5.2 C++ 语言的 static 关键字的四种用法
12.5.3 匿名 namespace 的不利之处
12.5.4 替代办法
12.6 采用有利于版本管理的代码格式
12.6.1 对 diff 友好的代码格式
12.6.2 对 grep 友好的代码风格
12.6.3 一切为了效率
12.7 再探 std::string
12.7.1 直接拷贝(eager copy)
12.7.2 写时复制(copy-on-write)
12.7.3 短字符串优化(SSO)
12.8 用 STL algorithm 轻松解决几道算法面试题
12.8.1 用 next_permutation() 生成排列与组合
12.8.2 用 unique() 去除连续重复空白
12.8.3 用 {make,push,pop}_heap() 实现多路归并
12.8.4 用 partition() 实现“重排数组,让奇数位于偶数前面”
12.8.5 用 lower_bound() 查找 IP 地址所属的城市
第 4 部分 附录
附录 A 谈一谈网络编程学习经验
附录 B 从《C++ Primer(第 4 版)》入手学习 C++
附录 C 关于 Boost 的看法
附录 D 关于 TCP 并发连接的几个思考题与试验
参考文献

代码

下载: bookcode.tar.bz2 bookcode.tar.gz
镜像: bookcode.tar.bz2 bookcode.tar.gz

md5sum:
82f0affcbb67e30023fdfc7a22396851  bookcode.tar.bz2
5e524bee2de6d0bb88b23ec2732bc775  bookcode.tar.gz

sha1sum:
4c9cbb2d76d7309b679686787b01e81bc1a6e22e  bookcode.tar.bz2
876e7fa976d4e35fc6995054e4d4d90c3cf29e77  bookcode.tar.gz

on Github.com:

本地镜像(不常更新):

留言

 

blog comments powered by Disqus