├── .gitignore ├── README.md ├── img ├── 20141226161119981.jpg ├── 20141226173222750.jpg ├── 20151224173731480.png ├── 20160711221626477.png ├── 20160711221642431.png ├── README ├── linux-ls.png ├── linux_cpu_info.png └── webbench.png └── src ├── c_plus_cplus ├── linux_chinese_in_c_plus_plus.md ├── reflection_in_c++_1.md ├── reflection_in_c++_2.md └── reflection_in_c++_3.md ├── code_reading ├── memorypool.md ├── threadpool.md ├── tinyhttpd.md ├── tornado-memcached-sessions.md └── webbench.md ├── linux ├── linux_cpu_info.md └── ls_implementation.md └── open_source_components ├── google_gflags.md ├── google_glog.md ├── google_gtest.md ├── my_docker_summary_1.md └── my_docker_summary_2.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | *.DS_Store 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Articles 2 | 3 | ## 源码剖析 4 | [threadpool —— 基于 pthread 实现的简单线程池](https://github.com/AngryHacker/articles/blob/master/src/code_reading/threadpool.md) 5 | 6 | [tinyhttpd —— C 语言实现最简单的 HTTP 服务器](https://github.com/AngryHacker/articles/blob/master/src/code_reading/tinyhttpd.md) 7 | 8 | [Webbench —— 简洁而优美的压力测试工具](https://github.com/AngryHacker/articles/blob/master/src/code_reading/webbench.md) 9 | 10 | [MemoryPool —— 简单高效的内存池 allocator 实现](https://github.com/AngryHacker/articles/blob/master/src/code_reading/memorypool.md) 11 | 12 | [tornado-memcached-sessions —— Tornado session 支持的实现](https://github.com/AngryHacker/articles/blob/master/src/code_reading/tornado-memcached-sessions.md) 13 | 14 | ## 开源组件 15 | [google gflags 库完全使用](https://github.com/AngryHacker/articles/blob/master/src/open_source_components/google_gflags.md) 16 | 17 | [google glog 简单使用小结](https://github.com/AngryHacker/articles/blob/master/src/open_source_components/google_glog.md) 18 | 19 | [google gtest 快速入门](https://github.com/AngryHacker/articles/blob/master/src/open_source_components/google_gtest.md) 20 | 21 | [我眼中的 Docker(一)docker、vm、lxc](https://github.com/AngryHacker/articles/blob/master/src/open_source_components/my_docker_summary_1.md) 22 | 23 | [我眼中的 Docker(二)Image](https://github.com/AngryHacker/articles/blob/master/src/open_source_components/my_docker_summary_2.md) 24 | 25 | ## C++ 26 | [Linux C++ 中文处理](https://github.com/AngryHacker/articles/blob/master/src/c_plus_cplus/linux_chinese_in_c_plus_plus.md) 27 | 28 | [C++ 实现反射(一)](https://github.com/AngryHacker/articles/blob/master/src/c_plus_cplus/reflection_in_c%2B%2B_1.md) 29 | 30 | [C++实现反射(二)](https://github.com/AngryHacker/articles/blob/master/src/c_plus_cplus/reflection_in_c%2B%2B_2.md) 31 | 32 | [C++实现反射(三)](https://github.com/AngryHacker/articles/blob/master/src/c_plus_cplus/reflection_in_c%2B%2B_3.md) 33 | 34 | ## Linux 35 | [ls 命令的实现](https://github.com/AngryHacker/articles/blob/master/src/linux/ls_implementation.md) 36 | 37 | [linux cpu 信息分析](https://github.com/AngryHacker/articles/blob/master/src/linux/linux_cpu_info.md) -------------------------------------------------------------------------------- /img/20141226161119981.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/20141226161119981.jpg -------------------------------------------------------------------------------- /img/20141226173222750.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/20141226173222750.jpg -------------------------------------------------------------------------------- /img/20151224173731480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/20151224173731480.png -------------------------------------------------------------------------------- /img/20160711221626477.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/20160711221626477.png -------------------------------------------------------------------------------- /img/20160711221642431.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/20160711221642431.png -------------------------------------------------------------------------------- /img/README: -------------------------------------------------------------------------------- 1 | here is images 2 | -------------------------------------------------------------------------------- /img/linux-ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/linux-ls.png -------------------------------------------------------------------------------- /img/linux_cpu_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/linux_cpu_info.png -------------------------------------------------------------------------------- /img/webbench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngryHacker/articles/95386c1335d469e6868533e385d6fe8578ec2a5f/img/webbench.png -------------------------------------------------------------------------------- /src/c_plus_cplus/linux_chinese_in_c_plus_plus.md: -------------------------------------------------------------------------------- 1 | # Linux C++ 中文处理 2 | 3 | ## 背景 4 | C++ 对于中文的处理是很蛋疼的事情,然而,不幸的我们接到命令,要在 Linux 下支持对文案进行文案超长截断处理。这样的话应该怎么做呢? 5 | 6 | ## UTF-8 介绍 7 | 首先,我们可以假定我们接受到的字符串是 UTF-8 编码的。如果在本地的话可以通过本地环境配置来保证。命令行下运行 `locale` 命令,LC_CTYPE 应该是 UTF-8 的。vim 打开文件敲下 `:set` 命令,应该有一行是 `fileencoding=utf-8`。这样我们就有了工作的基础。 8 | 9 | UTF-8 是对 Unicode 字符集的实现,它是一种变长编码,对于一个Unicode 的字符编码成 1 至 4 个字节。我们可以认为,在 UTF-8 中,英文是 1 个字节,中文是 3 个字节。 10 | 11 | UTF-8 的详细介绍可以看: 12 | * [Unicode 和 UTF-8 有何区别? — 知乎](https://www.zhihu.com/question/23374078) 13 | 14 | * [UTF-8 — 维基百科](https://zh.wikipedia.org/wiki/UTF-8) 15 | 16 | ## 设计思路 17 | 既然知道 UTF-8 的中英文字符字节长度,那我们可能想用这样一个方案:遍历字符串,判断当前字节属于中文还是英文,如果英文则对长度加一并从下一个字节继续处理,如果是中文则对长度加一并跳到后面第三个字节继续处理。达到我们需要的文案长度时,break 跳出循环,返回当前遍历得到的子字符串。 18 | 19 | 但是这样的实现会感觉很 hack,有点暴力,程序容易写出问题。而且我们前面的假设毕竟是一般情况下(虽然概率很低),如果出现一个四字节的字符那程序会错得一塌糊涂。 20 | 21 | 如果有一种编码或数据类型,每个中英文字符都占据相同长度,那我们的处理就会简单多了。这时候我们想到了 C++ 的 wstring 类型,wstring 的 size() 函数返回的就是包含的中英文字符个数。wstring 与 string 一样都是基于 basic_string 类模板,不同的是 string 使用 char 为基本类型,而 wstring 是 wchat_t。wchar_t 可以支持 Unicode 字符的存储,在 Win 下是两个字节, Linux 的实现则是四个字节,可以直接用 `sizeof(wchar_t)` 查看类型长度。 22 | 23 | 到这里我们已经有了基本的思路:实现 string 和 wstring 的互相转换,并用 wstring 来判断字符个数,在超长时进行截断。 24 | 25 | ## string 与 wstring 的转换 26 | ### 转换版本一 27 | 如果你的 g++ 版本够高(5.0以上),那么可以采用下面的写法,这是最好的: 28 | ``` 29 | #include 30 | #include 31 | 32 | std::wstring s2ws(const std::string& str) 33 | { 34 | using convert_typeX = std::codecvt_utf8; 35 | std::wstring_convert converterX; 36 | 37 | return converterX.from_bytes(str); 38 | } 39 | 40 | std::string ws2s(const std::wstring& wstr) 41 | { 42 | using convert_typeX = std::codecvt_utf8; 43 | std::wstring_convert converterX; 44 | 45 | return converterX.to_bytes(wstr); 46 | } 47 | ``` 48 | 49 | std::wstring_convert 是 C++11 标准库提供的对 string 和 wstring 的转换,对 Unicode 进行了语言和库级别的支持。但这一特性在 gcc/g++ 5.0 以上才被支持。 50 | 51 | 参考资料: 52 | * [How to convert wstring into string? — stackoverflow](http://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string) 53 | 54 | * [std::wstring_convert — cppreference](http://en.cppreference.com/w/cpp/locale/wstring_convert) 55 | 56 | * [std::wstring_convert — cplusplus](http://www.cplusplus.com/reference/locale/wstring_convert/) 57 | 58 | ### 转换版本二 59 | 如果你的 g++ 版本是支持部分 c++11 特性,那么第二个版本可以用 unique_ptr 来管理内存,这样可以避免直接操作指针的尴尬,程序更加安全。 60 | 61 | ```c++ 62 | #include 63 | #include 64 | #include 65 | 66 | std::wstring s2ws(const std::string& str) { 67 | if (str.empty()) { 68 | return L""; 69 | } 70 | unsigned len = str.size() + 1; 71 | setlocale(LC_CTYPE, "en_US.UTF-8"); 72 | std::unique_ptr p(new wchar_t[len]); 73 | mbstowcs(p.get(), str.c_str(), len); 74 | std::wstring w_str(p.get()); 75 | return w_str; 76 | } 77 | 78 | std::string ws2s(const std::wstring& w_str) { 79 | if (w_str.empty()) { 80 | return ""; 81 | } 82 | unsigned len = w_str.size() * 4 + 1; 83 | setlocale(LC_CTYPE, "en_US.UTF-8"); 84 | std::unique_ptr p(new char[len]); 85 | wcstombs(p.get(), w_str.c_str(), len); 86 | std::string str(p.get()); 87 | return str; 88 | } 89 | ``` 90 | 91 | new 数组的长度要考虑到,因为 wchar_t 为 4 个字节,对于 s2ws, wstring 的长度肯定小于等于 string 的长度,而对 ws2s, string 的长度也肯定小于等于 wstring 4 倍的长度。+1 是预留给字符串的结束符 '\0'。 92 | 93 | setlocale 函数用于运行时的语言环境,可以在命令行用 locale 查看当前系统的语言环境设置,LC_CTYPE 指语言符号及其分类 。网上很多版本使用 `setlocale(LC_CTYPE, "");` , 这里第二个参数用空字符串,会使用系统当前默认的 locale 设置。但是这样有个问题,也许你写出来的程序在本机运行正确,但到服务器上就错了,因为服务器的 locale 不一定是 utf8,所以这里要强制设置为 en_US.UTF-8。 94 | 95 | mbstowcs 和 wcstombs 是两个 C 语言中对多字节字符串和宽字符字符串的互相转换函数,依赖于当前 locale 中所指定的字符编码。 96 | 97 | ### 转换版本三 98 | 如果 g++ 连 unique_ptr 都不支持,那就只能使用下面的 new/delete 了。 99 | 100 | ```c++ 101 | #include 102 | #include 103 | 104 | std::wstring s2ws(const std::string& str) { 105 | if (str.empty()) { 106 | return L""; 107 | } 108 | unsigned len = str.size() + 1; 109 | setlocale(LC_CTYPE, "en_US.UTF-8"); 110 | wchar_t *p = new wchar_t[len]; 111 | mbstowcs(p, str.c_str(), len); 112 | std::wstring w_str(p); 113 | delete[] p; 114 | return w_str; 115 | } 116 | 117 | std::string ws2s(const std::wstring& w_str) { 118 | if (w_str.empty()) { 119 | return ""; 120 | } 121 | unsigned len = w_str.size() * 4 + 1; 122 | setlocale(LC_CTYPE, "en_US.UTF-8"); 123 | char *p = new char[len]; 124 | wcstombs(p, w_str.c_str(), len); 125 | std::string str(p); 126 | delete[] p; 127 | return str; 128 | } 129 | ``` 130 | 131 | ## 最终实现 132 | 实现了 string 和 wstring 的转换后,接下来的处理就很简单了。实现处理函数 FormatText,然后加入 main 函数测试,完整代码如下: 133 | 134 | ```c++ 135 | #include 136 | #include 137 | #include 138 | #include 139 | 140 | static const int kTextSize = 10; 141 | 142 | std::wstring s2ws(const std::string& str) { 143 | if (str.empty()) { 144 | return L""; 145 | } 146 | unsigned len = str.size() + 1; 147 | setlocale(LC_CTYPE, ""); 148 | wchar_t *p = new wchar_t[len]; 149 | mbstowcs(p, str.c_str(), len); 150 | std::wstring w_str(p); 151 | delete[] p; 152 | return w_str; 153 | } 154 | 155 | std::string ws2s(const std::wstring& w_str) { 156 | if (w_str.empty()) { 157 | return ""; 158 | } 159 | unsigned len = w_str.size() * 4 + 1; 160 | setlocale(LC_CTYPE, ""); 161 | char *p = new char[len]; 162 | wcstombs(p, w_str.c_str(), len); 163 | std::string str(p); 164 | delete[] p; 165 | return str; 166 | } 167 | 168 | 169 | bool FormatText(std::string* txt) { 170 | if (NULL == txt) { 171 | return false; 172 | } 173 | std::cout << "before:" << *txt << std::endl; 174 | std::wstring w_txt = s2ws(*txt); 175 | std::cout << "wstring size:" << w_txt.size() << std::endl; 176 | std::cout << "string size:" << (*txt).size() << std::endl; 177 | if (w_txt.size() > kTextSize) { 178 | w_txt = w_txt.substr(0, kTextSize); 179 | *txt = ws2s(w_txt); 180 | *txt += "..."; 181 | } 182 | std::cout << "after:" << *txt << std::endl; 183 | return true; 184 | } 185 | 186 | int main() { 187 | assert(L"" == s2ws("")); 188 | 189 | std::string txt = "龙之谷app好玩等你"; 190 | assert(24 == txt.size()); 191 | std::wstring w_txt = s2ws(txt); 192 | assert(10 == w_txt.size()); 193 | 194 | assert("" == ws2s(L"")); 195 | 196 | w_txt = L"龙之谷app好玩等你"; 197 | assert(10 == w_txt.size()); 198 | txt = ws2s(w_txt); 199 | assert(24 == txt.size()); 200 | 201 | txt = "龙之谷app公测开启"; 202 | std::string format_txt = txt; 203 | FormatText(&format_txt); 204 | assert(txt == format_txt); 205 | 206 | txt = "龙之谷app公测火爆开启"; 207 | FormatText(&txt); 208 | format_txt = "龙之谷app公测火爆..."; 209 | assert(format_txt == txt); 210 | 211 | return 0; 212 | } 213 | 214 | ``` -------------------------------------------------------------------------------- /src/c_plus_cplus/reflection_in_c++_1.md: -------------------------------------------------------------------------------- 1 | # C++ 实现反射(一) 2 | 反射,就是根据一个类名,即可根据类名获取类信息,创建新对象。反射在很多语言都天然支持,然而不包括 C++,但我们肯定会经常遇到这种根据类名生成对象的场景,这就需要我们自己动手来实现了。反正 C++ 这么强大,一定没有问题 :) 3 | ### version 1 4 | 我们略做思考,就可以想到一种最简单的方案: 5 | 6 | ```C++ 7 | if (class_name == "A") { 8 | return new A(); 9 | } else if (class_name == "B") { 10 | return new B(); 11 | } ... 12 | ``` 13 | 当然,这个方案略做思考就可以否掉,这么暴力毫无美感,代码难以维护,每定义个新的类就需要添加一段新的 if,如果很多人共用和维护一定是场灾难。 14 | 15 | ### Version 2 16 | 然而其实我没有放弃,看起来需要添加的代码这么相似,好像用个 template 可以解决? 17 | 18 | ```C++ 19 | #include 20 | 21 | template 22 | BaseClassName* CreateObject() { 23 | return new SubClassName(); 24 | } 25 | 26 | #define CLASS_OBJECT_CREATOR(base_class_name, sub_class_name) \ 27 | CreateObject() 28 | ``` 29 | 每次调用的时候只要形如 `base* p = CLASS_OBJECT_CREATOR(base, A);` 这样就好了,也许我们不知道传入的是哪个派生类,但都可以用基类的指针保存下来,像下面: 30 | 31 | ```c++ 32 | class base 33 | { 34 | public: 35 | base() {} 36 | virtual test() { std::cout << "I'm class base!" << std::endl; } 37 | virtual ~base() {} 38 | }; 39 | // 基类这里再封装一个宏,就不用每次都传入基类名 40 | #define CreateMyClass(class_name) \ 41 | dynamic_cast(CLASS_OBJECT_CREATOR(base, class_name)); 42 | 43 | class A : public base 44 | { 45 | public : 46 | A(){ std::cout << "A constructor!" << std::endl; } 47 | virtual test() { std::cout << "I'm class A!" << std::endl; } 48 | ~A() { std::cout << "A destructor!" << std::endl; } 49 | }; 50 | 51 | class B : public base 52 | { 53 | public : 54 | B(){ std::cout << "B constructor!" << std::endl; } 55 | virtual test() { std::cout << "I'm class B!" << std::endl; } 56 | ~B() { std::cout << "B destructor!" << std::endl; } 57 | }; 58 | 59 | int main() 60 | { 61 | A* p1 = CreateMyClass(A); 62 | delete p; 63 | B* p2 = CreateMyClass(B); 64 | delete p2; 65 | return 0; 66 | } 67 | ``` 68 | 看起来好像没什么问题,只要调用 `CreateMyClass(class_name)` 就可以创建一个新的对象了。但总觉得哪里不对....仔细一想,如果类名是个字符串呢?`CreateMyClass("A")` 压根就没办法处理呀!因为 C++ 本质上无法处理 `new "A"` 这种语法所以才说不支持反射,像上面这样处理的话也根本没有解决这个问题,只是把 new 的语法用宏包装起来而已...囧... 69 | 70 | 还是认真想想怎么解决这个问题吧,见下篇 71 | -------------------------------------------------------------------------------- /src/c_plus_cplus/reflection_in_c++_2.md: -------------------------------------------------------------------------------- 1 | # C++实现反射(二) 2 | 找了一些资料,参考了 [C++反射——开源中国](http://www.oschina.net/code/snippet_230828_9913) 这篇,做了一些修改和简化,成为了 Version3. 3 | 4 | 思路其实并不复杂,可以进行反推: 5 | 1. 反射是根据类名动态生成类,如果我们有一个全局的映射关系,可以从类名得到类的相关信息 ClassInfo,包括类的构造函数,那么我们便能实现这一点。所以我们需要维护一个 `map` 这样的结构作为全局映射,并提供 Register 接口作为 map 的注册操作,只要再定义一个 `CreateObject(const std::string class_name)`,每次在 map 中检索到对应的 ClassInfo,然后创建并返回对象,这样就有了反射的基础。 6 | 2. 那么 `CreateObject` 函数返回的类型是什么呢?因为反射得到的类各不相同,所以我们需要一个 Object 类作为所有反射类的基类,这样只要返回一个 Object* 指针就好了。 7 | 3. 而 ClassInfo 是作为保存类信息的结构,所以我们需要把类名和创建对象的函数指针保存下来。除此之外,还应提供一个 `CreateObject` 的成员函数供调用生成类。 8 | 4. 最后,ClassInfo 在哪里创建和保存呢?顾名思义,每个 ClassInfo 对象应该是每个类唯一的,所以我们可以每个类定义一个 static 的 ClassInfo 对象。还有,每个类要提供构造自身的接口,这样才可以把接口保存到 ClassInfo 中,供 `CreateObject` 调用。因为这两点每个反射类都是相似的,可以借助宏来简化。 9 | 10 | 这样分析完是不是挺简单的?当然更大的可能是你被我绕晕了。可以看看下面的代码,回过头再看一遍分析,应该就清楚了。:) 11 | 12 | ```C++ 13 | // base.h 14 | #ifndef __BASE_H__ 15 | #define __BASE_H__ 16 | #include 17 | #include 18 | 19 | 20 | class Object; 21 | class ClassInfo; 22 | 23 | typedef Object* (*ObjectConstructorFn)(void); 24 | bool Register(ClassInfo* ci); 25 | 26 | class ClassInfo { 27 | public: 28 | ClassInfo(const std::string class_name, ObjectConstructorFn object_constructor): 29 | class_name_(class_name), object_constructor_(object_constructor) { 30 | Register(this); // 构造的时候就进行注册 31 | } 32 | // 根据保存下来的函数指针构造对象 33 | Object* CreateObject() const { 34 | return object_constructor_ ? (*object_constructor_)() : nullptr; 35 | } 36 | ~ClassInfo() {} 37 | public: 38 | std::string class_name_; 39 | ObjectConstructorFn object_constructor_; 40 | }; 41 | 42 | // 维护全局的映射关系 43 | static std::map *class_info_map = nullptr; 44 | 45 | // 每个反射类的都需要一个 ClassInfo 和 CreateObject 46 | #define DECLARE_CLASS(name) \ 47 | protected: \ 48 | static ClassInfo class_info_; \ 49 | public: \ 50 | static Object* CreateObject(); 51 | 52 | // class_info 用类名和类的 CreateObject 函数初始化 53 | // 在每个反射类的 cpp 文件中使用该宏 54 | #define IMPLEMENT_CLASS(name) \ 55 | ClassInfo name::class_info_(#name, (ObjectConstructorFn) name::CreateObject);\ 56 | Object* name::CreateObject() { \ 57 | return new name; \ 58 | } 59 | 60 | // 所有反射类的基类 61 | // 用来传递反射对象的指针 62 | class Object { 63 | DECLARE_CLASS(Object); 64 | public: 65 | Object() {} 66 | virtual ~Object() {} 67 | // 提供全局可使用的 CreateObject 函数,根据类名生成对象 68 | static Object* CreateObject(std::string name); 69 | }; 70 | 71 | IMPLEMENT_CLASS(Object) 72 | 73 | Object* Object::CreateObject(std::string name) 74 | { 75 | std::map::const_iterator iter = 76 | class_info_map->find(name); 77 | if(class_info_map->end() != iter) { 78 | return iter->second->CreateObject(); 79 | } 80 | return nullptr; 81 | } 82 | 83 | // map 的注册接口 84 | bool Register(ClassInfo* ci) { 85 | if (!class_info_map) { 86 | class_info_map = new std::map(); 87 | } 88 | if (ci) { 89 | if (class_info_map->find(ci->class_name_) == class_info_map->end()) { 90 | class_info_map->insert( 91 | std::map::value_type(ci->class_name_, ci)); 92 | } 93 | } 94 | } 95 | #endif 96 | ``` 97 | 98 | 使用的时候包含该头文件,反射类(例如 A)继承 Object,并在类中加入宏 `DECLARE_CLASS(A)`,在类的实现文件中加入宏 `IMPLEMENT_CLASS(A)`,最后创建对象时使用 `Object::CreateObject("A")` 就可以了。 99 | ```C++ 100 | // base_test.cpp 101 | #include 102 | #include 103 | #include "base.h" 104 | 105 | using namespace std; 106 | 107 | class A : public Object 108 | { 109 | DECLARE_CLASS(A) 110 | public : 111 | A(){ std::cout << "A constructor!" << std::endl; } 112 | virtual test() { std::cout << "I'm class A!" << std::endl; } 113 | ~A() { std::cout << "A destructor!" << std::endl; } 114 | }; 115 | IMPLEMENT_CLASS(A) 116 | 117 | class B : public Object 118 | { 119 | DECLARE_CLASS(B) 120 | public : 121 | B(){ std::cout << "B constructor!" << std::endl; } 122 | virtual test() { std::cout << "I'm class B!" << std::endl; } 123 | ~B() { std::cout << "B destructor!" << std::endl; } 124 | }; 125 | IMPLEMENT_CLASS(B) 126 | 127 | int main() 128 | { 129 | Object* p = Object::CreateObject("A"); 130 | delete p; 131 | p = Object::CreateObject("B"); 132 | delete p; 133 | p = Object::CreateObject("A"); 134 | delete p; 135 | return 0; 136 | } 137 | ``` 138 | 139 | 到这一步我们就真正实现反射了,是不是其实并不难?但我还是觉得不够好: 140 | 1. 每个反射类都要继承于 Object,看起来总是有点奇怪,使用的时候要求使用方知道这个用 Object 指针来保存,不甚明朗。 141 | 2. 对于我来说,我只想实现根据类名创建对象,所以代码其实可以更简化一点,map 里直接保存类名和函数指针的映射就好了,这样整体会更简化些。 142 | 143 | 下一篇我们解决这两个问题,实现 Version 4. 144 | 145 | -------------------------------------------------------------------------------- /src/c_plus_cplus/reflection_in_c++_3.md: -------------------------------------------------------------------------------- 1 | # C++实现反射(三) 2 | 上一篇我们用一个 Object 类,让所有需要反射的类都继承这个对象,这样虽然解决了问题,但是用起来不太方便。Object 类的存在主要为了解决保存和返回时的类型问题,如果取消这个类,我们怎么对这些反射类做统一处理呢?答案当然是模板。 3 | 4 | 1. 实现一个模板类管理类名和类构造函数的映射关系,并提供构造对象的接口,每个基类需要初始化一个这样的管理对象。 5 | 2. 提供一个对应的 static 模板函数,用来保存和返回对应的管理对象。 6 | 3. 使用模板函数和 new 操作符作为每个类的构造函数。 7 | 4. 实现一个简单的 helper 模板类提供作为注册的简单封装,并封装宏实现注册。 8 | 5. 封装一个宏实现反射类的创建。 9 | 10 | 11 | ```C++ 12 | #ifndef __BASE_H__ 13 | #define __BASE_H__ 14 | #include 15 | #include 16 | #include 17 | 18 | // 使用模板,每个基类单独生成一个 ClassRegister 19 | // 好处是需要反射的类不需要去继承 Object 对象 20 | // ClassRegister 用来管理类名->类构造函数的映射,对外提供根据类名构造对象对函数 21 | template 22 | class ClassRegister { 23 | public: 24 | typedef ClassName* (*Constructor)(void); 25 | private: 26 | typedef std::map ClassMap; 27 | ClassMap constructor_map_; 28 | public: 29 | // 添加新类的构造函数 30 | void AddConstructor(const std::string class_name, Constructor constructor) { 31 | typename ClassMap::iterator it = constructor_map_.find(class_name); 32 | if (it != constructor_map_.end()) { 33 | std::cout << "error!"; 34 | return; 35 | } 36 | constructor_map_[class_name] = constructor; 37 | } 38 | // 根据类名构造对象 39 | ClassName* CreateObject(const std::string class_name) const { 40 | typename ClassMap::const_iterator it = constructor_map_.find(class_name); 41 | if (it == constructor_map_.end()) { 42 | return nullptr; 43 | } 44 | return (*(it->second))(); 45 | } 46 | }; 47 | 48 | // 用来保存每个基类的 ClassRegister static 对象,用于全局调用 49 | template 50 | ClassRegister& GetRegister() { 51 | static ClassRegister class_register; 52 | return class_register; 53 | } 54 | 55 | // 每个类的构造函数,返回对应的base指针 56 | template 57 | BaseClassName* NewObject() { 58 | return new SubClassName(); 59 | } 60 | 61 | // 为每个类反射提供一个 helper,构造时就完成反射函数对注册 62 | template 63 | class ClassRegisterHelper { 64 | public: 65 | ClassRegisterHelper( 66 | const std::string sub_class_name, 67 | typename ClassRegister::Constructor constructor) { 68 | GetRegister().AddConstructor(sub_class_name, constructor); 69 | } 70 | ~ClassRegisterHelper(){} 71 | }; 72 | 73 | // 提供反射类的注册宏,使用时仅提供基类类名和派生类类名 74 | #define RegisterClass(base_class_name, sub_class_name) \ 75 | static ClassRegisterHelper \ 76 | sub_class_name##_register_helper( \ 77 | #sub_class_name, NewObject); 78 | 79 | // 创建对象的宏 80 | #define CreateObject(base_class_name, sub_class_name_as_string) \ 81 | GetRegister().CreateObject(sub_class_name_as_string) 82 | 83 | #endif 84 | ``` 85 | 86 | 下面是使用的示例: 87 | 88 | ```C++ 89 | #include 90 | #include 91 | #include 92 | #include "base3.h" 93 | using namespace std; 94 | 95 | class base 96 | { 97 | public: 98 | base() {} 99 | virtual void test() { std::cout << "I'm base!" << std::endl; } 100 | virtual ~base() {} 101 | }; 102 | 103 | class A : public base 104 | { 105 | public: 106 | A() { cout << " A constructor!" << endl; } 107 | virtual void test() { std::cout << "I'm A!" <test(); 150 | delete p1; 151 | p1 = CreateObject(base, "B"); 152 | p1->test(); 153 | delete p1; 154 | base2* p2 = CreateObject(base2, "C"); 155 | p2->test(); 156 | delete p2; 157 | return 0; 158 | } 159 | 160 | ``` 161 | -------------------------------------------------------------------------------- /src/code_reading/memorypool.md: -------------------------------------------------------------------------------- 1 | # 【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现 2 | 3 | 什么是内存池?什么是 C++ 的 allocator? 4 | 5 | 内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙))。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。 6 | 7 | 那么,allocator 呢?它默默的工作在 C++ 所有容器的内存分配上。默默贴几个链接吧: 8 | 9 | * http://www.cnblogs.com/wpcockroach/archive/2012/05/10/2493564.html 10 | * http://blog.csdn.net/justaipanda/article/details/7790355 11 | * http://www.cplusplus.com/reference/memory/allocator/ 12 | * http://www.cplusplus.com/reference/memory/allocator_traits/ 13 | 14 | 当你对 allocator 有基本的了解之后,再看这个项目应该会有恍然大悟的感觉,因为这个内存池是以一个 allocator 的标准来实现的。一开始不明白项目里很多函数的定义是为了什么,结果初步了解了 allocator 后才知道大部分是标准接口。这样一个 memory pool allocator 可以与大多数 STL 容器兼容,也可以应用于你自定义的类。像作者给出的例子 —— test.cpp, 是用一个基于自己写的 stack 来做 memory pool allocator 和 std::allocator 性能的对比 —— 最后当然是 memory pool allocator 更优。 15 | 16 | ### 项目 17 | Github:[MemoryPool](https://github.com/cacay/MemoryPool) 18 | 19 | ### 基本使用 20 | 因为这是一个 allocator 类,所以所有使用 std::allocator 的地方都可以使用这个 MemoryPool。在项目的 test.cpp 中,MemoryPool 作为 allocator 用于 StackAlloc(作者实现的 demo 类) 的内存管理类。定义如下: 21 | 22 | ``` 23 | StackAlloc > stackPool; 24 | ``` 25 | 26 | 其次,你也可以将其直接作为任一类型的内存池,用 newElement 创建新元素,deleteElement 释放元素,就像 new/delete 一样。用下面的例子和 new/delete 做对比: 27 | 28 | ```C++ 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "MemoryPool.h" 36 | 37 | using namespace std; 38 | 39 | /* Adjust these values depending on how much you trust your computer */ 40 | #define ELEMS 1000000 41 | #define REPS 50 42 | 43 | int main() 44 | { 45 | 46 | clock_t start; 47 | 48 | MemoryPool pool; 49 | start = clock(); 50 | for(int i = 0;i < REPS;++i) 51 | { 52 | for(int j = 0;j< ELEMS;++j) 53 | { 54 | // 创建元素 55 | size_t* x = pool.newElement(); 56 | 57 | // 释放元素 58 | pool.deleteElement(x); 59 | } 60 | } 61 | std::cout << "MemoryPool Time: "; 62 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n"; 63 | 64 | 65 | start = clock(); 66 | for(int i = 0;i < REPS;++i) 67 | { 68 | for(int j = 0;j< ELEMS;++j) 69 | { 70 | size_t* x = new size_t; 71 | 72 | delete x; 73 | } 74 | } 75 | std::cout << "new/delete Time: "; 76 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n"; 77 | 78 | return 0; 79 | 80 | } 81 | ``` 82 | 83 | 运行的结果是: 84 | 85 | ``` 86 |      MemoryPool Time: 1.93389 87 | 88 |      new/delete Time: 4.64903 89 | ``` 90 | 91 | 嗯,内存池快了一倍多。如果是自定义的类的话这个差距应该还会更大一点。 92 | 93 | ### 代码分析: 94 | 95 | 项目的实现有 C++11 和 C++98 两个版本,C++11 版本似乎更加高效,不过个人 C++11 了解不多,就以 C++98 版本来分析吧。 96 | 97 | #### 主要函数 98 | 99 | * allocate    分配一个对象所需的内存空间 100 | * deallocate   释放一个对象的内存(归还给内存池,不是给操作系统) 101 | * construct   在已申请的内存空间上构造对象 102 | * destroy  析构对象 103 | * newElement  从内存池申请一个对象所需空间,并调用对象的构造函数 104 | * deleteElement  析构对象,将内存空间归还给内存池 105 | * allocateBlock  从操作系统申请一整块内存放入内存池 106 | 107 | #### 关键知识点 108 | 109 | 理解项目的关键在于理解 placement new 和 union 的用法. 110 | 111 | placement new: http://blog.csdn.net/zhangxinrun/article/details/5940019 112 | 113 | union:http://www.cnblogs.com/BeyondTechnology/archive/2010/09/19/1831293.html 114 | 115 | 关于 union 的使用觉得好巧妙,这是相应的定义: 116 | 117 | ```C++ 118 | 119 | union Slot_ { 120 | value_type element; 121 | Slot_* next; 122 | }; 123 | ``` 124 | 125 | Slot_ 在创建对象的时候存放对象的值,当这个对象被释放时这块内存作为一个 Slot_* 指针放入 free 的链表。所以 Slot_ 既可以用来存放对象,又可以用来构造链表。 126 | 127 | 128 | #### 工作原理 129 | 130 | 内存池是一个一个的 block 以链表的形式连接起来,每一个 block 是一块大的内存,当内存池的内存不足的时候,就会向操作系统申请新的 block 加入链表。还有一个 freeSlots_ 的链表,链表里面的每一项都是对象被释放后归还给内存池的空间,内存池刚创建时 freeSlots_ 是空的,之后随着用户创建对象,再将对象释放掉,这时候要把内存归还给内存池,怎么归还呢?就是把指向这个对象的内存的指针加到 freeSlots_ 链表的前面(前插)。 131 | 132 | 用户在创建对象的时候,先检查 freeSlots_ 是否为空,不为空的时候直接取出一项作为分配出的空间。否则就在当前 block 内取出一个 Slot_ 大小的内存分配出去,如果 block 里面的内存已经使用完了呢?就向操作系统申请一个新的 block。 133 | 134 | 内存池工作期间的内存只会增长,不释放给操作系统。直到内存池销毁的时候,才把所有的 block delete 掉。 135 | 136 | #### 注释源码 137 | 138 | [点我到 Github](https://github.com/AngryHacker/code-with-comments/tree/master/memorypool) 139 | 140 | ##### 头文件 141 | 142 | ```C++ 143 | /*- 144 | * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com 145 | * 146 | * Permission is hereby granted, free of charge, to any person obtaining a 147 | * copy of this software and associated documentation files (the "Software"), 148 | * to deal in the Software without restriction, including without limitation 149 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 150 | * and/or sell copies of the Software, and to permit persons to whom the 151 | * Software is furnished to do so, subject to the following conditions: 152 | * 153 | * The above copyright notice and this permission notice shall be included in 154 | * all copies or substantial portions of the Software. 155 | * 156 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 157 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 158 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 159 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 160 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 161 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 162 | * IN THE SOFTWARE. 163 | */ 164 | 165 | #ifndef MEMORY_POOL_H 166 | #define MEMORY_POOL_H 167 | 168 | #include 169 | #include 170 | 171 | template 172 | class MemoryPool 173 | { 174 | public: 175 | /* Member types */ 176 | typedef T value_type; // T 的 value 类型 177 | typedef T* pointer; // T 的 指针类型 178 | typedef T& reference; // T 的引用类型 179 | typedef const T* const_pointer; // T 的 const 指针类型 180 | typedef const T& const_reference; // T 的 const 引用类型 181 | typedef size_t size_type; // size_t 类型 182 | typedef ptrdiff_t difference_type; // 指针减法结果类型 183 | 184 | template struct rebind { 185 | typedef MemoryPool other; 186 | }; 187 | 188 | /* Member functions */ 189 | /* 构造函数 */ 190 | MemoryPool() throw(); 191 | MemoryPool(const MemoryPool& memoryPool) throw(); 192 | template MemoryPool(const MemoryPool& memoryPool) throw(); 193 | 194 | /* 析构函数 */ 195 | ~MemoryPool() throw(); 196 | 197 | /* 元素取址 */ 198 | pointer address(reference x) const throw(); 199 | const_pointer address(const_reference x) const throw(); 200 | 201 | // Can only allocate one object at a time. n and hint are ignored 202 | // 分配和收回一个元素的内存空间 203 | pointer allocate(size_type n = 1, const_pointer hint = 0); 204 | void deallocate(pointer p, size_type n = 1); 205 | 206 | // 可达到的最多元素数 207 | size_type max_size() const throw(); 208 | 209 | // 基于内存池的元素构造和析构 210 | void construct(pointer p, const_reference val); 211 | void destroy(pointer p); 212 | 213 | // 自带申请内存和释放内存的构造和析构 214 | pointer newElement(const_reference val); 215 | void deleteElement(pointer p); 216 | 217 | private: 218 | // union 结构体,用于存放元素或 next 指针 219 | union Slot_ { 220 | value_type element; 221 | Slot_* next; 222 | }; 223 | 224 | typedef char* data_pointer_; // char* 指针,主要用于指向内存首地址 225 | typedef Slot_ slot_type_; // Slot_ 值类型 226 | typedef Slot_* slot_pointer_; // Slot_* 指针类型 227 | 228 | slot_pointer_ currentBlock_; // 内存块链表的头指针 229 | slot_pointer_ currentSlot_; // 元素链表的头指针 230 | slot_pointer_ lastSlot_; // 可存放元素的最后指针 231 | slot_pointer_ freeSlots_; // 元素构造后释放掉的内存链表头指针 232 | 233 | size_type padPointer(data_pointer_ p, size_type align) const throw(); // 计算对齐所需空间 234 | void allocateBlock(); // 申请内存块放进内存池 235 | /* 236 | static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small."); 237 | */ 238 | }; 239 | 240 | #include "MemoryPool.tcc" 241 | 242 | #endif // MEMORY_POOL_H 243 | ``` 244 | 245 | ##### 实现文件 246 | 247 | ```C++ 248 | /*- 249 | * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com 250 | * 251 | * Permission is hereby granted, free of charge, to any person obtaining a 252 | * copy of this software and associated documentation files (the "Software"), 253 | * to deal in the Software without restriction, including without limitation 254 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 255 | * and/or sell copies of the Software, and to permit persons to whom the 256 | * Software is furnished to do so, subject to the following conditions: 257 | * 258 | * The above copyright notice and this permission notice shall be included in 259 | * all copies or substantial portions of the Software. 260 | * 261 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 262 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 263 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 264 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 265 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 266 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 267 | * IN THE SOFTWARE. 268 | */ 269 | 270 | #ifndef MEMORY_BLOCK_TCC 271 | #define MEMORY_BLOCK_TCC 272 | 273 | // 计算对齐所需补的空间 274 | template 275 | inline typename MemoryPool::size_type 276 | MemoryPool::padPointer(data_pointer_ p, size_type align) 277 | const throw() 278 | { 279 | size_t result = reinterpret_cast(p); 280 | return ((align - result) % align); 281 | } 282 | 283 | /* 构造函数,所有成员初始化 */ 284 | template 285 | MemoryPool::MemoryPool() 286 | throw() 287 | { 288 | currentBlock_ = 0; 289 | currentSlot_ = 0; 290 | lastSlot_ = 0; 291 | freeSlots_ = 0; 292 | } 293 | 294 | /* 复制构造函数,调用 MemoryPool 初始化*/ 295 | template 296 | MemoryPool::MemoryPool(const MemoryPool& memoryPool) 297 | throw() 298 | { 299 | MemoryPool(); 300 | } 301 | 302 | /* 复制构造函数,调用 MemoryPool 初始化*/ 303 | template 304 | template 305 | MemoryPool::MemoryPool(const MemoryPool& memoryPool) 306 | throw() 307 | { 308 | MemoryPool(); 309 | } 310 | 311 | /* 析构函数,把内存池中所有 block delete 掉 */ 312 | template 313 | MemoryPool::~MemoryPool() 314 | throw() 315 | { 316 | slot_pointer_ curr = currentBlock_; 317 | while (curr != 0) { 318 | slot_pointer_ prev = curr->next; 319 | // 转化为 void 指针,是因为 void 类型不需要调用析构函数,只释放空间 320 | operator delete(reinterpret_cast(curr)); 321 | curr = prev; 322 | } 323 | } 324 | 325 | /* 返回地址 */ 326 | template 327 | inline typename MemoryPool::pointer 328 | MemoryPool::address(reference x) 329 | const throw() 330 | { 331 | return &x; 332 | } 333 | 334 | /* 返回地址的 const 重载*/ 335 | template 336 | inline typename MemoryPool::const_pointer 337 | MemoryPool::address(const_reference x) 338 | const throw() 339 | { 340 | return &x; 341 | } 342 | 343 | // 申请一块空闲的 block 放进内存池 344 | template 345 | void 346 | MemoryPool::allocateBlock() 347 | { 348 | // Allocate space for the new block and store a pointer to the previous one 349 | // operator new 申请对应大小内存,返回 void* 指针 350 | data_pointer_ newBlock = reinterpret_cast 351 | (operator new(BlockSize)); 352 | // 原来的 block 链头接到 newblock 353 | reinterpret_cast(newBlock)->next = currentBlock_; 354 | // 新的 currentblock_ 355 | currentBlock_ = reinterpret_cast(newBlock); 356 | // Pad block body to staisfy the alignment requirements for elements 357 | data_pointer_ body = newBlock + sizeof(slot_pointer_); 358 | // 计算为了对齐应该空出多少位置 359 | size_type bodyPadding = padPointer(body, sizeof(slot_type_)); 360 | // currentslot_ 为该 block 开始的地方加上 bodypadding 个 char* 空间 361 | currentSlot_ = reinterpret_cast(body + bodyPadding); 362 | // 计算最后一个能放置 slot_type_ 的位置 363 | lastSlot_ = reinterpret_cast 364 | (newBlock + BlockSize - sizeof(slot_type_) + 1); 365 | } 366 | 367 | // 返回指向分配新元素所需内存的指针 368 | template 369 | inline typename MemoryPool::pointer 370 | MemoryPool::allocate(size_type, const_pointer) 371 | { 372 | // 如果 freeSlots_ 非空,就在 freeSlots_ 中取内存 373 | if (freeSlots_ != 0) { 374 | pointer result = reinterpret_cast(freeSlots_); 375 | // 更新 freeSlots_ 376 | freeSlots_ = freeSlots_->next; 377 | return result; 378 | } 379 | else { 380 | if (currentSlot_ >= lastSlot_) 381 | // 之前申请的内存用完了,分配新的 block 382 | allocateBlock(); 383 | // 从分配的 block 中划分出去 384 | return reinterpret_cast(currentSlot_++); 385 | } 386 | } 387 | 388 | // 将元素内存归还给 free 内存链表 389 | template 390 | inline void 391 | MemoryPool::deallocate(pointer p, size_type) 392 | { 393 | if (p != 0) { 394 | // 转换成 slot_pointer_ 指针,next 指向 freeSlots_ 链表 395 | reinterpret_cast(p)->next = freeSlots_; 396 | // 新的 freeSlots_ 头为 p 397 | freeSlots_ = reinterpret_cast(p); 398 | } 399 | } 400 | 401 | // 计算可达到的最大元素上限数 402 | template 403 | inline typename MemoryPool::size_type 404 | MemoryPool::max_size() 405 | const throw() 406 | { 407 | size_type maxBlocks = -1 / BlockSize; 408 | return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks; 409 | } 410 | 411 | // 在已分配内存上构造对象 412 | template 413 | inline void 414 | MemoryPool::construct(pointer p, const_reference val) 415 | { 416 | // placement new 用法,在已有内存上构造对象,调用 T 的复制构造函数, 417 | new (p) value_type (val); 418 | } 419 | 420 | // 销毁对象 421 | template 422 | inline void 423 | MemoryPool::destroy(pointer p) 424 | { 425 | // placement new 中需要手动调用元素 T 的析构函数 426 | p->~value_type(); 427 | } 428 | 429 | // 创建新元素 430 | template 431 | inline typename MemoryPool::pointer 432 | MemoryPool::newElement(const_reference val) 433 | { 434 | // 申请内存 435 | pointer result = allocate(); 436 | // 在内存上构造对象 437 | construct(result, val); 438 | return result; 439 | } 440 | 441 | // 删除元素 442 | template 443 | inline void 444 | MemoryPool::deleteElement(pointer p) 445 | { 446 | if (p != 0) { 447 | // placement new 中需要手动调用元素 T 的析构函数 448 | p->~value_type(); 449 | // 归还内存 450 | deallocate(p); 451 | } 452 | } 453 | 454 | #endif // MEMORY_BLOCK_TCC 455 | ``` 456 | -------------------------------------------------------------------------------- /src/code_reading/threadpool.md: -------------------------------------------------------------------------------- 1 | # 【源码剖析】threadpool —— 基于 pthread 实现的简单线程池 2 | ## 线程池介绍 3 | 线程池可以说是项目中经常会用到的组件,在这里假设读者都有一定的多线程基础,如果没有的话不妨在这里进行了解:[POSIX 多线程基础](https://github.com/AngryHacker/ocean/blob/master/multithreaded%20programming/README.md)。 4 | 5 | 线程池是什么?我的简单理解是有一组预先派生的线程,然后有一个管理员来管理和调度这些线程,你只需不断把需要完成的任务交给他,他就会调度线程的资源来帮你完成。 6 | 7 | 那么管理员是怎么做的呢?一种简单的方式就是,管理员管理一个任务的队列,如果收到新的任务,就把任务加到队列尾。每个线程盯着队列,如果队列非空,就去队列头拿一个任务来处理(每个任务只能被一个线程拿到),处理完了就继续去队列取任务。如果没有任务了,线程就休眠,直到任务队列不为空。如果这个管理员更聪明一点,他可能会在没有任务或任务少的时候减少线程的数量,任务处理不过来的时候增加线程的数量,这样就实现了资源的动态管理。 8 | 9 | 那么任务是什么呢?以后台服务器为例,每一个用户的请求就是一个任务,线程不断的在请求队列里取出请求,完成后继续处理下一个请求。 10 | 11 | 简单图示为: 12 | ![threadpool](https://raw.githubusercontent.com/AngryHacker/articles/master/img/20151224173731480.png) 13 | 14 | 线程池有一个好处就是减少线程创建和销毁的时间,在任务处理时间比较短的时候这个好处非常显著,可以提升任务处理的效率。 15 | 16 | ## 线程池实现 17 | 这里介绍的是线程池的一个简单实现,在创建的时候预先派生指定数量的线程,然后去任务队列取添加进来的任务进行处理就好。 18 | 19 | 作者说之后会添加更多特性,我们作为学习之后就以这个版本为准就好了。 20 | 21 | 项目主页:[threadpool](https://github.com/mbrossard/threadpool) 22 | ### 数据结构 23 | 主要有两个自定义的数据结构 24 | #### `threadpool_task_t` 25 | 用于保存一个等待执行的任务。一个任务需要指明:要运行的对应函数及函数的参数。所以这里的 struct 里有函数指针和 void 指针。 26 | 27 | ```c 28 | typedef struct { 29 | void (*function)(void *); 30 | void *argument; 31 | } threadpool_task_t; 32 | ``` 33 | 34 | #### `thread_pool_t` 35 | 一个线程池的结构。因为是 C 语言,所以这里任务队列是用数组,并维护队列头和队列尾来实现。 36 | ```c 37 | struct threadpool_t { 38 | pthread_mutex_t lock; /* 互斥锁 */ 39 | pthread_cond_t notify; /* 条件变量 */ 40 | pthread_t *threads; /* 线程数组的起始指针 */ 41 | threadpool_task_t *queue; /* 任务队列数组的起始指针 */ 42 | int thread_count; /* 线程数量 */ 43 | int queue_size; /* 任务队列长度 */ 44 | int head; /* 当前任务队列头 */ 45 | int tail; /* 当前任务队列尾 */ 46 | int count; /* 当前待运行的任务数 */ 47 | int shutdown; /* 线程池当前状态是否关闭 */ 48 | int started; /* 正在运行的线程数 */ 49 | }; 50 | ``` 51 | 52 | ### 函数 53 | #### 对外接口 54 | 55 | * `threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);` 创建线程池,用 thread_count 指定派生线程数,queue_size 指定任务队列长度,flags 为保留参数,未使用。 56 | * `int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg, int flags);` 添加需要执行的任务。第二个参数为对应函数指针,第三个为对应函数参数。flags 未使用。 57 | * `int threadpool_destroy(threadpool_t *pool, int flags);` 销毁存在的线程池。flags 可以指定是立刻结束还是平和结束。立刻结束指不管任务队列是否为空,立刻结束。平和结束指等待任务队列的任务全部执行完后再结束,在这个过程中不可以添加新的任务。 58 | 59 | #### 内部辅助函数 60 | 61 | * `static void *threadpool_thread(void *threadpool);` 线程池每个线程所执行的函数。 62 | * `int threadpool_free(threadpool_t *pool);` 释放线程池所申请的内存资源。 63 | 64 | ## 线程池使用 65 | ### 编译 66 | 参考项目根目录下的 Makefile, 直接用 `make` 编译。 67 | ### 测试用例 68 | 项目提供了三个测试用例(见 `threadpool/test/`),我们可以以此来学习线程池的用法并测试是否正常工作。这里提供其中一个: 69 | 70 | ```c 71 | #define THREAD 32 72 | #define QUEUE 256 73 | 74 | #include 75 | #include 76 | #include 77 | #include 78 | 79 | #include "threadpool.h" 80 | 81 | int tasks = 0, done = 0; 82 | pthread_mutex_t lock; 83 | 84 | void dummy_task(void *arg) { 85 | usleep(10000); 86 | pthread_mutex_lock(&lock); 87 | /* 记录成功完成的任务数 */ 88 | done++; 89 | pthread_mutex_unlock(&lock); 90 | } 91 | 92 | int main(int argc, char **argv) 93 | { 94 | threadpool_t *pool; 95 | 96 | /* 初始化互斥锁 */ 97 | pthread_mutex_init(&lock, NULL); 98 | 99 | /* 断言线程池创建成功 */ 100 | assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL); 101 | fprintf(stderr, "Pool started with %d threads and " 102 | "queue size of %d\n", THREAD, QUEUE); 103 | 104 | /* 只要任务队列还没满,就一直添加 */ 105 | while(threadpool_add(pool, &dummy_task, NULL, 0) == 0) { 106 | pthread_mutex_lock(&lock); 107 | tasks++; 108 | pthread_mutex_unlock(&lock); 109 | } 110 | 111 | fprintf(stderr, "Added %d tasks\n", tasks); 112 | 113 | /* 不断检查任务数是否完成一半以上,没有则继续休眠 */ 114 | while((tasks / 2) > done) { 115 | usleep(10000); 116 | } 117 | /* 这时候销毁线程池,0 代表 immediate_shutdown */ 118 | assert(threadpool_destroy(pool, 0) == 0); 119 | fprintf(stderr, "Did %d tasks\n", done); 120 | 121 | return 0; 122 | } 123 | ``` 124 | 125 | ## 源码注释 126 | 源码注释一并放在 github, [点我。](https://github.com/AngryHacker/code-with-comments#threadpool) 127 | 128 | ### threadpool.h 129 | 130 | ```c 131 | /* 132 | * Copyright (c) 2013, Mathias Brossard . 133 | * All rights reserved. 134 | * 135 | * Redistribution and use in source and binary forms, with or without 136 | * modification, are permitted provided that the following conditions are 137 | * met: 138 | * 139 | * 1. Redistributions of source code must retain the above copyright 140 | * notice, this list of conditions and the following disclaimer. 141 | * 142 | * 2. Redistributions in binary form must reproduce the above copyright 143 | * notice, this list of conditions and the following disclaimer in the 144 | * documentation and/or other materials provided with the distribution. 145 | * 146 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 147 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 148 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 149 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 150 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 152 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 153 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 154 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 155 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 156 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 157 | */ 158 | 159 | #ifndef _THREADPOOL_H_ 160 | #define _THREADPOOL_H_ 161 | 162 | #ifdef __cplusplus 163 | /* 对于 C++ 编译器,指定用 C 的语法编译 */ 164 | extern "C" { 165 | #endif 166 | 167 | /** 168 | * @file threadpool.h 169 | * @brief Threadpool Header File 170 | */ 171 | 172 | /** 173 | * Increase this constants at your own risk 174 | * Large values might slow down your system 175 | */ 176 | #define MAX_THREADS 64 177 | #define MAX_QUEUE 65536 178 | 179 | /* 简化变量定义 */ 180 | typedef struct threadpool_t threadpool_t; 181 | 182 | /* 定义错误码 */ 183 | typedef enum { 184 | threadpool_invalid = -1, 185 | threadpool_lock_failure = -2, 186 | threadpool_queue_full = -3, 187 | threadpool_shutdown = -4, 188 | threadpool_thread_failure = -5 189 | } threadpool_error_t; 190 | 191 | typedef enum { 192 | threadpool_graceful = 1 193 | } threadpool_destroy_flags_t; 194 | 195 | /* 以下是线程池三个对外 API */ 196 | 197 | /** 198 | * @function threadpool_create 199 | * @brief Creates a threadpool_t object. 200 | * @param thread_count Number of worker threads. 201 | * @param queue_size Size of the queue. 202 | * @param flags Unused parameter. 203 | * @return a newly created thread pool or NULL 204 | */ 205 | /** 206 | * 创建线程池,有 thread_count 个线程,容纳 queue_size 个的任务队列,flags 参数没有使用 207 | */ 208 | threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); 209 | 210 | /** 211 | * @function threadpool_add 212 | * @brief add a new task in the queue of a thread pool 213 | * @param pool Thread pool to which add the task. 214 | * @param function Pointer to the function that will perform the task. 215 | * @param argument Argument to be passed to the function. 216 | * @param flags Unused parameter. 217 | * @return 0 if all goes well, negative values in case of error (@see 218 | * threadpool_error_t for codes). 219 | */ 220 | /** 221 | * 添加任务到线程池, pool 为线程池指针,routine 为函数指针, arg 为函数参数, flags 未使用 222 | */ 223 | int threadpool_add(threadpool_t *pool, void (*routine)(void *), 224 | void *arg, int flags); 225 | 226 | /** 227 | * @function threadpool_destroy 228 | * @brief Stops and destroys a thread pool. 229 | * @param pool Thread pool to destroy. 230 | * @param flags Flags for shutdown 231 | * 232 | * Known values for flags are 0 (default) and threadpool_graceful in 233 | * which case the thread pool doesn't accept any new tasks but 234 | * processes all pending tasks before shutdown. 235 | */ 236 | /** 237 | * 销毁线程池,flags 可以用来指定关闭的方式 238 | */ 239 | int threadpool_destroy(threadpool_t *pool, int flags); 240 | 241 | #ifdef __cplusplus 242 | } 243 | #endif 244 | 245 | #endif /* _THREADPOOL_H_ */ 246 | ``` 247 | 248 | ### threadpool.c 249 | ```c 250 | /* 251 | * Copyright (c) 2013, Mathias Brossard . 252 | * All rights reserved. 253 | * 254 | * Redistribution and use in source and binary forms, with or without 255 | * modification, are permitted provided that the following conditions are 256 | * met: 257 | * 258 | * 1. Redistributions of source code must retain the above copyright 259 | * notice, this list of conditions and the following disclaimer. 260 | * 261 | * 2. Redistributions in binary form must reproduce the above copyright 262 | * notice, this list of conditions and the following disclaimer in the 263 | * documentation and/or other materials provided with the distribution. 264 | * 265 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 266 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 267 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 268 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 269 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 270 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 271 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 272 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 273 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 274 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 275 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 276 | */ 277 | 278 | /** 279 | * @file threadpool.c 280 | * @brief Threadpool implementation file 281 | */ 282 | 283 | #include 284 | #include 285 | #include 286 | 287 | #include "threadpool.h" 288 | 289 | /** 290 | * 线程池关闭的方式 291 | */ 292 | typedef enum { 293 | immediate_shutdown = 1, 294 | graceful_shutdown = 2 295 | } threadpool_shutdown_t; 296 | 297 | /** 298 | * @struct threadpool_task 299 | * @brief the work struct 300 | * 301 | * @var function Pointer to the function that will perform the task. 302 | * @var argument Argument to be passed to the function. 303 | */ 304 | /** 305 | * 线程池一个任务的定义 306 | */ 307 | 308 | typedef struct { 309 | void (*function)(void *); 310 | void *argument; 311 | } threadpool_task_t; 312 | 313 | /** 314 | * @struct threadpool 315 | * @brief The threadpool struct 316 | * 317 | * @var notify Condition variable to notify worker threads. 318 | * @var threads Array containing worker threads ID. 319 | * @var thread_count Number of threads 320 | * @var queue Array containing the task queue. 321 | * @var queue_size Size of the task queue. 322 | * @var head Index of the first element. 323 | * @var tail Index of the next element. 324 | * @var count Number of pending tasks 325 | * @var shutdown Flag indicating if the pool is shutting down 326 | * @var started Number of started threads 327 | */ 328 | /** 329 | * 线程池的结构定义 330 | * @var lock 用于内部工作的互斥锁 331 | * @var notify 线程间通知的条件变量 332 | * @var threads 线程数组,这里用指针来表示,数组名 = 首元素指针 333 | * @var thread_count 线程数量 334 | * @var queue 存储任务的数组,即任务队列 335 | * @var queue_size 任务队列大小 336 | * @var head 任务队列中首个任务位置(注:任务队列中所有任务都是未开始运行的) 337 | * @var tail 任务队列中最后一个任务的下一个位置(注:队列以数组存储,head 和 tail 指示队列位置) 338 | * @var count 任务队列里的任务数量,即等待运行的任务数 339 | * @var shutdown 表示线程池是否关闭 340 | * @var started 开始的线程数 341 | */ 342 | struct threadpool_t { 343 | pthread_mutex_t lock; 344 | pthread_cond_t notify; 345 | pthread_t *threads; 346 | threadpool_task_t *queue; 347 | int thread_count; 348 | int queue_size; 349 | int head; 350 | int tail; 351 | int count; 352 | int shutdown; 353 | int started; 354 | }; 355 | 356 | /** 357 | * @function void *threadpool_thread(void *threadpool) 358 | * @brief the worker thread 359 | * @param threadpool the pool which own the thread 360 | */ 361 | /** 362 | * 线程池里每个线程在跑的函数 363 | * 声明 static 应该只为了使函数只在本文件内有效 364 | */ 365 | static void *threadpool_thread(void *threadpool); 366 | 367 | int threadpool_free(threadpool_t *pool); 368 | 369 | threadpool_t *threadpool_create(int thread_count, int queue_size, int flags) 370 | { 371 | if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) { 372 | return NULL; 373 | } 374 | 375 | threadpool_t *pool; 376 | int i; 377 | 378 | /* 申请内存创建内存池对象 */ 379 | if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) { 380 | goto err; 381 | } 382 | 383 | /* Initialize */ 384 | pool->thread_count = 0; 385 | pool->queue_size = queue_size; 386 | pool->head = pool->tail = pool->count = 0; 387 | pool->shutdown = pool->started = 0; 388 | 389 | /* Allocate thread and task queue */ 390 | /* 申请线程数组和任务队列所需的内存 */ 391 | pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count); 392 | pool->queue = (threadpool_task_t *)malloc 393 | (sizeof(threadpool_task_t) * queue_size); 394 | 395 | /* Initialize mutex and conditional variable first */ 396 | /* 初始化互斥锁和条件变量 */ 397 | if((pthread_mutex_init(&(pool->lock), NULL) != 0) || 398 | (pthread_cond_init(&(pool->notify), NULL) != 0) || 399 | (pool->threads == NULL) || 400 | (pool->queue == NULL)) { 401 | goto err; 402 | } 403 | 404 | /* Start worker threads */ 405 | /* 创建指定数量的线程开始运行 */ 406 | for(i = 0; i < thread_count; i++) { 407 | if(pthread_create(&(pool->threads[i]), NULL, 408 | threadpool_thread, (void*)pool) != 0) { 409 | threadpool_destroy(pool, 0); 410 | return NULL; 411 | } 412 | pool->thread_count++; 413 | pool->started++; 414 | } 415 | 416 | return pool; 417 | 418 | err: 419 | if(pool) { 420 | threadpool_free(pool); 421 | } 422 | return NULL; 423 | } 424 | 425 | int threadpool_add(threadpool_t *pool, void (*function)(void *), 426 | void *argument, int flags) 427 | { 428 | int err = 0; 429 | int next; 430 | 431 | if(pool == NULL || function == NULL) { 432 | return threadpool_invalid; 433 | } 434 | 435 | /* 必须先取得互斥锁所有权 */ 436 | if(pthread_mutex_lock(&(pool->lock)) != 0) { 437 | return threadpool_lock_failure; 438 | } 439 | 440 | /* 计算下一个可以存储 task 的位置 */ 441 | next = pool->tail + 1; 442 | next = (next == pool->queue_size) ? 0 : next; 443 | 444 | do { 445 | /* Are we full ? */ 446 | /* 检查是否任务队列满 */ 447 | if(pool->count == pool->queue_size) { 448 | err = threadpool_queue_full; 449 | break; 450 | } 451 | 452 | /* Are we shutting down ? */ 453 | /* 检查当前线程池状态是否关闭 */ 454 | if(pool->shutdown) { 455 | err = threadpool_shutdown; 456 | break; 457 | } 458 | 459 | /* Add task to queue */ 460 | /* 在 tail 的位置放置函数指针和参数,添加到任务队列 */ 461 | pool->queue[pool->tail].function = function; 462 | pool->queue[pool->tail].argument = argument; 463 | /* 更新 tail 和 count */ 464 | pool->tail = next; 465 | pool->count += 1; 466 | 467 | /* pthread_cond_broadcast */ 468 | /* 469 | * 发出 signal,表示有 task 被添加进来了 470 | * 如果由因为任务队列空阻塞的线程,此时会有一个被唤醒 471 | * 如果没有则什么都不做 472 | */ 473 | if(pthread_cond_signal(&(pool->notify)) != 0) { 474 | err = threadpool_lock_failure; 475 | break; 476 | } 477 | /* 478 | * 这里用的是 do { ... } while(0) 结构 479 | * 保证过程最多被执行一次,但在中间方便因为异常而跳出执行块 480 | */ 481 | } while(0); 482 | 483 | /* 释放互斥锁资源 */ 484 | if(pthread_mutex_unlock(&pool->lock) != 0) { 485 | err = threadpool_lock_failure; 486 | } 487 | 488 | return err; 489 | } 490 | 491 | int threadpool_destroy(threadpool_t *pool, int flags) 492 | { 493 | int i, err = 0; 494 | 495 | if(pool == NULL) { 496 | return threadpool_invalid; 497 | } 498 | 499 | /* 取得互斥锁资源 */ 500 | if(pthread_mutex_lock(&(pool->lock)) != 0) { 501 | return threadpool_lock_failure; 502 | } 503 | 504 | do { 505 | /* Already shutting down */ 506 | /* 判断是否已在其他地方关闭 */ 507 | if(pool->shutdown) { 508 | err = threadpool_shutdown; 509 | break; 510 | } 511 | 512 | /* 获取指定的关闭方式 */ 513 | pool->shutdown = (flags & threadpool_graceful) ? 514 | graceful_shutdown : immediate_shutdown; 515 | 516 | /* Wake up all worker threads */ 517 | /* 唤醒所有因条件变量阻塞的线程,并释放互斥锁 */ 518 | if((pthread_cond_broadcast(&(pool->notify)) != 0) || 519 | (pthread_mutex_unlock(&(pool->lock)) != 0)) { 520 | err = threadpool_lock_failure; 521 | break; 522 | } 523 | 524 | /* Join all worker thread */ 525 | /* 等待所有线程结束 */ 526 | for(i = 0; i < pool->thread_count; i++) { 527 | if(pthread_join(pool->threads[i], NULL) != 0) { 528 | err = threadpool_thread_failure; 529 | } 530 | } 531 | /* 同样是 do{...} while(0) 结构*/ 532 | } while(0); 533 | 534 | /* Only if everything went well do we deallocate the pool */ 535 | if(!err) { 536 | /* 释放内存资源 */ 537 | threadpool_free(pool); 538 | } 539 | return err; 540 | } 541 | 542 | int threadpool_free(threadpool_t *pool) 543 | { 544 | if(pool == NULL || pool->started > 0) { 545 | return -1; 546 | } 547 | 548 | /* Did we manage to allocate ? */ 549 | /* 释放线程 任务队列 互斥锁 条件变量 线程池所占内存资源 */ 550 | if(pool->threads) { 551 | free(pool->threads); 552 | free(pool->queue); 553 | 554 | /* Because we allocate pool->threads after initializing the 555 | mutex and condition variable, we're sure they're 556 | initialized. Let's lock the mutex just in case. */ 557 | pthread_mutex_lock(&(pool->lock)); 558 | pthread_mutex_destroy(&(pool->lock)); 559 | pthread_cond_destroy(&(pool->notify)); 560 | } 561 | free(pool); 562 | return 0; 563 | } 564 | 565 | 566 | static void *threadpool_thread(void *threadpool) 567 | { 568 | threadpool_t *pool = (threadpool_t *)threadpool; 569 | threadpool_task_t task; 570 | 571 | for(;;) { 572 | /* Lock must be taken to wait on conditional variable */ 573 | /* 取得互斥锁资源 */ 574 | pthread_mutex_lock(&(pool->lock)); 575 | 576 | /* Wait on condition variable, check for spurious wakeups. 577 | When returning from pthread_cond_wait(), we own the lock. */ 578 | /* 用 while 是为了在唤醒时重新检查条件 */ 579 | while((pool->count == 0) && (!pool->shutdown)) { 580 | /* 任务队列为空,且线程池没有关闭时阻塞在这里 */ 581 | pthread_cond_wait(&(pool->notify), &(pool->lock)); 582 | } 583 | 584 | /* 关闭的处理 */ 585 | if((pool->shutdown == immediate_shutdown) || 586 | ((pool->shutdown == graceful_shutdown) && 587 | (pool->count == 0))) { 588 | break; 589 | } 590 | 591 | /* Grab our task */ 592 | /* 取得任务队列的第一个任务 */ 593 | task.function = pool->queue[pool->head].function; 594 | task.argument = pool->queue[pool->head].argument; 595 | /* 更新 head 和 count */ 596 | pool->head += 1; 597 | pool->head = (pool->head == pool->queue_size) ? 0 : pool->head; 598 | pool->count -= 1; 599 | 600 | /* Unlock */ 601 | /* 释放互斥锁 */ 602 | pthread_mutex_unlock(&(pool->lock)); 603 | 604 | /* Get to work */ 605 | /* 开始运行任务 */ 606 | (*(task.function))(task.argument); 607 | /* 这里一个任务运行结束 */ 608 | } 609 | 610 | /* 线程将结束,更新运行线程数 */ 611 | pool->started--; 612 | 613 | pthread_mutex_unlock(&(pool->lock)); 614 | pthread_exit(NULL); 615 | return(NULL); 616 | } 617 | ``` 618 | 619 | -------------------------------------------------------------------------------- /src/code_reading/tinyhttpd.md: -------------------------------------------------------------------------------- 1 | # 【源码剖析】tinyhttpd —— C 语言实现最简单的 HTTP 服务器 2 | 3 | tinyhttpd 是一个不到 500 行的超轻量型 Http Server,用来学习非常不错,可以帮助我们真正理解服务器程序的本质。 4 | 5 | 看完所有源码,真的感觉有很大收获,无论是 unix 的编程,还是 GET/POST 的 Web 处理流程,都清晰了不少。废话不说,开始我们的 Server 探索之旅。 6 | 7 | ## 项目主页 8 | http://sourceforge.net/projects/tinyhttpd/ 9 | 10 | ## 主要函数 11 | 这是所有函数的声明: 12 | 13 | ```c 14 | void accept_request(int); 15 | void bad_request(int); 16 | void cat(int, FILE *); 17 | void cannot_execute(int); 18 | void error_die(const char *); 19 | void execute_cgi(int, const char *, const char *, const char *); 20 | int get_line(int, char *, int); 21 | void headers(int, const char *); 22 | void not_found(int); 23 | void serve_file(int, const char *); 24 | int startup(u_short *); 25 | void unimplemented(int); 26 | ``` 27 | 28 | 先简单地解释每个函数的作用: 29 | 30 | * accept_request: 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。 31 | 32 | * bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST. 33 | 34 | * cat: 读取服务器上某个文件写到 socket 套接字。 35 | 36 | * cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。 37 | 38 | * error_die: 把错误信息写到 perror 并退出。 39 | 40 | * execute_cgi: 运行 cgi 程序的处理,也是个主要函数。 41 | 42 | * get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。 43 | 44 | * headers: 把 HTTP 响应的头部写到套接字。 45 | 46 | * not_found: 主要处理找不到请求的文件时的情况。 47 | 48 | * sever_file: 调用 cat 把服务器文件返回给浏览器。 49 | 50 | * startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。 51 | 52 | * unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。 53 | 54 | 建议源码阅读顺序: main -> startup -> accept_request -> execute_cgi, 通晓主要工作流程后再仔细把每个函数的源码看一看。 55 | 56 | ## 工作流程 57 | 58 | 1. 服务器启动,在指定端口或随机选取端口绑定 httpd 服务。 59 | 2. 收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数 60 | 3. 取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ? 后面的 GET 参数。 61 | 4. 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页。 62 | 5. 如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到 10。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本。 63 | 6. 读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200 状态码写到套接字。 64 | 7. 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程。 65 | 8. 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序。 66 | 9. 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。这一部分比较乱,见下图说明: 67 | 68 | - 管道初始状态 69 | ![管道初始状态](https://github.com/AngryHacker/articles/blob/master/img/20141226173222750.jpg?raw=true) 70 | - 管道最终状态 71 | ![管道最终状态](https://github.com/AngryHacker/articles/blob/master/img/20141226161119981.jpg?raw=true) 72 | 10. 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。 73 | 74 | ## 注释版源码 75 | 76 | 源码已写了注释,放在 Github: [这里](https://github.com/AngryHacker/code-with-comments/blob/master/tinyhttpd/httpd.c) 77 | 78 | 懒得跳转的同学看下面.... 79 | 80 | ```c 81 | /* J. David's webserver */ 82 | /* This is a simple webserver. 83 | * Created November 1999 by J. David Blackstone. 84 | * CSE 4344 (Network concepts), Prof. Zeigler 85 | * University of Texas at Arlington 86 | */ 87 | /* This program compiles for Sparc Solaris 2.6. 88 | * To compile for Linux: 89 | * 1) Comment out the #include line. 90 | * 2) Comment out the line that defines the variable newthread. 91 | * 3) Comment out the two lines that run pthread_create(). 92 | * 4) Uncomment the line that runs accept_request(). 93 | * 5) Remove -lsocket from the Makefile. 94 | */ 95 | #include 96 | #include 97 | #include 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | 109 | #define ISspace(x) isspace((int)(x)) 110 | 111 | #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 112 | 113 | void accept_request(int); 114 | void bad_request(int); 115 | void cat(int, FILE *); 116 | void cannot_execute(int); 117 | void error_die(const char *); 118 | void execute_cgi(int, const char *, const char *, const char *); 119 | int get_line(int, char *, int); 120 | void headers(int, const char *); 121 | void not_found(int); 122 | void serve_file(int, const char *); 123 | int startup(u_short *); 124 | void unimplemented(int); 125 | 126 | /**********************************************************************/ 127 | /* A request has caused a call to accept() on the server port to 128 | * return. Process the request appropriately. 129 | * Parameters: the socket connected to the client */ 130 | /**********************************************************************/ 131 | void accept_request(int client) 132 | { 133 | char buf[1024]; 134 | int numchars; 135 | char method[255]; 136 | char url[255]; 137 | char path[512]; 138 | size_t i, j; 139 | struct stat st; 140 | int cgi = 0; /* becomes true if server decides this is a CGI program */ 141 | char *query_string = NULL; 142 | 143 | /*得到请求的第一行*/ 144 | numchars = get_line(client, buf, sizeof(buf)); 145 | i = 0; j = 0; 146 | /*把客户端的请求方法存到 method 数组*/ 147 | while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 148 | { 149 | method[i] = buf[j]; 150 | i++; j++; 151 | } 152 | method[i] = '\0'; 153 | 154 | /*如果既不是 GET 又不是 POST 则无法处理 */ 155 | if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 156 | { 157 | unimplemented(client); 158 | return; 159 | } 160 | 161 | /* POST 的时候开启 cgi */ 162 | if (strcasecmp(method, "POST") == 0) 163 | cgi = 1; 164 | 165 | /*读取 url 地址*/ 166 | i = 0; 167 | while (ISspace(buf[j]) && (j < sizeof(buf))) 168 | j++; 169 | while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 170 | { 171 | /*存下 url */ 172 | url[i] = buf[j]; 173 | i++; j++; 174 | } 175 | url[i] = '\0'; 176 | 177 | /*处理 GET 方法*/ 178 | if (strcasecmp(method, "GET") == 0) 179 | { 180 | /* 待处理请求为 url */ 181 | query_string = url; 182 | while ((*query_string != '?') && (*query_string != '\0')) 183 | query_string++; 184 | /* GET 方法特点,? 后面为参数*/ 185 | if (*query_string == '?') 186 | { 187 | /*开启 cgi */ 188 | cgi = 1; 189 | *query_string = '\0'; 190 | query_string++; 191 | } 192 | } 193 | 194 | /*格式化 url 到 path 数组,html 文件都在 htdocs 中*/ 195 | sprintf(path, "htdocs%s", url); 196 | /*默认情况为 index.html */ 197 | if (path[strlen(path) - 1] == '/') 198 | strcat(path, "index.html"); 199 | /*根据路径找到对应文件 */ 200 | if (stat(path, &st) == -1) { 201 | /*把所有 headers 的信息都丢弃*/ 202 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 203 | numchars = get_line(client, buf, sizeof(buf)); 204 | /*回应客户端找不到*/ 205 | not_found(client); 206 | } 207 | else 208 | { 209 | /*如果是个目录,则默认使用该目录下 index.html 文件*/ 210 | if ((st.st_mode & S_IFMT) == S_IFDIR) 211 | strcat(path, "/index.html"); 212 | if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) 213 | cgi = 1; 214 | /*不是 cgi,直接把服务器文件返回,否则执行 cgi */ 215 | if (!cgi) 216 | serve_file(client, path); 217 | else 218 | execute_cgi(client, path, method, query_string); 219 | } 220 | 221 | /*断开与客户端的连接(HTTP 特点:无连接)*/ 222 | close(client); 223 | } 224 | 225 | /**********************************************************************/ 226 | /* Inform the client that a request it has made has a problem. 227 | * Parameters: client socket */ 228 | /**********************************************************************/ 229 | void bad_request(int client) 230 | { 231 | char buf[1024]; 232 | 233 | /*回应客户端错误的 HTTP 请求 */ 234 | sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 235 | send(client, buf, sizeof(buf), 0); 236 | sprintf(buf, "Content-type: text/html\r\n"); 237 | send(client, buf, sizeof(buf), 0); 238 | sprintf(buf, "\r\n"); 239 | send(client, buf, sizeof(buf), 0); 240 | sprintf(buf, "

Your browser sent a bad request, "); 241 | send(client, buf, sizeof(buf), 0); 242 | sprintf(buf, "such as a POST without a Content-Length.\r\n"); 243 | send(client, buf, sizeof(buf), 0); 244 | } 245 | 246 | /**********************************************************************/ 247 | /* Put the entire contents of a file out on a socket. This function 248 | * is named after the UNIX "cat" command, because it might have been 249 | * easier just to do something like pipe, fork, and exec("cat"). 250 | * Parameters: the client socket descriptor 251 | * FILE pointer for the file to cat */ 252 | /**********************************************************************/ 253 | void cat(int client, FILE *resource) 254 | { 255 | char buf[1024]; 256 | 257 | /*读取文件中的所有数据写到 socket */ 258 | fgets(buf, sizeof(buf), resource); 259 | while (!feof(resource)) 260 | { 261 | send(client, buf, strlen(buf), 0); 262 | fgets(buf, sizeof(buf), resource); 263 | } 264 | } 265 | 266 | /**********************************************************************/ 267 | /* Inform the client that a CGI script could not be executed. 268 | * Parameter: the client socket descriptor. */ 269 | /**********************************************************************/ 270 | void cannot_execute(int client) 271 | { 272 | char buf[1024]; 273 | 274 | /* 回应客户端 cgi 无法执行*/ 275 | sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 276 | send(client, buf, strlen(buf), 0); 277 | sprintf(buf, "Content-type: text/html\r\n"); 278 | send(client, buf, strlen(buf), 0); 279 | sprintf(buf, "\r\n"); 280 | send(client, buf, strlen(buf), 0); 281 | sprintf(buf, "

Error prohibited CGI execution.\r\n"); 282 | send(client, buf, strlen(buf), 0); 283 | } 284 | 285 | /**********************************************************************/ 286 | /* Print out an error message with perror() (for system errors; based 287 | * on value of errno, which indicates system call errors) and exit the 288 | * program indicating an error. */ 289 | /**********************************************************************/ 290 | void error_die(const char *sc) 291 | { 292 | /*出错信息处理 */ 293 | perror(sc); 294 | exit(1); 295 | } 296 | 297 | /**********************************************************************/ 298 | /* Execute a CGI script. Will need to set environment variables as 299 | * appropriate. 300 | * Parameters: client socket descriptor 301 | * path to the CGI script */ 302 | /**********************************************************************/ 303 | void execute_cgi(int client, const char *path, const char *method, const char *query_string) 304 | { 305 | char buf[1024]; 306 | int cgi_output[2]; 307 | int cgi_input[2]; 308 | pid_t pid; 309 | int status; 310 | int i; 311 | char c; 312 | int numchars = 1; 313 | int content_length = -1; 314 | 315 | buf[0] = 'A'; buf[1] = '\0'; 316 | if (strcasecmp(method, "GET") == 0) 317 | /*把所有的 HTTP header 读取并丢弃*/ 318 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 319 | numchars = get_line(client, buf, sizeof(buf)); 320 | else /* POST */ 321 | { 322 | /* 对 POST 的 HTTP 请求中找出 content_length */ 323 | numchars = get_line(client, buf, sizeof(buf)); 324 | while ((numchars > 0) && strcmp("\n", buf)) 325 | { 326 | /*利用 \0 进行分隔 */ 327 | buf[15] = '\0'; 328 | /* HTTP 请求的特点*/ 329 | if (strcasecmp(buf, "Content-Length:") == 0) 330 | content_length = atoi(&(buf[16])); 331 | numchars = get_line(client, buf, sizeof(buf)); 332 | } 333 | /*没有找到 content_length */ 334 | if (content_length == -1) { 335 | /*错误请求*/ 336 | bad_request(client); 337 | return; 338 | } 339 | } 340 | 341 | /* 正确,HTTP 状态码 200 */ 342 | sprintf(buf, "HTTP/1.0 200 OK\r\n"); 343 | send(client, buf, strlen(buf), 0); 344 | 345 | /* 建立管道*/ 346 | if (pipe(cgi_output) < 0) { 347 | /*错误处理*/ 348 | cannot_execute(client); 349 | return; 350 | } 351 | /*建立管道*/ 352 | if (pipe(cgi_input) < 0) { 353 | /*错误处理*/ 354 | cannot_execute(client); 355 | return; 356 | } 357 | 358 | if ((pid = fork()) < 0 ) { 359 | /*错误处理*/ 360 | cannot_execute(client); 361 | return; 362 | } 363 | if (pid == 0) /* child: CGI script */ 364 | { 365 | char meth_env[255]; 366 | char query_env[255]; 367 | char length_env[255]; 368 | 369 | /* 把 STDOUT 重定向到 cgi_output 的写入端 */ 370 | dup2(cgi_output[1], 1); 371 | /* 把 STDIN 重定向到 cgi_input 的读取端 */ 372 | dup2(cgi_input[0], 0); 373 | /* 关闭 cgi_input 的写入端 和 cgi_output 的读取端 */ 374 | close(cgi_output[0]); 375 | close(cgi_input[1]); 376 | /*设置 request_method 的环境变量*/ 377 | sprintf(meth_env, "REQUEST_METHOD=%s", method); 378 | putenv(meth_env); 379 | if (strcasecmp(method, "GET") == 0) { 380 | /*设置 query_string 的环境变量*/ 381 | sprintf(query_env, "QUERY_STRING=%s", query_string); 382 | putenv(query_env); 383 | } 384 | else { /* POST */ 385 | /*设置 content_length 的环境变量*/ 386 | sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 387 | putenv(length_env); 388 | } 389 | /*用 execl 运行 cgi 程序*/ 390 | execl(path, path, NULL); 391 | exit(0); 392 | } else { /* parent */ 393 | /* 关闭 cgi_input 的读取端 和 cgi_output 的写入端 */ 394 | close(cgi_output[1]); 395 | close(cgi_input[0]); 396 | if (strcasecmp(method, "POST") == 0) 397 | /*接收 POST 过来的数据*/ 398 | for (i = 0; i < content_length; i++) { 399 | recv(client, &c, 1, 0); 400 | /*把 POST 数据写入 cgi_input,现在重定向到 STDIN */ 401 | write(cgi_input[1], &c, 1); 402 | } 403 | /*读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT */ 404 | while (read(cgi_output[0], &c, 1) > 0) 405 | send(client, &c, 1, 0); 406 | 407 | /*关闭管道*/ 408 | close(cgi_output[0]); 409 | close(cgi_input[1]); 410 | /*等待子进程*/ 411 | waitpid(pid, &status, 0); 412 | } 413 | } 414 | 415 | /**********************************************************************/ 416 | /* Get a line from a socket, whether the line ends in a newline, 417 | * carriage return, or a CRLF combination. Terminates the string read 418 | * with a null character. If no newline indicator is found before the 419 | * end of the buffer, the string is terminated with a null. If any of 420 | * the above three line terminators is read, the last character of the 421 | * string will be a linefeed and the string will be terminated with a 422 | * null character. 423 | * Parameters: the socket descriptor 424 | * the buffer to save the data in 425 | * the size of the buffer 426 | * Returns: the number of bytes stored (excluding null) */ 427 | /**********************************************************************/ 428 | int get_line(int sock, char *buf, int size) 429 | { 430 | int i = 0; 431 | char c = '\0'; 432 | int n; 433 | 434 | /*把终止条件统一为 \n 换行符,标准化 buf 数组*/ 435 | while ((i < size - 1) && (c != '\n')) 436 | { 437 | /*一次仅接收一个字节*/ 438 | n = recv(sock, &c, 1, 0); 439 | /* DEBUG printf("%02X\n", c); */ 440 | if (n > 0) 441 | { 442 | /*收到 \r 则继续接收下个字节,因为换行符可能是 \r\n */ 443 | if (c == '\r') 444 | { 445 | /*使用 MSG_PEEK 标志使下一次读取依然可以得到这次读取的内容,可认为接收窗口不滑动*/ 446 | n = recv(sock, &c, 1, MSG_PEEK); 447 | /* DEBUG printf("%02X\n", c); */ 448 | /*但如果是换行符则把它吸收掉*/ 449 | if ((n > 0) && (c == '\n')) 450 | recv(sock, &c, 1, 0); 451 | else 452 | c = '\n'; 453 | } 454 | /*存到缓冲区*/ 455 | buf[i] = c; 456 | i++; 457 | } 458 | else 459 | c = '\n'; 460 | } 461 | buf[i] = '\0'; 462 | 463 | /*返回 buf 数组大小*/ 464 | return(i); 465 | } 466 | 467 | /**********************************************************************/ 468 | /* Return the informational HTTP headers about a file. */ 469 | /* Parameters: the socket to print the headers on 470 | * the name of the file */ 471 | /**********************************************************************/ 472 | void headers(int client, const char *filename) 473 | { 474 | char buf[1024]; 475 | (void)filename; /* could use filename to determine file type */ 476 | 477 | /*正常的 HTTP header */ 478 | strcpy(buf, "HTTP/1.0 200 OK\r\n"); 479 | send(client, buf, strlen(buf), 0); 480 | /*服务器信息*/ 481 | strcpy(buf, SERVER_STRING); 482 | send(client, buf, strlen(buf), 0); 483 | sprintf(buf, "Content-Type: text/html\r\n"); 484 | send(client, buf, strlen(buf), 0); 485 | strcpy(buf, "\r\n"); 486 | send(client, buf, strlen(buf), 0); 487 | } 488 | 489 | /**********************************************************************/ 490 | /* Give a client a 404 not found status message. */ 491 | /**********************************************************************/ 492 | void not_found(int client) 493 | { 494 | char buf[1024]; 495 | 496 | /* 404 页面 */ 497 | sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 498 | send(client, buf, strlen(buf), 0); 499 | /*服务器信息*/ 500 | sprintf(buf, SERVER_STRING); 501 | send(client, buf, strlen(buf), 0); 502 | sprintf(buf, "Content-Type: text/html\r\n"); 503 | send(client, buf, strlen(buf), 0); 504 | sprintf(buf, "\r\n"); 505 | send(client, buf, strlen(buf), 0); 506 | sprintf(buf, "Not Found\r\n"); 507 | send(client, buf, strlen(buf), 0); 508 | sprintf(buf, "

The server could not fulfill\r\n"); 509 | send(client, buf, strlen(buf), 0); 510 | sprintf(buf, "your request because the resource specified\r\n"); 511 | send(client, buf, strlen(buf), 0); 512 | sprintf(buf, "is unavailable or nonexistent.\r\n"); 513 | send(client, buf, strlen(buf), 0); 514 | sprintf(buf, "\r\n"); 515 | send(client, buf, strlen(buf), 0); 516 | } 517 | 518 | /**********************************************************************/ 519 | /* Send a regular file to the client. Use headers, and report 520 | * errors to client if they occur. 521 | * Parameters: a pointer to a file structure produced from the socket 522 | * file descriptor 523 | * the name of the file to serve */ 524 | /**********************************************************************/ 525 | void serve_file(int client, const char *filename) 526 | { 527 | FILE *resource = NULL; 528 | int numchars = 1; 529 | char buf[1024]; 530 | 531 | /*读取并丢弃 header */ 532 | buf[0] = 'A'; buf[1] = '\0'; 533 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 534 | numchars = get_line(client, buf, sizeof(buf)); 535 | 536 | /*打开 sever 的文件*/ 537 | resource = fopen(filename, "r"); 538 | if (resource == NULL) 539 | not_found(client); 540 | else 541 | { 542 | /*写 HTTP header */ 543 | headers(client, filename); 544 | /*复制文件*/ 545 | cat(client, resource); 546 | } 547 | fclose(resource); 548 | } 549 | 550 | /**********************************************************************/ 551 | /* This function starts the process of listening for web connections 552 | * on a specified port. If the port is 0, then dynamically allocate a 553 | * port and modify the original port variable to reflect the actual 554 | * port. 555 | * Parameters: pointer to variable containing the port to connect on 556 | * Returns: the socket */ 557 | /**********************************************************************/ 558 | int startup(u_short *port) 559 | { 560 | int httpd = 0; 561 | struct sockaddr_in name; 562 | 563 | /*建立 socket */ 564 | httpd = socket(PF_INET, SOCK_STREAM, 0); 565 | if (httpd == -1) 566 | error_die("socket"); 567 | memset(&name, 0, sizeof(name)); 568 | name.sin_family = AF_INET; 569 | name.sin_port = htons(*port); 570 | name.sin_addr.s_addr = htonl(INADDR_ANY); 571 | if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 572 | error_die("bind"); 573 | /*如果当前指定端口是 0,则动态随机分配一个端口*/ 574 | if (*port == 0) /* if dynamically allocating a port */ 575 | { 576 | int namelen = sizeof(name); 577 | if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 578 | error_die("getsockname"); 579 | *port = ntohs(name.sin_port); 580 | } 581 | /*开始监听*/ 582 | if (listen(httpd, 5) < 0) 583 | error_die("listen"); 584 | /*返回 socket id */ 585 | return(httpd); 586 | } 587 | 588 | /**********************************************************************/ 589 | /* Inform the client that the requested web method has not been 590 | * implemented. 591 | * Parameter: the client socket */ 592 | /**********************************************************************/ 593 | void unimplemented(int client) 594 | { 595 | char buf[1024]; 596 | 597 | /* HTTP method 不被支持*/ 598 | sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 599 | send(client, buf, strlen(buf), 0); 600 | /*服务器信息*/ 601 | sprintf(buf, SERVER_STRING); 602 | send(client, buf, strlen(buf), 0); 603 | sprintf(buf, "Content-Type: text/html\r\n"); 604 | send(client, buf, strlen(buf), 0); 605 | sprintf(buf, "\r\n"); 606 | send(client, buf, strlen(buf), 0); 607 | sprintf(buf, "Method Not Implemented\r\n"); 608 | send(client, buf, strlen(buf), 0); 609 | sprintf(buf, "\r\n"); 610 | send(client, buf, strlen(buf), 0); 611 | sprintf(buf, "

HTTP request method not supported.\r\n"); 612 | send(client, buf, strlen(buf), 0); 613 | sprintf(buf, "\r\n"); 614 | send(client, buf, strlen(buf), 0); 615 | } 616 | 617 | /**********************************************************************/ 618 | 619 | int main(void) 620 | { 621 | int server_sock = -1; 622 | u_short port = 0; 623 | int client_sock = -1; 624 | struct sockaddr_in client_name; 625 | int client_name_len = sizeof(client_name); 626 | pthread_t newthread; 627 | 628 | /*在对应端口建立 httpd 服务*/ 629 | server_sock = startup(&port); 630 | printf("httpd running on port %d\n", port); 631 | 632 | while (1) 633 | { 634 | /*套接字收到客户端连接请求*/ 635 | client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len); 636 | if (client_sock == -1) 637 | error_die("accept"); 638 | /*派生新线程用 accept_request 函数处理新请求*/ 639 | /* accept_request(client_sock); */ 640 | if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) 641 | perror("pthread_create"); 642 | } 643 | 644 | close(server_sock); 645 | 646 | return(0); 647 | } 648 | ``` 649 | 650 | ## 编译运行 651 | 这里我们如何让这个服务器跑起来呢? 652 | * 首先,需要修改 Makefile 文件,如果直接执行 make 的话,你会遇到这个错误:cannot find -lsocket。 653 | * 解决方法简单说就是在 linux 系统中没有这样一个库,而且这个库在 linux 中的实现位于 libc 中,编译时被默认包含,所以可以直接在 Makefile 中去掉 -lsocket。详见:stackoverflow 654 | * 另外,在 htdocs 文件下,有 cgi 的程序和 html 代码,cgi 是用 perl 写的,但文件中声明的 perl 执行程序位置在我这是错的,我这里 perl 脚本位于 /usr/bin 中(用 which perl 可以查看),所以把 cgi 文件中的第一行改为:#!/usr/bin/perl -Tw 655 | * 现在都没问题了。mkae 编译后,用 ./httpd 运行,在浏览器中输入 127.0.0.1:端口,就可以看到网页了: 656 | 657 | ![run](https://github.com/AngryHacker/articles/blob/master/img/20160711221626477.png?raw=true) 658 | ![demo](https://github.com/AngryHacker/articles/blob/master/img/20160711221642431.png?raw=true) 659 | -------------------------------------------------------------------------------- /src/code_reading/tornado-memcached-sessions.md: -------------------------------------------------------------------------------- 1 | ## 【源码剖析】tornado-memcached-sessions —— Tornado session 支持的实现 2 | 3 | tornado 里面没有 session?不,当然有~ 只有官方没有,不存在 github 没有 4 | 5 | 我们看下下面这个项目,用 memcached 实现 tornado 的 session。 6 | 7 | 项目地址:[tornado-memcached-sessions](https://github.com/mmejia27/tornado-memcached-sessions) 8 | 9 | ### Demo 10 | 11 | app.py 中: 12 | 首先可以注意到,这里定义了一个新的 Application 类,继承于 tornado.web.Application, 在该类的初始化方法中,设定了应用参数 settings, 之后初始化父类和 session_manager. 13 | 14 | ```python 15 | class Application(tornado.web.Application): 16 | def __init__(self): 17 | settings = dict( 18 | # 设定 cookie_secret, 用于 secure_cookie 19 | cookie_secret = "e446976943b4e8442f099fed1f3fea28462d5832f483a0ed9a3d5d3859f==78d", 20 | # 设定 session_secret 用于生成 session_id 21 | session_secret = "3cdcb1f00803b6e78ab50b466a40b9977db396840c28307f428b25e2277f1bcc", 22 | # memcached 地址 23 | memcached_address = ["127.0.0.1:11211"], 24 | # session 过期时间 25 | session_timeout = 60, 26 | template_path = os.path.join(os.path.dirname(__file__), "templates"), 27 | static_path = os.path.join(os.path.dirname(__file__), "static"), 28 | xsrf_cookies = True, 29 | login_url = "/login", 30 | ) 31 | 32 | handlers = [ 33 | (r"/", MainHandler), 34 | (r"/login", LoginHandler) 35 | ] 36 | 37 | # 初始化父类 tornado.web.Application 38 | tornado.web.Application.__init__(self, handlers, **settings) 39 | # 初始化该类的 session_manager 40 | self.session_manager = session.SessionManager(settings["session_secret"], settings["memcached_address"], settings["session_timeout"]) 41 | ``` 42 | 43 | 在下面的 LoginHandler 中我们可以看到 session 的使用: 44 | 45 | ```python 46 | class LoginHandler(BaseHandler): 47 | def get(self): 48 | self.render("login.html") 49 | 50 | def post(self): 51 | # 以字典的键值对形式存取 52 | self.session["user_name"] = self.get_argument("name") 53 | # 修改完要调用 session 的 save, 否则等于没有修改哦... 54 | self.session.save() 55 | self.redirect("/") 56 | ``` 57 | 58 | 从使用来看,组件非常地简洁和清晰。那么,细心的你是不是发现现在的 handler 没有继承于 tornado.web.RequestHandler?我们看下 BaseHandler 的实现。 59 | 60 | ### BaseHandler 61 | 62 | 打开 base.py,可以看到 BaseHandler 的方法只是初始化,并重写了 get_current_user 的用于用户登录验证的方法。 63 | 64 | ```python 65 | class BaseHandler(tornado.web.RequestHandler): 66 | def __init__(self, *argc, **argkw): 67 | super(BaseHandler, self).__init__(*argc, **argkw) 68 | # 定义 handler 的 session, 注意,根据 HTTP 特点,每次访问都会初始化一个 Session 实例哦,这对于你后面的理解很重要 69 | self.session = session.Session(self.application.session_manager, self) 70 | 71 | # 这是干嘛的?用于验证登录...请 google 关于 tornado.web.authenticated, 其实就是 tornado 提供的用户验证 72 | def get_current_user(self): 73 | return self.session.get("user_name") 74 | ``` 75 | 76 | 可以看到,Handler 是实现主要是借助了 Session 类,下面来看下 session.py 77 | 78 | ### Session 79 | 首先看看 session 需要的库: 80 | * pickle: 一个用于序列化反序列化的库(听不懂?你直接看成和 json 一样作用就行了...) 81 | * hmac:和 hashlib 用于生成加密字符串 82 | * uuid:用于生成一个唯一 id 83 | * memcache :Python 的 memcache 客户端 84 | 85 | 这里面有三个类,SessionData Session 和 SessionManager。先看最简单的 SessionData。 86 | 87 | SessionData 用于以字典的结构存储 session 数据,继承于字典,其实只比字典多了两个成员变量: 88 | 89 | ```python 90 | # 继承字典,因为 session 的存取类似于字典 91 | class SessionData(dict): 92 | # 初始化时提供 session id 和 hmac_key 93 | def __init__(self, session_id, hmac_key): 94 | self.session_id = session_id 95 | self.hmac_key = hmac_key 96 | ``` 97 | 98 | 然后就是真正的 Session 类了。Session 类继承于 SessionData, 注意,它还是十分像内置类型字典,只是重写了自己的初始化方法,并定义了 save 接口——用于保存修改后的 session 数据。 99 | 100 | ```python 101 | # 继承 SessionData 类 102 | class Session(SessionData): 103 | # 初始化,绑定 session_manager 和 tornado 的对应 handler 104 | def __init__(self, session_manager, request_handler): 105 | self.session_manager = session_manager 106 | self.request_handler = request_handler 107 | 108 | try: 109 | # 正常是获取该 session 的所有数据,以 SessionData 的形式保存 110 | current_session = session_manager.get(request_handler) 111 | except InvalidSessionException: 112 | # 如果是第一次访问会抛出异常,异常的时候是获取了一个空的 SessionData 对象, 113 | # 里面没有数据,但包含新生成的 session_id 和 hmac_key 114 | current_session = session_manager.get() 115 | 116 | # 取出 current_session 中的数据,以键值对的形式迭代存下 117 | for key, data in current_session.iteritems(): 118 | self[key] = data 119 | 120 | # 保存下 session_id 121 | self.session_id = current_session.session_id 122 | # 以及对应的 hmac_key 123 | self.hmac_key = current_session.hmac_key 124 | 125 | # 定义 save 方法,用于 session 修改后的保存,实际调用 session_manager 的 set 方法 126 | def save(self): 127 | self.session_manager.set(self.request_handler, self) 128 | ``` 129 | 130 | __init__ 方法比较难理解,基本流程是定义自己的 session_manager 和 handler 处理对象。然后通过 session_manager 获得已有的 session 数据,用这些数据初始化一个访问的用户的 session, 如果用户是第一次访问,那么他拿到的是一个新的 SessionData 对象,因为有可能是新用户,所以这里要对 session_id 和 hmac_key 进行赋值。 131 | 132 | 而 save 方法是提供了对修改 session 数据后的保存接口,实际是调用 session_manager 的 set 方法,具体实现先不考虑。 133 | 134 | 看到这两个类,你就应该对 session 的工作有基本理解,可以从用户访问的流程来考虑。 135 | 136 | 注意 BaseHandler 这个入口,每个用户的访问都是一次 HTTP 请求。当用户第一次访问或者上一次的 session 过期了,这时用户访问时 tornado 建立了一个 handler 对象(该 handler 一定继承于 BaseHandler),并且在初始化时建立了一个 session 对象,因为是新访问,所以目前 session 里面没有数据,在之后采用 键/值 对的形式读写 session(不要忘了 Session 具有字典的所有操作),修改后通过 save 方法保存 session。 137 | 138 | 如果用户不是新访问,那么也是按照上述的流程,不过 session 初始化时把 之前的数据取出来保存在该实例中。当用户结束访问,HTTP 断开连接,handler 实例销毁,session 实例销毁(注意,是实例销毁,不是数据销毁)。 139 | 140 | ### SessionManager 141 | 142 | 最后,我们需要再看下 SessionManager 的实现。 143 | 144 | 首先是初始化,设置密钥, memcache 地址,session 超时时间。 145 | 146 | ```python 147 | # 初始化需要一个用于 session 加密的 secret, memcache 地址, session 的过期时间 148 | def __init__(self, secret, memcached_address, session_timeout): 149 | self.secret = secret 150 | self.memcached_address = memcached_address 151 | self.session_timeout = session_timeout 152 | ``` 153 | 154 | 接着是 _fetch 方法,以 session_id 为键从 memcached 中取出数据,并用 pickle 反序列化解析数据: 155 | 156 | ```python 157 | # 该方法用 session_id 从 memcache 中取出数据 158 | def _fetch(self, session_id): 159 | try: 160 | # 连接 memcache 服务器 161 | mc = memcache.Client(self.memcached_address, debug=0) 162 | # 获取数据 163 | session_data = raw_data = mc.get(session_id) 164 | if raw_data != None: 165 | # 为了重新刷新 timeout 166 | mc.replace(session_id, raw_data, self.session_timeout, 0) 167 | # 反序列化 168 | session_data = pickle.loads(raw_data) 169 | # 如果拿到的数据是字典形式,才进行返回 170 | if type(session_data) == type({}): 171 | return session_data 172 | else: 173 | return {} 174 | except IOError: 175 | return {} 176 | ``` 177 | 178 | get 经过安全检查后,以 SessionData 的形式返回 memcached 的数据(调用了 _fetch)方法。 179 | 180 | 181 | ```python 182 | def get(self, request_handler = None): 183 | 184 | # 获取对应的 session_id 和 hmac_key 185 | if (request_handler == None): 186 | session_id = None 187 | hmac_key = None 188 | else: 189 | # session 的基础还是靠 cookie 190 | session_id = request_handler.get_secure_cookie("session_id") 191 | hmac_key = request_handler.get_secure_cookie("verification") 192 | 193 | # session_id 不存在的时候则生成一个新的 session_id 和 hmac_key 194 | if session_id == None: 195 | session_exists = False 196 | session_id = self._generate_id() 197 | hmac_key = self._generate_hmac(session_id) 198 | else: 199 | session_exists = True 200 | 201 | # 检查 hmac_key 202 | check_hmac = self._generate_hmac(session_id) 203 | # 不通过则抛出异常 204 | if hmac_key != check_hmac: 205 | raise InvalidSessionException() 206 | 207 | # 新建 SessionData 对象 208 | session = SessionData(session_id, hmac_key) 209 | 210 | if session_exists: 211 | # 通过 _fetch 方法获取 memcache 中该 session 的所有数据 212 | session_data = self._fetch(session_id) 213 | for key, data in session_data.iteritems(): 214 | session[key] = data 215 | 216 | return session 217 | ``` 218 | 219 | 至于 set 方法,是为了更新 memcached 的数据。 220 | 221 | ```python 222 | # 设置新的 session,需要设置 handler 的 cookie 和 memcache 客户端 223 | def set(self, request_handler, session): 224 | # 设置浏览器的 cookie 225 | request_handler.set_secure_cookie("session_id", session.session_id) 226 | request_handler.set_secure_cookie("verification", session.hmac_key) 227 | # 用 pickle 进行序列化 228 | session_data = pickle.dumps(dict(session.items()), pickle.HIGHEST_PROTOCOL) 229 | # 连接 memcache 服务器 230 | mc = memcache.Client(self.memcached_address, debug=0) 231 | # 写入 memcache 232 | mc.set(session.session_id, session_data, self.session_timeout, 0) 233 | ``` 234 | 235 | 最后的两个函数,一个是生成 session_id,另一个用 session_id 与密钥加密后生成一个加密字符串,用于验证。 236 | 237 | ```python 238 | # 生成 session_id 239 | def _generate_id(self): 240 | new_id = hashlib.sha256(self.secret + str(uuid.uuid4())) 241 | return new_id.hexdigest() 242 | 243 | # 生成 hmac_key 244 | def _generate_hmac(self, session_id): 245 | return hmac.new(session_id, self.secret, hashlib.sha256).hexdigest() 246 | ``` 247 | 248 | 回顾一下,我们在哪里初始化了 SessionManager 呢?可以回看开始的 demo, 我们在定义 Application 的时候就生成了一个 SessionManager,到此结束啦 249 | -------------------------------------------------------------------------------- /src/code_reading/webbench.md: -------------------------------------------------------------------------------- 1 | # 【源码剖析】Webbench —— 简洁而优美的压力测试工具 2 | 3 | Webbench 是一个古老而著名的网站压力测试工具,简单而实用。如果你不清楚你的网站能承受多大的压力,或者你想分析对比两个网站的性能,webbench 再好用不过了。 4 | 5 | ### 项目地址 6 | Gitbub 地址:[点我](https://github.com/AngryHacker/code-with-comments/tree/master/webbench) 7 | 8 | ### 安装 9 | 10 | 很简单,cd 进项目主页后进行 make install clean 就好了。 11 | 12 | ### 用法 13 | 想要知道用法可以在安装后直接输入 webbench 或 webbench -h 或 webbench --help. 可以看到: 14 | 15 | ``` 16 | webbench [option]... URL 17 | -f|--force Don't wait for reply from server. 18 | -r|--reload Send reload request - Pragma: no-cache. 19 | -t|--time Run benchmark for seconds. Default 30. 20 | -p|--proxy Use proxy server for request. 21 | -c|--clients Run HTTP clients at once. Default one. 22 | -9|--http09 Use HTTP/0.9 style requests. 23 | -1|--http10 Use HTTP/1.0 protocol. 24 | -2|--http11 Use HTTP/1.1 protocol. 25 | --get Use GET request method. 26 | --head Use HEAD request method. 27 | --options Use OPTIONS request method. 28 | --trace Use TRACE request method. 29 | -?|-h|--help This information. 30 | -V|--version Display program version. 31 | ``` 32 | 33 |  说一下主要的几个选项: 指定 -f 时不等待服务器数据返回,  -t 为指定压力测试运行时间, -c 指定由多少个客户端发起测试请求。 34 | 35 | -9 -1 -2 分别为指定 HTTP/0.9 HTTP/1.0 HTTP/1.1。 36 | 37 | ### 示例 38 | 访问 http://baidu.com/ , 一个客户端,持续 60 秒。 39 | 40 | ``` 41 | ➜ webbench-1.5 webbench -t 60 http://www.baidu.com/ 42 | Webbench - Simple Web Benchmark 1.5 43 | Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. 44 | 45 | Benchmarking: GET http://www.baidu.com/ 46 | 1 client, running 60 sec. 47 | 48 | Speed=36 pages/min, 54150 bytes/sec. 49 | Requests: 36 susceed, 0 failed. 50 | ``` 51 | 52 | 访问 http://baidu.com/ , 10 个客户端,持续 60 秒,socket 提前关闭。 53 | 54 | ``` 55 | ➜ webbench-1.5 webbench -f -c 10 -t 60 http://www.baidu.com/ 56 | Webbench - Simple Web Benchmark 1.5 57 | Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. 58 | 59 | Benchmarking: GET http://www.baidu.com/ 60 | 10 clients, running 60 sec, early socket close. 61 | 62 | Speed=586 pages/min, 0 bytes/sec. 63 | Requests: 586 susceed, 0 failed. 64 | ``` 65 | 66 | ### 代码分析 67 | 68 | 其实我把项目 clone 之后做的第一件事是把代码风格全部调整了....强迫症面对不一样的缩进和花括号都不能忍... 69 | 70 | 项目源码都位于 socket.c 和 webbench.c 两个 c 文件里面。其他都是说明文档或用于编译。 71 | 72 | 可能涉及知识点:命令行参数解析(getopt_long) 信号处理(sigaction) socket  管道读写。这些都可以通过 man 相关函数或者直接百度,不赘述了。 73 | 74 | #### 主要函数 75 | 76 | * alarm_handler 信号处理函数,时钟结束时进行调用。 77 | * usage 输出 webbench 命令用法 78 | * main 提供程序入口... 79 | * build_request 构造 HTTP 请求 80 | * bench 派生子进程,父子进程管道通信最后输出计算结果。 81 | * benchcore 每个子进程的实际发起请求函数。 82 | 83 | #### 主要流程 84 | 说一下程序执行的主要流程: 85 | 86 | 1. 解析命令行参数,根据命令行指定参数设定变量,可以认为是初始化配置。 87 | 2. 根据指定的配置构造 HTTP 请求报文格式。 88 | 3. 开始执行 bench 函数,先进行一次 socket 连接建立与断开,测试是否可以正常访问。 89 | 4. 建立管道,派生根据指定进程数派生子进程。 90 | 5. 每个子进程调用 benchcore 函数,先通过 sigaction 安装信号,用 alarm 设置闹钟函数,接着不断建立 socket 进行通信,与服务器交互数据,直到收到信号结束访问测试。子进程将访问测试结果写进管道。 91 | 6. 父进程读取管道数据,汇总子进程信息,收到所有子进程消息后,输出汇总信息,结束。 92 | 93 | #### 流程图 94 | ![webbench](https://raw.githubusercontent.com/AngryHacker/articles/master/img/webbench.png)                                               95 | 96 | #### 注释源码 97 | 98 | ##### socket.c 99 | 100 | ```C 101 | 102 | /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ 103 | * 104 | * This module has been modified by Radim Kolar for OS/2 emx 105 | */ 106 | 107 | /*********************************************************************** 108 | module: socket.c 109 | program: popclient 110 | SCCS ID: @(#)socket.c 1.5 4/1/94 111 | programmer: Virginia Tech Computing Center 112 | compiler: DEC RISC C compiler (Ultrix 4.1) 113 | environment: DEC Ultrix 4.3 114 | description: UNIX sockets code. 115 | ***********************************************************************/ 116 | 117 | #include 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | #include 124 | #include 125 | #include 126 | #include 127 | #include 128 | #include 129 | 130 | int Socket(const char *host, int clientPort) 131 | { 132 | int sock; 133 | unsigned long inaddr; 134 | struct sockaddr_in ad; 135 | struct hostent *hp; 136 | 137 | /* 初始化地址 */ 138 | memset(&ad, 0, sizeof(ad)); 139 | ad.sin_family = AF_INET; 140 | 141 | /* 尝试把主机名转化为数字 */ 142 | inaddr = inet_addr(host); 143 | if (inaddr != INADDR_NONE) 144 | memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); 145 | else 146 | { 147 | /* 取得 ip 地址 */ 148 | hp = gethostbyname(host); 149 | if (hp == NULL) 150 | return -1; 151 | memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); 152 | } 153 | ad.sin_port = htons(clientPort); 154 | 155 | /* 建立 socket */ 156 | sock = socket(AF_INET, SOCK_STREAM, 0); 157 | if (sock < 0) 158 | return sock; 159 | /* 建立链接 */ 160 | if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) 161 | return -1; 162 | return sock; 163 | } 164 | ``` 165 | 166 | ##### webbench.c 167 | 168 | ```C 169 | /* 170 | * (C) Radim Kolar 1997-2004 171 | * This is free software, see GNU Public License version 2 for 172 | * details. 173 | * 174 | * Simple forking WWW Server benchmark: 175 | * 176 | * Usage: 177 | * webbench --help 178 | * 179 | * Return codes: 180 | * 0 - sucess 181 | * 1 - benchmark failed (server is not on-line) 182 | * 2 - bad param 183 | * 3 - internal error, fork failed 184 | * 185 | */ 186 | #include "socket.c" 187 | #include 188 | #include 189 | #include 190 | #include 191 | #include 192 | #include 193 | #include 194 | 195 | /* values */ 196 | volatile int timerexpired = 0; 197 | int speed = 0; 198 | int failed = 0; 199 | int bytes = 0; 200 | 201 | /* globals */ 202 | int http10 = 1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ 203 | 204 | /* Allow: GET, HEAD, OPTIONS, TRACE */ 205 | #define METHOD_GET 0 206 | #define METHOD_HEAD 1 207 | #define METHOD_OPTIONS 2 208 | #define METHOD_TRACE 3 209 | #define PROGRAM_VERSION "1.5" 210 | 211 | /* 默认设置 */ 212 | int method = METHOD_GET; /* GET 方式 */ 213 | int clients = 1; /* 只模拟一个客户端 */ 214 | int force = 0; /* 等待响应 */ 215 | int force_reload = 0; /* 失败时重新请求 */ 216 | int proxyport = 80; /* 访问端口 */ 217 | char *proxyhost = NULL; /* 代理服务器 */ 218 | int benchtime = 30; /* 模拟请求时间 */ 219 | 220 | /* internal */ 221 | int mypipe[2]; /* 管道 */ 222 | char host[MAXHOSTNAMELEN]; /* 网络地址 */ 223 | #define REQUEST_SIZE 2048 224 | char request[REQUEST_SIZE]; /* 请求 */ 225 | 226 | static const struct option long_options[]= 227 | { 228 | {"force",no_argument,&force,1}, 229 | {"reload",no_argument,&force_reload,1}, 230 | {"time",required_argument,NULL,'t'}, 231 | {"help",no_argument,NULL,'?'}, 232 | {"http09",no_argument,NULL,'9'}, 233 | {"http10",no_argument,NULL,'1'}, 234 | {"http11",no_argument,NULL,'2'}, 235 | {"get",no_argument,&method,METHOD_GET}, 236 | {"head",no_argument,&method,METHOD_HEAD}, 237 | {"options",no_argument,&method,METHOD_OPTIONS}, 238 | {"trace",no_argument,&method,METHOD_TRACE}, 239 | {"version",no_argument,NULL,'V'}, 240 | {"proxy",required_argument,NULL,'p'}, 241 | {"clients",required_argument,NULL,'c'}, 242 | {NULL,0,NULL,0} 243 | }; 244 | 245 | /* prototypes */ 246 | static void benchcore(const char* host,const int port, const char *request); 247 | static int bench(void); 248 | static void build_request(const char *url); 249 | 250 | static void alarm_handler(int signal) 251 | { 252 | timerexpired = 1; 253 | } 254 | 255 | /* help 信息 */ 256 | static void usage(void) 257 | { 258 | fprintf(stderr, 259 | "webbench [option]... URL\n" 260 | " -f|--force Don't wait for reply from server.\n" 261 | " -r|--reload Send reload request - Pragma: no-cache.\n" 262 | " -t|--time Run benchmark for seconds. Default 30.\n" 263 | " -p|--proxy Use proxy server for request.\n" 264 | " -c|--clients Run HTTP clients at once. Default one.\n" 265 | " -9|--http09 Use HTTP/0.9 style requests.\n" 266 | " -1|--http10 Use HTTP/1.0 protocol.\n" 267 | " -2|--http11 Use HTTP/1.1 protocol.\n" 268 | " --get Use GET request method.\n" 269 | " --head Use HEAD request method.\n" 270 | " --options Use OPTIONS request method.\n" 271 | " --trace Use TRACE request method.\n" 272 | " -?|-h|--help This information.\n" 273 | " -V|--version Display program version.\n" 274 | ); 275 | }; 276 | 277 | int main(int argc, char *argv[]) 278 | { 279 | int opt = 0; 280 | int options_index = 0; 281 | char *tmp = NULL; 282 | 283 | /* 不带参数时直接输出 help 信息 */ 284 | if(argc == 1) 285 | { 286 | usage(); 287 | return 2; 288 | } 289 | 290 | /* getopt_long 为命令行解析的库函数,可通过 man 3 getopt_long 查看 */ 291 | while((opt = getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index)) != EOF ) 292 | { 293 | /* 如果有返回对应的命令行参数 */ 294 | switch(opt) 295 | { 296 | case 0 : break; 297 | case 'f': force = 1;break; 298 | case 'r': force_reload = 1;break; 299 | case '9': http10 = 0;break; 300 | case '1': http10 = 1;break; 301 | case '2': http10 = 2;break; 302 | case 'V': 303 | printf(PROGRAM_VERSION"\n"); 304 | exit(0); 305 | case 't': 306 | benchtime = atoi(optarg); 307 | break; 308 | case 'p': 309 | /* proxy server parsing server:port */ 310 | tmp = strrchr(optarg,':'); 311 | proxyhost = optarg; 312 | if(tmp == NULL) 313 | { 314 | break; 315 | } 316 | if(tmp == optarg) 317 | { 318 | fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 319 | return 2; 320 | } 321 | if(tmp == optarg + strlen(optarg)-1) 322 | { 323 | fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 324 | return 2; 325 | } 326 | *tmp = '\0'; 327 | proxyport = atoi(tmp+1); 328 | break; 329 | case ':': 330 | case 'h': 331 | case '?': usage();return 2;break; 332 | case 'c': clients = atoi(optarg);break; 333 | } 334 | } 335 | 336 | /* optind 被 getopt_long 设置为命令行参数中未读取的下一个元素下标值 */ 337 | if(optind == argc) 338 | { 339 | fprintf(stderr,"webbench: Missing URL!\n"); 340 | usage(); 341 | return 2; 342 | } 343 | 344 | /* 不能指定客户端数和请求时间为 0 */ 345 | if(clients == 0) clients = 1; 346 | if(benchtime == 0) benchtime = 60; 347 | 348 | /* Copyright */ 349 | fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 350 | "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" 351 | ); 352 | 353 | /* 构造 HTTP 请求到 request 数组 */ 354 | build_request(argv[optind]); 355 | 356 | /* 以下到函数结束为输出提示信息 */ 357 | /* print bench info */ 358 | printf("\nBenchmarking: "); 359 | switch(method) 360 | { 361 | case METHOD_GET: 362 | default: 363 | printf("GET");break; 364 | case METHOD_OPTIONS: 365 | printf("OPTIONS");break; 366 | case METHOD_HEAD: 367 | printf("HEAD");break; 368 | case METHOD_TRACE: 369 | printf("TRACE");break; 370 | } 371 | 372 | printf(" %s",argv[optind]); 373 | switch(http10) 374 | { 375 | case 0: printf(" (using HTTP/0.9)");break; 376 | case 2: printf(" (using HTTP/1.1)");break; 377 | } 378 | 379 | printf("\n"); 380 | 381 | if(clients == 1) printf("1 client"); 382 | else 383 | printf("%d clients",clients); 384 | 385 | printf(", running %d sec", benchtime); 386 | if(force) printf(", early socket close"); 387 | if(proxyhost != NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); 388 | if(force_reload) printf(", forcing reload"); 389 | printf(".\n"); 390 | 391 | /* 开始压力测试,返回 bench 函数执行结果 */ 392 | return bench(); 393 | } 394 | 395 | void build_request(const char *url) 396 | { 397 | char tmp[10]; 398 | int i; 399 | 400 | /* 初始化 */ 401 | bzero(host,MAXHOSTNAMELEN); 402 | bzero(request,REQUEST_SIZE); 403 | 404 | /* 判断应该使用的 HTTP 协议 */ 405 | if(force_reload && proxyhost != NULL && http10 < 1) http10 = 1; 406 | if(method == METHOD_HEAD && http10 < 1) http10 = 1; 407 | if(method == METHOD_OPTIONS && http10 < 2) http10 = 2; 408 | if(method == METHOD_TRACE && http10 < 2) http10 = 2; 409 | 410 | /*填写 method 方法 */ 411 | switch(method) 412 | { 413 | default: 414 | case METHOD_GET: strcpy(request,"GET");break; 415 | case METHOD_HEAD: strcpy(request,"HEAD");break; 416 | case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 417 | case METHOD_TRACE: strcpy(request,"TRACE");break; 418 | } 419 | 420 | strcat(request," "); 421 | 422 | /* URL 合法性判断 */ 423 | if(NULL == strstr(url,"://")) 424 | { 425 | fprintf(stderr, "\n%s: is not a valid URL.\n",url); 426 | exit(2); 427 | } 428 | 429 | if(strlen(url)>1500) 430 | { 431 | fprintf(stderr,"URL is too long.\n"); 432 | exit(2); 433 | } 434 | 435 | if(proxyhost == NULL) 436 | if(0 != strncasecmp("http://",url,7)) 437 | { 438 | /* 只支持 HTTP 地址 */ 439 | fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 440 | exit(2); 441 | } 442 | 443 | /* 找到主机名开始的地方 */ 444 | /* protocol/host delimiter */ 445 | i = strstr(url,"://")-url+3; 446 | 447 | /* 必须以 / 结束*/ 448 | /* printf("%d\n",i); */ 449 | if(strchr(url+i,'/')==NULL) 450 | { 451 | fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 452 | exit(2); 453 | } 454 | 455 | if(proxyhost == NULL) 456 | { 457 | /* get port from hostname */ 458 | if(index(url+i,':') != NULL && index(url+i,':') < index(url+i,'/')) 459 | { 460 | strncpy(host,url+i,strchr(url+i,':')-url-i); 461 | /* 端口 */ 462 | bzero(tmp,10); 463 | strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1); 464 | /* printf("tmp=%s\n",tmp); */ 465 | /* 设置端口 */ 466 | proxyport = atoi(tmp); 467 | if(proxyport==0) proxyport=80; 468 | 469 | } else { 470 | strncpy(host,url+i,strcspn(url+i,"/")); 471 | } 472 | // printf("Host=%s\n",host); 473 | 474 | strcat(request+strlen(request),url+i+strcspn(url+i,"/")); 475 | } else { 476 | // printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport); 477 | strcat(request,url); 478 | } 479 | 480 | if(http10 == 1) 481 | strcat(request," HTTP/1.0"); 482 | else if (http10==2) 483 | strcat(request," HTTP/1.1"); 484 | strcat(request,"\r\n"); 485 | 486 | if(http10 > 0) 487 | strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); 488 | 489 | if(proxyhost == NULL && http10 > 0) 490 | { 491 | strcat(request,"Host: "); 492 | strcat(request,host); 493 | strcat(request,"\r\n"); 494 | } 495 | 496 | if(force_reload && proxyhost != NULL) 497 | { 498 | strcat(request,"Pragma: no-cache\r\n"); 499 | } 500 | 501 | if(http10 > 1) 502 | strcat(request,"Connection: close\r\n"); 503 | 504 | /* add empty line at end */ 505 | if(http10>0) strcat(request,"\r\n"); 506 | // printf("Req=%s\n",request); 507 | } 508 | 509 | /* vraci system rc error kod */ 510 | static int bench(void) 511 | { 512 | int i,j,k; 513 | pid_t pid = 0; 514 | FILE *f; 515 | 516 | /* 作为测试地址是否合法 */ 517 | /* check avaibility of target server */ 518 | i = Socket(proxyhost == NULL?host:proxyhost, proxyport); 519 | if(i < 0){ 520 | fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); 521 | return 1; 522 | } 523 | close(i); 524 | 525 | /* 建立管道 */ 526 | /* create pipe */ 527 | if(pipe(mypipe)) 528 | { 529 | perror("pipe failed."); 530 | return 3; 531 | } 532 | 533 | /* not needed, since we have alarm() in childrens */ 534 | /* wait 4 next system clock tick */ 535 | /* 536 | * cas=time(NULL); 537 | * while(time(NULL)==cas) 538 | * sched_yield(); 539 | * */ 540 | 541 | /* 派生子进程 */ 542 | /* fork childs */ 543 | for(i = 0;i < clients;i++) 544 | { 545 | pid = fork(); 546 | if(pid <= (pid_t)0) 547 | { 548 | /* child process or error*/ 549 | sleep(1); /* make childs faster */ 550 | break; /* 子进程立刻跳出循环,要不就子进程继续 fork 了 */ 551 | } 552 | } 553 | 554 | if( pid < (pid_t)0) 555 | { 556 | fprintf(stderr,"problems forking worker no. %d\n",i); 557 | perror("fork failed."); 558 | return 3; 559 | } 560 | 561 | if(pid == (pid_t)0) 562 | { 563 | /* 子进程发出实际请求 */ 564 | /* I am a child */ 565 | if(proxyhost == NULL) 566 | benchcore(host,proxyport,request); 567 | else 568 | benchcore(proxyhost,proxyport,request); 569 | 570 | /* 打开管道写 */ 571 | /* write results to pipe */ 572 | f = fdopen(mypipe[1],"w"); 573 | if(f == NULL) 574 | { 575 | perror("open pipe for writing failed."); 576 | return 3; 577 | } 578 | 579 | /* fprintf(stderr,"Child - %d %d\n",speed,failed); */ 580 | fprintf(f,"%d %d %d\n",speed,failed,bytes); 581 | fclose(f); 582 | 583 | return 0; 584 | 585 | } else { 586 | /* 父进程打开管道读 */ 587 | f = fdopen(mypipe[0],"r"); 588 | if(f == NULL) 589 | { 590 | perror("open pipe for reading failed."); 591 | return 3; 592 | } 593 | 594 | setvbuf(f,NULL,_IONBF,0); 595 | speed = 0; /* 传输速度 */ 596 | failed = 0; /* 失败请求数 */ 597 | bytes = 0; /* 传输字节数 */ 598 | 599 | while(1) 600 | { 601 | pid = fscanf(f,"%d %d %d",&i,&j,&k); 602 | if(pid<2) 603 | { 604 | fprintf(stderr,"Some of our childrens died.\n"); 605 | break; 606 | } 607 | speed += i; 608 | failed += j; 609 | bytes += k; 610 | /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */ 611 | /* 子进程是否读取完 */ 612 | if(--clients == 0) break; 613 | } 614 | fclose(f); 615 | 616 | /* 结果计算 */ 617 | printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n", 618 | (int)((speed+failed)/(benchtime/60.0f)), 619 | (int)(bytes/(float)benchtime), 620 | speed, 621 | failed); 622 | } 623 | return i; 624 | } 625 | 626 | void benchcore(const char *host,const int port,const char *req) 627 | { 628 | int rlen; 629 | char buf[1500]; 630 | int s,i; 631 | struct sigaction sa; 632 | 633 | /*安装信号 */ 634 | /* setup alarm signal handler */ 635 | sa.sa_handler = alarm_handler; 636 | sa.sa_flags = 0; 637 | if(sigaction(SIGALRM,&sa,NULL)) 638 | exit(3); 639 | 640 | /* 设置闹钟函数 */ 641 | alarm(benchtime); 642 | 643 | rlen = strlen(req); 644 | 645 | nexttry: 646 | while(1){ 647 | /* 收到信号则 timerexpired = 1 */ 648 | if(timerexpired) 649 | { 650 | if(failed > 0) 651 | { 652 | /* fprintf(stderr,"Correcting failed by signal\n"); */ 653 | failed--; 654 | } 655 | return; 656 | } 657 | /* 建立 socket, 进行 HTTP 请求 */ 658 | s = Socket(host,port); 659 | if(s < 0) 660 | { 661 | failed++; 662 | continue; 663 | } 664 | if(rlen!=write(s,req,rlen)) 665 | { 666 | failed++; 667 | close(s); 668 | continue; 669 | } 670 | /* HTTP 0.9 的处理 */ 671 | if(http10==0) 672 | /* 如果关闭不成功 */ 673 | if(shutdown(s,1)) 674 | { 675 | failed++; 676 | close(s); 677 | continue; 678 | } 679 | 680 | /* -f 选项时不读取服务器回复 */ 681 | if(force == 0) 682 | { 683 | /* read all available data from socket */ 684 | while(1) 685 | { 686 | if(timerexpired) break; 687 | i = read(s,buf,1500); 688 | /* fprintf(stderr,"%d\n",i); */ 689 | if(i<0) 690 | { 691 | failed++; 692 | close(s); 693 | goto nexttry; 694 | } 695 | else 696 | if(i == 0) break; 697 | else bytes+=i; 698 | } 699 | } 700 | if(close(s)) 701 | { 702 | failed++; 703 | continue; 704 | } 705 | speed++; 706 | } 707 | } 708 | ``` 709 | -------------------------------------------------------------------------------- /src/linux/linux_cpu_info.md: -------------------------------------------------------------------------------- 1 | # linux cpu 信息分析 2 | 在 Linux 下如何查看 CPU 信息呢?只要查看 /proc/cpuinfo 文件就好了。 3 | 4 | ```sh 5 | cat /proc/cpuinfo 6 | ``` 7 | 8 | 在我的电脑下得到如下结果: 9 | ![linux cpu info](https://raw.githubusercontent.com/AngryHacker/articles/master/img/linux_cpu_info.png) 10 | 11 | 12 | 其中包含了很多信息,比较重要的字段是: 13 | 14 | * processor 逻辑处理器的 id,从 0 开始 15 | 16 | * physical id 物理处理器的 id,从 0 开始,可以判断电脑中有多少个 CPU 17 | 18 | * core id 每个核心的 id 19 | 20 | * cpu cores 位于同一个物理处理器中的内核数量,可以看到每个 CPU 有几个物理核 21 | 22 | * siblings 位于同一个物理处理器中的逻辑处理器的数量,可以看到一个 CPU 有多少逻辑处理器 23 | 24 | 25 | 写了个简单的 Shell 脚本来判断对应的 CPU 信息: 26 | 27 | ```sh 28 | #!/bin/bash 29 | echo "CPU 分析菜单:" 30 | echo "1.查看逻辑 cpu 个数" 31 | echo "2.查看物理 cpu 个数" 32 | echo "3.查看每个 cpu 的物理核数" 33 | echo "4.查看每个 cpu 的逻辑处理器数" 34 | echo "5.退出" 35 | read -p "请选择:" input 36 | while [[ $input != '5' ]] 37 | do 38 | if [[ $input = '1' ]]; 39 | then 40 | echo -en "\n逻辑处理器共有:" 41 | cat /proc/cpuinfo | grep 'processor' | wc -l 42 | elif [[ $input = '2' ]]; 43 | then 44 | echo -en "\n物理处理器共有:" 45 | cat /proc/cpuinfo | grep 'physical id' | sort -u | wc -l 46 | elif [[ $input = '3' ]]; 47 | then 48 | echo -en "\n每个 cpu 的物理核数为:" 49 | cat /proc/cpuinfo | grep 'cpu cores' | sort -u | awk -F ':' '{print $2}' 50 | elif [[ $input = '4' ]]; 51 | then 52 | echo -en "\n每个 cpu 的逻辑处理器为:" 53 | cat /proc/cpuinfo | grep 'siblings' | sort -u | awk -F ':' '{print $2}' 54 | else 55 | echo -e '\n错误输入' 56 | fi 57 | echo -e "\nCPU 分析菜单:" 58 | echo "1.查看逻辑 cpu 个数" 59 | echo "2.查看物理 cpu 个数" 60 | echo "3.查看每个 cpu 的物理核数" 61 | echo "4.查看每个 cpu 的逻辑处理器数" 62 | echo "5.退出" 63 | read -p "请选择:" input 64 | done 65 | ``` -------------------------------------------------------------------------------- /src/linux/ls_implementation.md: -------------------------------------------------------------------------------- 1 | # ls 命令的实现 2 | 在 linux 下,我们用的最多的命令应该是 ls 吧,那么,你有没有想过这个命令怎么是实现呢?其实了解了 UNIX 环境的相关接口后,也就不难了~ 3 | 4 | ### 目标 5 | 可以用 ls 列出目录的简略信息, ls -l 列出目录的详细信息。默认列出本工作目录下的信息,可通过参数指定目录,可同时列出多个目录信息。 6 | 7 | ### 实现 8 | 实现的方式有非常多种,只要能够以良好的风格做出来就行,我下面简单说说自己的思路。 9 | 10 | 1. 用 is_detail 变量来记录是否列出文件详细信息, initpath 表示将要读取的目录路径。 11 | 2. 如果用户没有指定目录,则直接设置路径为当前工作目录,获取目录下所有文件后,进行排序,接着简略输出或者详细输出。结束程序。 12 | 3. 用户有提供目录参数的时候,判断是否要进行详细输出,设置 is_detail 变量。 13 | 4. 对于每个用户指定目录参数,循环进行:获取目录下所有文件名,进行排序,根据 is_detail 打印信息。 14 | 15 | 思路是如此的简单,你只要把每一个模块的细节处理好就行了。 16 | 17 | 主要的辅助函数有: 18 | * getWidth 获取终端的宽度,用于判断简略输出文件名时能放下多少列。 19 | * get_dir_detail 获取指定目录下的所有文件名。 20 | * getPermission 根据文件权限位 st_mode 获取文件权限信息,用字符串表示。 21 | * print_file_info 根据文件指针输出文件信息。 22 | * search_file_info 遍历处理目录下的所有文件。 23 | * init 根据路径是文件还是目录进行分别处理。 24 | * print_simple 只输出文件名,即简略显示 25 | 26 | ### 代码 27 | 28 | ```C++ 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #define MAX_FILE 1000 42 | 43 | /*保存文件名和文件所在目录*/ 44 | typedef struct item{ 45 | char d_name[256]; 46 | char dir_name[256]; 47 | }file_item; 48 | 49 | file_item files[MAX_FILE]; 50 | 51 | /*保存文件信息*/ 52 | typedef struct item_info{ 53 | unsigned int i_node; 54 | char permission[16]; 55 | short owner; 56 | short group; 57 | off_t size; 58 | time_t mod_time; 59 | nlink_t link_num; 60 | char name[256]; 61 | }info; 62 | 63 | int is_detail; 64 | int size_of_path; 65 | int terminalWidth; 66 | 67 | /* 获得用户名 */ 68 | const char *uid_to_name( short uid ) 69 | { 70 | struct passwd *pw_ptr; 71 | 72 | if ( ( pw_ptr = getpwuid( uid ) ) == NULL ) 73 | return "Unknown" ; 74 | else 75 | return pw_ptr->pw_name ; 76 | } 77 | 78 | /* 获得组名 */ 79 | const char *gid_to_name( short gid ) 80 | { 81 | struct group *grp_ptr; 82 | 83 | if ( ( grp_ptr = getgrgid(gid) ) == NULL ) 84 | return "Unknown" ; 85 | else 86 | return grp_ptr->gr_name; 87 | } 88 | 89 | /* 比较函数 */ 90 | int cmp(const void *a, const void *b) 91 | { 92 | return strcasecmp((*(file_item *)a).d_name,(*(file_item *)b).d_name); 93 | } 94 | 95 | /*获得终端宽度*/ 96 | void getWidth() 97 | { 98 | struct winsize wbuf; 99 | terminalWidth = 80; 100 | if( isatty(STDOUT_FILENO) ) 101 | { 102 | if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &wbuf) == -1 || wbuf.ws_col == 0) 103 | { 104 | char *tp; 105 | if( tp = getenv("COLUMNS") ) 106 | terminalWidth = atoi( tp ); 107 | } 108 | else 109 | terminalWidth = wbuf.ws_col; 110 | } 111 | return ; 112 | } 113 | 114 | /*获取所有文件名*/ 115 | void get_dir_detail(const char* dirname) 116 | { 117 | DIR* dir; 118 | struct dirent* drt; 119 | int cur = 0; 120 | /*打开目录*/ 121 | dir = opendir(dirname); 122 | if(dir == NULL) 123 | { 124 | perror("Read directroy Error."); 125 | return; 126 | } 127 | /*遍历目录*/ 128 | while((drt = readdir(dir)) != NULL) 129 | { 130 | file_item* cur_node = files + cur; 131 | /*忽略 . 和 .. 目录*/ 132 | if((strcmp(drt->d_name,".")==0)||(strcmp(drt->d_name,"..")==0)) 133 | continue; 134 | 135 | if(drt->d_name[0] == '.') 136 | continue; 137 | 138 | size_of_path ++; 139 | cur++; 140 | if(cur >= MAX_FILE) 141 | { 142 | printf("Too many files!\n"); 143 | exit(-1); 144 | } 145 | 146 | /*文件名*/ 147 | strcpy(cur_node->d_name,drt->d_name); 148 | /*目录名*/ 149 | strcpy(cur_node->dir_name,dirname); 150 | strcat(cur_node->dir_name,drt->d_name); 151 | } 152 | closedir(dir); 153 | } 154 | 155 | /*获取文件权限*/ 156 | void getPermission(mode_t st_mode, char* permission) 157 | { 158 | /*初始化 111000000 */ 159 | unsigned int mask = 0700; 160 | static const char* perm[] = {"---","--x","-w-","-wx","r--","r-x","rw-","rwx"}; 161 | 162 | char type; 163 | 164 | if(S_ISREG(st_mode)) 165 | type = '-'; 166 | else if(S_ISDIR(st_mode)) 167 | type = 'd'; 168 | else if(S_ISCHR(st_mode)) 169 | type = 'c'; 170 | else if(S_ISLNK(st_mode)) 171 | type = 'l'; 172 | else 173 | type = '?'; 174 | 175 | permission[0] = type; 176 | 177 | /* 计算权限*/ 178 | int i = 3; 179 | char* ptr = permission + 1; 180 | while(i>0) 181 | { 182 | *ptr++ = perm[(st_mode & mask)>>(i-1)*3][0]; 183 | *ptr++ = perm[(st_mode & mask)>>(i-1)*3][1]; 184 | *ptr++ = perm[(st_mode & mask)>>(i-1)*3][2]; 185 | i--; 186 | mask>>=3; 187 | } 188 | } 189 | 190 | /*输出文件信息*/ 191 | void print_file_info(info *file_info) 192 | { 193 | /*格式转换*/ 194 | unsigned fsize = file_info->size; 195 | char ftime[64]; 196 | strcpy(ftime,ctime(&file_info->mod_time)); 197 | ftime[strlen(ftime) - 1] = '\0'; 198 | 199 | int i = 8; 200 | printf("%10s ",file_info->permission); 201 | printf("%3i ",file_info->link_num); 202 | printf("%14s",uid_to_name(file_info->owner)); 203 | printf("%14s ",gid_to_name(file_info->group)); 204 | printf("%8u ",fsize); 205 | printf("%26s ",ftime); 206 | printf("%s \n",file_info->name); 207 | 208 | } 209 | 210 | /*遍历处理所有文件*/ 211 | void search_file_info() 212 | { 213 | 214 | struct stat file_stat; 215 | 216 | int cur = 0; 217 | info file_info; 218 | 219 | /*遍历文件*/ 220 | while(cur < size_of_path) 221 | { 222 | file_item* cur_node = files + cur; 223 | memset(file_info.permission,'\0',sizeof(file_info.permission)); 224 | if(stat(cur_node->dir_name,&file_stat) == -1) 225 | { 226 | perror("Can't get the information of the file.\n"); 227 | continue; 228 | } 229 | 230 | /*获取文件权限*/ 231 | getPermission(file_stat.st_mode,file_info.permission); 232 | 233 | /*用户 ID 和 组ID*/ 234 | file_info.owner = file_stat.st_uid; 235 | file_info.group = file_stat.st_gid; 236 | /*修改时间*/ 237 | file_info.mod_time = file_stat.st_atime; 238 | /*文件大小*/ 239 | file_info.size = file_stat.st_size; 240 | /* i-node 编号*/ 241 | file_info.i_node = file_stat.st_ino; 242 | /*链接数*/ 243 | file_info.link_num = file_stat.st_nlink; 244 | /*拷贝文件名*/ 245 | strcpy(file_info.name,cur_node->d_name); 246 | 247 | /*打印文件信息*/ 248 | print_file_info(&file_info); 249 | cur++; 250 | } 251 | } 252 | 253 | /*根据初始路径是文件还是目录区分处理*/ 254 | void init(char* pathname){ 255 | size_of_path = 0; 256 | struct stat file_stat; 257 | if(stat(pathname,&file_stat) == -1) 258 | { 259 | perror("Can't get the information of the given path.\n"); 260 | return; 261 | } 262 | /*普通文件*/ 263 | if(S_ISREG(file_stat.st_mode)) 264 | { 265 | size_of_path = 1; 266 | char* base_name = basename(pathname); 267 | strcpy(files[0].d_name ,base_name); 268 | strcpy(files[0].dir_name,pathname); 269 | return; 270 | } 271 | /*目录文件*/ 272 | if(S_ISDIR(file_stat.st_mode)) 273 | { 274 | /*统一目录格式*/ 275 | if(pathname[strlen(pathname)-1] != '/') 276 | { 277 | char* ptr = pathname + strlen(pathname); 278 | *ptr++ = '/'; 279 | *ptr = 0; 280 | } 281 | get_dir_detail(pathname); 282 | return; 283 | } 284 | } 285 | 286 | void print_simple() 287 | { 288 | int max_len = 0; 289 | int num_in_row = 0; 290 | int num_in_col = 0; 291 | 292 | getWidth(); 293 | 294 | /*获取最大文件名长度*/ 295 | int i = 1; 296 | max_len = strlen(files[0].d_name); 297 | while(i < size_of_path) 298 | { 299 | int cur_len = strlen(files[i].d_name); 300 | max_len = max_len > cur_len?max_len:cur_len; 301 | i++; 302 | } 303 | max_len += 2; 304 | 305 | /*计算每行数目和每列数目*/ 306 | num_in_row = terminalWidth/max_len; 307 | if(size_of_path % num_in_row == 0) 308 | num_in_col = size_of_path / num_in_row; 309 | else 310 | num_in_col = size_of_path / num_in_row + 1; 311 | 312 | i = 0; 313 | while(i < num_in_col) 314 | { 315 | int j; 316 | for(j = 0;j < num_in_row;++j) 317 | { 318 | file_item* cur = files + i + j*num_in_col; 319 | printf("%-*s",max_len,cur->d_name); 320 | } 321 | printf("\n"); 322 | i++; 323 | } 324 | } 325 | 326 | int main(int argc,char* argv[]) 327 | { 328 | 329 | is_detail = 0; 330 | 331 | /*设置初始路径*/ 332 | char initpath[256]; 333 | if(argc == 1 || (argc == 2 && strcmp(argv[1],"-l") == 0)) 334 | { 335 | strcpy(initpath,"./"); 336 | 337 | /*获取目录下所有文件*/ 338 | init(initpath); 339 | 340 | qsort(files,size_of_path,sizeof(files[0]),cmp); 341 | 342 | /*打印每个文件信息*/ 343 | if(argc == 1) 344 | print_simple(); 345 | else 346 | search_file_info(); 347 | exit(0); 348 | } 349 | 350 | int i = 1; 351 | if(argc > 2 && strcmp(argv[1],"-l") == 0) 352 | { 353 | i++; 354 | is_detail = 1; 355 | } 356 | 357 | int flag = 1; 358 | while(i < argc) 359 | { 360 | if(!flag) printf("\n\n"); 361 | flag = 0; 362 | strcpy(initpath,argv[i]); 363 | 364 | /*获取目录下所有文件*/ 365 | init(initpath); 366 | 367 | if(size_of_path == 0) 368 | { 369 | perror("usage error\n"); 370 | exit(-1); 371 | } 372 | 373 | qsort(files,size_of_path,sizeof(files[0]),cmp); 374 | 375 | if(is_detail) 376 | { 377 | /*打印每个文件信息*/ 378 | search_file_info(); 379 | } 380 | else 381 | { 382 | /*打印简略信息*/ 383 | print_simple(); 384 | } 385 | 386 | i++; 387 | } 388 | 389 | return 0; 390 | } 391 | ``` 392 | 393 | ### 效果 394 | 贴一张效果图: 395 | ![linux ls](https://raw.githubusercontent.com/AngryHacker/articles/master/img/linux-ls.png) -------------------------------------------------------------------------------- /src/open_source_components/google_gflags.md: -------------------------------------------------------------------------------- 1 | # google gflags 库完全使用 2 | 3 | ### 简单介绍 4 | gflags 是 google 开源的用于处理命令行参数的项目。 5 | 6 | ### 安装编译 7 | 项目主页:[gflags](https://github.com/gflags/gflags) 8 | 9 | ``` 10 | ➜ ~ git clone https://github.com/gflags/gflags.git # 下载源码 11 | ➜ ~ cd gflags 12 | ➜ gflags git:(master) ✗ mkdir build && cd build # 建立文件夹 13 | ➜ build git:(master) ✗ cmake .. # 使用 cmake 编译生成 Makefile 文件 14 | ➜ build git:(master) ✗ make # make 编译 15 | ➜ build git:(master) ✗ sudo make install # 安装库 16 | ``` 17 | 18 | 这时 gflags 库会默认安装在 `/usr/local/lib/` 下,头文件放在 `/usr/local/include/gflags/` 中。 19 | 20 | ### 基础使用 21 | 我们从一个简单的需求来看 gflags 的使用,只要一分钟。假如我们有个程序,需要知道服务器的 ip 和端口,我们在程序中有默认的指定参数,同时希望可以通过命令行来指定不同的值。 22 | 23 | 实现如下: 24 | 25 | ```c++ 26 | #include 27 | 28 | #include 29 | 30 | /** 31 | * 定义命令行参数变量 32 | * 默认的主机地址为 127.0.0.1,变量解释为 'the server host' 33 | * 默认的端口为 12306,变量解释为 'the server port' 34 | */ 35 | DEFINE_string(host, "127.0.0.1", "the server host"); 36 | DEFINE_int32(port, 12306, "the server port"); 37 | 38 | int main(int argc, char** argv) { 39 | // 解析命令行参数,一般都放在 main 函数中开始位置 40 | gflags::ParseCommandLineFlags(&argc, &argv, true); 41 | // 访问参数变量,加上 FLAGS_ 42 | std::cout << "The server host is: " << FLAGS_host 43 | << ", the server port is: " << FLAGS_port << std::endl; 44 | return 0; 45 | } 46 | ``` 47 | 48 | OK, 写完了让我们编译运行。 49 | 50 | ```shell 51 | ➜ test g++ gflags_test.cc -o gflags_test -lgflags -lpthread # -l 链接库进行编译 52 | 53 | ➜ test ./gflags_test #不带任何参数 54 | The server host is: 127.0.0.1, the server port is: 12306 55 | 56 | ➜ test ./gflags_test -host 10.123.78.90 #只带 host 参数 57 | The server host is: 10.123.78.90, the server port is: 12306 58 | 59 | ➜ test ./gflags_test -port 8008 # 只带 port 参数 60 | The server host is: 127.0.0.1, the server port is: 8008 61 | 62 | ➜ test ./gflags_test -host 10.123.78.90 -port 8008 # host 和 port 参数 63 | The server host is: 10.123.78.90, the server port is: 8008 64 | 65 | ➜ test ./gflags_test --host 10.123.78.90 --port 8008 # 用 -- 指定 66 | The server host is: 10.123.78.90, the server port is: 8008 67 | 68 | ➜ test ./gflags_test --host=10.123.78.90 --port=8008 # 用 = 连接参数值 69 | The server host is: 10.123.78.90, the server port is: 8008 70 | 71 | ➜ test ./gflags_test --help # 用 help 查看可指定的参数及参数说明 72 | gflags_test: Warning: SetUsageMessage() never called 73 | 74 | Flags from /home/rookie/code/gflags/src/gflags.cc: 75 | .... # 略 76 | 77 | Flags from /home/rookie/code/gflags/src/gflags_reporting.cc: 78 | ..... # 略 79 | 80 | Flags from gflags_test.cc: #这里是我们定义的参数说明和默认值 81 | -host (the server host) type: string default: "127.0.0.1" 82 | -port (the server port) type: int32 default: 12306 83 | 84 | ``` 85 | 86 | 看,我们不仅快速完成了需求,而且似乎多了很多看起来不错的特性。在上面我们使用了两种类型的参数,string 和 int32,gflags 一共支持 5 种类型的命令行参数定义: 87 | 88 | * DEFINE_bool: 布尔类型 89 | * DEFINE_int32: 32 位整数 90 | * DEFINE_int64: 64 位整数 91 | * DEFINE_uint64: 无符号 64 位整数 92 | * DEFINE_double: 浮点类型 double 93 | * DEFINE_string: C++ string 类型 94 | 95 | 如果你希望支持更复杂的结构,比如 list,你需要通过自己做一定的定义和解析,比如字符串按某个分隔符分割得到一个列表。 96 | 97 | 每一种类型的定义和使用都跟上面我们的例子相似,有所不同的是 bool 参数,bool 参数在命令行可以不指定值也可以指定值,假如我们定义了一个 bool 参数 debug_switch,可以在命令行这样指定: 98 | 99 | ```shell 100 | ➜ test ./gflags_test -debug_switch # 这样就是 true 101 | ➜ test ./gflags_test -debug_switch=true # 这样也是 true 102 | ➜ test ./gflags_test -debug_switch=1 # 这样也是 true 103 | ➜ test ./gflags_test -debug_switch=false # 0 也是 false 104 | ``` 105 | 所有我们定义的 gflags 变量都可以通过 FLAGS_ 前缀加参数名访问,gflags 变量也可以被自由修改: 106 | 107 | ```c++ 108 | if (FLAGS_consider_made_up_languages) 109 | FLAGS_languages += ",klingon"; 110 | if (FLAGS_languages.find("finnish") != string::npos) 111 | HandleFinnish(); 112 | ``` 113 | 114 | ### 进阶?同样 Easy 115 | 116 | #### 定义规范 117 | 118 | 如果你想要访问在另一个文件定义的 gflags 变量呢?使用 `DECLARE_`,它的作用就相当于用 extern 声明变量。为了方便的管理变量,我们推荐在 .cc 或者 .cpp 文件中 DEFINE 变量,然后只在对应 .h 中或者单元测试中 DECLARE 变量。例如,在 foo.cc 定义了一个 gflags 变量 `DEFINE_string(name, 'bob', '')`,假如你需要在其他文件中使用该变量,那么在 foo.h 中声明 `DECLARE_string(name)`,然后在使用该变量的文件中 `include "foo.h"` 就可以。当然,这只是为了更好地管理文件关联,如果你不想遵循也是可以的。 119 | 120 | #### 参数检查 121 | 如果你定义的 gflags 参数很重要,希望检查其值是否符合预期,那么可以定义并注册参数的值的检查函数。如果采用 static 全局变量来确保检查函数会在 main 开始时被注册,可以保证注册会在 ParseCommandLineFlags 函数之前。如果默认值检查失败,那么 ParseCommandLineFlags 将会使程序退出。如果之后使用 SetCommandLineOption() 来改变参数的值,那么检查函数也会被调用,但是如果验证失败,只会返回 false,然后参数保持原来的值,程序不会结束。看下面的程序示例: 122 | 123 | ```c++ 124 | #include 125 | #include 126 | #include 127 | 128 | #include 129 | 130 | // 定义对 FLAGS_port 的检查函数 131 | static bool ValidatePort(const char* name, int32_t value) { 132 | if (value > 0 && value < 32768) { 133 | return true; 134 | } 135 | printf("Invalid value for --%s: %d\n", name, (int)value); 136 | return false; 137 | } 138 | 139 | /** 140 | * 设置命令行参数变量 141 | * 默认的主机地址为 127.0.0.1,变量解释为 'the server host' 142 | * 默认的端口为 12306,变量解释为 'the server port' 143 | */ 144 | DEFINE_string(host, "127.0.0.1", "the server host"); 145 | DEFINE_int32(port, 12306, "the server port"); 146 | 147 | // 使用全局 static 变量来注册函数,static 变量会在 main 函数开始时就调用 148 | static const bool port_dummy = gflags::RegisterFlagValidator(&FLAGS_port, &ValidatePort); 149 | 150 | int main(int argc, char** argv) { 151 | // 解析命令行参数,一般都放在 main 函数中开始位置 152 | gflags::ParseCommandLineFlags(&argc, &argv, true); 153 | std::cout << "The server host is: " << FLAGS_host 154 | << ", the server port is: " << FLAGS_port << std::endl; 155 | 156 | // 使用 SetCommandLineOption 函数对参数进行设置才会调用检查函数 157 | gflags::SetCommandLineOption("port", "-2"); 158 | std::cout << "The server host is: " << FLAGS_host 159 | << ", the server port is: " << FLAGS_port << std::endl; 160 | return 0; 161 | } 162 | ``` 163 | 让我们运行一下程序,看看怎么样: 164 | ```shell 165 | #命令行指定非法值,程序解析参数时直接退出 166 | ➜ test ./gflags_test -port -2 167 | Invalid value for --port: -2 168 | ERROR: failed validation of new value '-2' for flag 'port' 169 | # 这里参数默认值合法,但是 SetCommandLineOption 指定的值不合法,程序不退出,参数保持原来的值 170 | ➜ test ./gflags_test 171 | The server host is: 127.0.0.1, the server port is: 12306 172 | Invalid value for --port: -2 173 | The server host is: 127.0.0.1, the server port is: 12306 174 | ``` 175 | 176 | #### 使用 flagfile 177 | 如果我们定义了很多参数,那么每次启动时都在命令行指定对应的参数显然是不合理的。gflags 库已经很好的解决了这个问题。你可以把 flag 参数和对应的值写在文件中,然后运行时使用 -flagfile 来指定对应的 flag 文件就好。文件中的参数定义格式与通过命令行指定是一样的。 178 | 179 | 例如,我们可以定义这样一个文件,文件后缀名没有关系,为了方便管理可以使用 .flags: 180 | 181 | ``` 182 | --host=10.123.14.11 183 | --port=23333 184 | ``` 185 | 186 | 然后命令行指定: 187 | 188 | ``` 189 | ➜ test ./gflags_test --flagfile server.flags 190 | The server host is: 10.123.14.11, the server port is: 23333 191 | ``` 192 | 193 | 棒!以后再也不用担心参数太多了~^_^ 194 | 195 | 看到这里,是不是觉得 gflags 对你的项目很有帮助?用起来吧,释放超能力 :) 196 | -------------------------------------------------------------------------------- /src/open_source_components/google_glog.md: -------------------------------------------------------------------------------- 1 | # google glog 简单使用小结 2 | glog 是 google 的一个 c++ 开源日志系统,轻巧灵活,入门简单,而且功能也比较完善。 3 | 4 | ### 安装 5 | 以下是官方的安装方法,一句命令: 6 | ``` 7 | ➜ code git clone https://github.com/google/glog.git 8 | ➜ code cd glog 9 | ➜ glog git:(master) ✗ ./configure && make && make install 10 | ``` 11 | 12 | 然而我出现了 N 个错误,以下是两个编译的错误: 13 | 14 | #### 错误记录: 15 | Issue1: 出现 `aclocal-1.14: command not found` 和 `recipe for target 'aclocal.m4' failed` 的错误提示。 16 | 解决方法: 17 | 18 | ``` 19 | ➜ glog git:(master) ✗ sudo apt-get install automake 20 | ➜ glog git:(master) ✗ sudo autoreconf -ivf 21 | ``` 22 | 23 | Issue2:之后出现另一个错误:`error: expected initializer before 'Demangle'`。 24 | 解决方法: 25 | 26 | ``` 27 | ➜ code cd glog 28 | ➜ glog git:(master) ✗ mkdir build && cd build 29 | ➜ build git:(master) ✗ export CXXFLAGS="-fPIC" && cmake .. && make VERBOSE=1 30 | ➜ build git:(master) ✗ make 31 | ➜ build git:(master) ✗ sudo make install 32 | ``` 33 | 34 | ### 使用 35 | #### 菜鸟级 36 | 从一个最简单的程序开始: 37 | 38 | ```c++ 39 | #include 40 | 41 | int main(int argc,char* argv[]) 42 | { 43 | google::InitGoogleLogging(argv[0]); //初始化 glog 44 | LOG(INFO) << "Hello,GOOGLE!"; 45 | } 46 | ``` 47 | 编译: 48 | ``` 49 | ➜ glog g++ glog_test2.cpp -lglog -lgflags -lpthread -o glog_test #编译 50 | ➜ glog ./glog_test 51 | 52 | ➜ glog ll /tmp # 默认日志在 /tmp 下 53 | total 28K 54 | -rw-rw-r-- 1 angryrookie angryrookie 193 7月 3 22:04 glog_test.cheng.angryrookie.log.INFO.20160703-220405.6569 55 | lrwxrwxrwx 1 angryrookie angryrookie 57 7月 3 22:04 glog_test.INFO -> glog_test.cheng.angryrookie.log.INFO.20160703-220405.6569 56 | ➜ glog cat /tmp/glog_test.INFO 57 | Log file created at: 2016/07/03 22:04:05 58 | Running on machine: cheng 59 | Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg 60 | I0703 22:04:05.242153 6569 glog_test2.cpp:7] Hello,GLOG! 61 | ``` 62 | 63 | 这里 g++ 编译的时候注意链接的库是要按顺序的(我也是才知道)。gcc 和 g++ 中库的链接顺序是从右往左进行,所以要把最基础实现的库放在最后,这样左边的 lib 就可以调用右边的 lib 中的代码。 64 | 65 | 一开始没注意顺序出现各种找不到函数,用 `nm -C` 定位了好久。而且这里我必须再链接上 gflags 才能成功编译,看网上其他人都没有。如果有大神知道为什么麻烦带带我。 66 | 67 | 这里没有指定日志文件的目录,默认会放在 /tmp 下,文件名的格式为 `...log...