-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcn.search-data.min.0b98420e480ba22dbf4dfa693a8da83925282ea461907cc8bb60363cab8dc80f.json
More file actions
1 lines (1 loc) · 259 KB
/
cn.search-data.min.0b98420e480ba22dbf4dfa693a8da83925282ea461907cc8bb60363cab8dc80f.json
File metadata and controls
1 lines (1 loc) · 259 KB
1
[{"id":0,"href":"/cn/blog/2023/hbov/","title":"co::vector 中的一个隐藏 bug","section":"2023","content":"最近有用户反馈了一个 bug,使用 co::vector 时程序崩溃了。\n拿用户提供的代码在 mac 上跑了下,没问题,又在 linux 上试了下,复现了。重新编译 debug 版本的 coost 及用户程序,再用 gdb 启动该程序,挂掉后用 bt 命令查看堆栈信息,发现是 co::vector\u0026lt;std::string\u0026gt; 析构时崩溃的。从用户的代码看,只是往 vector 里逐个添加了 2000 个字符串,并没有其他操作。\n想起之前在实现 co::chan 时遇到的一个问题,猜测可能是同一个问题,将 std::string 换成 coost 中的 fastring,重新编译,程序可以正常运行,验证了我的猜测。\n程序崩溃有两个原因,\n"},{"id":1,"href":"/cn/co/concurrency/atomic/","title":"原子操作","section":"并发编程","content":"include: co/atomic.h.\n#Memory Order coost 从 v3.0 开始,增加了对 memory order 的支持。coost 中的 6 种 memory order 定义如下:\nenum memory_order_t { mo_relaxed, mo_consume, mo_acquire, mo_release, mo_acq_rel, mo_seq_cst, }; 为了保持兼容性,coost 中的原子操作默认的 memory order 均为 mo_seq_cst。\n#v3.0 删除的 API atomic_get,使用 atomic_load 取代之。 atomic_set,使用 atomic_store 取代之。 atomic_reset,使用 atomic_store(\u0026amp;x, 0) 取代之。 #load \u0026amp; store #atomic_load template \u0026lt;typename T\u0026gt; inline T atomic_load(const T* p, memory_order_t mo = mo_seq_cst); 此函数获取 p 指向的变量的值,T 是长度为 1, 2, 4, 8 字节的任意内置数据类型(包括指针类型)。\nmo 可以是 mo_relaxed, mo_consume, mo_acquire, mo_seq_cst。\n示例\nint i = 7; int r = atomic_load(\u0026amp;i); // r = 7 int x = atomic_load(\u0026amp;i, mo_relaxed); #atomic_store template \u0026lt;typename T, typename V\u0026gt; inline void atomic_store(T* p, V v, memory_order_t mo = mo_seq_cst); 此函数将 p 指向的值设为 v,T 是长度为 1, 2, 4, 8 字节的任意内置数据类型(包括指针类型),V 是可以转换为 T 类型的任意类型。\nmo 可以是 mo_relaxed, mo_release, mo_seq_cst。\n示例\nint i = 7; atomic_store(\u0026amp;i, 3); // i -\u0026gt; 3 atomic_store(\u0026amp;i, 3, mo_release); #交换 #atomic_swap template \u0026lt;typename T, typename V\u0026gt; inline T atomic_swap(T* p, V v, memory_order_t mo = mo_seq_cst); 原子交换操作,T 是长度为 1, 2, 4, 8 字节的任意内置数据类型(包括指针类型),V 是可以转换为 T 类型的任意类型。\n此函数对 p 指向的值与 v 进行交换操作,并返回交换操作前的值。\nmo 可以是任意类型。\n示例\nbool b = false; int i = 0; void* p = 0; bool x = atomic_swap(\u0026amp;b, true); // b -\u0026gt; true, x = false int r = atomic_swap(\u0026amp;i, 1); // i -\u0026gt; 1, r = 0 void* q = atomic_swap(\u0026amp;p, (void*)8); // p -\u0026gt; 8, q = 0 #atomic_cas template\u0026lt;typename T, typename O, typename V\u0026gt; inline T atomic_cas( T* p, O o, V v, memory_order_t smo = mo_seq_cst, memory_order_t fmo = mo_seq_cst ); template \u0026lt;typename T, typename O, typename V\u0026gt; inline T atomic_compare_swap( T* p, O o, V v, memory_order_t smo = mo_seq_cst, memory_order_t fmo = mo_seq_cst ); 原子交换操作,T 是长度为 1, 2, 4, 8 字节的任意内置数据类型(包括指针类型),O 与 V 是可以转换为 T 类型的任意类型。\n此函数仅在 p 指向的值与 o 相等时,才与 v 进行交换操作。\n此函数返回交换操作前的值,用户可以根据返回值是否与 o 相等来判断是否进行了交换操作。\nsmo,交换操作成功时的 memory order,可以是任意类型;fmo,交换操作失败时的 memory order,不能是 mo_release, mo_acq_rel,并且不能强于 smo。\n示例\nbool b = false; int i = 0; void* p = 0; bool x = atomic_cas(\u0026amp;b, false, true); // b -\u0026gt; true, x = false int r = atomic_cas(\u0026amp;i, 1, 2); // 不会交换, i 保持不变, r = 0 void* q = atomic_cas(\u0026amp;p, 0, (void*)8); // p -\u0026gt; 8, q = 0 #atomic_bool_cas template\u0026lt;typename T, typename O, typename V\u0026gt; inline bool atomic_bool_cas( T* p, O o, V v, memory_order_t smo = mo_seq_cst, memory_order_t fmo = mo_seq_cst ); 与 atomic_cas 类似,交换操作成功时返回 true,否则返回 false。 #算术运算 #atomic_inc template\u0026lt;typename T\u0026gt; inline T atomic_inc(T* p, memory_order_t mo = mo_seq_cst); 原子自增,T 是长度为 1, 2, 4, 8 字节的任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数进行自增操作,并返回自增后的结果。\n示例\nint i = 0; uint64 u = 0; int r = atomic_inc(\u0026amp;i); // i -\u0026gt; 1, r = 1 uint64 x = atomic_inc(\u0026amp;u); // u -\u0026gt; 1, x = 1 #atomic_fetch_inc template\u0026lt;typename T\u0026gt; inline T atomic_fetch_inc(T* p, memory_order_t mo = mo_seq_cst); 与 atomic_inc 一样,但返回自增前的值。\n示例\nint i = 0; uint64 u = 0; int r = atomic_fetch_inc(\u0026amp;i); // i -\u0026gt; 1, r = 0 uint64 x = atomic_fetch_inc(\u0026amp;u); // u -\u0026gt; 1, x = 0 #atomic_dec template\u0026lt;typename T\u0026gt; inline T atomic_dec(T* p, memory_order_t mo = mo_seq_cst); 原子自减,T 是长度为 1, 2, 4, 8 字节的任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数进行自减操作,并返回自减后的结果。\n示例\nint i = 1; uint64 u = 1; int r = atomic_dec(\u0026amp;i); // i -\u0026gt; 0, r = 0 uint64 x = atomic_dec(\u0026amp;u); // u -\u0026gt; 0, x = 0 #atomic_fetch_dec template\u0026lt;typename T\u0026gt; inline T atomic_fetch_dec(T* p, memory_order_t mo = mo_seq_cst); 与 atomic_dec 一样,但返回自减前的值。\n示例\nint i = 1; uint64 u = 1; int r = atomic_fetch_dec(\u0026amp;i); // i -\u0026gt; 0, r = 1 uint64 x = atomic_fetch_dec(\u0026amp;u); // u -\u0026gt; 0, x = 1 #atomic_add template\u0026lt;typename T, typename V\u0026gt; inline T atomic_add(T* p, V v, memory_order_t mo = mo_seq_cst); 原子加法,T 是长度为 1, 2, 4, 8 字节的任意整数类型,V 是任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数加上值 v,并返回加 v 后的结果。\n示例\nint i = 0; uint64 u = 0; int r = atomic_add(\u0026amp;i, 1); // i -\u0026gt; 1, r = 1 uint64 x = atomic_add(\u0026amp;u, 1); // u -\u0026gt; 1, x = 1 #atomic_fetch_add template\u0026lt;typename T, typename V\u0026gt; inline T atomic_fetch_add(T* p, V v, memory_order_t mo = mo_seq_cst); 与 atomic_add 一样,但返回加 v 前的值。\n示例\nint i = 0; uint64 u = 0; int r = atomic_fetch_add(\u0026amp;i, 1); // i -\u0026gt; 1, r = 0 uint64 x = atomic_fetch_add(\u0026amp;u, 1); // u -\u0026gt; 1, x = 0 #atomic_sub template\u0026lt;typename T, typename V\u0026gt; inline T atomic_sub(T* p, V v, memory_order_t mo = mo_seq_cst); 原子减法,T 是长度为 1, 2, 4, 8 字节的任意整数类型,V 是任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数减去值 v,并返回减 v 后的结果。\n示例\nint i = 1; uint64 u = 1; int r = atomic_sub(\u0026amp;i, 1); // i -\u0026gt; 0, r = 0 uint64 x = atomic_sub(\u0026amp;u, 1); // u -\u0026gt; 0, x = 0 #atomic_fetch_sub template\u0026lt;typename T, typename V\u0026gt; inline T atomic_fetch_sub(T* p, V v, memory_order_t mo = mo_seq_cst); 与 atomic_sub 一样,但返回减 v 前的值。\n示例\nint i = 1; uint64 u = 1; int r = atomic_fetch_sub(\u0026amp;i, 1); // i -\u0026gt; 0, r = 1 uint64 x = atomic_fetch_sub(\u0026amp;u, 1); // u -\u0026gt; 0, x = 1 #位运算 #atomic_or template\u0026lt;typename T, typename V\u0026gt; inline T atomic_or(T* p, V v, memory_order_t mo = mo_seq_cst); 原子位或,T 是长度为 1, 2, 4, 8 字节的任意整数类型,V 是任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数与 v 进行位或操作,并返回操作后的结果。\n示例\nint i = 5; uint64 u = 5; int r = atomic_or(\u0026amp;i, 3); // i |= 3, i -\u0026gt; 7, r = 7 uint64 x = atomic_or(\u0026amp;u, 3); // u |= 3, u -\u0026gt; 7, x = 7 #atomic_fetch_or template\u0026lt;typename T, typename V\u0026gt; inline T atomic_fetch_or(T* p, V v, memory_order_t mo = mo_seq_cst); 与 atomic_or 一样,但返回位或操作之前的值。\n示例\nint i = 5; uint64 u = 5; int r = atomic_fetch_or(\u0026amp;i, 3); // i |= 3, i -\u0026gt; 7, r = 5 uint64 x = atomic_fetch_or(\u0026amp;u, 3); // u |= 3, u -\u0026gt; 7, x = 5 #atomic_and template\u0026lt;typename T, typename V\u0026gt; inline T atomic_and(T* p, V v, memory_order_t mo = mo_seq_cst); 原子位与,T 是长度为 1, 2, 4, 8 字节的任意整数类型,V 是任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数与 v 进行位与操作,并返回操作后的结果。\n示例\nint i = 5; uint64 u = 5; int r = atomic_and(\u0026amp;i, 3); // i \u0026amp;= 3, i -\u0026gt; 1, r = 1 uint64 x = atomic_and(\u0026amp;u, 3); // u \u0026amp;= 3, u -\u0026gt; 1, x = 1 #atomic_fetch_and template\u0026lt;typename T, typename V\u0026gt; inline T atomic_fetch_and(T* p, V v, memory_order_t mo = mo_seq_cst); 与 atomic_and 一样,但返回位与操作之前的值。\n示例\nint i = 5; uint64 u = 5; int r = atomic_fetch_and(\u0026amp;i, 3); // i \u0026amp;= 3, i -\u0026gt; 1, r = 5 uint64 x = atomic_fetch_and(\u0026amp;u, 3); // u \u0026amp;= 3, u -\u0026gt; 1, x = 5 #atomic_xor template\u0026lt;typename T, typename V\u0026gt; inline T atomic_xor(T* p, V v, memory_order_t mo = mo_seq_cst); 原子按位异或,T 是长度为 1, 2, 4, 8 字节的任意整数类型,V 是任意整数类型,参数 p 是 T 类型的指针。\n此函数对 p 指向的整数与 v 进行按位异或操作,并返回操作后的结果。\n示例\nint i = 5; uint64 u = 5; int r = atomic_xor(\u0026amp;i, 3); // i ^= 3, i -\u0026gt; 6, r = 6 uint64 x = atomic_xor(\u0026amp;u, 3); // u ^= 3, u -\u0026gt; 6, x = 6 #atomic_fetch_xor template\u0026lt;typename T, typename V\u0026gt; inline T atomic_fetch_xor(T* p, V v, memory_order_t mo = mo_seq_cst); 与 atomic_xor 一样,但返回按位异或操作之前的值。\n示例\nint i = 5; uint64 u = 5; int r = atomic_fetch_xor(\u0026amp;i, 3); // i ^= 3, i -\u0026gt; 6, r = 5 uint64 x = atomic_fetch_xor(\u0026amp;u, 3); // u ^= 3, u -\u0026gt; 6, x = 5 "},{"id":2,"href":"/cn/co/def/","title":"基本定义","section":"参考文档","content":"include: co/def.h.\n#typedefs #定长整数类型 co/def.h 定义了如下的 8 种整数类型:\ntypedef int8_t int8; typedef int16_t int16; typedef int32_t int32; typedef int64_t int64; typedef uint8_t uint8; typedef uint16_t uint16; typedef uint32_t uint32; typedef uint64_t uint64; 这些类型在不同平台的长度是一致的,不存在可移植性问题。Google Code Style 建议除了 int,不要使用 short, long, long long 等内置整数类型。\n#macros #整型最大、最小值 MAX_UINT8 MAX_UINT16 MAX_UINT32 MAX_UINT64 MAX_INT8 MAX_INT16 MAX_INT32 MAX_INT64 MIN_INT8 MIN_INT16 MIN_INT32 MIN_INT64 这些宏分别表示 8 种整数类型的最大、最小值。\n#DISALLOW_COPY_AND_ASSIGN 这个宏用于禁止 C++ 类中的拷贝构造函数与赋值操作。\n示例 class T { public: T(); DISALLOW_COPY_AND_ASSIGN(T); }; #__arch64, __arch32 64位系统上,__arch64 定义为 1;32位系统上,__arch32 定义为 1。\n示例 #if __arch64 inline size_t murmur_hash(const void* s, size_t n) { return murmur_hash64(s, n, 0); } #else inline size_t murmur_hash(const void* s, size_t n) { return murmur_hash32(s, n, 0); } #endif #__forceinline __forceinline 是 VS 中的关键字,放在函数定义开头,强制内联函数,linux 与 mac 平台用下面的宏模拟:\n#define __forceinline __attribute__((always_inline)) #__thread __thread 是 gcc/clang 中的关键字,用于支持 TLS,windows 平台用下面的宏模拟:\n#define __thread __declspec(thread) 示例 // get id of the current thread __forceinline unsigned int gettid() { static __thread unsigned int id = 0; if (id != 0) return id; return id = __gettid(); } #unlikely 这个宏用于分支选择优化,仅支持 gcc/clang。\n示例 // 与 if (v == 0) 逻辑上等价,但提示编译器 v == 0 的可能性较小 if (unlikey(v == 0)) { cout \u0026lt;\u0026lt; \u0026#34;v == 0\u0026#34; \u0026lt;\u0026lt; endl; } "},{"id":3,"href":"/cn/co/concurrency/coroutine/basic/","title":"基本概念","section":"协程","content":"#协程基本概念 协程是运行于线程中的轻量级调度单位。 协程之于线程,类似于线程之于进程。 一个进程中可以存在多个线程,一个线程中可以存在多个协程。 协程所在的线程一般被称为调度线程。 协程发生 io 阻塞或调用 sleep 等操作时,调度线程会挂起此协程。 协程挂起时,调度线程会切换到其他等待中的协程运行。 协程的切换是在用户态进行的,比线程间的切换更快。 协程非常适合写网络程序,可以实现同步的编程方式,不需要异步回调,大大减轻了程序员的思想负担。\n#co 协程特性 coost 中实现的是一种类似 goroutine 的协程,有如下特性:\n支持多线程调度,默认线程数为系统 CPU 核数。 共享栈,同一线程中的协程共用若干个栈(大小默认为 1MB),内存占用低,Linux 上的测试显示 1000 万协程只用了 2.8G 内存(仅供参考)。 协程创建后,始终在同一个线程中运行,而不会切换到其他线程。 各协程之间为平级关系,可以在任何地方(包括在协程中)创建新的协程。 coost 协程库中 context 相关代码取自 ruki 的 tbox,而 tbox 则参考了 boost 的实现,在此表示感谢! "},{"id":4,"href":"/cn/co/net/byte_order/","title":"字节序","section":"网络编程","content":"include: co/byte_order.h.\n计算机中的数据在内存中是以字节(8 bit)为基本单位进行存储的,大端机采用大端字节序,即高位字节在低地址,低位字节在高地址,小端机则采用小端字节序,即低位字节在低地址,高位字节在高地址。\n单个字节在大、小端机器上是完全相同的,而多个字节的基本数据类型,在大、小端机器上有着不同的字节序。这里说的基本数据类型是指像 int, double 这样的内置类型,字符串不在此列,它是由单字节构成的序列,在大、小端机器上有着相同的存储形式。\n网络上传输的数据采用大端字节序,所谓的网络字节序也就是大端字节序。发送数据到网络上时,需要将其中的多字节基本类型转换成网络字节序,而从网络上接收数据时,则需要转换成所在主机的字节序。\nbyte_order.h 定义了如下的方法:\nntoh16 ntoh32 ntoh64 hton16 hton32 hton64 这些方法分别适用于长度为 2, 4, 8 字节的整数,其中 ntoh 系列将网络字节序转换成主机字节序,hton 系列则将主机字节序转换成网络字节序。\n代码示例 uint32 h = 777; uint32 n = hton32(h); "},{"id":5,"href":"/cn/co/other/console/","title":"终端输出","section":"其他","content":"include: co/cout.h.\n#颜色 如下:\ncout \u0026lt;\u0026lt; text::red(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::green(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::blue(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::yellow(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::magenta(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::cyan(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; \u0026#34;hello\\n\u0026#34;; cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).red(); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).green(); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).blue(); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).yellow(); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).magenta(); cout \u0026lt;\u0026lt; text::bold(\u0026#34;hello\\n\u0026#34;).cyan(); #co::print template\u0026lt;typename ...X\u0026gt; void print(X\u0026amp;\u0026amp; ... x); 接受任意数量的参数,输出到 stdout,末尾会添加换行符。 内部有加锁,支持多线程同时调用 co::print。 co::print(\u0026#34;hello \u0026#34;, 23); co::print(text::red(\u0026#34;hello\u0026#34;)); co::vector\u0026lt;int\u0026gt; v = { 1, 2, 3 }; co::print(v); "},{"id":6,"href":"/cn/co/concurrency/coroutine/api/","title":"APIs","section":"协程","content":"include: co/co.h.\n#Coroutine APIs v3.0 移除了 co::init, co::exit, co::stop。 #go 1. void go(Closure* cb); 2. template\u0026lt;typename F\u0026gt; void go(F\u0026amp;\u0026amp; f); 3. template\u0026lt;typename F, typename P\u0026gt; void go(F\u0026amp;\u0026amp; f, P\u0026amp;\u0026amp; p); 4. template\u0026lt;typename F, typename T, typename P\u0026gt; void go(F\u0026amp;\u0026amp; f, T* t, P\u0026amp;\u0026amp; p); 此函数用于创建协程,与创建线程类似,需要指定一个协程函数。\n1, 参数 cb 指向一个 Closure 对象,协程启动后会调用 Closure 中的 run() 方法。\n2-4, 将传入的参数打包成一个 Closure,然后调用 1。\n2, 参数 f 是任意可调用的对象,只要能调用 f() 或 (*f)() 就行。\n3, 参数 f 是任意可调用的对象,只要能调用 f(p), (*f)(p) 或 (p-\u0026gt;*f)() 就行。\n4, 参数 f 是类中带一个参数的方法 void T::f(P),参数 t 是 T 类型的指针,参数 p 是方法 f 的参数。\n实际测试发现,创建 std::function 类型的对象开销较大,应尽量少用。\n示例\ngo(f); // void f(); go(f, 7); // void f(int); go(\u0026amp;T::f, \u0026amp;o); // void T::f(); T o; go(\u0026amp;T::f, \u0026amp;o, 3); // void T::f(int); T o; // lambda go([](){ LOG \u0026lt;\u0026lt; \u0026#34;hello co\u0026#34;; }); // std::function std::function\u0026lt;void()\u0026gt; x(std::bind(f, 7)); go(x); go(\u0026amp;x); // Ensure that x is alive when the coroutine is running. #DEF_main 这个宏用于定义 main 函数,并将 main 函数中的代码也放到协程中运行。DEF_main 内部已经调用 flag::parse() 解析命令行参数,用户无需再次调用。\n示例 DEF_main(argc, argv) { go([](){ LOG \u0026lt;\u0026lt; \u0026#34;hello world\u0026#34;; }); co::sleep(100); return 0; } #——————————— #co::coroutine void* coroutine(); 返回当前的 coroutine 指针,若在非协程中调用此函数,则返回值是 NULL。 此函数的返回值,可作为 co::resume() 的参数,用于唤醒协程。 #co::resume void resume(void* p); 唤醒指定的协程,参数 p 是 co::coroutine() 的返回值。 此函数是线程安全的,可在任意地方调用。 #co::yield void yield(); 挂起当前协程,必须在协程中调用。 此函数配合 co::coroutine() 与 co::resume(),可以手动控制协程的调度,详情参考 test/yield.cc。 #——————————— #co::coroutine_id int coroutine_id(); 返回当前协程的 id,在非协程中调用时,返回值是 -1。 #co::main_sched MainSched* main_sched(); 此函数用于将主线程变成调度线程。 用户获取 MainSched 指针后,必须在主线程中调用其 loop() 方法。 #include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); co::print(\u0026#34;main thread id: \u0026#34;, co::thread_id()); auto s = co::main_sched(); for (int i = 0; i \u0026lt; 8; ++i) { go([]{ co::print(\u0026#34;thread: \u0026#34;, co::thread_id(), \u0026#34; sched: \u0026#34;, co::sched_id()); }); } s-\u0026gt;loop(); // loop forever return 0; // fake return value } #co::next_sched Sched* next_sched(); 返回指向下一个 Sched 的指针。\ngo(\u0026hellip;) 实际上等价于 co::next_sched()-\u0026gt;go(...)。\n示例\n// 创建在同一个线程中运行的协程 auto s = co::next_sched(); s-\u0026gt;go(f1); s-\u0026gt;go(f2); v3.0.1 中将 co::next_scheduler 重命名为 co::next_sched。 #co::sched Sched* sched(); 返回指向当前协程调度器的指针,调度器与调度线程是一一对应的,如果当前线程不是调度线程,返回值是 NULL。 v3.0.1 中将 co::scheduler 重命名为 co::sched。 #co::scheds const co::vector\u0026lt;Sched*\u0026gt;\u0026amp; scheds(); 返回协程调度器列表的引用。 v3.0.1 中将 co::schedulers 重命名为 co::scheds。 #co::sched_id int sched_id(); 返回当前协程调度器的 id,这个值是 0 到 co::sched_num()-1 之间的值。如果当前线程不是调度线程,返回值是 -1。 v3.0.1 中将 co::scheduler_id 重命名为 co::sched_id。 #co::sched_num int sched_num(); 返回协程调度器的数量,此函数常用于实现一些协程安全的数据结构。\n示例\nco::vector\u0026lt;T\u0026gt; v(co::sched_num(), 0); void f() { // get object for the current scheduler auto\u0026amp; t = v[co::sched_id()]; } go(f); v3.0.1 中将 co::scheduler_num 重命名为 co::sched_num。 #co::stop_scheds void stop_scheds(); 停止所有协程调度器,退出所有调度线程。 #代码示例 // print sched id and coroutine id every 3 seconds #include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; void f() { while (true) { co::print(\u0026#34;s: \u0026#34;, co::sched_id(), \u0026#34; c: \u0026#34;, co::coroutine_id()); co::sleep(3000); } } int main(int argc, char** argv) { flag::parse(argc, argv); for (int i = 0; i \u0026lt; 32; ++i) go(f); while (true) sleep::sec(1024); return 0; } #——————————— #co::sleep void sleep(uint32 ms); 让当前协程睡一会儿,参数 ms 是时间,单位是毫秒。 此函数一般在协程中调用,在非协程中调用相当于 sleep::ms(ms)。 #co::timeout bool timeout(); 判断之前的 IO 操作是否超时。用户在调用 co::recv() 等带超时时间的函数后,可以调用此函数判断是否超时。 此函数必须在协程中调用。 "},{"id":7,"href":"/cn/co/other/defer/","title":"defer","section":"其他","content":"include: co/defer.h.\n#defer defer 是 coost 提供的一个宏,它实现了类似 golang 中 defer 的功能。defer 的参数可以是一条或多条语句。\nvoid f() { void* p = malloc(32); defer(free(p)); defer( std::cout \u0026lt;\u0026lt; \u0026#34;111\u0026#34; \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; \u0026#34;222\u0026#34; \u0026lt;\u0026lt; std::endl; ); std::cout \u0026lt;\u0026lt; \u0026#34;333\u0026#34; \u0026lt;\u0026lt; std::endl; } 上面的例子中,defer 中的代码将在函数 f 结束时执行,因此 333 先于 111 与 222 打印。\n"},{"id":8,"href":"/cn/co/net/sock/","title":"socket 编程","section":"网络编程","content":"include: co/co.h.\n#Socket APIs co 提供了常用的协程化的 socket API,以支持基于协程的网络编程。\n大部分 API 形式上与原生的 socket API 保持一致,这样可以减轻用户的学习负担,熟悉 socket 编程的用户可以轻松上手。\n这些 API 大部分需要在协程中使用,它们在 I/O 阻塞或调用 sleep 等操作时,调度线程会挂起当前协程,切换到其他等待中的协程运行,调度线程本身并不会阻塞。借助这些 API,用户可以轻松的实现高并发、高性能的网络程序。\n#术语约定 阻塞\n在描述 co 中的一些 socket API 时,会用到阻塞一词,如 accept, recv,文档中说它们会阻塞,是指当前的协程会阻塞,而当前的调度线程并不会阻塞(可以切换到其他协程运行)。用户看到的是协程,而不是调度线程,因此从用户的角度看,它们是阻塞的。实际上,这些 API 内部使用 non-blocking socket,并不会真的阻塞,只是在 socket 上没有数据可读或者无法立即写入数据时,调度线程会挂起当前协程,当 socket 变为可读或可写时,调度线程会重新唤起该协程,继续 I/O 操作。\nnon-blocking socket\nco 中的 socket API 必须使用 non-blocking socket,在 windows 平台还要求 socket 支持 overlapped I/O,win32 API 创建的 socket 默认都支持 overlapped I/O,用户一般不需要担心这个问题。为了叙述方便,这里约定文档中说到 non-blocking socket 时,同时也表示它在 windows 上支持 overlapped I/O。\n#co::socket 1. sock_t socket(int domain, int type, int proto); 2. sock_t tcp_socket(int domain=AF_INET); 3. sock_t udp_socket(int domain=AF_INET); 创建 socket。 1, 形式上与原生 API 完全一样,在 linux 系统可以用 man socket 查看参数详情。 2, 创建一个 TCP socket。 3, 创建一个 UDP socket。 参数 domain 一般是 AF_INET 或 AF_INET6,前者表示 ipv4,后者表示 ipv6。 这些函数返回一个 non-blocking socket。发生错误时,返回值是 -1,可以调用 co::error() 获取错误信息。 #co::accept sock_t accept(sock_t fd, void* addr, int* addrlen); 在指定 socket 上接收客户端连接,参数 fd 是之前调用 listen() 监听的 non-blocking socket,参数 addr 与 addrlen 用于接收客户端的地址信息,*addrlen 的初始值是 addr 所指向 buffer 的长度。如果用户不需要客户端地址信息,可以将 addr 与 addrlen 设置为 NULL。 此函数必须在协程中调用。 此函数会阻塞,直到有新的连接进来,或者发生错误。 此函数成功时返回一个 non-blocking socket,发生错误时返回 -1,可以调用 co::error() 获取错误信息。 #co::bind int bind(sock_t fd, const void* addr, int addrlen); 给 socket 绑定 ip 地址,参数 addr 与 addrlen 是地址信息,与原生 API 相同。 此函数成功时返回 0,否则返回 -1,可以调用 co::error() 获取错误信息。 #co::close int close(sock_t fd, int ms=0); 关闭 socket。 在 2.0.0 及之前的版本中,此函数必须在进行 I/O 操作的线程中调用。从 2.0.1 版本开始,此函数可以在协程或非协程中调用。 参数 ms 大于 0 时,会先调用 co::sleep(ms) 将当前协程挂起一段时间,再关闭 socket。一般只在 server 端将 ms 设置为大于 0 的值,可以在一定程度上缓解非法的网络攻击。 此函数内部已经处理了 EINTR 信号,用户无需考虑。 此函数成功时返回 0,否则返回 -1,可以调用 co::error() 获取错误信息。 #co::connect int connect(sock_t fd, const void* addr, int addrlen, int ms=-1); 在指定 socket 上创建到指定地址的连接,参数 fd 必须是 non-blocking 的,参数 addr 与 addrlen 是地址信息,参数 ms 是超时时间,单位为毫秒,默认为 -1,永不超时。 此函数必须在协程中调用。 此函数会阻塞,直到连接完成,或者超时、发生错误。 此函数成功时返回 0,超时或发生错误返回 -1,用户可以调用 co::timeout() 判断是否超时,调用 co::error() 获取错误信息。 #co::listen int listen(sock_t fd, int backlog=1024); 监听指定的 socket,参数 fd 是已经调用 bind() 绑定 ip 及端口的 socket。 此函数成功时返回 0,否则返回 -1,可以调用 co::error() 获取错误信息。 #co::recv int recv(sock_t fd, void* buf, int n, int ms=-1); 在指定 socket 上接收数据,参数 fd 必须是 non-blocking 的,参数 buf 是用于接收数据的 buffer,参数 n 是 buffer 长度,参数 ms 是超时时间,单位为毫秒,默认为 -1,永不超时。 此函数必须在协程中调用。 在 Windows 平台,此函数只适用于 TCP 等 stream 类型的 socket。 此函数会阻塞,直到有数据进来,或者超时、发生错误。 此函数成功时返回接收的数据长度(可能小于 n),对端关闭连接时返回 0,超时或发生错误返回 -1,用户可以调用 co::timeout() 判断是否超时,调用 co::error() 获取错误信息。 #co::recvn int recvn(sock_t fd, void* buf, int n, int ms=-1); 在指定 socket 上接收指定长度的数据,参数 fd 必须是 non-blocking 的,参数 buf 是用于接收数据的 buffer,参数 n 是要接收数据的长度,参数 ms 是超时时间,单位为毫秒,默认为 -1,永不超时。 此函数必须在协程中调用。 此函数会阻塞,直到 n 字节的数据全部接收完,或者超时、发生错误。 此函数成功时返回 n,对端关闭连接时返回 0,超时或发生错误返回 -1,用户可以调用 co::timeout() 判断是否超时,调用 co::error() 获取错误信息。 #co::recvfrom int recvfrom(sock_t fd, void* buf, int n, void* src_addr, int* addrlen, int ms=-1); 与 co::recv 类似,只是可以用参数 src_addr 与 addrlen 接收源地址信息,*addrlen 的初始值是 src_addr 所指向 buffer 的长度,如果用户不需要源地址信息,可以将 addr 与 addrlen 设置为 NULL。 一般建议只用此函数接收 UDP 数据。 #co::send int send(sock_t fd, const void* buf, int n, int ms=-1); 向指定 socket 上发送数据,参数 fd 必须是 non-blocking 的,参数 buf 与 n 是要发送的数据及长度,参数 ms 是超时时间,单位为毫秒,默认为 -1,永不超时。 此函数必须在协程中调用。 在 Windows 平台,此函数只适用于 TCP 等 stream 类型的 socket。 此函数会阻塞,直到 n 字节的数据全部发送完,或者超时、发生错误。 此函数成功时返回 n,超时或发生错误返回 -1,用户可以调用 co::timeout() 判断是否超时,调用 co::error() 获取错误信息。 #co::sendto int sendto(sock_t fd, const void* buf, int n, const void* dst_addr, int addrlen, int ms=-1); 向指定的地址发送数据,当 dst_addr 为 NULL,addrlen 为 0 时,与 co::send 等价。 一般建议只用此函数发送 UDP 数据。 fd 是 UDP socket 时,n 最大是 65507。 #co::shutdown int shutdown(sock_t fd, char c=\u0026#39;b\u0026#39;); 此函数一般用于半关闭 socket,参数 c 为 'r' 时表示关闭读,为 'w' 时表示关闭写,默认为 'b',关闭读与写。 一般建议在进行 IO 操作的线程中调用此函数。 此函数成功时返回 0,否则返回 -1,可以调用 co::error() 获取错误信息。 #——————————— #co::getsockopt int getsockopt(sock_t fd, int lv, int opt, void* optval, int* optlen); 获取 socket option 信息,与原生 API 完全一样,man getsockopt 看详情。 #co::setsockopt int setsockopt(sock_t fd, int lv, int opt, const void* optval, int optlen); 设置 socket option 信息,与原生 API 完全一样,man setsockopt 看详情。 #co::set_nonblock void set_nonblock(sock_t fd); 给 socket 设置 O_NONBLOCK 选项。 #co::set_reuseaddr void set_reuseaddr(sock_t fd); 给 socket 设置 SO_REUSEADDR 选项,一般 server 端的 listening socket 需要设置这个选项,防止 server 重启后 bind 失败。 #co::set_recv_buffer_size void set_recv_buffer_size(sock_t fd, int n); 设置 socket 的接收缓冲区大小,必须在 socket 连接前调用此函数。 #co::set_send_buffer_size void set_send_buffer_size(sock_t fd, int n); 设置 socket 的发送缓冲区大小,必须在 socket 连接前调用此函数。 #co::set_tcp_keepalive void set_tcp_keepalive(sock_t fd); 给 socket 设置 SO_KEEPALIVE 选项。 #co::set_tcp_nodelay void set_tcp_nodelay(sock_t fd); 给 socket 设置 TCP_NODELAY 选项。 #co::reset_tcp_socket int reset_tcp_socket(sock_t fd, int ms=0); 重置 TCP 连接,与 co::close 类似,但主动调用方不会进入 TIME_WAIT 状态。 一般只有 server 端会调用此函数,用于主动关闭客户端连接,同时避免进入 TIME_WAIT 状态。 #——————————— #co::addr2str 1. fastring addr2str(const struct sockaddr_in* addr); 2. fastring addr2str(const struct sockaddr_in6* addr); 3. fastring addr2str(const void* addr, int len); 将 sockaddr 地址转换成 \u0026quot;ip:port\u0026quot; 形式的字符串。\n1 用于 ipv4 地址,2 用于 ipv6 地址,3 根据 len 选择调用版本 1 或版本 2。\n示例\nstruct sockaddr_in addr; co::init_addr(\u0026amp;addr, \u0026#34;127.0.0.1\u0026#34;, 80); co::addr2str(\u0026amp;addr); // \u0026#34;127.0.0.1:80\u0026#34; co::addr2str(\u0026amp;addr, sizeof(addr)); // \u0026#34;127.0.0.1:80\u0026#34; #co::init_addr 1. bool init_addr(struct sockaddr_in* addr, const char* ip, int port); 2. bool init_addr(struct sockaddr_in6* addr, const char* ip, int port); 用 ip 及 port 初始化 sockaddr 结构。\n1 用于 ipv4 地址,2 用于 ipv6 地址。\n示例\nunion { struct sockaddr_in v4; struct sockaddr_in6 v6; } addr; co::init_addr(\u0026amp;addr.v4, \u0026#34;127.0.0.1\u0026#34;, 7777); co::init_addr(\u0026amp;addr.v6, \u0026#34;::\u0026#34;, 7777); #co::init_ip_addr bool init_ip_addr(struct sockaddr_in* addr, const char* ip, int port); bool init_ip_addr(struct sockaddr_in6* addr, const char* ip, int port); v3.0.1 标记为 deprecated,建议用 co::init_addr 取代之。 #co::peer fastring peer(sock_t fd); 获取 peer 端的地址信息,返回值是 \u0026quot;ip:port\u0026quot; 形式的字符串。 "},{"id":9,"href":"/cn/about/co/","title":"简介","section":"关于","content":"#coost 简介 coost 是一个兼具性能与易用性的跨平台 C++ 基础库,其目标是打造一把 C++ 开发神器,让 C++ 编程变得简单、轻松、愉快。\ncoost 简称为 co,曾被称为小型 boost 库,与 boost 相比,coost 小而精美,在 linux 与 mac 上编译出来的静态库仅 1M 左右大小,却包含了不少强大的功能:\n命令行与配置文件解析(flag) 高性能日志库(log) 单元测试框架 基准测试框架 go-style 协程 基于协程的网络编程框架 基于 JSON 的 RPC 框架 原子操作(atomic) 高效字符流(fastream) 高效字符串(fastring) 字符串操作(str) 时间库(time) 线程库(thread) 定时任务调度器 面向玄学编程 高效 JSON 库 hash 库 path 库 文件系统操作(fs) 系统操作(os) 高性能内存分配器 #coost 发展历程 2013-2015 年,Alvin(idealvin) 在使用 google gflags、glog、gtest 等时,感到有些繁琐,就自己动手实现了相应的功能,即现今 coost 中的 flag、log、unitest 等组件。\n2015-2018 年,Alvin 将自研的这套基础库引入实际项目中,供自己与同事使用,大幅度提升了 C++ 开发效率,coost 也得以经受工业项目的检验,并在实践中不断完善、扩充新的功能。\n2019 年,Alvin 实现了类似 goroutine 的协程机制,以及基于协程的网络编程框架,之后将项目命名为 co,在 github 上发布 1.0 版本。\n2020-2021 年,完善 hook 机制、协程同步机制,增加 golang 中的 channel、defer 等特性,发布 2.x 版本。在此期间,码友们提供了很多宝贵的改进意见,并帮忙完善了 xmake、cmake 编译脚本以及 coost 中的很多功能。\n2022 年,新增内存分配器、提升整体性能,对 flag、log、JSON、RPC、fastring、fastream 等很多组件做出了重大改进,并将项目更名为 coost,发布 3.0 版本。\n#快速上手 #编译 建议安装 xmake,在 coost 根目录执行如下命令构建所有子项目:\nxmake -a 如果需要使用 http::Client, SSL 或 HTTPS 特性,则可以用下面的命令构建:\nxmake f --with_libcurl=true --with_openssl=true xmake -a xmake 会自动从网络安装 libcurl 与 openssl,视网络情况,这个过程可能会较慢。xmake -a 会编译 libco, gen, unitest 以及 test 目录下面的所有测试代码。编译完后可以执行如下命令,运行 coost 中的测试程序:\nxmake r unitest xmake r flag xmake r log -cout xmake r co #使用 coost 开发 C++ 项目 最简单的,可以直接包含 co/all.h,使用 coost 中的所有特性。如果担心影响编译速度,也可以只包含需要用到的头文件,如包含 co/co.h,可以使用 flag, log 以及协程相关的所有特性。\n#include \u0026#34;co/all.h\u0026#34; DEF_string(s, \u0026#34;nice\u0026#34;, \u0026#34;xxx\u0026#34;); int main(int argc, char** argv) { flag::parse(argc, argv); LOG \u0026lt;\u0026lt; FLG_s; return 0; } coost 中的部分组件用 flag 定义配置项,因此一般需要在 main 函数开头调用 flag::parse() 解析命令行参数。\n#性能 #内存分配器 ptmalloc、jemalloc、tcmalloc 以及 mimalloc 等内存分配器,小内存释放后大概率不会归还给操作系统,这可能造成释放大量小内存后内存占用量却始终不降的疑似内存泄漏的现象。为解决此问题,coost 设计了一个专用的内存分配器,在兼顾性能的同时,会尽可能多的将释放的内存归还给系统,有利于降低程序的内存占用量,在实测中也取得了良好的效果。\nco/test 中提供了简单的测试代码,可以执行如下命令编译及运行:\nxmake b mem xmake r mem -t 4 -s -t 指定线程数量,-s 表示与系统内存分配器进行对比。下面是在不同系统中的测试结果(4线程):\nos/cpu co::alloc co::free ::malloc ::free speedup win/AMD 3.2G 7.32 6.83 86.05 105.06 11.7/15.3 mac/i7 2.4G 9.91 9.86 55.64 60.20 5.6/6.1 linux/i7 2.2G 10.80 7.51 1070.5 21.17 99.1/2.8 表中数据为平均耗时,单位为纳秒(ns),linux 是在 Windows WSL 中运行的 ubuntu 系统,speedup 是 coost 内存分配器相对于系统内存分配器的性能提升倍数。\n可以看到,co::alloc 在 Linux 上比 ::malloc 提升了近 99 倍,这其中的一个重要原因是 ptmalloc 在多线程环境中锁竞争开销较大,而 coost 内存分配器在设计上尽可能避免锁的使用,小块内存的分配、释放不需要锁,跨线程释放时连自旋锁也不用。\n#日志 platform glog co/log speedup win2012 HHD 1.6MB/s 180MB/s 112.5 win10 SSD 3.7MB/s 560MB/s 151.3 mac SSD 17MB/s 450MB/s 26.4 linux SSD 54MB/s 1023MB/s 18.9 上表是 co/log 与 glog 在单线程连续打印 100 万条日志时测得的写速度对比,可以看到 co/log 比 glog 快了近两个数量级。\nthreads linux co/log linux spdlog win co/log win spdlog speedup 1 0.087235 2.076172 0.117704 0.461156 23.8/3.9 2 0.183160 3.729386 0.158122 0.511769 20.3/3.2 4 0.206712 4.764238 0.316607 0.743227 23.0/2.3 8 0.302088 3.963644 0.406025 1.417387 13.1/3.5 上表是分别用 1、2、4、8 个线程打印 100 万条日志的耗时,单位为秒,speedup 是 co.log 在 linux、windows 平台相对于 spdlog 的性能提升倍数。\n#JSON 库 os co/json stringify co/json parse rapidjson stringify rapidjson parse speedup win 569 924 2089 2495 3.6/2.7 mac 783 1097 1289 1658 1.6/1.5 linux 468 764 1359 1070 2.9/1.4 上表是将 twitter.json 最小化后测得的 stringify 及 parse 的平均耗时,单位为微秒(us),speedup 是 co.json 在 stringify、parse 方面相对于 rapidjson 的性能提升倍数。\n#核心组件 #面向玄学编程 co/god.h 提供模板相关的一些功能。模板用到深处有点玄,有些 C++ 程序员称之为面向玄学编程。\n#include \u0026#34;co/god.h\u0026#34; void f() { god::bless_no_bugs(); god::align_up\u0026lt;8\u0026gt;(31); // -\u0026gt; 32 god::is_same\u0026lt;T, int, bool\u0026gt;(); // T is int or bool? } #flag flag 是一个简单易用的命令行参数与配置文件解析库,coost 中的一些组件用它定义配置项。\n每个 flag(配置项) 都有一个默认值,在缺省情况下,程序可以按默认配置参数运行。用户也可以从命令行或配置文件传入参数,在需要配置文件时,还可以用 -mkconf 自动生成配置文件。\n// xx.cc #include \u0026#34;co/flag.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; DEF_bool(x, false, \u0026#34;x\u0026#34;); DEF_bool(debug, false, \u0026#34;dbg\u0026#34;, d); DEF_uint32(u, 0, \u0026#34;xxx\u0026#34;); DEF_string(s, \u0026#34;\u0026#34;, \u0026#34;xx\u0026#34;); int main(int argc, char** argv) { flag::parse(argc, argv); co::print(\u0026#34;x: \u0026#34;, FLG_x); co::print(\u0026#34;y: \u0026#34;, FLG_y); co::print(\u0026#34;debug: \u0026#34;, FLG_debug); co::print(\u0026#34;u: \u0026#34;, FLG_u); co::print(FLG_s, \u0026#39;|\u0026#39;, FLG_s.size()); return 0; } 上述代码中 DEF_ 开头的宏定义了 4 个 flag,每个 flag 对应一个全局变量,变量名是 FLG_ 加 flag 名,其中 flag debug 还有一个别名 d。上述代码编译后,可以按如下方式运行:\n./xx # 按默认配置运行 ./xx -x -s good # x -\u0026gt; true, s -\u0026gt; \u0026#34;good\u0026#34; ./xx -debug # debug -\u0026gt; true ./xx -xd # x -\u0026gt; true, debug -\u0026gt; true ./xx -u 8k # u -\u0026gt; 8192, 整数可带单位(k,m,g,t,p), 不分大小写 ./xx -mkconf # 自动生成配置文件 xx.conf ./xx xx.conf # 从配置文件传入参数 ./xx -conf xx.conf # 与上同 #log log 是一个高性能日志组件,coost 中的一些组件用它打印日志。\nlog 支持两种类型的日志:一种是 level log,分为 debug, info, warning, error, fatal 5 个级别,打印 fatal 级别的日志会终止程序的运行;另一种是 topic log,日志按 topic 分类,不同 topic 的日志写入不同的文件。\n#include \u0026#34;co/log.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); TLOG(\u0026#34;xx\u0026#34;) \u0026lt;\u0026lt; \u0026#34;s\u0026#34; \u0026lt;\u0026lt; 23; // topic log DLOG \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // debug LOG \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // info WLOG \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // warning ELOG \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // error FLOG \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // fatal return 0; } log 还提供了一系列 CHECK 宏,可以视为加强版的 assert,它们在 debug 模式下也不会被清除。CHECK 断言失败时,log 会打印函数调用栈信息,然后终止程序的运行。\nvoid* p = malloc(32); CHECK(p != NULL) \u0026lt;\u0026lt; \u0026#34;malloc failed..\u0026#34;; CHECK_NE(p, NULL) \u0026lt;\u0026lt; \u0026#34;malloc failed..\u0026#34;; #unitest unitest 是一个简单易用的单元测试框架,coost 中的很多组件用它写单元测试代码,为 coost 的稳定性提供了重要保障。\n#include \u0026#34;co/unitest.h\u0026#34; #include \u0026#34;co/os.h\u0026#34; namespace test { DEF_test(os) { DEF_case(homedir) { EXPECT_NE(os::homedir(), \u0026#34;\u0026#34;); } DEF_case(cpunum) { EXPECT_GT(os::cpunum(), 0); } } } // namespace test 上面是一个简单的例子,DEF_test 宏定义了一个测试单元,实际上就是一个函数。DEF_case 宏定义了测试用例,每个测试用例实际上就是一个代码块。main 函数一般只需要下面几行:\n#include \u0026#34;co/unitest.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); unitest::run_tests(); return 0; } unitest 目录下面是 coost 中的单元测试代码,编译后可执行下述命令运行:\nxmake r unitest # 运行所有单元测试用例 xmake r unitest -os # 仅运行 os 单元中的测试用例 #基准测试 benchmark 是一个简单易用的基准测试框架。\n#include \u0026#34;co/benchmark.h\u0026#34; #include \u0026#34;co/mem.h\u0026#34; BM_group(malloc) { void* p; BM_add(::malloc)( p = ::malloc(32); ); BM_use(p); BM_add(co::alloc)( p = co::alloc(32); ); BM_use(p); } int main(int argc, char** argv) { flag::parse(argc, argv); bm::run_benchmarks(); return 0; } 上例中,BM_group 定义了一个测试组,BM_add 添加了两个需要进行比较的测试用例,BM_use 防止编译器将测试代码优化掉。\n基准测试结果以 markdown table 格式输出,如下所示: #JSON coost v3.0 中,Json 采用流畅(fluent)接口设计,用起来更加方便。\n// {\u0026#34;a\u0026#34;:23,\u0026#34;b\u0026#34;:false,\u0026#34;s\u0026#34;:\u0026#34;123\u0026#34;,\u0026#34;v\u0026#34;:[1,2,3],\u0026#34;o\u0026#34;:{\u0026#34;xx\u0026#34;:0}} Json x = { { \u0026#34;a\u0026#34;, 23 }, { \u0026#34;b\u0026#34;, false }, { \u0026#34;s\u0026#34;, \u0026#34;123\u0026#34; }, { \u0026#34;v\u0026#34;, {1,2,3} }, { \u0026#34;o\u0026#34;, { {\u0026#34;xx\u0026#34;, 0} }}, }; // equal to x Json y = Json() .add_member(\u0026#34;a\u0026#34;, 23) .add_member(\u0026#34;b\u0026#34;, false) .add_member(\u0026#34;s\u0026#34;, \u0026#34;123\u0026#34;) .add_member(\u0026#34;v\u0026#34;, Json().push_back(1).push_back(2).push_back(3)) .add_member(\u0026#34;o\u0026#34;, Json().add_member(\u0026#34;xx\u0026#34;, 0)); x.get(\u0026#34;a\u0026#34;).as_int(); // 23 x.get(\u0026#34;s\u0026#34;).as_string(); // \u0026#34;123\u0026#34; x.get(\u0026#34;s\u0026#34;).as_int(); // 123, string -\u0026gt; int x.get(\u0026#34;v\u0026#34;, 0).as_int(); // 1 x.get(\u0026#34;v\u0026#34;, 2).as_int(); // 3 x.get(\u0026#34;o\u0026#34;, \u0026#34;xx\u0026#34;).as_int(); // 0 x[\u0026#34;a\u0026#34;] == 23; // true x[\u0026#34;s\u0026#34;] == \u0026#34;123\u0026#34;; // true x.get(\u0026#34;o\u0026#34;, \u0026#34;xx\u0026#34;) != 0; // false #协程 coost 实现了类似 golang 中 goroutine 的协程机制,它有如下特性:\n支持多线程调度,默认线程数为系统 CPU 核数。 共享栈,同一线程中的协程共用若干个栈(大小默认为 1MB),内存占用低。 各协程之间为平级关系,可以在任何地方(包括在协程中)创建新的协程。 支持协程同步事件、协程锁、channel、waitgroup 等协程同步机制。 #include \u0026#34;co/co.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); co::wait_group wg; wg.add(2); go([wg](){ LOG \u0026lt;\u0026lt; \u0026#34;hello world\u0026#34;; wg.done(); }); go([wg](){ LOG \u0026lt;\u0026lt; \u0026#34;hello again\u0026#34;; wg.done(); }); wg.wait(); return 0; } 上面的代码中,go() 创建的协程会根据默认策略分配到不同的调度线程中。用户也可以自行控制协程的调度:\n// run f1 and f2 in the same scheduler auto s = co::next_sched(); s-\u0026gt;go(f1); s-\u0026gt;go(f2); // run f in all schedulers for (auto\u0026amp; s : co::scheds()) { s-\u0026gt;go(f); } #网络编程 coost 提供了一套基于协程的网络编程框架:\n协程化的 socket API,形式上与系统 socket API 类似,熟悉 socket 编程的用户,可以轻松的用同步的方式写出高性能的网络程序。 TCP、HTTP、RPC 等高层网络编程组件,兼容 IPv6,同时支持 SSL,用起来比 socket API 更方便。 RPC server\n#include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/rpc.h\u0026#34; #include \u0026#34;co/time.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); rpc::Server() .add_service(new xx::HelloWorldImpl) .start(\u0026#34;127.0.0.1\u0026#34;, 7788, \u0026#34;/xx\u0026#34;); for (;;) sleep::sec(80000); return 0; } rpc::Server 同时支持 HTTP 协议,可以用 HTTP 的 POST 方法调用 RPC 服务:\ncurl http://127.0.0.1:7788/xx --request POST --data \u0026#39;{\u0026#34;api\u0026#34;:\u0026#34;ping\u0026#34;}\u0026#39; 静态 web server\n#include \u0026#34;co/flag.h\u0026#34; #include \u0026#34;co/http.h\u0026#34; DEF_string(d, \u0026#34;.\u0026#34;, \u0026#34;root dir\u0026#34;); // docroot for the web server int main(int argc, char** argv) { flag::parse(argc, argv); so::easy(FLG_d.c_str()); // mum never have to worry again return 0; } HTTP server\nvoid cb(const http::Req\u0026amp; req, http::Res\u0026amp; res) { if (req.is_method_get()) { if (req.url() == \u0026#34;/hello\u0026#34;) { res.set_status(200); res.set_body(\u0026#34;hello world\u0026#34;); } else { res.set_status(404); } } else { res.set_status(405); // method not allowed } } // http http::Server().on_req(cb).start(\u0026#34;0.0.0.0\u0026#34;, 80); // https http::Server().on_req(cb).start( \u0026#34;0.0.0.0\u0026#34;, 443, \u0026#34;privkey.pem\u0026#34;, \u0026#34;certificate.pem\u0026#34; ); HTTP client\nvoid f() { http::Client c(\u0026#34;https://github.com\u0026#34;); c.get(\u0026#34;/\u0026#34;); LOG \u0026lt;\u0026lt; \u0026#34;response code: \u0026#34;\u0026lt;\u0026lt; c.status(); LOG \u0026lt;\u0026lt; \u0026#34;body size: \u0026#34;\u0026lt;\u0026lt; c.body().size(); LOG \u0026lt;\u0026lt; \u0026#34;Content-Length: \u0026#34;\u0026lt;\u0026lt; c.header(\u0026#34;Content-Length\u0026#34;); LOG \u0026lt;\u0026lt; c.header(); c.post(\u0026#34;/hello\u0026#34;, \u0026#34;data xxx\u0026#34;); LOG \u0026lt;\u0026lt; \u0026#34;response code: \u0026#34;\u0026lt;\u0026lt; c.status(); } go(f); "},{"id":10,"href":"/cn/co/concurrency/thread/","title":"线程","section":"并发编程","content":"include: co/co.h.\n#线程 v3.0.1 删除了 co/thread.h 头文件,移除了全局的 Thread 与 Mutex 类,其功能与 C++11 中的 std::thread 与 std::mutex 一样,用户可以直接使用 std 版本。 #co::thread_id uint32 thread_id(); 返回当前线程的 id 值。 #co::sync_event 同步事件是多线程间的一种同步机制,适用于生产者-消费者模型。消费者线程调用 wait() 方法等待同步信号,生产者线程则调用 signal() 方法产生同步信号。co::sync_event 支持多生产者、多消费者,但实际应用中,单个消费者的情况比较多。\nv3.0.1 移除了全局范围的 SyncEvent,请使用 co::sync_event 替代之。 #constructor explicit sync_event(bool manual_reset=false, bool signaled=false); 构造函数,参数 manual_reset 表示是否手动将同步状态设置成未同步,参数 signaled 表示初始状态是否为同步状态。 #sync_event::reset void reset(); 将事件设置成未同步状态。 当构造函数中 manual_reset 为 true 时,用户在调用 wait() 后需要手动调用此方法,将事件设置成未同步状态。 #sync_event::signal void signal(); 产生同步信号,将事件设置成同步状态。 #sync_event::wait 1. void wait(); 2. bool wait(uint32 ms); 1, 一直等待直到事件变成同步状态。 2, 等待直到事件变成同步状态或超时。参数 ms 指定超时时间,单位为毫秒。若事件变成同步状态,返回 true,否则返回 false。 当构造函数中 manual_reset 为 false 时,wait() 结束时会自动将事件设置成未同步状态。 #代码示例 #include \u0026#34;co/co.h\u0026#34; bool manual_reset = false; co::sync_event ev(manual_reset); void f1() { if (!ev.wait(1000)) { LOG \u0026lt;\u0026lt; \u0026#34;f1: timedout..\u0026#34;; } else { LOG \u0026lt;\u0026lt; \u0026#34;f1: event signaled..\u0026#34;; if (manual_reset) ev.reset(); } } void f2() { LOG \u0026lt;\u0026lt; \u0026#34;f2: send a signal..\u0026#34;; ev.signal(); } int main(int argc, char** argv) { std::thread(f1).detach(); std::thread(f2).detach(); co::sleep(3000); return 0; } #co::tls template\u0026lt;typename T\u0026gt; class tls; co::tls 对系统的 thread local 接口进行封装。\nv3.0.1 中移除了 thread_ptr,取而代之提供了 co::tls。 #constructor tls(); 构造函数,分配系统资源及初始化。 #tls::get T* get() const; 返回当前线程拥有的指针值。 #tls::set void set(T* p); 设置当前线程拥有的指针值。 #tls::operator-\u0026gt; T* operator-\u0026gt;() const; 返回当前线程拥有的指针值。 #tls::operator* T\u0026amp; operator*() const; 返回当前线程拥有指针所指向对象的引用。 #operator== bool operator==(T* p) const; 判断当前线程所拥有的指针是否等于 p。 #operator!= bool operator!=(T* p) const; 判断当前线程所拥有的指针是否不等于 p。 #operator bool explicit operator bool() const; 当前线程所拥有的指针不是 NULL 时,返回 true,否则返回 false。 "},{"id":11,"href":"/cn/co/other/error/","title":"错误","section":"其他","content":"include: co/error.h.\n#co::error 1. int error(); 2. void error(int e); 1, 返回当前的错误码。 2, 将当前错误码设置为 e。 #co::strerror 1. const char* strerror(int e); 2. const char* strerror(); 1, 获取错误码 e 的描述信息,线程安全。 2, 获取当前错误码的描述信息,线程安全。 "},{"id":12,"href":"/cn/co/god/","title":"面向玄学","section":"参考文档","content":"include: co/god.h.\n#god god 模块提供了一些与模板相关的功能,模板用到深处有点玄,一些 C++ 程序员将之称为面向玄学编程。\n#god::bless_no_bugs void bless_no_bugs(); 祈求老天保佑代码无 bug,线程安全,可任意调用。\n示例\n#include \u0026#34;co/god.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; int main(int argc, char** argv) { god::bless_no_bugs(); co::print(\u0026#34;hello world\u0026#34;); return 0; } #god::cast template\u0026lt;typename To, typename From\u0026gt; constexpr To cast(From\u0026amp;\u0026amp; f) { return (To) std::forward\u0026lt;From\u0026gt;(f); } 万能转换,将 From 类型转换为 To 类型,To 可以是引用。\n示例\nint i = 65; char c = god::cast\u0026lt;char\u0026gt;(i); // c -\u0026gt; \u0026#39;A\u0026#39; god::cast\u0026lt;char\u0026amp;\u0026gt;(i) = \u0026#39;a\u0026#39;; // 将 i 的低字节设置为 \u0026#39;a\u0026#39; #——————————— #god::align_down 1. template\u0026lt;size_t A, typename X\u0026gt; constexpr X align_down(X x); 2. template\u0026lt;size_t A, typename X\u0026gt; constexpr X* align_down(X* x); 3. template\u0026lt;typename X, typename A\u0026gt; constexpr X align_down(X x, A a); 4. template\u0026lt;typename X, typename A\u0026gt; constexpr X* align_down(X* x, A a); 将整数或指针向下对齐。\n1, 将整数 x 按编译期常量 A 向下对齐,A 必须是 2 的幂。\n2, 将指针 x 按编译期常量 A 向下对齐,A 必须是 2 的幂。\n3, 将整数 x 按整数 a 向下对齐,a 必须是 2 的幂。\n4, 将指针 x 按整数 a 向下对齐,a 必须是 2 的幂。\n示例\ngod::align_down\u0026lt;8\u0026gt;(30); // 24 god::align_down(30, 8); // 24 god::align_down\u0026lt;64\u0026gt;(30); // 0 void* p = (void*)40; god::align_down\u0026lt;64\u0026gt;(p); // (void*)0 god::align_down\u0026lt;32\u0026gt;(p); // (void*)32 god::align_down(p, 32); // (void*)32 #god::align_up 1. template\u0026lt;size_t A, typename X\u0026gt; constexpr X align_up(X x); 2. template\u0026lt;size_t A, typename X\u0026gt; constexpr X* align_up(X* x); 3. template\u0026lt;typename X, typename A\u0026gt; constexpr X align_up(X x, A a); 4. template\u0026lt;typename X, typename A\u0026gt; constexpr X* align_up(X* x, A a); 将整数或指针向上对齐。\n1, 将整数 x 按编译期常量 A 向上对齐,A 必须是 2 的幂。\n2, 将指针 x 按编译期常量 A 向上对齐,A 必须是 2 的幂。\n3, 将整数 x 按整数 a 向上对齐,a 必须是 2 的幂。\n4, 将指针 x 按整数 a 向上对齐,a 必须是 2 的幂。\n示例\ngod::align_up\u0026lt;8\u0026gt;(30); // 32 god::align_up(30, 8); // 32 god::align_up\u0026lt;64\u0026gt;(30); // 64 void* p = (void*)40; god::align_up\u0026lt;64\u0026gt;(p); // (void*)64 god::align_up\u0026lt;32\u0026gt;(p); // (void*)64 god::align_up(p, 32); // (void*)64 #god::copy template\u0026lt;size_t N\u0026gt; inline void copy(void* dst, const void* src); 从 src 拷贝 N 字节到 dst 中。\n示例\nchar s[8] = \u0026#34;1234567\u0026#34;; uint32 x; god::copy\u0026lt;4\u0026gt;(\u0026amp;x, s + 1); #god::eq template\u0026lt;typename T\u0026gt; bool eq(const void* p, const void* q); 判断 *(T*)p == *(T*)q 是否成立。\n示例\ngod::eq\u0026lt;uint32\u0026gt;(\u0026#34;nicecode\u0026#34;, \u0026#34;nicework\u0026#34;); // true god::eq\u0026lt;uint64\u0026gt;(\u0026#34;nicecode\u0026#34;, \u0026#34;nicework\u0026#34;); // false #god::log2 template\u0026lt;typename T\u0026gt; constexpr T log2(T x); 计算整数 x 的以 2 为底的对数,结果仅取整数部分。\n示例\ngod::log2(32); // 5 god::log2(5); // 2 #god::nb template\u0026lt;size_t N, typename X\u0026gt; constexpr X nb(X x); 计算 block 数,N 是 block 大小,N 必须是 2 的幂。\n示例\ngod::nb\u0026lt;8\u0026gt;(31); // 4 god::nb\u0026lt;16\u0026gt;(32); // 2 god::nb\u0026lt;32\u0026gt;(33); // 2 #——————————— #god::fetch_add template\u0026lt;typename T, typename V\u0026gt; inline T fetch_add(T* p, V v); 将 *p 的值加上 v,返回原来的值。\n示例\nint i = 8; int x = god::fetch_add(\u0026amp;i, 1); // i -\u0026gt; 9, x -\u0026gt; 8 #god::fetch_sub template\u0026lt;typename T, typename V\u0026gt; inline T fetch_sub(T* p, V v); 将 *p 的值减去 v,返回原来的值。\n示例\nint i = 8; int x = god::fetch_sub(\u0026amp;i, 1); // i -\u0026gt; 7, x -\u0026gt; 8 #god::fetch_and template\u0026lt;typename T, typename V\u0026gt; inline T fetch_and(T* p, V v); 对 *p 执行 and 操作,返回原来的值。\n示例\nuint32 u = 5; uint32 x = god::fetch_and(\u0026amp;u, 1); // u -\u0026gt; 1, x -\u0026gt; 5 #god::fetch_or template\u0026lt;typename T, typename V\u0026gt; inline T fetch_or(T* p, V v); 对 *p 执行 or 操作,返回原来的值。\n示例\nuint32 u = 2; uint32 x = god::fetch_or(\u0026amp;u, 1); // u -\u0026gt; 3, x -\u0026gt; 2 #god::fetch_xor template\u0026lt;typename T, typename V\u0026gt; inline T fetch_xor(T* p, V v); 对 *p 执行 xor 操作,返回原来的值。\n示例\nuint32 u = 2; uint32 x = god::fetch_xor(\u0026amp;u, 2); // u -\u0026gt; 0, x -\u0026gt; 2 #god::swap template\u0026lt;typename T, typename V\u0026gt; inline T swap(T* p, V v); 将 *p 的值设置为 v,返回原来的值。\n示例\nuint32 u = 23; uint32 x = god::swap(\u0026amp;u, 77); // u -\u0026gt; 77, x -\u0026gt; 23 #——————————— #god::const_ref_t template\u0026lt;typename T\u0026gt; using _const_t = typename std::add_const\u0026lt;T\u0026gt;::type; template\u0026lt;typename T\u0026gt; using const_ref_t = typename std::add_lvalue_reference\u0026lt;_const_t\u0026lt;rm_ref_t\u0026lt;T\u0026gt;\u0026gt;\u0026gt;::type; 添加 const 与左值引用。\n示例\ngod::const_ref_t\u0026lt;int\u0026gt; -\u0026gt; const int\u0026amp; #god::if_t template\u0026lt;bool C, typename T=void\u0026gt; using if_t = typename std::enable_if\u0026lt;C, T\u0026gt;::type; 等价于 std::enable_if_t。 #god::rm_arr_t template\u0026lt;typename T\u0026gt; using rm_arr_t = typename std::remove_extent\u0026lt;T\u0026gt;::type; 移除 T 的第一个数组维度。\n示例\ngod::rm_arr_t\u0026lt;int[8]\u0026gt; -\u0026gt; int god::rm_arr_t\u0026lt;int[6][8]\u0026gt; -\u0026gt; int[8] #god::rm_cv_t template\u0026lt;typename T\u0026gt; using rm_cv_t = typename std::remove_cv\u0026lt;T\u0026gt;::type; 移除 T 中的 const 与 volatile。 #god::rm_ref_t template\u0026lt;typename T\u0026gt; using rm_ref_t = typename std::remove_reference\u0026lt;T\u0026gt;::type; 移除 T 中的引用。\n示例\ngod::rm_ref_t\u0026lt;bool\u0026amp;\u0026gt; -\u0026gt; bool god::rm_ref_t\u0026lt;int\u0026amp;\u0026amp;\u0026gt; -\u0026gt; int #god::rm_cvref_t template\u0026lt;typename T\u0026gt; using rm_cvref_t = rm_cv_t\u0026lt;rm_ref_t\u0026lt;T\u0026gt;\u0026gt;; rm_cv_t 与 rm_ref_t 的组合。\n示例\ngod::rm_cvref_t\u0026lt;const int\u0026amp;\u0026gt; -\u0026gt; int #——————————— #god::is_array template\u0026lt;typename T\u0026gt; constexpr bool is_array(); 判断 T 是否为数组类型。\n示例\ngod::is_array\u0026lt;char[8]\u0026gt;(); // true god::is_array\u0026lt;char*\u0026gt;(); // false #god::is_class template\u0026lt;typename T\u0026gt; constexpr bool is_class(); 判断 T 是否为 class。\n示例\ngod::is_class\u0026lt;std::string\u0026gt;(); // true #god::is_ref template\u0026lt;typename T\u0026gt; constexpr bool is_ref(); 判断 T 是否为引用。 god::is_ref\u0026lt;const int\u0026amp;\u0026gt;(); // true god::is_ref\u0026lt;int\u0026amp;\u0026amp;\u0026gt;(); // true #god::is_same template\u0026lt;typename T, typename U, typename ...X\u0026gt; constexpr bool is_same(); 判断类型 T 是否为 U 或 X... 中的一个。\n示例\ngod::is_same\u0026lt;int, int\u0026gt;(); // true god::is_same\u0026lt;int, bool, int\u0026gt;(); // true god::is_same\u0026lt;int, bool, char\u0026gt;(); // false #god::is_scalar template\u0026lt;typename T\u0026gt; constexpr bool is_scalar(); 判断 T 是否为标量类型。 "},{"id":13,"href":"/cn/co/concurrency/coroutine/chan/","title":"channel","section":"协程","content":"include: co/co.h.\n#co::chan template\u0026lt;typename T\u0026gt; class chan; co::chan 是一个模板类,它类似于 golang 中的 channel,用于在协程之间传递数据。\n从 v3.0.1 开始,co::chan 可在协程或非协程中使用,且可存储 std::string 等非 POD 类型的值。 #constructor 1. explicit chan(uint32 cap=1, uint32 ms=(uint32)-1); 2. chan(chan\u0026amp;\u0026amp; c); 3. chan(const chan\u0026amp; c); 1, 参数 cap 是内部队列的最大容量,默认是 1,参数 ms 是读写操作的超时时间,单位为毫秒,默认为 -1,永不超时。 2, 移动构造函数。 3, 拷贝构造函数,仅将内部引用计数加 1。 #close void close() const; 关闭 channel,关闭后 channel 不可写,但可读。 #done bool done() const; 判断读、写操作是否成功完成。 #operator bool explicit operator bool() const; 若 channel 为关闭状态,返回 false,否则返回 true。 #operator\u0026laquo; chan\u0026amp; operator\u0026lt;\u0026lt;(const T\u0026amp; x) const; chan\u0026amp; operator\u0026lt;\u0026lt;(T\u0026amp;\u0026amp; x) const; 写入元素 x。 此方法会阻塞,直到写入操作完成或超时。可以调用 done() 方法判断写操作是否成功完成。 若 channel 已关闭,写操作会失败,此方法立即返回。 #operator\u0026raquo; chan\u0026amp; operator\u0026gt;\u0026gt;(T\u0026amp; x) const; 读取元素。 此方法会阻塞,直到读取操作完成或超时。可以调用 done() 方法判断读操作是否成功完成。 若 channel 已关闭且 channel 中无元素可读时,读操作会失败,此方法立即返回。 #代码示例 #include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; void f() { co::chan\u0026lt;int\u0026gt; ch; go([ch]() { ch \u0026lt;\u0026lt; 7; }); int v = 0; ch \u0026gt;\u0026gt; v; co::print(\u0026#34;v: \u0026#34;, v); } void g() { co::chan\u0026lt;int\u0026gt; ch(32, 500); go([ch]() { ch \u0026lt;\u0026lt; 7; if (!ch.done()) co::print(\u0026#34;write to channel timeout..\u0026#34;); }); int v = 0; ch \u0026gt;\u0026gt; v; if (ch.done()) co::print(\u0026#34;v: \u0026#34;, v); } int main(int argc, char** argv) { flag::parse(argc, argv); f(); g(); return 0; } "},{"id":14,"href":"/cn/co/other/fastring/","title":"fastring","section":"其他","content":"include: co/fastring.h.\n#fastring fastring 是 coost 提供的字符串类型,它实现了 std::string 中的大部分方法,同时也提供了一些 std::string 没有的方法。\n#constructor 1. constexpr fastring() noexcept; 2. explicit fastring(size_t cap); 3. fastring(const void* s, size_t n); 4. fastring(const char* s); 5. fastring(const std::string\u0026amp; s); 6. fastring(size_t n, char c); 7. fastring(const fastring\u0026amp; s); 8. fastring(fastring\u0026amp;\u0026amp; s) noexcept; 1, 默认构造函数,创建一个空的 fastring 对象,不会分配任何内存。 2, 创建一个空的 fastring 对象,但用参数 cap 指定初始容量,即预分配 cap 字节的内存。 3, 用给定的字节序列创建 fastring 对象,参数 n 是序列长度。 4, 用 C 风格的字符串创建 fastring 对象,s 必须是 \u0026lsquo;\\0\u0026rsquo; 结尾的字符串。 5, 用 std::string 创建一个 fastring 对象。 6, 将 fastring 对象初始化为 n 个字符 c 构成的字符串。 7, 拷贝构造函数,内部会进行内存拷贝。 8, 移动构造函数,不会进行内存拷贝。 v3.0.2 中移除了 fastring(char c, size_t n);。 示例 fastring s; // 空字符串,无内存分配 fastring s(32); // 空字符串,预分配内存(容量为32) fastring s(\u0026#34;hello\u0026#34;); // 初始化 s 为 \u0026#34;hello\u0026#34; fastring s(\u0026#34;hello\u0026#34;, 3); // 初始化 s 为 \u0026#34;hel\u0026#34; fastring s(88, \u0026#39;x\u0026#39;); // 初始化 s 为 88 个 \u0026#39;x\u0026#39; fastring t(s); // 拷贝构造 fastring x(std::move(s)); // 移动构造,s 自身变成空字符串 #operator= 1. fastring\u0026amp; operator=(const char* s); 2. fastring\u0026amp; operator=(const std::string\u0026amp; s); 3. fastring\u0026amp; operator=(const fastring\u0026amp; s); 4. fastring\u0026amp; operator=(fastring\u0026amp;\u0026amp; s) noexcept; 1, 用 \u0026lsquo;\\0\u0026rsquo; 结尾的字符串进行赋值,s 可以是进行赋值操作的 fastring 的一部分。\n2, 用 std::string 进行赋值。\n3, 拷贝赋值操作。\n4, 移动赋值操作,s 自身会变成空字符串。\n示例\nfastring s; fastring t; s = \u0026#34;hello\u0026#34;; // s -\u0026gt; \u0026#34;hello\u0026#34; s = s; // nothing will be done s = s.c_str() + 2; // s -\u0026gt; \u0026#34;llo\u0026#34; s = std::string(\u0026#34;x\u0026#34;); // s -\u0026gt; \u0026#34;x\u0026#34; t = s; // t -\u0026gt; \u0026#34;x\u0026#34; t = std::move(s); // t -\u0026gt; \u0026#34;x\u0026#34;, s -\u0026gt; \u0026#34;\u0026#34; #assign 1. fastring\u0026amp; assign(const void* s, size_t n); 2. template\u0026lt;typename S\u0026gt; fastring\u0026amp; assign(S\u0026amp;\u0026amp; s); 3. fastring\u0026amp; assign(size_t n, char c); 对 fastring 进行赋值,v3.0.1 新增。 1, 用字节序列赋值,n 是字节序列 s 的长度,s 可以是 fastring 自身的一部分。 2, 与 operator= 等价。 3, v3.0.2 新增,将 fastring 赋值为 n 个字符 c。 #——————————— #back char\u0026amp; back(); const char\u0026amp; back() const; 此方法返回 fastring 中最后一个字符的引用。 若 fastring 为空,调用此方法会导致未定义的行为。 示例 fastring s(\u0026#34;hello\u0026#34;); char c = s.back(); // c = \u0026#39;o\u0026#39; s.back() = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;hellx\u0026#34; #front char\u0026amp; front(); const char\u0026amp; front() const; 此方法返回 fastring 中第一个字符的引用。 若 fastring 为空,调用此方法会导致未定义的行为。 示例 fastring s(\u0026#34;hello\u0026#34;); char c = s.front(); // c = \u0026#39;h\u0026#39; s.front() = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;xello\u0026#34; #operator[] char\u0026amp; operator[](size_t n); const char\u0026amp; operator[](size_t n) const; 此方法返回 fastring 中第 n 个字符的引用。 若 n 超出合理的范围,调用此方法会导致未定义的行为。 示例 fastring s(\u0026#34;hello\u0026#34;); char c = s[1]; // c = \u0026#39;e\u0026#39; s[1] = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;hxllo\u0026#34; #——————————— #capacity size_t capacity() const noexcept; 此方法返回 fastring 的容量。 #c_str const char* c_str() const; 此方法获取等效的 C 风格字符串 (\\0结尾)。 通过 c_str() 访问的字符数组是只读的,不可进行写操作。 #data char* data() noexcept; const char* data() const noexcept; 此方法与 c_str() 类似,但不保证字符串以 \u0026lsquo;\\0\u0026rsquo; 结尾。 #empty bool empty() const noexcept; 此方法判断 fastring 是否为空。 #size size_t size() const noexcept; 此方法返回 fastring 的长度。 #——————————— #clear 1. void clear(); 2. void clear(char c); 此方法仅将 fastring 的 size 置为 0,capacity 保持不变。 2 与 1 类似,只是 size 置为 0 前会用字符 c 填充内部内存。 2 是 v3.0.1 新增,可用于清除内存中的敏感信息。 #ensure void ensure(size_t n); 此方法确保 fastring 剩余的内存能容纳至少 n 个字符。 #reserve void reserve(size_t n); 此方法调整 fastring 的容量,确保容量至少是 n。 当 n 小于原来的容量时,则保持容量不变。 #reset void reset(); v2.0.3 新增。清空 fastring 并释放内存。 #resize 1. void resize(size_t n); 2. void resize(size_t n, char c); 此方法将 fastring 的 size 设置为 n。\n当 n 大于原来的 size 时,此操作将 size 扩大到 n。1 中扩展的部分,内容是未定义的;2 中会用字符 c 填充扩展的部分。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.resize(3); // s -\u0026gt; \u0026#34;hel\u0026#34; s.resize(6); char c = s[5]; // c 是不确定的随机值 s.resize(3); s.resize(6, 0); c = s[5]; // c 是 \u0026#39;\\0\u0026#39; #shrink void shrink(); 释放 fastring 中多余的内存。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.reserve(32); // capacity -\u0026gt; 32 s.shrink(); // capacity -\u0026gt; 6 #swap void swap(fastring\u0026amp; s) noexcept; void swap(fastring\u0026amp;\u0026amp; s) noexcept; 交换两个 fastring,仅交换内部指针、容量、大小。\n示例\nfastring s(\u0026#34;hello\u0026#34;); fastring x(\u0026#34;world\u0026#34;); s.swap(x); // s -\u0026gt; \u0026#34;world\u0026#34;, x -\u0026gt; \u0026#34;hello\u0026#34; #——————————— #append 1. fastring\u0026amp; append(const void* s, size_t n); 2. fastring\u0026amp; append(const char* s); 3. fastring\u0026amp; append(const fastring\u0026amp; s); 4. fastring\u0026amp; append(const std::string\u0026amp; s); 5. fastring\u0026amp; append(size_t n, char c); 6. fastring\u0026amp; append(char c); 1, 追加指定长度的字节序列,n 为序列长度。 2, 追加 \u0026lsquo;\\0\u0026rsquo; 结尾的字符串,s 可以是执行 append 操作的 fastring 的一部分。 3, 追加 fastring 对象,s 可以是执行 append 操作的 fastring 对象本身。 4, 追加 std::string 对象。 5, 追加 n 个字符 c。 6, 追加单个字符 c。 v3.0.2 移除了 fastring\u0026amp; append(char c, size_t n);。 示例 fastring s; s.append(\u0026#39;c\u0026#39;); // s -\u0026gt; \u0026#34;c\u0026#34; s.append(2, \u0026#39;c\u0026#39;); // s -\u0026gt; \u0026#34;ccc\u0026#34; s.clear(); s.append(\u0026#39;c\u0026#39;).append(2, \u0026#39;x\u0026#39;); // s -\u0026gt; \u0026#34;cxx\u0026#34; s.append(s.c_str() + 1); // s -\u0026gt; \u0026#34;cxxxx\u0026#34; s.append(s.data(), 3); // s -\u0026gt; \u0026#34;cxxxxcxx\u0026#34; #append_nomchk fastring\u0026amp; append_nomchk(const void* s, size_t n); fastring\u0026amp; append_nomchk(const char* s) 与 append() 类似,但不会检查 s 是否与内部内存重叠。 若 s 可能与 fastring 内部内存重叠,则不可使用此方法。 #cat template\u0026lt;typename X, typename ...V\u0026gt; fastring\u0026amp; cat(X\u0026amp;\u0026amp; x, V\u0026amp;\u0026amp; ... v); v2.0.3 新增。将任意数量的元素连接到 fastring 中。\n此方法调用 operator\u0026lt;\u0026lt; 操作,将参数中的元素逐个追加到 fastring 中。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.cat(\u0026#39; \u0026#39;, 23, \u0026#34;xx\u0026#34;, false); // s -\u0026gt; \u0026#34;hello 23xxfalse\u0026#34; #operator\u0026laquo; fastring\u0026amp; operator\u0026lt;\u0026lt;(bool v); fastring\u0026amp; operator\u0026lt;\u0026lt;(char v); fastring\u0026amp; operator\u0026lt;\u0026lt;(signed char v); fastring\u0026amp; operator\u0026lt;\u0026lt;(unsigned char v); fastring\u0026amp; operator\u0026lt;\u0026lt;(short v); fastring\u0026amp; operator\u0026lt;\u0026lt;(unsigned short v); fastring\u0026amp; operator\u0026lt;\u0026lt;(int v); fastring\u0026amp; operator\u0026lt;\u0026lt;(unsigned int v); fastring\u0026amp; operator\u0026lt;\u0026lt;(long v); fastring\u0026amp; operator\u0026lt;\u0026lt;(unsigned long v); fastring\u0026amp; operator\u0026lt;\u0026lt;(long long v); fastring\u0026amp; operator\u0026lt;\u0026lt;(unsigned long long v); fastring\u0026amp; operator\u0026lt;\u0026lt;(double v); fastring\u0026amp; operator\u0026lt;\u0026lt;(float v); fastring\u0026amp; operator\u0026lt;\u0026lt;(const dp::_fpt\u0026amp; v); fastring\u0026amp; operator\u0026lt;\u0026lt;(const void* v); fastring\u0026amp; operator\u0026lt;\u0026lt;(std::nullptr_t); fastring\u0026amp; operator\u0026lt;\u0026lt;(const char* s); fastring\u0026amp; operator\u0026lt;\u0026lt;(const signed char* s); fastring\u0026amp; operator\u0026lt;\u0026lt;(const unsigned char* s); fastring\u0026amp; operator\u0026lt;\u0026lt;(const fastring\u0026amp; s); fastring\u0026amp; operator\u0026lt;\u0026lt;(const std::string\u0026amp; s); 将 bool、char、整数类型、浮点数类型、指针类型、字符串类型的值格式化后追加到 fastring 中。\noperator\u0026lt;\u0026lt;(const dp::_fpt\u0026amp;) 用于格式化输出浮点数,可指定有效小数位数。\n示例\nfastring s; s \u0026lt;\u0026lt; \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;x\u0026#34; s \u0026lt;\u0026lt; s; // s -\u0026gt; \u0026#34;xx\u0026#34; (append itself) s \u0026lt;\u0026lt; false; // s -\u0026gt; \u0026#34;xxfalse\u0026#34; s.clear(); s \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // s -\u0026gt; \u0026#34;hello 23\u0026#34; s \u0026lt;\u0026lt; (s.c_str() + 6); // s -\u0026gt; \u0026#34;hello 2323\u0026#34; (append part of s) s.clear(); s \u0026lt;\u0026lt; 3.1415; // s -\u0026gt; \u0026#34;3.1415\u0026#34; s.clear(); s \u0026lt;\u0026lt; (void*)32; // s -\u0026gt; \u0026#34;0x20\u0026#34; 指定有效小数的位数\ncoost 提供 dp::_1, dp::_2, ..., dp::_16, dp::_n,用于设置浮点数的有效小数位数。\nfastring s; s \u0026lt;\u0026lt; dp::_2(3.1415); // \u0026#34;3.14 s \u0026lt;\u0026lt; dp::_3(3.1415); // \u0026#34;3.141\u0026#34; s \u0026lt;\u0026lt; dp::_n(3.14, 1); // \u0026#34;3.1\u0026#34;, 与 dp::_1(3.14) 等价 #operator+= fastring\u0026amp; operator+=(const char* s); fastring\u0026amp; operator+=(const fastring\u0026amp; s); fastring\u0026amp; operator+=(const std::string\u0026amp; s); fastring\u0026amp; operator+=(char c); 此方法等价于 append() 方法。\n示例\nfastring s; s += \u0026#39;c\u0026#39;; // s -\u0026gt; \u0026#34;c\u0026#34; s += \u0026#34;xx\u0026#34;; // s -\u0026gt; \u0026#34;cxx\u0026#34; #push_back fastring\u0026amp; push_back(char c); 追加单个字符到 fastring 尾部,与 append(c) 等价。 #pop_back char pop_back(); 取出并返回 fastring 最后的一个字符。 若 fastring 为空,调用此方法会导致未定义的行为。 #——————————— #compare 1. int compare(const char* s, size_t n) const; 2. int compare(const char* s) const; 3. int compare(const fastring\u0026amp; s) const noexcept; 4. int compare(const std::string\u0026amp; s) const noexcept; 5. int compare(size_t pos, size_t len, const char* s, size_t n) const; 6. int compare(size_t pos, size_t len, const char* s) const; 7. int compare(size_t pos, size_t len, const fastring\u0026amp; s) const; 8. int compare(size_t pos, size_t len, const std::string\u0026amp; s) const; 9. int compare(size_t pos, size_t len, const fastring\u0026amp; s, size_t spos, size_t n=npos) const; 10. int compare(size_t pos, size_t len, const std::string\u0026amp; s, size_t spos, size_t n=npos) const; 比较 fastring 与指定的字符串。 1-4, 将 fastring 与 s 比较,n 是 s 的长度。 5-8, 将 fastring 的 [pos, pos+len) 部分与 s 进行比较,n 是 s 的长度。 9-10, 将 fastring 的 [pos, pos+len) 部分与 s 的 [spos, spos+n) 部分进行比较。 #contains bool contains(char c) const; bool contains(const char* s) const; bool contains(const fastring\u0026amp; s) const; bool contains(const std::string\u0026amp; s) const; 判断 fastring 中是否包含指定的字符或字符串。 #find 1. size_t find(char c) const; 2. size_t find(char c, size_t pos) const; 3. size_t find(char c, size_t pos, size_t len) const; 4. size_t find(const char* s) const; 5. size_t find(const char* s, size_t pos) const; 6. size_t find(const char* s, size_t pos, size_t n) const; 7. size_t find(const fastring\u0026amp; s, size_t pos=0) const; 8. size_t find(const std::string\u0026amp; s, size_t pos=0) const; 1, 从位置 0 开始查找字符 c。 2, 从位置 pos 开始查找字符 c。 3, v3.0 新增,在 [pos, pos + len) 范围内查找字符 c。 4, 从位置 0 开始查找子串 s。 5, 从位置 pos 开始查找子串 s。 6, 从位置 pos 开始查找子串 s,s 长度为 n。 7-8, 从位置 pos 开始查找子串 s。 此方法查找成功时,返回所查找字符或子串的位置,否则返回 npos。 v3.0.1 新增上述 6-8,并支持二进制字符串的查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.find(\u0026#39;l\u0026#39;); // return 2 s.find(\u0026#39;l\u0026#39;, 3); // return 3 s.find(\u0026#34;ll\u0026#34;); // return 2 s.find(\u0026#34;ll\u0026#34;, 3); // return s.npos #ifind size_t ifind(char c, size_t pos=0) const; size_t ifind(const char* s) const; size_t ifind(const char* s, size_t pos) const size_t ifind(const char* s, size_t pos, size_t n) const; size_t ifind(const fastring\u0026amp; s, size_t pos=0) const; size_t ifind(const std::string\u0026amp; s, size_t pos=0) const; v3.0.1 新增,与 find 类似,但是忽略大小写。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.ifind(\u0026#39;L\u0026#39;); // return 2 s.ifind(\u0026#39;L\u0026#39;, 3); // return 3 s.ifind(\u0026#34;Ll\u0026#34;); // return 2 #rfind 1. size_t rfind(char c) const; 2. size_t rfind(char c, size_t pos) const; 3. size_t rfind(const char* s) const; 4. size_t rfind(const char* s, size_t pos) const; 5. size_t rfind(const char* s, size_t pos, size_t n) const; 6. size_t rfind(const fastring\u0026amp; s, size_t pos=npos) const; 7. size_t rfind(const std::string\u0026amp; s, size_t pos=npos) const; 与 find 类似,只是从反向开始查找。 v3.0.1 新增上述 2, 4-7,并支持二进制字符串的反向查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.rfind(\u0026#39;l\u0026#39;); // return 3 s.rfind(\u0026#39;l\u0026#39;, 2); // return 2 s.rfind(\u0026#34;ll\u0026#34;); // return 2 s.rfind(\u0026#34;le\u0026#34;); // return s.npos #find_first_of 1. size_t find_first_of(const char* s, size_t pos=0) const; 2. size_t find_first_of(const char* s, size_t pos, size_t n) const; 3. size_t find_first_of(const fastring\u0026amp; s, size_t pos=0) const; 4. size_t find_first_of(const std::string\u0026amp; s, size_t pos=0) const; 查找第一个出现的指定字符集 s 中的字符, n 是 s 的长度。 此方法从位置 pos(默认为0) 开始查找,遇到 s 中的任意字符时,即返回该字符的位置,否则返回 npos。 v3.0.1 新增 2-4,并支持二进制字符串的查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.find_first_of(\u0026#34;def\u0026#34;); // return 1 s.find_first_of(\u0026#34;ol\u0026#34;, 3); // return 3 #find_first_not_of 1. size_t find_first_not_of(char c, size_t pos=0) const; 2. size_t find_first_not_of(const char* s, size_t pos=0) const; 3. size_t find_first_not_of(const char* s, size_t pos, size_t n) const; 4. size_t find_first_not_of(const fastring\u0026amp; s, size_t pos=0) const; 5. size_t find_first_not_of(const std::string\u0026amp; s, size_t pos=0) const; 查找第一个出现的非指定字符 c 或非指定字符集 s 中的字符,n 是 s 的长度。 此方法从位置 pos(默认为0) 开始查找,遇到不是 c 或非 s 中的任意字符时,即返回该字符的位置,否则返回 npos。 v3.0.1 中新增 3-5,并支持二进制字符串的查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.find_first_not_of(\u0026#34;he\u0026#34;); // return 2 s.find_first_not_of(\u0026#34;he\u0026#34;, 3); // return 3 s.find_first_not_of(\u0026#39;x\u0026#39;); // return 0 #find_last_of 1. size_t find_last_of(const char* s, size_t pos=npos) const; 2. size_t find_last_of(const char* s, size_t pos, size_t n) const; 3. size_t find_last_of(const fastring\u0026amp; s, size_t pos=npos) const; 4. size_t find_last_of(const std::string\u0026amp; s, size_t pos=npos) const; 查找最后一个出现的指定字符集 s 中的字符。 此方法从 pos(默认值为npos) 处开始反向查找,遇到 s 中的任意字符时,即返回该字符的位置,否则返回 npos。 v3.0.1 中新增 2-4。此方法支持二进制字符串的查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.find_last_of(\u0026#34;le\u0026#34;); // return 3 s.find_last_of(\u0026#34;le\u0026#34;, 1); // return 1 #find_last_not_of 1. size_t find_last_not_of(char c, size_t pos=npos) const; 2. size_t find_last_not_of(const char* s, size_t pos=npos) const; 3. size_t find_last_not_of(const char* s, size_t pos, size_t n) const; 4. size_t find_last_not_of(const fastring\u0026amp; s, size_t pos=npos) const; 5. size_t find_last_not_of(const std::string\u0026amp; s, size_t pos=npos) const; 查找最后一个出现的非指定字符 c 或者非指定字符集 s 中的字符。 此方法从 pos(默认为npos) 处开始反向查找,遇到不等于 c 或者非 s 中的任意字符时,即返回该字符的位置,否则返回 npos。 v3.0.1 中新增 3-5,并且支持二进制字符串的查找。 示例 fastring s(\u0026#34;hello\u0026#34;); s.find_last_not_of(\u0026#34;le\u0026#34;); // return 4 s.find_last_not_of(\u0026#34;le\u0026#34;, 3); // return 0 s.find_last_not_of(\u0026#39;o\u0026#39;); // return 3 #npos static const size_t npos = (size_t)-1; npos 是 size_t 类型的最大值。\nnpos 作为长度时,表示直到字符串结尾。\nnpos 作为返回值时,表示未找到。\n示例\nfastring s(\u0026#34;hello\u0026#34;); size_t r = s.find(\u0026#39;x\u0026#39;); r == s.npos; // true #substr fastring substr(size_t pos) const; fastring substr(size_t pos, size_t len) const; 第 1 个版本返回从位置 pos 开始的子串。\n第 2 个版本返回从位置 pos 开始、长度为 len 的子串。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.substr(2); // \u0026#34;llo\u0026#34; s.substr(2, 2); // \u0026#34;ll\u0026#34; #——————————— #starts_with bool starts_with(char c) const; bool starts_with(const char* s, size_t n) const; bool starts_with(const char* s) const; bool starts_with(const fastring\u0026amp; s) const; bool starts_with(const std::string\u0026amp; s) const; 此方法判断 fastring 是否以字符 c 或字符串 s 开头,n 是 s 的长度。 当 s 为空字符串时,此方法始终返回 true。 #ends_with bool ends_with(char c) const; bool ends_with(const char* s, size_t n) const; bool ends_with(const char* s) const; bool ends_with(const fastring\u0026amp; s) const; bool ends_with(const std::string\u0026amp; s) const; 此方法判断 fastring 是否以字符 c 或字符串 s 结尾,n 是 s 的长度。 当 s 为空字符串时,此方法始终返回 true。 #match bool match(const char* pattern) const; 判断 fastring 是否匹配模式 pattern,* 匹配 0 个或多个字符,? 匹配单个字符。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s.match(\u0026#34;he??o\u0026#34;); // true s.match(\u0026#34;h*o\u0026#34;); // true s.match(\u0026#34;he?o\u0026#34;); // false s.match(\u0026#34;*o\u0026#34;); // true s.match(\u0026#34;h*l?\u0026#34;); // true #remove_prefix fastring\u0026amp; remove_prefix(const char* s, size_t n); fastring\u0026amp; remove_prefix(const char* s); fastring\u0026amp; remove_prefix(const fastring\u0026amp; s); fastring\u0026amp; remove_prefix(const std::string\u0026amp; s); 删除 fastring 中的前缀 s,n 是 s 的长度。\nv3.0.1 新增。\n示例\nfastring s(\u0026#34;12345678\u0026#34;); s.remove_prefix(\u0026#34;123\u0026#34;); // s -\u0026gt; \u0026#34;45678\u0026#34; s.remove_prefix(\u0026#34;xxx\u0026#34;); // nothing will be done #remove_suffix fastring\u0026amp; remove_suffix(const char* s, size_t n); fastring\u0026amp; remove_suffix(const char* s); fastring\u0026amp; remove_suffix(const fastring\u0026amp; s); fastring\u0026amp; remove_suffix(const std::string\u0026amp; s); 删除 fastring 的后缀 s,n 是 s 的长度。 v3.0.1 中将 remove_tail 更名为 remove_suffix。 示例 fastring s(\u0026#34;hello.log\u0026#34;); s.remove_suffix(\u0026#34;.log\u0026#34;); // s -\u0026gt; \u0026#34;hello\u0026#34; s.remove_suffix(\u0026#34;.xxx\u0026#34;); // nothing will be done #——————————— #replace 1. fastring\u0026amp; replace(const char* sub, const char* to, size_t maxreplace=0); 2. fastring\u0026amp; replace(const fastring\u0026amp; sub, const fastring\u0026amp; to, size_t maxreplace=0); 3. fastring\u0026amp; replace(const char* sub, size_t n, const char* to, size_t m, size_t maxreplace=0); 此方法将 fastring 中的子串 sub 替换成 to,参数 maxreplace 指定最大的替换次数,0 表示不限次数。n 和 m 分别是子串 sub、to 的长度。 v3.0.1 中新增 2-3,并支持二进制字符串的替换。 示例 fastring s(\u0026#34;hello\u0026#34;); s.replace(\u0026#34;ll\u0026#34;, \u0026#34;rr\u0026#34;); // s -\u0026gt; \u0026#34;herro\u0026#34; s.replace(\u0026#34;err\u0026#34;, \u0026#34;ok\u0026#34;).replace(\u0026#34;k\u0026#34;, \u0026#34;o\u0026#34;); // s -\u0026gt; \u0026#34;hooo\u0026#34; #strip template\u0026lt;typename ...X\u0026gt; fastring\u0026amp; strip(X\u0026amp;\u0026amp; ...x); 修剪字符串,与 trim 等价。 #trim 1. fastring\u0026amp; trim(char c, char d=\u0026#39;b\u0026#39;); 2. fastring\u0026amp; trim(unsigned char c, char d=\u0026#39;b\u0026#39;); 3. fastring\u0026amp; trim(signed char c, char d=\u0026#39;b\u0026#39;); 4. fastring\u0026amp; trim(const char* s=\u0026#34; \\t\\r\\n\u0026#34;, char d=\u0026#39;b\u0026#39;); 5. fastring\u0026amp; trim(size_t n, char d=\u0026#39;b\u0026#39;); 6. fastring\u0026amp; trim(int n, char d=\u0026#39;b\u0026#39;); 修剪字符串,去掉 fastring 左边、右边或两边的指定字符。 参数 d 表示方向,\u0026rsquo;l\u0026rsquo; 或 \u0026lsquo;L\u0026rsquo; 表示左边,\u0026lsquo;r\u0026rsquo; 或 \u0026lsquo;R\u0026rsquo; 表示右边,默认为 \u0026lsquo;b\u0026rsquo; 表示左右两边。 1-3, 去掉左、右或两边的单个字符 c。 4, 去掉左、右或两边的 s 中的字符。 5-6, 去掉左、右或两边的 n 个字符。 从 v3.0.1 开始,参数 c 可以是 \\0。 示例 fastring s = \u0026#34; sos\\r\\n\u0026#34;; s.trim(); // s -\u0026gt; \u0026#34;sos\u0026#34; s.trim(\u0026#39;s\u0026#39;, \u0026#39;l\u0026#39;); // s -\u0026gt; \u0026#34;os\u0026#34; s.trim(\u0026#39;s\u0026#39;, \u0026#39;r\u0026#39;); // s -\u0026gt; \u0026#34;o\u0026#34; s = \u0026#34;[[xx]]\u0026#34;; s.trim(2); // s -\u0026gt; \u0026#34;xx\u0026#34; #tolower fastring\u0026amp; tolower(); 此方法将 fastring 转换成小写,并返回 fastring 的引用。 #toupper fastring\u0026amp; toupper(); 此方法将 fastring 转换成大写,并返回 fastring 的引用。 #lower fastring lower() const; 此方法返回 fastring 的小写形式。 #upper fastring upper() const; 此方法返回 fastring 的大写形式。\n示例\nfastring s(\u0026#34;Hello\u0026#34;); fastring x = s.lower(); // x = \u0026#34;hello\u0026#34;, s 保持不变 fastring y = s.upper(); // x = \u0026#34;HELLO\u0026#34;, s 保持不变 s.tolower(); // s -\u0026gt; \u0026#34;hello\u0026#34; s.toupper(); // s -\u0026gt; \u0026#34;HELLO\u0026#34; #global functions #operator+ fastring operator+(const fastring\u0026amp; a, char b); fastring operator+(char a, const fastring\u0026amp; b); fastring operator+(const fastring\u0026amp; a, const fastring\u0026amp; b); fastring operator+(const fastring\u0026amp; a, const char* b); fastring operator+(const char* a, const fastring\u0026amp; b); fastring operator+(const fastring\u0026amp; a, const std::string\u0026amp; b); fastring operator+(const std::string\u0026amp; a, const fastring\u0026amp; b); 加法操作,此方法至少有一个参数是 fastring。\n示例\nfastring s; s = s + \u0026#39;^\u0026#39;; // s -\u0026gt; \u0026#34;^\u0026#34; s = \u0026#34;o\u0026#34; + s + \u0026#34;o\u0026#34;; // s -\u0026gt; \u0026#34;o^o\u0026#34; #operator== bool operator==(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator==(const fastring\u0026amp; a, const char* b); bool operator==(const char* a, const fastring\u0026amp; b); bool operator==(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator==(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断两个字符串是否相等,至少有一个参数是 fastring。 #operator!= bool operator!=(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator!=(const fastring\u0026amp; a, const char* b); bool operator!=(const char* a, const fastring\u0026amp; b); bool operator!=(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator!=(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断两个字符串是否不相等,至少有一个参数是 fastring。 #operator\u0026lt; bool operator\u0026lt;(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator\u0026lt;(const fastring\u0026amp; a, const char* b); bool operator\u0026lt;(const char* a, const fastring\u0026amp; b); bool operator\u0026lt;(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator\u0026lt;(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断字符串 a 是否小于 b,至少有一个参数是 fastring。 #operator\u0026gt; bool operator\u0026gt;(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator\u0026gt;(const fastring\u0026amp; a, const char* b); bool operator\u0026gt;(const char* a, const fastring\u0026amp; b); bool operator\u0026gt;(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator\u0026gt;(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断字符串 a 是否大于 b,至少有一个参数是 fastring。 #operator\u0026lt;= bool operator\u0026lt;=(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator\u0026lt;=(const fastring\u0026amp; a, const char* b); bool operator\u0026lt;=(const char* a, const fastring\u0026amp; b); bool operator\u0026lt;=(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator\u0026lt;=(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断字符串 a 是否小于或等于 b,至少有一个参数是 fastring。 #operator\u0026gt;= bool operator\u0026gt;=(const fastring\u0026amp; a, const fastring\u0026amp; b); bool operator\u0026gt;=(const fastring\u0026amp; a, const char* b); bool operator\u0026gt;=(const char* a, const fastring\u0026amp; b); bool operator\u0026gt;=(const fastring\u0026amp; a, const std::string\u0026amp; b); bool operator\u0026gt;=(const std::string\u0026amp; a, const fastring\u0026amp; b); 此方法判断字符串 a 是否大于或等于 b,至少有一个参数是 fastring。\n示例\nfastring s(\u0026#34;hello\u0026#34;); s == \u0026#34;hello\u0026#34;; // true s != \u0026#34;hello\u0026#34;; // false s \u0026gt; \u0026#34;aa\u0026#34;; // true s \u0026lt; \u0026#34;xx\u0026#34;; // true s \u0026gt;= \u0026#34;he\u0026#34;; // true s \u0026lt;= \u0026#34;he\u0026#34;; // false #operator\u0026laquo; std::ostream\u0026amp; operator\u0026lt;\u0026lt;(std::ostream\u0026amp; os, const fastring\u0026amp; s); 示例 fastring s(\u0026#34;xx\u0026#34;); std::cout \u0026lt;\u0026lt; s \u0026lt;\u0026lt; std::endl; "},{"id":15,"href":"/cn/co/mem/","title":"内存分配","section":"参考文档","content":"include: co/mem.h.\n#co::alloc 1. void* alloc(size_t size); 2. void* alloc(size_t size, size_t align); 1, 分配 size 字节的内存。 2, 分配 size 字节的内存,内存边界是 align 字节对齐的(align \u0026lt;= 1024)。 第 2 个版本中,align 必须是 2 的幂,且不能超过 1024。 #co::free void free(void* p, size_t size); 释放 co::alloc 或 co::realloc 分配的内存,size 是所分配内存的大小。 co::free 不同于系统提供的 ::free,需要额外带上一个 size 参数。 #co::realloc void* realloc(void* p, size_t old_size, size_t new_size); 重新分配内存,old_size 是之前的内存大小,new_size 是新的大小,后者必须大于前者。 #co::zalloc void* zalloc(size_t size); 分配 size 字节的内存,并将内存清零。 #——————————— #co::make template\u0026lt;typename T, typename... Args\u0026gt; inline T* make(Args\u0026amp;\u0026amp;... args); 调用 co::alloc 分配内存,并用参数 args 在所分配的内存上构建 T 类型的对象。\n示例\nint* p = co::make\u0026lt;int\u0026gt;(7); std::string* s = co::make\u0026lt;std::string\u0026gt;(3, \u0026#39;x\u0026#39;); #co::del template\u0026lt;typename T\u0026gt; inline void del(T* p, size_t n=sizeof(T)); 销毁 co::make 创建的对象,并释放内存。\n参数 n 是 p 所指向对象的内存大小,默认为 sizeof(T)。\n示例\nint* p = co::make\u0026lt;int\u0026gt;(7); co::del(p); #co::make_rootic template\u0026lt;typename T, typename... Args\u0026gt; inline T* make_rootic(Args\u0026amp;\u0026amp;... args); 与 co::make_static 类似,只是该函数创建的静态对象比 co::make_static 创建的静态对象后析构。 #co::make_static template\u0026lt;typename T, typename... Args\u0026gt; inline T* make_static(Args\u0026amp;\u0026amp;... args); 用参数 args 创建 T 类型的静态对象,所创建的静态对象在程序退出时会自动销毁。\n示例\nfastring\u0026amp; s = *co::make_static\u0026lt;fastring\u0026gt;(32, \u0026#39;x\u0026#39;); #——————————— #co::shared template\u0026lt;typename T\u0026gt; class shared; 与 std::shared_ptr 类似,但有些细微区别。\n#constructor 1. constexpr shared() noexcept; 2. constexpr shared(std::nullptr_t) noexcept; 3. shared(const shared\u0026amp; x) noexcept; 4. shared(shared\u0026amp;\u0026amp; x) noexcept; 5. shared(const shared\u0026lt;X\u0026gt;\u0026amp; x) noexcept; 6. shared(shared\u0026lt;X\u0026gt;\u0026amp;\u0026amp; x) noexcept; 1-2, 创建空的 shared 对象。 3, 拷贝构造函数,若 x 不是空对象,则将内部引用计数加 1。 4, 移动构造函数,对象构造完成后,x 变为空对象。 5-6, 从 shared\u0026lt;X\u0026gt; 对象构建 shared\u0026lt;T\u0026gt; 对象,T 是 X 的基类,且 T 的析构函数是 virtual 的。 co::shared 对象不能直接从 T* 指针构建,coost 提供 co::make_shared 用于构建 co::shared 对象。 #destructor ~shared(); 若对象非空,则将内部引用计数减 1,引用计数减至 0 时会销毁内部的对象,并释放内存。 #operator= 1. shared\u0026amp; operator=(const shared\u0026amp; x); 2. shared\u0026amp; operator=(shared\u0026amp;\u0026amp; x); 3. shared\u0026amp; operator=(const shared\u0026lt;X\u0026gt;\u0026amp; x); 4. shared\u0026amp; operator=(shared\u0026lt;X\u0026gt;\u0026amp;\u0026amp; x); 赋值操作。 3-4, 用 shared\u0026lt;X\u0026gt; 类型的值对 shared\u0026lt;T\u0026gt; 类型的对象进行赋值,T 是 X 的基类,且 T 的析构函数是 virtual 的。 #get T* get() const noexcept; 获取内部对象的指针。 #operator-\u0026gt; T* operator-\u0026gt;() const; 重载 operator-\u0026gt;,返回内部对象的指针。 #operator* T\u0026amp; operator*() const; 重载 operator*,返回内部对象的引用。 #operator== bool operator==(T* p) const noexcept; 判断内部对象的指针值是否与 p 相等。 #operator!= bool operator!=(T* p) const noexcept; 判断内部对象的指针值是否与 p 不相等。 #operator bool explicit operator bool() const noexcept; 内部指针为NULL时返回 false,否则返回 true。 #ref_count size_t ref_count() const noexcept; 获取内部对象的引用计数。 #reset void reset(); 若内部指针不为 NULL,则将引用计数减 1(减至 0 时销毁内部对象),再将内部指针设置为 NULL。 #swap void swap(shared\u0026amp; x); void swap(shared\u0026amp;\u0026amp; x); 交换 co::shared 对象的内部指针。 #use_count size_t use_count() const noexcept; 与 ref_count 等价。 #co::make_shared template\u0026lt;typename T, typename... Args\u0026gt; inline shared\u0026lt;T\u0026gt; make_shared(Args\u0026amp;\u0026amp;... args); 用参数 args 创建 shared\u0026lt;T\u0026gt; 类型的对象。\n示例\nco::shared\u0026lt;int\u0026gt; i = co::make_shared\u0026lt;int\u0026gt;(23); co::shared\u0026lt;fastring\u0026gt; s = co::make_shared\u0026lt;fastring\u0026gt;(32, \u0026#39;x\u0026#39;); #co::unique template\u0026lt;typename T\u0026gt; class unique; 与 std::unique_ptr 类似,但有些细微区别。\n#constructor 1. constexpr unique() noexcept; 2. constexpr unique(std::nullptr_t) noexcept; 3. unique(unique\u0026amp; x) noexcept; 4. unique(unique\u0026amp;\u0026amp; x) noexcept; 5. unique(unique\u0026lt;X\u0026gt;\u0026amp; x) noexcept; 6. unique(unique\u0026lt;X\u0026gt;\u0026amp;\u0026amp; x) noexcept; 1-2, 创建空的 unique 对象。 3-4, 将 x 内部的指针转移到所构建的 unique 对象中,x 内部指针变为 NULL。 5-6, 从 unique\u0026lt;X\u0026gt; 对象构建 unique\u0026lt;T\u0026gt; 对象,T 是 X 的基类,且 T 的析构函数是 virtual 的。 co::unique 对象不能直接从 T* 指针构建,coost 提供 co::make_unique 用于构建 co::unique 对象。 #destructor ~unique(); 销毁内部对象,并释放内存。 #operator= 1. unique\u0026amp; operator=(unique\u0026amp; x); 2. unique\u0026amp; operator=(unique\u0026amp;\u0026amp; x); 3. unique\u0026amp; operator=(unique\u0026lt;X\u0026gt;\u0026amp; x); 4. unique\u0026amp; operator=(unique\u0026lt;X\u0026gt;\u0026amp;\u0026amp; x); 赋值操作。 3-4, 用 unique\u0026lt;X\u0026gt; 类型的值对 unique\u0026lt;T\u0026gt; 类型的对象进行赋值,T 是 X 的基类,且 T 的析构函数是 virtual 的。 #get T* get() const noexcept; 获取内部对象的指针。 #operator-\u0026gt; T* operator-\u0026gt;() const; 重载 operator-\u0026gt;,返回内部对象的指针。 #operator* T\u0026amp; operator*() const; 重载 operator*,返回内部对象的引用。 #operator== bool operator==(T* p) const noexcept; 判断内部对象的指针值是否与 p 相等。 #operator!= bool operator!=(T* p) const noexcept; 判断内部对象的指针值是否与 p 不相等。 #operator bool explicit operator bool() const noexcept; 内部指针为NULL时返回 false,否则返回 true。 #reset void reset(); 销毁内部对象,并释放内存。 #swap void swap(unique\u0026amp; x); void swap(unique\u0026amp;\u0026amp; x); 交换 co::unique 对象的内部指针。 #co::make_unique template\u0026lt;typename T, typename... Args\u0026gt; inline unique\u0026lt;T\u0026gt; make_unique(Args\u0026amp;\u0026amp;... args); 用参数 args 创建 unique\u0026lt;T\u0026gt; 类型的对象。\n示例\nco::unique\u0026lt;int\u0026gt; i = co::make_unique\u0026lt;int\u0026gt;(23); co::unique\u0026lt;fastring\u0026gt; s = co::make_unique\u0026lt;fastring\u0026gt;(32, \u0026#39;x\u0026#39;); "},{"id":16,"href":"/cn/co/net/mode/","title":"网络编程模式","section":"网络编程","content":"#基于协程的网络编程模式 协程可以用同步的方式,实现高并发、高性能的网络程序。协程虽然会阻塞,但调度线程可以在大量的协程间快速切换,因此要实现高并发,只需要创建大量的协程即可。\n以 TCP 程序为例,服务端一般采用一个连接一个协程的模式,为每个客户端连接创建新的协程,在协程中处理连接上的数据。客户端没必要一个连接一个协程,一般使用连接池,多个协程共用连接池中的连接。\n#服务端网络模型 void on_connection(int fd) { while (true) { co::recv(fd, ...); // recv request from client process(...); // process the request co::send(fd, ...); // send response to client } } void server_fun() { while (true) { int fd = co::accept(...); if (fd != -1) go(on_connection, fd); } } go(server_fun); 服务端采用一个连接一个协程的模型。 在一个协程中,调用 co::accept() 接受客户端连接。 有连接到来时,创建一个新的协程,在协程中处理连接上的数据。 on_connection() 是处理连接的协程函数,接收、处理与发送数据,在该协程中以完全同步的方式进行,不需要任何异步回调。 #客户端网络模型 void client_fun() { while (true) { if (!connected) co::connect(...); // connect to the server co::send(...); // send request to the server co::recv(...); // recv response from the server process(...); // process the response if (over) co::close(...); // close the connection } } go(client_fun); 建立连接,发送、接收、处理数据,在协程中以完全同步的方式进行。 实际应用中,一般使用 co::pool 作为连接池,以避免创建过多的连接:\nco::pool g_p; void client_fun() { while (true) { co::pool_guard\u0026lt;Connection\u0026gt; conn(g_p); // get a idle connection from the pool conn-\u0026gt;send(...); // send request to the server conn-\u0026gt;recv(...); // recv response from the server process(...); // process the response if (over) conn-\u0026gt;close(...); // close the connection } } go(client_fun); "},{"id":17,"href":"/cn/about/contact/","title":"联系","section":"关于","content":"联系方式\nEmail: idealvin at qq.com github: https://github.com/idealvin/coost gitee: https://gitee.com/idealvin/coost zhihu: idealvin "},{"id":18,"href":"/cn/co/other/fastream/","title":"fastream","section":"其他","content":"include: co/fastream.h.\n#fastream fastream 用于取代 C++ 标准库中的 std::ostringstream。std::ostringstream 性能较差,实测比 snprintf 慢好几倍,fastream 在不同平台测试比 snprintf 快 10~30 倍左右。\n#constructor 1. constexpr fastream() noexcept; 2. explicit fastream(size_t cap); 3. fastream(fastream\u0026amp;\u0026amp; s) noexcept; 1, 默认构造函数,创建一个空的 fastream 对象,内部不会分配任何内存。\n2, 用参数 cap 指定 fastream 的初始容量,即预分配 cap 字节的内存。\n3, 移动构造函数,不会进行内存拷贝。\n示例\nfastream s; // 空对象, 未分配内存 fastream s(1024); // 预分配 1k 内存 fastream x(std::move(s)); // 移动构造, s 变成空对象 #operator= fastream\u0026amp; operator=(fastream\u0026amp;\u0026amp; s) noexcept; fastream 只支持 move 赋值操作,s 的内容被转移到 fastream 中,s 自身变成空对象。\n示例\nfastream s(32); fastream x; x = std::move(s); // x capacity -\u0026gt; 32, s -\u0026gt; empty #——————————— #back char\u0026amp; back(); const char\u0026amp; back() const; 此方法返回 fastream 中最后一个字符的引用。 若 fastream 为空,调用此方法会导致未定义的行为。 示例 fastream s; s.append(\u0026#34;hello\u0026#34;); char c = s.back(); // c = \u0026#39;o\u0026#39; s.back() = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;hellx\u0026#34; #front char\u0026amp; front(); const char\u0026amp; front() const; 此方法返回 fastream 中第一个字符的引用。 若 fastream 为空,调用此方法会导致未定义的行为。 示例 fastream s; s.append(\u0026#34;hello\u0026#34;); char c = s.front(); // c = \u0026#39;h\u0026#39; s.front() = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;xello\u0026#34; #operator[] char\u0026amp; operator[](size_t n); const char\u0026amp; operator[](size_t n) const; 此方法返回 fastream 中第 n 个字符的引用。 若 n 超出合理的范围,调用此方法会导致未定义的行为。 示例 fastream s; s.append(\u0026#34;hello\u0026#34;); char c = s[1]; // c = \u0026#39;e\u0026#39; s[1] = \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;hxllo\u0026#34; #——————————— #capacity size_t capacity() const noexcept; 此方法返回 fastream 的容量。 #c_str const char* c_str() const; 此方法获取等效的 C 风格字符串 (\\0结尾)。 通过 c_str() 访问的字符数组是只读的,不可进行写操作。 #data char* data() noexcept; const char* data() const noexcept; 此方法与 c_str() 类似,但不保证字符串以 \u0026lsquo;\\0\u0026rsquo; 结尾。 #empty bool empty() const noexcept; 此方法判断 fastream 是否为空。 #size size_t size() const noexcept; 此方法返回 fastream 内部数据的长度。 #str fastring str() const; 此方法以 fastring 形式返回 fastream 内部数据的一份拷贝。\n示例\nfastream s; s.append(\u0026#34;hello\u0026#34;); fastring x = s.str(); // x = \u0026#34;hello\u0026#34; #——————————— #clear 1. void clear(); 2. void clear(char c); 此方法仅将 fastream 的 size 置为 0,capacity 保持不变。 2 与 1 类似,只是 size 置为 0 前会用字符 c 填充内部内存。 2 是 v3.0.1 新增,可用于清除内存中的敏感信息。 #ensure void ensure(size_t n); 此方法确保 fastream 剩余的内存能容纳至少 n 个字符。 #reserve void reserve(size_t n); 此方法调整 fastream 的容量,确保容量至少是 n。 当 n 小于原来的容量时,则保持容量不变。 #reset void reset(); v2.0.3 新增。清空 fastream 并释放内存。 #resize 1. void resize(size_t n); 2. void resize(size_t n, char c); 此方法将 fastream 的 size 设置为 n。\n当 n 大于原来的 size 时,此操作将 size 扩大到 n。1 中扩展的部分,内容是未定义的;2 中会用字符 c 填充扩展的部分。\n示例\nfastream s; s.append(\u0026#34;hello\u0026#34;); s.resize(3); // s -\u0026gt; \u0026#34;hel\u0026#34; s.resize(6); char c = s[5]; // c 是不确定的随机值 s.resize(3); s.resize(6, 0); c = s[5]; // c 是 \u0026#39;\\0\u0026#39; #swap void swap(fastream\u0026amp; s) noexcept; void swap(fastream\u0026amp;\u0026amp; s) noexcept; 交换两个 fastream,仅交换内部指针、容量、大小。\n示例\nfastream s(32); fastring x(64); s.swap(x); // s: cap -\u0026gt; 64, x: cap -\u0026gt; 32 #——————————— #append 1. fastream\u0026amp; append(const void* s, size_t n); 2. fastream\u0026amp; append(const char* s); 3. fastream\u0026amp; append(const fastring\u0026amp; s); 4. fastream\u0026amp; append(const std::string\u0026amp; s); 5. fastream\u0026amp; append(const fastream\u0026amp; s); 6. fastream\u0026amp; append(size_t n, char c); 7. fastream\u0026amp; append(char c); 8. fastream\u0026amp; append(signed char v) 9. fastream\u0026amp; append(unsigned char c); 10. fastream\u0026amp; append(uint16 v); 11. fastream\u0026amp; append(uint32 v); 12. fastream\u0026amp; append(uint64 v); 1, 追加长度为 n 的字节序列。 2-4, 追加字符串 s。 5, 追加 fastream,s 可以是进行 append 操作的 fastream 对象本身。 6, 追加 n 个字符 c。 7-9, 追加单个字符 c。 10-12, 等价于 append(\u0026amp;v, sizeof(v))。 从 v3.0.1 开始,1-2 参数 s 可以与 fastream 内部内存重叠。\nv3.0.2 移除了 fastream\u0026amp; append(char c, size_t n);。 示例 fastream s; int32 i = 7; char buf[8]; s.append(\u0026#34;xx\u0026#34;); // s -\u0026gt; \u0026#34;xx s.append(s); // 追加自身, s -\u0026gt; \u0026#34;xxxx\u0026#34; s.append(buf, 8); // 追加 8 字节 s.append(\u0026#39;c\u0026#39;); // 追加单个字符 s.append(100, \u0026#39;c\u0026#39;); // 追加 100 个 \u0026#39;c\u0026#39; s.append(\u0026amp;i, 4); // 追加 4 字节 s.append(i); // 追加 4 字节, 与上同 s.append((int16)23); // 追加 2 字节 s.append(s.c_str() + 1); // v3.0.1 支持 #append_nomchk fastream\u0026amp; append_nomchk(const void* s, size_t n); fastream\u0026amp; append_nomchk(const char* s) 与 append() 类似,但不会检查 s 是否与内部内存重叠。 若 s 与 fastream 内部内存可能重叠,则不能使用此方法。 #cat template\u0026lt;typename X, typename ...V\u0026gt; fastream\u0026amp; cat(X\u0026amp;\u0026amp; x, V\u0026amp;\u0026amp; ... v); v2.0.3 新增。将任意数量的元素连接到 fastream 中。\n此方法调用 operator\u0026lt;\u0026lt; 操作,将参数中的元素逐个追加到 fastream 中。\n示例\nfastream s; s \u0026lt;\u0026lt; \u0026#34;hello\u0026#34;; s.cat(\u0026#39; \u0026#39;, 23, \u0026#34;xx\u0026#34;, false); // s -\u0026gt; \u0026#34;hello 23xxfalse\u0026#34; #operator\u0026laquo; fastream\u0026amp; operator\u0026lt;\u0026lt;(bool v); fastream\u0026amp; operator\u0026lt;\u0026lt;(char v); fastream\u0026amp; operator\u0026lt;\u0026lt;(signed char v); fastream\u0026amp; operator\u0026lt;\u0026lt;(unsigned char v); fastream\u0026amp; operator\u0026lt;\u0026lt;(short v); fastream\u0026amp; operator\u0026lt;\u0026lt;(unsigned short v); fastream\u0026amp; operator\u0026lt;\u0026lt;(int v); fastream\u0026amp; operator\u0026lt;\u0026lt;(unsigned int v); fastream\u0026amp; operator\u0026lt;\u0026lt;(long v); fastream\u0026amp; operator\u0026lt;\u0026lt;(unsigned long v); fastream\u0026amp; operator\u0026lt;\u0026lt;(long long v); fastream\u0026amp; operator\u0026lt;\u0026lt;(unsigned long long v); fastream\u0026amp; operator\u0026lt;\u0026lt;(double v); fastream\u0026amp; operator\u0026lt;\u0026lt;(float v); fastream\u0026amp; operator\u0026lt;\u0026lt;(const dp::_fpt\u0026amp; v); fastream\u0026amp; operator\u0026lt;\u0026lt;(const void* v); fastream\u0026amp; operator\u0026lt;\u0026lt;(std::nullptr_t); fastream\u0026amp; operator\u0026lt;\u0026lt;(const char* s); fastream\u0026amp; operator\u0026lt;\u0026lt;(const signed char* s); fastream\u0026amp; operator\u0026lt;\u0026lt;(const unsigned char* s); fastream\u0026amp; operator\u0026lt;\u0026lt;(const fastring\u0026amp; s); fastream\u0026amp; operator\u0026lt;\u0026lt;(const std::string\u0026amp; s); fastream\u0026amp; operator\u0026lt;\u0026lt;(const fastream\u0026amp; s); 将 bool、char、整数类型、浮点数类型、指针类型、字符串类型的值格式化后追加到 fastream 中。\noperator\u0026lt;\u0026lt;(const dp::_fpt\u0026amp;) 用于格式化输出浮点数,可指定有效小数位数。\n示例\nfastream s; s \u0026lt;\u0026lt; \u0026#39;x\u0026#39;; // s -\u0026gt; \u0026#34;x\u0026#34; s \u0026lt;\u0026lt; s; // s -\u0026gt; \u0026#34;xx\u0026#34; (append itself) s \u0026lt;\u0026lt; false; // s -\u0026gt; \u0026#34;xxfalse\u0026#34; s.clear(); s \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; // s -\u0026gt; \u0026#34;hello 23\u0026#34; s \u0026lt;\u0026lt; (s.c_str() + 6); // s -\u0026gt; \u0026#34;hello 2323\u0026#34; (append part of s) s.clear(); s \u0026lt;\u0026lt; 3.1415; // s -\u0026gt; \u0026#34;3.1415\u0026#34; s.clear(); s \u0026lt;\u0026lt; (void*)32; // s -\u0026gt; \u0026#34;0x20\u0026#34; 指定有效小数的位数\ncoost 提供 dp::_1, dp::_2, ..., dp::_16, dp::_n,用于设置浮点数的有效小数位数。\nfastream s; s \u0026lt;\u0026lt; dp::_2(3.1415); // \u0026#34;3.14 s \u0026lt;\u0026lt; dp::_3(3.1415); // \u0026#34;3.141\u0026#34; s \u0026lt;\u0026lt; dp::_n(3.14, 1); // \u0026#34;3.1\u0026#34;, 与 dp::_1(3.14) 等价 co.log 基于 fastream 实现,因此用 co.log 打印日志时,也可以用上述方法控制浮点数的有效小数位数。\nLOG \u0026lt;\u0026lt; dp::_2(3.1415); "},{"id":19,"href":"/cn/co/net/third/","title":"使用三方网络库","section":"网络编程","content":"#协程中使用三方网络库 在协程中使用三方网络库有两种方式:\n直接使用三方网络库的阻塞 API,此方式最简单,依赖于 co 内部的系统 API hook。 使用三方网络库的非阻塞 API,此方式需要借助 co::io_event 将其转换为同步方式。 #系统 API hook 原理 API hook 简单来说就是拦截系统 API 请求,如果该请求是在协程中,且使用 blocking socket,就将 socket 修改成 non-blocking 模式,当 socket 不可读或写时,利用 co::io_event 或 co 中更底层的接口等待 I/O 事件,I/O 事件到来时,再唤醒协程,调用系统原生的 socket API 完成 I/O 操作。\n#使用非阻塞 API 下面是基于 openssl 的非阻塞 API 实现的 recv 方法:\nint recv(S* s, void* buf, int n, int ms) { CHECK(co::sched()) \u0026lt;\u0026lt; \u0026#34;must be called in coroutine..\u0026#34;; int r, e; int fd = SSL_get_fd((SSL*)s); if (fd \u0026lt; 0) return -1; do { ERR_clear_error(); r = SSL_read((SSL*)s, buf, n); if (r \u0026gt; 0) return r; // success if (r == 0) return 0; e = SSL_get_error((SSL*)s, r); if (e == SSL_ERROR_WANT_READ) { co::io_event ev(fd, co::ev_read); if (!ev.wait(ms)) return -1; } else if (e == SSL_ERROR_WANT_WRITE) { co::io_event ev(fd, co::ev_write); if (!ev.wait(ms)) return -1; } else { return r; } } while (true); } 整个过程比较简单,底层使用 non-blocking socket,在 SSL_read 产生 SSL_ERROR_WANT_READ 错误时,用 co::io_event 等待读事件,产生 SSL_ERROR_WANT_WRITE 错误时,用 co::io_event 等待写事件,wait() 正常返回时,表示 socket 可读或可写,继续调用 SSL_read 完成 I/O 操作。\n一般而言,提供非阻塞 I/O 接口的三方网络库,都可以用与上面类似的方法,将非阻塞 API 转换为同步方式。\n"},{"id":20,"href":"/cn/co/concurrency/coroutine/event/","title":"同步事件","section":"协程","content":"include: co/co.h.\n#co::event co::event 是协程间的一种同步机制,它与线程中的 co::sync_event 类似。\n从 v2.0.1 版本开始,co::event 可以在协程与非协程中使用。 #constructor 1. explicit event(bool manual_reset=false, bool signaled=false); 2. event(event\u0026amp;\u0026amp; e); 3. event(const event\u0026amp; e); 1, 与线程中的 co::sync_event 类似。 2, 移动构造函数。 3, 拷贝构造函数,仅将内部引用计数加 1。 #reset void reset() const; 将事件重置为未同步状态。 #signal void signal() const; 产生同步信号,将事件设置成同步状态。 所有 waiting 状态的协程或线程会被唤醒。若当前并没有 waiting 状态的协程或线程,则下一个调用 wait() 方法的协程或线程会立即返回。 #wait 1. void wait() const; 2. bool wait(uint32 ms) const; 1, 等待直到事件变成同步状态。 2, 等待直到事件变成同步状态或超时。参数 ms 指定超时时间,单位为毫秒。若事件变成同步状态,返回 true,否则返回 false。 当构造函数中 manual_reset 为 false 时,wait() 结束时会自动将事件设置成未同步状态。 #代码示例 #include \u0026#34;co/co.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); co::event ev; // capture by value, // as data on stack may be overwritten by other coroutines. go([ev](){ ev.signal(); }); ev.wait(100); // wait for 100 ms return 0; } "},{"id":21,"href":"/cn/about/sponsor/","title":"赞助💕","section":"关于","content":"#赞助 coost 是个人项目,如果您有意向赞助 coost,可以联系 Alvin(idealvin at qq.com),我们将会在这里展示您的 logo、网址等信息。非常感谢。\n我们也提供面向企业用户的付费服务,企业用户可选择支付 7000¥/年或 5000¥/年,即可享受不同程度的 coost 相关的技术支持与咨询服务。 #咖啡 如果您喜欢 coost,也可以考虑给作者来杯咖啡,非常感谢。\n"},{"id":22,"href":"/cn/co/flag/","title":"配置","section":"参考文档","content":"include: co/flag.h.\n#基本概念 co.flag 是一个命令行参数及配置文件解析库,其原理很简单,代码中定义全局变量,然后在程序启动时解析命令行参数或配置文件,修改这些全局变量的值。\n#flag 变量 co.flag 中的宏定义的配置项,实际上是全局变量,称为 flag 变量。如下面的代码定义了一个 flag 变量,变量名是 FLG_x。\nDEF_int32(x, 0, \u0026#34;xxx\u0026#34;); // int32 FLG_x = 0; co.flag 支持 7 种类型的 flag 变量:\nbool, int32, int64, uint32, uint64, double, string 每个 flag 变量都有一个默认值,用户可以通过命令行参数或配置文件修改 flag 变量的值。如前面定义的 FLG_x,在命令行中可以用 -x=23,在配置文件中可以用 x = 23,设置一个新的值。\n#command line flag 命令行参数中,以 -x=y 的形式出现,其中 x 被称为一个 command line flag(以下简称为 flag)。命令行中的 flag x 对应代码中的全局变量 FLG_x,命令行中的 -x=y 就相当于将 FLG_x 的值设置为 y。\n为了方便,本文档中可能将 command line flag、flag 变量统一称为 flag。 co.flag 为了简便易用,设计得非常灵活:\n-x=y 可以省略前面的 -,简写为 x=y.\n-x=y 也可以写成 -x y.\nx=y 前面可以添加任意数量的 -.\nbool 类型的 flag,-b=true 可以简写为 -b.\n示例\n# b, i, s 都是 flag, xx 不是 flag ./exe -b -i=32 -s=hello xx #APIs #flag::parse co::vector\u0026lt;fastring\u0026gt; parse(int argc, char** argv); void parse(const fastring\u0026amp; path); v3.0.1 新增。\n第 1 个 parse 函数,解析命令行参数及配置文件,并更新 flag 变量的值。此函数一般需要在 main 函数开头调用一次。大致流程如下:\n对命令行参数进行预处理,此过程中可能会更新 FLG_config 的值。 如果 FLG_config 非空,解析由它指定的配置文件,更新 flag 变量的值。 解析其他命令行参数,更新 flag 变量的值。 若 FLG_mkconf 为 true,则生成配置文件,并退出程序。 若 FLG_daemon 为 true,则将程序放入后台运行 (仅适用于 linux 平台)。 遇到任何错误时,输出错误信息,并立即退出程序。 若未发生任何错误,返回 non-flag 列表。如执行 ./exe x y 时,此函数将返回 [\u0026quot;x\u0026quot;, \u0026quot;y\u0026quot;]。 第 2 个 parse 函数,解析配置文件,并更新 flag 变量的值。参数 path 是配置文件的路径。遇到错误时,会输出错误信息,并退出程序。\nflag::init\nv3.0.1 中,flag::init() 被标记为 deprecated,请使用 flag::parse()。 示例 #include \u0026#34;co/flag.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); } #flag::set_value fastring set_value(const fastring\u0026amp; name, const fastring\u0026amp; value) v3.0 新增,设置 flag 变量的值,name 是 flag 名。\n此函数非线程安全,一般需要在 main 函数开头调用。\n示例\nDEF_bool(b, false, \u0026#34;\u0026#34;); DEF_int32(i, 0, \u0026#34;\u0026#34;); DEF_string(s, \u0026#34;\u0026#34;, \u0026#34;\u0026#34;); int main(int argc, char** argv) { flag::set_value(\u0026#34;b\u0026#34;, \u0026#34;true\u0026#34;); // FLG_b -\u0026gt; true flag::set_value(\u0026#34;i\u0026#34;, \u0026#34;23\u0026#34;); // FLG_i -\u0026gt; 23 flag::set_value(\u0026#34;s\u0026#34;, \u0026#34;xx\u0026#34;); // FLG_s -\u0026gt; \u0026#34;xx\u0026#34; flag::parse(argc, argv); } #flag::alias bool alias(const char* name, const char* new_name); v3.0 新增,给 flag 取别名,在命令行参数或配置文件中可以用别名取代原名。\n此函数非线程安全,需要在 flag::parse() 之前调用。\n示例\nDEF_bool(all, false, \u0026#34;\u0026#34;); int main(int argc, char** argv) { flag::alias(\u0026#34;all\u0026#34;, \u0026#34;a\u0026#34;); flag::parse(argc, argv); } #代码中使用 flag 变量 #定义 flag 变量 DEF_bool(name, value, help, ...) DEF_int32(name, value, help, ...) DEF_int64(name, value, help, ...) DEF_uint32(name, value, help, ...) DEF_uint64(name, value, help, ...) DEF_double(name, value, help, ...) DEF_string(name, value, help, ...) 上面的 7 个宏,分别用于定义 7 种不同类型的 flag 变量。\n参数 name 是 flag 名,对应的全局变量名是 FLG_name,参数 value 是默认值,参数 help 是注释信息。\nflag 变量是全局变量,一般不要在头文件中定义。\nflag 变量的名字是唯一的,不能定义两个名字相同的 flag 变量。\nflag 变量一般在命名空间之外定义,否则可能无法使用 FLG_name 访问 flag 变量。\n示例\nDEF_bool(b, false, \u0026#34;comments\u0026#34;); // bool FLG_b = false; DEF_int32(i32, 32, \u0026#34;comments\u0026#34;); // int32 FLG_i32 = 32; DEF_int64(i64, 64, \u0026#34;comments\u0026#34;); // int64 FLG_i64 = 64; DEF_uint32(u32, 0, \u0026#34;comments\u0026#34;); // uint32 FLG_u32 = 0; DEF_uint64(u64, 0, \u0026#34;comments\u0026#34;); // uint64 FLG_u64 = 0; DEF_double(d, 2.0, \u0026#34;comments\u0026#34;); // double FLG_d = 2.0; DEF_string(s, \u0026#34;x\u0026#34;, \u0026#34;comments\u0026#34;); // fastring\u0026amp; FLG_s = ...; v3.0.1 中,DEF_string 实际上定义了一个 fastring 类型的引用。 #flag 添加别名 v3.0 新增,定义 flag 变量时,可以为 flag 添加任意数量的别名。\n在命令行或配置文件中,可以用别名取代原名。\n示例\nDEF_bool(debug, false, \u0026#34;\u0026#34;); // no alias DEF_bool(debug, false, \u0026#34;\u0026#34;, d); // d is an alias of debug DEF_bool(debug, false, \u0026#34;\u0026#34;, d, dbg); // 2 aliases #声明 flag 变量 DEC_bool(name) DEC_int32(name) DEC_int64(name) DEC_uint32(name) DEC_uint64(name) DEC_double(name) DEC_string(name) 上面的 7 个宏,分别用于声明 7 种不同类型的 flag 变量。\n参数 name 是 flag 名,对应的全局变量名是 FLG_name。\n一个 flag 变量只能定义一次,但可以声明多次,可以在任何需要的地方声明它们。\nflag 变量一般在命名空间之外声明,否则可能无法使用 FLG_name 访问 flag 变量。\n示例\nDEC_bool(b); // extern bool FLG_b; DEC_int32(i32); // extern int32 FLG_i32; DEC_int64(i64); // extern int64 FLG_i64; DEC_uint32(u32); // extern uint32 FLG_u32; DEC_uint64(u64); // extern uint64 FLG_u64; DEC_double(d); // extern double FLG_d; DEC_string(s); // extern fastring\u0026amp; FLG_s; #使用 flag 变量 定义或声明 flag 变量后,就可以像普通变量一样使用它们:\n#include \u0026#34;co/flag.h\u0026#34; DEC_bool(b); DEF_string(s, \u0026#34;hello\u0026#34;, \u0026#34;xxx\u0026#34;); int main(int argc, char** argv) { flag::parse(argc, argv); if (!FLG_b) std::cout \u0026lt;\u0026lt; \u0026#34;b is false\u0026#34; \u0026lt;\u0026lt; std::endl; FLG_s += \u0026#34; world\u0026#34;; std::cout \u0026lt;\u0026lt; FLG_s \u0026lt;\u0026lt; std::endl; return 0; } #命令行中使用 flag #修改 flag 变量的值 假设程序中定义了如下的 flag:\nDEF_bool(x, false, \u0026#34;bool x\u0026#34;); DEF_bool(y, false, \u0026#34;bool y\u0026#34;); DEF_int32(i, -32, \u0026#34;int32\u0026#34;); DEF_uint64(u, 64, \u0026#34;uint64\u0026#34;); DEF_string(s, \u0026#34;nice\u0026#34;, \u0026#34;string\u0026#34;); 程序启动时,可以通过命令行参数修改 flag 变量的值:\n# -x=y, x=y, -x y, 三者是等价的 ./xx -i 8 -u 88 -s \u0026#34;hello world\u0026#34; ./xx -i=8 u=88 -s=xxx ./xx -i8 # 仅适用于单字母命名的整数类型 flag # bool 类型设置为 true 时, 可以略去值 ./xx -x # -x=true # 多个单字母命名的 bool flag, 可以合并设置为 true ./xx -xy # -x=true -y=true # 整数类型的 flag 可以带单位 k, m, g, t, p, 不区分大小写 ./xx -i -4k # i=-4096 # 整数类型的 flag 可以传 8 进制 或 16 进制数 ./xx i=032 # i=26 8 进制 ./xx u=0xff # u=255 16 进制 #查看帮助信息(\u0026ndash;help) co.flag 支持用 --help 命令查看程序的帮助信息,该命令会显示 usage 信息及用户定义的 flag 列表。\n$ ./xx --help usage: $exe [-flag] [value] $exe -x -i 8k -s ok # x=true, i=8192, s=\u0026#34;ok\u0026#34; $exe -- # print all flags $exe -mkconf # generate config file $exe -conf xx.conf # run with config file flags: -n int32 type: int32 default: 0 from: test/flag.cc -s string type: string default: \u0026#34;hello world\u0026#34; from: test/flag.cc #查看 flag 列表(\u0026ndash;) co.flag 可以用 -- 命令查看程序中定义的全部 flag 列表(包括co内部定义的flags):\n$ ./xx -- flags: -boo bool flag type: bool default: false from: test/flag.cc -co_sched_num number of coroutine schedulers, default: os::cpunum() type: uint32 default: os::cpunum() from: src/co/sched.cc #查看程序版本信息 version 是 coost 内部定义的 flag,命令行中可以使用 -version 命令查看版本信息。\nversion 默认值为空,用户需要在调用 flag::parse() 前,修改其值。\n示例\n#include \u0026#34;co/flag.h\u0026#34; int main(int argc, char** argv) { FLG_version = \u0026#34;v3.0.0\u0026#34;; flag::parse(argc, argv); return 0; } $ ./xx -version v3.0.0 #配置文件 #配置文件格式 co.flag 的配置文件格式比较灵活:\n一行一个配置项,每个配置项对应一个 flag,形式统一为 x = y,看起来一目了然。\n# 或 // 表示注释,支持行尾注释。\n引号中的 # 或 // 不是注释。\n忽略行前、行尾的空白字符,书写更自由,不容易出错。\n= 号前后可以任意添加空白字符,书写更自由。\n可以用 \\ 续行,以免一行太长,影响美观。\n字符串不支持转义,以免产生歧义。\n字符串可以用双引号、单引号或 3个反引号括起来。\n配置文件示例\n# config file: xx.conf boo = true # bool 类型 s = # 空字符串 s = hello \\ world # s = \u0026#34;helloworld\u0026#34; s = \u0026#34;http://github.com\u0026#34; # 引号中的 # 与 // 不是注释 s = \u0026#34;I\u0026#39;m ok\u0026#34; # 字符串中含有单引号,两端可以用双引号括起来 s = \u0026#39;how are \u0026#34;U\u0026#34;\u0026#39; # 字符串中含有双引号,两端可以用单引号括起来 s = ```I\u0026#39;m \u0026#34;ok\u0026#34;``` # 字符串两端也可以用 3 个反引号括起来 i32 = 4k # 4096, 整型可以带单位 k,m,g,t,p, 不区分大小写 i32 = 032 # 8 进制, i32 = 26 i32 = 0xff # 16 进制, i32 = 255 pi = 3.14159 # double 类型 #自动生成配置文件 mkconf 是 coost 内部定义的 flag,它是自动生成配置文件的开关。 命令行中可以用 -mkconf 自动生成配置文件。 ./xx -mkconf # 在 xx 所在目录生成 xx.conf ./xx -mkconf -x u=88 # 自定义配置项的值 #调整配置项的顺序 自动生成的配置文件中,配置项按 flag 级别、所在文件名、所在代码行数进行排序。如果用户想让某些配置项的排序靠前些,可以将 flag 的级别设成较小的值,反之可以将 flag 级别设成较大的值。\n定义 flag 时可以在注释开头用 #n 指定级别,n 必须是 0 到 9 之间的整数,若注释非空,n 后面必须有一个空格。不指定时,默认 flag 级别为 5。\nDEF_bool(x, false, \u0026#34;comments\u0026#34;); // 默认级别为 5 DEF_bool(y, false, \u0026#34;#3\u0026#34;); // 级别为 3, 注释为空 DEF_bool(z, false, \u0026#34;#3 comments\u0026#34;); // 级别为 3, 注释非空, 3 后面必须有一个空格 #禁止配置项生成到配置文件 注释以 . 开头的 flag,带有隐藏属性,不会生成到配置文件中,但用 -- 命令可以查看。注释为空的 flag,则是完全不可见的,既不会生成到配置文件中,也不能用 -- 命令查看。\nDEF_bool(x, false, \u0026#34;.say something here\u0026#34;); DEF_string(s, \u0026#34;good\u0026#34;, \u0026#34;\u0026#34;); #程序启动时指定配置文件 DEF_string(config, \u0026#34;\u0026#34;, \u0026#34;.path of config file\u0026#34;, conf); config 是 coost 内部定义的 flag,表示配置文件的路径,它有一个别名 conf。 命令行中可以用 -config 或 -conf 指定配置文件。 代码中可以在调用 flag::parse() 之前,修改 FLG_config 的值,以指定配置文件。 ./xx -config xx.conf ./xx -conf xx.conf # 若配置文件名以 .conf 或 config 结尾, 且是命令行的 # 第一个 non-flag 参数, 则可省略 -config ./xx xx.conf ./xx xx.conf -x #自定义帮助信息 help 是 coost 内部定义的 flag,命令行中可以使用 --help 命令查看帮助信息。\nFLG_help 默认为空,使用 coost 内部提供的默认帮助信息。\n用户想自定义帮助信息时,可以在调用 flag::parse() 前,修改 FLG_help 的值。\n示例\n#include \u0026#34;co/flag.h\u0026#34; int main(int argc, char** argv) { FLG_help \u0026lt;\u0026lt; \u0026#34;usage:\\n\u0026#34; \u0026lt;\u0026lt; \u0026#34;\\t./xx -ip 127.0.0.1 -port 7777\\n\u0026#34;; flag::parse(argc, argv); return 0; } #让程序在后台运行 daemon 是 coost 内部定义的 flag,若为 true,程序将在后台运行,仅支持 linux 平台。\n命令行中可以用 -daemon 指定程序以 daemon 形式在后台运行。\n示例\n./xx -daemon "},{"id":23,"href":"/cn/co/concurrency/coroutine/io_event/","title":"IO 事件","section":"协程","content":"include: co/co.h.\n#co::_ev_t enum _ev_t { ev_read = 1, ev_write = 2, }; I/O 事件类型,ev_read 表示读,ev_write 表示写。 #co::io_event co::io_event 用于将非阻塞 I/O 转换为同步方式。用户在协程中对一个 non-blocking socket 进行 I/O 操作,当 socket 不可读或不可写时,用户调用 co::io_event 的 wait() 方法挂起协程,等待 I/O 事件;当 socket 变为可读或可写时,调度线程唤醒该协程,继续 I/O 操作。\n#constructor 1. io_event(sock_t fd, _ev_t ev); 2. io_event(sock_t fd, int n=0); // for windows only 1, 参数 fd 是一个 non-blocking socket,参数 ev 是 ev_read 或 ev_write 中的一种。调用 wait() 方法会在 socket 上等待 ev 指定的 I/O 事件,wait() 成功返回时,需要用户调用 recv, send 等函数完成 I/O 操作。在 windows 平台,fd 必须是 TCP socket(对于 UDP,很难用 IOCP 模拟 epoll 或 kqueue 的行为)。 2, 仅适用于 windows,fd 可以是 UDP socket,但用户需要手动调用 WSARecvFrom, WSASendTo 等函数向 IOCP 发送 overlapped I/O 请求,然后调用 wait() 方法,当 wait() 成功返回时,表示 IOCP 已经帮用户完成了 I/O 操作。具体的用法此处不详述,代码中有详细的注释,建议直接参考 co::io_event 的源码,以及 windows 上 co::accept, co::connect, co::recvfrom, co::sendto 的实现。 #destructor ~io_event(); 析构函数,从 epoll 或 kqueue 中移除之前注册的 I/O 事件。 #wait bool wait(uint32 ms=-1); 此方法等待 socket 上的 I/O 事件,参数 ms 是超时时间,单位为毫秒,默认为 -1,永不超时。 此方法阻塞,直到 I/O 事件到来,或者超时、发生错误。 此方法成功时返回 true,超时或发生错误时返回 false。 #代码示例 int recv(sock_t fd, void* buf, int n, int ms) { const auto sched = xx::gSched; CHECK(sched) \u0026lt;\u0026lt; \u0026#34;must be called in coroutine..\u0026#34;; co::io_event ev(fd, ev_read); do { int r = (int) __sys_api(recv)(fd, buf, n, 0); if (r != -1) return r; if (errno == EWOULDBLOCK || errno == EAGAIN) { if (!ev.wait(ms)) return -1; } else if (errno != EINTR) { return -1; } } while (true); } 上面的例子是 co::recv 的实现,调用原生 recv 方法产生 EWOULDBLOCK 或 EAGAIN 错误时,用 co::io_event 等待读事件,wait() 正常返回时表示 socket 可读,继续调用原生 recv 方法完成读操作。\n"},{"id":24,"href":"/cn/co/other/stl/","title":"STL","section":"其他","content":"include: co/stl.h.\n#常用容器 下表中,左边 coost 中的容器与右边相应的 std 版本是等价的,仅内部的内存分配器不同。\ncoost std co::deque std::deque co::list std::list co::map std::map co::set std::set co::multimap std::multimap co::multiset std::multiset co::hash_map std::unordered_map co::hash_set std::unordered_set 当 key 为 const char* 类型(即 C 风格字符串)时,co::map, co::set, co::multimap, co::multiset, co::hash_map, co::hash_set 会根据字符串内容比较 key 及计算 key 的 hash 值。 #co::lru_map template\u0026lt;typename K, typename V\u0026gt; class lru_map; co::lru_map 是基于 LRU (least recently used) 策略实现的 map,当 map 中元素数量达到上限时,优先替换掉最近最少使用的数据。它基于 co::hash_map 与 co::list 实现,内部元素是无序的。\n#constructor 1. lru_map(); 2. explicit lru_map(size_t capacity); 1, 默认构造函数,使用 1024 作为最大容量。 2, 使用参数 capacity 作为最大容量。 #begin iterator begin() const; 返回指向第一个元素的 iterator,当 lru_map 为空时,返回值与 end() 相等。 #clear void clear(); 清空 lru_map 内的元素,size 会变成 0,容量保持不变。 #empty bool empty() const; 判断 lru_map 是否为空。 #end iterator end() const; 返回指向最后一个元素的下一个位置的 iterator,它本身并不指向任何元素。 #erase void erase(iterator it); void erase(const key_type\u0026amp; key); 通过 iterator 或 key 删除元素。 #find iterator find(const key_type\u0026amp; key) 查找 key 对应的元素。 #insert template\u0026lt;typename Key, typename Val\u0026gt; void insert(Key\u0026amp;\u0026amp; key, Val\u0026amp;\u0026amp; value); 插入元素,仅当 key 不存在时,才会插入新元素。若 key 已经存在,则不会进行任何操作。 插入元素时,若元素数量已经达到最大容量,则会删除最近最少访问的元素。 #size size_t size() const; 返回 lru_map 中的元素个数。 #swap void swap(lru_map\u0026amp; x) noexcept; void swap(lru_map\u0026amp;\u0026amp; x) noexcept; 交换两个 lru_map 的内容,此操作仅交换内部指针、大小、容量等信息。 #代码示例 co::lru_map\u0026lt;int, int\u0026gt; m(128); // capacity: 128 auto it = m.find(1); if (it == m.end()) { m.insert(1, 23); } else { it-\u0026gt;second = 23; } m.erase(it); // erase by iterator m.erase(1); // erase by key m.clear(); // clear the map #co::vector C++ 标准库中 std::vector\u0026lt;bool\u0026gt; 是个不太明智的设计,为此,coost 单独实现了 co::vector。\n#constructor 1. constexpr vector() noexcept; 2. explicit vector(size_t cap); 3. vector(const vector\u0026amp; x); 4. vector(vector\u0026amp;\u0026amp; x) noexcept; 5. vector(std::initializer_list\u0026lt;T\u0026gt; x); 6. vector(T* p, size_t n); 7. template\u0026lt;typename It\u0026gt; vector(It beg, It end); 8. template\u0026lt;typename X\u0026gt; vector(size_t n, X\u0026amp;\u0026amp; x); 1, 默认构造函数,构造一个空的 vector,size 与 capacity 均为 0。 2, 构造一个空的 vector,capacity 为 cap。 3, 拷贝构造函数。 4, 移动构造函数,构造完后,x 变成空对象。 5, 用初始化列表构造 vector 对象,T 是 vector 中元素的类型。 6, 用数组构造 vector 对象,p 指向数组第一个元素,n 是数组长度。 7, 用 [beg, end) 范围内的元素构造 vector 对象。 8, 有两种情况:X 不是 int 或元素类型 T 是 int 时,vector 初始化为 n 个 x;X 是 int 且元素类型 T 不是 int 时,vector 初始化为 n 个 T 类型的默认值。 构造函数 2 与 std::vector 中相应版本的行为是不同的,若要构建 n 个默认值构成的 vector,可以使用构造函数 8(第 2 个参数传 0):\nco::vector\u0026lt;fastring\u0026gt; v(32, 0); 示例 co::vector\u0026lt;int\u0026gt; a(32); // size: 0, capacity: 32 co::vector\u0026lt;int\u0026gt; b = { 1, 2 }; // [1,2] co::vector\u0026lt;int\u0026gt; v(8, 0); // 包含 8 个 0 co::vector\u0026lt;fastring\u0026gt; s(8, 0); // 包含 8 个空的 fastring 对象 #destructor ~vector(); 释放内存,析构后 vector 变为空对象。 #operator= 1. vector\u0026amp; operator=(const vector\u0026amp; x); 2. vector\u0026amp; operator=(vector\u0026amp;\u0026amp; x); 3. vector\u0026amp; operator=(std::initializer_list\u0026lt;T\u0026gt; x); 赋值操作。\n示例\nco::vector\u0026lt;int\u0026gt; v; v = { 1, 2, 3 }; co::vector\u0026lt;int\u0026gt; x; x = v; x = std::move(v); #——————————— #back T\u0026amp; back(); const T\u0026amp; back() const; 返回 vector 中最后一个元素的引用。 若 vector 为空,调用此方法会导致未定义的行为。 #front T\u0026amp; front(); const T\u0026amp; front() const; 返回 vector 第一个元素的引用。 若 vector 为空,调用此方法会导致未定义的行为。 #operator[] T\u0026amp; operator[](size_t n); const T\u0026amp; operator[](size_t n) const; 返回 vector 中第 n 个元素的引用。 若 n 超出合理的范围,调用此方法会导致未定义的行为。 #——————————— #begin iterator begin() const noexcept; 返回指向第一个元素的 iterator。 #end iterator end() const noexcept; 返回 end iterator。 #——————————— #capacity size_t capacity() const noexcept; 返回 vector 的容量。 #data T* data() const noexcept; 返回指向 vector 内部元素的指针。 #empty bool empty() const noexcept; 判断 vector 是否为空。 #size size_t size() const noexcept; 返回 vector 中元素个数。 #——————————— #clear void clear(); 清空 vector,size 变为 0,capacity 不变。 #reserve void reserve(size_t n); 调整 vector 的容量,确保容量至少是 n。 当 n 小于原来的容量时,则保持容量不变。 #reset void reset(); 销毁 vector 中所有元素,并释放内存。 #resize void resize(size_t n); 将 vector 的 size 调整为 n。 当 n 大于原来的 size 时,用默认元素值填充扩展的部分。 #swap void swap(vector\u0026amp; x) noexcept; void swap(vector\u0026amp;\u0026amp; x) noexcept; 交换两个 vector,仅交换内部指针、容量、大小。 #——————————— #append 1. void append(const T\u0026amp; x); 2. void append(T\u0026amp;\u0026amp; x); 3. void append(size_t n, const T\u0026amp; x); 4. void append(const T* p, size_t n); 5. void append(const vector\u0026amp; x); 6. void append(vector\u0026amp;\u0026amp; x); 7. template\u0026lt;typename It\u0026gt; void append(It beg, It end); 添加元素到 vector 尾部。\n1-2, 追加单个元素。\n3, 追加 n 个元素 x。\n4, 追加数组,n 是数组长度。\n5-6, 追加 vector x 中的所有元素。\n7, 追加 [beg, end) 范围内的元素。\n示例\nint a[4] = { 1, 2, 3, 4 }; co::vector\u0026lt;int\u0026gt; v; v.append(7); v.append(v); v.append(a, 4); #emplace_back template\u0026lt;typename ... X\u0026gt; void emplace_back(X\u0026amp;\u0026amp; ... x); 在 vector 尾部插入新元素,该元素由参数 x... 构建。\n示例\nco::vector\u0026lt;fastring\u0026gt; v; v.emplace_back(4, \u0026#39;x\u0026#39;); // append(\u0026#34;xxxx\u0026#34;) #push_back void push_back(const T\u0026amp; x); void push_back(T\u0026amp;\u0026amp; x); 添加元素 x 到 vector 尾部。 #pop_back T pop_back(); 取出并返回 vector 尾部的元素。 若 vector 为空,调用此方法会导致未定义的行为。 #remove void remove(size_t n); 删除第 n 个元素,并将最后一个元素移动到所删除元素的位置。\n示例\nco::vector\u0026lt;int\u0026gt; v = { 1, 2, 3, 4 }; v.remove(1); // v -\u0026gt; [1, 4, 3] v.remove(2); // v -\u0026gt; [1, 4] #remove_back void remove_back(); 删除 vector 尾部的元素,vector 为空时不进行任何操作。 "},{"id":25,"href":"/cn/co/net/tcp/","title":"TCP","section":"网络编程","content":"include: co/tcp.h.\n#tcp::Connection tcp::Connection 类是对 TCP 连接的简单封装,用于实现 TCP server,客户端不需要用这个类。当服务端启用 SSL 时,tcp::Connection 会用 SSL 传输数据。\n#Connection::Connection Connection(int sock); Connection(void* ssl); Connection(Connection\u0026amp;\u0026amp; c); 构造函数,Connection 由 tcp::Server 创建,用户不需要手动创建。 第 1 个版本构造一般的 TCP 连接,第 2 个版本构造使用 SSL 传输数据的 TCP 连接,第 3 个是移动构造函数。 从 v2.0.2 开始,用户不能继承 Connection 类。 #Connection::~Connection Connection::~Connection(); 析构函数,调用 close() 关闭连接。 #Connection::close int close(int ms = 0); 关闭连接,参数 ms \u0026gt; 0 时,延迟一段时间再关闭连接。 从 v2.0.1 开始,此方法可以在协程或非协程中调用。 #Connection::recv int recv(void* buf, int n, int ms=-1); 接收数据,与 co::recv 类似。 此方法必须在协程中调用。 此方法成功时返回值 \u0026gt;0,超时或发生错误时返回值 \u0026lt;0,对端关闭连接时返回 0。 #Connection::recvn int recvn(void* buf, int n, int ms=-1); 接收指定长度的数据,与 co::recvn 类似。 此方法成功时返回 n,超时或发生错误时返回值 \u0026lt;0,对端关闭连接时返回 0。 #Connection::reset int reset(int ms = 0) 重置 TCP 连接,与 close() 不同,它不会进入 TIME_WAIT 状态。参数 ms \u0026gt; 0 时,延迟一段时间再重置连接。 此方法必须在 I/O 线程(一般是进行 I/O 操作的协程)中调用。 #Connection::send int send(const void* buf, int n, int ms=-1); 发送数据,与 co::send() 类似。 此方法成功时返回 n,超时或发生错误时返回值 \u0026lt;=0。 #Connection::socket int socket() const; 返回内部的 socket 描述符,连接已关闭时返回 -1。 #Connection::strerror const char* strerror() const; Connection 中的方法报错时,可以调用此方法查看错误信息。 #tcp::Server tcp::Server 是基于协程的 TCP 服务端,它的特性如下:\n支持 IPv4 与 IPv6。 支持 SSL (需要 openssl)。 采用一个连接一个协程的模型。 #Server::Server Server(); 构造函数,初始化。 #Server::conn_num uint32 conn_num() const; 返回当前的客户端连接数。 #Server::on_connection Server\u0026amp; on_connection(std::function\u0026lt;void(Connection)\u0026gt;\u0026amp;\u0026amp; f); Server\u0026amp; on_connection(const std::function\u0026lt;void(Connection)\u0026gt;\u0026amp; f); template\u0026lt;typename T\u0026gt; Server\u0026amp; on_connection(void (T::*f)(Connection), T* o); 设置处理客户端连接的回调函数。\n第 1, 2 个版本中,参数 f 是 void f(Connection) 类型的函数,或 std::function\u0026lt;void(Connection)\u0026gt; 类型的函数对象。\n第 3 个版本中,参数 f 是类中的方法,参数 o 是 T 类型的指针。\n从 v2.0.2 开始,f 的参数改为 tcp::Connection 对象,而非指针,用户不需要手动 delete。\n服务端接收到新的客户端连接时,会新建一个协程,并在协程中调用此方法设置的回调函数,处理新连接上的数据。\n示例\nvoid f(tcp::Connection conn); tcp::Server s; s.on_connection(f); void f(tcp::Connection conn) { while (true) { conn.recv(...); process(...); conn.send(...); } conn.close(); } #Server::on_exit Server\u0026amp; on_exit(std::function\u0026lt;void()\u0026gt;\u0026amp;\u0026amp; cb); 设置一个 callback,它将在 server 退出时被调用。 #Server::start void start(const char* ip, int port, const char* key=0, const char* ca=0); 启动 TCP server,此方法不会阻塞当前线程。\n参数 ip 是服务器 ip,可以是 IPv4 或 IPv6 地址,参数 port 是服务器端口。\n参数 key 是存放 SSL private key 的 PEM 文件路径,参数 ca 是存放 SSL 证书的 PEM 文件路径,默认 key 和 ca 是 NULL,不启用 SSL。\n从 v3.0 开始,server 启动后就不再依赖于 tcp::Server 对象。\n示例\nvoid f(tcp::Connection conn); tcp::Server().on_connection(f).start(\u0026#34;0.0.0.0\u0026#34;, 7788); #Server::exit void exit(); v2.0.2 新增。 退出 TCP server,关闭 listening socket,不再接收新的连接。 此方法不会关闭之前已经建立的连接。 若需要在 server 退出后,关闭之前建立的连接,可以参考 test/tcp2.cc 或 co 中 http::Server 与 rpc::Server 的实现。 #tcp::Client tcp::Client 是基于协程的 TCP 客户端,它有如下特性:\n支持 IPv4 与 IPv6。 支持 SSL (需要安装 openssl)。 一个客户端对象,对应一个连接。 它必须在协程中使用。 它不是协程安全的,同一时刻,不能有多个协程对它进行操作。 #Client::Client Client(const char* ip, int port, bool use_ssl=false); Client(const Client\u0026amp; c); 构造函数。参数 ip 是服务端 ip,可以是域名、IPv4 或 IPv6 地址;参数 port 是服务端口;参数 use_ssl 表示是否启用 SSL 传输,默认为 false。 第 2 个版本是拷贝构造函数,仅拷贝 ip, port, use_ssl。 tcp::Client 构建时,并没有建立连接。 一般建议在调用 recv, send 前,判断连接是否建立,没有的话就调用 connect() 方法建立连接,这种方式可以实现自动重连。 #Client::~Client Client::~Client(); 析构函数,调用 disconnect() 方法关闭连接。 #Client::close void close(); 关闭连接,与 disconnect() 相同。 #Client::connect bool connect(int ms); 建立连接,参数 ms 是超时时间,单位为毫秒。 此方法必须在协程中调用。 此方法成功时返回 true,否则返回 false。失败时,用户可以调用 strerror() 方法查看错误信息。 #Client::connected bool connected() const; 判断是否已经建立连接。 #Client::disconnect void disconnect(); 从 v2.0.1 开始,可以在协程或非协程中调用。 多次调用此方法是安全的,析构函数中会自动调用此方法。 #Client::recv int recv(void* buf, int n, int ms=-1); 接收数据,与 co::recv() 类似。 此方法必须在协程中调用。 此方法成功时返回值 \u0026gt;0,超时或发生错误时返回值 \u0026lt;0,对端关闭连接时返回 0。 #Client::recvn int recvn(void* buf, int n, int ms=-1); 接收指定长度的数据,与 co::recvn() 类似。 此方法必须在协程中调用。 此方法成功时返回 n,超时或发生错误时返回值 \u0026lt;0,对端关闭连接时返回 0。 #Client::send int send(const void* buf, int n, int ms=-1); 发送数据,与 co::send() 类似。 此方法必须在协程中调用。 此方法成功时返回 n,超时或发生错误时返回值 \u0026lt;=0。 #Client::socket int socket() const; 返回内部的 socket 描述符。 未建立连接或连接已经关闭时,返回值是 -1。 #Client::strerror const char* strerror() const; tcp::Client 中的方法报错时,可以调用此方法查看错误信息。 #TCP 服务端代码示例 void on_connection(tcp::Connection conn) { char buf[8] = { 0 }; while (true) { int r = conn.recv(buf, 8); if (r == 0) { /* client close the connection */ conn.close(); break; } else if (r \u0026lt; 0) { /* error */ conn.reset(3000); break; } else { LOG \u0026lt;\u0026lt; \u0026#34;server recv \u0026#34; \u0026lt;\u0026lt; fastring(buf, r); LOG \u0026lt;\u0026lt; \u0026#34;server send pong\u0026#34;; r = conn.send(\u0026#34;pong\u0026#34;, 4); if (r \u0026lt;= 0) { LOG \u0026lt;\u0026lt; \u0026#34;server send error: \u0026#34; \u0026lt;\u0026lt; conn.strerror(); conn.reset(3000); break; } } } } tcp::Server s; s.on_connection(on_connection); s.start(\u0026#34;0.0.0.0\u0026#34;, 7788); // no ssl s.start(\u0026#34;0.0.0.0\u0026#34;, 7788, \u0026#34;privkey.pem\u0026#34;, \u0026#34;certificate.pem\u0026#34;); // use ssl 上面的例子实现了一个简单的 ping-pong server,收到客户端发送的 ping 时,回复一个 pong。 #TCP 客户端代码示例 bool use_ssl = false; std::unique_ptr\u0026lt;tcp::Client\u0026gt; proto; co::pool pool( []() {return (void*) new tcp::Client(*proto); }, [](void* p) {delete (tcp::Client*) p;} ); void client_fun() { co::pool_guard\u0026lt;tcp::Client\u0026gt; c(pool); if (!c-\u0026gt;connect(3000)) { LOG \u0026lt;\u0026lt; \u0026#34;connect failed: \u0026#34;\u0026lt;\u0026lt; c-\u0026gt;strerror(); return; } char buf[8] = {0 }; while (true) { LOG \u0026lt;\u0026lt; \u0026#34;client send ping\u0026#34;; int r = c-\u0026gt;send(\u0026#34;ping\u0026#34;, 4); if (r \u0026lt;= 0) { LOG \u0026lt;\u0026lt; \u0026#34;client send error: \u0026#34;\u0026lt;\u0026lt; c-\u0026gt;strerror(); break; } r = c-\u0026gt;recv(buf, 8); if (r \u0026lt; 0) { LOG \u0026lt;\u0026lt; \u0026#34;client recv error: \u0026#34;\u0026lt;\u0026lt; c-\u0026gt;strerror(); break; } else if (r == 0) { LOG \u0026lt;\u0026lt; \u0026#34;server close the connection\u0026#34;; break; } else { LOG \u0026lt;\u0026lt; \u0026#34;client recv \u0026#34;\u0026lt;\u0026lt; fastring(buf, r) \u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; co::sleep(3000); } } } proto.reset(new tcp::Client(\u0026#34;127.0.0.1\u0026#34;, 7788, use_ssl)); for (int i = 0; i \u0026lt;8; ++i) { go(client_fun); } 上面的例子中,我们用 co::pool 缓存客户端连接,不同协程可以共用 pool 中的连接。 "},{"id":26,"href":"/cn/co/log/","title":"日志","section":"参考文档","content":"include: co/log.h.\n#简介 co.log 是 coost 提供的高性能日志库,支持两种类型的日志,level log 与 topic log(TLOG),它像下面这样打印日志:\nLOG \u0026lt;\u0026lt; \u0026#34;hello world\u0026#34; \u0026lt;\u0026lt; 23; // level log TLOG(\u0026#34;topic\u0026#34;) \u0026lt;\u0026lt; \u0026#34;hello\u0026#34; \u0026lt;\u0026lt; 23; // topic log #Level Log Level log 分为 debug, info, warning, error, fatal 5 个级别,并提供一系列的宏,用于打印不同级别的日志。\n打印 fatal 级别的日志会终止程序的运行,co.log 还会试图在程序退出前打印函数调用栈信息,以方便排查程序崩溃的原因。 此类型的日志会写入同一个文件中,一般用于打印调试信息。\n#Topic Log Topic log(TLOG) 没有级别之分,而是按主题分类。\n此类型的日志,按主题写入不同的文件,一般用于打印业务日志,按业务功能划分,便于日志管理。\n#性能 co.log 内部采用异步的实现方式,日志先写入缓存,达到一定量或超过一定时间后,由后台线程一次性写入文件,性能在不同平台比 glog 提升了 20~150 倍左右。下表是不同平台单线程连续打印 100 万条(每条 50 字节左右) info 日志的测试结果:\nplatform glog co.log win2012 HHD 1.6MB/s 180MB/s win10 SSD 3.7MB/s 560MB/s mac SSD 17MB/s 450MB/s linux SSD 54MB/s 1023MB/s #APIs #log::exit void exit(); 将缓存中的日志写入文件,并退出后台写日志的线程。 程序正常退出时,co.log 会自动调用此函数。 多次调用此函数是安全的。 co.log 内部捕获到 SIGINT, SIGTERM, SIGQUIT 等信号时,会在程序退出前调用此函数。 #log::set_write_cb void set_write_cb( const std::function\u0026lt;void(const void*, size_t)\u0026gt;\u0026amp; cb, int flags=0 ); void set_write_cb( const std::function\u0026lt;void(const char*, const void*, size_t)\u0026gt;\u0026amp; cb, int flags=0 ); co.log 默认将日志写到本地文件,用户可以调用此 API 自定义写日志的 callback,将日志写到不同的目标。 参数 cb 是 callback。第 1 个版本用于 Level log,cb 带 2 个参数,表示日志 buffer 的地址及长度;第 2 个版本用于 Topic log(TLOG),cb 带 3 个参数,第 1 个参数是 topic,后两个参数与第 1 个版本相同。buffer 中可能含有多条日志。 参数 flags 是 v3.0 新增,默认为 0,可以是下述选项的组合: log::log2local,在本地也写一份日志。 #v3.0 删除的 API log::init,v3.0 移除,从 v3.0 开始,一般只需要在 main 函数开头调用 flag::parse(argc, argv)。\nlog::set_single_write_cb,v3.0 移除。\nlog::close,v3.0 移除,使用 log::exit() 取代之。\n#Level Log #基本用法 DLOG LOG WLOG ELOG FLOG 上面的 5 个宏分别用于打印 5 种不同级别的日志,它们是线程安全的。\n这些宏实际上是 fastream 的引用,因此可以打印 fastream::operator\u0026lt;\u0026lt; 支持的任何类型。\n这些宏会自动在每条日志末尾加上 \u0026lsquo;\\n\u0026rsquo; 换行符,用户无需手动输入换行符。\n前 4 种仅在 FLG_min_log_level 不大于当前日志级别时,才会打印日志,用户可以将 FLG_min_log_level 的值设置大一些,以屏蔽低级别的日志。\n打印 fatal 级别的日志,表示程序出现了致命错误,coost 会打印当前线程的函数调用栈信息,并终止程序的运行。\n示例\nDLOG \u0026lt;\u0026lt; \u0026#34;this is DEBUG log \u0026#34; \u0026lt;\u0026lt; 23; LOG \u0026lt;\u0026lt; \u0026#34;this is INFO log \u0026#34; \u0026lt;\u0026lt; 23; WLOG \u0026lt;\u0026lt; \u0026#34;this is WARNING log \u0026#34; \u0026lt;\u0026lt; 23; ELOG \u0026lt;\u0026lt; \u0026#34;this is ERROR log \u0026#34; \u0026lt;\u0026lt; 23; FLOG \u0026lt;\u0026lt; \u0026#34;this is FATAL log \u0026#34; \u0026lt;\u0026lt; 23; #条件日志 #define DLOG_IF(cond) if (cond) DLOG #define LOG_IF(cond) if (cond) LOG #define WLOG_IF(cond) if (cond) WLOG #define ELOG_IF(cond) if (cond) ELOG #define FLOG_IF(cond) if (cond) FLOG 上面的 5 个宏,接受一个条件参数 cond,当 cond 为 true 时才打印日志。 参数 cond 可以是值为 bool 类型的任意表达式。 由于条件判断在前面,即使相应级别的日志被屏蔽掉,这些宏也会保证 cond 表达式被执行。 示例 int s = socket(); DLOG_IF(s != -1) \u0026lt;\u0026lt; \u0026#34;create socket ok: \u0026#34; \u0026lt;\u0026lt; s; LOG_IF(s != -1) \u0026lt;\u0026lt; \u0026#34;create socket ok: \u0026#34; \u0026lt;\u0026lt; s; WLOG_IF(s == -1) \u0026lt;\u0026lt; \u0026#34;create socket ko: \u0026#34; \u0026lt;\u0026lt; s; ELOG_IF(s == -1) \u0026lt;\u0026lt; \u0026#34;create socket ko: \u0026#34; \u0026lt;\u0026lt; s; FLOG_IF(s == -1) \u0026lt;\u0026lt; \u0026#34;create socket ko: \u0026#34; \u0026lt;\u0026lt; s; #每 N 条打印一次日志 #define DLOG_EVERY_N(n) _LOG_EVERY_N(n, DLOG) #define LOG_EVERY_N(n) _LOG_EVERY_N(n, LOG) #define WLOG_EVERY_N(n) _LOG_EVERY_N(n, WLOG) #define ELOG_EVERY_N(n) _LOG_EVERY_N(n, ELOG) 上面的宏每 n 条打印一次日志。\n参数 n 必须是大于 0 的整数。\n第 1 条日志始终会被打印,之后每隔 n 条打印一次。\nfatal 日志一打印,程序就会终止运行,因此没有提供 FLOG_EVERY_N。\n示例\n// 每 32 条打印一次 (1,33,65...) DLOG_EVERY_N(32) \u0026lt;\u0026lt; \u0026#34;this is DEBUG log \u0026#34; \u0026lt;\u0026lt; 23; LOG_EVERY_N(32) \u0026lt;\u0026lt; \u0026#34;this is INFO log \u0026#34; \u0026lt;\u0026lt; 23; WLOG_EVERY_N(32) \u0026lt;\u0026lt; \u0026#34;this is WARNING log \u0026#34; \u0026lt;\u0026lt; 23; ELOG_EVERY_N(32) \u0026lt;\u0026lt; \u0026#34;this is ERROR log \u0026#34; \u0026lt;\u0026lt; 23; #打印前 N 条日志 #define DLOG_FIRST_N(n) _LOG_FIRST_N(n, DLOG) #define LOG_FIRST_N(n) _LOG_FIRST_N(n, LOG) #define WLOG_FIRST_N(n) _LOG_FIRST_N(n, WLOG) #define ELOG_FIRST_N(n) _LOG_FIRST_N(n, ELOG) 上面的宏打印前 n 条日志。\n参数 n 是不小于 0 的整数(等于 0 时不会打印日志),一般不要超过 int 类型的最大值。\n参数 n 一般不要用复杂的表达式,因为表达式 n 可能被执行两次。\nfatal 日志一打印,程序就会终止运行,因此没有提供 FLOG_FIRST_N。\n示例\n// 打印前 10 条日志 DLOG_FIRST_N(10) \u0026lt;\u0026lt; \u0026#34;this is DEBUG log \u0026#34; \u0026lt;\u0026lt; 23; LOG_FIRST_N(10) \u0026lt;\u0026lt; \u0026#34;this is INFO log \u0026#34; \u0026lt;\u0026lt; 23; WLOG_FIRST_N(10) \u0026lt;\u0026lt; \u0026#34;this is WARNING log \u0026#34; \u0026lt;\u0026lt; 23; ELOG_FIRST_N(10) \u0026lt;\u0026lt; \u0026#34;this is ERROR log \u0026#34; \u0026lt;\u0026lt; 23; #TLOG #define TLOG(topic) #define TLOG_IF(topic, cond) if (cond) TLOG(topic) TLOG 宏带有一个参数 topic,它是一个 C 风格的字符串,需要具备静态生命周期。\nTLOG_IF 宏仅当 cond 条件成立时,才打印日志。\n示例\nTLOG(\u0026#34;xx\u0026#34;) \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; TLOG_IF(\u0026#34;xx\u0026#34;, true) \u0026lt;\u0026lt; \u0026#34;hello \u0026#34; \u0026lt;\u0026lt; 23; #CHECK 断言 #define CHECK(cond) \\ if (!(cond)) _FLOG_STREAM \u0026lt;\u0026lt; \u0026#34;check failed: \u0026#34; #cond \u0026#34;! \u0026#34; #define CHECK_NOTNULL(p) \\ if ((p) == 0) _FLOG_STREAM \u0026lt;\u0026lt; \u0026#34;check failed: \u0026#34; #p \u0026#34; mustn\u0026#39;t be NULL! \u0026#34; #define CHECK_EQ(a, b) _CHECK_OP(a, b, ==) #define CHECK_NE(a, b) _CHECK_OP(a, b, !=) #define CHECK_GE(a, b) _CHECK_OP(a, b, \u0026gt;=) #define CHECK_LE(a, b) _CHECK_OP(a, b, \u0026lt;=) #define CHECK_GT(a, b) _CHECK_OP(a, b, \u0026gt;) #define CHECK_LT(a, b) _CHECK_OP(a, b, \u0026lt;) 上面的宏可视为加强版的 assert,它们在 DEBUG 模式下也不会被清除。\n这些宏与 FLOG 类似,可以打印 fatal 级别的日志。\nCHECK 断言条件 cond 为真,cond 可以是值为 bool 类型的任意表达式。\nCHECK_NOTNULL 断言指针不是 NULL,参数 p 是任意类型的指针。\nCHECK_EQ 断言 a == b。\nCHECK_NE 断言 a != b。\nCHECK_GE 断言 a \u0026gt;= b。\nCHECK_LE 断言 a \u0026lt;= b。\nCHECK_GT 断言 a \u0026gt; b。\nCHECK_LT 断言 a \u0026lt; b。\n一般建议优先使用 CHECK_XX(a, b) 这些宏,它们提供比 CHECK(cond) 更多的信息,会打印出参数 a 与 b 的值。\nfastream::operator\u0026lt;\u0026lt; 不支持的参数类型,如 STL 容器的 iterator 类型,不能用 CHECK_XX(a, b) 这些宏。\n断言失败时,co.log 先调用 log::exit(),再打印当前线程的函数调用栈信息,然后退出程序。\n示例\nint s = socket(); CHECK(s != -1); CHECK(s != -1) \u0026lt;\u0026lt; \u0026#34;create socket failed\u0026#34;; CHECK_NE(s, -1) \u0026lt;\u0026lt; \u0026#34;create socket failed\u0026#34;; // s != -1 CHECK_GE(s, 0) \u0026lt;\u0026lt; \u0026#34;create socket failed\u0026#34;; // s \u0026gt;= 0 CHECK_GT(s, -1) \u0026lt;\u0026lt; \u0026#34;create socket failed\u0026#34;; // s \u0026gt; -1 std::map\u0026lt;int, int\u0026gt; m; auto it = m.find(3); CHECK(it != m.end()); // 不能使用 CHECK_NE(it, m.end()), 编译器会报错 #打印堆栈信息 co.log 在 CHECK 断言失败或捕获到 SIGSEGV 等异常信号时,会打印函数调用栈,以方便定位问题,效果见下图:\n(https://asciinema.org/a/435894)\n此功能需要在编译时,加入调试信息,如 gcc 需要开启 -g 选项。在 linux 与 macosx 平台,还需要安装 libbacktrace,才能打印堆栈信息。在 linux 上,libbacktrace 可能已经集成到 gcc 里了,您也许可以在类似 /usr/lib/gcc/x86_64-linux-gnu/9 的目录中找到它。否则,您可以按下面的方式手动安装它:\ngit clone https://github.com/ianlancetaylor/libbacktrace.git cd libbacktrace-master ./configure make -j8 sudo make install #配置项 co.log 使用 co.flag 定义配置项,下面列出的是 co.log 内部定义的 flag,这些配置项如无特别说明,对 level log 与 TLOG 均有效。\n#log_dir 指定日志目录,默认为当前目录下的 logs 目录,不存在时将会自动创建。 log_dir 可以是绝对路径或相对路径,路径分隔符可以是 \u0026lsquo;/\u0026rsquo; 或 \u0026lsquo;\\\u0026rsquo;,一般建议使用 \u0026lsquo;/\u0026rsquo;。 程序启动时,确保当前用户有足够的权限,否则创建日志目录可能失败。 #log_file_name 指定日志文件名(不含路径),默认为空,使用程序名作为日志文件名(程序名末尾的 .exe 会被去掉),如程序 xx 或 xx.exe 对应的日志文件名是 xx.log。 如果日志文件名不是以 .log 结尾,co/log 自动在文件名末尾加上 .log。 #min_log_level 仅适用于 level log,指定打印日志的最小级别,用于屏蔽低级别的日志,默认为 0,打印所有级别的日志。 #max_log_size 单条日志的最大长度,默认为 4096,超过这个值,日志会被截断。 #max_log_file_size 指定日志文件的最大大小,默认 256M,超过此大小,生成新的日志文件,旧的日志文件会被重命名。 #max_log_file_num 指定日志文件的最大数量,默认是 8,超过此值,删除旧的日志文件。 #max_log_buffer_size 指定日志缓存的最大大小,默认 32M,超过此值,丢掉一半的日志。 #log_flush_ms 后台线程将日志缓存刷入文件的时间间隔,单位为毫秒。 #log_daily 按天生成日志文件,默认为 false。 #cout 终端日志开关,默认为 false。若为 true,将日志也打印到终端。 #日志文件 #日志文件名 co.log 将所有级别的日志记录到同一个文件中,默认使用程序名作为日志文件名,如进程 xx 的日志文件是 xx.log。当日志文件达到最大大小 (FLG_max_log_file_size) 时,co.log 会重命名日志文件,并生成新文件。日志目录下面可能包含如下的文件:\nxx.log xx_0523_16_12_54.970.log xx_0523_16_13_12.921.log xx_0523_16_15_05.264.log xx.log 始终是当前最新的日志文件。当文件数量超过 FLG_max_log_file_num 时,co.log 就会删除最旧的日志文件。\nfatal 级别的日志,还会额外记录到 xx.fatal 文件中,co.log 不会重命名 fatal 日志文件,也不会删除它。\n#日志格式 I0514 11:15:30.123 1045 xx.cc:11] hello world D0514 11:15:30.123 1045 xx.cc:12] hello world W0514 11:15:30.123 1045 xx.cc:13] hello world E0514 11:15:30.123 1045 xx.cc:14] hello world F0514 11:15:30.123 1045 xx.cc:15] hello world 0514 11:15:30.123 1045 xx.cc:11] hello world Level log 从左到右依次是级别、时间(月份到毫秒)、线程id、日志代码所在文件与行数、日志内容。 Level log 的第一个字母是日志级别,I 表示 info,D 表示 debug,W 表示 warning,E 表示 error,F 表示 fatal。 Topic log 没有级别,其他与 Level log 一样(上面的例子中最后一条即是 topic log)。 #查看日志 Linux、mac 等系统,可以用 grep,tail 等命令查看日志。\ngrep ^E xx.log tail -F xx.log tail -F xx.log | grep ^E 第 1 行用 grep 过滤出文件中的错误日志,^E 表示以字母 E 开头。 第 2 行用 tail -F 命令动态追踪日志文件,这里需要用大写的 F,因为 xx.log 可能被重命名,然后生成新的 xx.log 文件,-F 确保按文件名追踪到最新的日志文件。 第 3 行用 tail -F 配合 grep 动态追踪日志文件中的错误日志。 #构建及运行 co.log 测试程序 xmake -b log # build log or log.exe xmake r log # run log or log.exe xmake r log -cout # also log to terminal xmake r log -min_log_level=1 # 0-4: debug,info,warning,error,fatal xmake r log -perf # performance test 在 coost 根目录执行 xmake -b log 即可编译 test/log.cc 测试代码,并生成 log 二进制文件。 "},{"id":27,"href":"/cn/co/net/http/","title":"HTTP","section":"网络编程","content":"include: co/http.h.\n#http::Client http::Client 是基于协程的 http 客户端,它基于 libcurl 实现。\n#Client::Client explicit Client(const char* serv_url); 构造函数,参数 serv_url 是服务器的 url 地址,它的形式是 protocol://host:port,下面的 server url 都是合理的: \u0026ldquo;github.com\u0026rdquo; \u0026ldquo;https://github.com\u0026rdquo; \u0026ldquo;http://127.0.0.1:7788\u0026rdquo; \u0026ldquo;http://[::1]:8888\u0026rdquo; http::Client 对象创建时,并不会立即建立连接。 #Client::~Client Client::~Client(); 析构函数,关闭连接,释放 libcurl 相关资源。 #Client::add_header void add_header(const char* key, const char* val); void add_header(const char* key, int val); 添加 HTTP 头部,用户在进行 HTTP 请求前,可以用此方法添加头部,这些头部会自动添加到后续所有请求中。 第 2 个版本中,参数 val 是整数,内部自动转换成字符串。 #Client::body const fastring\u0026amp; body() const; 获取当前 HTTP 请求的响应体。 #Client::close void close(); 关闭 HTTP 连接,一般需要在协程中调用此方法。 调用此方法后,http::Client 对象就不能再用了,直到用户调用 reset() 方法重置 server url。 #Client::del void del(const char* url, const char* s, size_t n); void del(const char* url, const char* s); void del(const char* url); HTTP DELETE 请求,必须在协程中调用。 参数 url 必须是 '/' 开头的字符串。 前两个版本,适用于带 body 部分的 DELETE 请求,参数 s 是 body,n 是 s 的长度,第 2 个版本 s 以 \u0026lsquo;\\0\u0026rsquo; 结尾。 第 3 个版本适用于不带 body 的 DELETE 请求。 #Client::easy_handle void* easy_handle() const; 返回 libcurl 的 easy handle。 #Client::get void get(const char* url); HTTP GET 请求,必须在协程中调用。 参数 url 必须是 '/' 开头的字符串。 #Client::head void head(const char* url); HTTP HEAD 请求,必须在协程中调用。 参数 url 必须是 / 开头的字符串。 #Client::header const char* header(const char* key); const fastring\u0026amp; header() const; 第 1 个版本获取当前 HTTP 响应中 header 的值,header 不存在时,返回一个空字符串。\n第 2 个版本获取当前 HTTP 响应的整个 header 部分(包括起始行)。\n示例\nhttp::Client c(\u0026#34;xx.com\u0026#34;); c.get(\u0026#34;/\u0026#34;); auto s = c.header(\u0026#34;Content-Length\u0026#34;); #Client::perform void perform(); 执行 HTTP 请求,get, post 等方法实际上都是基于此方法实现。\n用户一般不需要调用此方法,只有当 http::Client 提供的 get, post 等方法满足不了需要时,才考虑用此方法自定义 HTTP 请求。\n示例\nvoid Client::post(const char* url, const char* data, size_t size) { curl_easy_setopt(_ctx-\u0026gt;easy, CURLOPT_POST, 1L); curl_easy_setopt(_ctx-\u0026gt;easy, CURLOPT_URL, make_url(url)); curl_easy_setopt(_ctx-\u0026gt;easy, CURLOPT_POSTFIELDS, data); curl_easy_setopt(_ctx-\u0026gt;easy, CURLOPT_POSTFIELDSIZE, (long)size); this-\u0026gt;perform(); } #Client::post void post(const char* url, const char* s, size_t n); void post(const char* url, const char* s); HTTP POST 请求,必须在协程中调用。 参数 url 必须是 '/' 开头的字符串。 参数 s 是 HTTP 请求的 body 数据,n 是 s 的长度,第 2 个版本 s 以 \u0026lsquo;\\0\u0026rsquo; 结尾。 #Client::put void put(const char* url, const char* path); HTTP PUT 请求,用于上传文件到服务器,必须在协程中调用。 参数 url 必须是 '/' 开头的字符串。 参数 path 是需要上传的文件路径。 #Client::remove_header void remove_header(const char* key); 删除之前添加的 HTTP header。调用 add_header() 方法添加的头部会存在于后续所有 HTTP 请求中,如果用户不想在后续请求中带上某些 header,可以用此方法显示删除。 #Client::reset void reset(const char* serv_url); 重置 server url,关于 url 的规范详见 Client::Client。 #Client::response_code int response_code() const; 获取当前 HTTP 请求的响应码。 正常情况下返回值是 100 到 511 之间的值。 若 HTTP 请求没有发送出去,或者没有收到服务端的响应,此方法返回 0,用户可以用 strerror() 方法获取错误信息。 #Client::status int status() const; 与 response_code() 等价,返回当前 HTTP 请求的响应吗。 #Client::strerror const char* strerror() const; 获取当前 HTTP 请求的错误信息。 #代码示例 void f() { http::Client c(\u0026#34;https://github.com\u0026#34;); int r; c.get(\u0026#34;/\u0026#34;); r = c.status(); LOG \u0026lt;\u0026lt; \u0026#34;staus: \u0026#34; \u0026lt;\u0026lt; r; LOG_IF(r == 0) \u0026lt;\u0026lt; \u0026#34;error: \u0026#34; \u0026lt;\u0026lt; c.strerror(); LOG \u0026lt;\u0026lt; \u0026#34;body size: \u0026#34; \u0026lt;\u0026lt; c.body().size(); LOG \u0026lt;\u0026lt; c.header(); c.get(\u0026#34;/idealvin/coost\u0026#34;); LOG \u0026lt;\u0026lt; \u0026#34;body size: \u0026#34; \u0026lt;\u0026lt; c.body().size(); LOG \u0026lt;\u0026lt; \u0026#34;Content-Length: \u0026#34; \u0026lt;\u0026lt; c.header(\u0026#34;Content-Length\u0026#34;); LOG \u0026lt;\u0026lt; c.header(); c.close(); } go(f); #http::Req http::Req 类是对 HTTP 请求的封装,用于实现 http::Server。\n#Req::Req Req() = default; 默认构造函数。 #Req::body const char* body() const; 获取 HTTP 请求中的 body 数据,返回一个指针,用户需要调用 body_size() 方法获取它的长度。 #Req::body_size size_t body_size() const; 返回 HTTP 请求 body 的长度。 #Req::header const char* header(const char* key) const; 获取 HTTP header 的值,header 不存在时,返回一个空字符串。 #Req::is_method_xxx bool is_method_get() const; bool is_method_head() const; bool is_method_post() const; bool is_method_put() const; bool is_method_delete() const; bool is_method_options() const; 判断 HTTP 请求的方法类型。 #Req::method Method method() const; 返回 HTTP 请求方法,返回值是 kGet, kHead, kPost, kPut, kDelete, kOptions 中的一种。 #Req::url const fastring\u0026amp; url() const; 返回 HTTP 请求中的 url 的引用,这个值是 HTTP 请求 start line 中的一部分。 #Req::version Version version() const; 返回 HTTP 请求中的 HTTP 版本,返回值是 http::kHTTP10 或 http::kHTTP11 中的一种,目前不支持 HTTP/2.0。 #http::Res http::Res 类是对 HTTP 响应的封装,用于实现 http::Server。\n#Res::Res Res(); 默认构造函数。 #Res::add_header void add_header(const char* key, const char* val); void add_header(const char* key, int val); 添加 HTTP header。 #Res::set_body void set_body(const void* s, size_t n); void set_body(const char* s); void set_body(const fastring\u0026amp; s); 设置 HTTP 响应的 body 部分。 参数 s 是 body 数据,参数 n 是 s 的长度,第 2 个版本中 s 以 '\\0' 结尾。 #Res::set_status void set_status(int status); 设置 HTTP 响应码,这个值一般是 100 到 511 之间的值。 #http::Server http::Server 是基于协程的 HTTP 服务器,它支持 HTTPS,使用 HTTPS 需要安装 openssl。\n#Server::Server Server(); 默认构造函数,用户无需关心。 #Server::on_req Server\u0026amp; on_req(std::function\u0026lt;void(const Req\u0026amp;, Res\u0026amp;)\u0026gt;\u0026amp;\u0026amp; f); Server\u0026amp; on_req(const std::function\u0026lt;void(const Req\u0026amp;, Res\u0026amp;)\u0026gt;\u0026amp; f) template\u0026lt;typename T\u0026gt; Server\u0026amp; on_req(void (T::*f)(const Req\u0026amp;, Res\u0026amp;), T* o); 设置处理客户端 HTTP 请求的 callback。 第 3 个版本中,参数 f 是类中的方法,参数 o 是 T 类型的指针。 服务端接收到 HTTP 请求时,会调用此方法设置的 callback,处理该请求。 #Server::start void start(const char* ip=\u0026#34;0.0.0.0\u0026#34;, int port=80); void start(const char* ip, int port, const char* key, const char* ca); 启动 HTTP server,此方法不会阻塞当前线程。 参数 key 是存放 SSL private key 的 PEM 文件路径,参数 ca 是存放 SSL 证书的 PEM 文件路径,默认 key 和 ca 是 NULL,不启用 SSL。 从 v3.0 开始,server 启动后就不再依赖于 http::Server 对象。 #Server::exit void exit(); v2.0.3 新增。 退出 HTTP server,关闭 listening socket,不再接收新的连接。 从 v3.0 开始,HTTP server 退出后,之前已经建立的连接将在未来被重置。 #代码示例 void cb(const http::Req\u0026amp; req, http::Res\u0026amp; res) { if (req.is_method_get()) { if (req.url() == \u0026#34;/hello\u0026#34;) { res.set_status(200); res.set_body(\u0026#34;hello world\u0026#34;); } else { res.set_status(404); } } else { res.set_status(405); // method not allowed } } // http http::Server().on_req(cb).start(\u0026#34;0.0.0.0\u0026#34;, 80); // https http::Server().on_req(cb).start( \u0026#34;0.0.0.0\u0026#34;, 443, \u0026#34;privkey.pem\u0026#34;, \u0026#34;certificate.pem\u0026#34; ); #静态 web server void easy(const char* root_dir=\u0026#34;.\u0026#34;, const char* ip=\u0026#34;0.0.0.0\u0026#34;, int port=80); void easy(const char* root_dir, const char* ip, int port, const char* key, const char* ca); 启动一个静态 web server,参数 root_dir 是 web server 的根目录。\n参数 ip 可以是 IPv4 或 IPv6 地址。\n参数 key 是存放 SSL private key 的 PEM 文件路径,参数 ca 是存放 SSL 证书的 PEM 文件路径,默认 key 和 ca 是 NULL,不启用 SSL。\n此方法会阻塞当前线程。\n示例\n#include \u0026#34;co/flag.h\u0026#34; #include \u0026#34;co/http.h\u0026#34; DEF_string(d, \u0026#34;.\u0026#34;, \u0026#34;root dir\u0026#34;); // root dir of web server int main(int argc, char** argv) { flag::parse(argc, argv); so::easy(FLG_d.c_str()); // mum never have to worry again return 0; } #配置项 coost 使用 co.flag 定义了 HTTP 相关的配置项。\n#http_conn_timeout DEF_uint32(http_conn_timeout, 3000, \u0026#34;#2 connect timeout in ms for http client\u0026#34;); http::Client 的连接超时时间,单位为毫秒。 #http_timeout DEF_uint32(http_timeout, 3000, \u0026#34;#2 send or recv timeout in ms for http client\u0026#34;); http::Client 接收、发送数据的超时时间 (libcurl 内部并不区分接收与发送超时时间),单位为毫秒。 #http_recv_timeout DEF_uint32(http_recv_timeout, 3000, \u0026#34;#2 recv timeout in ms for http server\u0026#34;); http::Server 接收超时时间,单位为毫秒。 #http_send_timeout DEF_uint32(http_send_timeout, 3000, \u0026#34;#2 send timeout in ms for http server\u0026#34;); http::Server 发送超时时间,单位为毫秒。 #http_conn_idle_sec DEF_uint32(http_conn_idle_sec, 180, \u0026#34;#2 http server may close the con...\u0026#34;); http::Server 保持空闲连接的超时时间,单位为秒,server 在此时间内,若未收到客户端的数据,则可能会关闭该连接。 #http_log DEF_bool(http_log, true, \u0026#34;#2 enable http server log if true\u0026#34;); http::Server 是否打印日志,默认为 true。 http::Server 中的日志会打印 HTTP 请求或响应的头部。 #http_max_idle_conn DEF_uint32(http_max_idle_conn, 128, \u0026#34;#2 max idle connections for http server\u0026#34;); http::Server 最大空闲连接数,超过此数量时,会关闭部分空闲连接。 #http_max_body_size DEF_uint32(http_max_body_size, 8 \u0026lt;\u0026lt; 20, \u0026#34;#2 max size of http body, default: 8M\u0026#34;); http::Server 支持的最大 body 长度,默认为 8M。 #http_max_header_size DEF_uint32(http_max_header_size, 4096, \u0026#34;#2 max size of http header\u0026#34;); http::Server 支持的最大 HTTP 头部(整个头部)长度,默认为 4k。 "},{"id":28,"href":"/cn/co/other/str/","title":"string utility","section":"其他","content":"include: co/str.h.\n#字符串操作 #str::cat template\u0026lt;typename ...X\u0026gt; inline fastring cat(X\u0026amp;\u0026amp; ...x); v2.0.3 新增,将任意数量的元素连接为一个字符串。\n示例\nstr::cat(\u0026#34;hello\u0026#34;, \u0026#39; \u0026#39;, 23); // -\u0026gt; \u0026#34;hello 23\u0026#34; str::cat(\u0026#34;xx\u0026#34;, 3.14, \u0026#34;true\u0026#34;); // -\u0026gt; \u0026#34;xx3.14true\u0026#34; #str::replace fastring replace(const char* s, size_t n, const char* sub, size_t m, const char* to, size_t l, size_t t=0); fastring replace(const char* s, size_t n, const char* sub, const char* to, size_t t=0); fastring replace(const char* s, const char* sub, const char* to, size_t t=0); fastring replace(const fastring\u0026amp; s, const char* sub, const char* to, size_t t=0); fastring replace(const std::string\u0026amp; s, const char* sub, const char* to, size_t t=0); 将字符串 s 中的子串 sub 替换为 to,n 是 s 的长度,m 是 sub 的长度,l 是 to 的长度;t 是最大替换次数,默认为 0 不限次数。\n原字符串 s 内容保持不变,返回替换后的新字符串。\n示例\nstr::replace(\u0026#34;xooxoox\u0026#34;, \u0026#34;oo\u0026#34;, \u0026#34;ee\u0026#34;); // -\u0026gt; \u0026#34;xeexeex\u0026#34; str::replace(\u0026#34;xooxoox\u0026#34;, \u0026#34;oo\u0026#34;, \u0026#34;ee\u0026#34;, 1); // -\u0026gt; \u0026#34;xeexoox\u0026#34; #str::split co::vector\u0026lt;fastring\u0026gt; split(const char* s, size_t n, char c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const char* s, size_t n, const char* c, size_t m, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const char* s, char c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const fastring\u0026amp; s, char c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const std::string\u0026amp; s, char c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const char* s, const char* c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const fastring\u0026amp; s, const char* c, size_t t=0); co::vector\u0026lt;fastring\u0026gt; split(const std::string\u0026amp; s, const char* c, size_t t=0); 将字符串 s 按分隔符 c 切分成若干个子串,返回切分后的结果。n 是 s 的长度,m 是 c 的长度,t 是最大切分次数,0 表示不限。\n原字符串 s 内容保持不变,返回切分后的结果。\n示例\nstr::split(\u0026#34;x y z\u0026#34;, \u0026#39; \u0026#39;); // -\u0026gt; [ \u0026#34;x\u0026#34;, \u0026#34;y\u0026#34;, \u0026#34;z\u0026#34; ] str::split(\u0026#34;|x|y|\u0026#34;, \u0026#39;|\u0026#39;); // -\u0026gt; [ \u0026#34;\u0026#34;, \u0026#34;x\u0026#34;, \u0026#34;y\u0026#34; ] str::split(\u0026#34;xooy\u0026#34;, \u0026#34;oo\u0026#34;); // -\u0026gt; [ \u0026#34;x\u0026#34;, \u0026#34;y\u0026#34;] str::split(\u0026#34;xooy\u0026#34;, \u0026#39;o\u0026#39;); // -\u0026gt; [ \u0026#34;x\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;y\u0026#34; ] str::split(\u0026#34;xooy\u0026#34;, \u0026#39;o\u0026#39;, 1); // -\u0026gt; [ \u0026#34;x\u0026#34;, \u0026#34;oy\u0026#34; ] #str::strip template\u0026lt;typename ...X\u0026gt; inline fastring strip(X\u0026amp;\u0026amp; ...x) { return trim(std::forward\u0026lt;X\u0026gt;(x)...); } 与 str::trim 等价。 #str::trim fastring trim(const char* s, const char* c=\u0026#34; \\t\\r\\n\u0026#34;, char d=\u0026#39;b\u0026#39;); fastring trim(const char* s, char c, char d=\u0026#39;b\u0026#39;); fastring trim(const fastring\u0026amp; s, const char* c=\u0026#34; \\t\\r\\n\u0026#34;, char d=\u0026#39;b\u0026#39;); fastring trim(const fastring\u0026amp; s, char c, char d=\u0026#39;b\u0026#39;); 修剪字符串,从字符串 s 两边去掉字符 c 或字符串 c 中的字符。参数 d 是方向,\u0026rsquo;l\u0026rsquo; 或 \u0026lsquo;L\u0026rsquo; 表示左边,\u0026lsquo;r\u0026rsquo; 或 \u0026lsquo;R\u0026rsquo; 表示右边,默认为 \u0026lsquo;b\u0026rsquo; 表示两边。\n原字符串 s 内容保持不变,返回修剪后的新字符串。\n示例\nstr::trim(\u0026#34; xx\\r\\n\u0026#34;); // -\u0026gt; \u0026#34;xx\u0026#34; str::trim(\u0026#34;abxxa\u0026#34;, \u0026#34;ab\u0026#34;); // -\u0026gt; \u0026#34;xx\u0026#34; str::trim(\u0026#34;abxxa\u0026#34;, \u0026#34;ab\u0026#34;, \u0026#39;l\u0026#39;); // -\u0026gt; \u0026#34;xxa\u0026#34; str::trim(\u0026#34;abxxa\u0026#34;, \u0026#34;ab\u0026#34;, \u0026#39;r\u0026#39;); // -\u0026gt; \u0026#34;abxx\u0026#34; #类型转换 #str::to_bool bool to_bool(const char* s); bool to_bool(const fastring\u0026amp; s); bool to_bool(const std::string\u0026amp; s); 将字符串转换为 bool 类型。\n当 s 等于 \u0026ldquo;0\u0026rdquo; 或 \u0026ldquo;false\u0026rdquo;,返回 false;当 s 等于 \u0026ldquo;1\u0026rdquo; 或 \u0026ldquo;true\u0026rdquo;,返回 true。\n此函数转换成功时 error code 为 0,转换失败时返回 false,并将 error code 设置为 EINVAL,可以调用 co::error() 获取错误码。\n示例\nbool b = str::to_bool(\u0026#34;true\u0026#34;); // b = true bool x = str::to_bool(\u0026#34;false\u0026#34;); // x = false #str::to_double double to_double(const char* s); double to_double(const fastring\u0026amp; s); double to_double(const std::string\u0026amp; s); 将字符串转换为 double 类型。\n此函数转换成功时 error code 为 0,转换失败时返回 0,并设置 error code 为 ERANGE 或 EINVAL,可以调用 co::error() 获取错误码。\n示例\ndouble x = str::to_double(\u0026#34;3.14\u0026#34;); // x = 3.14 #str::to_int int32 to_int32(const char* s); int32 to_int32(const fastring\u0026amp; s); int32 to_int32(const std::string\u0026amp; s); int64 to_int64(const char* s); int64 to_int64(const fastring\u0026amp; s); int64 to_int64(const std::string\u0026amp; s); uint32 to_uint32(const char* s); uint32 to_uint32(const fastring\u0026amp; s); uint32 to_uint32(const std::string\u0026amp; s); uint64 to_uint64(const char* s); uint64 to_uint64(const fastring\u0026amp; s); uint64 to_uint64(const std::string\u0026amp; s); 将字符串转换为整数类型。\n参数 s 末尾可以带一个单位 k, m, g, t, p,不区分大小写。\n这些函数转换成功时 error code 为 0,转换失败时返回 0,并设置 error code 为 ERANGE 或 EINVAL,可以调用 co::error() 获取错误码。\n示例\nint32 i32; int64 i64; uint32 u32; uint64 u64; i32 = str::to_int32(\u0026#34;-23\u0026#34;); // -23 u32 = str::to_uint32(\u0026#34;4k\u0026#34;); // 4096 i64 = str::to_int32(\u0026#34;8M\u0026#34;); // 8 \u0026lt;\u0026lt; 20 i64 = str::to_int64(\u0026#34;8T\u0026#34;); // 8ULL \u0026lt;\u0026lt; 40 u64 = str::to_int64(\u0026#34;1P\u0026#34;); // 1ULL \u0026lt;\u0026lt; 50 i32 = str::to_int32(\u0026#34;8g\u0026#34;); LOG \u0026lt;\u0026lt; (i32 == 0); LOG \u0026lt;\u0026lt; (co::error() == ERANGE); i32 = str::to_int32(\u0026#34;abx\u0026#34;); LOG \u0026lt;\u0026lt; (i32 == 0); LOG \u0026lt;\u0026lt; (co::error() == EINVAL); #str::from template\u0026lt;typename T\u0026gt; inline fastring from(T t); 此函数将内置类型转换为字符串。\nT 可以是任意内置类型,如 bool, int, double, void* 等等。\n示例\nfastring s; s = str::from(true); // -\u0026gt; \u0026#34;true\u0026#34; s = str::from(23); // -\u0026gt; \u0026#34;23\u0026#34; s = str::from(3.14); // -\u0026gt; \u0026#34;3.14\u0026#34; #str::dbg template\u0026lt;typename T\u0026gt; fastring dbg(const co::vector\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const std::vector\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const co::list\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const std::list\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const co::deque\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const std::deque\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const co::set\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const std::set\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const co::hash_set\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename T\u0026gt; fastring dbg(const std::unordered_set\u0026lt;T\u0026gt;\u0026amp; v); template\u0026lt;typename K, typename V\u0026gt; fastring dbg(const co::map\u0026lt;K, V\u0026gt;\u0026amp; v); template\u0026lt;typename K, typename V\u0026gt; fastring dbg(const std::map\u0026lt;K, V\u0026gt;\u0026amp; v); template\u0026lt;typename K, typename V\u0026gt; fastring dbg(const co::hash_map\u0026lt;K, V\u0026gt;\u0026amp; v); template\u0026lt;typename K, typename V\u0026gt; fastring dbg(const std::unordered_map\u0026lt;K, V\u0026gt;\u0026amp; v); template\u0026lt;typename K, typename V\u0026gt; fastring dbg(const co::lru_map\u0026lt;K, V\u0026gt;\u0026amp; v); 此函数将常用的容器类型转换成一个 debug 字符串,一般用于打印日志。\n示例\nstd::vector\u0026lt;int\u0026gt; v { 1, 2, 3 }; std::set\u0026lt;int\u0026gt; s { 1, 2, 3 }; std::map\u0026lt;int, int\u0026gt; m { {1, 1}, {2, 2} }; str::dbg(v); // -\u0026gt; \u0026#34;[1,2,3]\u0026#34; str::dbg(s); // -\u0026gt; \u0026#34;{1,2,3}\u0026#34; str::dbg(m); // -\u0026gt; \u0026#34;{1:1,2:2} std::vector\u0026lt;std::vector\u0026lt;int\u0026gt;\u0026gt; vv = { {1, 2, 3}, {6, 7, 8}, }; str::dbg(vv); // -\u0026gt; \u0026#34;[[1,2,3],[6,7,8]]\u0026#34; 从 v3.0.1 开始,str::dbg 支持多重嵌套的容器。 "},{"id":29,"href":"/cn/co/concurrency/coroutine/mutex/","title":"互斥锁","section":"协程","content":"include: co/co.h.\n#co::mutex 从 v3.0.1 开始,co::mutex 可以在协程与非协程中使用。 #constructor 1. mutex(); 2. mutex(mutex\u0026amp;\u0026amp; m); 3. mutex(const mutex\u0026amp; m); 1, 默认构造函数。 2, 移动构造函数。 3, 拷贝构造函数,仅将内部引用计数加 1。 #lock void lock() const; 获取锁,阻塞直到获得锁为止。 #try_lock bool try_lock() const; 获取锁,不会阻塞,成功获取锁时返回 true,否则返回 false。 #unlock void unlock() const; 释放锁,一般由之前获得锁的协程或线程调用。 #co::mutex_guard #constructor explicit mutex_guard(co::mutex\u0026amp; m); explicit mutex_guard(co::mutex* m); 构造函数,调用 m.lock() 获取锁,参数 m 是 co::mutex 类的引用或指针。 #destructor ~mutex_guard(); 析构函数,释放构造函数中获得的锁。 #代码示例 #include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; co::mutex g_m; int g_v = 0; void f() { co::mutex_guard g(g_m); ++g_v; } int main(int argc, char** argv) { flag::parse(argc, argv); go(f); go(f); f(); f(); co::sleep(100); co::print(\u0026#34;g_v: \u0026#34;, g_v); return 0; } "},{"id":30,"href":"/cn/co/unitest/","title":"单元测试","section":"参考文档","content":"include: co/unitest.h.\n#基本概念 co.unitest 是一个单元测试框架,与 google gtest 类似,但更简单易用。\n#测试单元与测试用例 一个测试程序可以按功能或模块划分为多个测试单元,每个测试单元下面可以有多个测试用例。如可以给 C++ 中的一个类(或模块)定义一个测试单元,类(或模块)中的每个方法定义一个测试用例。\nDEF_test(test_name) { DEF_case(a) { // write test code here } DEF_case(b) { // write test code here } } 上面的例子中,DEF_test 定义了一个测试单元(实际上就是定义了一个函数),DEF_case 则定义了测试用例,一个测试用例就相当于函数内的一个代码块。\n#DEF_test #define DEF_test(_name_) \\ DEF_bool(_name_, false, \u0026#34;enable this test if true\u0026#34;); \\ ... \\ void _co_ut_##_name_(unitest::xx::Test\u0026amp; _t_) DEF_test 宏用于定义测试单元,参数 _name_ 是测试单元的名字。 宏的第一行定义了一个 bool 类型的 flag 变量,是该测试单元的开关。如 DEF_test(os) 定义了一个测试单元 os,命令行参数中可以用 -os 指定运行 os 中的测试代码 宏的最后一行定义测试单元对应的函数。 #DEF_case #define DEF_case(name) \\ _t_.c = #name; \\ cout \u0026lt;\u0026lt; \u0026#34; case \u0026#34; \u0026lt;\u0026lt; #name \u0026lt;\u0026lt; \u0026#39;:\u0026#39; \u0026lt;\u0026lt; endl; DEF_case 宏用于定义测试单元中的测试用例,参数 name 是测试用例的名字,它必须在 DEF_test 定义的函数内部使用。 测试单元名必须可以作为类名或变量名的一部分,测试用例名则没有这个限制,如 DEF_case(sched.Copool) 也是合理的。 测试用例的代码,一般用一对大括号括起来,与其他测试用例隔离开来,互不影响。 DEF_test 中也可以不包含任何 DEF_case,这种情况下,co.unitest 会创建一个默认的测试用例。 #EXPECT 断言 #define EXPECT(x) ... #define EXPECT_EQ(x, y) EXPECT_OP(x, y, ==, \u0026#34;EQ\u0026#34;) #define EXPECT_NE(x, y) EXPECT_OP(x, y, !=, \u0026#34;NE\u0026#34;) #define EXPECT_GE(x, y) EXPECT_OP(x, y, \u0026gt;=, \u0026#34;GE\u0026#34;) #define EXPECT_LE(x, y) EXPECT_OP(x, y, \u0026lt;=, \u0026#34;LE\u0026#34;) #define EXPECT_GT(x, y) EXPECT_OP(x, y, \u0026gt;, \u0026#34;GT\u0026#34;) #define EXPECT_LT(x, y) EXPECT_OP(x, y, \u0026lt;, \u0026#34;LT\u0026#34;) EXPECT 断言 x 为真,x 可以是值为 bool 类型的任意表达式。 EXPECT_EQ 断言 x == y。 EXPECT_NE 断言 x != y。 EXPECT_GE 断言 x \u0026gt;= y。 EXPECT_LE 断言 x \u0026lt;= y。 EXPECT_GT 断言 x \u0026gt; y。 EXPECT_LT 断言 x \u0026lt; y。 DEF_case 定义测试用例时,可以用这些宏断言,断言失败即表示测试用例不通过,终端会以红色打印出相关的错误信息。 #编写测试代码 #测试代码示例 #include \u0026#34;co/unitest.h\u0026#34; #include \u0026#34;co/os.h\u0026#34; DEF_test(os) { DEF_case(homedir) { EXPECT_NE(os::homedir(), \u0026#34;\u0026#34;); } DEF_case(pid) { EXPECT_GE(os::pid(), 0); } DEF_case(cpunum) { EXPECT_GT(os::cpunum(), 0); } } int main(int argc, char** argv) { flag::parse(argc, argv); unitest::run_tests(); return 0; } 上面的代码定义了一个名为 os 的测试单元,os 有 3 个测试用例。 运行测试程序时,可在命令行参数中用 -os 启用此单元测试。 main 函数中需要先调用 flag::parse() 解析命令行参数,再调用 co.unitest 提供的 run_tests() 方法,即可运行单元测试代码。 #默认测试用例 DEF_test(os) { EXPECT_NE(os::homedir(), \u0026#34;\u0026#34;); EXPECT_GE(os::pid(), 0); EXPECT_GT(os::cpunum(), 0); } 上面的代码中,不包含任何 DEF_case,co.unitest 会创建一个名为 \u0026ldquo;default\u0026rdquo; 的默认测试用例。 较复杂的测试代码,一般不建议使用默认测试用例,最好划分成不同的 case,代码看起来更清晰些。 #构建及运行测试程序 #编译 co 自带的 unitest 代码 xmake -b unitest 在 coost 根目录执行上述命令,即可编译 co/unitest 目录下的单元测试代码,并生成 unitest 二进制程序。 #运行所有的测试用例 xmake r unitest 默认运行所有测试用例。 #运行指定测试单元中的测试用例 # 仅运行 os 测试单元中的测试用例 xmake r unitest -os # 运行 os 与 json 测试单元中的测试用例 xmake r unitest -os -json #测试结果示例 测试全部通过 测试用例未通过 "},{"id":31,"href":"/cn/co/net/rpc/","title":"RPC","section":"网络编程","content":"include: co/rpc.h.\ncoost 实现了一个基于协程的 RPC 框架,它内部使用 JSON 格式传输数据,与使用 protobuf 等二进制协议的 RPC 框架相比,它更加灵活,用起来更方便。\n从 v3.0 开始,RPC 框架同时也支持 HTTP 协议,可以用 HTTP 的 POST 方法调用 RPC 服务。 #rpc::Service class Service { public: Service() = default; virtual ~Service() = default; typedef std::function\u0026lt;void(Json\u0026amp;, Json\u0026amp;)\u0026gt; Fun; virtual const char* name() const = 0; virtual const co::map\u0026lt;const char*, Fun\u0026gt;\u0026amp; methods() const = 0; }; 接口类,它表示一个 RPC service,一个 RPC server 中可以包含多个 service。 方法 name() 返回 service 名,methods() 返回所有的 RPC 接口及其业务处理函数。 用户不需要关心此类。 #rpc::Server #Server::Server Server(); 默认构造函数,用户不需要关心。 #Server::add_service 1. Server\u0026amp; add_service(rpc::Service* s); 2. Server\u0026amp; add_service(const std::shared_ptr\u0026lt;rpc::Service\u0026gt;\u0026amp; s); 添加 service,1 中参数 s 必须是用 operator new 动态创建的。 用户可以多次调用此方法,添加多个 service,不同 service 必须有不同的名字。 #Server::start void start( const char* ip, int port, const char* url=\u0026#34;/\u0026#34;, const char* key=0, const char* ca=0 ); 启动 RPC server,此方法不会阻塞当前线程。 参数 ip 是服务 ip,可以是 IPv4 或 IPv6 地址,参数 port 是服务端口。 参数 url 是 HTTP 服务的 url,必须以 / 开头。 参数 key 是存放 SSL private key 的 PEM 文件路径,参数 ca 是存放 SSL 证书的 PEM 文件路径,默认 key 和 ca 是 NULL,不启用 SSL。 从 v3.0 开始,server 启动后就不再依赖于 rpc::Server 对象。 #Server::exit void exit(); v2.0.3 新增。 退出 RPC server,关闭 listening socket,不再接收新的连接。 从 v3.0 开始,RPC server 退出后,之前已经建立的连接将在未来被重置。 #RPC server 示例 #定义 proto 文件 下面是一个简单的 proto 文件 hello_world.proto:\npackage xx service HelloWorld { hello world } package 定义包名,在 C++ 中对应为 namespace。 service 定义一个 RPC service,该 service 提供 hello, world 两个方法。 由于 RPC 请求及响应都是 JSON,不需要在协议文件中定义结构体。 一个 proto 文件中最多只能定义一个 service。 v3.0.1 基于 flex 与 byacc 重写了 gen 工具,proto 语法上除了支持 service 定义,还支持结构体的定义,具体用法可以参考 j2s。 #生成 service 代码 gen 是 coost 提供的代码生成工具,它可以生成 service 相关代码。\nxmake -b gen # 构建 gen cp gen /usr/local/bin # 将 gen 放到 /usr/local/bin 目录 gen hello_world.proto # 生成代码 生成的文件 hello_world.h 如下:\n// Autogenerated. // DO NOT EDIT. All changes will be undone. #pragma once #include \u0026#34;co/rpc.h\u0026#34; namespace xx { class HelloWorld : public rpc::Service { public: typedef std::function\u0026lt;void(Json\u0026amp;, Json\u0026amp;)\u0026gt; Fun; HelloWorld() { using std::placeholders::_1; using std::placeholders::_2; _methods[\u0026#34;HelloWorld.hello\u0026#34;] = std::bind(\u0026amp;HelloWorld::hello, this, _1, _2); _methods[\u0026#34;HelloWorld.world\u0026#34;] = std::bind(\u0026amp;HelloWorld::world, this, _1, _2); } virtual ~HelloWorld() {} virtual const char* name() const { return \u0026#34;HelloWorld\u0026#34;; } virtual const co::map\u0026lt;const char*, Fun\u0026gt;\u0026amp; methods() const { return _methods; } virtual void hello(Json\u0026amp; req, Json\u0026amp; res) = 0; virtual void world(Json\u0026amp; req, Json\u0026amp; res) = 0; private: co::map\u0026lt;const char*, Fun\u0026gt; _methods; }; } // xx 可以看到,HelloWorld 类继承于 rpc::Service,它已经实现了 rpc::Service 类中的 name() 与 methods() 方法。 用户只需要继承 HelloWorld 类,实现 hello 与 world 两个方法即可。 #业务实现 #include \u0026#34;hello_world.h\u0026#34; namespace xx { class HelloWorldImpl : public HelloWorld { public: HelloWorldImpl() = default; virtual ~HelloWorldImpl() = default; virtual void hello(Json\u0026amp; req, Json\u0026amp; res) { res = { { \u0026#34;result\u0026#34;, { { \u0026#34;hello\u0026#34;, 23 } }} }; } virtual void world(Json\u0026amp; req, Json\u0026amp; res) { res = { { \u0026#34;error\u0026#34;, \u0026#34;not supported\u0026#34;} }; } }; } // xx 上面只是一个很简单的例子,实际应用中,一般需要根据 req 中的参数,进行相应的业务处理,然后填充 res。 #启动 RPC server int main(int argc, char** argv) { flag::parse(argc, argv); rpc::Server() .add_service(new xx::HelloWorldImpl) .start(\u0026#34;127.0.0.1\u0026#34;, 7788, \u0026#34;/xx\u0026#34;); for (;;) sleep::sec(80000); return 0; } 先调 add_service() 添加 service,再调用 start() 启动 server。 start() 方法不会阻塞当前线程,因此需要写一个 for 循环,防止 main 函数退出。 #用 curl 调用 RPC 服务 在 v3.0 版本中,RPC 框架支持 HTTP 协议,因此可以用 curl 命令调用 RPC 服务:\ncurl http://127.0.0.1:7788/xx --request POST --data \u0026#39;{\u0026#34;api\u0026#34;:\u0026#34;ping\u0026#34;}\u0026#39; curl http://127.0.0.1:7788/xx --request POST --data \u0026#39;{\u0026#34;api\u0026#34;:\u0026#34;HelloWorld.hello\u0026#34;}\u0026#39; 上面用 curl 给 RPC 服务发送 POST 请求,参数为 JSON 字符串,需要提供一个 api 字段,指明调用的 RPC 方法。 \u0026quot;ping\u0026quot; 是 RPC 框架内置的方法,一般用于测试或发送心跳。 url 中 /xx 要与 RPC server 启动时指定的 url 保持一致。 #rpc::Client #Client::Client 1. Client(const char* ip, int port, bool use_ssl=false); 2. Client(const Client\u0026amp; c); 1, 参数 ip 可以是域名、IPv4 或 IPv6 地址;参数 port 是服务端口;参数 use_ssl 表示是否启用 SSL 传输,默认为 false。 2, 拷贝构造函数。 rpc::Client 构建时,并不会立即建立连接。 #Client::~Client Client::~Client(); 析构函数,关闭连接。 #Client::call void call(const Json\u0026amp; req, Json\u0026amp; res); 执行 RPC 请求,必须在协程中调用。 参数 req 中必须带有 \u0026quot;api\u0026quot; 字段,该字段的值一般为 \u0026quot;service.method\u0026quot; 形式。 参数 res 是 RPC 请求的响应结果。 若 RPC 请求没有发送出去,或者没有收到服务端的响应,res 将不会被填充。 此方法在发送 RPC 请求前,会检查连接状态,未连接时,先建立连接。 #Client::close void close(); 关闭连接,多次调用此函数是安全的。 #Client::ping void ping(); 给 RPC server 发送 ping 请求,一般用于测试或发送心跳。 #RPC client 示例 #直接使用 rpc::Client DEF_bool(use_ssl, false, \u0026#34;use ssl if true\u0026#34;); DEF_int32(n, 3, \u0026#34;request num\u0026#34;); void client_fun() { rpc::Client c(\u0026#34;127.0.0.1\u0026#34;, 7788, FLG_use_ssl); for (int i = 0; i \u0026lt; FLG_n; ++i) { co::Json req = { {\u0026#34;api\u0026#34;, \u0026#34;HelloWorld.hello\u0026#34;} }; co::Json res; c.call(req, res); co::sleep(1000); } c.close(); } go(client_fun); 上面的例子中,client 每隔 1 秒向服务端发送一个 RPC 请求。 #使用连接池 co::pool 当客户端需要建立大量连接时,可以用 co::pool 管理这些连接。\nstd::unique_ptr\u0026lt;rpc::Client\u0026gt; proto; co::pool pool( []() { return (void*) new rpc::Client(*proto); }, [](void* p) { delete (rpc::Client*) p; } ); void client_fun() { co::pool_guard\u0026lt;rpc::Client\u0026gt; c(pool); while (true) { c-\u0026gt;ping(); co::sleep(3000); } } proto.reset(new rpc::Client(\u0026#34;127.0.0.1\u0026#34;, 7788)); for (int i = 0; i \u0026lt; 8; ++i) { go(client_fun); } 上面的例子,使用 co::pool 保存客户端,多个协程可以共享这些客户端。 co::pool 的 ccb 使用拷贝构造的方式从 proto 复制一个客户端连接。 #配置项 coost 使用 co.flag 定义了 RPC 相关的配置项。\n#rpc_conn_idle_sec DEF_int32(rpc_conn_idle_sec, 180, \u0026#34;#2 connection may be closed if no data...\u0026#34;); rpc::Server 空闲连接超时时间,单位为秒。一个连接在此时间内没有收到任何数据,server 可能会关闭此连接。 #rpc_conn_timeout DEF_int32(rpc_conn_timeout, 3000, \u0026#34;#2 connect timeout in ms\u0026#34;); rpc::Client 连接超时时间,单位为毫秒。 #rpc_log DEF_bool(rpc_log, true, \u0026#34;#2 enable rpc log if true\u0026#34;); 是否打印 RPC 日志,默认为 true,rpc::Server 与 rpc::Client 会打印 RPC 请求与响应。 #rpc_max_idle_conn DEF_int32(rpc_max_idle_conn, 128, \u0026#34;#2 max idle connections\u0026#34;); rpc::Server 最大空闲连接数,默认为 128,超过这个数量时,server 会关闭部分空闲连接。 #rpc_max_msg_size DEF_int32(rpc_max_msg_size, 8 \u0026lt;\u0026lt; 20, \u0026#34;#2 max size of rpc message, default: 8M\u0026#34;); RPC 消息的最大长度,默认为 8M。 #rpc_recv_timeout DEF_int32(rpc_recv_timeout, 3000, \u0026#34;#2 recv timeout in ms\u0026#34;); RPC 接收超时时间,单位为毫秒。 #rpc_send_timeout DEF_int32(rpc_send_timeout, 3000, \u0026#34;#2 send timeout in ms\u0026#34;); RPC 发送超时时间,单位为毫秒。 "},{"id":32,"href":"/cn/co/concurrency/coroutine/wg/","title":"wait group","section":"协程","content":"include: co/co.h.\n#co::wait_group co::wait_group 类似于 golang 中的 sync.WaitGroup,可用于等待协程或线程的退出。\n#constructor 1. explicit wait_group(uint32 n); 2. wait_group(); 3. wait_group(wait_group\u0026amp;\u0026amp; wg); 4. wait_group(const wait_group\u0026amp; wg); 1, 将内部计数器初始化为 n。 2, 默认构造函数,将内部计数器初始化为 0。 3, 移动构造函数。 4, 拷贝构造函数,仅将内部引用计数加 1。 #add void add(uint32 n=1) const; 将内部计数器加 n,n 默认为 1。 #done void done() const; 将内部计数器减 1。 #wait void wait() const; 等待直到内部计数器的值变为 0。 #代码示例 #include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; DEF_uint32(n, 8, \u0026#34;coroutine number\u0026#34;); int main(int argc, char** argv) { flag::parse(argc, argv); co::wait_group wg; wg.add(FLG_n); for (uint32 i = 0; i \u0026lt; FLG_n; ++i) { go([wg]() { co::print(\u0026#34;sched: \u0026#34;, co::sched_id(), \u0026#34; co: \u0026#34;, co::coroutine_id()); wg.done(); }); } wg.wait(); return 0; } "},{"id":33,"href":"/cn/co/benchmark/","title":"基准测试","section":"参考文档","content":"include: co/benchmark.h.\n#基本概念 co.benchmark 是 v3.0.1 新增的基准测试框架,可用于性能基准测试。\n#BM_group #define BM_group(_name_) \\ ...... \\ void _co_bm_group_##_name_(bm::xx::Group\u0026amp; _g_) BM_group 宏用于定义基准测试组,实际上定义了一个函数。 每个 group 内可以用 BM_add 定义多条基准测试。 参数 _name_ 是组名,也是所定义函数名的一部分,如 BM_group(atomic) 是合理的,而 BM_group(co.atomic) 则是不允许的。 #BM_add #define BM_add(_name_) \\ _g_.bm = #_name_; \\ _BM_add BM_add 宏用于定义基准测试,它必须在 BM_group 定义的函数内使用。 参数 _name_ 是基准测试名,与 BM_group 不同,BM_add(co.atomic) 也是允许的。 #BM_use #define BM_use(v) bm::xx::use(\u0026amp;v, sizeof(v)) BM_use 宏告诉编译器变量 v 会被使用,防止编译器将一些测试代码优化掉。 #编写基准测试代码 #测试代码示例 #include \u0026#34;co/benchmark.h\u0026#34; #include \u0026#34;co/mem.h\u0026#34; BM_group(malloc) { void* p; BM_add(::malloc)( p = ::malloc(32); ); BM_use(p); BM_add(co::alloc)( p = co::alloc(32); ); BM_use(p); } int main(int argc, char** argv) { flag::parse(argc, argv); bm::run_benchmarks(); return 0; } 上面的代码定义了一个名为 malloc 的基准测试组,组内用 BM_add 添加了 2 个基准测试。 调用 bm::run_benchmarks(),会执行所有的基准测试代码。 上例中,若无 BM_use(p),编译器可能认为 p 是未使用的变量,将相关的测试代码优化掉,导致无法测出准确的结果。 #测试结果示例 基准测试结果输出为 markdown 表格,可以轻松将测试结果复制到 markdown 文档中。 多个 BM_group 会生成多个 markdown 表格。 表格第 1 列是 group 内的所有基准测试,第 2 列是单次迭代用时(单位为纳秒),第 3 列是每秒迭代次数,第 4 列是性能提升倍数,以第一个基准测试为基准。 "},{"id":34,"href":"/cn/co/other/rand/","title":"随机值","section":"其他","content":"include: co/rand.h.\n#随机数(co::rand) 1. uint32 rand(); 2. uint32 rand(uint32\u0026amp; seed); 1, 返回一个 1 到 2G-2 之间的随机数,线程安全。 2, 返回一个 1 到 2G-2 之间的随机数,需要用户指定种子数 seed,种子数必须在 1 到 2G-2 之间,此函数会更新 seed 的值,非线程安全。 可以用 1 中的返回值初始化 2 中的种子数。 示例 uint32 x = co::rand(); uint32 y = co::rand(); uint32 seed = co::rand(); uint32 u = co::rand(seed); uint32 v = co::rand(seed); #随机字符串(co::randstr) 1. fastring randstr(int n=15); 2. fastring randstr(const char* s, int n); 1, 返回一个长度为 n(默认为15) 的随机字符串,线程安全。 2, 返回一个长度为 n、由字符串 s 中的字符构成的随机字符串,s 中可以使用类似 0-9, a-z 的缩写,s 展开后的长度不能超过 255,线程安全。 randstr 基于 nanoid 算法实现,返回的随机字符串足够长时,一般也可以用作唯一 id。 示例 fastring s = co::randstr(); s = co::randstr(8); s = co::randstr(\u0026#34;0123456789\u0026#34;, 6); s = co::randstr(\u0026#34;0-9a-f\u0026#34;, 8); // 长度为8的16进制字符串 "},{"id":35,"href":"/cn/co/other/hash/","title":"Hash","section":"其他","content":"include: co/hash.h.\n#Hash #hash32 uint32 hash32(const void* s, size_t n); uint32 hash32(const char* s); uint32 hash32(const fastring\u0026amp; s) uint32 hash32(const std::string\u0026amp; s); 此函数返回 32 位的 murmur hash 值。 s 为指针时,一般要求是 sizeof(void*) 字节对齐的。 #hash64 uint64 hash64(const void* s, size_t n); uint64 hash64(const char* s); uint64 hash64(const fastring\u0026amp; s); uint64 hash64(const std::string\u0026amp; s); 此函数返回 64 位的 murmur hash 值。 s 为指针时,一般要求是 8 字节对齐的。 #murmur_hash size_t murmur_hash(const void* s, size_t n); 此函数返回 size_t 类型的 hash 值,这个值在 64 位平台是 64 位的,在 32 位平台是 32 位的。 参数 s 一般要求是 sizeof(void*) 字节对齐的。 #md5 #md5digest void md5digest(const void* s, size_t n, char res[16]); fastring md5digest(const void* s, size_t n); fastring md5digest(const char* s); fastring md5digest(const fastring\u0026amp; s); fastring md5digest(const std::string\u0026amp; s); 计算字符串的 md5 值,结果为 16 字节的二进制字符串。 #md5sum void md5sum(const void* s, size_t n, char res[32]); fastring md5sum(const void* s, size_t n); fastring md5sum(const char* s); fastring md5sum(const fastring\u0026amp; s); fastring md5sum(const std::string\u0026amp; s); 计算字符串的 md5 值,结果为 32 字节、仅含十六进制字符(0-9,a-f)的字符串。 #Lower level APIs void md5_init(md5_ctx_t* ctx); void md5_update(md5_ctx_t* ctx, const void* s, size_t n); void md5_final(md5_ctx_t* ctx, uint8 res[16]); 上述 3 个 API 可用于增量计算 md5。\n示例\nchar buf[4096]; uint8 res[16]; md5_ctx_t ctx; md5_init(\u0026amp;ctx); while (true) { int r = read(fd, buf, 4096); if (r \u0026gt; 0) { md5_update(\u0026amp;ctx, buf, r); } else { break; } } md5_final(\u0026amp;ctx, res); #sha256 #sha256digest void sha256digest(const void* s, size_t n, char res[32]); fastring sha256digest(const void* s, size_t n); fastring sha256digest(const char* s); fastring sha256digest(const fastring\u0026amp; s); fastring sha256digest(const std::string\u0026amp; s); 计算字符串的 sha256 值,结果为 32 字节的二进制字符串。 #sha256sum void sha256sum(const void* s, size_t n, char res[64]); fastring sha256sum(const void* s, size_t n); fastring sha256sum(const char* s); fastring sha256sum(const fastring\u0026amp; s); fastring sha256sum(const std::string\u0026amp; s); 计算字符串的 sha256 值,结果为 64 字节、仅含十六进制字符(0-9,a-f)的字符串。 #Lower level APIs void sha256_init(sha256_ctx_t* ctx); void sha256_update(sha256_ctx_t* ctx, const void* s, size_t n); void sha256_final(sha256_ctx_t* ctx, uint8 res[32]); 上述 3 个 API 与 md5 类似,可用于增量计算 sha256。 #base64 #base64_encode fastring base64_encode(const void* s, size_t n); fastring base64_encode(const char* s); fastring base64_encode(const fastring\u0026amp; s); fastring base64_encode(const std::string\u0026amp; s); base64 编码,实现中不添加 \\r\\n,实际应用中,没有必要添加。 #base64_decode fastring base64_decode(const void* s, size_t n); fastring base64_decode(const char* s); fastring base64_decode(const fastring\u0026amp; s); fastring base64_decode(const std::string\u0026amp; s); base64 解码,如果输入的不是合理的 base64 编码的数据,解码将会失败,返回空字符串。 #url #url_encode fastring url_encode(const void* s, size_t n); fastring url_encode(const char* s); fastring url_encode(const fastring\u0026amp; s); fastring url_encode(const std::string\u0026amp; s); url 编码,保留字符 !()*#$\u0026amp;'+,/:;=?@[] 以及 a-z A-Z 0-9 -_.~ 不会编码,所有其它的字符都会被编码。 #url_decode fastring url_decode(const void* s, size_t n); fastring url_decode(const char* s); fastring url_decode(const fastring\u0026amp; s); fastring url_decode(const std::string\u0026amp; s); url 解码,如果输入的不是合理编码的 url,解码将会失败,返回空字符串。 #crc16 uint16 crc16(const void* s, size_t n); uint16 crc16(const char* s); uint16 crc16(const fastring\u0026amp; s); uint16 crc16(const std::string\u0026amp; s); 此函数计算字符串的 crc16 值,实现取自 redis,在实现 redis 集群客户端时会用到。 "},{"id":36,"href":"/cn/co/json/","title":"JSON","section":"参考文档","content":"include: co/json.h.\nco.json 是一个类似 rapidjson 的 JSON 库,与 rapidjson 相比,它既有性能上的优势,同时又更简单易用。\n#基本概念 JSON 是一种简单的数据格式,它支持两种数据结构:\n由一系列 key/value 键值对构成的集合,这类结构称为 object,对应编程语言中的 struct, map 等等。 由一系列 value 构成的列表,这类结构称为 array,对应编程语言中的 vector, list 等等。 上述 key 是 string,value 一般也称为 JSON value (co.json 中用 Json 类表示),可以是 object, array, number, string, bool(false, true), null 中的任意一种。number 是整数或浮点数,大部分实现会将整数与浮点数区分开。\nobject 由一对大括号括起来,array 由一对中括号括起来,它们看起来像下面这样:\n{\u0026#34;a\u0026#34;:1, \u0026#34;b\u0026#34;:false, \u0026#34;s\u0026#34;:\u0026#34;xxx\u0026#34;} [1, 2, 3] 由定义 object 与 array 可以嵌套,从而可以表示树等复杂数据结构。\n#global #json::array Json array(); 此函数在 namespace json 下,它返回一个空的 array 对象。 #json::object Json object(); 此函数在 namespace json 下,它返回一个空的 object 对象。 #json::parse Json parse(const char* s, size_t n); Json parse(const char* s); Json parse(const fastring\u0026amp; s); Json parse(const std::string\u0026amp; s); 从 JSON 字符串解析 Json 对象。 此函数不是 Json 类中的方法,而是定义于 namespace json 下的函数。 此函数返回一个 Json 对象,解析失败时,返回 null 对象。 #co::Json #constructor 1. Json() noexcept; 2. Json(decltype(nullptr)) noexcept; 3. Json(Json\u0026amp;\u0026amp; v) noexcept; 4. Json(Json\u0026amp; v) noexcept; Json(const Json\u0026amp; v) = delete; 5. Json(bool v); 6. Json(double v); 7. Json(int64 v); 8. Json(int32 v); 9. Json(uint32 v); 10. Json(uint64 v); 11. Json(const void* p, size_t n); 12. Json(const char* s); 13. Json(const fastring\u0026amp; s); 14. Json(const std::string\u0026amp; s); 15. Json(std::initializer_list\u0026lt;Json\u0026gt; v); 1-2, 构建一个 null 对象。\n3-4, move 构造与拷贝构造函数,二者均执行 move 语义,构造函数执行后,v 将变为一个 null 对象。\n5, 构造 bool 类型的 JSON 对象。\n6, 构造 double 类型的 JSON 对象。\n7-10, 构造整数类型的 JSON 对象。\n11-14, 构造字符串类型的 JSON 对象。\n15, 根据初始化列表构建 object 或 array 对象。\n示例\nco::Json a; // null co::Json b(nullptr); // null co::Json c = false; // bool co::Json d = 3.14; // double co::Json e = 23; // integer co::Json f = \u0026#34;xx\u0026#34;; // string co::Json g = {1, 2, 3}; // g -\u0026gt; [1, 2, 3] co::Json h = {\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;}; // h -\u0026gt; [\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;] co::Json i = { // i -\u0026gt; { \u0026#34;a\u0026#34;: \u0026#34;b\u0026#34; } {\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;} }; co::Json j = { // j -\u0026gt; {\u0026#34;a\u0026#34;: 1, \u0026#34;b\u0026#34;: [1,2,3]} {\u0026#34;a\u0026#34;, 1}, {\u0026#34;b\u0026#34;, {1, 2, 3}}, }; co::Json x(i); // i -\u0026gt; null co::Json y(std::move(j)); // j -\u0026gt; null #operator= Json\u0026amp; operator=(Json\u0026amp;\u0026amp; v); Json\u0026amp; operator=(Json\u0026amp; v); void operator=(const Json\u0026amp;) = delete; 赋值操作,上面的两个方法等价,该操作执行后,参数 v 变成 null 对象。 #dup Json dup() const; 深度拷贝一个 JSON 对象。\n示例\nco::Json x = {1, 2, 3}; // x -\u0026gt; [1,2,3] co::Json y, z; y = x; // x -\u0026gt; null, y -\u0026gt; [1,2,3] z = y.dup(); // y:[1,2,3], z -\u0026gt; [1,2,3] #——————————— #is_null bool is_null() const; 判断 Json 对象是否为 null。 #is_bool bool is_bool() const; 判断 Json 对象是否为 bool 类型。 #is_int bool is_int() const; 判断 Json 对象是否为整数类型。 #is_double bool is_double() const; 判断 Json 对象是否为 double 类型。 #is_string bool is_string() const; 判断 Json 对象是否为字符串类型。 #is_array bool is_array() const; 判断 Json 对象是否为 array 类型。 #is_object bool is_object() const; 判断 Json 对象是否为 object 类型。 #——————————— #as_bool bool as_bool() const; 获取 bool 类型的值。 对于 int 或 double 类型,若值为 0,返回 false,否则返回 true。 对于 string 类型,若值为 \u0026quot;true\u0026quot; 或 \u0026quot;1\u0026quot;,返回 true,否则返回 false。 对于其他非 bool 类型,返回 false。 #as_int int as_int() const; int32 as_int32() const; int64 as_int64() const; 获取整数类型的值。 对于 bool, double 或 string 类型,结果自动转换为整数类型。 对于其他非整数类型,返回 0。 #as_double double as_double() const; 获取 double 类型的值。 对于 bool, int 或 string 类型,结果自动转换为 double 类型。 对于其他非 double 类型,返回 0。 #as_string fastring as_string() const; 获取字符串类型的值,返回 fastring。 对于非 string 类型,此方法等价于 str(),结果将自动转换为 string 类型。 #as_c_str const char* as_c_str() const; 返回 \\0 结尾的 C 风格字符串,可以用 string_size() 获取其长度,一般用于对性能要求较高的地方。 对于非 string 类型,返回空字符串。 #get 1. Json\u0026amp; get(uint32 i) const; 2. Json\u0026amp; get(int i) const; 3. Json\u0026amp; get(const char* key) const; 4. template \u0026lt;class T, class ...X\u0026gt; inline Json\u0026amp; get(T\u0026amp;\u0026amp; v, X\u0026amp;\u0026amp; ... x) const; 根据 index 或 key 获取 JSON 对象,此方法是只读操作,不会修改调用此方法的 JSON 对象。 1-2, 获取 array 对象的第 i 个元素,若调用此方法的 JSON 对象不是 array 类型,或者 i 超出了 array 的范围,返回结果将引用一个 null 对象。 3, 获取 key 对应的 JSON value 对象,若调用此方法的 JSON 对象不是 object 类型,或者 key 不存在,返回结果将引用一个 null 对象。 4, 可以带任意数量的参数,每个参数是 index 或者 key。该方法遇到第一个不合适的 index 或 不存在的 key 时,立即返回,返回结果将引用一个 null 对象。 #set template \u0026lt;class T\u0026gt; inline Json\u0026amp; set(T\u0026amp;\u0026amp; v) { return *this = Json(std::forward\u0026lt;T\u0026gt;(v)); } template \u0026lt;class A, class B, class ...X\u0026gt; inline Json\u0026amp; set(A\u0026amp;\u0026amp; a, B\u0026amp;\u0026amp; b, X\u0026amp;\u0026amp; ... x); 设置 JSON 对象的值。 set 方法最后一个参数是所设置的值,其他参数是 index 或者 key。 #代码示例 co::Json r = { { \u0026#34;a\u0026#34;, 7 }, { \u0026#34;b\u0026#34;, false }, { \u0026#34;c\u0026#34;, { 1, 2, 3 } }, { \u0026#34;s\u0026#34;, \u0026#34;23\u0026#34; }, }; r.get(\u0026#34;a\u0026#34;).as_int(); // 7 r.get(\u0026#34;b\u0026#34;).as_bool(); // false r.get(\u0026#34;s\u0026#34;).as_string(); // \u0026#34;23\u0026#34; r.get(\u0026#34;s\u0026#34;).as_int(); // 23 r.get(\u0026#34;c\u0026#34;, 0).as_int(); // 1 r.get(\u0026#34;c\u0026#34;, 1).as_int(); // 2 // x -\u0026gt; {\u0026#34;a\u0026#34;:1,\u0026#34;b\u0026#34;:[0,1,2],\u0026#34;c\u0026#34;:{\u0026#34;d\u0026#34;:[\u0026#34;oo\u0026#34;]}} co::Json x; x.set(\u0026#34;a\u0026#34;, 1); x.set(\u0026#34;b\u0026#34;, co::Json({0,1,2})); x.set(\u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, 0, \u0026#34;oo\u0026#34;); #——————————— #operator== bool operator==(bool v) const; bool operator==(double v) const; bool operator==(int64 v) const; bool operator==(int v) const; bool operator==(uint32 v) const; bool operator==(uint64 v) const; bool operator==(const char* v) const; bool operator==(const fastring\u0026amp; v) const; bool operator==(const std::string\u0026amp; v) const; 判断 Json 对象的值是否等于 v。 若 Json 对象的类型与 v 不同,则直接返回 false。 #operator!= bool operator!=(bool v) const; bool operator!=(double v) const; bool operator!=(int64 v) const; bool operator!=(int v) const; bool operator!=(uint32 v) const; bool operator!=(uint64 v) const; bool operator!=(const char* v) const; bool operator!=(const fastring\u0026amp; v) const; bool operator!=(const std::string\u0026amp; v) const; 判断 Json 对象的值是否不等于 v。 若 Json 对象的类型与 v 不同,则直接返回 true。 #代码示例 co::Json x = { {\u0026#34;a\u0026#34;, 3}, {\u0026#34;b\u0026#34;, false}, {\u0026#34;s\u0026#34;, \u0026#34;xx\u0026#34;}, }; x == 7; // false x[\u0026#34;a\u0026#34;] == 3; // true x[\u0026#34;b\u0026#34;] == false; // true x[\u0026#34;s\u0026#34;] == \u0026#34;xx\u0026#34;; // true #——————————— #add_member Json\u0026amp; add_member(const char* key, Json\u0026amp;\u0026amp; v); Json\u0026amp; add_member(const char* key, Json\u0026amp; v); 向 object 类型的 Json 中添加 key-value 键值对 (非 object 对象调用此方法后自动变成 object)。\n此方法将保持 key 的添加顺序,key 可能重复出现。\n参数 key 是 '\\0' 结尾的 C 字符串,参数 v 是所添加的值。\n参数 v 执行 move 语义,调用此方法后,v 变为 null 对象。\nNOTE: co/json 出于性能上的考虑,要求 key 中不能包含双引号。\n示例\nco::Json r; r.add_member(\u0026#34;a\u0026#34;, 1); // r -\u0026gt; {\u0026#34;a\u0026#34;:1} r.add_member(\u0026#34;d\u0026#34;, 3.3); // r -\u0026gt; {\u0026#34;a\u0026#34;:1, \u0026#34;d\u0026#34;:3.3} r.add_member(\u0026#34;s\u0026#34;, \u0026#34;xx\u0026#34;); // r -\u0026gt; {\u0026#34;a\u0026#34;:1, \u0026#34;d\u0026#34;:3.3, \u0026#34;s\u0026#34;:\u0026#34;xx\u0026#34;} co::Json x; x.add_member(\u0026#34;xx\u0026#34;, r); // r -\u0026gt; null r.add_member(\u0026#34;o\u0026#34;, co::Json().add_member(\u0026#34;x\u0026#34;, 3)); // r -\u0026gt; {\u0026#34;o\u0026#34;:{\u0026#34;x\u0026#34;:3}} co::Json().add_member(\u0026#34;o\u0026#34;, 1).add_member(\u0026#34;k\u0026#34;, 2); // -\u0026gt; {\u0026#34;o\u0026#34;:1,\u0026#34;k\u0026#34;:2} #erase void erase(uint32 i); void erase(int i); void erase(const char* key); 前两个版本删除 array 中的第 i 个元素。 第 3 个版本删除 object 中 key 对应的元素。 #push_back Json\u0026amp; push_back(Json\u0026amp;\u0026amp; v); Json\u0026amp; push_back(Json\u0026amp; v); 向 array 类型的 Json 中添加元素(非 array 对象调用此方法后自动变成 array)。\n参数 v 执行 move 语义,调用此方法后,v 变为 null 对象。\n示例\nco::Json r; r.push_back(1); // r -\u0026gt; [1] r.push_back(3.3); // r -\u0026gt; [1, 3.3] r.push_back(\u0026#34;xx\u0026#34;); // r -\u0026gt; [1, 3.3, \u0026#34;xx\u0026#34;] co::Json x; x.push_back(r); // r -\u0026gt; null, x -\u0026gt; [[1, 3.3, \u0026#34;xx\u0026#34;]] r.push_back(co::Json().push_back(1).push_back(2)); // r -\u0026gt; [[1,2]] #remove void remove(uint32 i); void remove(int i); void remove(const char* key); 前两个版本移除 array 中的第 i 个元素。 第 3 个版本移除 object 中 key 对应的元素。 remove 操作会将最后一个元素移动到删除的元素所在的位置。 #reset void reset(); 重置 Json 对象为 null。 #swap void swap(Json\u0026amp; v) noexcept; void swap(Json\u0026amp;\u0026amp; v) noexcept; 交换两个 Json 对象的内容。 #——————————— #operator[] Json\u0026amp; operator[](uint32 i) const; Json\u0026amp; operator[](int i) const; Json\u0026amp; operator[](const char* key) const; 重载 operator[],根据 index 或 key 获取 Json 对象中的元素。\n1-2, 适用于 array 类型,获取 array 对象的第 i 个元素,i 必须在 array 大小范围内。\n3, 适用于 object 类型,key 不存在时,会在 Json 中插入一个 null 对象。\n一般情况下,建议尽量用只读的 get() 方法取代此操作。\n示例\nco::Json r = { { \u0026#34;a\u0026#34;, 7 }, { \u0026#34;x\u0026#34;, { 1, 2, 3 } }, }; r[\u0026#34;a\u0026#34;].as_int(); // 7 r[\u0026#34;x\u0026#34;][0].as_int(); // 1 #has_member bool has_member(const char* key) const; 判断 Json 对象中是否存在 key 对应的元素。\n若调用此方法的 Json 对象不是 object 类型,返回 false。\n示例\nco::Json r = {{\u0026#34;a\u0026#34;, 1}}; r.has_member(\u0026#34;a\u0026#34;); // true r.has_member(\u0026#34;x\u0026#34;); // false #size uint32 size() const; 若 Json 是 object 或 array 类型,此方法返回元素个数。\n若 Json 是 string 类型,此方法返回字符串长度。\n所有其他类型,此方法返回 0。\n示例\nco::Json r = { {\u0026#34;x\u0026#34;, 1}, {\u0026#34;s\u0026#34;, \u0026#34;hello\u0026#34;}, {\u0026#34;a\u0026#34;, {1, 2, 3}}, }; r.size(); // 3 r[\u0026#34;x\u0026#34;].size(); // 0 r[\u0026#34;s\u0026#34;].size(); // 5 r[\u0026#34;a\u0026#34;].size(); // 3 #empty bool empty() const; 判断 Json 对象是否为空,等价于 size() == 0。 #string_size uint32 string_size() const; 返回 string 类型的长度,若调用此方法的 Json 对象不是 string 类型,返回 0。 #array_size uint32 array_size() const; 返回 array 类型的元素个数,若调用此方法的 Json 对象不是 array 类型,返回 0。 #object_size uint32 object_size() const; 返回 object 类型的元素个数,若调用此方法的 Json 对象不是 object 类型,返回 0。 #——————————— #str fastream\u0026amp; str(fastream\u0026amp; s, int mdp=16) const; fastring\u0026amp; str(fastring\u0026amp; s, int mdp=16) const; fastring str(int mdp=16) const; 将 Json 对象转换成字符串。 第 1 个版本将 JSON 字符串追加到 fastream 中,返回值与参数 s 相同。 第 2 个版本将 JSON 字符串追加到 fastring 中,返回值与参数 s 相同。 第 3 个版本直接返回 JSON 字符串。 参数 mdp 是 max decimal places 的缩写,表示最多保留多少位小数。 #pretty fastream\u0026amp; pretty(fastream\u0026amp; s, int mdp=16) const; fastring\u0026amp; pretty(fastring\u0026amp; s, int mdp=16) const; fastring pretty(int mdp=16) const; 将 Json 对象转换成更漂亮的 JSON 字符串,除了结果好看点,其他与 str() 一样。 #dbg fastream\u0026amp; dbg(fastream\u0026amp; s, int mdp=16) const; fastring\u0026amp; dbg(fastring\u0026amp; s, int mdp=16) const; fastring dbg(int mdp=16) const; 将 Json 对象转换成 debug 字符串,当 string 类型的值长度超过 512 字节时,截断取前 32 字节,其他与 str() 一样。 此方法一般用于打印日志。有些应用场景中,Json 对象可能包含较长的 string,如一个图片文件的 base64 编码,这个时候用 dbg() 取代 str() 方法,可以避免打印过多无意义的日志。 #parse_from bool parse_from(const char* s, size_t n); bool parse_from(const char* s); bool parse_from(const fastring\u0026amp; s); bool parse_from(const std::string\u0026amp; s); 从 JSON 字符串解析 Json 对象。 第 1 个版本中,s 不要求以 '\\0' 结尾。 解析成功时,返回 true,否则返回 false。 解析失败时,Json 变成 null 对象。 #代码示例 co::Json r = { { \u0026#34;a\u0026#34;, {1,2,3} } }; fastring s = r.str(); // s -\u0026gt; {\u0026#34;a\u0026#34;:[1,2,3]} fastring p = r.pretty(); LOG \u0026lt;\u0026lt; r.dbg(); // print json debug string LOG \u0026lt;\u0026lt; r; // the same as above, but is more efficient co::Json x; x.parse_from(s); x.parse_from(p); co::Json v = json::parse(s); #——————————— #begin iterator begin() const; 返回指向 Json 对象的 beginning iterator。 若调用此方法的 Json 对象不是 array 或 object 类型,返回值等于 end()。 #end const iterator::End\u0026amp; end() const; 返回一个假的 end iterator。 返回值实际上并不是一个 iterator 对象,但 iterator 可以与它比较,若 iterator 与 end() 相等,表示没有更多的元素了。 #iterator #operator== bool operator==(const End\u0026amp;) const; 判断 iterator 是否等于 End,End 是一个假的 end iterator。 #operator!= bool operator!=(const End\u0026amp;) const; 判断 iterator 是否不等于 End,End 是一个假的 end iterator。 #operator++ iterator\u0026amp; operator++(); 重载前缀 ++ 操作,不支持后缀 ++ 操作。 #operator* Json\u0026amp; operator*() const; 重载 operator*,此方法仅适用于 array 类型的 iterator。 Json 为 array 时,iterator 指向 array 中的元素。 #key const char* key() const; 此方法仅适用于 object 类型的 iterator。 Json 为 object 时,iterator 指向 object 中的 key-value 键值对,此方法返回该键值对中的 key。 #value Json\u0026amp; value() const; 此方法仅适用于 object 类型的 iterator。 Json 为 object 时,iterator 指向 object 中的 key-value 键值对,此方法返回该键值对中的 value 的引用。 #遍历 array 或 object co.json 支持用 iterator 遍历 array 或 object 类型的 Json 对象。\n// {\u0026#34;i\u0026#34;:7, \u0026#34;s\u0026#34;:\u0026#34;xx\u0026#34;, \u0026#34;a\u0026#34;:[123, true, \u0026#34;nice\u0026#34;]} co::Json r = { {\u0026#34;i\u0026#34;, 7}, {\u0026#34;s\u0026#34;, \u0026#34;xx\u0026#34;}, {\u0026#34;a\u0026#34;, {1, 2, 3}}, } // object for (auto it = r.begin(); it != r.end(); ++it) { LOG \u0026lt;\u0026lt; it.key() \u0026lt;\u0026lt; \u0026#34;: \u0026#34; \u0026lt;\u0026lt; it.value(); } // array co::Json\u0026amp; a = r[\u0026#34;a\u0026#34;]; for (auto it = a.begin(); it != a.end(); ++it) { LOG \u0026lt;\u0026lt; (*it); } #性能优化建议 有些用户喜欢用下面的方式添加元素:\nco::Json r; r[\u0026#34;a\u0026#34;] = 1; r[\u0026#34;s\u0026#34;] = \u0026#34;hello world\u0026#34;; 上面的操作虽然可行,但是效率并不高。operator[] 操作会先查找 key,找到了就更新值,没找到就插入新的元素。一般建议用 add_member() 方法取而代之:\nco::Json r; r.add_member(\u0026#34;a\u0026#34;, 1); r.add_member(\u0026#34;s\u0026#34;, \u0026#34;hello world\u0026#34;); 或者像下面这样构造 Json 对象:\nco::Json r = { {\u0026#34;a\u0026#34;, 1}, {\u0026#34;s\u0026#34;, \u0026#34;hello world\u0026#34;}, }; 对于只读操作,建议用 get() 取代 operator[],前者无副作用。\nJson r = {{\u0026#34;a\u0026#34;, 1}}; r.get(\u0026#34;a\u0026#34;).as_int(); // 1 "},{"id":37,"href":"/cn/co/concurrency/coroutine/pool/","title":"协程池","section":"协程","content":"include: co/co.h.\n#co::pool co::pool 是一种通用的协程池,它是协程安全的,内部存储 void* 类型的指针,可以用作连接池、内存池或其他用途的缓存。\n#constructor 1. pool(); 2. pool(pool\u0026amp;\u0026amp; p); 3. pool(const pool\u0026amp; p); 4. pool(std::function\u0026lt;void*()\u0026gt;\u0026amp;\u0026amp; ccb, std::function\u0026lt;void(void*)\u0026gt;\u0026amp;\u0026amp; dcb, size_t cap=(size_t)-1); 1, 默认构造函数,与 4 相比,ccb 与 dcb 为 NULL。\n2, 移动构造函数。\n3, 拷贝构造函数,仅将内部引用计数加 1。\n4, 参数 ccb 用于创建元素,参数 dcb 用于销毁元素,参数 cap 指定 pool 的最大容量,默认为 -1,不限容量。\n注意参数 cap 并不是总容量,它是对单个线程而言,在 co::pool 内部实现中,每个线程都有自己的 pool,如 cap 设置为 1024,调度线程有 8 个,则总容量是 8192。\n当 dcb 为 NULL 时,参数 cap 会被忽略,这是因为当元素个数超过最大容量时,co::pool 需要用 dcb 销毁多余的元素。\n示例\nclass T {}; co::pool p( []() { return (void*) new T; }, // ccb [](void* p) { delete (T*) p; }, // dcb ); #clear void clear() const; 清空 pool,可以在任何地方调用。 如果设置了 dcb,会用 dcb 销毁 pool 中的元素。 #pop void* pop() const; 从 pool 中取出一个元素,必须在协程中调用。 pool 为空时,若 ccb 不是 NULL,则调用 ccb() 创建一个元素并返回,否则返回 NULL。 此方法是协程安全的,不需要加锁。 #push void push(void* e) const; 将元素放回 pool 中,必须在协程中调用。 参数 e 为 NULL 时,直接忽略。 由于每个线程在内部拥有自己的 pool,push() 与 pop() 方法需要在同一个线程中调用。 若 pool 已经达到最大容量,且 dcb 不为 NULL,则直接调用 dcb(e) 销毁该元素。 此方法是协程安全的,不需要加锁。 #size size_t size() const; 返回当前线程的 pool 大小,必须在协程中调用。 #co::pool_guard co::pool_guard 在构造时自动从 co::pool 取出元素,析构时自动将元素放回 co::pool。同时,它还重载了 operator-\u0026gt;,可以像智能指针一样使用它。\ntemplate\u0026lt;typename T\u0026gt; class pool_guard; 参数 T 是 co::pool 中指针所指向的实际类型。 #constructor explicit pool_guard(co::pool\u0026amp; p); explicit pool_guard(co::pool* p); 从 co::pool 中取出一个元素,参数 p 是 co::pool 类的引用或指针。 #destructor ~pool_guard(); 将构造函数中获取的元素,放回 co::pool 中。 #get T* get() const; 获取从 co::pool 中取出的指针。 #operator-\u0026gt; T* operator-\u0026gt;() const; 返回从 co::pool 中取出的指针。 #operator* T\u0026amp; operator*() const; 返回内部指针所指向对象的引用。 #operator== bool operator==(T* p) const; 判断内部指针是否等于 p。 #operator!= bool operator!=(T* p) const; 判断内部指针是否不等于 p。 #operator bool explicit operator bool() const; 若内部指针不是 NULL,返回 true,否则返回 false。 #代码示例 class Redis; // assume class Redis is a connection to the redis server co::pool p( []() { return (void*) new Redis; }, // ccb [](void* p) { delete (Redis*) p; }, // dcb 8192 // cap ); void f() { co::pool_guard\u0026lt;Redis\u0026gt; rds(p); rds-\u0026gt;get(\u0026#34;xx\u0026#34;); } go(f); 上面的例子相当于 redis 连接池。如果使用 CLS 机制,一个协程一个连接,则 100 万协程需要建立 100 万连接,消耗较大。但使用 pool 机制,100 万协程可能只需要共用少量的连接。pool 机制比 CLS 更高效、更合理,这是 coost 不支持 CLS 的原因。\n"},{"id":38,"href":"/cn/co/other/time/","title":"时间","section":"其他","content":"include: co/time.h.\n#epoch time epoch 是一个特定的时刻 1970-01-01 00:00:00 UTC,epoch time 是从 epoch 时刻开始的时间,它受系统时间影响。\n#epoch::ms int64 ms(); 返回自 epoch 到当前时刻的时间,单位为毫秒。 #epoch::us int64 us(); 返回自 epoch 到当前时刻的时间,单位为微秒。 #monotonic time monotonic time 是单调递增时间,大多数平台实现为自系统启动开始的时间,一般用于计时,比系统时间稳定,不受系统时间的影响。\n#now::ms int64 ms(); 返回一个单调递增的时间戳,单位为毫秒。 在 mac 平台,如果系统不支持 CLOCK_MONOTONIC,则使用 epoch::ms()。 #now::us int64 us(); 返回一个单调递增的时间戳,单位为微秒。\n在 mac 平台,如果系统不支持 CLOCK_MONOTONIC,则使用 epoch::us()。\n示例\nint64 beg = now::us(); int64 end = now::us(); LOG \u0026lt;\u0026lt; \u0026#34;time used: \u0026#34; \u0026lt;\u0026lt; (end - beg) \u0026lt;\u0026lt; \u0026#34; us\u0026#34;; #时间字符串(now::str) // fm: 时间输出格式 fastring str(const char* fm=\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;); 此函数以指定格式返回当前系统时间的字符串形式,它基于 strftime 实现。\n示例\nfastring s = now::str(); // \u0026#34;2021-07-07 17:07:07\u0026#34; fastring s = now::str(\u0026#34;%Y\u0026#34;); // \u0026#34;2021\u0026#34; #sleep Linux 平台支持微秒级的 sleep,但 Windows 平台难以实现。因此,coost 仅提供毫秒、秒级的 sleep。\n#sleep::ms void ms(uint32 n); sleep 参数 n 指定的一段时间,单位是毫秒。 #sleep::sec void sec(uint32 n); sleep 参数 n 指定的一段时间,单位是秒。\n示例\nsleep::ms(10); // sleep for 10 milliseconds sleep::sec(1); // sleep for 1 second #co::Timer Timer 类是一个简单的计时器,基于 monotonic time 实现。\nv3.0.1 将 Timer 类加入 namespace co 中。 #constructor Timer(); 设置计时器的起始时间,对象创建完,即开始计时。 #ms int64 ms() const; 返回从计时开始到当前的时间,单位为毫秒。 #us int64 us() const; 返回从计时开始到当前的时间,单位为微秒。 #restart void restart(); 重置计时器起始时间,即重新开始计时。 #代码示例 co::Timer t; sleep::ms(10); int64 us = t.us(); t.restart(); sleep::ms(20); int64 ms = t.ms(); "},{"id":39,"href":"/cn/co/concurrency/coroutine/conf/","title":"配置项","section":"协程","content":"#配置 Coost 使用 co.flag 定义了协程相关的配置项,配置详细用法请参考 co.flag 文档。\n#co_hook_log DEF_bool(co_hook_log, false, \u0026#34;\u0026gt;\u0026gt;#1 print log for API hooks\u0026#34;); 打印 API hook 相关的日志,默认为 false。 v3.0.1 中将配置项 hook_log 重命名为 co_hook_log。 #co_sched_log DEF_bool(co_sched_log, false, \u0026#34;\u0026gt;\u0026gt;#1 print logs for coroutine schedulers\u0026#34;); 打印协程调度相关的调试日志,默认为 false。 v3.0.1 将配置项 co_debug_log 重命名为 co_sched_log。 #co_sched_num DEF_uint32(co_sched_num, os::cpunum(), \u0026#34;\u0026gt;\u0026gt;#1 number of coroutine schedulers\u0026#34;); 协程调度线程的数量,默认为系统 CPU 核数。目前的实现中,这个值最大也是系统 CPU 核数。 #co_stack_num DEF_uint32(co_stack_num, 8, \u0026#34;\u0026gt;\u0026gt;#1 number of stacks per scheduler, must be power of 2\u0026#34;); v3.0.1 新增,每个协程调度器的共享协程栈数量,该值必须是 2 的幂,默认为 8。 #co_stack_size DEF_uint32(co_stack_size, 1024 * 1024, \u0026#34;\u0026gt;\u0026gt;#1 size of the stack shared by coroutines\u0026#34;); 协程栈大小,默认为 1M。 "},{"id":40,"href":"/cn/co/other/tasked/","title":"定时任务","section":"其他","content":"include: co/tasked.h.\n#co::Tasked Tasked 类是一个简单的定时任务调度器,内部由单线程调度所有任务,但可以从任意线程添加任务。Tasked 中的任务阻塞时,会影响后面的所有任务,因此不推荐用 Tasked 调度可能会长时间阻塞的任务。\nv3.0.1 将 Tasked 类加入 namespace co 中。 #constructor 1. Tasked(); 2. Tasked(Tasked\u0026amp;\u0026amp; t); 1, 默认构造函数,对象创建完,调度线程即开始运行。 2, 移动构造函数,支持将 Tasked 对象放到 STL 容器中。 #destructor ~Tasked(); 析构函数,退出任务调度线程。 #F typedef std::function\u0026lt;void()\u0026gt; F; 任务类型,固定为 std::function\u0026lt;void()\u0026gt; 类型的函数。 #run_at void run_at(F\u0026amp;\u0026amp; f, int hour, int minute=0, int second=0); void run_at(const F\u0026amp; f, int hour, int minute=0, int second=0); 添加指定时刻运行的任务,f 将在 hour:minute:second 时刻运行一次。 hour 必须是 0-23 之间的整数,minute 与 second 必须是 0-59 之间的整数,默认为 0。 #run_daily void run_daily(F\u0026amp;\u0026amp; f, int hour=0, int minute=0, int second=0); void run_daily(const F\u0026amp; f, int hour=0, int minute=0, int second=0); 添加每天指定时刻运行的周期性任务,f 将在每天的 hour:minute:second 时刻运行一次。 hour 必须是 0-23 之间的整数,默认为 0,minute 与 second 是 0-59 之间的整数,默认为 0。 #run_every void run_every(F\u0026amp;\u0026amp; f, int n); void run_every(const F\u0026amp; f, int n); 添加每 n 秒运行一次的周期性任务。 #run_in void run_in(F\u0026amp;\u0026amp; f, int n); void run_in(const F\u0026amp; f, int n); 添加 n 秒后运行一次的任务。 #stop void stop(); 退出任务调度线程,析构函数中会自动调用此方法。 多次调用此方法是安全的。 #代码示例 co::Tasked s; // create and start the scheduler s.run_in(f, 3); // run f 3 seconds later s.run_every(std::bind(f, 0), 3); // run f every 3 seconds s.run_at(f, 23); // run f once at 23:00:00 s.run_daily(f); // run f at 00:00:00 every day s.run_daily(f, 23); // run f at 23:00:00 every day s.run_daily(f, 23, 30); // run f at 23:30:00 every day s.stop(); // stop the scheduler "},{"id":41,"href":"/cn/co/other/path/","title":"文件路径(path)","section":"其他","content":"include: co/path.h.\n#path 此部分功能移植于 golang,路径分隔符必须为 /。\n#path::clean fastring clean(const char* s, size_t n); fastring clean(const char* s); fastring clean(const fastring\u0026amp; s); 返回路径的最短等价形式,路径中连续的分隔符会被清除掉。\n示例\npath::clean(\u0026#34;\u0026#34;); // \u0026#34;.\u0026#34; path::clean(\u0026#34;./x//y/\u0026#34;); // \u0026#34;x/y\u0026#34; path::clean(\u0026#34;./x/..\u0026#34;); // \u0026#34;.\u0026#34; path::clean(\u0026#34;./x/../..\u0026#34;); // \u0026#34;..\u0026#34; #path::join template\u0026lt;typename ...S\u0026gt; inline fastring join(S\u0026amp;\u0026amp;... s); 将任意数量的字符串拼接成一个完整的路径,返回 path::clean 处理后的结果。\n参数中的空字符串将会被忽略。\n示例\npath::join(\u0026#34;\u0026#34;, \u0026#34;\u0026#34;); // \u0026#34;\u0026#34; path::join(\u0026#34;x\u0026#34;, \u0026#34;y\u0026#34;, \u0026#34;z\u0026#34;); // \u0026#34;x/y/z\u0026#34; path::join(\u0026#34;/x/\u0026#34;, \u0026#34;y\u0026#34;); // \u0026#34;/x/y\u0026#34; #path::split std::pair\u0026lt;fastring, fastring\u0026gt; split(const char* s, size_t n); std::pair\u0026lt;fastring, fastring\u0026gt; split(const char* s); std::pair\u0026lt;fastring, fastring\u0026gt; split(const fastring\u0026amp; s); 将路径切分为 dir, file 两部分,若路径中不含分隔符,则 dir 部分为空。\n返回结果满足性质 path = dir + file。\n示例\npath::split(\u0026#34;/\u0026#34;); // -\u0026gt; { \u0026#34;/\u0026#34;, \u0026#34;\u0026#34; } path::split(\u0026#34;/a\u0026#34;); // -\u0026gt; { \u0026#34;/\u0026#34;, \u0026#34;a\u0026#34; } path::split(\u0026#34;/a/\u0026#34;); // -\u0026gt; { \u0026#34;/a/\u0026#34;, \u0026#34;\u0026#34; } path::split(\u0026#34;/a/b\u0026#34;); // -\u0026gt; { \u0026#34;/a/\u0026#34;, \u0026#34;b\u0026#34; } #path::dir fastring dir(const char* s, size_t n); fastring dir(const char* s); fastring dir(const fastring\u0026amp; s); 返回路径的目录部分,返回值是 path::clean 处理后的结果。\n示例\npath::dir(\u0026#34;a\u0026#34;); // \u0026#34;.\u0026#34; path::dir(\u0026#34;a/\u0026#34;); // \u0026#34;a\u0026#34; path::dir(\u0026#34;/\u0026#34;); // \u0026#34;/\u0026#34; path::dir(\u0026#34;/a\u0026#34;); // \u0026#34;/\u0026#34;; path::dir(\u0026#34;/a/\u0026#34;); // \u0026#34;/a\u0026#34;; #path::base fastring base(const char* s, size_t n); fastring base(const char* s); fastring base(const fastring\u0026amp; s); 返回路径最后的一个元素。\ns 是空字符串时,返回 \u0026ldquo;.\u0026quot;。\ns 中字符全是 / 时,返回 \u0026ldquo;/\u0026quot;。\n其他情况,先将 s 末尾的 / 去掉。\n示例\npath::base(\u0026#34;\u0026#34;); // \u0026#34;.\u0026#34; path::base(\u0026#34;/\u0026#34;); // \u0026#34;/\u0026#34; path::base(\u0026#34;/a/\u0026#34;); // \u0026#34;a\u0026#34; path::base(\u0026#34;/a\u0026#34;); // \u0026#34;a\u0026#34; path::base(\u0026#34;/a/b\u0026#34;); // \u0026#34;b\u0026#34; #path::ext fastring ext(const char* s, size_t n); fastring ext(const char* s); fastring ext(const fastring\u0026amp; s); 返回路径中的文件扩展名。\n示例\npath::ext(\u0026#34;/a.c\u0026#34;); // \u0026#34;.c\u0026#34; path::ext(\u0026#34;a/b\u0026#34;); // \u0026#34;\u0026#34; path::ext(\u0026#34;/a.c/\u0026#34;); // \u0026#34;\u0026#34; path::ext(\u0026#34;a.\u0026#34;); // \u0026#34;.\u0026#34; "},{"id":42,"href":"/cn/co/other/fs/","title":"文件系统","section":"其他","content":"include: co/fs.h.\nfs 模块最小限度的实现了常用的文件系统操作,不同平台路径分隔符建议统一使用 /。\n#元数据操作 #fs::exists bool exists(const char* path); bool exists(const fastring\u0026amp; path); bool exists(const std::string\u0026amp; path); 判断文件是否存在,参数 path 是文件或目录路径。 #fs::fsize int64 fsize(const char* path); int64 fsize(const fastring\u0026amp; path); int64 fsize(const std::string\u0026amp; path); 获取文件大小,文件不存在或其他错误返回 -1。 #fs::isdir bool isdir(const char* path); bool isdir(const fastring\u0026amp; path); bool isdir(const std::string\u0026amp; path); 判断文件是否是目录,若 path 存在且是目录,则返回 true,否则返回 false。 #fs::mtime int64 mtime(const char* path); int64 mtime(const fastring\u0026amp; path); int64 mtime(const std::string\u0026amp; path); 获取文件的修改时间,文件不存在时返回 -1。 #fs::mkdir bool mkdir(const char* path, bool p=false); bool mkdir(const fastring\u0026amp; path, bool p=false); bool mkdir(const std::string\u0026amp; path, bool p=false); 创建目录,参数 path 是目录路径,参数 p 表示是否创建整个路径。 参数 p 默认为 false,仅当父目录存在时,才会创建目录;参数 p 为 true 时,相当于 mkdir -p ,父目录不存在时,先创建父目录。 #fs::mv bool mv(const char* from, const char* to); bool mv(const fastring\u0026amp; from, const fastring\u0026amp; to); bool mv(const std::string\u0026amp; from, const std::string\u0026amp; to); v3.0.2 新增,移动或重命名文件、目录,行为与 linux 系统中 mv 命令类似。 to 存在且为目录时,将 from 移动到目录 to 下面。 若目标与 from 类型相同(都是目录或文件),且目标不是非空目录,则目标会被覆盖掉。 // 假设目录 d 已存在 fs::mv(\u0026#34;xx.txt\u0026#34;, \u0026#34;xx.log\u0026#34;); // xx.txt -\u0026gt; xx.log fs::mv(\u0026#34;xx.txt\u0026#34;, \u0026#34;d\u0026#34;); // xx.txt -\u0026gt; d/xx.txt #fs::remove bool remove(const char* path, bool r=false); bool remove(const fastring\u0026amp; path, bool r=false); bool remove(const std::string\u0026amp; path, bool r=false); 删除文件或目录,参数 path 是路径。 参数 r 默认为 false,仅删除文件或空目录;r 为 true 时,相当于 rm -r,可删除非空目录。 #fs::rename bool rename(const char* from, const char* to); bool rename(const fastring\u0026amp; from, const fastring\u0026amp; to); bool rename(const std::string\u0026amp; from, const std::string\u0026amp; to); v3.0.2 中标记为 deprecated,可以使用 fs::mv 取代之。 #fs::symlink bool symlink(const char* dst, const char* lnk); bool symlink(const fastring\u0026amp; dst, const fastring\u0026amp; lnk); bool symlink(const std::string\u0026amp; dst, const std::string\u0026amp; lnk); 创建软链接,参数 dst 是目标文件或目录的路径,参数 lnk 是软链接的路径。 此函数先调用 fs::remove(lnk) 删除旧的软链接,再创建新的软链接文件。 在 windows 平台,此函数需要 admin 权限。 #代码示例 bool x = fs::exists(path); // 判断文件是否存在 bool x = fs::isdir(path); // 判断文件是否为目录 int64 x = fs::mtime(path); // 获取文件的修改时间 int64 x = fs::fsize(path); // 获取文件的大小 fs::mkdir(\u0026#34;a/b\u0026#34;); // mkdir a/b fs::mkdir(\u0026#34;a/b\u0026#34;, true); // mkdir -p a/b fs::remove(\u0026#34;x/x.txt\u0026#34;); // rm x/x.txt fs::remove(\u0026#34;a/b\u0026#34;); // rmdir a/b fs::remove(\u0026#34;a/b\u0026#34;, true); // rm -r a/b fs::rename(\u0026#34;a/b\u0026#34;, \u0026#34;a/c\u0026#34;); // mv a/b a/c fs::symlink(\u0026#34;/usr\u0026#34;, \u0026#34;x\u0026#34;); // ln -s /usr x #fs::file fs::file 类实现了文件的基本读写操作,与 fread 与 fwrite 不同,它内部没有缓存,直接读写文件。\n#file::file 1. file(); 2. file(file\u0026amp;\u0026amp; f); 3. file(const char* path, char mode); 4. file(const fastring\u0026amp; path, char mode); 5. file(const std::string\u0026amp; path, char mode); 1, 默认构造函数,创建一个空的 file 对象,不会打开任何文件。 2, 移动构造函数,支持将 file 对象放到 STL 容器中。 3-5, 打开指定的文件,参数 path 是文件路径,参数 mode 是打开模式。 mode 是 'r', 'w', 'a', 'm' 或 '+' 中的一种,r 是只读模式,w 是写模式,a 是追加模式,m 是修改模式,+ 是读写模式。 mode 为 'r' 时,文件必须存在,否则打开失败。 mode 为 'w' 时,文件不存在时自动创建,文件已存在时清空文件数据。 mode 为 'a', 'm' 或 '+' 时,文件不存在时自动创建,文件已存在时不清空文件数据。 '+' 是 v3.0 新增,此模式下,读与写共享文件指针,因此在读、写操作前,一般需要调用 seek() 方法设置偏移位置。 #file::~file ~file(); 析构函数,关闭之前打开的文件,释放相关资源。 #file::close void close(); 关闭文件,析构函数中会自动调用此方法。 多次调用此方法是安全的。 #file::exists bool exists() const; 判断文件是否存在。 文件可能被其他进程删除,调用此方法可以判断之前打开的文件,是否仍然存在。 #file::open bool open(const char* path, char mode); bool open(const fastring\u0026amp; path, char mode); bool open(const std::string\u0026amp; path, char mode); 此方法打开指定的文件,path 是文件路径,mode 是打开模式,见构造函数中的说明。 此方法在打开文件前,会先关闭之前打开的文件。 #file::operator bool explicit operator bool() const; 将 fs::file 转换为 bool 类型,文件成功打开时返回 true,否则返回 false。 #file::operator! bool operator!() const; 文件未打开或打开失败时返回 true,否则返回 false。 #file::path const char* path() const; 返回文件路径。 若 file 对象并未关联任何文件,则返回空字符串 \u0026quot;\u0026quot;。 #file::read 1. size_t read(void* buf, size_t n); 2. fastring read(size_t n); 1, 读取数据到指定的 buffer 中,n 是要读取的字节数,返回实际读取的字节数。 2, 与 1 类似,但以 fastring 的形式返回读取的数据,n 是要读取的字节数。 此方法在遇到文件尾或发生错误时,实际读取的字节数可能小于 n。 #file::seek void seek(int64 off, int whence=seek_beg); 设置文件指针的当前位置,参数 off 是偏移位置,参数 whence 是起始位置,可以是 file::seek_beg, file::seek_cur, file::seek_end 中的一种。 此方法对以 'a' (append) 模式打开的文件无效。 #file::size int64 size() const; 此方法返回文件的大小,文件未打开或打开失败时,调用此方法会返回 -1。 #file::write 1. size_t write(const void* s, size_t n); 2. size_t write(const char* s); 3. size_t write(const fastring\u0026amp; s); 4. size_t write(const std::string\u0026amp; s); 5. size_t write(char c); 1, 写入指定长度的数据。 2-4, 写入字符串。 5, 写入单个字符。 此方法返回实际写入的字节数,在磁盘空间不足或发生其他错误时,返回值可能小于 n。 此方法内部已经处理了 EINTR 错误,用户无需额外处理。 #代码示例 fs::file f; // empty file fs::file f(\u0026#34;xx\u0026#34;, \u0026#39;w\u0026#39;); // write mode f.open(\u0026#34;xx\u0026#34;, \u0026#39;m\u0026#39;); // reopen with modify mode f.open(\u0026#34;xx\u0026#34;, \u0026#39;r\u0026#39;); // read mode if (f) f.read(buf, 512); // read at most 512 bytes fastring s = f.read(32); // read at most 32 bytes and return fastring f.open(\u0026#34;xx\u0026#34;, \u0026#39;a\u0026#39;); // append mode if(f) f.write(buf, 32); // write 32 bytes f.write(\u0026#34;hello\u0026#34;); // write a C string f.write(\u0026#39;c\u0026#39;); // write a single character f.open(\u0026#34;xx\u0026#34;, \u0026#39;+\u0026#39;); // read/write mode f.seek(0); // seek to beginning before write f.write(\u0026#34;hello\u0026#34;); f.seek(0); // seek to beginning before read f.read(buf, 8); f.close(); // close the file #fs::fstream fs::file 不支持缓存,写小文件性能较差,为此,coost 另外实现了支持缓存的 fs::fstream 类,fs::fstream 只用于写文件,不支持读操作。\n#fstream::fstream 1. fstream(); 2. fstream(fstream\u0026amp;\u0026amp; fs); 3. explicit fstream(size_t cap); 4. fstream(const char* path, char mode, size_t cap=8192); 5. fstream(const fastring\u0026amp; path, char mode, size_t cap=8192); 6. fstream(const std::string\u0026amp; path, char mode, size_t cap=8192); 1, 默认构造函数,内部缓存大小为 8k。 2, 移动构造函数,可以将 fstream 对象放到 STL 容器中。 3, 用参数 cap 指定缓存的大小。 4-6, 打开指定的文件,path 是文件路径,mode 是模式,cap 是缓存大小,默认为 8k。 参数 mode 是 'w' 或 'a' 中的一种,不支持读模式。 mode 为 'w' 时,文件不存在时自动创建,文件已存在时清空文件数据。 mode 为 'a' 时,文件不存在时自动创建,文件已存在时不会清空文件数据,在文件尾追加写。 #fstream::~fstream ~fstream(); 析构函数,关闭打开的文件,释放相关的资源。 #fstream::append fstream\u0026amp; append(const void* s, size_t n); 追加数据,参数 n 是数据的长度。 #fstream::close void close(); 关闭文件,析构函数中会自动调用此方法。 多次调用此方法是安全的。 #fstream::flush void flush(); 将缓存中的数据写入文件。 #fstream::open bool open(const char* path, char mode); bool open(const fastring\u0026amp; path, char mode); bool open(const std::string\u0026amp; path, char mode); 打开指定的文件,参数 path 是文件路径,参数 mode 是打开模式,见构造函数中的说明。 此方法在打开文件前,会关闭之前打开的文件。 #fstream::operator bool explicit operator bool() const; 将 fs::fstream 转换为 bool 类型,文件成功打开时返回 true,否则返回 false。 #fstream::operator! bool operator!() const; 文件未打开或打开失败时返回 true,否则返回 false。 #fstream::operator\u0026laquo; 1. fstream\u0026amp; operator\u0026lt;\u0026lt;(const char* s); 2. fstream\u0026amp; operator\u0026lt;\u0026lt;(const fastring\u0026amp; s); 3. fstream\u0026amp; operator\u0026lt;\u0026lt;(const std::string\u0026amp; s); 4. fstream\u0026amp; operator\u0026lt;\u0026lt;(const fastream\u0026amp; s); 5. template\u0026lt;typename T\u0026gt; fstream\u0026amp; operator\u0026lt;\u0026lt;(T v); 1-3, 参数 s 是字符串类型。 4, 参数 s 是 fastream 类型。 5, 参数 T 可以是任意内置类型,如 bool, char, int, double 等。 #fstream::reserve void reserve(size_t n); 调整缓存容量,参数 n 是容量大小。若 n 小于之前的容量,则缓存容量保持不变。 #代码示例 fs::fstream s; // cache size: 8k fs::fstream s(4096); // cache size: 4k fs::fstream s(\u0026#34;path\u0026#34;, \u0026#39;a\u0026#39;); // append mode, cache size: 8k fs::fstream s(\u0026#34;path\u0026#34;, \u0026#39;w\u0026#39;, 4096); // write mode, cache size: 4k s.reserve(8192); // make cache size at least 8k s.open(\u0026#34;path\u0026#34;, \u0026#39;a\u0026#39;); // open with append mode if (s) s \u0026lt;\u0026lt; \u0026#34;hello world\u0026#34; \u0026lt;\u0026lt; 23; // operator\u0026lt;\u0026lt; s.append(\u0026#34;hello\u0026#34;, 5); // append s.flush(); // flush data in cache to file s.close(); // close the file #fs::dir v3.0.1 中新增 fs::dir 类,用于读目录。\n#dir::dir 1. dir(); 2. dir(dir\u0026amp;\u0026amp; d); 3. explicit dir(const char* path); 4. explicit dir(const fastring\u0026amp; path); 5. explicit dir(const std::string\u0026amp; path); 1, 默认构造函数,创建空的 dir 对象,不打开任何目录。 2, 移动构造函数。 3-5, 打开 path 指定的目录。 #dir::~dir ~dir(); 析构函数,关闭打开的目录,释放相关资源。 #dir::all co::vector\u0026lt;fastring\u0026gt; all() const; 读取目录下所有子项,. 与 .. 会被忽略。 #dir::begin iterator begin() const; 返回 begin iterator。 #dir::close void close(); 关闭目录,析构函数中会自动调用此方法。 多次调用此方法是安全的。 #dir::end iterator end() const; 返回 end iterator。 #dir::iterator dir::iterator 类用于遍历 dir 对象中打开的目录。\n#operator* fastring operator*() const; 返回 iterator 对应的文件或目录名。 #operator++ iterator\u0026amp; operator++(); 重载前缀 ++ 操作。 #operator== bool operator==(const iterator\u0026amp; it) const; 判断两个 iterator 是否相等。 #operator!= bool operator!=(const iterator\u0026amp; it) const; 判断两个 iterator 是否不相等。 #dir::open bool open(const char* path); bool open(const fastring\u0026amp; path); bool open(const std::string\u0026amp; path); 打开 path 指定的目录,打开目录前会先关闭之前打开的目录。 #dir::path const char* path() const; 返回目录的路径,若 dir 对象未关联任何目录,则返回空字符串 \u0026quot;\u0026quot;。 #代码示例 fs::dir d(\u0026#34;xx\u0026#34;); auto v = d.all(); // 读取所有子项 d.open(\u0026#34;abc\u0026#34;); for (auto it = d.begin(); it != d.end(); ++it) { cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; endl; } "},{"id":43,"href":"/cn/co/other/os/","title":"操作系统","section":"其他","content":"include: co/os.h.\n#os #os::cpunum int cpunum(); 返回系统 CPU 核数。 #os::cwd fastring cwd(); 返回当前工作目录。 在 windows 平台,返回值中的 \\ 会转换成 /。 #os::daemon void daemon(); 将当前进程放到后台运行,仅支持 linux 平台。 #os::env 1. fastring env(const char* name); 2. bool env(const char* name, const char* value); 1, 获取系统环境变量的值,参数 name 是环境变量名。 2, v2.0.2 新增,设置环境变量的值,成功时返回 true,否则返回 false。 #os::exename fastring exename(); 返回当前进程名,不含路径。 #os::exepath fastring exepath(); 返回当前进程的完整路径。 在 windows 平台,返回值中的 \\ 会转换成 /。 #os::homedir fastring homedir(); 返回当前用户的 home 目录。 在 windows 平台,返回值中的 \\ 会转换成 /。 #os::pid int pid(); 返回当前进程的 id。 #os::signal typedef void (*sig_handler_t)(int); sig_handler_t signal(int sig, sig_handler_t handler, int flag=0); 设置信号处理函数,参数 sig 是信号值,参数 flag 是 SA_RESTART,SA_ONSTACK 等选项的组合。\n参数 flag 仅适用于 linux/mac 平台,windows 平台会忽略此参数。\n此函数返回旧的信号处理函数。\n示例\nvoid f(int); os::signal(SIGINT, f); // user defined handler os::signal(SIGABRT, SIG_DFL); // default handler os::signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE "},{"id":44,"href":"/cn/co/build/","title":"编译","section":"参考文档","content":"#编译器要求 各平台需要安装的编译器如下:\nLinux: gcc 4.8+ Mac: clang 3.3+ Windows: vs2015+ #xmake co 推荐使用 xmake 作为构建工具。\n#安装 xmake windows, mac 与 debian/ubuntu 可以直接去 xmake 的 release 页面下载安装包,其他系统请参考 xmake 的 Installation 说明。\n#快速构建 在 co 根目录执行下述命令构建:\nxmake -a # 构建所有项目 (libco, gen, test, unitest) 若需要使用 HTTP 或 SSL 特性,则可以用下面的命令构建:\nxmake f --with_libcurl=true --with_openssl=true xmake -a 启用 HTTP 或 SSL 特性时,xmake 会自动从网络安装 libcurl 与 openssl,可能需要花点时间。\n命令行中的 -a 表示构建 co 中的所有项目,如果不加 -a,默认只会构建 libco。另外,可以用 -v 或 -vD 让 xmake 打印更详细的编译信息:\nxmake -v -a #编译选项 xmake 提供了 xmake f 命令,用于配置编译选项。需要注意的是,多个配置选项必须在一条 xmake f 命令中完成,若多次执行 xmake f 命令,后面的会覆盖前面的配置。\n#编译 debug 版本的 libco xmake f -m debug xmake -v #编译动态库 xmake f -k shared xmake -v #编译 32 位的 libco Windows xmake f -a x86 xmake -v Linux xmake f -a i386 xmake -v xmake f 命令中的 -a 表示 arch,不同平台支持的 arch 可能不一样,可以执行 xmake f --help 命令查看详情。\n#Windows 平台指定 vs_runtime co 在 Windows 平台默认使用 MT 运行库,用户可以用下面的命令配置 vs_runtime:\nxmake f --vs_runtime=MD xmake -v #Android 与 IOS 支持 co 在 Android 与 IOS 平台也能编译,详情见 Github Actions。目前暂未在 Android 与 IOS 上测试。\nandroid xmake f -p android --ndk=/path/to/android-ndk-r21 xmake -v ios xmake f -p iphoneos xmake -v #构建及运行 unitest 代码 co/unitest 是单元测试代码,可以执行下述命令构建及运行:\nxmake -b unitest # build unitest xmake r unitest -a # 执行所有单元测试 xmake r unitest -os # 执行单元测试 os xmake r unitest -json # 执行单元测试 json #构建及运行 test 代码 co/test 是一些测试代码,在 co/test 目录或其子目录下增加 xx.cc 源文件,然后在 co 根目录下执行 xmake -b xx 即可构建。\nxmake -b flag # 编译 test/flag.cc xmake -b log # 编译 test/log.cc xmake -b json # 编译 test/json.cc xmake -b rpc # 编译 test/rpc.cc xmake r flag -xz # 测试 flag 库 xmake r log # 测试 log 库 xmake r log -cout # 终端也打印日志 xmake r log -perf # log 库性能测试 xmake r json # 测试 json xmake r rpc # 启动 rpc server xmake r rpc -c # 启动 rpc client #构建及使用 gen xmake -b gen cp gen /usr/local/bin/ gen hello_world.proto #安装 libco 构建完 libco 后,可以用 xmake install 命令安装 libco 到指定的目录:\nxmake install -o pkg # 打包安装到 pkg 目录 xmake i -o pkg # 同上 xmake i -o /usr/local # 安装到 /usr/local 目录 #从 xmake repo 安装 libco xrepo install -f \u0026#34;openssl=true,libcurl=true\u0026#34; coost #cmake izhengfan 帮忙提供了 cmake 编译脚本:\n默认只编译 libco。 编译生成的库文件在 build/lib 目录下,可执行文件在 build/bin 目录下。 可以用 BUILD_ALL 指定编译所有项目。 可以用 CMAKE_INSTALL_PREFIX 指定安装目录。 cmake 只提供简单的编译选项,若需要更复杂的配置,请使用 xmake。 #默认构建 libco mkdir build \u0026amp;\u0026amp; cd build cmake .. make -j8 #构建所有项目 mkdir build \u0026amp;\u0026amp; cd build cmake .. -DBUILD_ALL=ON -DCMAKE_INSTALL_PREFIX=pkg make -j8 #启用 HTTP 与 SSL 特性 使用 HTTP 或 SSL 特性,需要安装 libcurl, zlib, 以及 openssl 1.1.0 或以上版本。\nmkdir build \u0026amp;\u0026amp; cd build cmake .. -DBUILD_ALL=ON -DWITH_LIBCURL=ON -DWITH_OPENSSL=ON make -j8 "},{"id":45,"href":"/cn/co/faq/","title":"FAQ","section":"参考文档","content":"#FAQ #如何修改配置项的值? flag 是 coost 中的配置管理组件,它定义的配置项(也称flag),可以通过命令行参数或配置文件修改,具体用法见命令行中使用 flag及配置文件。\n使用 flag 库需要在 main 函数开头调用 flag::parse 解析命令行参数。 另外也可以通过 API flag::set_value 修改配置项的值:\nint main(int argc, char** argv) { flag::set_value(\u0026#34;co_sched_num\u0026#34;, \u0026#34;2\u0026#34;); flag::set_value(\u0026#34;version\u0026#34;, \u0026#34;v3.1.4\u0026#34;); flag::parse(argc, argv); return 0; } 此处 flag::set_value 在 flag::parse 之前,这样用户仍然能够通过命令行参数、配置文件修改相关配置项的值。 #程序启动时如何指定配置文件? 用户使用 flag 定义配置项后,程序启动时可用如下方式指定配置文件:\n./xx xx.conf # 此种情况,配置文件名需要以 .conf 或 config 结尾 ./xx -conf xx.conf # 此种情况,配置文件名没有上述限制 注意 main 函数开头需要调用 flag::parse 方法。另外,用户可以用 -mkconf 自动生成配置文件:\n# 自动生成配置文件 xx.conf ./xx -mkconf #如何使用 flag 别名? flag 支持别名,定义了别名的 flag,在命令行参数、配置文件中,可以用别名代替原名。给 flag 添加别名有两种方法,第一种是在定义 flag 时指定:\nDEF_bool(debug, false, \u0026#34;xxx\u0026#34;); // 无别名 DEF_bool(debug, false, \u0026#34;xxx\u0026#34;, d); // 别名 d DEF_bool(debug, false, \u0026#34;xxx\u0026#34;, d, dbg); // 两个别名: d, dbg 还有一种情况,flag 已经定义,且用户无法修改该定义时,可以用 flag::alias 设置别名,如 coost 内部定义的 string 类型的 flag version,用户可以用如下方式给它设置别名:\nint main(int argc, char** argv) { flag::alias(\u0026#34;version\u0026#34;, \u0026#34;v\u0026#34;); flag::parse(argc, argv); return 0; } flag::alias 须在 flag::parse 前调用。 #如何配置日志模块? coost 日志模块使用 flag 定义配置项,具体的配置项见 log 参考文档。用户可以在命令行参数或配置文件中修改这些配置项的值,具体用法见 flag 参考文档。\n#如何设置 JSON 中的小数位数? co::Json 的 str 方法有个参数 mdp,可用于指定最大有效小数位数(默认是 16):\nco::Json x = { {\u0026#34;a\u0026#34;, 3.14159}, {\u0026#34;b\u0026#34;, 1.2345e-5}, }; co::print(x.str()); // {\u0026#34;a\u0026#34;:3.14159,\u0026#34;b\u0026#34;:1.2345e-5} co::print(x.str(2)); // {\u0026#34;a\u0026#34;:3.14,\u0026#34;b\u0026#34;:1.23e-5} #如何实现 JSON 与结构体互转? coost v3.0.1 基于 flex 与 byacc 重写了代码生成工具 gen,可以根据 proto 文件中结构体的定义,自动生成 JSON 与结构体互转的代码,代码示例见 j2s。\n#频繁创建、销毁线程导致内存泄露? 频繁创建、销毁线程是设计上的错误。\ncoost 中使用了 TLS (线程局部存储),频繁创建、销毁线程,一些 TLS 资源在线程退出时并不会自动销毁,从而导致内存泄露。\n尽管 C++ 标准中的 thread_local,可以保证 TLS 对象在线程退出时自动析构,但是在线程退出时析构 TLS 对象并不总是正确的行为,如基于 TLS 实现的内存分配器,一个线程退出时,该线程中分配的内存,可能还在被其他线程使用,这个时候释放该线程的 TLS 资源,可能会导致程序崩溃。\n#如何设置协程调度线程的数量? coost 中协程支持多线程调度,默认线程数为系统 CPU 核数,用户可以通过配置项(flag) co_sched_num 设置该值,该值最大不能超过系统 CPU 核数。配置用法可参考如何修改配置项的值。\n#创建协程的数量是否有限制? 创建协程的数量仅受限于系统物理资源(主要是内存)。coost 协程采用共用栈的实现方式,实际内存占用量较少。Linux 上的简单测试显示 1000 万协程只用了 2.8G 内存(仅供参考)。\n#协程是否会出现栈溢出? coost 协程默认栈大小为 1M,足够大,一般不会出现栈溢出。如果 1M 不够,可以通过配置项改大一些。\n#协程共享栈在使用上有哪些限制? 协程中不要直接访问其他协程中的局部变量,因为共享栈上的局部变量可能被覆盖。\n#协程中可以使用三方网络库吗? coost 协程 hook 了系统 socket 相关 API,可以在协程中直接使用 mysql、redis 等三方网络库。详情见协程中使用三方网络库。\n#主线程可以设置成协程调度线程吗? coost 中调度线程默认是独立于主线程运行的,可借助 co::main_sched 将主线程设置为协程调度线程:\n#include \u0026#34;co/co.h\u0026#34; #include \u0026#34;co/cout.h\u0026#34; int main(int argc, char** argv) { flag::parse(argc, argv); co::print(\u0026#34;main thread id: \u0026#34;, co::thread_id()); auto s = co::main_sched(); for (int i = 0; i \u0026lt; 8; ++i) { go([]{ co::print(\u0026#34;thread: \u0026#34;, co::thread_id(), \u0026#34; sched: \u0026#34;, co::sched_id()); }); } s-\u0026gt;loop(); // loop forever return 0; // fake return } 须在创建协程之前调用 co::main_sched (该方法返回 MainSched 指针) 将主线程标记为调度线程,创建协程后,再调用 MainSched 的 loop 方法启动该协程调度器。 "}]