diff --git a/.gitignore b/.gitignore
index 65d831f..1a29d44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
-**/*.o
+.ccls-cache
+.vscode
+debug
+release
+stage
+compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..6baa161
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,100 @@
+cmake_minimum_required(VERSION 3.13)
+# -----------------------------------------------------------------------------
+# 默认编译器
+# -----------------------------------------------------------------------------
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE Debug)
+endif(NOT CMAKE_BUILD_TYPE)
+set(CLANG_GCC_TOOLCHAIN "<default>" CACHE PATH "gcc toolchain install prefix, used with clang --gcc-toolchain option")
+if (NOT (CLANG_GCC_TOOLCHAIN STREQUAL "<default>"))
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --gcc-toolchain=${CLANG_GCC_TOOLCHAIN}")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --gcc-toolchain=${CLANG_GCC_TOOLCHAIN}")
+endif()
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+# -----------------------------------------------------------------------------
+# 项目源文件
+# -----------------------------------------------------------------------------
+project(FLAME)
+execute_process(COMMAND find ${CMAKE_SOURCE_DIR}/src -name *.cpp
+    COMMAND tr "\n" ";"
+    OUTPUT_VARIABLE SOURCES)
+add_library(FLAME SHARED ${SOURCES})
+# -----------------------------------------------------------------------------
+# 依赖项目录
+# -----------------------------------------------------------------------------
+set(VENDOR_BOOST /data/vendor/boost-1.70.0)
+set(VENDOR_PARSER /data/vendor/parser-1.0.0)
+set(VENDOR_LLTOML /data/vendor/lltoml-1.0.0)
+set(VENDOR_HIREDIS /data/vendor/hiredis-0.14.0)
+set(VENDOR_AMQP /data/vendor/amqpcpp-4.1.5)
+set(VENDOR_OPENSSL /data/vendor/openssl-1.1.1c)
+set(VENDOR_MONGODB /data/vendor/mongoc-1.14.0)
+set(VENDOR_RDKAFKA /data/vendor/rdkafka-1.1.0)
+set(VENDOR_PHP /data/server/php-7.2.19)
+set(VENDOR_PHPEXT /data/vendor/phpext-2.2.0)
+set(VENDOR_CARES /data/vendor/cares-1.15.0)
+set(VENDOR_NGHTTP2 /data/vendor/nghttp2-1.39.1)
+set(VENDOR_CURL /data/vendor/curl-7.65.0)
+set(VENDOR_MARIA /data/vendor/mariac-3.1.2)
+
+execute_process(COMMAND ${VENDOR_PHP}/bin/php-config --includes
+    COMMAND sed "s/ *-I/;/g"
+    OUTPUT_VARIABLE VENDOR_PHP_INCLUDES
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+# -----------------------------------------------------------------------------
+# 编译选项
+# -----------------------------------------------------------------------------
+target_compile_options(FLAME BEFORE
+	PRIVATE "-std=c++17"
+    PRIVATE "-pthread")
+target_link_options(FLAME BEFORE
+	PRIVATE "-static-libstdc++"
+    PRIVATE "-pthread")
+set_target_properties(FLAME PROPERTIES
+    PREFIX ""
+    OUTPUT_NAME "flame")
+# 包含路径
+target_include_directories(FLAME SYSTEM PRIVATE
+    ${VENDOR_PARSER}/include
+    ${VENDOR_LLTOML}/include
+    ${VENDOR_HIREDIS}/include
+    ${VENDOR_AMQP}/include
+    ${VENDOR_RDKAFKA}/include
+    ${VENDOR_MONGODB}/include/libmongoc-1.0
+    ${VENDOR_MONGODB}/include/libbson-1.0
+    ${VENDOR_MARIA}/include 
+    ${VENDOR_NGHTTP2}/include
+    ${VENDOR_CURL}/include
+    ${VENDOR_PHPEXT}/include
+    ${VENDOR_PHP_INCLUDES}
+    ${VENDOR_OPENSSL}/include
+    ${VENDOR_BOOST}/include
+)
+# 链接库
+target_link_libraries(FLAME
+    ${VENDOR_HIREDIS}/lib/libhiredis.a
+    ${VENDOR_LLTOML}/lib/liblltoml.a
+    ${VENDOR_PHPEXT}/lib/libphpext.a
+    ${VENDOR_MARIA}/lib/mariadb/libmariadbclient.a
+    ${VENDOR_MONGODB}/lib/libmongoc-static-1.0.a
+    ${VENDOR_MONGODB}/lib/libbson-static-1.0.a
+    ${VENDOR_AMQP}/lib/libamqpcpp.a
+    ${VENDOR_RDKAFKA}/lib/librdkafka.a
+    ${VENDOR_BOOST}/lib/libboost_program_options.a
+    ${VENDOR_BOOST}/lib/libboost_context.a
+    ${VENDOR_BOOST}/lib/libboost_system.a
+    ${VENDOR_BOOST}/lib/libboost_thread.a
+    ${VENDOR_BOOST}/lib/libboost_filesystem.a
+    ${VENDOR_CURL}/lib/libcurl.a
+    ${VENDOR_NGHTTP2}/lib/libnghttp2.a
+    ${VENDOR_CARES}/lib/libcares.a
+    ${VENDOR_OPENSSL}/lib/libssl.a
+    ${VENDOR_OPENSSL}/lib/libcrypto.a
+    resolv
+    z
+    dl
+    m
+    nsl
+    rt
+)
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..d72693d
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017-2019 terrywh
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 2a8223f..0000000
--- a/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-ROOT_TERRYWH=/data/wuhao/cpdocs/github.com/terrywh
-
-EXTENSION_NAME=flame
-EXTENSION_VERSION=0.1.0
-
-ROOT_PROJECT=${ROOT_TERRYWH}/php-${EXTENSION_NAME}
-
-VENDOR_LIBRARY= ${ROOT_TERRYWH}/libphpext/libphpext.a /data/vendor/boost/lib/libboost_system.a -lpthread
-
-PHP=php
-PHP_CONFIG=php-config
-
-CXX?=g++
-CXXFLAGS?= -g -O0
-INCLUDE=-I${ROOT_TERRYWH}/libphpext `${PHP_CONFIG} --includes`
-LIBRARY=${VENDOR_LIBRARY}
-
-# SOURCES=$(wildcard src/*.cpp) $(wildcard src/**/*.cpp) $(wildcard src/**/**/*.cpp)
-SOURCES=src/extension.cpp src/core.cpp src/net/init.cpp src/net/udp_socket.cpp src/net/tcp_socket.cpp src/net/tcp_server.cpp
-OBJECTS=$(SOURCES:%.cpp=%.o)
-
-EXTENSION=${EXTENSION_NAME}.so
-
-.PHONY: install clean
-
-# 暂时先将 libphpext.a 作为依赖（库还不稳定）
-${EXTENSION}: ${OBJECTS} ${ROOT_TERRYWH}/libphpext/libphpext.a
-	${CXX} -shared ${OBJECTS} ${LIBRARY} -Wl,-rpath='$$ORIGIN/' -Wl,-rpath='/usr/local/gcc6/lib64/' -o ${EXTENSION_NAME}.so
-%.o: %.cpp
-	${CXX} -std=c++11 -fPIC -DEXTENSION_NAME=\"${EXTENSION_NAME}\" -DEXTENSION_VERSION=\"${EXTENSION_VERSION}\" ${CXXFLAGS} ${INCLUDE} -c $^ -o $@
-
-clean:
-	rm -f ${EXTENSION} ${OBJECTS}
-install: ${EXTENSION}
-	cp -f ${EXTENSION} `${PHP_CONFIG} --extension-dir`
-
-test:
-	${PHP} udp/server.php
diff --git a/README.md b/README.md
index 75a1e88..2a2855e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,232 @@
-#### 依赖
-##### c++ 11
-* -std=c+11
-##### boost
-* system
-* asio
+### FLAME
+为解决 PHP 被认为“并发处理能力不足”的问题，我最初在业务中试用 [Swoole](https://www.swoole.com/) 框架；估计由于使用的是 Swoole 相对早期的版本，在实际业务中出现了各式各样的问题（很多都是内存问题）；同时很多同学对 Swoole 中提供的进程使用形式，都感觉把握比较吃力；  
+于是在各方支持下，开发了 FLAME 框架：
+
+* 基于协程机制的异步化网络底层（ TCP / UDP / HTTP)；
+* 简单的多进程模式 （ SO_REUSEPORT ）；
+* 基于协程的队列机制（ 类似 Go 的 Channel）；
+* 多种驱动异步化支持（ MySQL / Redis / MongoDB / RabbitMQ / Kafka )；
+
+同时为了简化日常应用开发成本，及很多重复性工作，框架还提供了：
+* 平滑起停；
+* 多进程日志、登等级过滤、文件重载；
+* 数据库连接池机制；
+* 异步进程；
+* 等等（BSON / SNAPPY / INTERFACE）...
+
+目前，FLAME 已经使用在了公司内各种业务线；实际使用中，框架在**并行处理性能**方面表现十分**突出**，并在持续的改进、完善下，框架在**稳定性**方面也有了**不错**的表现；  
+不过，由于时间、能力问题，未能详尽的测试整个框架的各项功能，可能还存在一些问题、缺陷；感谢大家的包容、反馈与帮助（大礼参拜）
+
+### 示例
+以下代码实现了几个简单的 HTTP 接口，展示了框架的基本使用方法：
+
+``` PHP
+<?php
+// 框架初始化
+flame\init("http_server_demo");
+// 第一个（主）协程
+flame\go(function() {
+    // 创建 HTTP 服务器（监听）
+    $server = new flame\http\server(":::56101");
+
+    $server
+        ->before(function($req, $res) { // 前置处理器（HOOK）
+            $req->data["before"] = flame\time\now(); // 记录请求开始时间
+        })
+        ->get("/hello", function($req, $res) { // 路径处理器
+            // 简单响应方式
+            $res->status = 200;
+            $res->body = "world";
+        })
+        ->post("/hello/world", function($req, $res) { // 路径处理器
+            // Transfer-Encoding: Chunked
+            $res->write_header(200);
+            $res->write("CHUNKED RESPONSE:")
+            $res->write($res->body);
+            $res->end();
+        })
+        ->after(function($req, $res, $r) {
+            // 后置处理器（HOOK）
+            flame\log\trace($req->method, $req->path // 请求时长日志记录
+                , "in", (flame\time\now() - $req->data["before"]), "ms");
+            if(!$r) {
+                $res->status = 404;
+                $res->file(__DIR__."/404.html"); // 响应文件
+            }
+        });
+    $server->run();
+});
+// 启动（调度）
+flame\run();
+```
+
+### 功能
+
+* **core** - 核心，框架初始化设置，协程启动，协程队列，协程锁等；
+* **time** - 时间相关，协程休眠，当前时间等；
+> 毫秒级时间戳及缓存机制;  
+* **log** - 简单日志记录功能:
+> 多进程统一日志记录;  
+> 支持日志登记过滤设置;  
+> 支持通过信号进行日志重载;  
+* **os** - 操作系统相关信息获取，异步进程启停操作等；
+> 获取本地网卡地址;
+* **tcp** - 封装 TCP 相关服务端客户端功能；
+* **udp** - 封装 UDP 相关服务端客户端功能；
+* **http** - 简单的 HTTP 客户端/服务端封装;
+> 客户端支持长连 Keep-Alive 及相关连接数控制;  
+> 客户端支持 HTTPS 及 HTTP/2 协议部分功能;  
+> 客户端暂不支持 form-data/multipart 形式的请求;  
+> 服务器**不支持** HTTPS 可考虑使用 NGINX 等进行反向代理;  
+* **mysql** - 简单 MySQL 客户端:
+> 提供部分简化方法，如 `insert/delete/one` 等, 自动进行 ESCAPE 转义;  
+> 内部使用连接池形式自动连接复用;  
+> 支持事务;  
+* **redis** - 简单 Redis 客户端:
+> 内部使用连接池形式自动连接复用;  
+> 暂不支持 SUBSCRIBE 相关指令;  
+* **mongodb** - 简单 MongoDB 客户端:
+> 提供部分简化方法，如 `insert/delete/one/count` 等;  
+> 内部使用连接池自动连接复用;  
+* **rabbitmq** - 简单 RabbitMQ 生产消费支持:
+> 支持使用协程进行"并行"消费;
+* **kafka** - 简单 Kafka 生产消费支持:
+> 仅支持 Kafka 0.10+ 群组消费形式;  
+> 支持使用协程进行"并行"消费;  
+* **hash** - 提供了若干哈希算法；
+* **encoding** - 提供了若干编码、序列化函数；
+* **compress** - 提供了若干压缩算法；
+
+### 说明
+* [API 文档](https://github.com/terrywh/php-flame/tree/master/doc)
+* [常见问题](https://github.com/terrywh/php-flame/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
+* 自动完成（IDE提示）- 目录 `/doc` 下使用 PHP Doc 语法定义和说明 API 接口，可挂接 IDE 自动完成功能；
+    * VSCode/ (php-intelephense)
+    > ``` Bash
+    > ln -s /path/to/php-flame/doc /path/to/extensions/bmewburn.vscode-intelephense-client-x.x.xx/node_modules/intelephense/lib/stub/flame
+    > ```
+    * PhpStorm
+    > [Configuring Include Paths](https://www.jetbrains.com/help/phpstorm/configuring-include-paths.html#Configuring_Include_Paths.xml)
+
+### 其他
+<details><summary>依赖项编译安装，仅供参考</summary>
+<p>
+
+#### boost
+``` Bash
+./bootstrap.sh --prefix=/data/vendor/boost-1.70.0
+./b2 --prefix=/data/vendor/boost-1.70.0 cxxflags="-fPIC" variant=release link=static threading=multi install
+```
+
+#### cpp-parser
+``` Bash
+make install
+```
+
+#### lltoml
+``` Bash
+CFLAGS="-O2 -DNDEBUG" CXXFLAGS="-O2 -DNDEBUG" make
+make install
+```
+
+#### hiredis
+``` Bash
+CC=gcc make
+PREFIX=/data/vendor/hiredis-0.14.0 make install
+# 未提供禁用动态库选项
+# rm /data/vendor/hiredis-0.14.0/lib/*.so*
+```
+
+#### openssl
+``` Bash
+CC=gcc CXX=g++ ./Configure no-shared --prefix=/data/vendor/openssl-1.1.1c linux-x86_64
+make && make install
+```
+
+#### AMQP-CPP
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ CXXFLAGS="-fPIC -I/data/vendor/openssl-1.1.1c/include" LDFLAGS="-L/data/vendor/openssl-1.1.c/lib" cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/amqpcpp-4.1.5 -DCMAKE_BUILD_TYPE=Release -DAMQP-CPP_LINUX_TCP=ON ../
+make && make install
+```
+
+<!--
+#### sasl2
+``` Bash
+PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig CC=gcc CXX=g++ CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=/data/vendor/sasl2 --with-openssl=/data/vendor/openssl-1.1.1c --without-ldap --enable-shared=no
+make && make install
+```
+-->
+
+#### mongoc-driver
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-fPIC" LDFLAGS="-pthread -ldl" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX=/data/vendor/mongoc-1.14.0 -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE=Release -DENABLE_STATIC=ON -DENABLE_SASL=OFF -DENABLE_SHM_COUNTERS=OFF -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ../
+make && make install
+# 未提供 ENABLE_SHARED=OFF 或类似选项
+# rm /data/vendor/mongoc-1.14.0/lib/*.so*
+```
+
+#### rdkafka
+``` Bash
+CC=gcc CXX=g++ PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig ./configure --prefix=/data/vendor/rdkafka-1.1.0 --disable-sasl
+make && make install
+# rm /data/vendor/rdkafka-1.1.0/lib/*.so*
+cp src/snappy.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
+cp src/rdmurmur2.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
+cp src/xxhash.h /data/vendor/rdkafka-1.1.0/include/librdkafka/
+```
+
+#### PHP
+``` Bash
+CC=gcc CXX=g++ CFLAGS="-pthread" ./configure --prefix=/data/server/php-7.2.19 --with-config-file-path=/data/server/php-7.2.19/etc --disable-simplexml --disable-xml --disable-xmlreader --disable-xmlwriter --with-readline --enable-mbstring --without-pear --with-zlib --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+make && make install
+```
+
+<!--
+# external openssl extension for php
+CC=gcc CXX=g++ LDFLAGS="-pthread -ldl" ./configure --with-php-config=/data/server/php-7.2.19/bin/php-config --with-openssl=/data/vendor/openssl-1.1.1c --build=x86_64-linux-gnu
+-->
+
+#### libphpext
+``` Bash
+CXXFLAGS="-O2 -DNDEBUG" make
+make install
+```
+
+#### c-ares
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ cmake -DCARES_SHARED=OFF -DCARES_STATIC=ON -DCARES_STATIC_PIC=ON -DCMAKE_INSTALL_PREFIX=/data/vendor/cares-1.15.0 -DCMAKE_BUILD_TYPE=Release ../
+make && make install
+```
+
+#### nghttp2
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-fPIC" CXXFLAGS="-fPIC" PKG_CONFIG_PATH="/data/vendor/cares-1.15.0/lib/pkgconfig:/data/vendor/openssl-1.1.1c/lib/pkgconfig" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/vendor/nghttp2-1.39.1 -DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF -DENABLE_STATIC_LIB=ON -DCMAKE_INSTALL_LIBDIR:PATH=lib ../
+make && make install
+```
+
+#### curl
+``` Bash
+mkdir stage && cd stage
+# quote: cmake for curl is poorly maintained
+# segfault @7.65.1 
+CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure --with-ssl=/data/vendor/openssl-1.1.1c --enable-ares=/data/vendor/cares-1.15.0 --with-nghttp2=/data/vendor/nghttp2-1.39.1 --enable-shared=no --enable-static --enable-ipv6 --without-brotli --without-libidn2 --without-libidn --without-librtmp --disable-unix-sockets --disable-ftp --disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-file --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --without-libpsl --prefix=/data/vendor/curl-7.65.0
+make && make install
+```
+
+#### maria-connector-c
+``` Bash
+mkdir stage && cd stage
+CC=gcc CXX=g++ CFLAGS="-pthread" CXXFLAGS="-pthread" PKG_CONFIG_PATH=/data/vendor/openssl-1.1.1c/lib/pkgconfig:/data/vendor/curl-7.65.0/lib/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC -DCMAKE_INSTALL_PREFIX=/data/vendor/mariac-3.1.2 ../
+make && make install
+# rm /data/vendor/mariac-3.1.2/lib/mariadb/*.so*
+```
+
+</p>
+</details>
+
+### 版权声明
+本软件使用 MIT 许可协议；
diff --git a/doc/compress.php b/doc/compress.php
new file mode 100644
index 0000000..460f9b4
--- /dev/null
+++ b/doc/compress.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * 补充部分在框架中存在的压缩算法
+ */
+namespace flame\compress;
+/**
+ * snappy 压缩（与 https://github.com/kjdev/php-ext-snappy 兼容）
+ */
+function snappy_compress(string $data):string {
+    return "compressed data";
+}
+/**
+ * snappy 解压（与 https://github.com/kjdev/php-ext-snappy 兼容）
+ */
+function snappy_uncompress(string $data):string {
+    return "original string";
+}
diff --git a/doc/core.php b/doc/core.php
new file mode 100644
index 0000000..b6bde23
--- /dev/null
+++ b/doc/core.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ * 基本框架流程函数, 用于框架初始化, 启动协程等;
+ * 框架可在两种模式下工作：
+ * 
+ * 1. 单进程模式：
+ *  * 信号 SIGQUIT 立即终止进程；
+ *  * 信号 SIGINT / SIGTERM 将 “通知” 进程退出 (触发 `quit` 回调)，等待超时后立即终止；
+ *  * 信号 SIGUSR1 将切换 HTTP 服务器长短连状态 `Connection: close`；
+ *  * 单进程模式**不支持**日志文件输出，固定输出到 STDERR / STDOUT (取决于错误等级）；
+ * 
+ * 2. 父子进程模式：
+ *  * 使用环境变量 FLAME_MAX_WORKERS=X 启动父子进程模式；
+ *  * 子进程存在环境变量 FLAME_CUR_WORKER=N 标识该进程标号，1 ~ FLAME_MAX_WORKERS
+ *  * 主进程将自动进行日志文件写入，子进程自动拉起；
+ *  * 端口监听使用 SO_REUSEPORT 在多个工作进程间共享，但不排除外部监听的可能；
+ * 
+ *  * 向主进程发送 SIGINT / SIGQUIT 将立即终止进程；
+ *  * 项主进程发送 SIGTERM 将 ”通知“ 子进程退出 (触发 `quit` 回调)，并等待超时后立即终止；
+ *  * 向主进程发送 SIGUSR2 信号该文件将会被重新打开(或生成);
+ *  * 子进程继承主进程的 环境变量 / -c /path/to/php.ini 配置（通过命令行 -d XXX=XXX 的临时设置无效）；
+ *  * 向主进程发送 SIGUSR1 信号将切换 HTTP 服务器长短连状态 `Connection: close`；
+ *  * 向主进程发送 SIGRTMIN+1 将进行进程重载（陆续停止当前进程并启动新进程）
+ */
+namespace flame;
+/**
+ * 初始化框架, 设置进程名称及相关配置;
+ * @param string $process_name 进程名称
+ * @param array  $options 选项配置, 目前可用如下:
+ *  * "logger" - 日志输出重定向目标文件 (完整路径, 若不提供使用标准输出，单进程模式无效);
+ *  * "level" - 日志输出级别, 设置该级别下的日志将不被输出记录; 可用级别如下
+ *      "debug"
+ *      "info"
+ *      "warning"
+ *      "error"
+ *      "fatal"
+ *  * "timeout" - 进程退出超时, 单位毫秒 ( 200 ~ 100000ms ), 默认 3000ms（超时后会被强制杀死）
+ *  @see flame\log
+ */
+function init($process_name, $options = []) {}
+/**
+ * 启动协程(运行对应回调函数)
+ * 注意：框架使用 固定栈大小 方式进行协程调度（相对效率较高）；但为满足大部分需求，栈空间占用相对（初始值）较高；不应启动过多的协程；
+ */
+function go(callable $cb) {}
+/**
+ * 读取数组的指定层级键（常用于读取配置数据）
+ * @example
+ *  $a = ["a" => ["b" => 123]];
+ *  var_dump(flame\get($a, "a.b")); // 123
+ * @return mixed
+ * 注意：
+ *  1. 数字下标将被当作文本处理；
+ */
+function get($array, $keys) {}
+/**
+ * 设置数组的层级键值
+ * @example
+ *  $a = [];
+ *  flame\set($a, "a.b", 123);
+ *  var_dump($a); // ["a" => ["b" => 123]]
+ * 注意：
+ *   1. 数字下标将被当作文本处理；
+ */
+function set(&$array, $keys, $val) {}
+/**
+ * 获取一个当前协程 ID 标识
+ */
+function co_id():int {
+    return 123456;
+}
+/**
+ * 获取运行中的协程数量
+ */
+function co_count():int {
+    return 8;
+}
+/**
+ * 框架调度, 上述协程会在框架开始调度运行后启动
+ * 注意：协程异步调度需要 run() 才能启动执行；
+ */
+function run() {}
+/**
+ * 从若干个队列中选择(等待)一个有数据队列
+ * @return 若所有通道已关闭, 返回 null; 否则返回一个有数据的通道, 即: 可以无等待 pop()
+ */
+function select(queue $q1, $q2/*, ...*/):queue {
+    return new queue();
+}
+/**
+ * 向指定工作进程发送消息通知
+ * @param int $target_worker 目标工作进程编号，1 ~ FLAME_MAX_WORKERS；当前进程可读取环境变量 FLAME_CUR_WORKER 获取编号；
+ * @param mixed $data 消息数据，自动进行 JSON 序列化传输；
+ * 注意：
+ *   对象类型数据进行 JSON 序列化可能丢失数据细节而无法还原；
+ *   目标进程将会收到 `message`（自动 JSON 反序列化数据）回调;
+ *   @see flame\on("message", $cb);
+ */
+function send(int $target_worker, $data) {
+
+}
+/**
+ * 为指定事件添加处理回调（函数）
+ * @param string $event 目前消息存在以下两种:
+ *  * "exception" - 当协程发生未捕获异常, 执行对应的回调，并记录错误信息（随后进程会退出），用户可在此回调进行错误报告或报警;
+ *  * "quit" - 退出消息, 用户可在此回调停止各种服务，如停止 HTTP 服务器 / 关闭 MySQL 连接等;
+ *  * "message" - 消息通知，（启动内部协程）接收来自各子进程之间的相互通讯数据；回调函数接收一个不限类型参数；
+ * @see flame\send()
+ * @param callable 回调函数
+ */
+function on(string $event, callable $cb) {}
+/**
+ * 删除指定事件的所有处理回调（函数）
+ * 注意：
+ *  由于 `message` 事件内部启动协程，阻止了程序的停止；须取消对应回调，使该内部协程停止后，进程才可以正常退出；
+ */
+function off(string $event) {}
+/**
+ * 用于在用户处理流程中退出
+ * 注意：
+ *  1. 使用 PHP 内置 exit() 进行退出可能导致框架提供的清理、退出机制无效；(请示用本函数代替)
+ *  2. 调用本函数主动退出进程，不会触发上述 `on("quit", $cb)` 注册的回调;
+ *  3. 父子进程模式，非停止流程，当 $exit_code 不为 0 时表示异常退出，父进程会（1s ~ 3s 后）将当前工作进程重启；
+ */
+function quit(int $exit_code = 0) {}
+/**
+ * 协程型队列
+ */
+class queue {
+    /**
+     * @param int $max 队列容量, 若已放入数据达到此数量, push() 将"阻塞"(等待消费);
+     */
+    function __construct($max = 1) {}
+    /**
+     * 放入; 若向已关闭的队列放入, 将抛出异常;
+     */
+    function push($v) {}
+    /**
+     * 取出
+     */
+    function pop() {}
+    /**
+     * 关闭 (将唤醒阻塞在取出 pop() 的协程);
+     * 原则上仅能在生产者方向关闭队列;
+     */
+    function close() {}
+    /**
+     * 队列是否已关闭
+     */
+    function is_closed(): bool {
+        return false;
+    }
+}
+/**
+ * 协程互斥量（锁）
+ */
+class mutex {
+    /**
+     * 构建一个协程式 mutex 对象
+     */
+    function __construct() {}
+    /**
+     * 加锁
+     */
+    function lock() {}
+    /**
+     * 解锁
+     */
+    function unlock() {}
+}
+/**
+ * 协程锁（使用上述 mutex 构建自动加锁解锁流程）
+ */
+class guard {
+    /**
+     * 构建守护并锁定 mutex
+     * @param mutex $mutex 实际保护使用的 mutex 对象
+     */
+    function __construct(mutex $mutex) {}
+    /**
+     * 解锁 mutex 并销毁守护
+     */
+    function __destruct() {}
+}
diff --git a/doc/encoding.php b/doc/encoding.php
new file mode 100644
index 0000000..8d8f37f
--- /dev/null
+++ b/doc/encoding.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * 补充部分在框架中存在的编码功能
+ */
+namespace flame\encoding;
+/**
+ * 将 PHP 数组转换为 BSON 格式，并返回实际 BSON 的原始二进制数据
+ */
+function bson_encode(array $data):string {
+    return "encoded string";
+}
+/**
+ * 还原由上述 `bson_encode` 生成的原始二进制数据，返回对应的数组
+ */
+function bson_decode(string $data):array {
+    return [];
+}
diff --git a/doc/hash.php b/doc/hash.php
new file mode 100644
index 0000000..080d30a
--- /dev/null
+++ b/doc/hash.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * 补充部分在框架中存在的哈希算法函数
+ */
+namespace flame\hash;
+/**
+ * 默认情况 kafka 内部使用的哈希算法（返回 )
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function murmur2(string $data, bool $raw = false) {}
+/**
+ * 一种效率极高且哈希结果理想度高的哈希算法（返回 uint64)
+ * @param int $seed 用于对哈希结果进行预期内的调整
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function xxh64(string $data, int $seed = 0, bool $raw = false) {}
+/**
+ * 根据 EMCA 定义实现的 crc64 哈希（与 Golang 对应 hash/crc64 ECMA 计算值一直）
+ * @return mixed 当 $raw = true 时返回 uint32 整数，否则返回对应的定长 HEX 串；
+ */
+function crc64(string $data, bool $raw = false) {}
diff --git a/doc/http.php b/doc/http.php
new file mode 100644
index 0000000..ba470bf
--- /dev/null
+++ b/doc/http.php
@@ -0,0 +1,419 @@
+<?php
+/**
+ * 提供基本的 HTTP(S) 客户端、服务端封装；服务端仅支持 HTTP/1.1 协议; 客户端支持 (TLS) HTTP/1.x HTTP/2 协议;
+ * 注意：
+ * * 默认状态，服务器对客户端请求连接进行 KeepAlive 复用（长连状态）；若需要使用短连接，请自行制定 Connection: close 头（短连状态）；
+ * * 当进程接收到 SIGUSR1 信号时，切换连接复用状态（短连状态附加 Connection: close 头信息）；
+ */
+namespace flame\http;
+
+/**
+ * 使用默认客户端, 执行请求
+ */
+function exec(client_request $req): client_response {
+    return new client_response();
+}
+/**
+ * 使用默认客户端, 构建并执行一个 GET 请求
+ */
+function get(string $url, int $timeout = 3000): client_response {
+    return new client_response();
+}
+/**
+ * 使用默认客户端, 构建并执行一个 POST 请求
+ */
+function post(string $url, $data, int $timeout = 3000): client_response {
+    return new client_response();
+}
+/**
+ * 使用默认客户端, 构建并执行一个 PUT 请求
+ */
+function put(string $url, $data, int $timeout = 3000): client_response {
+    return new client_response();
+}
+/**
+ * 使用默认客户端, 构建并执行一个 DELETE 请求
+ */
+function delete(string $url, int $timeout = 3000): client_response {
+    return new client_response();
+}
+
+/**
+ * 客户端，用于执行请求
+ */
+class client {
+    /**
+     * 构建新的客户端对象
+     * @param array $options 选项, 目前支持的选项如下:
+     *  * "connection_per_host" - 单个 HOST 目标连接限制, 默认 16，超出限制时须等待;
+     */
+    function __construct(array $options = []) {}
+    /**
+     * 执行请求, 返回响应
+     */
+    function exec(client_request $req): client_response {
+        return new client_response();
+    }
+    /**
+     * 构建并执行一个 GET 请求
+     */
+    function get(string $url, int $timeout = 3000): client_response {
+        return new client_response();
+    }
+    /**
+     * 构建并执行一个 POST 请求
+     */
+    function post(string $url, $data, int $timeout = 3000): client_response {
+        return new client_response();
+    }
+    /**
+     * 构建并执行一个 PUT 请求
+     */
+    function put(string $url, $data, int $timeout = 3000): client_response {
+        return new client_response();
+    }
+    /**
+     * 构建并执行一个 DELETE 请求
+     */
+    function delete(string $url, int $timeout = 3000): client_response {
+        return new client_response();
+    }
+}
+/**
+ * 客户端请求对象，用于拼装生成一个客户端请求（待执行）
+ */
+class client_request {
+    /**
+     * 由框架自行选择版本
+     */
+    const HTTP_VERSION_NONE = 0;
+    /**
+     * 强制使用 HTTP/1.0 协议
+     */
+    const HTTP_VERSION_1_0   = 1;
+    /**
+     * 强制使用 HTTP/1.1 协议
+     */
+    const HTTP_VERSION_1_1   = 2;
+    /**
+     * 尝试使用 HTTP/2 协议，自动退回 HTTP/1.1 版本
+     */
+    const HTTP_VERSION_2     = 3;
+    /**
+     * 尝试在 TLS (HTTPS) 使用 HTTP/2 协议，自动退化 HTTP/1.1 版本
+     */
+    const HTTP_VERSION_2_TLS = 4;
+    /**
+     * 强制在 non-TLS 状态进行 HTTP/2 请求，TLS (HTTPS) 时同 HTTP_VERSION_2_TLS
+     */
+    const HTTP_VERSION_2_PRI = 5;
+
+    const SSL_VERIFY_NONE    = 0;
+    /**
+     * 验证对端证书
+     */
+    const SSL_VERIFY_PEER    = 1;
+    /**
+     * 验证证书域名
+     */
+    const SSL_VERIFY_HOST    = 2;
+    /*
+     * 验证证书状态
+     */
+    const SSL_VERIFY_STATUS  = 4;
+    /**
+     * 相当于 SSL_VERIFY_PEER | SSL_VERIFY_HOST | SSL_VERIFY_STATUS
+     */
+    const SSL_VERIFY_ALL     = 7;
+    /**
+     * @var int
+     */
+    public $timeout;
+    /**
+     * @var string
+     */
+    public $method;
+    /**
+     * @var string
+     */
+    public $url;
+    /**
+     * 请求头；可通过指定 "Connection" => "close" 防止连接复用；
+     * @var array
+     */
+    public $header;
+    /**
+     * @var array
+     */
+    public $cookie;
+    /**
+     * @var mixed
+     */
+    public $body;
+    /**
+     * 构建请求, 可选的指定请求体 `$body` (将自动设置为 POST 请求方法)及超时时间 `$timeout`
+     */
+    function __construct(string $url, $body = null, int $timeout = 3000) {}
+    /**
+     * @param string $cert_file 证书文件路径
+     * @param string $pkey_file 密钥文件路径
+     * @param string $pkey_pass 密钥密码
+     */
+    function ssl_pem(string $cert_file, string $pkey_file = "", string $pkey_pass = "") {}
+    /**
+     * 请使用 client_request::SSL_VERIFY_* 相关常量进行设置（可按位组合）
+     */
+    function ssl_verify(int $verify) {}
+    /**
+     * 请选择使用 client_request::HTTP_VERSION_* 常量设置版本
+     */
+    function http_version(int $version) {}
+}
+/**
+ * 客户端响应对象，执行请求后得到
+ */
+class client_response {
+    /**
+     * 对应请求实际的 HTTP 版本
+     * @var string
+     */
+    public $version;
+    /**
+     * 响应码
+     * @var int
+     */
+    public $status;
+    /**
+     * 注意: 目前使用了数组形式, 切暂不支持多个同名 Header 数据的访问;
+     * @var array
+     */
+    public $header;
+    /**
+     * @var mixed 以下几种 Content-Type 将自动进行解析并生成关联数组:
+     *  * "application/json"
+     *  * "application/x-www-form-urlencoded"
+     *  * "multipart/form-data"
+     * 其他类型保持文本的原始数据(与 $raw_body 相同);
+     */
+    public $body;
+    /**
+     * 原始请求体数据
+     * @var string
+     */
+    public $raw_body;
+}
+/**
+ * HTTP 服务器
+ */
+class server {
+    /**
+     * 服务端本地监听地址
+     */
+    public $address;
+    /**
+     * 创建服务器并绑定地址
+     */
+    function __construct(string $address) {}
+    /**
+     * 每次 HTTP 请求在执行对应 path 的处理器前, 先执行下述设置的回调;
+     * 回调形式如下:
+     *  function callback(server_request $req, server_response $res, bool $match) {}
+     * 其中 $match 参数标识此请求是否匹配了定义的 path 处理器;
+     * 当 callback 函数返回 false 时, 将**不再**执行后续处理器
+     */
+    function before(callable $cb):server {
+        return $this;
+    }
+    /**
+     * 每次 HTTP 请求在执行对应 path 处理器后, 会执行下述设置的回调;
+     * 回调形式同 before();
+     * 注意: 若 before 回调或 path 回调返回了 false, 此 after 回调将不再执行;
+     */
+    function after(callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "PUT $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function put(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "DELETE $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function delete(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "POST $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function post(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "PATCH $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function patch(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "GET $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function get(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "HEAD $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function head(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 设置一个处理 "OPTIONS $path HTTP/1.1" 请求的路径 path 处理器;
+     * 注意: 当 before 回调返回 false 时, 此处理器将不再被执行;
+     */
+    function options(string $path, callable $cb):server {
+        return $this;
+    }
+    /**
+     * 启动服务器, 监听请求并执行对应回调
+     * 注意: 运行服务器将阻塞当前协程;
+     */
+    function run() {}
+    /**
+     * 停止服务器, 不再接收新的连接; 上述 run() 阻塞协程将恢复执行;
+     * 注意: 正在处理中的请求不收影响(继续处理);
+     */
+    function close() {}
+}
+/**
+ * 服务端请求对象（由 server 对应回调获得）
+ */
+class server_request {
+    /**
+     * 请求方法（大写）
+     * @var string
+     */
+    public $method = "GET";
+    /**
+     * 请求路径（不包含查询参数）
+     * @var string
+     */
+    public $path = "/";
+    /**
+     * 请求参数, 使用 PHP 内置 parse_str() 函数解析得到;
+     * @var array
+     */
+    public $query;
+    /**
+     * 请求头;
+     * @var array
+     * 注意：所有头信息字段名被转换为**小写**形式;
+     * 注意：由于目前使用数组形式，暂不支持同名字段；
+     */
+    public $header;
+    /**
+     * 请求 Cookie
+     * @var array
+     */
+    public $cookie;
+    /**
+     * 请求体, 以下几种 Content-Type 时自动解析为关联数组:
+     *  * "application/json"
+     *  * "application/x-www-form-urlencoded"
+     *  * "multipart/form-data"
+     * 其他类型时与 $raw_body 相同;
+     * @var mixed
+     */
+    public $body;
+    /**
+     * 原始请求体
+     * @var string
+     */
+    public $raw_body;
+    /**
+     * 上传文件，形如：
+     *  [
+     *      "file_field_1"=>
+     *      [
+     *          "content-type" => "mime_type",
+     *          ... // other header field lowercased if available
+     *          "filename" => "original_file_name",
+     *          "size" => file_data_size,
+     *          "data" => "file_data", // content of the uploaded file
+     *      ],
+     *      "file_filed_2"=>
+     *      ...
+     *  ]
+     * 若无上传文件时可能为空
+     * @var array
+     */
+    public $file;
+    /**
+     * 用户可设置的请求关联数据;
+     * @example 在 before 回调中从 Cookie 中提取用户信息放在 $req->data["user"] 中, 后续处理器即可访问 $req->data["user"] 获取户信息;
+     * @var array
+     */
+    public $data;
+}
+
+/**
+ * 服务端响应对象（由 server 对应处理回调获得）提供接口向客户端返回数据
+ * 默认状态下 HTTP/1.1 使用 长连接（保持连接）；可使用 Connection: close 头禁止；
+ * 向进程发送 SIGUSR1 信号时将会在 长短连 状态切换（短连接状态自动附加 Connection: close 头信息）；
+ */
+class server_response {
+    /**
+     * 响应状态码, 可以直接设置; 也可以在 Transfer-Encoding: chunked 模式时使用 write_header 指定;
+     * @var int
+     */
+    public $status = 200;
+    /**
+     * 响应头信息, Key/Val 形式
+     * @var array
+     * 注意：一般情况下 Key/Val 均为字符串，其他类型会被转换为字符串输出；
+     */
+    public $header;
+    // public $cookie;
+    /**
+     * 响应体, 用于在 Content-Length: xxx 响应模式下返回数据;
+     * @var mixed 任意数据, 实际响应会序列化此数据; 以下几种 Content-Type 存在内置的序列化:
+     *  * "application/json" - 使用 json_encode 进行序列化;
+     *  * "application/x-www-form-urlencoded" - 使用 http_build_query 进行序列化;
+     * 其他类型强制转换为文本类型后返回响应;
+     */
+    public $body;
+    /**
+     * 设置返回的 Set-Cookie 项;
+     * 参数功能与 PHP 内置 set_cookie 函数类似;
+     */
+    function set_cookie(string $name, string $value = null, int $expire = 0, string $path = "/", string $domain = "", bool $secure = false, bool $http_only = false) {}
+    /**
+     * 返回响应头, 用于启用 Transfer-Encoding: chunked 模式, 准备持续响应数据;
+     * @param int $status 当参数存在时, 覆盖上述 $res->status 属性响应码;
+     *
+     * Transfer-Encoding: chunked 模式可用于 EventSource 等持续响应;
+     */
+    function write_header($status = 0) {}
+    /**
+     * 在 Transfer-Encoding: chunked 模式下, 返回一个 chunk 数据块;
+     */
+    function write(string $chunk) {}
+    /**
+     * 在 Transfer-Encoding: chunked 模式下, 结束响应过程 (返回最后一个结束块);
+     * @param string $chunk 可选, 调用上述 write() 返回一个数据块;
+     */
+    function end(string $chunk = null) {}
+    /**
+     * 响应一个文件的内容; 一般需要自行设置对应文件的 Content-Type 以保证浏览器能正常渲染;
+     * 下述参数中的 $path 将会被正常化处理, 以保证其不超出 $root 指定的根目录范围;
+     * 注意: 此功能一般仅用于调试或少量文件访问使用, 大量生产环境请考虑使用 nginx 等代为处理静态文件;
+     */
+    function file(string $root, string $path) {}
+}
diff --git a/doc/kafka.php b/doc/kafka.php
new file mode 100644
index 0000000..6c8fb3d
--- /dev/null
+++ b/doc/kafka.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * 提供 Kafka 基本生产消费功能封装
+ */
+namespace flame\kafka;
+/**
+ * @param array $config 基本 Kafka 配置, 以下两个选项必要:
+ *  * "bootstrap.servers" - string 服务器地址(一般为多个端口为 9092 的服务器地址，逗号分隔，例如："127.0.0.1:9092, 127.0.0.2:9092");
+ *  * "group.id" - string 消费组名称(同一消费组共享消费进度);
+ * 可以使用一个自定义的的参数:
+ *  * "concurrent" - 控制并行消费协程数量（即一个消费者消费到数据作为生产过程交由协程消费）, 默认为 8 (数值类型)，上限为 256;
+ * @see 其他可配置选项均为字符串类型(非字符串会被强制转换), 请参考:
+ *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
+ * @param array $topics 待消费的数据源 TOPIC 名称列表
+ */
+function consume(array $config, array $topics):consumer {
+    return new consumer();
+}
+
+/**
+ * @param array $config 基本 Kafka 配置, 一下两个选项必要:
+ *  * "bootstrap.servers" - 服务器地址(一般为多个端口为 9092 的服务器地址，逗号分隔，例如："127.0.0.1:9092, 127.0.0.2:9092");
+ *  * "group.id" - 消费组名称(同一消费组共享消费进度);
+ * @see 其他可配置选项请参考:
+ *  https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
+ * @param array $topics 待生产的目标 TOPIC 名称列表
+ */
+function produce(array $config, array $topics):producer {
+    return new producer();
+}
+/**
+ * 消费者
+ */
+class consumer {
+    /**
+     * 执行消费, 实际的回调函数会被对应的协程回调执行
+     * 注意: 由于 Kafka 消费组的平衡调度, 消费可能会被延迟执行 (15s+);
+     * @param callable $cb 消费回调函数
+     */
+    function run(callable $cb) {}
+    /**
+     * 手动提交消息(的偏移), 一般需要将 "enable.auto.commit" 设为 "false"
+     * @see consume();
+     */
+    function commit(message $msg) {}
+    /**
+     * 关闭消费者(停止消费)
+     * 注意: 为保证消费消息不丢失, 消费者的实际关闭可能会持续一段时间
+     */
+    function close() {}
+}
+/**
+ * 生产者
+ */
+class producer {
+    /**
+     * 生产消息
+     * 注意: 此函数会将消息放入"生产队列", 并通过后台线程传输;
+     * (为保证完成, 请使用 `flush()`)
+     * @see producer::flush()
+     * @param string $topic 生成目标 TOPIC 名称, 必须是 待生产 $topics 数组中的一个;
+     * @see produce()
+     * @param mixed $mssage 若为 message 类型的对象, 则后续参数无效;
+     *  否则为现场 message 对象的 payload 数据;
+     * @see class message;
+     * @param string $key
+     * @param array $header
+     */
+    function publish(string $topic, $message, string $key = null, array $header = []) {}
+    /**
+     * 清空"生产队列", 若当前队列中还有消息等待其传输完毕;
+     */
+    function flush() {}
+}
+/**
+ * 消息对象
+ */
+class message implements \JsonSerializable {
+    /**
+     * @var string $topic
+     */
+    public $topic;
+    /**
+     * @var int $partition
+     */
+    public $partition;
+    /**
+     * @var string $key
+     */
+    public $key;
+    /**
+     * @var int $offset
+     */
+    public $offset;
+    /**
+     * @var array $header
+     */
+    public $header;
+    /**
+     * @var string $payload 消息内容
+     */
+    public $payload;
+    /**
+     * @var $timestamp 消息时间戳(一般为毫秒)
+     */
+    public $timestamp;
+    /**
+     * 构造一个新消息, 将其 $timestamp 填充为当前时间
+     */
+    function __construct($payload = "", $key = null) {}
+    /**
+     * 返回当前消息内容 ($payload)
+     */
+    function __toString() {}
+    /**
+     * Json 序列化, 返回 topic + key + payload 关联数组
+     */
+    function jsonSerialize() {}
+}
diff --git a/doc/log.php b/doc/log.php
new file mode 100644
index 0000000..92a43c5
--- /dev/null
+++ b/doc/log.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * 日志模块, 可通过框架配置设置日志等级以过滤日志输出;
+ * @see flame\init()
+ */
+namespace flame\log;
+
+/**
+ * 创建（连接到 IPC 通道）一个新的日志记录器（记录到指定文件）
+ */
+function connect(string $filepath): logger {
+    return new logger();
+}
+/**
+ * 记录 无前缀（无时间戳 、无警告级别）的日志数据
+ */
+function write($x/*, $y, $z  ... */) {}
+/**
+ * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function trace($x/*, $y, $z  ... */) {}
+/**
+ * 记录 DEBUG 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function debug($x/*, $y, $z  ... */) {}
+/**
+ * 记录 INFO 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function info($x/*, $y, $z  ... */) {}
+/**
+ * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function warn($x/*, $y, $z  ... */) {}
+/**
+ * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function warning($x/*, $y, $z  ... */) {}
+/**
+ * 记录 ERROR 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function error($x/*, $y, $z  ... */) {}
+/**
+ * 同 fatal()
+ * @see fatal()
+ */
+function fail($x/*, $y, $z  ... */) {}
+/**
+ * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
+ */
+function fatal($x/*, $y, $z  ... */) {}
+
+class logger {
+    /**
+     * 禁止手动创建日志对象，请使用 flame\log\connect() 函数
+     * @see flame\log\connect()
+     */
+    private function __construct() {}
+    /**
+     * 记录 无前缀（无时间戳 、无警告级别）的日志数据
+     */
+    function write($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 TRACE 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function trace($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 DEBUG 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function debug($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 INFO 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function info($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function warn($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 WARNING 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function warning($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 ERROR 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function error($x/*, $y, $z  ... */) {}
+    /**
+     * 同 fatal()
+     * @see fatal()
+     */
+    function fail($x/*, $y, $z  ... */) {}
+    /**
+     * 记录 FATAL 级别日志, 自动进行 JSON 形式的序列化;
+     */
+    function fatal($x/*, $y, $z  ... */) {}
+};
diff --git a/doc/mongodb.php b/doc/mongodb.php
new file mode 100644
index 0000000..801694f
--- /dev/null
+++ b/doc/mongodb.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * 提供基本的 MongoDB 基本客户端封装，使用连接池维护与服务器连接
+ * 注意:
+ * 1. 同一协程中连续进行 MongoDB 查询操作, 且游标不读取数据不销毁 (或 不读取完不销毁) 可能导致进程死锁; (请将游标读取完毕 或 主动 unset 释放游标对象)
+ * 2. 对应 MongoDB ObjectID 映射类型 class flame\mongodb\object_id;
+ * 3. 对应 MongoDB DateTime 映射类型 class flame\mongodb\date_time;
+ * 4. 单客户端内连接池上限大小为 6（单进程），即并行访问超过 6 时须排队等待；
+ * 5. 由于 PHP 数组混合了关联数组机制，空查询条件时可考虑使用 new StdClass() 空对象代替空数组，以明确区分 JSON Object/Array 两种形式；
+ *    针对 find 相关操作框架内部**对查询条件**已进行了上述**适配**；其他情况的空数组将按照 BSON_ARRAY 转换；
+ */
+namespace flame\mongodb;
+
+/**
+ * 连接 Mongodb 并返回客户端对象
+ * @param string $url 连接地址, 形如:
+ *  mongodb://{username}:{password}@{host1}:{port1},{host2}:{port2}/{database}?{option1}={rs1}&{option2}=...
+ */
+function connect(string $url):client {
+    //....
+    return new client();
+}
+/**
+ * MongoDB 客户端（内部使用连接池）
+ */
+class client {
+    const COMMAND_RAW        = 0;
+    const COMMAND_WRITE      = 1;
+    const COMMAND_READ       = 2;
+    const COMMAND_READ_WRITE = 3;
+    /**
+     * 执行以 $command 描述的命令;
+     * 请参考：https://docs.mongodb.com/master/reference/command/ 编写指令；
+     * $type
+     * 注意: 更新型指令需要自行指定 $write =
+     * @return mixed array/cursor
+     */
+    function execute(array $command, int $type = 0) {}
+    /**
+     * 获取指定名称的 colleciton 对象
+     */
+    function collection($name):collection {
+        return new collection();
+    }
+    /**
+     * 与 `collection()` 相同, 返回指定的 collection 对象
+     */
+    function __get($name):collection {
+        return new collection();
+    }
+    /**
+     * @return bool 永远为 true ;
+     */
+    function __isset($name):bool {
+        return true;
+    }
+}
+class collection {
+    /**
+     * 执行插入
+     * @param array $data 一维关联数组插入单个文档; 二维时插入多个；
+     * @param bool $ordered 插入多个文档时，是否顺序执行（在遇到的第一个插入错误出停止）
+     * @return array 返回插入结果，形如：
+     *  array(
+     *      "n" => 1,  // 插入数量
+     *      "ok" => 1, // 插入结果
+     *      ...
+     *  )
+     */
+    function insert(array $data, bool $ordered = true):array {
+        return [];
+    }
+    function insert_many(array $data, bool $ordered = true):array {
+        return [];
+    }
+    function insert_one(array $data):array {
+        return [];
+    }
+    /**
+     * 执行删除
+     * @return array 形式同 insert() 类似：
+     *  array(
+     *      "n" => 1,  // 删除数量
+     *      "ok" => 1, // 删除结果
+     *      ...
+     *  )
+     */
+    function delete(array $query, int $limit = 0):array {
+        return [];
+    }
+    function delete_many(array $query, int $limit = 0):array {
+        return [];
+    }
+    function delete_one(array $query):array {
+        return [];
+    }
+    /**
+     * 执行更新动作
+     * @param array $update 一般使用类似如下形式进行更新设置：
+     *  ['$set'=>['a'=>'b'],'$inc'=>['c'=>2]]
+     * @return array 形式同 insert() 类似：
+     *  array(
+     *      "n" => 1,  // 更新数量
+     *      "ok" => 1, // 更新结果
+     *      ...
+     *  )
+     */
+    function update(array $query, array $update, $upsert = false):array {
+        return [];
+    }
+    function update_many(array $query, array $update, $upsert = false):array {
+        return [];
+    }
+    function update_one(array $query, array $update, $upsert = false):array {
+        return [];
+    }
+    /**
+     * 执行指定查询，返回游标对象
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
+     * @param mixed $limit 可以为 int 设置 `limit` 值，或 array 设置 `[$skip, $limit]`
+     */
+    function find(array $query, array $projection = null, array $sort = null, $limit = null):cursor {
+        return new cursor();
+    }
+    function find_many(array $query, array $projection = null, array $sort = null, $limit = null):cursor {
+        return new cursor();
+    }
+
+    /**
+     * 查询并返回单个文档
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
+     */
+    function find_one(array $query, array $sort = null): array {
+        return [];
+    }
+    function one(array $query, array $sort = null): array {
+        return [];
+    }
+    /**
+     * 查询并返回单个文档的指定字段值
+     * @param array $sort 一般使用如下形式表达排序：
+     *  ['a'=>1,'b'=>-1] // a 字段升序，b 字段降序
+     * @return mixed 字段类型映射请参见顶部说明；
+     */
+    function get(array $query, string $field, array $sort = null) {
+        return null;
+    }
+    /**
+     * 查询并返回匹配文档数量
+     * @param mixed $limit integer/array 请参见 find 函数对应 $limit 说明
+     */
+    function count(array $query, $limit): int {
+        return 0;
+    }
+    /**
+     * 执行指定聚合操作，返回游标对象；
+     * 请参考 https://docs.mongodb.com/master/reference/operator/aggregation-pipeline/ 编写；
+     */
+    function aggregate(array $pipeline, array $options = []): cursor {
+        return new cursor();
+    }
+    /**
+     * 查找满足条件的第一个文档，删除并将其返回
+     */
+    function find_and_delete(array $query, array $sort = null, boolean $upsert = false, $fields = null): array {
+        return [];
+    }
+    /**
+     * 查找满足条件的第一个文档，对其进行更新，并返回
+     * @param $new boolean 返回更新后的文档
+     */
+    function find_and_update(array $query, array $update, array $sort = null, $upsert = false, array $fields = null, $new = false) {
+        return [];
+    }
+}
+/**
+ * 游标对象
+ */
+class cursor {
+    /**
+     * 读取当前 cursor 返回一个文档; 若读取已完成, 返回 null;
+     * @return mixed array/null
+     */
+    function fetch_row():array {
+        return [];
+    }
+    /**
+     * 读取当前 cursor 返回所有文档 (二维); 若读取已完成, 返回 null;
+     * @return mixed array/null
+     */
+    function fetch_all(): array {
+        return [];
+    }
+}
+/**
+ * 对应 MongoDB 日期时间字段，毫秒级精度
+ */
+class date_time implements \JsonSerializable {
+    /**
+     * 构建日期时间对象
+     * @param int $milliseconds 指定毫秒级时间，默认 null 使用当前时间
+     */
+    function __construct(int $milliseconds = null) {}
+    function __toString() {}
+    function __toDateTime(): \DateTime {
+        return new \DateTime();
+    }
+    function __debugInfo() {}
+    function jsonSerialize(): array {
+        return [];
+    }
+    /**
+     * 返回秒级时间戳
+     */
+    function unix(): int {
+        return time();
+    }
+    /**
+     * 返回毫秒级时间戳
+     */
+    function unix_ms(): int {
+        return intval(microtime(true) * 1000);
+    }
+    /**
+     * 返回标准的 YYYY-MM-DD hh:mm:ss 文本形式的时间
+     */
+    function iso(): string {
+        return "2019-03-31 21:40:25";
+    }
+}
+/**
+ * 对应 MongoDB 对象ID
+ */
+class object_id implements \JsonSerializable {
+    /**
+     * 构建
+     * @param string $object_id 从文本形式构建（恢复）一个对象，默认 null，构建一个新的对象
+     */
+    function __construct(string $object_id = null) {}
+    function __toString() {}
+    function __toDateTime(): \DateTime {
+        return new \DateTime();
+    }
+    function __debugInfo() {}
+    function jsonSerialize(): array {
+        return [];
+    }
+    /**
+     * 秒级时间戳
+     */
+    function unix(): int {
+        return time();
+    }
+    /**
+     * 当前对象与另一 object_id 对象是否**值相等**
+     */
+    function equal(object_id $oid): bool {
+        return false;
+    }
+}
diff --git a/doc/mysql.php b/doc/mysql.php
new file mode 100644
index 0000000..024f61b
--- /dev/null
+++ b/doc/mysql.php
@@ -0,0 +1,265 @@
+<?php
+/**
+ * 提供基本的 MySQL 客户端封装，内部使用连接池
+ * 注意:
+ * 1. 同一协程中连续进行 MySQL 查询操作, 且结果集数据不读取且不销毁(或不读取完且不销毁)可能导致进程死锁; (请将结果集读取完 或 主动 unset 释放结果集对象)
+ * 2. MySQL 读取数据时数值型字段读取映射为 PHP 对应数值类型；`DATETIME` 映射为 PHP 内置类型 `DateTime`;
+ * 3. 客户端提供的简化方法如 `insert()` `update()` 也同时支持与上述反向的映射;
+ * 4. 单客户端内连接池上限大小为 6（单进程），即在并行访问超过 6 时须排队等待；
+ */
+namespace flame\mysql;
+/**
+ * 建立（连接）到 MySQL 服务器的客户端对象
+ * @param string $url 服务端地址, 形如:
+ *  mysql://{user}:{pass}@{host}:{port}/{database}?opt1=val1
+ *  目前可用的选项如下:
+ *      * "charset" => 字符集
+ *      * "auth" => 认证方式，默认 "mysql_native_password" 可用 "caching_sha2_password"（MySQL 8.0 服务器默认） / "sha256_parssword"
+ *      * "proxy" => 当前地址是否是代理服务（已提供连接复用机制） "1" - 是，已提供连接复用机制 (禁用框架提供的复用重置机制)， "0" - 否，未提供（默认）
+ * 注意：若服务端配置默认字符集与上述字符集不同，且未指定 proxy=1 参数，那么：
+ *      每次连接使用（复用）连接重置，字符集被恢复为服务器默认，框架将再次进行字符集设置（这里可能会产生少量额外消耗）；
+ * 注意：若指定了 proxy 参数框架不再进行任何”连接复用“的清理工作（CONNECTION_RESET / CHANGE_USER / CHARSET_RESET)；
+ * @return client 客户端对象
+ */
+function connect($url): client {
+    return new client();
+}
+
+/**
+ * WHERE 字句语法规则:
+ *
+ * @example
+ *  $where = ["a"=>"1", "b"=>[2,"3","4"], "c"=>null, "d"=>["{!=}"=>5]];
+ *  // " WHERE (`a`='1' AND `b`  IN ('2','3','4') AND `c` IS NULL AND `d`!='5')"
+ * @example
+ *  $where = ["{OR}"=>["a"=>["{!=}"=>1], "b"=>["{><}"=>[1, 10, 3]], "c"=>["{~}"=>"aaa%"]]];
+ *  // $sql == " WHERE (`a`!='1' OR `b` NOT BETWEEN 1 AND 10 OR `c` LIKE 'aaa%')"
+ * @example
+ *  $where = "`a`=1";
+ *  // $sql == " WHERE `a`=1";
+ *
+ * @param 特殊的符号均以 "{}" 进行包裹, 存在以下可用项:
+ *  * `{NOT}` / `{!}` - 逻辑非, 对逻辑子句取反, 生成形式: `NOT (.........)`;
+ *  * `{OR}` / `{||}` - 逻辑或, 对逻辑子句进行逻辑或拼接, 生成形式: `... OR ... OR ...`;
+ *  * `{AND}` / `{&&}` - 逻辑与, 对逻辑子句进行逻辑与拼接 (默认拼接方式), 生成形式: `... AND ... AND ...`;
+ *  * `{!=}` - 不等, 生成形式: `...!='...'` / ` ... IS NOT NULL`;
+ *  * `{>}` - 大于, 生成形式: `...>...`;
+ *  * `{<}` - 小于, 生成形式: `...<...`;
+ *  * `{>=}` - 大于等于, 生成形式: `...>=...`;
+ *  * `{<=}` - 小于等于, 生成形式: `...<=...`;
+ *  * `{<>}` - 区间内, 生成形式: `... BETWEEN ... AND ...`, 目标数组至少存在两个数值;
+ *  * `{><}` - 区间外, 生成形式: `... NOT BETWEEN ... AND ...`, 目标数组至少存在两个数值;
+ *  * `{~}` - 模糊正匹配, 生成形式: `... LIKE ...`;
+ *  * `{!~}` - 模糊非匹配, 生成形式: `... NOT LIKE ...`;
+ */
+
+/**
+ * ORDER 子句语法规则:
+ * @example
+ *  $order = "`a` ASC, `b` DESC";
+ *  // $sql == " ORDER BY `a` ASC, `b` DESC");
+ * @example
+ *  $order = ["a"=>1, "b"=>-1, "c"=>true, "d"=>false, "e"=>"ASC", "f"=>"DESC"];
+ *  // $sql == " ORDER BY `a` ASC, `b` DESC, `c` ASC, `d` DESC, `e` ASC, `f` DESC"
+ *
+ * @param
+ *  * 当 value 位正数或 `true` 时, 生成 `ASC`; 否则生成 `DESC`;
+ *  * 当 value 为文本时, 直接拼接;
+ */
+
+/**
+ * LIMIT 子句语法规则:
+ * @example
+ *  $limit = 10;
+ *  // $sql == " LIMIT 10"
+ * @example
+ *  $limit = [10,10];
+ *  // $sql == " LIMIT 10, 10"
+ * @example
+ *  $limit = "20, 300";
+ *  // $sql == " LIMIT 20, 300"
+ *
+ * @param
+ *  * 当 $limit 值位文本或数值时, 直接拼接;
+ *  * 当 $limit 为数组时, 拼接前两个值;
+ */
+
+/**
+ * MySQL 客户端（内部使用连接池）
+ */
+class client {
+    /**
+     * 将数据进行转移，防止可能出现的 SQL 注入等问题；
+     */
+    function escape($value):string {
+        return "escaped string";
+    }
+    /**
+     * 返回事务对象 (绑定在一个连接上)
+     */
+    function begin_tx(): ?tx {
+        return new tx();
+    }
+    /**
+     * 执行制定的 SQL 查询, 并返回结果
+     * @param string $sql
+     * @return mixed SELECT 型语句返回结果集对象 `result`; 否则返回关联数组, 一般包含 affected_rows / insert_id 两项;
+     */
+    function query(string $sql): result {
+        return new result();
+    }
+    /**
+     * 返回最近一次执行的 SQL 语句
+     * 注意：由于协程的“并行”（穿插）执行，本函数返回的语句可能与当前上下问代码不对应；
+     */
+    function last_query(): string {
+        return "last executed sql";
+    }
+    /**
+     * 向指定表插入一行或多行数据 (自动进行 ESCAPE 转义)
+     * @param string $table 表名
+     * @param array $data 待插入数据, 多行关联数组插入多行数据(以首行 KEY 做字段名); 普通关联数组插入一行数据;
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
+     */
+    function insert(string $table, array $data): result {
+        return new result();
+    }
+    /**
+     * 从指定表格删除匹配的数据
+     * @param string $table 表名
+     * @param mixed $where WHERE 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
+     * @param mixed $order ORDER 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
+     * @param mixed $limit LIMIT 子句, 请参考上文;（数组形式描述将自动进行 ESCAPE 转义)
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
+     */
+    function delete(string $table, $where, $order = null, $limit = null): result {
+        return new result();
+    }
+    /**
+     * 更新指定表
+     * @param string $table 表名
+     * @param mixed $where WHERE 子句, 请参考上文;
+     * @param mixed $modify 当 $modify 为字符串时, 直接拼接 "UPDATE ... SET $modify", 否则按惯例数组进行 KEY = VAL 拼接;
+     * @param mixed $order ORDER 子句, 请参考上文;
+     * @param mixed $limit LIMIT 子句, 请参考上文;
+     * @return array 关联数组, 一般包含 affected_rows / insert_id 两项;
+     */
+    function update(string $table, $where, $modify, $order = null, $limit = null): result {
+        return new result();
+    }
+    /**
+     * 从指定表筛选获取数据
+     * @param string $table 表名
+     * @param mixed $fields 待选取字段, 为数组时其元素表示各个字段; 文本时直接拼接在 "SELECT $fields FROM $table";
+     *  关联数组的 KEY 表示对应函数, 仅可用于简单函数调用, 例如:
+     *      "SUM" => "a"
+     *  表示:
+     *      SUM(`a`)
+     * @param mixed $where WHERE 子句, 请参考上文;
+     * @param mixed $order ORDER 子句, 请参考上文;
+     * @param mixed $limit LIMIT 子句, 请参考上文;
+     */
+    function select(string $table, $fields, $where, $order = null, $limit = null): result {
+        return new result();
+    }
+    /**
+     * 从指定表筛选获取一条数据, 并立即返回该行数据
+     */
+    function one(string $table, $where, $order = null): array {
+        return [];
+    }
+    /**
+     * 从指定表获取一行数据的指定字段值
+     * @return mixed 字段类型映射请参见顶部说明
+     */
+    function get(string $table, string $field, array $where, $order) {
+        return null;
+    }
+    /**
+     * 获取服务端版本
+     */
+    function server_version():string {
+        return "5.5.5";
+    }
+};
+
+/**
+ * 事务对象, 与 client 对象接口基本相同
+ */
+class tx {
+    /**
+     * 提交当前事务
+     */
+    function commit() {}
+    /**
+     * 回滚当前事务
+     */
+    function rollback() {}
+    /**
+     * @see client::query()
+     * @return mixed cursor/array
+     */
+    function query(string $sql) {}
+    /**
+     * @see client::insert()
+     */
+    function insert(string $table, array $data): array {
+        return [];
+    }
+    /**
+     * @see client::delete()
+     */
+    function delete(string $table, $where, $order, $limit): array {
+        return [];
+    }
+    /**
+     * @see client::update()
+     */
+    function update(string $table, $where, $modify, $order = null, $limit = null): array {
+        return [];
+    }
+    /**
+     * @see client::select()
+     */
+    function select(string $table, $fields, $where, $order = null, $limit = null): result {
+        return new result();
+    }
+    /**
+     * @see client::one()
+     */
+    function one(string $table, $where, $order = null): array {
+        return [];
+    }
+    /**
+     * @see client::get()
+     * @return mixed 字段类型映射参见顶部说明；
+     */
+    function get(string $table, string $field, $where, $order = null) {
+        return null;
+    }
+};
+/**
+ * 结果集
+ */
+class result {
+    /**
+     * @var 结果集包含的记录行数
+     */
+    public $fetched_rows;
+    /**
+     * 读取下一行
+     * @return 下一行数据关联数组;
+     *  若读取已完成, 返回 NULL
+     */
+    function fetch_row():?array {
+        return [];
+    }
+    /**
+     * 读取 (剩余) 全部行
+     * @return 二维数组, 可能为空数组;
+     *  若读取已完成, 返回 NULL
+     */
+    function fetch_all():?array {
+        return [];
+    }
+};
diff --git a/doc/os.php b/doc/os.php
new file mode 100644
index 0000000..75c889d
--- /dev/null
+++ b/doc/os.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * 提供操作系统相关信息读取、进程操作等功能封装
+ */
+namespace flame\os;
+/**
+ * 信号
+ */
+const SIGTERM = 15;
+/**
+ * 信号
+ */
+const SIGKILL = 9;
+/**
+ * 信号
+ */
+const SIGINT  = 2;
+/**
+ * 信号
+ */
+const SIGUSR1 = 10;
+/**
+ * 信号
+ */
+const SIGUSR2 = 12;
+
+/**
+ * 获取当前系统网卡绑定地址等信息
+ * @example
+ *  array(2) {
+ *      ["lo"]=>
+ *      array(1) {
+ *          [0]=>
+ *          array(2) {
+ *          ["family"]=>
+ *          string(4) "IPv4"
+ *          ["address"]=>
+ *          string(9) "127.0.0.1"
+ *          }
+ *      }
+ *      ["eth0"]=>
+ *      array(1) {
+ *          [0]=>
+ *          array(2) {
+ *          ["family"]=>
+ *          string(4) "IPv4"
+ *          ["address"]=>
+ *          string(13) "10.110.16.197"
+ *          }
+ *      }
+ *      }
+ */
+function interfaces():array {
+    return [];
+}
+/**
+ * 异步启动进程
+ * @return 进程对象
+ */
+function spawn(string $command, array $argv = [], array $options = []):process {
+    return new process();
+}
+/**
+ * 调用上述 spawn() 异步启动进程, 并等待其结束, 返回进程标准输出
+ * @param array $options 目前可用的选项如下：
+ *  * "cwd" - string - 工作路径;
+ *  * "env" - array - 环境变量，K/V 结构文本;
+ * @return string 进程标准输出内容
+ */
+function exec(string $command, array $argv, array $options = []):string {
+    return "output of the process";
+}
+
+/**
+ * 进程对象
+ */
+class process {
+    /**
+     * 向进程发送指定信号
+     */
+    function kill(int $signal = SIGTERM) {}
+    /**
+     * 等待进程结束
+     */
+    function wait() {}
+    /**
+     * 获取进程标准输出（若进程还未结束需要等待）
+     */
+    function stdout():string {
+        return "stdout output";
+    }
+    /**
+     * 获取进程错误输出（若进程还未结束需要等待）
+     */
+    function stderr():string {
+        return "stderr output";
+    }
+}
\ No newline at end of file
diff --git a/doc/rabbitmq.php b/doc/rabbitmq.php
new file mode 100644
index 0000000..3b8bc2e
--- /dev/null
+++ b/doc/rabbitmq.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * 提供简单的 RabbitMQ 生产消费封装
+ */
+namespace flame\rabbitmq;
+
+/**
+ * 通过 RabbitMQ 连接地址连接并准备消费
+ * @param string $url 连接地址, 形如:
+ *  amqp://{username}:{password}@{host}:{port}/{vhost}?key1=val1
+ * 可使用参数:
+ *  * `prefetch` - int - 用于指定未 ack 时最大读取数量，将启动半数的消息处理协程；
+ * 注意：消费者在消费过程中发生网络错误时，会中断消费过程 `run()` 并抛出异常；
+ */
+function consume(string $url, string $queue): consumer {
+    return new consumer();
+}
+/**
+ * 通过 RabbitMQ 连接地址连接并启动生产
+ */
+function produce(string $url): producer {
+    return new producer();
+}
+
+/**
+ * 生产者
+ */
+class consumer {
+    /**
+     * 开始消费, 框架将按照一定数量启动并行消费协程, 并使用消息对象调用 $cb 回调函数;
+     * @param callable $cb 消费回调, 形如:
+     *  function callback($message) {}
+     * 当消费过程失败（网络连接异常、断开等），将抛出异常（结束消费）
+     */
+    function run(callable $cb) {}
+    /**
+     * 确认消息
+     */
+    function confirm(message $msg) {}
+    /**
+     * 拒绝消息(可选的返回队列, 重复消费)
+     * @param bool $requeue 若为真值，将被拒绝的消息放回队列重新等待消费；
+     */
+    function reject(message $msg, bool $requeue = false) {}
+    /**
+     * 关闭消费者 (结束 run() 执行流程)
+     * 注意: 由于消费速度较快, 关闭消费者可能需要一段时间, 以保证完整消费;
+     */
+    function close() {}
+}
+/**
+ * 消费者
+ */
+class producer {
+    /**
+     * 通过指定 exchange 生产（发送）一条消息
+     * @param mixed $message 为 string 时表示生产的消息的包体; 也可以为 message 类型的对象;
+     * @param string $routing_key 路由KEY, 可以覆盖 $message 对象内部的 routing_key 属性
+     */
+    function publish(string $exchange, $message, string $routing_key = null) {}
+}
+/**
+ * 消息对象
+ */
+class message implements JsonSerializable {
+    /**
+     * @var string 路由键值
+     */
+    public $routing_key;
+    /**
+     * @var string 消息体
+     */
+    public $body;
+    public $expiration;
+    public $reply_to;
+    public $correlation_id;
+    public $priority;
+    public $delivery_mode;
+    /**
+     * @var array
+     */
+    public $header;
+    public $content_encoding;
+    public $content_type;
+    public $cluster_id;
+    public $app_id;
+    public $user_id;
+    public $type_name;
+    /**
+     * @var int 消息时间戳，一般为秒级（由于可用户自定指定，含义可能发生变异）
+     */
+    public $timestamp;
+    public $message_id;
+    /**
+     * 构建一条新的 message 消息
+     */
+    function __construct(string $body = "", string $routing_key = "") {}
+    /**
+     * 返回消息体
+     */
+    function __toString(): string {
+        return "body of message";
+    }
+    /**
+     * 自定义 JSON 序列化, 返回包含 routing_key / body / timestamp 字段的数组;
+     */
+    function jsonSerialize(): array {
+        return [];
+    }
+}
diff --git a/doc/redis.php b/doc/redis.php
new file mode 100644
index 0000000..28a17dc
--- /dev/null
+++ b/doc/redis.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * 提供 Redis 基本客户端封装，内部使用连接池：
+ * 注意：
+ * 1. 暂未实现 SUBSCRIBE 相关命令；
+ * 2. 单客户端内连接池大小上限为 6 （单进程），即访问并行很大时若 6 个连接都在使用中，须排队等待；
+ */
+namespace flame\redis;
+
+function connect(string $url):client {
+    return new client();
+}
+/**
+ * Redis 客户端
+ * 支持 Redis 大部分指令:
+ *
+ * @method get(string $key)
+ * @method int del(string $key)
+ * @method int expire(string $key, int $seconds)
+ * @method int ttl(string $key)
+ * @method string type(string $key)
+ * @method string set(string $key, string $val)
+ * @method string mset(string $key, string $val)
+ * @method array mget(string $key)
+ * @method array scan(int $cursor)
+ * @method int incr(string $key)
+ * @method int incrby(string $key, int $by)
+ * @method int sadd(string $key, string $member)
+ * @method int scard(string $key)
+ * @method string spop(string $key)
+ * @method int srem(string $key, string $member)
+ * @method string lpop(string $key)
+ * @method string rpop(string $key)
+ * @method int llen(string $key)
+ * @method int lpush(string $key)
+ * @method int rpush(string $key)
+ * @method array blpop(string $key)
+ * @method array lrpop(string $key)
+ * @method int hdel(string $key)
+ * @method hget(string $key, string $field)
+ * @method int hset(string $key, string $field, string $val)
+ * @method array hmget(string $key, string $field1)
+ * @method string hmset(string $key, string $field, string $val)
+ * @method array hgetall(string $key)
+ * @method int hincrby(string $key, string $field, int $by)
+ * @method int hlen(string $key)
+ * @method int zadd(string $key, $score, $member)
+ * @method int zincrby(string $key, int $inc, string $member)
+ * @method int zcount(string $key, int $min, int $max)
+ * @method int zcard(string $key)
+ * @method int zrank(string $key, string $member)
+ * @method int zrem(string $key, string $member)
+ * @method string zscore(string $key, string $member)
+ * @method array zrange(string $key, int $start, int $stop)
+ * @method array zrevrange(string $key, int $start, int $stop)
+ * @method array zrangebyscore(string $key, int $min, int $max)
+ * @method array zrevrangebyscore(string $key, int $min, int $max)
+ * ...
+ * 这里仅列出了常用的部分函数及其对应的必要参数
+ */
+class client {
+    /**
+     * 用于通用的实现可用 Redis 命令，支持 REDIS 大部分指令
+     * @return mixed 可能返回 int/string/array/null 等类型
+     */
+    function __call($name, $argv) {}
+    /**
+     * @return bool true
+     */
+    function __isset($name) {
+        return true;
+    }
+    /**
+     * 批量执行
+     */
+    function multi(): tx {
+        return new tx();
+    }
+    /**
+     * @uimplement 暂未实现
+     */
+    function subscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function unsubscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function psubscribe() {}
+    /**
+     * @uimplement 暂未实现
+     */
+    function punsubscribe() {}
+}
+/**
+ * 用于记录待执行的命令并批量执行
+ */
+class tx {
+    /**
+     * 所有 client 支持的命令, 但不会立即执行; 返回对象自身, 可连续串写, 例如:
+     *  @example
+     *      $r = $tx->get("xxx")->set("xxx","yyy")->exec();
+     */
+    function __call($name, $argv): tx {
+        return $this;
+    }
+    /**
+     * 执行目前提交的命令并带回所有响应返回
+     * @return array 返回数据项与命令一一对应
+     */
+    function exec():array {
+        return [];
+    }
+}
+
+
+
+$r = new client();
diff --git a/doc/tcp.php b/doc/tcp.php
new file mode 100644
index 0000000..d8502d0
--- /dev/null
+++ b/doc/tcp.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * 提供对 TCP 网络操作的相关封装
+ */
+namespace flame\tcp;
+
+/**
+ * 连接指定地址, 并返回 socket 对象
+ * @param string $address 连接地址, 例如: "127.0.0.1:8687"
+ */
+function connect(string $address):socket {
+    return new socket();
+}
+
+class socket {
+    /**
+     * 本地地址（含端口）
+     * @var string
+     */
+    public $local_address;
+    /**
+     * 远端地址（含端口）
+     * @var string
+     */
+    public $remote_address;
+    /**
+     * 读取一定量的数据, 实际读取方式根据 $completion 参数决定;
+     * @param mixed $completion 
+     *  * 参数为 null 时, 表示随机读取一定量数据 (不会超过 8192 字节);
+     *  * 参数为 int 时, 表示读取指定量的数据, 例如 $completion = 1024 表示读取 1024 字节;
+     *  * 参数为 string 时, 表示读取到指定的结束符, 例如 $completion = "\r\n" 读取到 "abc\r\n" 字符串;
+     */
+    function read($completion = null) {}
+    /**
+     * 发送数据
+     * @param string $data
+     */
+    function write(string $data) {}
+    /**
+     * 关闭连接
+     */
+    function close() {}
+}
+
+class server {
+    /**
+     * 创建一个服务器, 绑定在指定的地址上, 准备服务
+     * @param string $address 绑定地址, 例如: "0.0.0.0:8888" 或 ":::8888"
+     */
+    function __construct(string $address) {}
+    /**
+     * 启动服务器, 当有连接建立时, 启用协程执行回调函数;
+     * @param callable $cb 回调函数, 形如:
+     *  function($socket) {}
+     */
+    function run(callable $cb) {}
+    /**
+     * 关闭服务器
+     */
+    function close() {}
+}
\ No newline at end of file
diff --git a/doc/time.php b/doc/time.php
new file mode 100644
index 0000000..770697c
--- /dev/null
+++ b/doc/time.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * 时间相关函数
+ */
+namespace flame\time;
+
+/**
+ * 当前协程休眠 $ms 毫秒（调度式休眠，实际 CPU 可调度执行其他协程）
+ */
+function sleep(int $ms) {}
+/**
+ * 获取毫秒时间戳
+ * (此时间戳采用框架缓存机制，较默认获取系统时间函数消耗较小)
+ */
+function now():int {
+    return 1557659996427;
+}
+/**
+ * 获取标准时间 "YYYY-mm-dd HH:ii:ss"
+ * (按标准格式输出上述 `now()` 对应时间)
+ */
+function iso():string {
+    return "2019-03-31 21:40:25";
+}
diff --git a/doc/toml.php b/doc/toml.php
new file mode 100755
index 0000000..4209645
--- /dev/null
+++ b/doc/toml.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * 提供对 TOML 格式文本、文件的解析；
+ * TOML 格式定义请参考：https://github.com/toml-lang/toml
+ */
+namespace flame\toml;
+
+/**
+ * 解析 TOML 格式的字符串并返回关联数组
+ * @param string $toml 文本 TOML 格式
+ * 注意：若文本不完整或不正确会抛出异常；
+ */
+function parse_string(string $toml): array {
+    return [];
+}
+
+/**
+ * 解析指定 TOML 格式的数据文件，并返回关联数组
+ * @param string $file 待解析文件路径
+ */
+function parse_file($file): array {
+    return [];
+}
diff --git a/doc/udp.php b/doc/udp.php
new file mode 100644
index 0000000..9837b52
--- /dev/null
+++ b/doc/udp.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * 提供对 UDP 网络操作的简单封装
+ */
+namespace flame\udp;
+
+/**
+ * 连接指定地址, 并返回 socket 对象
+ * @param string $address 连接地址, 例如: "127.0.0.1:8687"
+ * 注意: UDP "链接" 能在一定程度上提高效率;
+ * 注意：非连接型可直接使用构造函数创建；
+ */
+function connect(string $address):socket {
+    return new socket();
+}
+
+/**
+ * 非连接型 socket 可直接调用构造创建：
+ * @example
+ *    $socket = new flame\udp\socket();
+ */
+class socket {
+    /**
+     * 构建 socket 可选的进行地址绑定
+     * 实际函数拥有两种形式:
+     *  __construct(string $address, array $option = [])
+     * 或
+     *  __construct(array $option = [])
+     * 前者可用于地址绑定;
+     * @param array $option 可用选项如下:
+     * * `max` integer - 接收缓冲区上限(默认为 64k 可减小已提升效率)
+     */
+    function __construct($opt_or_address = null, array $option = []) {}
+    /**
+     * @var string 本地地址（含端口
+     */
+    public $local_address;
+    /**
+     * @var string 远端地址（含端口）
+     */
+    public $remote_address;
+    /**
+     * 发送一个 UDP 包给已连接的对端
+     * 注意: 只能在"连接"形态下使用
+     * @see flame\udp\connect()
+     */
+    function send(string $data) {}
+    /**
+     * 向指定目标 $to 发送一个 UDP 包
+     * @param string $to 目标地址, 可以是域名或地址;
+     */
+    function send_to(string $data, string $to) {}
+    /**
+     * 接收并返回一个 UDP 包
+     * @return mixed 返回接收到的数据包(文本 string); 若 socket 被关闭则返回 null;
+     */
+    function recv():string {
+        return "payload from udp packet";
+    }
+    /**
+     * 关闭连接
+     * 注意: 正在进行的发送 send(_to) 或 recv(_from) 接收动作将被终止;
+     */
+    function close() {}
+}
+/**
+ * 简化的服务端, 相较使用上述 socket 自行实现效率稍高
+ */
+class server {
+    /**
+     * 创建一个服务器, 绑定在指定的地址上, 准备服务
+     * @param string $address 绑定地址, 例如: "0.0.0.0:8888" 或 ":::8888"
+     */
+    function __construct(string $address, array $options = []) {}
+    /**
+     * 启动服务器, 当有连接建立时, 启用协程执行回调函数;
+     * @param callable $cb 回调函数, 形如:
+     *  function(string $data, string $from) {}
+     *  $data - 接收的数据包
+     *  $from - 来源地址
+     */
+    function run(callable $cb) {}
+    /**
+     * 使用当前服务器对应 UDP 套接字, 向指定地址发送消息
+     * @see flame::socket::send_to()
+     */
+    function send_to(string $data, string $address) {}
+    /**
+     * 关闭服务器 (结束 run() 过程, 保证正在进行处理的"消费"协程完成)
+     */
+    function close() {}
+}
diff --git a/example/usage.php b/example/usage.php
deleted file mode 100644
index 436ada6..0000000
--- a/example/usage.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-dl("mill.so");
-// $app = new mill\application();
-// // for($i=0;$i<mill\application::concurrency;++$i) {
-// for($i=0;$i<1;++$i) {
-// 	$proc = new mill\process();
-// 	$svr  = new mill\udp\server("127.0.0.1", 6696);
-// 	$svr->on_packet = function($packet, $remote_addr) {
-// 		echo $packet, " ", $remote_addr, " ", posix_getpid(), "\n";
-// 	};
-// 	$proc->add($svr);
-// 	$app->add($proc);
-// }
-// $app->run();
-
-function server1() {
-	mill\sleep(1000);
-	echo "ccccc\n";
-	$server = new mill\udp\server("127.0.0.1", 6696);
-	$server->listen();
-	echo "dddddd\n";
-	while($packet = $server->recv()) {
-		echo posix_getpid(), " ", $server->remote_addr, " => ", $packet,"\n";
-	}
-	$server->close();
-}
-
-function server2() {
-	$server = new mill\udp\server("127.0.0.1", 6697);
-	$server->listen();
-	while($packet = $server->recv()) {
-		echo posix_getpid(), " ", $server->remote_addr, " => ", $packet,"\n";
-	}
-
-	$server->close();
-}
-
-function tcp1() {
-	$socket = mill\tcp\client();
-	$socket->recv();
-	$socket->send();
-}
-
-function http1() {
-	$task_queue = [];
-	mill\go(function() use($task_queue) {
-		// .......
-		array_push($task_queue, "task");
-	});
-	for($i=0;$i<100;++$i) {
-		mill\go(function() use($task_queue) {
-			$taks = array_pop($task_queue);
-			$client = mill\http\client();
-			$client->get("http://aaaaaa", ["a"=>$task], ["header"=>[]]);
-			$client->post("http://bbbbbbb", ["a"=>"b"], []);
-			$response = $client->request("GET", "URL", ["a"=>"b"], []);
-			$response->body
-			if($response->header["Content-type"] === "text/plain") {
-
-			}
-			$client->get("key", function($err, $value) {
-
-			});
-			$value = $client->get("key");
-		});
-	}
-}
-
-function main() {
-	echo "11111\n";
-	// mill\go(server1);
-	// echo "22222\n";
-	// mill\go(server2);
-	// file_get_contents();
-	// echo "33333\n";
-	mill\sleep(1000000);
-}
-
-mill\run(main, ["worker_count"=>1]);
-
-// yield target;
-//
-//
-// 1. target -> object of Generator
-// 2. target -> function() {} definition, if function return object of Generator -> 1.
-// 3. target -> evaluate other
diff --git a/src/core.cpp b/src/core.cpp
deleted file mode 100644
index 906525f..0000000
--- a/src/core.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-#include "vendor.h"
-#include "core.h"
-//#include "zend_generators.h"
-
-boost::asio::io_service* core::io_ = nullptr;
-
-php::value core::error_to_exception(const boost::system::error_code& err) {
-	php::object ex = php::object::create("Exception");
-	ex.call("__construct", err.message(), err.value());
-	return std::move(ex);
-}
-// 为了提高 sleep 函数的效率，考虑复用少量 timer
-static std::vector<boost::asio::deadline_timer*> timers;
-
-void core::init(php::extension_entry& extension) {
-	extension.on_module_startup(core::module_startup);
-	extension.on_module_shutdown(core::module_shutdown);
-
-	extension.add<core::go>("flame\\go");
-	extension.add<core::run>("flame\\run");
-	extension.add<core::sleep>("flame\\sleep");
-	extension.add<core::_fork>("flame\\fork");
-}
-bool core::module_startup(php::extension_entry& extension) {
-	core::io_ = new boost::asio::io_service();
-	for(int i=0;i<16;++i) {
-		timers.push_back(new boost::asio::deadline_timer(core::io()));
-	}
-	return true;
-}
-bool core::module_shutdown(php::extension_entry& extension) {
-	while(!timers.empty()) {
-		delete timers.back();
-		timers.pop_back();
-	}
-	delete core::io_;
-	return true;
-}
-// 核心调度
-static void generator_runner(php::object g) {
-
-    if(EG(exception)) {
-        core::io().stop();
-        return ;
-    }
-	if(!g.scall("valid").is_true()) {
-        /*if(exception) {*/
-            //core::io().stop();
-        /*}*/
-        //zend_object* og = g;
-        //zend_generator* zg = (zend_generator)og;
-        /*std::printf("exception: %08x\n", EG(exception));*/ 
-        return;
-	}
-	core::io().post([g] () mutable {
-		php::value v = g.call("current");
-		if(v.is_callable()) {
-			php::callable cb = v;
-			// 传入的 函数 用于将异步执行结果回馈到 "协程" 中
-			cb(php::value([g] (php::parameters& params) mutable -> php::value {
-				int len = params.length();
-				if(len == 0) {
-					g.call("next");
-					generator_runner(g);
-					return nullptr;
-				}
-				// 首个参数表达错误信息
-				if(params[0].is_empty()) { // 没有错误
-					if(len > 1) { // 有回调数据
-						g.call("send", params[1]);
-					}else{
-						g.call("next");
-					}
-					generator_runner(g);
-				}else if(params[0].is_instance_of("Exception")) { // 内置 exception
-					g.call("throw", params[0]);
-                    generator_runner(g);
-				}else{ // 其他错误信息
-					php::object ex = php::object::create("Exception");
-					ex.call("__construct", params[0].to_string());
-					g.call("throw", std::move(ex));
-					generator_runner(g);
-				}
-				return nullptr;
-			}));
-		}else{
-			g.call("send", v);
-			generator_runner(g);
-		}
-	});
-}
-// 所谓“协程”
-php::value core::go(php::parameters& params) {
-	php::value r;
-	if(params[0].is_callable()) {
-		php::callable c = params[0];
-		r = c();
-	}else{
-		r = params[0];
-	}
-	if(r.is_object() && r.is_instance_of("Generator")) {
-		generator_runner(r);
-		return nullptr;
-	}else{
-		return std::move(r);
-	}
-	return nullptr;
-}
-static bool started = false;
-// 程序启动
-php::value core::run(php::parameters& params) {
-	started = true;
-	if(params.length() > 0) {
-		go(params);
-	}
-	core::io().run();
-	return nullptr;
-}
-// sleep 对 timer 进行预分配的重用优化，实际上这个流程可能不会有太实际的优化效果，
-// 这里的优化更多的是对后面其他对象、函数实现的一种示范
-php::value core::sleep(php::parameters& params) {
-	int duration = params[0];
-
-	return php::value([duration] (php::parameters& params) -> php::value {
-		std::shared_ptr<boost::asio::deadline_timer> timer;
-
-		if(timers.empty()) { // 没有空闲的预分配 timer 需要创建新的
-			timer.reset(new boost::asio::deadline_timer(core::io()));
-		}else{ // 重复使用这些 timer
-			timer.reset(timers.back(), [] (boost::asio::deadline_timer *timer) {
-				timers.push_back(timer); // 用完放回去
-			});
-			timers.pop_back();
-		}
-
-		timer->expires_from_now(boost::posix_time::milliseconds(duration));
-		php::callable done = params[0];
-		timer->async_wait([timer, done] (const boost::system::error_code& ec) mutable {
-			if(ec) {
-				done(ec.message());
-			}else{
-				done(nullptr);
-			}
-		});
-		return nullptr;
-	});
-}
-
-static bool forked = false;
-php::value core::_fork(php::parameters& params) {
-#ifndef SO_REUSEPORT
-	throw php::exception("failed to fork: SO_REUSEPORT needed");
-#endif
-	if(started) {
-		throw php::exception("failed to fork: already running");
-	}
-	if(forked) {
-		throw php::exception("failed to fork: already forked");
-	}
-	forked = true;
-	int count = params[0];
-	if(count <= 0) {
-		count = 1;
-	}
-	for(int i=0;i<count;++i) {
-		if(fork() == 0) break;
-	}
-	return nullptr;
-}
diff --git a/src/core.h b/src/core.h
deleted file mode 100644
index b5945a3..0000000
--- a/src/core.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-class core {
-public:
-	// io_servcie 需要在 module 的 startup 流程中启动 shutdown 流程中销毁
-	// 内部任务队列引用了 PHP 对象，这些对象会在 shutdown 之前被 PHP 主动销毁
-	static inline boost::asio::io_service& io() {
-		return *io_;
-	}
-	static void init(php::extension_entry& extension);
-	static php::value error_to_exception(const boost::system::error_code& err);
-private:
-	static boost::asio::io_service* io_;
-	static bool module_startup (php::extension_entry& extension);
-	static bool module_shutdown(php::extension_entry& extension);
-	static php::value go(php::parameters& params);
-	static php::value run(php::parameters& params);
-	static php::value sleep(php::parameters& params);
-	static php::value _fork(php::parameters& params);
-};
diff --git a/src/coroutine.h b/src/coroutine.h
new file mode 100644
index 0000000..36f1360
--- /dev/null
+++ b/src/coroutine.h
@@ -0,0 +1,156 @@
+#pragma once
+#include "vendor.h"
+
+class coroutine;
+class coroutine_handler {
+public:
+    coroutine_handler() = default;
+    coroutine_handler(std::shared_ptr<coroutine> co)
+    : co_(co) { }
+    coroutine_handler(const coroutine_handler& ch) = default;
+    virtual ~coroutine_handler() {
+
+    }
+    coroutine_handler& operator[](boost::system::error_code& error) {
+        error_ = &error;
+        return *this;
+    }
+    coroutine_handler& operator[](std::size_t& size) {
+        size_ = &size;
+        return *this;
+    }
+    
+    void operator()(const boost::system::error_code& error, std::size_t size = 0);
+
+    inline bool operator !() {
+        return !co_;
+    }
+    
+    inline operator bool() {
+        return !!co_;
+    }
+
+    void reset() {
+        co_.reset();
+        size_ = nullptr;
+        error_= nullptr;
+    }
+    
+    void reset(std::shared_ptr<coroutine> co) {
+        co_ = co;
+        size_ = nullptr;
+        error_= nullptr;
+    }
+    void reset(coroutine_handler& ch) {
+        co_ = ch.co_;
+        size_ = nullptr;
+        error_= nullptr;
+    }
+
+    void suspend();
+    void resume();
+
+    inline void error(const boost::system::error_code& error) {
+        if(error_) *error_ = error;
+    }
+    inline void size(std::size_t sz) {
+        if(size_) *size_ = sz;
+    }
+public:
+    std::size_t*                size_ = nullptr;
+    boost::system::error_code* error_ = nullptr;
+    std::shared_ptr<coroutine>    co_;
+
+private:
+
+};
+
+class coroutine: public std::enable_shared_from_this<coroutine> {
+public:
+    coroutine(boost::asio::executor ex)
+    : ex_(ex) {
+        
+    }
+    virtual ~coroutine() {
+
+    }
+    
+    template <class Handler>
+    static void start(boost::asio::executor ex, Handler&& fn);
+
+    virtual void suspend() {
+        c2_ = std::move(c2_).resume();
+    }
+    virtual void resume() {
+        boost::asio::post(ex_, [co = shared_from_this()] () {
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+protected:
+    boost::context::fiber c1_;
+    boost::context::fiber c2_;
+    boost::asio::executor ex_;
+
+    friend class coroutine_handler;
+};
+
+template <class Handler>
+void coroutine::start(boost::asio::executor ex, Handler&& fn) {
+    auto co = std::make_shared<coroutine>(ex);
+    
+    boost::asio::post(co->ex_, [co, fn] () mutable {
+        co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
+            co->c2_ = std::move(c2);
+            fn(coroutine_handler{co});
+            return std::move(co->c2_);
+        });
+        co->c1_ = std::move(co->c1_).resume();
+    });
+}
+
+inline void coroutine_handler::operator()(const boost::system::error_code& error, std::size_t size) {
+    if(error_) *error_ = error;
+    if(size_) *size_ = size;
+    co_->resume();
+}
+
+inline void coroutine_handler::suspend() {
+    co_->suspend();
+}
+
+inline void coroutine_handler::resume() {
+    co_->resume();
+}
+
+namespace boost::asio {
+    template <>
+    class async_result<::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
+    public:
+        explicit async_result(::coroutine_handler& ch) : ch_(ch), size_(0) {
+            ch_.size_ = &size_;
+        }
+        using completion_handler_type = ::coroutine_handler;
+        using return_type = std::size_t;
+        return_type get() {
+            ch_.suspend();
+            return size_;
+        }
+    private:
+        ::coroutine_handler &ch_;
+        std::size_t size_;
+    };
+
+    template <>
+    class async_result<::coroutine_handler, void (boost::system::error_code error)> {
+    public:
+        explicit async_result(::coroutine_handler& ch) : ch_(ch) {
+        }
+        using completion_handler_type = ::coroutine_handler;
+        using return_type = void;
+        void get() {
+            ch_.suspend();
+        }
+    private:
+        ::coroutine_handler &ch_;
+    };
+} // namespace boost::asio
diff --git a/src/coroutine_mutex.h b/src/coroutine_mutex.h
new file mode 100644
index 0000000..7948f0c
--- /dev/null
+++ b/src/coroutine_mutex.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "coroutine.h"
+
+class coroutine_mutex {
+public:
+    coroutine_mutex()
+    : mt_(false) {
+
+    }
+
+    void lock(coroutine_handler& ch) {  
+        while(mt_) {
+            cm_.insert(&ch);
+            ch.suspend();
+        }
+        mt_ = true;
+    }
+
+    bool try_lock() {
+        if (!mt_) {
+            mt_ = true;
+            return true;
+        }
+        return false;
+    }
+
+    void unlock() {
+        while(!cm_.empty()) {
+            auto ch = cm_.extract(cm_.begin()).value();
+            ch->resume();
+        }
+        mt_ = false;
+    }
+private:
+    std::set<coroutine_handler*> cm_;
+    bool mt_;
+};
+class coroutine_guard {
+public:
+    coroutine_guard(coroutine_mutex& cm, coroutine_handler& ch)
+    : cm_(cm) {
+        cm_.lock(ch);
+    }
+    ~coroutine_guard() {
+        cm_.unlock();
+    }
+private:
+    coroutine_mutex   cm_;
+};
diff --git a/src/coroutine_queue.h b/src/coroutine_queue.h
new file mode 100644
index 0000000..6bf6826
--- /dev/null
+++ b/src/coroutine_queue.h
@@ -0,0 +1,80 @@
+#pragma once
+#include "coroutine.h"
+
+template <class T>
+class coroutine_queue: private boost::noncopyable {
+public:
+    coroutine_queue(std::size_t n = 1)
+    : n_(n)
+    , closed_(false) {
+    }
+
+    // !!!! 生产者进行关闭 !!!!
+    void close() {
+        closed_ = true;
+        while (!c_.empty()) {
+            auto ch = c_.extract(c_.begin()).value();
+            ch->resume();
+        }
+    }
+    bool is_closed() {
+        return q_.empty() && closed_;
+    }
+
+    void push(const T& t, coroutine_handler& ch) {
+        if (closed_) throw php::exception(zend_ce_error_exception
+            , "Failed to push queue: already closed"
+            , -1);
+        if (q_.size() >= n_) {
+            p_.insert(&ch);
+            ch.suspend();
+        }
+        q_.push_back(t);
+        while(!q_.empty() && !c_.empty()) {
+            auto ch = c_.extract(c_.begin()).value();
+            ch->resume();
+        }
+    }
+
+    std::optional<T> pop(coroutine_handler& ch) {
+        for(;;) {
+            if (!q_.empty()) break; // 有数据消费
+            else if (closed_) return std::optional<T>();  // 无数据关闭
+            else { // 无数据等待
+                c_.insert(&ch);
+                ch.suspend();
+            }
+        }
+        T t = q_.front();
+        q_.pop_front();
+
+        while (!p_.empty()) {
+            auto ch = p_.extract(p_.begin()).value();
+            ch->resume();
+        }
+        return std::optional<T>(t);
+    }
+
+/* private: */
+    std::size_t                 n_;
+    std::list<T>                q_;
+    std::set<coroutine_handler*> c_; // 消费者
+    std::set<coroutine_handler*> p_; // 生产者
+    bool                   closed_;
+};
+
+template <class T>
+std::shared_ptr < coroutine_queue<T> > select_queue(std::vector < std::shared_ptr<coroutine_queue<T>> > queues, coroutine_handler &ch) {
+TRY_ALL:
+    bool all_closed = true;
+    for(auto i=queues.begin();i!=queues.end();++i) {
+        if (!(*i)->q_.empty()) return *i;
+        else if (!(*i)->closed_) all_closed = false;
+    }
+    if (all_closed) return std::shared_ptr< coroutine_queue<T> >(nullptr);
+    for(auto i=queues.begin();i!=queues.end();++i) (*i)->c_.insert(&ch);
+    ch.suspend();
+    for (auto i = queues.begin(); i != queues.end(); ++i) (*i)->c_.erase(&ch);
+    goto TRY_ALL;
+}
+
diff --git a/src/extension.cpp b/src/extension.cpp
deleted file mode 100644
index 2505cfb..0000000
--- a/src/extension.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "vendor.h"
-#include "core.h"
-#include "net/init.h"
-#include "net_mill/http/init.h"
-
-extern "C" {
-	ZEND_DLEXPORT zend_module_entry* get_module() {
-		static php::extension_entry extension(EXTENSION_NAME, EXTENSION_VERSION);
-		core::init(extension);
-		net::init(extension);
-        net::http::init(extension);
-		return extension;
-	}
-}
diff --git a/src/flame/compress/compress.cpp b/src/flame/compress/compress.cpp
new file mode 100644
index 0000000..5407e38
--- /dev/null
+++ b/src/flame/compress/compress.cpp
@@ -0,0 +1,46 @@
+#include "compress.h"
+extern "C" {
+#include <librdkafka/snappy.h>
+}
+
+namespace flame::compress {
+    
+    static php::value snappy_compress(php::parameters& params) {
+        php::string data = params[0].to_string();
+        struct iovec iov_in = {
+            .iov_base = data.data(),
+            .iov_len = data.size(),
+        };
+        std::size_t len = rd_kafka_snappy_max_compressed_length(data.size());
+        php::string out(len);
+        struct iovec iov_out = {
+            .iov_base = out.data(),
+            .iov_len = 0xffffffff,
+        };
+        struct snappy_env env;
+        rd_kafka_snappy_init_env(&env);
+        rd_kafka_snappy_compress_iov(&env, &iov_in, 1, data.size(), &iov_out);
+        rd_kafka_snappy_free_env(&env);
+        out.shrink(iov_out.iov_len);
+        return std::move(out);
+    }
+
+    static php::value snappy_uncompress(php::parameters& params) {
+        php::string data = params[0].to_string();
+        std::size_t len;
+        rd_kafka_snappy_uncompressed_length(data.c_str(), data.size(), &len);
+        php::string out(len);
+        rd_kafka_snappy_uncompress(data.c_str(), data.size(), out.data());
+        return std::move(out);
+    }
+
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<snappy_compress>("flame\\compress\\snappy_compress", {
+                {"data", php::TYPE::STRING},
+            })
+            .function<snappy_uncompress>("flame\\compress\\snappy_uncompress", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/flame/compress/compress.h b/src/flame/compress/compress.h
new file mode 100644
index 0000000..480e4cb
--- /dev/null
+++ b/src/flame/compress/compress.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::compress {
+    void declare(php::extension_entry &ext);
+} // namespace flame::compress
diff --git a/src/flame/controller.cpp b/src/flame/controller.cpp
new file mode 100644
index 0000000..18b2821
--- /dev/null
+++ b/src/flame/controller.cpp
@@ -0,0 +1,73 @@
+#include "controller.h"
+#include "worker.h"
+// #include "../logger_master.h"
+// #include "../logger_worker.h"
+
+namespace flame {
+    // 全局控制器
+    std::unique_ptr<controller> gcontroller;
+
+    controller::controller()
+    : type(process_type::UNKNOWN)
+    , env(boost::this_process::environment())
+    , status(STATUS_UNKNOWN)
+    , evnt_cb(new std::multimap<std::string, php::callable>()) {
+
+        worker_size = std::atoi(env["FLAME_MAX_WORKERS"].to_string().c_str());
+        worker_size = std::min(std::max((int)worker_size, 0), 256);
+        // FLAME_MAX_WORKERS 环境变量会被继承, 故此处顺序须先检测子进程
+        if (env.count("FLAME_CUR_WORKER") > 0) {
+            type = process_type::WORKER;
+            worker_idx = std::atoi(env["FLAME_CUR_WORKER"].to_string().c_str());
+        }
+        else if (worker_size > 0) type = process_type::MASTER;
+        else { // 单进程模式
+            worker_size = 0;
+            type = process_type::WORKER;
+        }
+        mthread_id = std::this_thread::get_id();
+    }
+
+    controller *controller::on_init(std::function<void (const php::array &options)> fn) {
+        init_cb.push_back(fn);
+        return this;
+    }
+
+    void controller::init(php::array options) {
+        for (auto fn : init_cb) fn(options);
+    }
+
+    controller* controller::on_stop(std::function<void ()> fn) {
+        stop_cb.push_back(fn);
+        return this;
+    }
+
+    void controller::stop() {
+        for (auto fn : stop_cb) fn();
+        delete evnt_cb;
+    }
+
+    controller* controller::add_event(const std::string& event, php::callable cb) {
+        evnt_cb->insert({event, cb});
+        return this;
+    }
+
+    controller* controller::del_event(const std::string& event) {
+        evnt_cb->erase(event);
+        return this;
+    }
+
+    std::size_t controller::cnt_event(const std::string& event) {
+        return evnt_cb->count(event);
+    }
+
+    void controller::event(const std::string& event, std::vector<php::value> params) {
+        auto ft = evnt_cb->equal_range(event);
+        for(auto i=ft.first; i!=ft.second; ++i) i->second.call(params);
+    }
+
+    void controller::event(const std::string& event) {
+        auto ft = evnt_cb->equal_range(event);
+        for(auto i=ft.first; i!=ft.second; ++i) i->second.call();
+    }
+}
diff --git a/src/flame/controller.h b/src/flame/controller.h
new file mode 100644
index 0000000..2b9aa8d
--- /dev/null
+++ b/src/flame/controller.h
@@ -0,0 +1,52 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame {
+
+    class controller {
+    public:
+        boost::asio::io_context context_x;
+        boost::asio::io_context context_y;
+        boost::asio::io_context context_z;
+        enum class process_type {
+            UNKNOWN = 0,
+            MASTER = 1,
+            WORKER = 2,
+        } type;
+        boost::process::environment env;
+        enum status_t {
+            STATUS_UNKNOWN     = 0x00,
+            STATUS_INITIALIZED = 0x01,
+            STATUS_CLOSING     = 0x02,
+            STATUS_QUITING     = 0x04,
+            STATUS_RSETING     = 0x08,
+            STATUS_EXCEPTION   = 0x10,
+            STATUS_RUN         = 0x20,
+            STATUS_CLOSECONN   = 0x40,
+        };
+        int status;
+        std::uint8_t   worker_idx;
+        std::size_t    worker_size;
+        std::size_t    worker_quit; // 多进程退出超时时间
+        std::thread::id mthread_id;
+    private:
+        std::list<std::function<void (const php::array& options)>>  init_cb;
+        std::list<std::function<void ()>>                           stop_cb;
+        std::multimap<std::string, php::callable>*                  evnt_cb; // 防止 PHP 提前回收, 使用堆容器
+    public:
+        controller();
+        controller(const controller& c) = delete;
+        controller *on_init(std::function<void(const php::array &options)> fn);
+        void init(php::array options);
+        controller* on_stop(std::function<void ()> fn);
+        void stop();
+        controller* add_event(const std::string& event, php::callable cb);
+        controller* del_event(const std::string& event);
+        std::size_t cnt_event(const std::string& event);
+        void event(const std::string& event, std::vector<php::value> params);
+        void event(const std::string& event);
+    };
+
+    extern std::unique_ptr<controller> gcontroller;
+
+}
\ No newline at end of file
diff --git a/src/flame/coroutine.cpp b/src/flame/coroutine.cpp
new file mode 100644
index 0000000..334d012
--- /dev/null
+++ b/src/flame/coroutine.cpp
@@ -0,0 +1,99 @@
+#include "controller.h"
+#include "coroutine.h"
+#include "../util.h"
+
+static void coroutine_php_save_context(flame::coroutine::php_context_t &ctx) {
+    ctx.vm_stack = EG(vm_stack);
+    ctx.vm_stack_top = EG(vm_stack_top);
+    ctx.vm_stack_end = EG(vm_stack_end);
+    //   ctx.scope = EG(fake_scope);
+    ctx.current_execute_data = EG(current_execute_data);
+    ctx.exception = EG(exception);
+    ctx.exception_class = EG(exception_class);
+    ctx.error_handling = EG(error_handling);
+}
+
+static void coroutine_php_restore_context(flame::coroutine::php_context_t &ctx) {
+    EG(vm_stack) = ctx.vm_stack;
+    EG(vm_stack_top) = ctx.vm_stack_top;
+    EG(vm_stack_end) = ctx.vm_stack_end;
+    // EG(fake_scope) = ctx.scope;
+    EG(current_execute_data) = ctx.current_execute_data;
+    EG(exception) = ctx.exception;
+    EG(exception_class) = ctx.exception_class;
+    EG(error_handling) = ctx.error_handling;
+}
+// 参考 zend_execute.c 
+static zend_vm_stack coroutine_php_vm_stack_new_page(size_t size, zend_vm_stack prev) {
+    zend_vm_stack page = (zend_vm_stack)emalloc(size);
+
+    page->top = ZEND_VM_STACK_ELEMENTS(page);
+    page->end = (zval *)((char *)page + size);
+    page->prev = prev;
+    return page;
+}
+
+// 参考 zend_execute.c
+static void coroutine_php_vm_stack_init(void) {
+    EG(current_execute_data) = nullptr;
+    EG(error_handling) = EH_NORMAL;
+    EG(exception_class) = nullptr;
+    EG(exception) = nullptr;
+
+    EG(vm_stack) = coroutine_php_vm_stack_new_page(4 * sizeof(zval) * 1024, NULL);
+    EG(vm_stack)->top++;
+    EG(vm_stack_top) = EG(vm_stack)->top;
+    EG(vm_stack_end) = EG(vm_stack)->end;
+}
+
+namespace flame {
+
+    unsigned int               coroutine::count = 0;
+    std::shared_ptr<coroutine> coroutine::current;
+    coroutine::php_context_t   coroutine::gctx_;
+
+    void coroutine::start(php::callable fn) {
+        ++coroutine::count;
+        coroutine_php_save_context(coroutine::gctx_);
+        auto co = std::make_shared<flame::coroutine>();
+        
+        boost::asio::post(co->ex_, [co, fn] () mutable {
+            co->c1_ = boost::context::fiber([co, gd = boost::asio::make_work_guard(co->ex_), fn] (boost::context::fiber&& c2) mutable {
+                co->c2_ = std::move(c2);
+                
+                coroutine_php_vm_stack_init();
+                coroutine::current = co;
+                
+                fn.call(); // 产生 PHP 进栈 操作，才能进行捕获异常
+                fn = nullptr;
+
+                coroutine::current.reset();
+                zend_vm_stack_destroy();
+                coroutine_php_restore_context(coroutine::gctx_);
+                
+                --coroutine::count;
+                
+                return std::move(co->c2_);
+            });
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+
+    void coroutine::suspend() {
+        coroutine_php_save_context(cctx_);
+        coroutine_php_restore_context(coroutine::gctx_);
+        // coroutine::current.reset();
+        c2_ = std::move(c2_).resume();
+    }
+
+    void coroutine::resume() {
+        auto co = std::static_pointer_cast<coroutine>(shared_from_this());
+        boost::asio::post(ex_, [co] () { 
+            coroutine_php_save_context(coroutine::gctx_);
+            coroutine_php_restore_context(co->cctx_);
+            coroutine::current = co;
+            co->c1_ = std::move(co->c1_).resume();
+        });
+    }
+
+}
diff --git a/src/flame/coroutine.h b/src/flame/coroutine.h
new file mode 100644
index 0000000..5066eae
--- /dev/null
+++ b/src/flame/coroutine.h
@@ -0,0 +1,106 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine.h"
+#include "controller.h"
+
+namespace flame {
+
+    class coroutine : public ::coroutine {
+    public:
+        struct php_context_t {
+            zend_vm_stack vm_stack;
+            zval *vm_stack_top;
+            zval *vm_stack_end;
+            zend_class_entry *scope;
+            zend_execute_data *current_execute_data;
+
+            zend_object *         exception;
+            zend_error_handling_t error_handling;
+            zend_class_entry *    exception_class;
+        };
+        static void start(php::callable fn);
+        static unsigned int count;
+        static std::shared_ptr<flame::coroutine> current;
+
+        coroutine()
+        : ::coroutine(gcontroller->context_x.get_executor()) {
+
+        }
+        void suspend();
+        void resume();
+    protected:
+        static php_context_t gctx_;
+               php_context_t cctx_;
+
+        friend class coroutine_handler;
+    };
+
+    class coroutine_handler: public ::coroutine_handler {
+    public:
+        coroutine_handler()
+        : ::coroutine_handler() {
+
+        }
+        coroutine_handler(const coroutine_handler& ch) = default;
+        coroutine_handler(std::shared_ptr<coroutine> co)
+        : ::coroutine_handler(co) {
+            
+        }
+        ~coroutine_handler() {
+
+        }
+
+        void reset() {
+            co_.reset();
+        }
+        void reset(std::shared_ptr<coroutine> co) {
+            co_ = co;
+        }
+        void reset(const coroutine_handler& ch) {
+            co_ = ch.co_;
+        }
+
+        coroutine_handler& operator[](boost::system::error_code& error) {
+            error_ = &error;
+            return *this;
+        }
+        coroutine_handler& operator[](std::size_t& size) {
+            size_ = &size;
+            return *this;
+        }
+    };
+
+}
+
+namespace boost::asio {
+    template <>
+    class async_result<flame::coroutine_handler, void (boost::system::error_code error, std::size_t size)> {
+    public:
+        explicit async_result(flame::coroutine_handler& ch) : ch_(ch), size_(0) {
+            ch_.operator[](size_);
+        }
+        using completion_handler_type = flame::coroutine_handler;
+        using return_type = std::size_t;
+        return_type get() {
+            ch_.suspend();
+            return size_;
+        }
+    private:
+        flame::coroutine_handler &ch_;
+        std::size_t size_;
+    };
+
+    template <>
+    class async_result<flame::coroutine_handler, void (boost::system::error_code error)> {
+    public:
+        explicit async_result(flame::coroutine_handler& ch) : ch_(ch) {
+        }
+        using completion_handler_type = flame::coroutine_handler;
+        using return_type = void;
+        void get() {
+            ch_.suspend();
+        }
+    private:
+        flame::coroutine_handler &ch_;
+    };
+} // namespace boost::asio
diff --git a/src/flame/encoding/encoding.cpp b/src/flame/encoding/encoding.cpp
new file mode 100644
index 0000000..c35b5be
--- /dev/null
+++ b/src/flame/encoding/encoding.cpp
@@ -0,0 +1,32 @@
+#include "encoding.h"
+#include "../mongodb/mongodb.h"
+
+namespace flame::encoding {
+    static php::value bson_encode(php::parameters &params) {
+        if (params[0].type_of(php::TYPE::ARRAY)) {
+            auto doc = flame::mongodb::array2bson(params[0]);
+            return php::string((const char*)bson_get_data(doc.get()), doc->len);
+        }
+        else throw php::exception(zend_ce_type_error
+            , "Failed to encode: typeof 'array' required"
+            , -1);
+    }
+    static php::value bson_decode(php::parameters &params) {
+        php::string data = params[0].to_string();
+        bson_t* doc = bson_new_from_data((const uint8_t*)data.data(), data.size());
+        if (doc == nullptr) return nullptr;
+        php::array rv = flame::mongodb::bson2array(doc);
+        bson_destroy(doc);
+        return std::move(rv);
+    }
+    
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<bson_encode>("flame\\encoding\\bson_encode", {
+                {"data", php::TYPE::ARRAY},
+            })
+            .function<bson_decode>("flame\\encoding\\bson_decode", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/flame/encoding/encoding.h b/src/flame/encoding/encoding.h
new file mode 100644
index 0000000..dd66fc6
--- /dev/null
+++ b/src/flame/encoding/encoding.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::encoding {
+    void declare(php::extension_entry &ext);
+} // namespace flame::encoding
diff --git a/src/flame/flame.cpp b/src/flame/flame.cpp
new file mode 100644
index 0000000..0ef5ed8
--- /dev/null
+++ b/src/flame/flame.cpp
@@ -0,0 +1,30 @@
+#include "../vendor.h"
+#include "../util.h"
+#include "controller.h"
+#include "version.h"
+#include "worker.h"
+#include "master.h"
+
+extern "C" {
+    ZEND_DLEXPORT zend_module_entry *get_module()  {
+        static php::extension_entry ext(EXTENSION_NAME, EXTENSION_VERSION);
+        std::string sapi = php::constant("PHP_SAPI");
+        if (sapi != "cli") {
+            std::cerr << "[" << util::system_time() << "] (WARNING) FLAME disabled: SAPI='cli' mode only\n";
+            return ext;
+        }
+        // 内置一个 C++ 函数包裹类
+        php::class_entry<php::closure> class_closure("flame\\closure");
+        class_closure.method<&php::closure::__invoke>("__invoke");
+        ext.add(std::move(class_closure));
+        // 全局控制器
+        flame::gcontroller.reset(new flame::controller());
+        // 扩展版本
+        flame::version::declare(ext);
+        // 主进程与工作进程注册不同的函数实现
+        if (flame::gcontroller->type == flame::controller::process_type::WORKER) flame::worker::declare(ext);
+        else flame::master::declare(ext);
+
+        return ext;
+    }
+};
diff --git a/src/flame/hash/hash.cpp b/src/flame/hash/hash.cpp
new file mode 100644
index 0000000..b90ca2f
--- /dev/null
+++ b/src/flame/hash/hash.cpp
@@ -0,0 +1,57 @@
+#include "hash.h"
+#include <boost/crc.hpp>
+extern "C" {
+    #include <librdkafka/rdmurmur2.h>
+    #include <librdkafka/xxhash.h>
+}
+
+namespace flame::hash {
+    
+    template <typename INTEGER_TYPE>
+    static php::string dec2hex(INTEGER_TYPE dec) {
+        php::string hex(sizeof(INTEGER_TYPE) * 2);
+        sprintf(hex.data(), "%0*lx", sizeof(INTEGER_TYPE) * 2, dec);
+        return std::move(hex);
+    }
+
+    static php::value murmur2(php::parameters& params) {
+        php::string data = params[0].to_string();
+        std::uint32_t raw = rd_murmur2(data.c_str(), data.size());
+        if (params.size() > 1 && params[1].to_boolean()) return raw;
+        return dec2hex<std::uint32_t>(raw);
+    }
+
+    static php::value xxh64(php::parameters& params) {
+        php::string data = params[0].to_string();
+        unsigned long long seed = 0;
+        if (params.size() > 1) {
+            seed = params[1].to_integer();
+        }
+        std::uint64_t raw = XXH64(data.c_str(), data.size(), seed);
+        if (params.size() > 2 && params[2].to_boolean()) return raw;
+        return dec2hex<std::uint64_t>(raw);
+    }
+
+    static php::value crc64(php::parameters& params) {
+        php::string data = params[0].to_string();
+        boost::crc_optimal<64, 0x42F0E1EBA9EA3693, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true>  crc64;
+        crc64.process_bytes(data.c_str(), data.size());
+        std::uint64_t raw = crc64.checksum();
+        if (params.size() > 1 && params[1].to_boolean()) return raw;
+        return dec2hex<std::uint64_t>(raw);
+    }
+
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<murmur2>("flame\\hash\\murmur2", {
+                {"data", php::TYPE::STRING},
+            })
+            .function<xxh64>("flame\\hash\\xxh64", {
+                {"data", php::TYPE::STRING},
+                {"seed", php::TYPE::INTEGER, false, true},
+            })
+            .function<crc64>("flame\\hash\\crc64", {
+                {"data", php::TYPE::STRING},
+            });
+    }
+}
diff --git a/src/flame/hash/hash.h b/src/flame/hash/hash.h
new file mode 100644
index 0000000..31fac80
--- /dev/null
+++ b/src/flame/hash/hash.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::hash {
+    void declare(php::extension_entry &ext);
+} // namespace flame::hash
diff --git a/src/flame/http/_handler.cpp b/src/flame/http/_handler.cpp
new file mode 100644
index 0000000..319842e
--- /dev/null
+++ b/src/flame/http/_handler.cpp
@@ -0,0 +1,221 @@
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "../log/logger.h"
+#include "_handler.h"
+#include "http.h"
+#include "server.h"
+#include "server_request.h"
+#include "server_response.h"
+
+namespace flame::http {
+
+    _handler::_handler(server *svr, tcp::socket &&sock)
+    : svr_ptr(svr)
+    , svr_obj(svr)
+    , socket_(std::move(sock)) {
+    }
+
+    _handler::~_handler() {
+    }
+
+    php::value _handler::run(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        while(true) {
+            if (!prepare(ch)) return nullptr;
+            if (!execute(ch)) return nullptr;
+
+            // 由于错误清理了对象 或 不允许连接复用
+            if (!res_ || !res_->keep_alive()) return nullptr;
+        }
+    }
+
+    bool _handler::prepare(coroutine_handler& ch) {
+        boost::system::error_code error;
+        req_.reset(new boost::beast::http::request_parser<value_body<true>>());
+        req_->header_limit(16 * 1024);
+        req_->body_limit(body_max_size);
+        boost::beast::http::async_read(socket_, buffer_, *req_, ch[error]);
+        if(error) return false;
+        res_.reset(new boost::beast::http::message<false, value_body<false>>());
+        // 服务及将停止或服务器以进行关闭后，禁止连接复用
+        if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
+            res_->keep_alive(false);
+            res_->set(boost::beast::http::field::connection, "close");
+        }else{
+            res_->keep_alive(req_->get().keep_alive()); // 默认为请求 KeepAlive 配置
+        }
+        return true;
+    }
+
+    bool _handler::execute(coroutine_handler& ch) {
+        php::object req = php::object(php::class_entry<server_request>::entry());
+        php::object res = php::object(php::class_entry<server_response>::entry());
+        server_request *req_ptr = static_cast<server_request *>(php::native(req));
+        server_response *res_ptr = static_cast<server_response *>(php::native(res));
+
+        req_ptr->build_ex(req_->get()); // 将 C++ 请求对象展开到 PHP 中
+        res_ptr->handler_ = shared_from_this();
+
+        std::string route = req.get("method");
+        route.push_back(':');
+        route.append(req.get("path"));
+
+        auto ib = svr_ptr->cb_.find("before"),
+            ih = svr_ptr->cb_.find(route),
+            ia = svr_ptr->cb_.find("after");
+
+        try {
+            php::value rv = true;
+            if (ib != svr_ptr->cb_.end()) rv = ib->second.call({req, res, ih != svr_ptr->cb_.end()});
+            if (!res_) return false; // 处理过程网络异常，下同
+            if (rv.type_of(php::TYPE::NO)) goto HANDLE_SKIPPED;
+            if (ih != svr_ptr->cb_.end()) rv = ih->second.call({req, res});
+            if (!res_) return false;
+            if (rv.type_of(php::TYPE::NO)) goto HANDLE_SKIPPED;
+            if (ia != svr_ptr->cb_.end()) rv = ia->second.call({req, res, ih != svr_ptr->cb_.end()});
+            if (!res_) return false;
+HANDLE_SKIPPED:
+            if (res_->chunked()) {
+                // Chunked 方式响应，但未结束：
+                if(!(res_ptr->status_ & res_ptr->STATUS_BODY_END)) {
+                    write_end(res_ptr, ch); // 结束
+                }
+            }
+            else { // 使用 body 属性进行响应
+                write_body(res_ptr, ch);
+            }
+            return true;
+            /*else {
+                // Chunked 未结束 -> server_response::__destruct() -> finish()
+            }*/
+        } catch(const php::exception& ex) {
+            gcontroller->event("exception", {ex}); // 调用用户异常回调
+            // 记录错误信息
+            php::object obj = ex;
+            log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in HTTP handler: " << obj.call("__toString") << std::endl;
+            return false;
+        }
+    }
+
+    void _handler::write_body(server_response* res_ptr, coroutine_handler& ch) {
+        res_ptr->build_ex(*res_);
+        // Content 方式, 待结束
+        php::string body = res_ptr->get("body");
+        if (!body.empty())
+            body = ctype_encode(res_->find(boost::beast::http::field::content_type)->value(), body);
+        res_->body() = body;
+        res_->prepare_payload();
+
+        boost::system::error_code error;
+        boost::beast::http::async_write(socket_, *res_, ch[error]);
+        if(error) {
+            req_.reset();
+            res_.reset();
+        }
+    }
+
+    void _handler::write_head(server_response *res_ptr, coroutine_handler &ch) {
+        if (!res_) return;
+        res_ptr->build_ex(*res_);
+        res_->chunked(true);
+        res_->set(boost::beast::http::field::transfer_encoding, "chunked");
+        // 服务及将停止或服务器以进行关闭后，禁止连接复用
+        if (gcontroller->status & controller::STATUS_CLOSECONN || svr_ptr->closed_) {
+            res_->keep_alive(false);
+            res_->set(boost::beast::http::field::connection, "close");
+        }
+        boost::system::error_code error;
+
+        boost::beast::http::serializer<false, value_body<false>> sr(*res_);
+        boost::beast::http::async_write_header(socket_, sr, ch[error]);
+        if (error) {
+            res_.reset();
+            req_.reset();
+        }
+    }
+
+    void _handler::write_chunk(server_response *res_ptr, php::string data, coroutine_handler &ch) {
+        if (!res_) return;
+        if (!res_->chunked())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write_chunk: 'chunked' encoding required"
+                , -1);
+
+        boost::system::error_code error;
+        boost::asio::async_write(socket_,
+            boost::beast::http::make_chunk(boost::asio::const_buffer(data.c_str(), data.size())), ch[error]);
+        if (error) {
+            res_.reset();
+            req_.reset();
+        }
+    }
+
+    void _handler::write_end(server_response* res_ptr, coroutine_handler &ch) {
+        if (!res_) return;
+        if (!res_->chunked()) throw php::exception(zend_ce_error_exception
+            , "Failed to write body: 'chunked' encoding required"
+            , -1);
+        boost::system::error_code error;
+        boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+        if (error) {
+            res_.reset();
+            req_.reset();
+        }
+    }
+
+    void _handler::write_file(server_response *res_ptr, std::string path, coroutine_handler &ch) {
+        if (!res_) return;
+        if (!res_->chunked())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write_file: 'chunked' encoding required"
+                , -1);
+        std::ifstream file(path);
+        char buffer[2048];
+        std::size_t size;
+        boost::system::error_code error;
+
+        while (!file.eof()) {
+            boost::asio::post(gcontroller->context_y, [&file, &ch, &buffer, &size, &error] () {
+                try {
+                    file.read(buffer, sizeof(buffer));
+                    size = file.gcount();
+                } catch(const std::ios_base::failure& ex) {
+                    error.assign(ex.code().value(), boost::system::generic_category());
+                }
+                ch.resume();
+            });
+            ch.suspend();
+            if (error) {
+                res_.reset();
+                req_.reset();
+                throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to write_file: %s") % error.message()).str()
+                    , error.value());
+            }
+            boost::asio::async_write(socket_, boost::beast::http::make_chunk( boost::asio::buffer(buffer, size) ), ch[error]);
+            if (error) goto WRITE_ERROR;
+        }
+        boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+WRITE_ERROR:
+        if (error) {
+            res_.reset();
+            req_.reset();
+        }
+        // file.close();
+    }
+
+    void _handler::finish(server_response *res_ptr, coroutine_handler &ch) {
+        if (res_) {
+            if (res_->chunked() && !(res_ptr->status_ & server_response::STATUS_BODY_END)) {
+                boost::system::error_code error;
+                boost::asio::async_write(socket_, boost::beast::http::make_chunk_last(), ch[error]);
+                
+                if (error) {
+                    res_.reset();
+                    req_.reset();
+                }
+            }
+        }
+    }
+}
diff --git a/src/flame/http/_handler.h b/src/flame/http/_handler.h
new file mode 100644
index 0000000..c14275d
--- /dev/null
+++ b/src/flame/http/_handler.h
@@ -0,0 +1,53 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "http.h"
+
+namespace flame::http {
+    class server_response;
+    class server_request;
+
+    template <bool isRequest>
+    class value_body;
+    class server;
+    class _handler : public std::enable_shared_from_this<_handler> {
+    public:
+        _handler(server *svr, boost::asio::ip::tcp::socket &&sock);
+        ~_handler();
+        php::value run(php::parameters& params);
+        
+        bool prepare(coroutine_handler& ch);
+        bool execute(coroutine_handler& ch);
+
+        void write_body(server_response* res_ptr, coroutine_handler& ch);
+        void write_head(server_response* res_ptr, coroutine_handler& ch);
+        void write_chunk(server_response* res_ptr, php::string data, coroutine_handler& ch);
+        void write_end(server_response* res_ptr, coroutine_handler& ch);
+        void write_file(server_response* res_ptr, std::string file, coroutine_handler& ch);
+
+        // 由 server_response 销毁时调用
+        void finish(server_response *res, coroutine_handler &ch);
+
+    private:
+        server *svr_ptr;
+        php::object svr_obj;
+        boost::asio::ip::tcp::socket socket_;
+
+        
+        // std::shared_ptr<boost::beast::http::message<true, value_body<true>>> req_;
+        std::shared_ptr<boost::beast::http::request_parser<value_body<true>>> req_;
+        std::shared_ptr<boost::beast::http::message<false, value_body<false>>> res_;
+        boost::beast::flat_buffer buffer_;
+    };
+    // enum
+    // {
+    //     RESPONSE_STATUS_HEADER_BUILT = 0x01,
+    //     RESPONSE_STATUS_HEADER_SENT = 0x02,
+    //     RESPONSE_STATUS_FINISHED = 0x04,
+    //     RESPONSE_STATUS_DETACHED = 0x08,
+
+    //     RESPONSE_TARGET_WRITE_HEADER = 0x01,
+    //     RESPONSE_TARGET_WRITE_CHUNK = 0x02,
+    //     RESPONSE_TARGET_WRITE_CHUNK_LAST = 0x03,
+    // };
+} // namespace flame::http
diff --git a/src/flame/http/client.cpp b/src/flame/http/client.cpp
new file mode 100644
index 0000000..d79567e
--- /dev/null
+++ b/src/flame/http/client.cpp
@@ -0,0 +1,215 @@
+#include "../coroutine.h"
+#include "client.h"
+#include "client_poll.h"
+#include "client_request.h"
+#include "client_response.h"
+#include "../time/time.h"
+
+namespace flame::http {
+    
+    void client::declare(php::extension_entry& ext) {
+        php::class_entry<client> class_client("flame\\http\\client");
+        class_client
+            .method<&client::__construct>("__construct", {
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .method<&client::exec>("exec", {
+                {"options", "flame\\http\\client_request"},
+            })
+            .method<&client::get>("get", {
+                {"url", php::TYPE::STRING},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::post>("post", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::UNDEFINED},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::put>("put", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::UNDEFINED},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            })
+            .method<&client::delete_>("delete", {
+                {"url", php::TYPE::STRING},
+                {"timeout", php::TYPE::INTEGER, false, true}
+            });
+            ext.add(std::move(class_client));
+    }
+
+    void client::check_done() {
+        CURLMsg *msg;
+        int left;
+        client_response* res_;
+        while((msg = curl_multi_info_read(c_multi_, &left))) {
+            if (msg->msg == CURLMSG_DONE) {
+                curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &res_);
+                res_->c_final_ = msg->data.result;
+                res_->c_coro_.resume();
+            }
+        }
+    }
+
+    static const char* action_str[] = { "UNKNOWN", "CURL_POLL_IN", "CURL_POLL_OUT" , "CURL_POLL_INOUT", "CURL_POLL_REMOVE" };
+
+    void client::c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action) {
+        client* self = reinterpret_cast<client*>(poll->data);
+        if (error) action = CURL_CSELECT_ERR;
+        curl_multi_socket_action(self->c_multi_, fd, action, &self->c_still_);
+        self->check_done();
+        if (self->c_still_ <= 0) self->c_timer_.cancel();
+    }
+    
+    int client::c_socket_cb(CURL* e, curl_socket_t fd, int action, void* data, void* sock_data) {
+        client* self = reinterpret_cast<client*>(data);
+        client_poll* poll = reinterpret_cast<client_poll*>(sock_data);
+
+        if (action == CURL_POLL_REMOVE) {
+            curl_multi_assign(self->c_multi_, fd, nullptr);
+        }
+        else if (poll == nullptr) {
+            poll = client_poll::create_poll(gcontroller->context_x, fd, c_socket_ready_cb, self);
+            curl_multi_assign(self->c_multi_, fd, poll);
+        }
+        poll->async_wait(action); // 自行 delete
+        return 0;
+    }
+
+    int client::c_timer_cb(CURLM *m, long timeout_ms, void* data) {
+        client* self = reinterpret_cast<client*>(data);
+
+        self->c_timer_.cancel();
+        if (timeout_ms == 0) timeout_ms = 1;
+        if (timeout_ms > 0) {
+            self->c_timer_.expires_after(std::chrono::milliseconds(timeout_ms));
+            self->c_timer_.async_wait([self] (const boost::system::error_code& err) {
+                if (err) return;
+                curl_multi_socket_action(self->c_multi_, CURL_SOCKET_TIMEOUT, 0, &self->c_still_);
+                self->check_done();
+            });
+        }
+        return 0;
+    }
+
+    client::client()
+    : c_timer_(gcontroller->context_x) {
+        c_multi_ = curl_multi_init();
+
+        curl_multi_setopt(c_multi_, CURLMOPT_SOCKETDATA, this);
+        curl_multi_setopt(c_multi_, CURLMOPT_SOCKETFUNCTION, client::c_socket_cb);
+        curl_multi_setopt(c_multi_, CURLMOPT_TIMERDATA, this);
+        curl_multi_setopt(c_multi_, CURLMOPT_TIMERFUNCTION, client::c_timer_cb);
+        // 7.62.x + HTTP1 PIPELINE 功能已无效
+        curl_multi_setopt(c_multi_, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX/* | CURLPIPE_HTTP1*/);
+        // curl_multi_setopt(c_multi_, CURLMOPT_MAX_PIPELINE_LENGTH, 4L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, 64L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, 32L);
+        curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, 16L);
+    }
+
+    client::~client() {
+        curl_multi_cleanup(c_multi_);
+    }
+
+    php::value client::__construct(php::parameters& params) {
+        if (params.size() > 0) {
+            php::array opts = params[0];
+            if (opts.exists("connection_per_host")) {
+                long c = opts.get("connection_per_host");
+                if (c < 1 || c > 512) {
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS, c * 4);
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAXCONNECTS, c * 2);
+                    curl_multi_setopt(c_multi_, CURLMOPT_MAX_HOST_CONNECTIONS, c);
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    php::value client::exec_ex(const php::object& req) {
+        auto req_ = static_cast<client_request*>(php::native(req));
+        if (req_->c_easy_ == nullptr) req_->c_easy_ = curl_easy_init();
+        curl_easy_setopt(req_->c_easy_, CURLOPT_PIPEWAIT, 1);
+        req_->build_ex();
+
+        php::object res(php::class_entry<client_response>::entry());
+        auto res_ = static_cast<client_response*>(php::native(res));
+        res_->c_easy_ = req_->c_easy_;
+        res_->c_coro_.reset(coroutine::current);
+        res_->c_head_ = php::array(4);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_WRITEFUNCTION, client_response::c_write_cb);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_WRITEDATA, res_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_HEADERFUNCTION, client_response::c_header_cb);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_HEADERDATA, res_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_ERRORBUFFER, res_->c_error_);
+        curl_easy_setopt(res_->c_easy_, CURLOPT_PRIVATE, res_);
+
+        curl_multi_add_handle(c_multi_, res_->c_easy_);
+        res_->c_coro_.suspend(); // <----- check_done
+        res_->build_ex();
+        curl_multi_remove_handle(c_multi_, res_->c_easy_);
+        return std::move(res);
+    }
+    php::value client::exec(php::parameters& params) {
+        return exec_ex(params[0]);
+    }
+    php::value client::get(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method", "GET");
+        req.set("url", params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body", nullptr);
+        if (params.length() > 1) {
+            req.set("timeout", params[1]);
+        }
+        else {
+            req.set("timeout", 3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::post(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",        "POST");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",       params[1]);
+        if (params.length() > 2) {
+            req.set("timeout",params[2]);
+        }else{
+            req.set("timeout",     3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::put(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",        "PUT");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",       params[1]);
+        if (params.length() > 2) {
+            req.set("timeout",params[2]);
+        }
+        else {
+            req.set("timeout",     3000);
+        }
+        return exec_ex(req);
+    }
+    php::value client::delete_(php::parameters& params) {
+        php::object req(php::class_entry<client_request>::entry());
+        req.set("method",         "DELETE");
+        req.set("url",        params[0]);
+        req.set("header", php::array(0));
+        req.set("cookie", php::array(0));
+        req.set("body",         nullptr);
+        if (params.length() > 1) {
+            req.set("timeout", params[1]);
+        }
+        else {
+            req.set("timeout", 3000);
+        }
+        return exec_ex(req);
+    }
+}
diff --git a/src/flame/http/client.h b/src/flame/http/client.h
new file mode 100644
index 0000000..00df8ca
--- /dev/null
+++ b/src/flame/http/client.h
@@ -0,0 +1,33 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class client_poll;
+    class client: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        client();
+        ~client();
+        php::value __construct(php::parameters& params);
+        php::value exec(php::parameters& params);
+        php::value get(php::parameters& params);
+        php::value post(php::parameters& params);
+        php::value put(php::parameters& params);
+        php::value delete_(php::parameters& params);
+    private:
+        CURLM *c_multi_ = nullptr;
+        int    c_still_ = 0;
+        boost::asio::steady_timer c_timer_;
+        ;
+
+        php::value exec_ex(const php::object& req);
+        static int c_socket_cb(CURL* e, curl_socket_t fd, int action, void* cbp, void* data);
+        static int c_timer_cb(CURLM *m, long timeout_ms, void* data);
+        static curl_socket_t c_socket_open_cb(void* data, curlsocktype purpose, struct curl_sockaddr* address);
+        static int c_socket_close_cb(void* data, curl_socket_t fd);
+        static void c_socket_ready_cb(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
+        void check_done();
+    };
+}
diff --git a/src/flame/http/client_body.cpp b/src/flame/http/client_body.cpp
new file mode 100644
index 0000000..9b1cb28
--- /dev/null
+++ b/src/flame/http/client_body.cpp
@@ -0,0 +1,8 @@
+#include "client_body.h"
+
+namespace flame::http {
+    void client_body::declare(php::extension_entry& ext) {
+        php::class_entry<client_body> class_client_body("flame\\http\\client_body");
+        ext.add(std::move(class_client_body));
+    }
+}
\ No newline at end of file
diff --git a/src/flame/http/client_body.h b/src/flame/http/client_body.h
new file mode 100644
index 0000000..2cb3187
--- /dev/null
+++ b/src/flame/http/client_body.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class client_body: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+    };
+}
\ No newline at end of file
diff --git a/src/flame/http/client_poll.cpp b/src/flame/http/client_poll.cpp
new file mode 100644
index 0000000..4a43ed2
--- /dev/null
+++ b/src/flame/http/client_poll.cpp
@@ -0,0 +1,106 @@
+#include "client_poll.h"
+#include "http.h"
+#include "client.h"
+
+namespace flame::http {
+
+    std::map<curl_socket_t, client_poll*> client_poll::pool_;
+
+    client_poll* client_poll::create_poll(boost::asio::io_context &io, curl_socket_t fd, client_poll::poll_callback_t cb, void* data) {
+        int       type, r;
+        socklen_t size = sizeof(type);
+        struct sockaddr addr;
+        r = ::getsockopt(fd, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &size);
+        assert(r == 0);
+        size = sizeof(addr);
+        r = ::getsockname(fd, &addr, &size);
+        assert(r == 0);
+        client_poll* poll;
+        if (type == SOCK_STREAM) poll = new client_poll_tcp(io, addr.sa_family == AF_INET6
+            ? boost::asio::ip::tcp::v6() : boost::asio::ip::tcp::v4(), ::dup(fd));
+        else if (type == SOCK_DGRAM) poll = new client_poll_udp(io, addr.sa_family == AF_INET6
+            ? boost::asio::ip::udp::v6() : boost::asio::ip::udp::v4(), ::dup(fd));
+        else return nullptr;
+
+        poll->data = data;
+        poll->fd_  = fd;
+        poll->cb_  = cb;
+        return poll;
+    }
+
+    client_poll::client_poll()
+    : action_(CURL_POLL_NONE)
+    , ref_(0) {
+        
+    }
+
+    client_poll::~client_poll() {
+        
+    }
+
+    void client_poll::on_ready(const boost::system::error_code& error, int action) {
+        --ref_;
+        if(ref_ == 0 && action_ == CURL_POLL_REMOVE) {
+            delete this;
+            return;   
+        }
+        if (error == boost::asio::error::operation_aborted) return; // 取消监听（销毁）或设置了新的监听
+        
+        cb_(error, this, fd_, action);
+        if (!error && action_ == action) async_wait();
+    }
+
+    void client_poll::async_wait(int action) {
+        if (action_ != action) {
+            action_ = action;
+            async_wait();
+        }
+    }
+
+    client_poll_tcp::client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd)
+    : sock_(io, proto, fd) {
+        
+    }
+
+    client_poll_tcp::~client_poll_tcp() {
+
+    }
+
+    void client_poll_tcp::async_wait() {
+        sock_.cancel();
+        // 重新监听
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) {
+            ++ref_;
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+        }
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) {
+            ++ref_;
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_tcp::on_ready, this, std::placeholders::_1, action_));
+        }
+        if (action_ == CURL_POLL_REMOVE && ref_ == 0) delete this;
+    }
+    
+    client_poll_udp::client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd)
+    : client_poll(), sock_(io, proto, fd) {
+
+    }
+
+    client_poll_udp::~client_poll_udp() {
+        
+    }
+
+    void client_poll_udp::async_wait() {
+        sock_.cancel();
+        // 重新监听
+        if (action_ == CURL_POLL_IN || action_ == CURL_POLL_INOUT) {
+            ++ref_;
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_read, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+        }
+        if (action_ == CURL_POLL_OUT || action_ == CURL_POLL_INOUT) {
+            ++ref_;
+            sock_.async_wait(boost::asio::ip::tcp::socket::wait_write, std::bind(&client_poll_udp::on_ready, this, std::placeholders::_1, action_));
+        }
+        if (action_ == CURL_POLL_REMOVE && ref_ == 0) delete this;
+    }
+
+}
\ No newline at end of file
diff --git a/src/flame/http/client_poll.h b/src/flame/http/client_poll.h
new file mode 100644
index 0000000..0564383
--- /dev/null
+++ b/src/flame/http/client_poll.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class client_poll {
+    public:
+        client_poll();
+        typedef void (*poll_callback_t)(const boost::system::error_code& error, client_poll* poll, curl_socket_t fd, int action);
+        virtual ~client_poll();
+        static client_poll* create_poll(boost::asio::io_context& io, curl_socket_t fd, poll_callback_t cb, void* data = nullptr);
+        static void destory_poll(client_poll* poll);
+        virtual void async_wait() = 0;
+        void async_wait(int action);
+        void* data;
+    protected:
+        static std::map<curl_socket_t, client_poll*> pool_;
+
+        curl_socket_t    fd_;
+        int          action_;
+        poll_callback_t  cb_;
+        int             ref_;
+        void on_ready(const boost::system::error_code& error, int action);
+    };
+
+    class client_poll_tcp: public client_poll {
+    public:
+        client_poll_tcp(boost::asio::io_context &io, boost::asio::ip::tcp proto, curl_socket_t fd);
+        ~client_poll_tcp();
+        void async_wait() override;
+    private:
+        boost::asio::ip::tcp::socket sock_;
+    };
+
+    class client_poll_udp: public client_poll {
+    public:
+        client_poll_udp(boost::asio::io_context &io, boost::asio::ip::udp proto, curl_socket_t fd);
+        ~client_poll_udp();
+        void async_wait() override;
+    private:
+        boost::asio::ip::udp::socket sock_;
+    };
+}
diff --git a/src/flame/http/client_request.cpp b/src/flame/http/client_request.cpp
new file mode 100644
index 0000000..1eda358
--- /dev/null
+++ b/src/flame/http/client_request.cpp
@@ -0,0 +1,182 @@
+#include "../coroutine.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "client_body.h"
+#include "http.h"
+
+namespace flame::http {
+
+    void client_request::declare(php::extension_entry& ext) {
+        php::class_entry<client_request> class_client_request(
+            "flame\\http\\client_request");
+        class_client_request
+            .constant({"HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0})
+            .constant({"HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1})
+            .constant({"HTTP_VERSION_2", CURL_HTTP_VERSION_2})
+            .constant({"HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0})
+            .constant({"HTTP_VERSION_2_TLS", CURL_HTTP_VERSION_2TLS})
+            .constant({"HTTP_VERSION_2_PRI", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE})
+            .constant({"SSL_VERIFY_NONE",   0})
+            .constant({"SSL_VERIFY_PEER",   1}) // CURLOPT_SSL_VERIFYPEER
+            .constant({"SSL_VERIFY_HOST",   2}) // CURLOPT_SSL_VERIFYHOST
+            .constant({"SSL_VERIFY_STATUS", 4}) // CURLOPT_SSL_VERIFYSTATUS
+            .constant({"SSL_VERIFY_ALL",    7})
+            .property({"timeout", 3000})
+            .property({"method", "GET"})
+            .property({"url", nullptr})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body", ""})
+            .method<&client_request::__construct>("__construct", {
+                {"url", php::TYPE::STRING},
+                {"body", php::TYPE::UNDEFINED, false, true},
+                {"timeout", php::TYPE::INTEGER, false, true},
+            })
+            .method<&client_request::http_version>("http_version", {
+                {"version", php::TYPE::INTEGER},
+            })
+            .method<&client_request::ssl_pem>("ssl_pem", {
+                {"cert", php::TYPE::STRING},
+                {"pkey", php::TYPE::STRING, false, true},
+            })
+            .method<&client_request::ssl_verify>("ssl_verify", {
+                {"verify", php::TYPE::INTEGER},
+            })
+            .method<&client_request::__destruct>("__destruct");
+
+        ext.add(std::move(class_client_request));
+    }
+
+    php::value client_request::__construct(php::parameters& params) {
+        if (params.length() > 2) set("timeout", params[2]);
+        else set("timeout", 3000);
+
+        if (params.length() > 1 && !params[1].empty()) {
+            set("body", params[1]);
+            set("method", "POST");
+        }
+        else set("method", "GET");
+        set("url", params[0]);
+        set("header", php::array(0));
+        set("cookie", php::array(0));
+
+        c_easy_ = curl_easy_init();
+        return nullptr;
+    }
+
+    php::value client_request::__destruct(php::parameters &params) {
+        if (c_head_) curl_slist_free_all(c_head_);
+        if (c_easy_) curl_easy_cleanup(c_easy_);
+        return nullptr;
+    }
+
+    php::value client_request::http_version(php::parameters& params) {
+        long v = static_cast<int>(params[0]);
+        if (v <= CURL_HTTP_VERSION_NONE || v >= CURL_HTTP_VERSION_LAST)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to set HTTP version: value out of range"
+                , -1);
+
+        curl_easy_setopt(c_easy_, CURLOPT_HTTP_VERSION, v);
+        return nullptr;
+    }
+
+    php::value client_request::ssl_pem(php::parameters& params) {
+        curl_easy_setopt(c_easy_, CURLOPT_SSLCERTTYPE, "PEM");
+        php::string cert = params[0];
+        curl_easy_setopt(c_easy_, CURLOPT_SSLCERT, cert.c_str());
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
+            php::string pkey = params[1];
+            curl_easy_setopt(c_easy_, CURLOPT_SSLKEY, pkey.c_str());
+        }
+        if (params.size() > 2 && params[2].type_of(php::TYPE::STRING)) {
+            php::string pass = params[2];
+            curl_easy_setopt(c_easy_, CURLOPT_KEYPASSWD, pass.c_str());
+        }
+        return nullptr;
+    }
+
+    php::value client_request::ssl_verify(php::parameters& params) {
+        long v = static_cast<int>(params[0]), YES = 1, NO = 0;
+        if (v | CURLOPT_SSL_VERIFYPEER)
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, YES);
+        else
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYPEER, NO);
+        if (v | CURLOPT_SSL_VERIFYHOST)
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, YES);
+        else
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYHOST, NO);
+        if (v | CURLOPT_SSL_VERIFYSTATUS)
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, YES);
+        else
+            curl_easy_setopt(c_easy_, CURLOPT_SSL_VERIFYSTATUS, NO);
+        return nullptr;
+    }
+
+    void client_request::build_ex() {
+        long timeout = static_cast<long>(get("timeout"));
+        curl_easy_setopt(c_easy_, CURLOPT_TIMEOUT_MS, timeout);
+        // 目标请求地址
+        // ---------------------------------------------------------------------------
+        php::string u = get("url");
+        if (!u.type_of(php::TYPE::STRING))
+            throw php::exception(zend_ce_type_error
+                , "Failed to build client request: 'url' typeof 'string' required"
+                , -1);
+
+        curl_easy_setopt(c_easy_, CURLOPT_URL, u.c_str());
+        php::string m = get("method");
+        curl_easy_setopt(c_easy_, CURLOPT_CUSTOMREQUEST, m.c_str());
+        // 头
+        // ---------------------------------------------------------------------------
+        if (c_head_ != nullptr) curl_slist_free_all(c_head_);
+        std::string ctype;
+        long keepalive = 1;
+        php::array header = get("header");
+        for (auto i=header.begin(); i!=header.end(); ++i) {
+            php::string key = i->first.to_string();
+            php::string val = i->second.to_string();
+            if (strncasecmp(key.c_str(), "content-type", 12) == 0) ctype.assign(val.data(), val.size());
+            else if (strncasecmp(key.c_str(), "connection", 10) == 0
+                && strncasecmp(val.c_str(), "close", 5) == 0) keepalive = 0;
+            c_head_ = curl_slist_append(c_head_, (boost::format("%s: %s") % key % val).str().c_str());
+        }
+        c_head_ = curl_slist_append(c_head_, "Expect: ");
+        curl_easy_setopt(c_easy_, CURLOPT_HTTPHEADER, c_head_);
+        curl_easy_setopt(c_easy_, CURLOPT_TCP_KEEPALIVE, keepalive);
+        // COOKIE
+        // ---------------------------------------------------------------------------
+        php::array cookie = get("cookie");
+        php::buffer cookies;
+        for (auto i=cookie.begin(); i!=cookie.end(); ++i) {
+            php::string key = i->first.to_string();
+            php::string val = i->second.to_string();
+            val = php::url_encode(val.c_str(), val.size());
+            cookies.append(key);
+            cookies.push_back('=');
+            cookies.append(val);
+            cookies.push_back(';');
+            cookies.push_back(' ');
+        }
+        php::string cookie_str = std::move(cookies);
+        if (cookie_str.size() > 0) curl_easy_setopt(c_easy_, CURLOPT_COOKIE, cookie_str.c_str());
+        // 体
+        // ---------------------------------------------------------------------------
+        php::string body = get("body");
+        if (ctype.empty()) ctype.assign("application/x-www-form-urlencoded", 33);
+        if (body.empty()) {
+            // TODO 空 BODY 的处理流程？
+        }
+        else if (body.instanceof(php::class_entry<client_body>::entry())) {
+            // TODO multipart support
+        }
+        else {
+            body = ctype_encode(ctype, body);
+            // 注意: CURLOPT_POSTFIELDS 仅"引用" body 数据
+            set("body", body);
+            // curl_easy_setopt(c_easy_, CURLOPT_POST, 1);
+            curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDSIZE, body.size());
+            curl_easy_setopt(c_easy_, CURLOPT_POSTFIELDS, body.c_str());
+        }
+    }
+}
diff --git a/src/flame/http/client_request.h b/src/flame/http/client_request.h
new file mode 100644
index 0000000..fd6f6dd
--- /dev/null
+++ b/src/flame/http/client_request.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../url.h"
+#include "../coroutine.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class client_request: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters& params);
+        php::value http_version(php::parameters& params);
+        php::value ssl_pem(php::parameters& params);
+        php::value ssl_verify(php::parameters& params);
+    private:
+        void build_ex();
+        CURL*       c_easy_ = nullptr;
+        curl_slist* c_head_ = nullptr;
+        
+        friend class client;
+        friend class _connection_pool;
+    };
+}
diff --git a/src/flame/http/client_response.cpp b/src/flame/http/client_response.cpp
new file mode 100644
index 0000000..9d24990
--- /dev/null
+++ b/src/flame/http/client_response.cpp
@@ -0,0 +1,75 @@
+#include "http.h"
+#include "value_body.h"
+#include "client_response.h"
+
+namespace flame::http {
+    void client_response::declare(php::extension_entry& ext) {
+        php::class_entry<client_response>  class_client_response("flame\\http\\client_response");
+        class_client_response
+            .property({"version", "HTTP/1.0"})
+            .property({"status", 3000})
+            .property({"header", nullptr})
+            .property({"body", nullptr})
+            .property({"raw_body", ""})
+            .method<&client_response::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client_response::to_string>("__toString");
+        ext.add(std::move(class_client_response));
+    }
+    // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+    php::value client_response::__construct(php::parameters& params) {
+        return nullptr; 
+    }
+    php::value client_response::to_string(php::parameters& params) {
+        return get("raw_body");
+    }
+    void client_response::build_ex() {
+        if (c_final_ != 0) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to execute HTTP request: %s") % c_error_).str()
+            , c_final_);
+        // 响应码
+        long rv = 0;
+        curl_easy_getinfo(c_easy_, CURLINFO_HTTP_VERSION, &rv);
+        set("version", rv == 3 ? "HTTP/2" : rv == 2 ? "HTTP/1.1" : "HTTP/1.0");
+        curl_easy_getinfo(c_easy_, CURLINFO_RESPONSE_CODE, &rv);
+        set("status", rv);
+        // 响应头
+        set("header", c_head_);
+        // 响应体
+        php::string rbody = std::move(c_body_);
+        set("raw_body", rbody);
+        if (c_head_.exists("content-type")) {
+            php::string ctype = c_head_.get("content-type");
+            std::string_view sv {ctype.c_str(), ctype.size()};
+            set("body", ctype_decode(sv, rbody));
+        }
+        else {
+            set("body", rbody);
+        }
+    }
+
+    size_t client_response::c_write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
+        client_response* self = static_cast<client_response*>(data);
+        assert(size == 1);
+        self->c_body_.append(ptr, nmemb);
+        return nmemb;
+    }
+
+    size_t client_response::c_header_cb(char *buffer, size_t size, size_t nitems, void *data) {
+        client_response* self = static_cast<client_response*>(data);
+        size = size * nitems;       
+
+        std::string_view sv{ buffer, size };
+        auto psep = sv.find_first_of(':', 1);
+        if (psep != sv.npos) {
+            auto pval = sv.find_first_not_of(' ', psep + 1);
+            php::string key = php::lowercase(buffer, psep);
+            if (pval == sv.npos) {
+                self->c_head_.set( key, php::string(0) );
+            }
+            else {
+                self->c_head_.set( key, php::string( buffer + pval, size - pval - 2)); // trailing \r\n
+            }
+        }
+        return size;
+    }
+}
diff --git a/src/flame/http/client_response.h b/src/flame/http/client_response.h
new file mode 100644
index 0000000..0757d66
--- /dev/null
+++ b/src/flame/http/client_response.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class client_response: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value to_string(php::parameters& params);
+        // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+        php::value __construct(php::parameters& params);
+    private:
+        CURL*       c_easy_;
+        php::array  c_head_;
+        php::buffer c_body_;
+        CURLcode c_final_ = CURLE_OK;
+        char c_error_[CURL_ERROR_SIZE];
+        coroutine_handler c_coro_;
+        void build_ex();
+        // 接收 curl 回调(响应数据)
+        static size_t c_write_cb(char *ptr, size_t size, size_t nmemb, void *data);
+        static size_t c_header_cb(char *buffer, size_t size, size_t nitems, void *data);
+        friend class client;
+    };
+}
diff --git a/src/flame/http/http.cpp b/src/flame/http/http.cpp
new file mode 100644
index 0000000..3630f10
--- /dev/null
+++ b/src/flame/http/http.cpp
@@ -0,0 +1,168 @@
+#include "../coroutine.h"
+#include "http.h"
+#include "client.h"
+#include "value_body.h"
+#include "client_request.h"
+#include "client_response.h"
+#include "client_body.h"
+#include "server.h"
+#include "server_request.h"
+#include "server_response.h"
+#include "_handler.h"
+
+namespace flame::http {
+    
+    client* client_;
+    std::int64_t body_max_size = 1024 * 1024 * 1024;
+
+    void declare(php::extension_entry& ext) {
+        client_ = nullptr;
+        ext.on_module_startup([] (php::extension_entry& ext) -> bool {
+            // 在框架初始化后创建全局HTTP客户端
+            gcontroller->on_init([] (const php::array& opts) {
+                body_max_size = std::max(php::ini("post_max_size").calc(), body_max_size);
+                client_ = new client();
+                php::parameters p(0, nullptr);
+                client_->__construct(p);
+            })->on_stop([] {
+                delete client_;
+                client_ = nullptr;
+            });
+            return true;
+        });
+        ext
+            .function<get>("flame\\http\\get")
+            .function<post>("flame\\http\\post")
+            .function<put>("flame\\http\\put")
+            .function<delete_>("flame\\http\\delete")
+            .function<exec>("flame\\http\\exec");
+
+        client_request::declare(ext);
+        client_response::declare(ext);
+        client_body::declare(ext);
+        client::declare(ext);
+        server::declare(ext);
+        server_request::declare(ext);
+        server_response::declare(ext);
+    }
+    static void init_guard() {
+        if (!client_)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to execute HTTP request: exception or missing 'flame\\init()' ?"
+                , -1);
+    }
+    php::value get(php::parameters& params) {
+        init_guard();
+        return client_->get(params);
+    }
+    php::value post(php::parameters& params) {
+        init_guard();
+        return client_->post(params);
+    }
+    php::value put(php::parameters& params) {
+        init_guard();
+        return client_->put(params);
+    }
+    php::value delete_(php::parameters& params) {
+        init_guard();
+        return client_->delete_(params);
+    }
+    php::value exec(php::parameters& params) {
+        init_guard();
+        return client_->exec(params);
+    }
+    php::string ctype_encode(std::string_view ctype, const php::value& v) {
+        if (v.type_of(php::TYPE::STRING)) return v;
+
+        php::value r = v;
+        if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+            if (r.type_of(php::TYPE::ARRAY)) {
+                r = php::callable("http_build_query")({v});
+            }
+            else {
+                r.to_string();
+            }
+        }
+        else if (ctype.compare(0, 16, "application/json") == 0) {
+            r = php::json_encode(r);
+        }
+        else {
+            r.to_string();
+        }
+        return r;
+    }
+    php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* files) {
+        if (ctype.compare(0, 16, "application/json") == 0) {
+            return php::json_decode(v);
+        }
+        else if (ctype.compare(0, 33, "application/x-www-form-urlencoded") == 0) {
+            php::array data(4);
+            php::callable("parse_str")({v, data.make_ref()});
+            return data;
+        }
+        else if (ctype.compare(0, 19, "multipart/form-data") == 0) {
+            std::string boundary;
+            std::string field;
+            php::array meta(4);
+            parser::separator_parser<std::string, std::string> p1('\0','\0','=','"','"',';', [&boundary, &field, &meta] (std::pair<std::string, std::string> entry) {
+                if (entry.first == "boundary") {
+                    boundary = std::move(entry.second);
+                }
+                else if (entry.first == "content-disposition") {
+                    // 此项头信息没有用途
+                }
+                else if (entry.first == "name") {
+                    // 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+                    // 表单字段名
+                    field = std::move(entry.second);
+                }
+                else {
+                    meta.set(entry.first, entry.second);
+                }
+            });
+            std::size_t begin = ctype.find_first_of(';', 19) + 1;
+            p1.parse(ctype.data() + begin, ctype.size() - begin);
+            p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
+
+            php::array data(8);
+            parser::multipart_parser<std::string, php::buffer> p2(boundary, [&p1, &field, &meta, &data, &files] (std::pair<std::string, php::buffer> entry) {
+                if (entry.first.size() == 0) { // Part Data
+                    if (meta.exists("filename") > 0) {
+                        if (files) { // 接收对应文件
+                            // 上传文件 meta 
+                            meta["size"] = entry.second.size();
+                            meta["data"] = std::move(entry.second);
+                            files->set(field, meta);
+                        }
+                    }
+                    else {
+                        data.set(field, std::move(entry.second));
+                    }
+                    meta = php::array(4);
+                }
+                else { // Part Header
+                    php::lowercase_inplace(entry.first.data(), entry.first.size());
+                    std::size_t end = std::string_view(entry.second.data(), entry.second.size()).find_first_of(';');
+                    if (end == std::string::npos) {
+                        meta[entry.first] = std::move(entry.second);
+                    }
+                    else if (entry.first == "content-disposition") {
+                        // 这里可能有不严格的地方存在，即每个 Header 项后可能都存在对应的补充字段，且这些字段名称可能重复
+                        std::size_t begin = end + 1;
+                        if (entry.second.size() > begin + 2) {
+                            p1.parse(entry.second.data() + begin, entry.second.size() - begin);
+                            p1.parse(";", 1); // 保证一行 K/V 结束，复用 PARSER
+                        }
+                    }
+                    else {
+                        meta[entry.first] = php::string(entry.second.data(), end);
+                    }
+                }
+            });
+            p2.parse(v.data(), v.size());
+
+            return data;
+        }
+        else return v;
+    }
+}
diff --git a/src/flame/http/http.h b/src/flame/http/http.h
new file mode 100644
index 0000000..0ff3a8e
--- /dev/null
+++ b/src/flame/http/http.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../../vendor.h"
+#define BOOST_BEAST_USE_STD_STRING_VIEW
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <curl/curl.h>
+
+namespace flame::http {
+    
+    class _connection_pool;
+    class client;
+    extern client* client_;
+    extern std::int64_t body_max_size;
+
+    void declare(php::extension_entry& ext);
+    php::value get(php::parameters& params);
+    php::value post(php::parameters& params);
+    php::value put(php::parameters& params);
+    php::value delete_(php::parameters& params);
+    php::value exec(php::parameters& params);
+
+    php::string ctype_encode(std::string_view ctype, const php::value& v);
+    php::value ctype_decode(std::string_view ctype, const php::string& v, php::array* file = nullptr);
+}
diff --git a/src/flame/http/server.cpp b/src/flame/http/server.cpp
new file mode 100644
index 0000000..ec2be5d
--- /dev/null
+++ b/src/flame/http/server.cpp
@@ -0,0 +1,156 @@
+#include "../coroutine.h"
+#include "../udp/udp.h"
+#include "server.h"
+#include "_handler.h"
+
+namespace flame::http {
+    
+    void server::declare(php::extension_entry &ext)  {
+        php::class_entry<server> class_server("flame\\http\\server");
+        class_server
+            .property({"address", "127.0.0.1:7678"})
+            .method<&server::__construct>("__construct", {
+                {"address", php::TYPE::STRING},
+            })
+            .method<&server::before>("before", {
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::after>("after", {
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::put>("put", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::delete_>("delete", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::post>("post", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::patch>("patch", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::get>("get", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::head>("head", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::options>("options", {
+                {"path", php::TYPE::STRING},
+                {"callback", php::TYPE::CALLABLE},
+            })
+            .method<&server::run>("run")
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
+    }
+
+    server::server()
+    : accp_(gcontroller->context_x)
+    , sock_(gcontroller->context_x)
+    , closed_(false) {
+
+    }
+
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+    php::value server::__construct(php::parameters &params) {
+        php::string str = params[0];
+        auto pair = udp::addr2pair(str);
+        if (pair.first.empty() || pair.second.empty())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to bind tcp socket: address malformed"
+                , -1);
+        boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
+        addr_.address(addr);
+        addr_.port(std::atoi(pair.second.c_str()));
+
+        accp_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        accp_.set_option(opt1);
+        reuse_port opt2(true);
+        accp_.set_option(opt2);
+
+        set("address", params[0]);
+        return nullptr;
+    }
+
+    php::value server::before(php::parameters &params) {
+        cb_["before"] = params[0];
+        return this;
+    }
+
+    php::value server::after(php::parameters &params) {
+        cb_["after"] = params[0];
+        return this;
+    }
+
+    php::value server::put(php::parameters &params) {
+        cb_[std::string("PUT:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::delete_(php::parameters &params) {
+        cb_[std::string("DELETE:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::post(php::parameters &params) {
+        cb_[std::string("POST:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::get(php::parameters &params) {
+        cb_[std::string("GET:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::head(php::parameters &params) {
+        cb_[std::string("HEAD:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::options(php::parameters &params) {
+        cb_[std::string("OPTIONS:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::patch(php::parameters &params) {
+        cb_[std::string("PATCH:") + params[0].to_string()] = params[1];
+        return this;
+    }
+
+    php::value server::run(php::parameters &params) {
+        boost::system::error_code err;
+        accp_.bind(addr_, err);
+        if (err) throw php::exception(zend_ce_error_exception
+            , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
+            , err.value());
+        accp_.listen(boost::asio::socket_base::max_listen_connections);
+
+        coroutine_handler ch{coroutine::current};
+        while(!closed_) {
+            boost::system::error_code err;
+            accp_.async_accept(sock_, ch[err]);
+            if (err == boost::asio::error::operation_aborted) break; 
+            else if (err) throw php::exception(zend_ce_error_exception, (boost::format("Failed to accept connection: %s") % err.message()).str(), err.value());
+            else coroutine::start(php::callable( // 按连接启动处理协程（目前仅支持 HTTP/1.1 按 HTTP/2 机制须按照 STREAM 启动协程）
+                std::bind(&_handler::run, std::make_shared<_handler>(this, std::move(sock_)), std::placeholders::_1)
+            ));
+        }
+        return nullptr;
+    }
+
+    php::value server::close(php::parameters &params) {
+        if (closed_) return nullptr;
+        closed_ = true;
+        accp_.cancel();
+        accp_.close();
+        return nullptr;
+    }
+} // namespace flame::http
diff --git a/src/flame/http/server.h b/src/flame/http/server.h
new file mode 100644
index 0000000..e0b10c4
--- /dev/null
+++ b/src/flame/http/server.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+    class acceptor;
+    class server : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+
+        server();
+
+        php::value __construct(php::parameters &params);
+        php::value before(php::parameters &params);
+        php::value after(php::parameters &params);
+        php::value put(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value post(php::parameters &params);
+        php::value patch(php::parameters &params);
+        php::value get(php::parameters &params);
+        php::value head(php::parameters &params);
+        php::value options(php::parameters &params);
+        php::value run(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::tcp::endpoint addr_;
+        boost::asio::ip::tcp::acceptor accp_;
+        boost::asio::ip::tcp::socket   sock_;
+        std::map<std::string, php::callable> cb_;
+        bool closed_;
+
+        friend class _handler;
+    };
+} // namespace flame::http
diff --git a/src/flame/http/server_request.cpp b/src/flame/http/server_request.cpp
new file mode 100644
index 0000000..0695c1b
--- /dev/null
+++ b/src/flame/http/server_request.cpp
@@ -0,0 +1,90 @@
+#include "../coroutine.h"
+#include "http.h"
+#include "value_body.h"
+#include "server_request.h"
+
+namespace flame::http {
+
+    void server_request::declare(php::extension_entry& ext) {
+        php::class_entry<server_request> class_server_request(
+            "flame\\http\\server_request");
+        class_server_request
+            .property({"method", "GET"})
+            .property({"path", "/"})
+            .property({"query", nullptr})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body", nullptr})
+            .property({"raw_body", ""})
+            .property({"file", nullptr})
+            .property({"data", nullptr})
+            .method<&server_request::__construct>("__construct", {}, php::PRIVATE);
+        ext.add(std::move(class_server_request));
+    }
+    
+    server_request::server_request() {
+    }
+
+    server_request::~server_request() {
+    }
+
+    php::value server_request::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    void server_request::build_ex(const boost::beast::http::message<true, value_body<true>>& ctr_) {
+        auto method = boost::beast::http::to_string(ctr_.method());
+        set("method", php::string(method.data(), method.size()));
+        // 目标请求地址
+        auto target = ctr_.target();
+        std::shared_ptr<php::url> url_ = php::parse_url(target.data(), target.size());
+        if (url_->path) set("path", php::string(url_->path));
+        else set("path", php::string("/", 1));
+        if (url_->query) {
+            php::array query(4);
+            php::callable("parse_str")({php::string(url_->query), query.make_ref()});
+            set("query", query);
+        }
+        php::array header(4);
+        for (auto i=ctr_.begin(); i!=ctr_.end(); ++i) {
+            php::string key {i->name_string().data(), i->name_string().size()};
+            php::string val {i->value().data(), i->value().size()};
+
+            if (key.size() == 6 && strncasecmp(key.c_str(), "cookie", 6) == 0) {
+                php::array cookie(4);
+                parser::separator_parser<std::string, php::buffer> p1('\0','\0','=','\0','\0',';', [&cookie] (std::pair<std::string, php::buffer> entry) {
+                    if (entry.second.size() > 0) {
+                        entry.second.shrink( php::url_decode_inplace(entry.second.data(), entry.second.size()) );
+                        cookie.set(entry.first, php::string(std::move(entry.second)));
+                    }
+                    else {
+                        cookie.set(entry.first, php::string(0));
+                    }
+                });
+                p1.parse(val.c_str(), val.size());
+                p1.parse(";", 1); // 结尾分隔符可能不存在(可以认为数据行可能不完整)
+                set("cookie", cookie);
+            }
+            // TODO 多个同名 HEADER 的处理
+            php::lowercase_inplace(key.data(), key.size());
+            header.set(key, val);
+        }
+        set("header", header);
+
+        php::string body = ctr_.body();
+        if (body.type_of(php::TYPE::STRING)) {
+            auto ctype = ctr_.find(boost::beast::http::field::content_type);
+            if (ctype == ctr_.end() || ctype->value().compare(0, 19, "multipart/form-data") != 0)
+                set("raw_body", body); // 不在 multipart 时保留原始数据
+
+            if (ctype != ctr_.end()) { // 存在时按照类型进行解析
+                php::array files(4);
+                set("body", ctype_decode(ctype->value(), body, &files));
+                set("file", files);
+            }
+            else { // 不存在与 raw_body 相同
+                set("body", body);
+            }
+        }
+    }
+}
diff --git a/src/flame/http/server_request.h b/src/flame/http/server_request.h
new file mode 100644
index 0000000..b2f4d68
--- /dev/null
+++ b/src/flame/http/server_request.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+    
+    class handler;
+    class server_request: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        server_request();
+        ~server_request();
+        php::value __construct(php::parameters& params);
+    private:
+        void build_ex(const boost::beast::http::message<true, value_body<true>>& ctr);
+
+        friend class _handler;
+    };
+}
diff --git a/src/flame/http/server_response.cpp b/src/flame/http/server_response.cpp
new file mode 100644
index 0000000..8f43910
--- /dev/null
+++ b/src/flame/http/server_response.cpp
@@ -0,0 +1,233 @@
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "_handler.h"
+#include "server_response.h"
+
+namespace flame::http {
+
+    void server_response::declare(php::extension_entry& ext) {
+        php::class_entry<server_response> class_server_response("flame\\http\\server_response");
+        class_server_response
+            .property({"status", 200})
+            .property({"header", nullptr})
+            .property({"cookie", nullptr})
+            .property({"body",   nullptr})
+            .method<&server_response::__construct>("__construct", {}, php::PRIVATE)
+            .method<&server_response::set_cookie>("set_cookie", {
+                {"name", php::TYPE::STRING},
+                {"value", php::TYPE::STRING, false, true},
+                {"expire", php::TYPE::INTEGER, false, true},
+                {"path", php::TYPE::STRING, false, true},
+                {"domain", php::TYPE::STRING, false, true},
+                {"secure", php::TYPE::BOOLEAN, false, true},
+                {"httponly", php::TYPE::BOOLEAN, false, true},
+            })
+            .method<&server_response::write_header>("write_header", {
+                {"status", php::TYPE::INTEGER, false, true}
+            })
+            .method<&server_response::write>("write", {
+                {"chunk", php::TYPE::UNDEFINED}
+            })
+            .method<&server_response::end>("end", {
+                {"chunk", php::TYPE::UNDEFINED, false, true}
+            })
+            .method<&server_response::file>("file", {
+                {"root", php::TYPE::STRING},
+                {"path", php::TYPE::STRING},
+            });
+
+        ext.add(std::move(class_server_response));
+    }
+
+    server_response::server_response()
+    : status_(0) {
+
+    }
+
+    server_response::~server_response() {
+
+    }
+    // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+    php::value server_response::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value server_response::set_cookie(php::parameters& params) {
+        php::array cookie(4);
+        php::string name = params[0], val;
+        name.to_string();
+        cookie.set("name", name);
+        if (params.size() > 1) {
+            val = params[1];
+            val.to_string();
+        }
+        else val = php::string("");
+        cookie.set("value", val);
+        if (params.size() > 2) {
+            // php::object expire = php::datetime(time::now() +  * 1000);
+            cookie.set("expire", params[2].to_integer());
+        }
+        if (params.size() > 3) {
+            php::string path = params[3];
+            path.to_string();
+            cookie.set("path", path);
+        }
+        if (params.size() > 4) {
+            php::string domain = params[4];
+            domain.to_string();
+            cookie.set("domain", domain);
+        }
+        if (params.size() > 5) {
+            bool secure = params[5];
+            cookie.set("secure", secure);
+        }
+        if (params.size() > 6) {
+            bool http_only = params[5];
+            cookie.set("http_only", http_only);
+        }
+
+        php::array cookies = get("cookie", true);
+        if (cookies.type_of(php::TYPE::NULLABLE)) cookies = php::array(4);
+        cookies.set(cookies.size(), cookie);
+        return nullptr;
+    }
+    // chunked encoding 用法
+    php::value server_response::write_header(php::parameters& params) {
+        if ((status_ & STATUS_HEAD_SENT) || (status_ & STATUS_BODY_SENT))
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write header: already sent"
+                , -1);
+
+        status_ |= STATUS_HEAD_SENT;
+        if (params.size() > 0) set("status", params[0].to_integer());
+        coroutine_handler ch{coroutine::current};
+        handler_->write_head(this, ch);
+        return nullptr;
+    }
+
+    php::value server_response::write(php::parameters& params) {
+        if (status_ & STATUS_BODY_END)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write body: response already done"
+                , -1);
+
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        status_ |= STATUS_BODY_SENT;
+        php::string chunk = params[0].to_string();
+        handler_->write_chunk(this, chunk, ch);
+        return nullptr;
+    }
+    
+    php::value server_response::end(php::parameters& params) {
+        if (status_ & STATUS_BODY_END)
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write body: response already done"
+                , -1);
+
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        if (params.size() > 0) {
+            status_ |= STATUS_BODY_SENT;
+            php::string chunk = params[0].to_string();
+            handler_->write_chunk(this, chunk, ch);
+        }
+        else {
+            status_ |= STATUS_BODY_END;
+            handler_->write_end(this, ch);
+            handler_.reset();
+        }
+        return nullptr;
+    }
+
+    php::value server_response::file(php::parameters& params) {
+        if ((status_ & STATUS_BODY_END) || (status_ & STATUS_BODY_SENT))
+            throw php::exception(zend_ce_error_exception
+                , "Failed to write file: response already done"
+                , -1);
+
+        coroutine_handler ch{coroutine::current};
+        if (!(status_ & STATUS_HEAD_SENT)) {
+            status_ |= STATUS_HEAD_SENT;
+            handler_->write_head(this, ch);
+        }
+        status_ |= STATUS_BODY_END | STATUS_BODY_SENT;
+
+        std::filesystem::path root, file;
+        root += params[0].to_string();
+        file += params[1].to_string();
+
+        handler_->write_file(this, (root / file.lexically_normal()).string(), ch);
+        return nullptr;
+    }
+
+    // 支持 content-length 形式的用法
+    void server_response::build_ex(boost::beast::http::message<false, value_body<false>>& ctr_) {
+        if (status_ & STATUS_BUILT) return; // 重复使用
+        status_ |= STATUS_BUILT; // 在 chunked_writer 中设置
+        ctr_.result( get("status").to_integer() ); // 非法 status_code 会抛出异常
+        php::array headers = get("header");
+        if (headers.type_of(php::TYPE::ARRAY)) {
+            for (auto i=headers.begin(); i!=headers.end(); ++i) {
+                php::string key = i->first.to_string();
+                php::string val = i->second.to_string();
+                ctr_.set(std::string_view(key.c_str(), key.size()), static_cast<std::string>(val));
+            }
+        }
+        php::array cookies = get("cookie");
+        if (cookies.type_of(php::TYPE::ARRAY)) {
+            for(auto i=cookies.begin(); i!=cookies.end(); ++i) {
+                php::array  cookie = i->second;
+                std::string buffer;
+                buffer.append(cookie.get("name"));
+                buffer.push_back('=');
+                php::string value = cookie.get("value");
+                if (!value.empty()) {
+                    buffer.push_back('"');
+                    buffer.append(php::url_encode(value.c_str(), value.size()));
+                    buffer.push_back('"');
+                }
+                if (cookie.exists("expire")) {
+                    std::int64_t expire = cookie.get("expire");
+                    // 范围 30 天内的数值, 按时间长度计算
+                    if (expire < 30 * 86400) {
+                        buffer.append("; Max-Age=", 10);
+                        buffer.append(std::to_string(expire));
+                        buffer.append("; Expire=", 9);
+                        php::object date = php::datetime(expire * 1000 + std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count());
+                        buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+                    }
+                    else {
+                    // 超过上面范围, 按时间点计算
+                        buffer.append("; Expire=", 9);
+                        php::object date = php::datetime(expire * 1000);
+                        buffer.append(date.call("format", {"l, d-M-Y H:i:s T"}));
+                    }
+                }
+                if (cookie.exists("path")) {
+                    buffer.append("; Path=", 7);
+                    buffer.append(cookie.get("path"));
+                }
+                if (cookie.exists("domain")) {
+                    buffer.append("; Domain=", 9);
+                    buffer.append(cookie.get("domain"));
+                }
+                if (!cookie.get("secure").empty()) buffer.append("; Secure", 8);
+                if (!cookie.get("http_only").empty()) buffer.append("; HttpOnly", 10);
+                ctr_.insert("Set-Cookie", buffer);
+            }
+        }
+CTYPE_AGAIN:
+        auto ctype = ctr_.find(boost::beast::http::field::content_type);
+        if (ctype == ctr_.end()) {
+            ctr_.set(boost::beast::http::field::content_type, "text/plain");
+            goto CTYPE_AGAIN;
+        }
+    }
+}
diff --git a/src/flame/http/server_response.h b/src/flame/http/server_response.h
new file mode 100644
index 0000000..958c00c
--- /dev/null
+++ b/src/flame/http/server_response.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+#include "value_body.h"
+
+namespace flame::http {
+
+    class _handler;
+    class server_response: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        // 声明为 ZEND_ACC_PRIVATE 禁止创建（不会被调用）
+        php::value __construct(php::parameters& params);
+        php::value set_cookie(php::parameters& params);
+        php::value write_header(php::parameters& params);
+        php::value write(php::parameters& params);
+        php::value end(php::parameters& params);
+        php::value file(php::parameters& params);
+        server_response();
+        ~server_response();
+    private:
+        enum {
+            STATUS_BUILT = 0x01,
+            STATUS_HEAD_SENT = 0x02,
+            STATUS_BODY_SENT = 0x04,
+            STATUS_BODY_END  = 0x08,
+        };
+        int status_;
+        std::shared_ptr<_handler> handler_; // 允许通过 $res 对象保留 handler 持续响应
+        void build_ex(boost::beast::http::message<false, value_body<false>> &ctr);
+
+        friend class _handler;
+    };
+}
diff --git a/src/flame/http/value_body.cpp b/src/flame/http/value_body.cpp
new file mode 100644
index 0000000..d03cb9d
--- /dev/null
+++ b/src/flame/http/value_body.cpp
@@ -0,0 +1,29 @@
+#include "../coroutine.h"
+#include "value_body.h"
+
+namespace flame::http {
+
+    void value_body_reader::init(boost::optional<std::uint64_t> n, boost::system::error_code& error) {
+        error.assign(0, error.category());
+    }
+
+    void value_body_reader::finish(boost::system::error_code& error) {
+        v_ = std::move(b_);
+        error.assign(0, error.category());
+    }
+
+    void value_body_writer::init(boost::system::error_code& error) {
+        error.assign(0, error.category());
+    }
+
+    boost::optional<std::pair<value_body_writer::const_buffers_type, bool>> value_body_writer::get(boost::system::error_code& error) {
+        if (v_.empty()) {
+            return {{boost::asio::const_buffer(nullptr, 0), false}};
+        }
+        else {
+            php::string data = v_;
+            error.assign(0, error.category());
+            return {{boost::asio::const_buffer(data.data(), data.size()), false}};
+        }
+    }
+} // namespace flame::http
diff --git a/src/flame/http/value_body.h b/src/flame/http/value_body.h
new file mode 100644
index 0000000..01afcac
--- /dev/null
+++ b/src/flame/http/value_body.h
@@ -0,0 +1,47 @@
+#pragma once
+#include "../../vendor.h"
+#include "http.h"
+
+namespace flame::http {
+
+    class value_body_reader;
+    class value_body_writer;
+    template <bool isRequest>
+    class value_body {
+    public:
+        // Body
+        using value_type = php::value;
+        static std::uint64_t size(const value_type& v);
+        // BodyReader (Body)
+        using reader = value_body_reader;
+        // BodyWriter (Body)
+        using writer = value_body_writer;
+    };
+
+    class value_body_reader {
+    public:
+        template <bool isRequest, class Fields>
+        value_body_reader(boost::beast::http::header<isRequest, Fields>& h, php::value& v);
+        void init(boost::optional<std::uint64_t> n, boost::system::error_code& error);
+        template <typename ConstBufferSequence>
+        std::size_t put(ConstBufferSequence s, boost::system::error_code& error);
+        void finish(boost::system::error_code& error);
+    private:
+        php::value& v_;
+        php::buffer b_;
+    };
+
+    class value_body_writer {
+    public:
+        using const_buffers_type = boost::asio::const_buffer;
+        template <bool isRequest, class Fields>
+        value_body_writer(const boost::beast::http::header<isRequest, Fields>& h, const php::value& v);
+        void init(boost::system::error_code& error);
+        boost::optional<std::pair<const_buffers_type, bool>> get(boost::system::error_code& error);
+    private:
+        // const boost::beast::http::header<true>& h_;
+        const php::value& v_;
+    };
+} // namnespace flame::http
+
+#include "value_body.ipp"
diff --git a/src/flame/http/value_body.ipp b/src/flame/http/value_body.ipp
new file mode 100644
index 0000000..63e9fce
--- /dev/null
+++ b/src/flame/http/value_body.ipp
@@ -0,0 +1,32 @@
+#pragma once
+
+namespace flame::http {
+    
+    template <bool isRequest, class Fields>
+    value_body_reader::value_body_reader(boost::beast::http::header<isRequest, Fields>& h, php::value& v)
+    : v_(v) {
+
+    }
+
+    template <typename ConstBufferSequence>
+    std::size_t value_body_reader::put(ConstBufferSequence s, boost::system::error_code& error) {
+        std::size_t n = 0;
+        for (auto i=boost::asio::buffer_sequence_begin(s); i!=boost::asio::buffer_sequence_end(s); ++i) {
+            boost::asio::const_buffer cbuf(*i);
+            b_.append((const char *)cbuf.data(), cbuf.size());
+            n += cbuf.size();
+        }
+        error.assign(0, error.category());
+        return n;
+    }
+
+    template <bool isRequest, class Fields>
+    value_body_writer::value_body_writer(const boost::beast::http::header<isRequest, Fields>& h, const php::value& v)
+    : v_(v) {}
+
+    template <bool isRequest>
+    std::uint64_t value_body<isRequest>::size(const value_body<isRequest>::value_type& v) {
+        return v.size();
+    }
+
+} // namespace flame::http
diff --git a/src/flame/kafka/_consumer.cpp b/src/flame/kafka/_consumer.cpp
new file mode 100644
index 0000000..2fb641d
--- /dev/null
+++ b/src/flame/kafka/_consumer.cpp
@@ -0,0 +1,148 @@
+#include "../controller.h"
+#include "../time/time.h"
+#include "../log/logger.h"
+#include "_consumer.h"
+#include "../../coroutine_queue.h"
+#include "kafka.h"
+#include "message.h"
+
+namespace flame::kafka {
+    static rd_kafka_topic_partition_list_t *array2topics(const php::array &topics) {
+        // 目标订阅的 TOPIC
+        rd_kafka_topic_partition_list_t *t = rd_kafka_topic_partition_list_new(topics.size());
+        for (auto i = topics.begin(); i != topics.end(); ++i)
+            rd_kafka_topic_partition_list_add(t, i->second.to_string().c_str(), RD_KAFKA_PARTITION_UA);
+        return t;
+    }
+
+    _consumer::_consumer(php::array &config, php::array &topics)
+    : close_(false) {
+        if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: 'bootstrap.servers' required"
+                , -1);
+
+        if (!config.exists("group.id"))
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: 'group.id' required"
+                , -1);
+
+        if (topics.size() == 0)
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka consumer: target topics missing"
+                , -1);
+
+        rd_kafka_conf_t *conf = array2conf(config);
+        char err[256];
+        rd_kafka_conf_set_opaque(conf, this);
+        rd_kafka_conf_set_error_cb(conf, on_error);
+
+        conn_ = rd_kafka_new(RD_KAFKA_CONSUMER, conf, err, sizeof(err));
+        if (!conn_)
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to create Kafka consumer: %s") % err).str()/*, 0*/);
+
+        auto r = rd_kafka_poll_set_consumer(conn_);
+        if (r != RD_KAFKA_RESP_ERR_NO_ERROR)
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to create Kafka Consumer: %s") % rd_kafka_err2str(r)).str()
+                , r);
+
+
+        tops_ = array2topics(topics);
+    }
+
+    _consumer::~_consumer() {
+        if (conn_) {
+            // rd_kafka_consumer_close(conn_);
+            rd_kafka_topic_partition_list_destroy(tops_);
+            rd_kafka_destroy(conn_);
+        }
+    }
+
+    void _consumer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
+        _consumer* self = reinterpret_cast<_consumer*>(data);
+        if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Consumer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << std::endl;
+    }
+
+    void _consumer::subscribe(coroutine_handler &ch) {
+        rd_kafka_resp_err_t err;
+        boost::asio::post(gcontroller->context_y, [this, &err, &ch] ()  {
+            err = rd_kafka_subscribe(conn_, tops_);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to subcribe Kafka topics: %s") % rd_kafka_err2str(err)).str()
+                , err);
+    }
+
+    void _consumer::consume(coroutine_queue<php::object>& q, coroutine_handler& ch) {
+        rd_kafka_resp_err_t err = RD_KAFKA_RESP_ERR_NO_ERROR;
+        rd_kafka_message_t* msg = nullptr;
+POLL_MESSAGE:
+        // 采用内部队列，理论上仅占用一个工作线程（应保证占用时间不要过长）
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]() {
+            msg = rd_kafka_consumer_poll(conn_, 40);
+            if (!msg) err = RD_KAFKA_RESP_ERR__PARTITION_EOF;
+            else if (msg->err) {
+                err = msg->err;
+                rd_kafka_message_destroy(msg);
+            }
+            else err = RD_KAFKA_RESP_ERR_NO_ERROR;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err == RD_KAFKA_RESP_ERR__PARTITION_EOF) {
+            if (close_)  {
+                q.close(); // 关闭队列使对应消费协程自行退出
+                return;
+            } // 消费已被关闭
+            else goto POLL_MESSAGE;
+        }
+        else if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to consume Kafka message: %s") % rd_kafka_err2str(err)).str()
+                , err);
+        }
+        else {
+            php::object obj(php::class_entry<message>::entry());
+            message* ptr = static_cast<message*>(php::native(obj));
+            ptr->build_ex(msg); // msg 交由 message 对象管理
+            q.push(std::move(obj), ch);
+            goto POLL_MESSAGE;
+        }
+    }
+
+    void _consumer::commit(const php::object& obj, coroutine_handler& ch) {
+        rd_kafka_resp_err_t err;
+        rd_kafka_message_t *msg = static_cast<message*>(php::native(obj))->msg_;
+        boost::asio::post(gcontroller->context_y, [this, &err, &msg, &ch]() {
+            err = rd_kafka_commit_message(conn_, msg, 0);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to commit Kafka message: %s") % err % rd_kafka_err2str(err)).str()
+                , err);
+    }
+
+    void _consumer::close(coroutine_handler& ch) {
+        rd_kafka_resp_err_t err;
+        boost::asio::post(gcontroller->context_y, [this, &err, &ch] () {
+            err = rd_kafka_consumer_close(conn_);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR)
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to close Kafka consumer: %s") % rd_kafka_err2str(err)).str()
+                , err);
+
+        close_ = true;
+    }
+
+} // namespace flame::kafka
diff --git a/src/flame/kafka/_consumer.h b/src/flame/kafka/_consumer.h
new file mode 100644
index 0000000..bbf7ec7
--- /dev/null
+++ b/src/flame/kafka/_consumer.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "kafka.h"
+
+
+template <class T>
+class coroutine_queue;
+
+namespace flame::kafka {
+    // 消费者
+    // 目前的实现方式无需 shared_from_this 管理
+    class _consumer/*: public std::enable_shared_from_this<_consumer> */{
+    public:
+        _consumer(php::array& config, php::array& topics);
+        ~_consumer();
+        void subscribe(coroutine_handler& ch);
+        void consume(coroutine_queue<php::object>& q, coroutine_handler& ch);
+        void commit(const php::object& msg, coroutine_handler& ch);
+        void close(coroutine_handler& ch);
+    private:
+        rd_kafka_t* conn_;
+        rd_kafka_topic_partition_list_t* tops_;
+        bool       close_;
+        static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data);
+    };
+} // namespace flame::kafka
diff --git a/src/flame/kafka/_producer.cpp b/src/flame/kafka/_producer.cpp
new file mode 100644
index 0000000..ad321dc
--- /dev/null
+++ b/src/flame/kafka/_producer.cpp
@@ -0,0 +1,127 @@
+#include "../controller.h"
+#include "../time/time.h"
+#include "../log/logger.h"
+#include "_producer.h"
+#include "kafka.h"
+
+namespace flame::kafka {
+
+    _producer::_producer(php::array& config, php::array& topics)
+    : poll_(gcontroller->context_y)
+    , close_(false) {
+        if (!config.exists("bootstrap.servers") && !config.exists("metadata.broker.list"))
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka producer: 'bootstrap.servers' required"
+                , -1);
+        if (topics.size() == 0)
+            throw php::exception(zend_ce_type_error
+                , "Failed to create Kafka producer: target topics missing"
+                , -1);
+
+        rd_kafka_conf_t *conf = array2conf(config);
+        rd_kafka_conf_set_opaque(conf, this);
+        rd_kafka_conf_set_error_cb(conf, on_error);
+
+        char err[256];
+        conn_ = rd_kafka_new(RD_KAFKA_PRODUCER, conf, err, sizeof(err));
+        if (!conn_)
+            throw php::exception(zend_ce_type_error
+                , (boost::format("Failed to create Kafka Consumer: %s") % err).str()
+                , -1);
+
+        for (auto i = topics.begin(); i != topics.end(); ++i) {
+            php::string topic = i->second.to_string();
+            tops_[topic] = rd_kafka_topic_new(conn_, topic.c_str(), nullptr);
+        }
+    }
+
+    _producer::~_producer() {
+        if (!conn_) return;
+        for(auto i=tops_.begin(); i!=tops_.end(); ++i) {
+            rd_kafka_topic_destroy(i->second);
+        }
+        rd_kafka_destroy(conn_);
+    }
+
+    void _producer::on_error(rd_kafka_t* conn, int error, const char* reason, void* data) {
+        _producer* self = reinterpret_cast<_producer*>(data);
+        if (log::logger::LEVEL_OPT <= log::logger::LEVEL_WARNING)
+            log::logger_->stream() << "[" << time::iso() << "] (WARNING) Kafka Producer " << rd_kafka_err2str((rd_kafka_resp_err_t)error) << ": " << reason << std::endl;
+    }
+
+    void _producer::start() {
+        poll();
+    }
+
+    void _producer::poll(int expire) {
+        poll_.expires_after(std::chrono::milliseconds(expire));
+        poll_.async_wait([this, self = shared_from_this()] (const boost::system::error_code& error) {
+            if (error || close_) return;
+            auto begin = std::chrono::steady_clock::now();
+            int r = rd_kafka_poll(conn_, 40); // 防止对工作线程占用时间过长
+            if (close_) return;
+            auto end = std::chrono::steady_clock::now();
+            // 计算实际 poll 占用时间，稳定间隔
+            int e = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
+            // 无消息时放慢 poll 以减少对工作线程的占用
+            if (r > 0) poll(std::max(40 - e, 1));
+            else poll(std::max(1600 - e, 1));
+        });
+    }
+
+    void _producer::close(coroutine_handler& ch) {
+        if (!conn_) return;
+        close_ = true;
+        poll_.cancel();
+        boost::asio::post(gcontroller->context_y, [this, &ch] {
+            rd_kafka_yield(conn_);
+            rd_kafka_flush(conn_, 10000);
+            ch.resume();
+        });
+        ch.suspend();
+    }
+
+    void _producer::publish(const php::string &topic, const php::string &key
+        , const php::string &payload, const php::array &headers, coroutine_handler &ch) {
+
+        rd_kafka_resp_err_t err = RD_KAFKA_RESP_ERR_NO_ERROR;
+        rd_kafka_headers_t* hdrs = array2hdrs(headers);
+        boost::asio::post(gcontroller->context_y, [this, &err, &topic, &key, &payload, &hdrs, &ch]() {
+            if (key.size() > 0) {
+                err = rd_kafka_producev(conn_,
+                    RD_KAFKA_V_TOPIC(topic.c_str()),
+                    RD_KAFKA_V_KEY(key.c_str(), key.size()),
+                    RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
+                    RD_KAFKA_V_HEADERS(hdrs),
+                    RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
+                    RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
+                    RD_KAFKA_V_END);
+            }
+            else { // 无 KEY 时默认 partitioner 会随机分配
+                err = rd_kafka_producev(conn_,
+                    RD_KAFKA_V_TOPIC(topic.c_str()),
+                    RD_KAFKA_V_PARTITION(RD_KAFKA_PARTITION_UA),
+                    RD_KAFKA_V_HEADERS(hdrs),
+                    RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
+                    RD_KAFKA_V_VALUE((void *)payload.c_str(), payload.size()),
+                    RD_KAFKA_V_END);
+            }
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != RD_KAFKA_RESP_ERR_NO_ERROR) {
+            if (hdrs) rd_kafka_headers_destroy(hdrs);  // 发生错误时, 需要手动销毁
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish Kafka message: %s") % rd_kafka_err2str(err)).str()
+                , err);
+        }
+    }
+
+    void _producer::flush(coroutine_handler& ch) {
+        if (!conn_) return;
+        poll_.cancel();
+        rd_kafka_yield(conn_); // 停止当前的 poll
+        rd_kafka_flush(conn_, 10000);
+        poll(); // 重新启动 poll 流程
+    }
+} // namespace flame::kafka
diff --git a/src/flame/kafka/_producer.h b/src/flame/kafka/_producer.h
new file mode 100644
index 0000000..8269959
--- /dev/null
+++ b/src/flame/kafka/_producer.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "kafka.h"
+
+namespace flame::kafka {
+    // 生产者
+    // 目前的实现方式 poll 定时调用机制需要 shared_from_this 保证生命周期
+    class _producer: public std::enable_shared_from_this<_producer> {
+    public:
+        _producer(php::array& config, php::array& topics);
+        ~_producer();
+        void publish(const php::string& topic, const php::string& key, const php::string& payload, const php::array& headers, coroutine_handler& ch);
+        void flush(coroutine_handler& ch);
+        void close(coroutine_handler& ch);
+        void start();
+    private:
+        rd_kafka_t* conn_;
+        std::map<php::string, rd_kafka_topic_t*> tops_;
+        boost::asio::steady_timer poll_;
+        bool close_;
+        void poll(int expire = 970);
+        static void on_error(rd_kafka_t* conn, int error, const char* reason, void* data);
+        friend class producer;
+    };
+} // namespace flame::kafka
diff --git a/src/flame/kafka/consumer.cpp b/src/flame/kafka/consumer.cpp
new file mode 100644
index 0000000..026e878
--- /dev/null
+++ b/src/flame/kafka/consumer.cpp
@@ -0,0 +1,76 @@
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "consumer.h"
+#include "_consumer.h"
+#include "kafka.h"
+#include "message.h"
+#include "../../coroutine_queue.h"
+#include "../log/logger.h"
+
+namespace flame::kafka {
+
+    void consumer::declare(php::extension_entry& ext) {
+        php::class_entry<consumer> class_consumer("flame\\kafka\\consumer");
+        class_consumer
+            .method<&consumer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&consumer::run>("run", {
+                {"callable", php::TYPE::CALLABLE},
+            })
+            .method<&consumer::commit>("commit", {
+                {"message", "flame\\kafka\\message"}
+            })
+            .method<&consumer::close>("close");
+        ext.add(std::move(class_consumer));
+    }
+
+    php::value consumer::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value consumer::run(php::parameters& params) {
+        cb_ = params[0];
+        coroutine_handler ch_run {coroutine::current};
+        coroutine_queue<php::object> q;
+        // 启动若干协程, 然后进行"并行"消费
+        int count = cc_;
+        for (int i = 0; i < count; ++i) {
+            // 启动协程开始消费
+            coroutine::start(php::value([this, &ch_run, &q, &count](php::parameters &params) -> php::value {
+                coroutine_handler ch {coroutine::current};
+                while(true) {
+                    try {
+                        // consume 本身可能出现异常，不应导致进程停止
+                        std::optional<php::object> m = q.pop(ch);
+                        if (m) cb_.call({m.value()});
+                        else break;
+                    } catch(const php::exception& ex) {
+                        // 调用用户异常回调
+                        gcontroller->event("exception", {ex});
+                        // 记录错误信息
+                        php::object obj = ex;
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught exception in Kafka consumer: " << obj.call("__toString") << std::endl;
+                    }
+                }
+                if (--count == 0) ch_run.resume();
+                return nullptr;
+            }));
+        }
+        cs_->consume(q, ch_run);
+        ch_run.suspend();
+        return nullptr;
+    }
+
+    php::value consumer::commit(php::parameters& params) {
+        php::object obj = params[0];
+        coroutine_handler ch {coroutine::current};
+
+        cs_->commit(obj, ch);
+        return nullptr;
+    }
+
+    php::value consumer::close(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        cs_->close(ch);
+        return nullptr;
+    }
+} // namespace
diff --git a/src/flame/kafka/consumer.h b/src/flame/kafka/consumer.h
new file mode 100644
index 0000000..255ad80
--- /dev/null
+++ b/src/flame/kafka/consumer.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "kafka.h"
+
+namespace flame::kafka {
+
+    class _consumer;
+    class consumer : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value run(php::parameters &params);
+        php::value commit(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        std::shared_ptr<_consumer> cs_;
+        int                        cc_ = 8;
+        php::callable              cb_;
+
+        friend php::value consume(php::parameters &params);
+    };
+} // namespace flame::kafka
diff --git a/src/flame/kafka/kafka.cpp b/src/flame/kafka/kafka.cpp
new file mode 100644
index 0000000..24d8454
--- /dev/null
+++ b/src/flame/kafka/kafka.cpp
@@ -0,0 +1,98 @@
+#include "kafka.h"
+#include "message.h"
+#include "consumer.h"
+#include "_consumer.h"
+#include "producer.h"
+#include "_producer.h"
+
+namespace flame::kafka {
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<consume>("flame\\kafka\\consume", {
+                {"options", php::TYPE::ARRAY},
+                {"topics", php::TYPE::ARRAY},
+            })
+            .function<produce>("flame\\kafka\\produce", {
+                {"options", php::TYPE::ARRAY},
+                {"topics", php::TYPE::ARRAY},
+            });;
+        message::declare(ext);
+        consumer::declare(ext);
+        producer::declare(ext);
+    }
+
+    php::value consume(php::parameters& params) {
+        php::array config = params[0];
+        php::array topics = params[1];
+
+        php::object obj(php::class_entry<consumer>::entry());
+        consumer* ptr = static_cast<consumer*>(php::native(obj));
+        if (config.exists("concurrent")) {
+            ptr->cc_ = std::min(std::max(static_cast<int>(config.get("concurrent")), 1), 256);
+            config.erase("concurrent");
+        }
+        if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
+
+        ptr->cs_ = std::make_shared<_consumer>(config, topics);
+        coroutine_handler ch {coroutine::current};
+        // 订阅
+        ptr->cs_->subscribe(ch);
+        return std::move(obj);
+    }
+
+    php::value produce(php::parameters& params) {
+        php::array config = params[0];
+        php::array topics = params[1];
+
+        php::object obj(php::class_entry<producer>::entry());
+        producer* ptr = static_cast<producer*>(php::native(obj));
+
+        if (!config.exists("log.connection.close")) config.set("log.connection.close", "false");
+
+        ptr->pd_ = std::make_shared<_producer>(config, topics);
+        ptr->pd_->start(); // 不能在构造中启动 poll 流程
+        // TODO 优化: 确认首次连接已建立
+        return std::move(obj);
+    }
+
+    rd_kafka_conf_t* array2conf(const php::array &config) {
+        if (config.empty()) return nullptr;
+        char err[256];
+        rd_kafka_conf_t *conf = rd_kafka_conf_new();
+        for (auto i = config.begin(); i != config.end(); ++i) {
+            php::string key = i->first.to_string();
+            php::string val = i->second.to_string();
+
+            if (RD_KAFKA_CONF_OK != rd_kafka_conf_set(conf, key.data(), val.data(), err, sizeof(err)))
+                throw php::exception(zend_ce_type_error
+                    , (boost::format("Failed to set Kafka config: %s") % err).str()
+                    , -1);
+        }
+        return conf;
+    }
+
+    rd_kafka_headers_t* array2hdrs(const php::array& data) {
+        rd_kafka_headers_t *hdrs = nullptr;
+        if (!data.empty() && data.type_of(php::TYPE::ARRAY) && data.size() > 0) {
+            hdrs = rd_kafka_headers_new(data.size());
+            for (auto i = data.begin(); i != data.end(); ++i) {
+                php::string key = i->first.to_string(),
+                            val = i->second.to_string();
+                rd_kafka_header_add(hdrs,
+                    key.c_str(), key.size(),
+                    val.c_str(), val.size());
+            }
+        }
+        return hdrs;
+    }
+    php::array hdrs2array(rd_kafka_headers_t* hdrs) {
+        if (hdrs == nullptr) return php::array(0);
+        php::array header(rd_kafka_header_cnt(hdrs));
+        const char *key;
+        const void *val;
+        std::size_t len;
+        for (std::size_t i = 0; rd_kafka_header_get_all(hdrs, i, &key, &val, &len) == RD_KAFKA_RESP_ERR_NO_ERROR; ++i)
+            header.set(key, php::string((const char *)val, len));
+        return header;
+    }
+}
diff --git a/src/flame/kafka/kafka.h b/src/flame/kafka/kafka.h
new file mode 100644
index 0000000..a9ad7a0
--- /dev/null
+++ b/src/flame/kafka/kafka.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "../../vendor.h"
+#include <librdkafka/rdkafka.h>
+
+namespace flame::kafka {
+    void declare(php::extension_entry &ext);
+    php::value consume(php::parameters& params);
+    php::value produce(php::parameters& params);
+    
+    rd_kafka_conf_t*    array2conf(const php::array& data);
+    rd_kafka_headers_t* array2hdrs(const php::array& data);
+    php::array          hdrs2array(rd_kafka_headers_t* hdrs);
+} // namespace flame::kafka
diff --git a/src/flame/kafka/message.cpp b/src/flame/kafka/message.cpp
new file mode 100644
index 0000000..4d71b93
--- /dev/null
+++ b/src/flame/kafka/message.cpp
@@ -0,0 +1,66 @@
+#include "message.h"
+#include "kafka.h"
+#include "../time/time.h"
+
+namespace flame::kafka {
+    void message::declare(php::extension_entry& ext) {
+        php::class_entry<message> class_message("flame\\kafka\\message");
+        class_message
+            .implements(&php_json_serializable_ce)
+            .property({"topic", ""})
+            .property({"partition", -1})
+            .property({"key", ""})
+            .property({"offset", -1})
+            .property({"header", nullptr})
+            .property({"payload", nullptr})
+            .property({"timestamp", 0})
+            .method<&message::__construct>("__construct", {
+                {"payload", php::TYPE::STRING, false, true},
+                {"key", php::TYPE::STRING, false, true},
+            })
+            .method<&message::to_string>("__toString")
+            .method<&message::to_json>("jsonSerialize");
+        ext.add(std::move(class_message));
+    }
+
+    message::~message()  {
+        if (msg_) rd_kafka_message_destroy(msg_);
+    }
+
+    void message::build_ex(rd_kafka_message_t* msg) {
+        // 用于单挑 message 的提交
+        msg_ = msg;
+        // ！！！ 是否必须复制出 PHP 的内容？
+        set("topic", php::string(rd_kafka_topic_name(msg->rkt)));
+        set("partition", msg->partition);
+        set("key", php::string((const char*)msg->key, msg->key_len));
+        set("offset", msg->offset);
+        rd_kafka_headers_t* hdrs;
+        if (rd_kafka_message_headers(msg, &hdrs) == RD_KAFKA_RESP_ERR_NO_ERROR)
+            set("header", hdrs2array(hdrs));
+        // header
+        set("payload", php::string((const char*)msg->payload, msg->len));
+        set("timestamp", rd_kafka_message_timestamp(msg, nullptr));
+    }
+
+    php::value message::__construct(php::parameters& params) {
+        if (params.size() > 0) set("payload", params[0].to_string());
+        if (params.size() > 1) set("key", params[1].to_string());
+        set("header", php::array(4));
+        set("timestamp", 
+            std::chrono::duration_cast<std::chrono::milliseconds>(flame::time::now().time_since_epoch()).count());
+        return nullptr;
+    }
+
+    php::value message::to_json(php::parameters& params) {
+        php::array json(4);
+        json.set("topic",  get("topic"));
+        json.set("key",    get("key"));
+        json.set("payload",get("payload"));
+        return json;
+    }
+
+    php::value message::to_string(php::parameters& params) {
+        return get("payload");
+    }
+} // namespace flame::kafka
diff --git a/src/flame/kafka/message.h b/src/flame/kafka/message.h
new file mode 100644
index 0000000..f2ba398
--- /dev/null
+++ b/src/flame/kafka/message.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../../vendor.h"
+#include "kafka.h"
+
+namespace flame::kafka {
+    
+    class _consumer;
+    class message: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        ~message();
+        void build_ex(rd_kafka_message_t* msg);
+        php::value __construct(php::parameters& params); // 私有
+        php::value to_json(php::parameters& params);
+        php::value to_string(php::parameters& params);
+    private:
+      rd_kafka_message_t *msg_ = nullptr;
+
+      friend class _consumer;
+    //   friend class _producer;
+    };
+}
diff --git a/src/flame/kafka/producer.cpp b/src/flame/kafka/producer.cpp
new file mode 100644
index 0000000..b141d25
--- /dev/null
+++ b/src/flame/kafka/producer.cpp
@@ -0,0 +1,63 @@
+#include "../coroutine.h"
+#include "kafka.h"
+#include "message.h"
+#include "_producer.h"
+#include "producer.h"
+
+namespace flame::kafka {
+
+    void producer::declare(php::extension_entry &ext) {
+        php::class_entry<producer> class_producer("flame\\kafka\\producer");
+        class_producer
+            .method<&producer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&producer::__destruct>("__destruct")
+            .method<&producer::publish>("publish", {
+                {"topic", php::TYPE::STRING},
+            })
+            .method<&producer::flush>("flush");
+        ext.add(std::move(class_producer));
+    }
+
+    php::value producer::__construct(php::parameters &params) {
+        return nullptr;
+    }
+
+    php::value producer::__destruct(php::parameters &params) {
+        if (pd_) {
+            coroutine_handler ch {coroutine::current};
+            pd_->close(ch); // 内部存在 flush 机制
+        }
+        return nullptr;
+    }
+
+    php::value producer::publish(php::parameters &params) {
+        php::string topic = params[0].to_string(), key, payload;
+        php::array header;
+        if (params[1].instanceof (php::class_entry<message>::entry())) {
+            php::object msg = params[1];
+            key = msg.get("key").to_string();
+            payload = msg.get("payload").to_string();
+            header = msg.get("header");
+            if (!header.type_of(php::TYPE::ARRAY)) header = php::array(0);
+        }
+        else {
+            payload = params[1].to_string();
+            if (params.size() > 2) key = params[2].to_string();
+            else key = php::string(0);
+            if (params.size() > 3) {
+                if (params[3].type_of(php::TYPE::ARRAY)) header = params[4];
+                else header = php::array(0);
+            }
+        }
+
+        coroutine_handler ch {coroutine::current};
+        pd_->publish(topic, key, payload, header, ch);
+        return nullptr;
+    }
+
+    php::value producer::flush(php::parameters &params) {
+        coroutine_handler ch {coroutine::current};
+        pd_->flush(ch);
+        return nullptr;
+    }
+} // namespace flame::kafka
diff --git a/src/flame/kafka/producer.h b/src/flame/kafka/producer.h
new file mode 100644
index 0000000..3bdc269
--- /dev/null
+++ b/src/flame/kafka/producer.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../../vendor.h"
+#include "kafka.h"
+
+namespace flame::kafka {
+    
+    class _producer;
+    class producer : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters &params);
+        php::value publish(php::parameters &params);
+        php::value flush(php::parameters &params);
+
+    private:
+        std::shared_ptr<_producer> pd_;
+        friend php::value produce(php::parameters &params);
+    };
+} // namespace flame::kafka
diff --git a/src/flame/log/log.cpp b/src/flame/log/log.cpp
new file mode 100644
index 0000000..badc556
--- /dev/null
+++ b/src/flame/log/log.cpp
@@ -0,0 +1,66 @@
+#include "../coroutine.h"
+#include "../worker.h"
+#include "log.h"
+#include "logger.h"
+
+namespace flame::log {
+    
+    static php::value write(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_EMPTY, params);
+        return nullptr;
+    }
+
+    static php::value trace(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_TRACE, params);
+        return nullptr;
+    }
+
+    static php::value debug(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_DEBUG, params);
+        return nullptr;
+    }
+
+    static php::value info(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_INFO, params);
+        return nullptr;
+    }
+
+    static php::value warning(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_WARNING, params);
+        return nullptr;
+    }
+
+    static php::value error(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_ERROR, params);
+        return nullptr;
+    }
+
+    static php::value fatal(php::parameters &params) {
+        logger_->write_ex(logger::LEVEL_FATAL, params);
+        return nullptr;
+    }
+
+    php::value connect(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        php::object obj {php::class_entry<logger>::entry()};
+        logger* ptr = static_cast<logger*>(php::native(obj));
+        ptr->connect(params[0], ch);
+        return obj;
+    }
+
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<connect>("flame\\log\\connect")
+            .function<write>("flame\\log\\write")
+            .function<trace>("flame\\log\\trace")
+            .function<debug>("flame\\log\\debug")
+            .function<info>("flame\\log\\info")
+            .function<warning>("flame\\log\\warn")
+            .function<warning>("flame\\log\\warning")
+            .function<error>("flame\\log\\error")
+            .function<fatal>("flame\\log\\fail")
+            .function<fatal>("flame\\log\\fatal");
+        
+        logger::declare(ext);
+    }
+}
diff --git a/src/flame/log/log.h b/src/flame/log/log.h
new file mode 100644
index 0000000..0b69f90
--- /dev/null
+++ b/src/flame/log/log.h
@@ -0,0 +1,7 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::log {
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters& params);
+} // namespace flame::log
diff --git a/src/flame/log/logger.cpp b/src/flame/log/logger.cpp
new file mode 100644
index 0000000..68d45d9
--- /dev/null
+++ b/src/flame/log/logger.cpp
@@ -0,0 +1,113 @@
+#include "../../worker_logger.h"
+#include "log.h"
+#include "logger.h"
+#include "../worker.h"
+#include "../time/time.h"
+#include "../coroutine.h"
+
+namespace flame::log {
+    
+    std::array<std::string, 6> logger::LEVEL_STR = {
+        "TRACE",
+        "DEBUG",
+        "INFO",
+        "WARNING",
+        "ERROR",
+        "FATAL",
+    };
+    int logger::LEVEL_OPT = logger::LEVEL_TRACE;
+
+    logger* logger_ = nullptr;
+
+    void logger::declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
+            if (options.exists("level")) logger::LEVEL_OPT = options.get("level").to_integer();
+            else logger::LEVEL_OPT = logger::LEVEL_TRACE;
+            std::string output = "<clog>";
+            if (options.exists("logger")) output = options.get("logger").to_string();
+            logger_ = new logger();
+            // 启动的一个更轻量的 C++ 内部协程
+            ::coroutine::start(gcontroller->context_x.get_executor(), [output] (::coroutine_handler ch) {
+                logger_->connect(output, ch);
+            });
+        });
+        for(int i=0;i<logger::LEVEL_STR.size();++i) {
+            ext.constant({"flame\\log\\" + logger::LEVEL_STR[i], i}); // 日志等级常量
+        }
+
+        php::class_entry<logger> class_logger("flame\\log\\logger");
+        class_logger
+            .method<&logger::__construct>("__construct", php::PRIVATE) // 禁止手动创建
+            .method<&logger::write>("write")
+            .method<&logger::trace>("trace")
+            .method<&logger::debug>("debug")
+            .method<&logger::info>("info")
+            .method<&logger::warning>("warn")
+            .method<&logger::warning>("warning")
+            .method<&logger::error>("error")
+            .method<&logger::fatal>("fail")
+            .method<&logger::fatal>("fatal");
+        ext.add(std::move(class_logger));
+    }
+    
+    php::value logger::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    void logger::connect(const std::string& path, ::coroutine_handler& ch) {
+        lg_ = worker::get()->lm_connect(path, ch);
+    }
+    
+    std::ostream& logger::stream() {
+        return lg_ ? lg_->stream() : std::clog; // 由于 lg_ 的填充是异步的，须防止其还未初始化就被其他模块访问
+    }
+
+    void logger::write_ex(int lv, php::parameters& params) {
+        if (lv < LEVEL_OPT) return;
+        std::ostream& os = logger::stream();
+        if (lv != LEVEL_EMPTY) {
+            os << '[' << time::iso() << "] (";
+            os << LEVEL_STR[lv];
+            os << ") ";
+        }
+        int i = 0;
+        for (; i < params.size() - 1; ++i)
+            os << params[i].ptr() << ' ';
+        os << params[i].ptr() << std::endl; // 使用 endl 会进行 flush 使日志快速出现
+    }
+
+    php::value logger::write(php::parameters& params) {
+        write_ex(LEVEL_EMPTY, params);
+        return nullptr;
+    }
+
+    php::value logger::trace(php::parameters &params) {
+        write_ex(LEVEL_TRACE, params);
+        return nullptr;
+    }
+
+    php::value logger::debug(php::parameters &params) {
+        write_ex(LEVEL_DEBUG, params);
+        return nullptr;
+    }
+
+    php::value logger::info(php::parameters &params) {
+        write_ex(LEVEL_INFO, params);
+        return nullptr;
+    }
+
+    php::value logger::warning(php::parameters &params) {
+        write_ex(LEVEL_WARNING, params);
+        return nullptr;
+    }
+
+    php::value logger::error(php::parameters &params) {
+        write_ex(LEVEL_ERROR, params);
+        return nullptr;
+    }
+
+    php::value logger::fatal(php::parameters &params) {
+        write_ex(LEVEL_FATAL, params);
+        return nullptr;
+    }
+}
diff --git a/src/flame/log/logger.h b/src/flame/log/logger.h
new file mode 100644
index 0000000..8e55212
--- /dev/null
+++ b/src/flame/log/logger.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "../../vendor.h"
+
+class coroutine_handler;
+class worker_logger;
+namespace flame::log {
+
+    class logger: public php::class_base {
+    public:
+        enum {
+            LEVEL_TRACE,
+            LEVEL_DEBUG,
+            LEVEL_INFO,
+            LEVEL_WARNING,
+            LEVEL_ERROR,
+            LEVEL_FATAL,
+
+            LEVEL_EMPTY,
+        };
+        static void declare(php::extension_entry& ext);
+        static std::array<std::string, 6> LEVEL_STR;
+        static int LEVEL_OPT;
+        php::value __construct(php::parameters& params);
+        // 使用父类型 coroutine_handler 引用能够兼容 C++ / PHP 协程
+        void connect(const std::string& path, ::coroutine_handler& ch);
+        void write_ex(int lv, php::parameters& params);
+        std::ostream& stream();
+        php::value write(php::parameters &params);
+        php::value trace(php::parameters &params);
+        php::value debug(php::parameters &params);
+        php::value info(php::parameters &params);
+        php::value warning(php::parameters &params);
+        php::value error(php::parameters &params);
+        php::value fatal(php::parameters &params);
+    private:
+        std::shared_ptr<worker_logger> lg_;
+    };
+    // 默认日志记录器
+    extern logger* logger_;
+}
\ No newline at end of file
diff --git a/src/flame/master.cpp b/src/flame/master.cpp
new file mode 100644
index 0000000..dafdd14
--- /dev/null
+++ b/src/flame/master.cpp
@@ -0,0 +1,155 @@
+#include "master.h"
+#include "controller.h"
+#include "../util.h"
+#include "../master_logger.h"
+#include "../master_process.h"
+
+namespace flame {
+
+    std::shared_ptr<master> master::mm_;
+
+    void master::declare(php::extension_entry& ext) {
+        ext
+            .function<master::init>("flame\\init")
+            .function<master::dummy>("flame\\go")
+            .function<master::dummy>("flame\\on")
+            .function<master::run>("flame\\run");
+    }
+
+    php::value master::init(php::parameters& params) {
+        php::array options = php::array(0);
+        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
+        if (options.exists("timeout"))
+            gcontroller->worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+        else
+            gcontroller->worker_quit = 3000;
+        
+        gcontroller->status |= controller::STATUS_INITIALIZED;
+        // 设置进程标题
+        std::string title = params[0];
+        php::callable("cli_set_process_title").call({title + " (php-flame/m)"});
+
+        // 主进程控制对象
+        master::mm_.reset(new master());
+        if (options.exists("logger")) {
+            php::string logger = options.get("logger");
+            master::mm_->lg_ = master::mm_->lm_connect({logger.data(), logger.size()});
+        }
+        else
+            master::mm_->lg_ = master::mm_->lm_connect("<clog>");
+        // 初始化启动
+        gcontroller->init(options);
+        master::mm_->ipc_start(); // IPC 启动
+        master::mm_->sw_watch(); // 信号监听启动
+        return nullptr;
+    }
+
+    php::value master::run(php::parameters& params) {
+         if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+            
+        gcontroller->status |= controller::STATUS_RUN;
+        master::mm_->pm_start(gcontroller->context_x); // 启动工作进程
+        
+        std::thread ts[2];
+        ts[0] = std::thread([] () {
+            gcontroller->context_y.run();
+        });
+        ts[1] = std::thread([] () {
+            gcontroller->context_z.run();
+        });
+        gcontroller->context_x.run();
+        master::mm_->sw_close();
+        master::mm_->ipc_close();
+        master::mm_->lm_close();
+
+        // if (gcontroller->status & controller::STATUS_EXCEPTION) _exit(-1);
+        gcontroller->stop();
+        ts[0].join();
+        ts[1].join();
+
+        master::mm_.reset();
+        return nullptr;
+    }
+
+    php::value master::dummy(php::parameters& params) {
+        return nullptr;
+    }
+
+    master::master()
+    : master_process_manager(gcontroller->context_y, gcontroller->worker_size, gcontroller->worker_quit)
+    , signal_watcher(gcontroller->context_y)
+    , master_logger_manager(gcontroller->context_y)
+    , master_ipc(gcontroller->context_y) {
+        
+    }
+
+    std::ostream& master::output() {
+        return lg_->stream();
+    }
+    // !!! 此函数在工作线程中工作
+    bool master::on_signal(int sig) {
+        switch(sig) {
+        break;
+        case SIGUSR2: 
+            lm_reload(); // 日志重载
+        break;
+        case SIGUSR1:
+            boost::asio::post(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] () {
+                gcontroller->status ^= controller::STATUS_CLOSECONN;
+                gcontroller->status & controller::STATUS_CLOSECONN ? 
+                    (output() << "[" << util::system_time() << "] [INFO] append 'Connection: close' header." << std::endl) :
+                    (output() << "[" << util::system_time() << "] [INFO] remove 'Connection: close' header." << std::endl) ;
+
+                pm_kills(SIGUSR1); // 长短连切换
+            });
+        break;
+        case SIGINT:
+        case SIGQUIT:
+            coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                gcontroller->status |= controller::STATUS_QUITING | controller::STATUS_CLOSECONN;
+                pm_close(ch, true); // 立即关闭
+            });
+            return false; // 除强制停止信号外，需要持续监听信号  
+        break;
+        case SIGTERM:
+            coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                gcontroller->status |= controller::STATUS_CLOSING | controller::STATUS_CLOSECONN;
+                pm_close(ch); // 超时关闭
+            });
+        break;
+        default:
+            if(sig == SIGRTMIN + 1)
+                coroutine::start(gcontroller->context_y.get_executor(), [this, self = shared_from_this()] (coroutine_handler ch) { // 使用轻量级的 C++ 内部协程
+                    if(gcontroller->status & (controller::STATUS_CLOSING | controller::STATUS_QUITING | controller::STATUS_RSETING)) return; // 关闭进行中
+                    gcontroller->status |=   controller::STATUS_RSETING | controller::STATUS_CLOSECONN;
+                    pm_reset(ch);
+                    gcontroller->status &= ~(controller::STATUS_RSETING | controller::STATUS_CLOSECONN);
+                });
+        }
+        return true;
+    }
+    // !!! 此函数在工作线程中工作
+    bool master::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
+        switch(msg->command) {
+        // 工作进程创建了新的日志对象，连接到指定的文件输出
+        case ipc::COMMAND_LOGGER_CONNECT:
+            msg->xdata[0]  = lm_connect({&msg->payload[0], msg->length})->index();
+            msg->target = msg->source; // 响应给来源的工作进程
+            msg->length = 0;
+            ipc_request(msg);
+            return true;
+        // 工作进程写入日志数据
+        case ipc::COMMAND_LOGGER_DATA:
+            // 使用 xdata[0] 标识实际写入的目标日志
+            lm_get(msg->xdata[0])->stream() << "~~~~~" << std::string_view {&msg->payload[0], msg->length};
+            return true;
+        case ipc::COMMAND_LOGGER_DESTROY:
+            lm_destroy(msg->target);
+            return true;
+        }
+        return master_ipc::on_message(msg, sock);
+    }
+}
diff --git a/src/flame/master.h b/src/flame/master.h
new file mode 100644
index 0000000..33afa68
--- /dev/null
+++ b/src/flame/master.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "../vendor.h"
+#include "../master_process_manager.h"
+#include "../master_logger_manager.h"
+#include "../signal_watcher.h"
+#include "../master_ipc.h"
+
+namespace flame {
+    class master: public std::enable_shared_from_this<master>, public master_process_manager, public signal_watcher, public master_logger_manager, public master_ipc {
+    public:
+        static inline std::shared_ptr<master> get() {
+            return mm_;
+        }
+        static void declare(php::extension_entry& ext);
+        static php::value init(php::parameters& params);
+        static php::value run(php::parameters& params);
+        static php::value dummy(php::parameters& params);
+
+        master();
+        std::ostream& output() override;
+    protected:
+        std::shared_ptr<master_process_manager> pm_self() override {
+            return std::static_pointer_cast<master_process_manager>(shared_from_this());
+        }
+        virtual std::shared_ptr<signal_watcher> sw_self() override {
+            return std::static_pointer_cast<signal_watcher>(shared_from_this());
+        }
+        virtual std::shared_ptr<master_logger_manager> lm_self() override {
+            return std::static_pointer_cast<master_logger_manager>(shared_from_this());
+        }
+        virtual std::shared_ptr<master_ipc> ipc_self() override {
+            return std::static_pointer_cast<master_ipc>(shared_from_this());
+        }
+        // void on_child_close(master_process* w, bool normal) override;
+        bool on_signal(int sig) override;
+        bool on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) override;
+        
+    private:
+        static std::shared_ptr<master> mm_;
+        master_logger* lg_;
+
+        friend class controller;
+    };
+}
\ No newline at end of file
diff --git a/src/flame/mongodb/_connection_base.cpp b/src/flame/mongodb/_connection_base.cpp
new file mode 100644
index 0000000..a453af9
--- /dev/null
+++ b/src/flame/mongodb/_connection_base.cpp
@@ -0,0 +1,98 @@
+#include "../controller.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+#include "mongodb.h"
+#include "cursor.h"
+
+namespace flame::mongodb {
+
+    void _connection_base::fake_deleter(bson_t *doc) {}
+
+    php::value _connection_base::exec(std::shared_ptr<mongoc_client_t> conn
+        , php::array& pcmd, int type, coroutine_handler &ch
+        , std::shared_ptr<mongoc_client_session_t> session) {
+
+        std::shared_ptr<bson_t> cmd = array2bson(pcmd);
+        // 此处不能使用 bson_new 创建 bson_t 否则会导致内存泄漏
+        //（实际对 reply 的使用流程会重新初始化 reply 导致其值被至于 stack 上，导致此处 heap 内存泄漏）
+        bson_t reply, option = BSON_INITIALIZER;
+        std::shared_ptr<bson_t> rep(&reply, bson_destroy), opt(&option, bson_destroy);
+        std::shared_ptr<bson_error_t> err = std::make_shared<bson_error_t>();
+        std::uint32_t server_id;
+        int  rok = 0;
+
+        boost::asio::post(gcontroller->context_y, [&session, conn, type, &cmd, &rep, &opt, &err, &rok, &ch] () {
+            if (!session) {
+                session.reset(mongoc_client_start_session(conn.get(), nullptr, nullptr)
+                    , mongoc_client_session_destroy);
+                mongoc_client_session_append(session.get(), opt.get(), nullptr);
+            }
+
+            mongoc_server_description_t* server;
+            switch(type) {
+            case COMMAND_READ:
+                server = mongoc_client_select_server(conn.get(), false, nullptr, nullptr);
+                break;
+            case COMMAND_RAW:
+            case COMMAND_WRITE:
+            case COMMAND_READ_WRITE:
+            default:
+                server = mongoc_client_select_server(conn.get(), true, nullptr, nullptr);
+            }
+            uint32_t server_id = mongoc_server_description_id(server);
+            mongoc_server_description_destroy(server);
+            bson_append_int32(opt.get(), "serverId", 8, server_id);
+            switch(type) {
+            case COMMAND_READ:
+                rok = mongoc_client_read_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_WRITE:
+                rok = mongoc_client_write_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_READ_WRITE:
+                rok = mongoc_client_read_write_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+                break;
+            case COMMAND_RAW:
+            default:
+                rok = mongoc_client_command_with_opts(conn.get()
+                    , mongoc_uri_get_database(mongoc_client_get_uri(conn.get())), cmd.get(), nullptr, opt.get(), rep.get(), err.get());
+            }
+            ch.resume();
+        });
+        ch.suspend();
+        if (!rok) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to execute MongoDB command: %s") % err->message).str()
+            , err->code);
+        else if (bson_has_field(rep.get(), "cursor")) {
+            php::object obj{php::class_entry<cursor>::entry()};
+            auto ptr = static_cast<cursor *>(php::native(obj));
+            ptr->cl_.reset(new _connection_lock(conn));
+            ptr->ss_ = session;
+            ptr->cs_.reset(
+                mongoc_cursor_new_from_command_reply_with_opts(conn.get(), rep.get(), opt.get()),
+                mongoc_cursor_destroy);
+            // cursor 实际会窃取 rep 对应的 bson_t 结构;
+            // 按文档说会被上面函数 "销毁"
+            // 参考: http://mongoc.org/libmongoc/current/mongoc_cursor_new_from_command_reply_with_opts.html
+            *std::get_deleter<void (*)(bson_t*)>(rep) = fake_deleter;
+            return std::move(obj);
+        }
+        else {
+            php::array r(4);
+
+            bson_iter_t i;
+            bson_oid_t oid;
+            bson_iter_init(&i, rep.get());
+            while (bson_iter_next(&i)) {
+                const char* key = bson_iter_key(&i);
+                std::uint32_t len = bson_iter_key_len(&i);
+                if (len > 0 && key[0] == '$') continue;
+                r.set(php::string(key, len), iter2value(&i));
+            }
+            return std::move(r);
+        }
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/_connection_base.h b/src/flame/mongodb/_connection_base.h
new file mode 100644
index 0000000..28488da
--- /dev/null
+++ b/src/flame/mongodb/_connection_base.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+
+    class _connection_base {
+    public:
+        enum command_type_t {
+            COMMAND_RAW,
+            COMMAND_WRITE,
+            COMMAND_READ,
+            COMMAND_READ_WRITE,
+        };
+        virtual std::shared_ptr<mongoc_client_t> acquire(coroutine_handler& ch) = 0;
+        php::value exec(std::shared_ptr<mongoc_client_t> conn
+            , php::array& cmd, int type, coroutine_handler& ch
+            , std::shared_ptr<mongoc_client_session_t> session = nullptr);
+
+        static void fake_deleter(bson_t *doc);
+    };
+}
diff --git a/src/flame/mongodb/_connection_lock.cpp b/src/flame/mongodb/_connection_lock.cpp
new file mode 100644
index 0000000..88fada7
--- /dev/null
+++ b/src/flame/mongodb/_connection_lock.cpp
@@ -0,0 +1,34 @@
+#include "../controller.h"
+#include "_connection_lock.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+    _connection_lock::_connection_lock(std::shared_ptr<mongoc_client_t> c)
+    : conn_(c)
+    , guard_(gcontroller->context_y) {
+    }
+
+    _connection_lock::~_connection_lock() {
+    }
+    
+    std::shared_ptr<mongoc_client_t> _connection_lock::acquire(coroutine_handler &ch) {
+        return conn_;
+    }
+
+    php::array _connection_lock::fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch) {
+        bool has = false;
+        auto err = std::make_shared<bson_error_t>();
+        const bson_t *doc;
+        boost::asio::post(guard_, [this, &cs, &ch, &has, &err, &doc]() {
+            if (!mongoc_cursor_next(cs.get(), &doc)) has = mongoc_cursor_error(cs.get(), err.get());
+            ch.resume();
+        });
+        ch.suspend();
+        // 发生了错误
+        if (has) throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to fetch document: %s") % err->message).str()
+                    , err->code);
+        // 文档 doc 仅为 "引用" 流程
+        return bson2array(const_cast<bson_t*>(doc));
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/_connection_lock.h b/src/flame/mongodb/_connection_lock.h
new file mode 100644
index 0000000..a10db40
--- /dev/null
+++ b/src/flame/mongodb/_connection_lock.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+#include "_connection_base.h"
+
+namespace flame::mongodb {
+
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
+    public:
+        _connection_lock(std::shared_ptr<mongoc_client_t> c);
+        ~_connection_lock();
+        std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
+        php::array fetch(std::shared_ptr<mongoc_cursor_t> cs, coroutine_handler &ch);
+    private:
+        boost::asio::io_context::strand guard_; // 防止对 cursor 的并行访问
+        std::shared_ptr<mongoc_client_t> conn_;
+    };
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/_connection_pool.cpp b/src/flame/mongodb/_connection_pool.cpp
new file mode 100644
index 0000000..922af34
--- /dev/null
+++ b/src/flame/mongodb/_connection_pool.cpp
@@ -0,0 +1,52 @@
+#include "../controller.h"
+#include "mongodb.h"
+#include "_connection_pool.h"
+
+namespace flame::mongodb {
+    
+    _connection_pool::_connection_pool(const std::string& url)
+    : guard_(gcontroller->context_y) {
+        std::unique_ptr<mongoc_uri_t, void (*)(mongoc_uri_t *)> uri(mongoc_uri_new(url.c_str()), mongoc_uri_destroy);
+        const bson_t* options = mongoc_uri_get_options(uri.get());
+        if (!bson_has_field(options, MONGOC_URI_READPREFERENCE)) {
+            mongoc_read_prefs_t* pref = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); // secondaryPreferred
+            mongoc_uri_set_read_prefs_t(uri.get(), pref);
+            mongoc_read_prefs_destroy(pref);
+        }
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_CONNECTTIMEOUTMS, 5000);
+        mongoc_uri_set_option_as_int32(uri.get(), MONGOC_URI_MAXPOOLSIZE, 6);
+
+        p_ = mongoc_client_pool_new(uri.get());
+    }
+
+    _connection_pool::~_connection_pool() {
+        mongoc_client_pool_destroy(p_);
+    }
+
+    std::shared_ptr<mongoc_client_t> _connection_pool::acquire(coroutine_handler &ch) {
+        std::shared_ptr<mongoc_client_t> conn;
+        auto self = shared_from_this();
+        boost::asio::post(guard_, [this, self, &conn, &ch] () {
+            await_.push_back([this, self, &conn, &ch] (mongoc_client_t* c) {
+                // 对应释放(归还)连接过程, 须持有当前对象的引用 self (否则当前对象可能先于连接释放被销毁)
+                conn.reset(c, [this, self] (mongoc_client_t* c) {
+                    boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+                });
+                ch.resume();
+            });
+            mongoc_client_t* c = mongoc_client_pool_try_pop(p_);
+            if (c) release(c);
+        });
+        ch.suspend();
+        return conn;
+    }
+
+    void _connection_pool::release(mongoc_client_t* c) {
+        if (await_.empty()) mongoc_client_pool_push(p_, c);
+        else {
+            auto cb = await_.front();
+            await_.pop_front();
+            cb(c);
+        }
+    }
+}
diff --git a/src/flame/mongodb/_connection_pool.h b/src/flame/mongodb/_connection_pool.h
new file mode 100644
index 0000000..89c1978
--- /dev/null
+++ b/src/flame/mongodb/_connection_pool.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+#include "_connection_base.h"
+
+namespace flame::mongodb {
+
+    class _connection_pool: public _connection_base, public std::enable_shared_from_this<_connection_pool> {
+    public:
+        _connection_pool(const std::string& url);
+        ~_connection_pool();
+        std::shared_ptr<mongoc_client_t> acquire(coroutine_handler &ch) override;
+        void release(mongoc_client_t* c);
+    private:
+        mongoc_client_pool_t* p_;
+        boost::asio::io_context::strand guard_; // 防止下面队列并行访问
+        std::list<std::function<void(mongoc_client_t *)>> await_;
+    };
+}
diff --git a/src/flame/mongodb/client.cpp b/src/flame/mongodb/client.cpp
new file mode 100644
index 0000000..63532ae
--- /dev/null
+++ b/src/flame/mongodb/client.cpp
@@ -0,0 +1,67 @@
+#include "../coroutine.h"
+#include "mongodb.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "collection.h"
+
+namespace flame::mongodb {
+
+    void client::declare(php::extension_entry &ext) {
+        php::class_entry<client> class_client("flame\\mongodb\\client");
+        class_client
+            .constant({"COMMAND_RAW", _connection_base::COMMAND_RAW})
+            .constant({"COMMAND_READ", _connection_base::COMMAND_READ})
+            .constant({"COMMAND_READ_WRITE", _connection_base::COMMAND_READ_WRITE})
+            .constant({"COMMAND_WRITE", _connection_base::COMMAND_WRITE})
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::dump>("dump", {
+                {"data", php::TYPE::ARRAY},
+            })
+            .method<&client::execute>("execute", {
+                {"command", php::TYPE::ARRAY},
+                {"write", php::TYPE::INTEGER, false, true},
+            })
+            .method<&client::__get>("collection", {
+                {"name", php::TYPE::STRING}
+            })
+            .method<&client::__get>("__get", {
+                {"name", php::TYPE::STRING}
+            })
+            .method<&client::__isset>("__isset", {
+                {"name", php::TYPE::STRING}
+            });
+        ext.add(std::move(class_client));
+    }
+
+    php::value client::__construct(php::parameters &params) {
+        return nullptr;
+    }
+
+    php::value client::dump(php::parameters& params) {
+        std::clog << bson_as_relaxed_extended_json(array2bson(params[0]).get(), nullptr) << std::endl;
+        return nullptr;
+    }
+
+    php::value client::execute(php::parameters &params) {
+        int write = _connection_base::COMMAND_RAW;
+        if (params.size() > 1) write = params[1].to_integer();
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+
+        php::array cmd = params[0];
+        return cp_->exec(conn_, cmd, write, ch);
+    }
+
+    php::value client::__get(php::parameters &params) {
+        php::object obj(php::class_entry<collection>::entry());
+        collection *ptr = static_cast<collection *>(php::native(obj));
+        ptr->cp_ = cp_;
+        ptr->name_ = params[0];
+        obj.set("name", params[0]);
+        return std::move(obj);
+    }
+
+    php::value client::__isset(php::parameters &params) {
+        return true;
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/client.h b/src/flame/mongodb/client.h
new file mode 100644
index 0000000..4dd9693
--- /dev/null
+++ b/src/flame/mongodb/client.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+
+    class _connection_pool;
+    class client : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params);
+        php::value dump(php::parameters &params);
+        php::value execute(php::parameters &params);
+        php::value __get(php::parameters &params);
+        php::value __isset(php::parameters &params);
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/collection.cpp b/src/flame/mongodb/collection.cpp
new file mode 100644
index 0000000..4d75a92
--- /dev/null
+++ b/src/flame/mongodb/collection.cpp
@@ -0,0 +1,332 @@
+#include "../coroutine.h"
+#include "_connection_pool.h"
+#include "collection.h"
+#include "cursor.h"
+
+namespace flame::mongodb {
+    void collection::declare(php::extension_entry &ext) {
+        php::class_entry<collection> class_collection("flame\\mongodb\\collection");
+        class_collection
+            .property({"name", ""})
+            .method<&collection::__construct>("__construct", {}, php::PRIVATE)
+            .method<&collection::insert>("insert",{
+                {"data", php::TYPE::ARRAY},
+                {"ordered", php::TYPE::BOOLEAN, false, true} // true
+            })
+            .method<&collection::insert>("insert_many", {
+                {"data", php::TYPE::ARRAY},
+                {"ordered", php::TYPE::BOOLEAN, false, true} // true
+            })
+            .method<&collection::insert>("insert_one", {
+                {"data", php::TYPE::ARRAY},
+            })
+            .method<&collection::delete_>("delete", {
+                {"query", php::TYPE::ARRAY},
+                {"limit", php::TYPE::INTEGER, false, true}, // 0
+            })
+            .method<&collection::delete_>("delete_many", {
+                {"query", php::TYPE::ARRAY},
+                {"limit", php::TYPE::INTEGER, false, true}, // 0
+            })
+            .method<&collection::delete_>("delete_one", {
+                {"query", php::TYPE::ARRAY},
+            })
+            .method<&collection::update>("update", {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
+            .method<&collection::update>("update_many", {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
+            .method<&collection::update_one>("update_one", {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"upsert", php::TYPE::BOOLEAN, false, true}, // false
+            })
+            .method<&collection::find>("find", {
+                {"filter", php::TYPE::ARRAY},
+                {"projection", php::TYPE::ARRAY, false, true},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&collection::find_one>("one", {
+                {"filter", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::find_one>("find_one", { // 兼容原 API 名称定义
+                {"filter", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::get>("get", {
+                {"filter", php::TYPE::ARRAY},
+                {"field", php::TYPE::STRING},
+                {"sort", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::count>("count", {
+                {"query", php::TYPE::ARRAY},
+            })
+            .method<&collection::aggregate>("aggregate", {
+                {"pipeline", php::TYPE::ARRAY},
+            })
+            .method<&collection::find_and_delete>("find_and_delete", {
+                {"query", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"upsert", php::TYPE::BOOLEAN, false, true},
+                {"fields", php::TYPE::ARRAY, false, true},
+            })
+            .method<&collection::find_and_update>("find_and_update", {
+                {"query", php::TYPE::ARRAY},
+                {"update", php::TYPE::ARRAY},
+                {"sort", php::TYPE::ARRAY, false, true},
+                {"upsert", php::TYPE::BOOLEAN, false, true},
+                {"fields", php::TYPE::ARRAY, false, true},
+                {"new", php::TYPE::BOOLEAN, false, true},
+            });
+        ext.add(std::move(class_collection));
+    }
+
+    php::value collection::__construct(php::parameters &params) { // 私有
+        return nullptr;
+    }
+
+    php::value collection::insert(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("insert", name_);
+        php::array docs = params[0];
+        if (!docs.exists(0)) { // 单项插入的情况
+            docs = php::array(2);
+            docs.set(0, params[0]);
+        }
+        cmd.set("documents", docs);
+        if (params.size() > 1) cmd.set("ordered", params[1].to_boolean());
+
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+
+    php::value collection::delete_(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("delete", name_);
+        php::array deletes(2), deleteo(2);
+        deleteo.set("q", params[0]);
+        if (params.size() > 1 && params[1].type_of(php::TYPE::INTEGER))
+            deleteo.set("limit", params[1].to_integer());
+        else deleteo.set("limit", 0);
+        
+        deletes.set(0, deleteo);
+        cmd.set("deletes", deletes);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+
+    php::value collection::delete_one(php::parameters& params) {
+        return call("delete", {params[0], 1});
+    }
+
+    php::value collection::update(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("update", name_);
+        php::array updates(2), updateo(4);
+        updateo.set("q", params[0]);
+        updateo.set("u", params[1]);
+        if (params.size() > 2)
+            updateo.set("upsert", params[2].to_boolean());
+
+        updateo.set("multi", true);
+        updates.set(0, updateo);
+        cmd.set("updates", updates);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+
+    php::value collection::update_one(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("update", name_);
+        php::array updates(2), updateo(4);
+        updateo.set("q", params[0]);
+        updateo.set("u", params[1]);
+        if (params.size() > 2)
+            updateo.set("upsert", params[2].to_boolean());
+
+        // updateo.set("multi", false);
+        updates.set(0, updateo);
+        cmd.set("updates", updates);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_WRITE, ch);
+    }
+
+    php::value collection::find(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        if(params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
+
+        if (params.size() > 1) {
+            php::array project;
+            if (params[1].type_of(php::TYPE::ARRAY)) {
+                php::array fields = params[1];
+                if (fields.exists(0)) { // 将纯字段列表形式转换为K/V形式
+                    project = php::array(8);
+                    for (auto i = fields.begin(); i != fields.end(); ++i)
+                        project.set(php::string(i->second), 1);
+                }
+                else project = fields;
+            }
+            else if (params[1].type_of(php::TYPE::STRING)) { // 单个字段
+                php::array project(2);
+                project.set(php::string(params[1]), 1);
+            }
+            else goto PROJECTION_ALL;
+
+            if (!project.exists("_id")) project.set("_id", 0);
+            cmd.set("projection", project);
+        }
+    PROJECTION_ALL:
+        if (params.size() > 2 && params[2].type_of(php::TYPE::ARRAY))
+            cmd.set("sort", params[2]);
+        if (params.size() > 3) {
+            if (params[3].type_of(php::TYPE::INTEGER))
+                cmd.set("limit", params[3]);
+            else if (params[3].type_of(php::TYPE::ARRAY)) {
+                php::array limits = params[3];
+                if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
+                if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
+            }
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+    }
+
+    php::value collection::find_one(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        if(params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
+
+        if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY))
+            cmd.set("sort", params[1]);
+
+        cmd.set("limit", 1);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::object cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+        assert(cs.instanceof(php::class_entry<cursor>::entry()));
+        return cs.call("fetch_row");
+    }
+
+    php::value collection::get(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("find", name_);
+        php::array project(2);
+        php::string field = params[1];
+        project.set(field, 1);
+        cmd.set("projection", project);
+        if (params[0].empty()) cmd.set("filter", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("filter", params[0]);
+        if (params.size() > 2 && params[2].type_of(php::TYPE::ARRAY))
+            cmd.set("sort", params[2]);
+        cmd.set("limit", 1);
+
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::object cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+        assert(cs.instanceof (php::class_entry<cursor>::entry()));
+        php::array row = cs.call("fetch_row");
+        if (row.empty()) return nullptr;
+        else return row.get(field);
+    }
+
+    php::value collection::count(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("count", name_);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
+        cmd.set("query", params[0]);
+        if (params.size() > 1) {
+            if (params[1].type_of(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
+            else if (params[1].type_of(php::TYPE::ARRAY)) {
+                php::array limits = params[1];
+                if (limits.size() > 0) cmd.set("skip", limits[0].to_integer());
+                if (limits.size() > 1) cmd.set("limit", limits[1].to_integer());
+            }
+        }
+        if (params.size() > 1 && params[1].type_of(php::TYPE::INTEGER)) cmd.set("limit", params[1]);
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::array cs = cp_->exec(conn_, cmd, _connection_base::COMMAND_READ, ch);
+        return cs.get("n");
+    }
+
+    php::value collection::aggregate(php::parameters &params) {
+        php::array cmd(8);
+        cmd.set("aggregate", name_);
+        php::array pipeline = params[0];
+        if (!pipeline.exists(0)) throw php::exception(zend_ce_type_error
+            , "Failed to aggregate: 'pipeline' typeof array required"
+            , -1);
+        cmd.set("pipeline", pipeline);
+        cmd.set("cursor", php::object(php::CLASS(zend_standard_class_def)));
+        if (params.size() > 1) {
+            php::array options = params[1];
+            for(auto i=options.begin(); i!=options.end(); ++i)
+                cmd.set(i->first.to_string(), i->second);
+        }
+        int execute_type = _connection_base::COMMAND_READ;
+        if (params.size() > 2)
+            execute_type = params[2].to_integer();
+
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, execute_type, ch);
+    }
+
+    php::value collection::find_and_delete(php::parameters& params) {
+        php::array cmd(8);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("query", params[0]);
+        cmd.set("remove", true);
+        if (params.size() > 1) {
+            php::value f = params[1];
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("sort", f);
+        }
+        if (params.size() > 2) cmd.set("upsert", params[2].to_boolean());
+        if (params.size() > 3) {
+            php::value f = params[3];
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("fields", f);
+        }
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
+    }
+
+    php::value collection::find_and_update(php::parameters& params) {
+        php::array cmd(8);
+        if (params[0].empty()) cmd.set("query", php::object(php::CLASS(zend_standard_class_def)));
+        else cmd.set("query", params[0]);
+        if (params.size() > 1) {
+            php::value f = params[1];
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("update", f);
+        }
+        if (params.size() > 2) {
+            php::value f = params[2];
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("sort", f);
+        }
+        if (params.size() > 3) cmd.set("upsert", params[3].to_boolean());
+        if (params.size() > 4) {
+            php::value f = params[4];
+            if (f.type_of(php::TYPE::ARRAY)) cmd.set("fields", f);
+        }
+        if (params.size() > 5) cmd.set("new", params[5].to_boolean());
+
+        coroutine_handler ch {coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        return cp_->exec(conn_, cmd, _connection_base::COMMAND_READ_WRITE, ch);
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/collection.h b/src/flame/mongodb/collection.h
new file mode 100644
index 0000000..916c291
--- /dev/null
+++ b/src/flame/mongodb/collection.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+
+    class _connection_pool;
+    class collection : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params);
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value delete_one(php::parameters& params);
+        php::value update(php::parameters &params);
+        php::value update_one(php::parameters& params);
+        php::value find(php::parameters &params);
+        php::value find_one(php::parameters &params);
+        php::value get(php::parameters &params);
+        php::value count(php::parameters &params);
+        php::value aggregate(php::parameters &params);
+        php::value find_and_update(php::parameters& params);
+        php::value find_and_delete(php::parameters& params);
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        php::string name_;
+        friend class client;
+    };
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/cursor.cpp b/src/flame/mongodb/cursor.cpp
new file mode 100644
index 0000000..b57de08
--- /dev/null
+++ b/src/flame/mongodb/cursor.cpp
@@ -0,0 +1,46 @@
+#include "../coroutine.h"
+#include "cursor.h"
+#include "_connection_lock.h"
+
+namespace flame::mongodb {
+
+    void cursor::declare(php::extension_entry &ext) {
+        php::class_entry<cursor> class_cursor("flame\\mongodb\\cursor");
+        class_cursor
+            .method<&cursor::__construct>("__construct", {}, php::PRIVATE)
+            .method<&cursor::__destruct>("__destruct")
+            .method<&cursor::fetch_row>("fetch_row")
+            .method<&cursor::fetch_all>("fetch_all");
+        ext.add(std::move(class_cursor));
+    }
+
+    php::value cursor::fetch_row(php::parameters &params) {
+        php::array row(nullptr);
+        if (cs_ && cl_) {
+            coroutine_handler ch{coroutine::current};
+            row = cl_->fetch(cs_, ch);
+        }
+        if (row.type_of(php::TYPE::NULLABLE)) {
+            // 尽早释放连接 (注意顺序)
+            cs_.reset();
+            ss_.reset();
+            cl_.reset();
+        }
+        return row;
+    }
+
+    php::value cursor::fetch_all(php::parameters &params) {
+        if (cs_ && cl_) {
+            coroutine_handler ch{coroutine::current};
+            php::array data(8);
+            for(php::array row = cl_->fetch(cs_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(cs_, ch))
+                data.set(data.size(), row);
+            // 尽早释放连接 (注意顺序)
+            cs_.reset();
+            ss_.reset();
+            cl_.reset();
+            return data;
+        }
+        else return nullptr;
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/cursor.h b/src/flame/mongodb/cursor.h
new file mode 100644
index 0000000..d7a995c
--- /dev/null
+++ b/src/flame/mongodb/cursor.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+    class _connection_lock;
+    class cursor: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params) { // 私有
+            return nullptr;
+        }
+        php::value __destruct(php::parameters& params) {
+            return nullptr;
+        }
+        php::value fetch_row(php::parameters& params);
+        php::value fetch_all(php::parameters& params);
+    private:
+        // 须先销毁 指针 后归还 连接
+        std::shared_ptr<_connection_lock> cl_;
+        std::shared_ptr<mongoc_client_session_t> ss_;
+        std::shared_ptr<mongoc_cursor_t>  cs_;
+        friend class _connection_base;
+    };
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/date_time.cpp b/src/flame/mongodb/date_time.cpp
new file mode 100644
index 0000000..25e35bc
--- /dev/null
+++ b/src/flame/mongodb/date_time.cpp
@@ -0,0 +1,58 @@
+#include "date_time.h"
+#include "../time/time.h"
+
+namespace flame::mongodb {
+
+    void date_time::declare(php::extension_entry& ext) {
+        php::class_entry<date_time> class_date_time("flame\\mongodb\\date_time");
+        class_date_time
+            .implements(&php_json_serializable_ce)
+            .method<&date_time::__construct>("__construct", {
+                {"milliseconds", php::TYPE::INTEGER, false, true}
+            })
+            .method<&date_time::to_string>("__toString")
+            .method<&date_time::to_json>("__toJSON")
+            .method<&date_time::to_datetime>("__toDateTime")
+            .method<&date_time::unix>("unix")
+            .method<&date_time::unix_ms>("unix_ms")
+            .method<&date_time::to_json>("jsonSerialize")
+            .method<&date_time::to_json>("__debugInfo")
+            .method<&date_time::iso>("iso");
+        ext.add(std::move(class_date_time));
+    }
+
+    php::value date_time::__construct(php::parameters& params) {
+        if (params.size() > 0) tm_ = params[0].to_integer();
+        else // 默认以当前时间建立
+            tm_ = std::chrono::duration_cast<std::chrono::milliseconds>(time::now().time_since_epoch()).count();
+        return nullptr;
+    }
+
+    php::value date_time::to_string(php::parameters& params) {
+        return std::to_string(tm_);
+    }
+
+    php::value date_time::unix(php::parameters& params) {
+        return std::int64_t(tm_ / 1000);
+    }
+
+    php::value date_time::unix_ms(php::parameters& params) {
+        return tm_;
+    }
+
+    php::value date_time::to_datetime(php::parameters& params) {
+        return php::datetime(tm_);
+    }
+
+    php::value date_time::to_json(php::parameters& params) {
+        // 自定义 JSON 形式 (保持和官方 MongoDB 驱动形式一致)
+        php::array ret(1), num(1);
+        num.set("$numberLong", std::to_string(tm_));
+        ret.set("$date", num);
+        return std::move(ret);
+    }
+    
+    php::value date_time::iso(php::parameters& params) {
+        return time::iso(std::chrono::system_clock::time_point( std::chrono::milliseconds(tm_) ));
+    }
+}
diff --git a/src/flame/mongodb/date_time.h b/src/flame/mongodb/date_time.h
new file mode 100644
index 0000000..5b75005
--- /dev/null
+++ b/src/flame/mongodb/date_time.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+    
+    class date_time: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value to_string(php::parameters& params);
+        php::value unix(php::parameters& params);
+        php::value unix_ms(php::parameters& params);
+        php::value to_datetime(php::parameters& params);
+        php::value to_json(php::parameters& params);
+        php::value iso(php::parameters& params);
+    private:
+        std::int64_t tm_;
+        friend php::value iter2value(bson_iter_t *i);
+        friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+    };
+}
diff --git a/src/flame/mongodb/mongodb.cpp b/src/flame/mongodb/mongodb.cpp
new file mode 100644
index 0000000..462679c
--- /dev/null
+++ b/src/flame/mongodb/mongodb.cpp
@@ -0,0 +1,159 @@
+#include "../coroutine.h"
+#include "mongodb.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "cursor.h"
+#include "collection.h"
+#include "object_id.h"
+#include "date_time.h"
+
+namespace flame::mongodb {
+
+    void declare(php::extension_entry &ext) {
+        gcontroller->on_init([] (const php::array& options) {
+                mongoc_init();
+            })->on_stop([]() {
+                mongoc_cleanup();
+            });
+        ext
+            .function<connect>("flame\\mongodb\\connect");
+        client::declare(ext);
+        object_id::declare(ext);
+        date_time::declare(ext);
+        cursor::declare(ext);
+        collection::declare(ext);
+    }
+
+    php::value connect(php::parameters &params) {
+        php::object obj(php::class_entry<client>::entry());
+        client *ptr = static_cast<client *>(php::native(obj));
+        std::string url = params[0];
+        ptr->cp_.reset(new _connection_pool(url));
+
+        // TODO 优化: 实际确认建立第一个连接?
+        return std::move(obj);
+    }
+
+    php::value iter2value(bson_iter_t *i) {
+        switch (bson_iter_type(i)) {
+        case BSON_TYPE_DOUBLE:
+            return bson_iter_double(i);
+        case BSON_TYPE_UTF8: {
+            std::uint32_t size = 0;
+            const char *data = bson_iter_utf8(i, &size);
+            return php::string(data, size);
+        }
+        case BSON_TYPE_DOCUMENT:
+        case BSON_TYPE_ARRAY: {
+            bson_iter_t j;
+            bson_iter_recurse(i, &j);
+            php::array a(4);
+            while (bson_iter_next(&j)) a.set(bson_iter_key(&j), iter2value(&j));
+            return std::move(a);
+        }
+        case BSON_TYPE_BINARY: {
+            std::uint32_t size = 0;
+            const unsigned char *data;
+            bson_iter_binary(i, nullptr, &size, &data);
+            return php::string((const char *)data, size);
+        }
+        case BSON_TYPE_OID: {
+            php::object o(php::class_entry<object_id>::entry());
+            object_id *o_ = static_cast<object_id *>(php::native(o));
+            bson_oid_copy(bson_iter_oid(i), &o_->oid_);
+            return std::move(o);
+        }
+        case BSON_TYPE_BOOL:
+            return bson_iter_bool(i);
+        case BSON_TYPE_DATE_TIME: {
+            php::object o(php::class_entry<date_time>::entry());
+            date_time *o_ = static_cast<date_time *>(php::native(o));
+            o_->tm_ = bson_iter_date_time(i);
+            return o;
+        }
+        case BSON_TYPE_INT32:
+            return bson_iter_int32(i);
+        case BSON_TYPE_INT64:
+            return bson_iter_int64(i);
+        default:
+            return nullptr;
+        }
+    }
+
+    php::array bson2array(std::shared_ptr<bson_t> v) {
+        return bson2array(v.get());
+    }
+
+    php::array bson2array(bson_t* v) {
+        if (v == nullptr) return nullptr;
+
+        php::array doc(4);
+
+        bson_iter_t i;
+        bson_oid_t oid;
+        bson_iter_init(&i, v);
+        while (bson_iter_next(&i)) doc.set(bson_iter_key(&i), iter2value(&i));
+        return std::move(doc);
+    }
+
+    std::shared_ptr<bson_t> array2bson(const php::array &v) {
+        assert(v.type_of(php::TYPE::ARRAY));
+        bson_t *doc = bson_new();
+        for (auto i = v.begin(); i != v.end(); ++i) {
+            php::string key = i->first;
+            key.to_string();
+            php::value val = i->second;
+            switch (Z_TYPE_P(static_cast<zval *>(val))) {
+            case IS_UNDEF:
+                break;
+            case IS_NULL:
+                bson_append_null(doc, key.c_str(), key.size());
+                break;
+            case IS_TRUE:
+                bson_append_bool(doc, key.c_str(), key.size(), true);
+                break;
+            case IS_FALSE:
+                bson_append_bool(doc, key.c_str(), key.size(), false);
+                break;
+            case IS_LONG:
+                bson_append_int64(doc, key.c_str(), key.size(), static_cast<std::int64_t>(val));
+                break;
+            case IS_DOUBLE:
+                bson_append_double(doc, key.c_str(), key.size(), static_cast<double>(val));
+                break;
+            case IS_STRING: {
+                php::string str = val;
+                bson_append_utf8(doc, key.c_str(), key.size(), str.c_str(), str.size());
+                break;
+            }
+            case IS_ARRAY: {
+                auto a = array2bson(val);
+                if (bson_has_field(a.get(), "0") || bson_count_keys(a.get()) == 0)
+                    bson_append_array(doc, key.c_str(), key.size(), a.get());
+                else
+                    bson_append_document(doc, key.c_str(), key.size(), a.get());
+                break;
+            }
+            case IS_OBJECT: {
+                php::object o = val;
+                if (o.instanceof (php_date_get_date_ce())) // PHP 内置的 DateTime 类型
+                    bson_append_date_time(doc, key.c_str(), key.size(), static_cast<std::int64_t>(o.call("getTimestamp")) * 1000);
+                else if (o.instanceof (php::class_entry<date_time>::entry())) {
+                    date_time *o_ = static_cast<date_time *>(php::native(o));
+                    bson_append_date_time(doc, key.c_str(), key.size(), o_->tm_);
+                }
+                else if (o.instanceof (php::class_entry<object_id>::entry())) {
+                    object_id *o_ = static_cast<object_id *>(php::native(o));
+                    bson_append_oid(doc, key.c_str(), key.size(), &o_->oid_);
+                }
+                else {
+                    bson_t uninitialized;
+                    bson_append_document_begin(doc, key.c_str(), key.size(), &uninitialized);
+                    bson_append_document_end(doc, &uninitialized);
+                }
+            }
+            }
+        }
+        return std::shared_ptr<bson_t>(doc, bson_destroy);
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/mongodb.h b/src/flame/mongodb/mongodb.h
new file mode 100644
index 0000000..018631c
--- /dev/null
+++ b/src/flame/mongodb/mongodb.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "../../vendor.h"
+#include <mongoc.h>
+
+namespace flame::mongodb {
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+    php::value iter2value(bson_iter_t *i);
+    php::array bson2array(std::shared_ptr<bson_t> v);
+    php::array bson2array(bson_t* v);
+    std::shared_ptr<bson_t> array2bson(const php::array &v);
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/object_id.cpp b/src/flame/mongodb/object_id.cpp
new file mode 100644
index 0000000..2c8478b
--- /dev/null
+++ b/src/flame/mongodb/object_id.cpp
@@ -0,0 +1,68 @@
+#include "object_id.h"
+
+namespace flame::mongodb {
+
+    void object_id::declare(php::extension_entry &ext) {
+        php::class_entry<object_id> class_object_id("flame\\mongodb\\object_id");
+        class_object_id
+            .implements(&php_json_serializable_ce)
+            .method<&object_id::__construct>("__construct")
+            .method<&object_id::to_string>("__toString")
+            .method<&object_id::to_json>("__toJSON")
+            .method<&object_id::to_datetime>("__toDateTime")
+            .method<&object_id::unix>("unix")
+            .method<&object_id::to_json>("jsonSerialize")
+            .method<&object_id::to_json>("__debugInfo")
+            .method<&object_id::equal>("equal",{
+                {"object_id", "?flame\\mongodb\\object_id"}
+            });
+        ext.add(std::move(class_object_id));
+    }
+
+    php::value object_id::__construct(php::parameters &params) {
+        if (params.size() > 0 && params[0].type_of(php::TYPE::STRING)) {
+            php::string oid = params[0].to_string();
+            bson_oid_init_from_string(&oid_, oid.c_str());
+        }
+        else bson_oid_init(&oid_, nullptr);
+        return nullptr;
+    }
+
+    php::value object_id::to_string(php::parameters &params) {
+        php::string str(24);
+        bson_oid_to_string(&oid_, str.data());
+        return std::move(str);
+    }
+
+    php::value object_id::unix(php::parameters &params) {
+        return bson_oid_get_time_t(&oid_);
+    }
+
+    php::value object_id::to_datetime(php::parameters &params) {
+        return php::datetime(bson_oid_get_time_t(&oid_) * 1000);
+    }
+
+    php::value object_id::to_json(php::parameters &params) {
+        // 定制 JSON 输出形式 (为何官方 MongoDB 驱动保持一致)
+        php::array oid(1);
+        php::string str(24);
+        bson_oid_to_string(&oid_, str.data());
+        oid.set("$oid", str);
+        return std::move(oid);
+    }
+
+    php::value object_id::equal(php::parameters &params) {
+        if (params[0].type_of(php::TYPE::STRING)) {
+            php::string data = params[0];
+            bson_oid_t oid;
+            bson_oid_init_from_string(&oid, data.c_str());
+            return bson_oid_equal(&oid_, &oid);
+        }
+        else if (params[0].instanceof (php::class_entry<object_id>::entry())) {
+            php::object obj = params[0];
+            object_id *ptr = static_cast<object_id *>(php::native(obj));
+            return bson_oid_equal(&oid_, &ptr->oid_);
+        }
+        else return false;
+    }
+} // namespace flame::mongodb
diff --git a/src/flame/mongodb/object_id.h b/src/flame/mongodb/object_id.h
new file mode 100644
index 0000000..3347d39
--- /dev/null
+++ b/src/flame/mongodb/object_id.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "../../vendor.h"
+#include "mongodb.h"
+
+namespace flame::mongodb {
+    
+    class object_id: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value to_string(php::parameters& params);
+        php::value unix(php::parameters& params);
+        php::value to_datetime(php::parameters& params);
+        php::value to_json(php::parameters& params);
+        php::value equal(php::parameters& params);
+    private:
+        bson_oid_t oid_;
+        friend php::value iter2value(bson_iter_t *i);
+        friend std::shared_ptr<bson_t> array2bson(const php::array &v);
+    };
+}
diff --git a/src/flame/mutex.cpp b/src/flame/mutex.cpp
new file mode 100644
index 0000000..646e044
--- /dev/null
+++ b/src/flame/mutex.cpp
@@ -0,0 +1,55 @@
+#include "coroutine.h"
+#include "mutex.h"
+#include "../coroutine_mutex.h"
+
+namespace flame {
+    void mutex::declare(php::extension_entry &ext) {
+        php::class_entry<mutex> class_mutex("flame\\mutex");
+        class_mutex
+            .method<&mutex::__construct>("__construct", {
+                {"n", php::TYPE::INTEGER, false, true},
+            })
+            .method<&mutex::lock>("lock")
+            .method<&mutex::unlock>("unlock");
+        ext.add(std::move(class_mutex));
+    }
+
+    php::value mutex::__construct(php::parameters& params) {
+        mutex_.reset(new coroutine_mutex());
+        return nullptr;
+    }
+
+    php::value mutex::lock(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        mutex_->lock(ch);
+        return nullptr;
+    }
+
+    php::value mutex::unlock(php::parameters& params) {
+        mutex_->unlock();
+        return nullptr;
+    }
+
+    void guard::declare(php::extension_entry &ext) {
+        php::class_entry<guard> class_guard("flame\\guard");
+        class_guard
+            .method<&guard::__construct>("__construct", {
+                {"mutex", "flame\\mutex"},
+            })
+            .method<&guard::__destruct>("__destruct");
+        ext.add(std::move(class_guard));
+    }
+
+    php::value guard::__construct(php::parameters & params) {
+        php::object obj = params[0];
+        mutex_ = static_cast<mutex*>(php::native(obj))->mutex_;
+        coroutine_handler ch {coroutine::current};
+        mutex_->lock(ch);
+        return nullptr;
+    }
+
+    php::value guard::__destruct(php::parameters & params) {
+        mutex_->unlock();
+        return nullptr;
+    }
+}
diff --git a/src/flame/mutex.h b/src/flame/mutex.h
new file mode 100644
index 0000000..2330b47
--- /dev/null
+++ b/src/flame/mutex.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../vendor.h"
+
+class coroutine_mutex;
+namespace flame {
+
+    class mutex: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value lock(php::parameters& params);
+        php::value unlock(php::parameters& params);
+
+    private:
+        std::shared_ptr<coroutine_mutex> mutex_;
+        friend class guard;
+    };
+
+    class guard: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters &params);
+    private:
+        std::shared_ptr<coroutine_mutex> mutex_;
+    };
+}
diff --git a/src/flame/mysql/_connection_base.cpp b/src/flame/mysql/_connection_base.cpp
new file mode 100644
index 0000000..c313f1c
--- /dev/null
+++ b/src/flame/mysql/_connection_base.cpp
@@ -0,0 +1,188 @@
+#include "../controller.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+#include "result.h"
+
+namespace flame::mysql {
+    
+    void _connection_base::escape(std::shared_ptr<MYSQL> conn, php::buffer &b, const php::value &v, char quote) {
+        switch (Z_TYPE_P(static_cast<zval *>(v))) {
+        case IS_NULL:
+            b.append("NULL", 4);
+            break;
+        case IS_TRUE:
+            b.append("TRUE", 4);
+            break;
+        case IS_FALSE:
+            b.append("FALSE", 5);
+            break;
+        // case IS_LONG:
+        // case IS_DOUBLE: {
+        //  php::string str = v;
+        //  str.to_string();
+        //  b.append(str);
+        //  break;
+        // }
+        case IS_STRING: {
+            // 支持字段名 aaa.bbb 进行 ESCAPE 变为 `aaa`.`bbb`
+            if (quote == '`') {
+                std::string str = v; // 这里进行拷贝（原字符串可能发生更改）
+                char *s = str.data(), *c, *e = str.data() + str.size();
+                char *t = b.prepare(str.size() * 2 + 2);
+                std::size_t n = 0;
+ESCAPE_REMAINING:
+                for (c = s; c < e; ++c) {
+                    if (*c == '`') *c = '\\';
+                    if (*c == '.') {
+                        t[n++] = quote;
+                        n += mysql_real_escape_string(conn.get(), t + n, s, c - s);
+                        t[n++] = quote;
+                        t[n++] = '.';
+                        s = c + 1;
+                        goto ESCAPE_REMAINING;
+                    }
+                }
+                t[n++] = quote;
+                n += mysql_real_escape_string(conn.get(), t + n, s, c - s);
+                t[n++] = quote;
+                b.commit(n);
+            } else {
+                php::string str = v;
+                char *t = b.prepare(str.size() * 2 + 2);
+                std::size_t n = 0;
+                t[n++] = quote;
+                n += mysql_real_escape_string(conn.get(), t + n, str.c_str(), str.size());
+                t[n++] = quote;
+                b.commit(n);
+            }
+            break;
+        }
+        case IS_ARRAY: {
+            php::array arr = v;
+            int index = 0;
+            b.push_back('(');
+            for (auto i = arr.begin(); i != arr.end(); ++i) {
+                if (++index > 1) b.push_back(',');
+                escape(conn, b, i->second, quote);
+            }
+            b.push_back(')');
+            break;
+        }
+        case IS_OBJECT: {
+            php::object obj = v;
+            php::string str;
+            if (obj.instanceof(php_date_get_date_ce()))  // DateTime 类型的 SQL 拼接
+                str = obj.call("format", {php::string("Y-m-d H:i:s")});
+            else str = obj.to_string();
+            escape(conn, b, str, quote);
+            break;
+        }
+        default: {
+            php::string str = v;
+            str.to_string();
+            escape(conn, b, str, quote);
+        }
+        }
+    }
+
+    php::object _connection_base::query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch) {
+        MYSQL_RES* rst = nullptr;
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [&err, &conn, &ch, &rst, &sql] () {
+            // 在工作线程执行查询
+            err = mysql_real_query(conn.get(), sql.c_str(), sql.size());
+            if (err == 0) {
+                // 防止锁表, 均使用 store 方式
+                rst = mysql_store_result(conn.get());
+                if (!rst && mysql_field_count(conn.get()) > 0) {
+                    err = -1;
+                }
+            }
+            ch.resume();
+        });
+        ch.suspend();
+        last_query_ = sql;
+        if (err != 0) {
+            int err = mysql_errno(conn.get());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to query MySQL server: %s") % mysql_error(conn.get())).str()
+                , err);
+        }
+        if (rst) { // 存在结果集
+            php::object obj(php::class_entry<result>::entry());
+            result* ptr = static_cast<result*>(php::native(obj));
+            ptr->cl_.reset(new _connection_lock(conn));
+            ptr->rs_.reset(rst, mysql_free_result);
+            ptr->f_ = mysql_fetch_fields(rst);
+            ptr->n_ = mysql_num_fields(rst);
+            obj.set("stored_rows", static_cast<std::int64_t>(mysql_num_rows(rst)));
+            return std::move(obj);
+        }
+        else { // 更新型操作
+            php::array data(2);
+            data.set(php::string("affected_rows", 13), static_cast<std::int64_t>(mysql_affected_rows(conn.get())));
+            data.set(php::string("insert_id", 9), static_cast<std::int64_t>(mysql_insert_id(conn.get())));
+            return std::move(data);
+        }
+    }
+
+    const std::string& _connection_base::last_query() {
+        return last_query_;
+    }
+
+    php::array _connection_base::fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst
+        , MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch) {
+
+        MYSQL_ROW row;
+        unsigned long* len;
+        boost::asio::post(gcontroller->context_y, [&rst, &ch, &row, &len] () {
+            row = mysql_fetch_row(rst.get());
+            if (row) len = mysql_fetch_lengths(rst.get());
+            ch.resume();
+        });
+        ch.suspend();
+        if (!row) {
+            int err = mysql_errno(conn.get());
+            if (err != 0)
+                throw php::exception(zend_ce_error
+                    , (boost::format("Failed to fetch MySQL row: %s") % mysql_error(conn.get())).str()
+                    , err);
+            else return nullptr;
+        }
+        php::array php_row {std::size_t(n)};
+        for(int i=0;i<n;++i) {
+            php::string field(f[i].name, f[i].name_length);
+            php::value  value;
+            if (row[i] == nullptr) {
+                value = nullptr;
+                php_row.set(field, value);
+                continue;
+            }
+            switch(f[i].type) {
+                case MYSQL_TYPE_DOUBLE:
+                case MYSQL_TYPE_FLOAT:
+                    value = std::strtod(row[i], nullptr);
+                    break;
+                case MYSQL_TYPE_TINY:
+                case MYSQL_TYPE_SHORT:
+                case MYSQL_TYPE_LONG:
+                case MYSQL_TYPE_INT24:
+                    value = std::strtol(row[i], nullptr, 10);
+                    break;
+                case MYSQL_TYPE_LONGLONG:
+                    value = static_cast<std::int64_t>(std::strtoll(row[i], nullptr, 10));
+                    break;
+                case MYSQL_TYPE_DATETIME: {
+                    php::object obj( php::CLASS{php_date_get_date_ce()} );
+                    obj.call("__construct", {php::string(row[i], len[i])});
+                    value = std::move(obj);
+                    break;
+                }
+                default:
+                    value = php::string(row[i], len[i]);
+            }
+            php_row.set(field, value);
+        }
+        return php_row;
+    }
+} // namespace flame::mysql
diff --git a/src/flame/mysql/_connection_base.h b/src/flame/mysql/_connection_base.h
new file mode 100644
index 0000000..b8a50ff
--- /dev/null
+++ b/src/flame/mysql/_connection_base.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+
+    class _connection_base {
+    public:
+        // 此函数仅允许 escape 主线程调用
+        static void escape(std::shared_ptr<MYSQL> c, php::buffer &b, const php::value &v, char quote = '\''); // 方便使用
+        virtual std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) = 0;
+        php::object query(std::shared_ptr<MYSQL> conn, std::string sql, coroutine_handler& ch);
+        php::array fetch(std::shared_ptr<MYSQL> conn, std::shared_ptr<MYSQL_RES> rst, MYSQL_FIELD *f, unsigned int n, coroutine_handler &ch);
+        const std::string& last_query();
+    protected:
+    private:
+        std::string last_query_;
+    };
+} // namespace flame::mysql
diff --git a/src/flame/mysql/_connection_lock.cpp b/src/flame/mysql/_connection_lock.cpp
new file mode 100644
index 0000000..8453607
--- /dev/null
+++ b/src/flame/mysql/_connection_lock.cpp
@@ -0,0 +1,63 @@
+#include "../coroutine.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+
+namespace flame::mysql {
+
+    _connection_lock::_connection_lock(std::shared_ptr<MYSQL> c)
+    : conn_(c) {
+    }
+
+    _connection_lock::~_connection_lock() {
+    }
+
+    std::shared_ptr<MYSQL> _connection_lock::acquire(coroutine_handler &ch) {
+        return conn_;
+    }
+
+    void _connection_lock::begin_tx(coroutine_handler &ch) {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "START TRANSACTION", 17);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != 0) {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to start transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
+        }
+    }
+
+    void _connection_lock::commit(coroutine_handler &ch) {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "COMMIT", 6);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != 0) {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_exception
+                , (boost::format("failed to commit transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
+        }
+    }
+
+    void _connection_lock::rollback(coroutine_handler &ch) {
+        int err = 0;
+        boost::asio::post(gcontroller->context_y, [this, &ch, &err]() {
+            err = mysql_real_query(conn_.get(), "ROLLBACK", 8);
+            ch.resume();
+        });
+        ch.suspend();
+        if (err != 0) {
+            err = mysql_errno(conn_.get());
+            throw php::exception(zend_ce_error
+                , (boost::format("failed to rollback transaction: %s") % mysql_error(conn_.get())).str()
+                , err);
+        }
+    }
+
+} // namespace flame::mysql
diff --git a/src/flame/mysql/_connection_lock.h b/src/flame/mysql/_connection_lock.h
new file mode 100644
index 0000000..cec27a7
--- /dev/null
+++ b/src/flame/mysql/_connection_lock.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "_connection_base.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
+    public:
+        _connection_lock(std::shared_ptr<MYSQL> c);
+        ~_connection_lock();
+        std::shared_ptr<MYSQL> acquire(coroutine_handler& ch) override;
+        void begin_tx(coroutine_handler& ch);
+        void commit(coroutine_handler& ch);
+        void rollback(coroutine_handler& ch);
+      private:
+        std::shared_ptr<MYSQL> conn_;
+    };
+} // namespace flame::mysql
diff --git a/src/flame/mysql/_connection_pool.cpp b/src/flame/mysql/_connection_pool.cpp
new file mode 100644
index 0000000..d9d94ae
--- /dev/null
+++ b/src/flame/mysql/_connection_pool.cpp
@@ -0,0 +1,160 @@
+#include "../controller.h"
+#include "_connection_pool.h"
+
+namespace flame::mysql {
+    _connection_pool::_connection_pool(url u)
+    : url_(std::move(u)), min_(2), max_(6), size_(0), guard_(gcontroller->context_y), tm_(gcontroller->context_y)
+    , flag_(FLAG_UNKNOWN) {
+        if(url_.query.count("proxy") > 0 && std::stoi(url_.query["proxy"]) != 0)
+            flag_ =  FLAG_CHARSET_EQUAL | FLAG_REUSE_BY_PROXY;
+    }
+
+    _connection_pool::~_connection_pool() {
+        while (!conn_.empty()) {
+            mysql_close(conn_.front().conn);
+            conn_.pop_front();
+        }
+    }
+
+    std::shared_ptr<MYSQL> _connection_pool::acquire(coroutine_handler& ch) {
+        std::shared_ptr<MYSQL> conn;
+        int         errnum = 0;
+        std::string errmsg;
+        // 提交异步任务
+        boost::asio::post(guard_, [this, &errnum, &errmsg, &conn, &ch] () {
+            // 设置对应的回调, 在获取连接后恢复协程
+            await_.push_back([&conn, &ch] (std::shared_ptr<MYSQL> cc) {
+                conn = cc;
+                // RESUME 需要在主线程进行
+                ch.resume();
+            });
+            auto now = std::chrono::steady_clock::now();
+            while (!conn_.empty()) {
+                if (now - conn_.front().ttl < std::chrono::seconds(15)
+                    || mysql_ping(conn_.front().conn) == 0) {  // 可用连接
+
+                    MYSQL* c = conn_.front().conn;
+                    conn_.pop_front();
+                    release(c);
+                    return;
+                }
+                else { // 连接已丢失，回收资源
+                    mysql_close(conn_.front().conn);
+                    conn_.pop_front();
+                    --size_;
+                }
+            }
+            if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
+            MYSQL* c = mysql_init(nullptr);
+            init_options(c);
+            if (mysql_real_connect(c, url_.host.c_str(), url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1, url_.port, nullptr, 0)) {
+                if (flag_ & FLAG_REUSE_BY_PROXY) set_names(c);
+                ++size_; // 当前还存在的连接数量
+                release(c);
+            }
+            else {
+                errnum = mysql_errno(c);
+                errmsg = mysql_error(c);
+                mysql_close(c);
+                await_.pop_back();
+                ch.resume();
+            }
+        });
+        // 暂停, 等待连接获取(异步任务)
+        ch.suspend();
+        if (!conn) {
+            if (errnum) throw php::exception(zend_ce_exception
+                    , (boost::format("Failed to connect to MySQL server: %s") % errmsg).str()
+                    , errnum);
+            else throw php::exception(zend_ce_exception
+                , "Failed to connect to MySQL server", 0);
+        }
+        // 恢复, 已经填充连接
+        return conn;
+    }
+
+    void _connection_pool::init_options(MYSQL* c) {
+        // 这里的 CHARSET 设定会被 reset_connection 重置为系统值
+        mysql_options(c, MYSQL_SET_CHARSET_NAME, url_.query["charset"].c_str());
+        // 版本 8.0.3 后默认使用 caching_sha2_password 进行认证，低版本不支持
+        mysql_options(c, MYSQL_DEFAULT_AUTH, url_.query["auth"].c_str());
+        unsigned int timeout = 5; // 连接超时
+        mysql_options(c, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
+    }
+
+    void _connection_pool::set_names(MYSQL* c) {
+        // 兼容 proxysql 流程：纯粹的字符集设置，首次 SQL 会卡住:
+        // 代理：2019, Can't initialize character set (null) (path: compiled_in)
+        // 后端：不断尝试连接、断开
+        std::string sql = (boost::format("SET NAMES '%s'") % url_.query["charset"]).str();
+        mysql_real_query(c, sql.c_str(), sql.size());
+    }
+
+    void _connection_pool::release(MYSQL *c) {
+        // 无等待分配的请求, 保存连接(待后续分配)
+        if (await_.empty()) conn_.push_back({c, std::chrono::steady_clock::now()});
+        else { // 立刻分配使用
+            // 每次连接复用前，需要清理状态;
+            // 这里兼容不支持 mysql_reset_connection() 新 API 的情况
+            // （不支持自动切换到 mysql_change_user() 兼容老版本或变异版本）
+            if (!(flag_ & FLAG_REUSE_MASK)) query_version(c);
+            if (flag_ & FLAG_REUSE_BY_RESET) mysql_reset_connection(c);
+            else if (flag_ & FLAG_REUSE_BY_CUSER) mysql_change_user(c, url_.user.c_str(), url_.pass.c_str(), url_.path.c_str() + 1);
+            // 由于 reset 动作会导致字符集被重置为服务端字符集，确认字符集是否匹配
+            if (!(flag_ & FLAG_CHARSET_MASK)) query_charset(c);
+            // else if (flag_ & FLAG_REUSE_BY_PROXY) ; // PROXY 已处理复用流程，此处无需处理
+            if (flag_ & FLAG_CHARSET_DIFFER)
+                mysql_set_character_set(c, url_.query["charset"].c_str());
+            std::function<void(std::shared_ptr<MYSQL> c)> cb = await_.front();
+            await_.pop_front();
+            auto self = this->shared_from_this();
+            // 释放回调函数须持有当前对象引用 self (否则连接池可能先于连接归还被销毁)
+            cb(std::shared_ptr<MYSQL>(c, [this, self] (MYSQL *c) {
+                boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+            }));
+        }
+    }
+
+    void _connection_pool::query_charset(MYSQL* c) {
+        // 使用 mysql_get_charactor_set_name() 获取到的字符集与下述查询不同
+        if (0 == mysql_real_query(c, "SHOW VARIABLES WHERE `Variable_name`='character_set_client'", 59)) {
+            std::unique_ptr<MYSQL_RES, void (*)(MYSQL_RES*)> rst(mysql_store_result(c), mysql_free_result);
+            if (!rst) return;
+            MYSQL_ROW row = mysql_fetch_row(rst.get());
+            if (!row) return;
+            unsigned long* len = mysql_fetch_lengths(rst.get());
+            std::string charset(row[1], len[1]);
+            flag_ |= charset == url_.query["charset"] ? FLAG_CHARSET_EQUAL : FLAG_CHARSET_DIFFER;
+        }
+        else flag_ |= FLAG_CHARSET_DIFFER; // 这里忽略了 charset 查询的错误
+    }
+
+    void _connection_pool::query_version(MYSQL* c) {
+        // PROXY 存在时不会进行下述动作
+        flag_ |= mysql_get_server_version(c) >= 50700
+            ? FLAG_REUSE_BY_RESET
+            : FLAG_REUSE_BY_CUSER;
+    }
+
+    void _connection_pool::sweep() {
+        tm_.expires_from_now(std::chrono::seconds(60));
+        // 注意, 实际的清理流程需要保证 guard_ 串行流程
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this] (const boost::system::error_code &error) {
+            if (error) return; // 当前对象销毁时会发生对应的 abort 错误
+            auto now = std::chrono::steady_clock::now();
+            // 超低水位，关闭不活跃或已丢失的连接
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;) {
+                auto duration = now - (*i).ttl;
+                if (duration > std::chrono::seconds(60) || mysql_ping((*i).conn) != 0) {
+                    mysql_close((*i).conn);
+                    --size_;
+                    i = conn_.erase(i);
+                }
+                else  ++i;
+            }
+            // 再次启动
+            sweep();
+        }));
+    }
+
+} // namespace flame::mysql
diff --git a/src/flame/mysql/_connection_pool.h b/src/flame/mysql/_connection_pool.h
new file mode 100644
index 0000000..0f4826a
--- /dev/null
+++ b/src/flame/mysql/_connection_pool.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../url.h"
+#include "../coroutine.h"
+
+#include "_connection_base.h"
+
+namespace flame::mysql {
+
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> {
+    public:
+        _connection_pool(url u);
+        ~_connection_pool();
+        std::shared_ptr<MYSQL> acquire(coroutine_handler &ch) override;
+        void sweep();
+    private:
+        url                               url_;
+        const std::uint16_t               min_;
+        const std::uint16_t               max_;
+        std::uint16_t                    size_;
+
+        boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
+        std::list<std::function<void(std::shared_ptr<MYSQL>)>> await_;
+        struct connection_t {
+            MYSQL *conn;
+            std::chrono::time_point<std::chrono::steady_clock> ttl;
+        };
+        std::list<connection_t> conn_;
+        boost::asio::steady_timer tm_;
+
+        int flag_;
+        enum {
+            FLAG_UNKNOWN        = 0x00,
+            FLAG_REUSE_MASK     = 0x0f,
+            FLAG_REUSE_BY_RESET = 0x01,
+            FLAG_REUSE_BY_CUSER = 0x02,
+            FLAG_REUSE_BY_PROXY = 0x04,
+            FLAG_CHARSET_MASK   = 0xf0,
+            FLAG_CHARSET_EQUAL  = 0x10,
+            FLAG_CHARSET_DIFFER = 0x20,
+        };
+
+        void init_options(MYSQL *c);
+        void release(MYSQL *c);
+        void set_names(MYSQL* c);
+        void query_charset(MYSQL* c);
+        void query_version(MYSQL* c);
+    };
+} // namespace flame::mysql
diff --git a/src/flame/mysql/client.cpp b/src/flame/mysql/client.cpp
new file mode 100644
index 0000000..f878fbc
--- /dev/null
+++ b/src/flame/mysql/client.cpp
@@ -0,0 +1,163 @@
+#include "../coroutine.h"
+#include "client.h"
+#include "_connection_pool.h"
+#include "_connection_lock.h"
+#include "mysql.h"
+#include "tx.h"
+
+namespace flame::mysql {
+    
+    void client::declare(php::extension_entry &ext) {
+        php::class_entry<client> class_client("flame\\mysql\\client");
+        class_client
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::__destruct>("__destruct")
+            .method<&client::escape>("escape", {
+                {"data", php::TYPE::UNDEFINED},
+            })
+            .method<&client::begin_tx>("begin_tx")
+            .method<&client::query>("query", {
+                {"sql", php::TYPE::STRING},
+            })
+            .method<&client::insert>("insert", {
+                {"table", php::TYPE::STRING},
+                {"rows", php::TYPE::ARRAY},
+            })
+            .method<&client::delete_>("delete", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::update>("update", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"set", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::select>("select", {
+                {"table", php::TYPE::STRING},
+                {"fields", php::TYPE::UNDEFINED},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::one>("one", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::get>("get", {
+                {"table", php::TYPE::STRING},
+                {"field", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&client::last_query>("last_query")
+            .method<&client::server_version>("server_version");
+        ext.add(std::move(class_client));
+    }
+
+    php::value client::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value client::__destruct(php::parameters &params) {
+        // std::cout << "~client" << std::endl;
+        return nullptr;
+    }
+
+    php::value client::escape(php::parameters &params) {
+        coroutine_handler ch {coroutine::current};
+        std::shared_ptr<MYSQL> conn = cp_->acquire(ch);
+
+        php::buffer buffer;
+        char quote = '\'';
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
+            php::string q = params[1];
+            if (q.data()[0] == '`') quote = '`';
+        }
+        _connection_base::escape(conn, buffer, params[0], quote);
+        return std::move(buffer);
+    }
+
+    php::value client::begin_tx(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        auto cl = std::make_shared<_connection_lock>(conn);
+        cl->begin_tx(ch);
+        // 构建事务对象
+        php::object obj(php::class_entry<tx>::entry());
+        tx *ptr = static_cast<tx *>(php::native(obj));
+        ptr->cl_ = cl; // 继续持有当前连接
+        return std::move(obj);
+    }
+
+    php::value client::query(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        return cp_->query(cp_->acquire(ch), params[0], ch);
+    }
+
+    php::value client::insert(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_insert(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value client::delete_(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_delete(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value client::update(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_update(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value client::select(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_select(conn, buf, params);
+        return cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value client::one(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_one(conn, buf, params);
+        php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+        return rst.call("fetch_row");
+    }
+
+    php::value client::get(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        php::buffer buf;
+        build_get(conn, buf, params);
+        php::object rst = cp_->query(conn, std::string(buf.data(), buf.size()), ch);
+        php::array row = rst.call("fetch_row");
+        if (!row.empty()) return row.get( static_cast<php::string>(params[1]) );
+        else return nullptr;
+    }
+
+    php::value client::last_query(php::parameters& params) {
+        return cp_->last_query();
+    }
+
+    php::value client::server_version(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cp_->acquire(ch);
+        return php::string(mysql_get_server_info(conn.get()));
+    }
+} // namespace flame::mysql
diff --git a/src/flame/mysql/client.h b/src/flame/mysql/client.h
new file mode 100644
index 0000000..ccd6164
--- /dev/null
+++ b/src/flame/mysql/client.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "../../vendor.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+
+    class _connection_pool;
+    class client : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params);
+        php::value __destruct(php::parameters &params);
+        php::value escape(php::parameters &params);
+        php::value begin_tx(php::parameters &params);
+        // php::value where(php::parameters &params);
+        // php::value order(php::parameters &params);
+        // php::value limit(php::parameters &params);
+        php::value query(php::parameters &params);
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value update(php::parameters &params);
+        php::value select(php::parameters &params);
+        php::value one(php::parameters &params);
+        php::value get(php::parameters &params);
+        php::value server_version(php::parameters& params);
+        php::value last_query(php::parameters& params);
+    protected:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::mysql
diff --git a/src/flame/mysql/mysql.cpp b/src/flame/mysql/mysql.cpp
new file mode 100644
index 0000000..4a9c0d3
--- /dev/null
+++ b/src/flame/mysql/mysql.cpp
@@ -0,0 +1,463 @@
+#include "../coroutine.h"
+#include "mysql.h"
+#include "_connection_base.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "result.h"
+#include "tx.h"
+
+namespace flame::mysql {
+
+    void declare(php::extension_entry &ext) {
+        gcontroller
+            ->on_init([] (const php::array& options) {
+                mysql_library_init(0, nullptr, nullptr);
+            })
+            ->on_stop([] () {
+                mysql_library_end();
+            });
+        ext
+            .function<connect>("flame\\mysql\\connect", {
+                {"url", php::TYPE::STRING},
+            });
+        client::declare(ext);
+        result::declare(ext);
+        tx::declare(ext);
+    }
+
+    php::value connect(php::parameters& params) {
+        url u(params[0]);
+        if (u.port < 10) u.port = 3306;
+        if (!u.query.count("charset")) u.query["charset"] = "utf8mb4";
+        // 版本 8.0.3 后默认使用 caching_sha2_password 进行认证，低版本不支持
+        if (!u.query.count("auth")) u.query["auth"] = "mysql_native_password";
+
+        php::object obj(php::class_entry<client>::entry());
+        client *ptr = static_cast<client *>(php::native(obj));
+        ptr->cp_.reset(new _connection_pool(u));
+        ptr->cp_->sweep(); // 启动自动清理扫描
+        // TODO 优化: 确认第一个连接建立 ?
+        return std::move(obj);
+    }
+    // 相等
+    static void where_eq(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(" IS NULL", 8);
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('=');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 不等 {!=}
+    static void where_ne(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(" IS NOT NULL", 12);
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.append("!=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 大于 {>}
+    static void where_gt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(">0", 2);
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('>');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 小于 {<}
+    static void where_lt(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.type_of(php::TYPE::NULLABLE)) {
+            buf.append("<0", 2);
+        }
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.push_back('<');
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 大于等于 {>=}
+    static void where_gte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond)  {
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append(">=0", 3);
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.append(">=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 小于等于 {<=}
+    static void where_lte(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        if (cond.type_of(php::TYPE::NULLABLE)) buf.append("<=0", 3);
+        else {
+            php::string str = cond;
+            str.to_string();
+            buf.append("<=", 2);
+            _connection_base::escape(cc, buf, str);
+        }
+    }
+    // 某个 IN 无对应符号
+    static void where_in(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
+        assert(cond.type_of(php::TYPE::ARRAY) && "目标格式错误");
+
+        buf.append(" IN (", 5);
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
+            if (static_cast<int>(i->first) > 0) buf.push_back(',');
+            php::string v = i->second;
+            v.to_string();
+            _connection_base::escape(cc, buf, v);
+        }
+        buf.push_back(')');
+    }
+    // WITH IN RANGE
+    static void where_wir(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
+        assert(cond.type_of(php::TYPE::ARRAY) && "目标格式错误");
+
+        buf.append(" BETWEEN ", 9);
+        std::int64_t min = std::numeric_limits<std::int64_t>::max(), max = std::numeric_limits<std::int64_t>::min();
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
+            std::int64_t x = i->second.to_integer();
+            if (x > max) max = x;
+            if (x < min) min = x;
+        }
+        buf.append(std::to_string(min));
+        buf.append(" AND ", 5);
+        buf.append(std::to_string(max));
+    }
+    // OUT OF RANGE
+    static void where_oor(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond) {
+        buf.append(" NOT", 4);
+        where_wir(cc, buf, cond);
+    }
+    // LINK
+    static void where_lk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        buf.append(" LIKE ", 6);
+        _connection_base::escape(cc, buf, cond);
+    }
+    // NOT LIKE
+    static void where_nlk(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &cond) {
+        buf.append(" NOT LIKE ", 10);
+        _connection_base::escape(cc, buf, cond);
+    }
+
+    static void where_ex(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &cond
+        , const php::string &field, const php::string &separator) {
+
+        if (cond.size() > 1) buf.push_back('(');
+
+        int j = -1;
+        for (auto i = cond.begin(); i != cond.end(); ++i) {
+            if (++j > 0) buf.append(separator);
+
+            if (i->first.type_of(php::TYPE::INTEGER))  {
+                if (i->second.type_of(php::TYPE::ARRAY)) where_ex(cc, buf, i->second, i->first, " AND ");
+                else {
+                    php::string str = i->second;
+                    str.to_string();
+                    buf.append(str);
+                }
+            }
+            else {
+                php::string key = i->first;
+                if (key.c_str()[0] == '{') { // OPERATOR (php::TYPE::STRING)
+                    if ((key.size() == 5 && strncasecmp(key.c_str(), "{NOT}", 5) == 0)
+                        || (key.size() == 3 && strncasecmp(key.c_str(), "{!}", 3) == 0)) {
+
+                        assert(i->second.type_of(php::TYPE::ARRAY));
+                        buf.append(" NOT ", 5);
+                        where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
+                    }
+                    else if (key.size() == 4 && (strncasecmp(key.c_str(), "{OR}", 4) == 0
+                        || strncasecmp(key.c_str(), "{||}", 4) == 0)) {
+
+                        assert(i->second.type_of(php::TYPE::ARRAY));
+                        where_ex(cc, buf, i->second, php::string(nullptr), " OR ");
+                    }
+                    else if ((key.size() == 5 && strncasecmp(key.c_str(), "{AND}", 5) == 0)
+                        || (key.size() == 4 && strncasecmp(key.c_str(), "{&&}", 4) == 0)) {
+                        assert(i->second.type_of(php::TYPE::ARRAY));
+                        where_ex(cc, buf, i->second, php::string(nullptr), " AND ");
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!=}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_ne(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{>}", 3) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_gt(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{<}", 2) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lt(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{>=}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_gte(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<=}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lte(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{<>}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_wir(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{><}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_oor(cc, buf, i->second);
+                    }
+                    else if (key.size() == 3 && strncasecmp(key.c_str(), "{~}", 3) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_lk(cc, buf, i->second);
+                    }
+                    else if (key.size() == 4 && strncasecmp(key.c_str(), "{!~}", 4) == 0) {
+                        _connection_base::escape(cc, buf, field, '`');
+                        where_nlk(cc, buf, i->second);
+                    }
+                }
+                else { // php::TYPE::STRING
+                    if (i->second.type_of(php::TYPE::ARRAY)) {
+                        php::array cond = i->second;
+                        if (cond.exists(0)) {
+                            _connection_base::escape(cc, buf, key, '`');
+                            buf.push_back(' ');
+                            where_in(cc, buf, i->second);
+                        }
+                        else where_ex(cc, buf, cond, key, " AND ");
+                    }
+                    else {
+                        _connection_base::escape(cc, buf, key, '`');
+                        where_eq(cc, buf, i->second);
+                    }
+                }
+            }
+        }
+        if (cond.size() > 1) buf.push_back(')');
+    }
+
+    void build_where(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
+        buf.append(" WHERE ", 7);
+        if (data.type_of(php::TYPE::STRING) || data.type_of(php::TYPE::INTEGER)) {
+            buf.push_back(' ');
+            buf.append(data);
+        }
+        else if (data.type_of(php::TYPE::ARRAY)) {
+            where_ex(cc, buf, data, php::string(0), " AND ");
+        }
+        else buf.push_back('1');
+    }
+
+    void build_order(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
+        if (data.type_of(php::TYPE::STRING)) {
+            buf.append(" ORDER BY ", 10);
+            buf.append(data);
+        }
+        else if (data.type_of(php::TYPE::ARRAY)) {
+            buf.append(" ORDER BY", 9);
+            php::array order = data;
+            int j = -1;
+            for (auto i = order.begin(); i != order.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
+                if (i->first.type_of(php::TYPE::INTEGER)) {
+                    if (i->second.type_of(php::TYPE::STRING)) {
+                        buf.push_back(' ');
+                        buf.append(i->second);
+                    }
+                }
+                else {
+                    buf.push_back(' ');
+                    _connection_base::escape(cc, buf, i->first, '`');
+                    if (i->second.type_of(php::TYPE::STRING)) {
+                        buf.push_back(' ');
+                        buf.append(i->second);
+                    }
+                    else if (i->second.type_of(php::TYPE::YES)
+                        || (i->second.type_of(php::TYPE::INTEGER) && static_cast<int>(i->second) >= 0))
+                        buf.append(" ASC", 4);
+                    else buf.append(" DESC", 5);
+                }
+            }
+        }
+        else ; // throw php::exception(zend_ce_type_error, "Failed to build 'ORDER BY' clause: unsupported type specified for 'order'", -1);
+    }
+
+    void build_limit(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::value &data) {
+        if (data.type_of(php::TYPE::STRING) || data.type_of(php::TYPE::INTEGER)) {
+            buf.append(" LIMIT ", 7);
+            buf.append(data);
+        }
+        else if (data.type_of(php::TYPE::ARRAY)) {
+            buf.append(" LIMIT", 6);
+            php::array limit = data;
+            int j = -1;
+            for (auto i = limit.begin(); i != limit.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
+                buf.push_back(' ');
+                buf.append(i->second);
+                if (j > 0) break;
+            }
+        }
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'LIMIT' clause: unsupported type specified for `limit`"
+            , -1);
+    }
+
+    static void insert_row(std::shared_ptr<MYSQL> cc, php::buffer &buf, const php::array &row) {
+        int j = -1;
+        for (auto i = row.begin(); i != row.end(); ++i)  {
+            if (++j > 0) buf.append(", ", 2);
+            _connection_base::escape(cc, buf, i->second);
+        }
+    }
+
+    void build_insert(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("INSERT INTO ", 12);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 字段
+        buf.push_back('(');
+        php::array rows = params[1], row;
+        if (rows.exists(0)) {
+            row = rows.get(0);
+            assert(!row.exists(0) && "二级行无字段");
+        }
+        else {
+            row = rows;
+            rows = nullptr;
+        }
+        assert(row.type_of(php::TYPE::ARRAY) && "行非数组");
+        int j = -1;
+        for (auto i = row.begin(); i != row.end(); ++i) {
+            if (++j > 0) buf.append(", ", 2);
+            _connection_base::escape(cc, buf, i->first, '`');
+        }
+        // 数据
+        buf.append(") VALUES", 8);
+        if (rows.type_of(php::TYPE::ARRAY)) {
+            buf.push_back('(');
+            int j = -1;
+            for (auto i = rows.begin(); i != rows.end(); ++i) {
+                if (++j > 0) buf.append("), (", 4);
+                insert_row(cc, buf, i->second);
+            }
+            buf.push_back(')');
+        }
+        else {
+            buf.push_back('(');
+            insert_row(cc, buf, row);
+            buf.push_back(')');
+        }
+    }
+
+    void build_delete(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("DELETE FROM ", 12);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 2) build_order(cc, buf, params[2]);
+        // 限制
+        if (params.size() > 3) build_limit(cc, buf, params[3]);
+    }
+
+    void build_update(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("UPDATE ", 7);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 数据
+        buf.append(" SET", 4);
+        php::array data = params[2];
+        if (data.type_of(php::TYPE::STRING)) {
+            buf.push_back(' ');
+            buf.append(data);
+        }
+        else if (data.type_of(php::TYPE::ARRAY)) {
+            int j = -1;
+            for (auto i = data.begin(); i != data.end(); ++i) {
+                if (++j > 0) buf.push_back(',');
+                buf.push_back(' ');
+                _connection_base::escape(cc, buf, i->first, '`');
+                buf.push_back('=');
+                _connection_base::escape(cc, buf, i->second);
+            }
+        }
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'UPDATE' clause: unsupported type specified for `update`"
+            , -1);
+        // 条件
+        build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 3) build_order(cc, buf, params[3]);
+        // 限制
+        if (params.size() > 4) build_limit(cc, buf, params[4]);
+    }
+
+    void build_select(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("SELECT ", 7);
+        // 字段
+        php::value fields = params[1];
+        if (fields.type_of(php::TYPE::STRING)) buf.append(fields);
+        else if (fields.type_of(php::TYPE::ARRAY)) {
+            php::array a = fields;
+            int j = -1;
+            for (auto i = a.begin(); i != a.end(); ++i) {
+                if (++j > 0) buf.append(", ", 2);
+                if (i->first.type_of(php::TYPE::INTEGER)) _connection_base::escape(cc, buf, i->second, '`');
+                else {
+                    buf.append(i->first);
+                    buf.push_back('(');
+                    _connection_base::escape(cc, buf, i->second, '`');
+                    buf.push_back(')');
+                }
+            }
+        }
+        else if (fields.type_of(php::TYPE::NULLABLE)) buf.push_back('*');
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'SELECT' clause: unsupported type specified for `fields`"
+            , -1);
+        // 表名
+        buf.append(" FROM ", 6);
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 2) build_where(cc, buf, params[2]);
+        // 排序
+        if (params.size() > 3) build_order(cc, buf, params[3]);
+        // 限制
+        if (params.size() > 4) build_limit(cc, buf, params[4]);
+    }
+
+    void build_one(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("SELECT * FROM ", 14);
+        // 表名
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 1) build_where(cc, buf, params[1]);
+        // 排序
+        if (params.size() > 2) build_order(cc, buf, params[2]);
+        buf.append(" LIMIT 1", 8);
+    }
+
+    void build_get(std::shared_ptr<MYSQL> cc, php::buffer &buf, php::parameters &params) {
+        buf.append("SELECT ", 7);
+        // 字段
+        if (params[1].type_of(php::TYPE::STRING)) _connection_base::escape(cc, buf, params[1], '`');
+        else throw php::exception(zend_ce_type_error
+            , "Failed to build 'SELECT' clause: unsupported type specified for `field`"
+            , -1);
+        // 表名
+        buf.append(" FROM ", 6);
+        _connection_base::escape(cc, buf, params[0], '`');
+        // 条件
+        if (params.size() > 2) build_where(cc, buf, params[2]);
+        // 排序
+        if (params.size() > 3) build_order(cc, buf, params[3]);
+        buf.append(" LIMIT 1", 8);
+    }
+
+}
diff --git a/src/flame/mysql/mysql.h b/src/flame/mysql/mysql.h
new file mode 100644
index 0000000..781a718
--- /dev/null
+++ b/src/flame/mysql/mysql.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+#include <mariadb/mysql.h>
+
+namespace flame::mysql {
+
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters& params);
+
+    void build_where(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_order(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_limit(std::shared_ptr<MYSQL> cm, php::buffer &buf, const php::value &data);
+    void build_insert(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_delete(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_update(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_select(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_one(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+    void build_get(std::shared_ptr<MYSQL> cm, php::buffer &buf, php::parameters &params);
+}
diff --git a/src/flame/mysql/result.cpp b/src/flame/mysql/result.cpp
new file mode 100644
index 0000000..2deb12b
--- /dev/null
+++ b/src/flame/mysql/result.cpp
@@ -0,0 +1,49 @@
+#include "../coroutine.h"
+#include "result.h"
+#include "_connection_lock.h"
+
+namespace flame::mysql {
+
+    void result::declare(php::extension_entry &ext) {
+        php::class_entry<result> class_result("flame\\mysql\\result");
+        class_result
+            .property({"stored_rows", 0})
+            .method<&result::fetch_row>("fetch_row")
+            .method<&result::fetch_all>("fetch_all");
+
+        ext.add(std::move(class_result));
+    }
+    
+    php::value result::fetch_row(php::parameters &params) {
+        php::array row(nullptr);
+        if (cl_ && rs_) {
+            coroutine_handler ch{coroutine::current};
+            row = cl_->fetch(cl_->acquire(ch), rs_, f_, n_, ch);
+        }
+        if (row.type_of(php::TYPE::NULLABLE)) {
+            // 尽早的释放连接
+            rs_.reset();
+            cl_.reset();
+        }
+        return row;
+    }
+
+    php::value result::fetch_all(php::parameters &params) {
+        if (cl_ && rs_) {
+            coroutine_handler ch{coroutine::current};
+            auto conn = cl_->acquire(ch);
+            php::array data {4}, row;
+
+            for(row = cl_->fetch(conn, rs_, f_, n_, ch); !row.type_of(php::TYPE::NULLABLE); row = cl_->fetch(conn, rs_, f_, n_, ch)) {
+                data.set(data.size(), row);
+            }
+            // 尽早的释放连接
+            rs_.reset();
+            cl_.reset();
+            return data;
+        }
+        else {
+            return nullptr;
+        }
+    }
+} // namespace flame::mysql
diff --git a/src/flame/mysql/result.h b/src/flame/mysql/result.h
new file mode 100644
index 0000000..ae18a0b
--- /dev/null
+++ b/src/flame/mysql/result.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "../../vendor.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+
+    class _connection_lock;
+    class result: public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value fetch_row(php::parameters &params);
+        php::value fetch_all(php::parameters &params);
+
+    private:
+        std::shared_ptr<_connection_lock> cl_;
+        std::shared_ptr<MYSQL_RES>        rs_;
+        MYSQL_FIELD                       *f_;
+        unsigned int                       n_; // Number Of Fields
+        friend class _connection_base;
+    };
+} // namespace flame::mysql
diff --git a/src/flame/mysql/tx.cpp b/src/flame/mysql/tx.cpp
new file mode 100644
index 0000000..7f96b65
--- /dev/null
+++ b/src/flame/mysql/tx.cpp
@@ -0,0 +1,152 @@
+#include "../coroutine.h"
+#include "tx.h"
+#include "_connection_lock.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+    void tx::declare(php::extension_entry &ext) {
+        php::class_entry<tx> class_tx("flame\\mysql\\tx");
+        class_tx
+            .method<&tx::__construct>("__construct", {}, php::PRIVATE)
+            .method<&tx::__destruct>("__destruct")
+            .method<&tx::escape>("escape", {
+                {"data", php::TYPE::UNDEFINED},
+            })
+            .method<&tx::commit>("commit")
+            .method<&tx::rollback>("rollback")
+            .method<&tx::query>("query", {
+                {"sql", php::TYPE::STRING},
+            })
+            .method<&tx::insert>("insert", {
+                {"table", php::TYPE::STRING},
+                {"rows", php::TYPE::ARRAY},
+            })
+            .method<&tx::delete_>("delete", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::update>("update", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"set", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::select>("select", {
+                {"table", php::TYPE::STRING},
+                {"fields", php::TYPE::UNDEFINED},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+                {"limit", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::one>("one", {
+                {"table", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            })
+            .method<&tx::get>("get", {
+                {"table", php::TYPE::STRING},
+                {"field", php::TYPE::STRING},
+                {"where", php::TYPE::UNDEFINED},
+                {"order", php::TYPE::UNDEFINED, false, true},
+            });
+        ext.add(std::move(class_tx));
+    }
+
+    php::value tx::__construct(php::parameters &params) {
+        return nullptr;
+    }
+
+    php::value tx::__destruct(php::parameters &params) {
+        if (!done_) rollback(params);
+        return nullptr;
+    }
+
+    php::value tx::escape(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
+
+        php::buffer buffer;
+        char quote = '\'';
+        if (params.size() > 1 && params[1].type_of(php::TYPE::STRING)) {
+            php::string q = params[1];
+            if (q.data()[0] == '`') quote = '`';
+        }
+        _connection_base::escape(conn, buffer, params[0], quote);
+        return std::move(buffer);
+    }
+
+    php::value tx::commit(php::parameters &params) {
+        done_ = true;
+        coroutine_handler ch{coroutine::current};
+        cl_->commit(ch);
+        return nullptr;
+    }
+
+    php::value tx::rollback(php::parameters &params) {
+        done_ = true;
+        coroutine_handler ch{coroutine::current};
+        cl_->rollback(ch);
+        return nullptr;
+    }
+
+    php::value tx::query(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<MYSQL> conn = cl_->acquire(ch);
+        return cl_->query(conn, params[0], ch);
+    }
+
+    php::value tx::insert(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_insert(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value tx::delete_(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_delete(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value tx::update(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_update(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value tx::select(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_select(conn, buf, params);
+        return cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+    }
+
+    php::value tx::one(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_one(conn, buf, params);
+        php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+        return rst.call("fetch_row");
+    }
+
+    php::value tx::get(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn = cl_->acquire(ch);
+        php::buffer buf;
+        build_get(conn, buf, params);
+        php::object rst = cl_->query(conn, std::string(buf.data(), buf.size()), ch);
+        php::array row = rst.call("fetch_row");
+        if (!row.empty()) return row.get( static_cast<php::string>(params[1]) );
+        else return nullptr;
+    }
+} // namespace flame::mysql
diff --git a/src/flame/mysql/tx.h b/src/flame/mysql/tx.h
new file mode 100644
index 0000000..bbaec9a
--- /dev/null
+++ b/src/flame/mysql/tx.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "../../vendor.h"
+#include "mysql.h"
+
+namespace flame::mysql {
+
+    class _connection_lock;
+    class tx : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters& params);
+        php::value commit(php::parameters &params);
+        php::value rollback(php::parameters &params);
+
+        php::value escape(php::parameters &params);
+        php::value query(php::parameters &params);
+
+        php::value insert(php::parameters &params);
+        php::value delete_(php::parameters &params);
+        php::value update(php::parameters &params);
+        php::value select(php::parameters &params);
+        php::value one(php::parameters &params);
+        php::value get(php::parameters &params);
+
+    protected:
+        std::shared_ptr<_connection_lock> cl_;
+        bool done_ = false;
+        friend class client;
+    };
+} // namespace flame::mysql
diff --git a/src/flame/os/os.cpp b/src/flame/os/os.cpp
new file mode 100644
index 0000000..18fb1f1
--- /dev/null
+++ b/src/flame/os/os.cpp
@@ -0,0 +1,116 @@
+#include "../coroutine.h"
+#include "../controller.h"
+#include "os.h"
+#include "process.h"
+#include <filesystem>
+// 获取本地网卡地址
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
+
+namespace flame::os {
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<interfaces>("flame\\os\\interfaces")
+            .function<spawn>("flame\\os\\spawn", {
+                {"executable", php::TYPE::STRING},
+                {"arguments", php::TYPE::ARRAY, false, true},
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .function<exec>("flame\\os\\exec", {
+                {"executable", php::TYPE::STRING},
+                {"arguments", php::TYPE::ARRAY, false, true},
+                {"options", php::TYPE::ARRAY, false, true},
+            });
+
+        process::declare(ext);
+    }
+
+    static void interface(php::array& data, const struct ifaddrs *addr) {
+        php::string name(addr->ifa_name);
+        php::array info(2);
+        info.set("family", addr->ifa_addr->sa_family == AF_INET ? "IPv4" : "IPv6");
+        char address[NI_MAXHOST];
+        if (getnameinfo(addr->ifa_addr,
+                addr->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
+                address, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST) != 0)
+            throw php::exception(zend_ce_exception, gai_strerror(errno), errno);
+        
+        info.set("address", address);
+        if (data.exists(name)) {
+            php::array iface = data.get(name);
+            iface.set(iface.size(), info);
+        }
+        else {
+            php::array iface(2);
+            iface.set(iface.size(), info);
+            data.set(name, iface);
+        }
+    }
+
+    php::value interfaces(php::parameters &params) {
+        struct ifaddrs *addr;
+        if (getifaddrs(&addr) != 0) throw php::exception(zend_ce_exception, std::strerror(errno), errno);
+        else if (!addr) return php::array(0);
+       
+        php::array data(4);
+        // 用 shared_ptr 作自动释放保证
+        std::shared_ptr<struct ifaddrs> autofree(addr, freeifaddrs);
+        do {
+            if (addr->ifa_addr->sa_family != AF_INET && addr->ifa_addr->sa_family != AF_INET6) continue;
+            interface(data, addr);
+        } while ((addr = addr->ifa_next) != nullptr);
+        return std::move(data);
+    }
+
+    php::value spawn(php::parameters &params) {
+        php::object proc(php::class_entry<process>::entry());
+        process *proc_ = static_cast<process *>(php::native(proc));
+        proc_->exit_ = false;
+
+        auto env = boost::this_process::environment();
+        std::string exec = params[0].to_string();
+        if (exec[0] != '.' && exec[0] != '/')
+            exec = boost::process::search_path(exec).native();
+        
+        std::vector<std::string> argv;
+        std::string cwdv = boost::filesystem::current_path().native();
+        if (params.size() > 1) {
+            php::array args = params[1];
+            for (auto i = args.begin(); i != args.end(); ++i)
+                argv.push_back(i->second.to_string());
+        }
+        if (params.size() > 2) {
+            php::array opts = params[2];
+            php::array envs = opts.get("env");
+            if (envs.type_of(php::TYPE::ARRAY)) {
+                for (auto i = envs.begin(); i != envs.end(); ++i)
+                    env[i->first.to_string()] = i->second.to_string();
+            }
+            php::string cwds = opts.get("cwd");
+            if (cwds.type_of(php::TYPE::STRING)) cwdv = cwds.to_string();
+        }
+
+        boost::process::child c(exec, boost::process::args = argv, env, gcontroller->context_x,
+            boost::process::start_dir = cwdv,
+            boost::process::std_out > proc_->out_,
+            boost::process::std_err > proc_->err_,
+            boost::process::on_exit = [proc, proc_] (int exit_code, const std::error_code &) {
+                proc_->exit_ = true;
+                if (proc_->ch_) proc_->ch_.resume();
+            });
+        proc.set("pid", c.id());
+        proc_->c_ = std::move(c);
+        return proc;
+    }
+
+    php::value exec(php::parameters &params) {
+        php::object proc = spawn(params);
+        process *proc_ = static_cast<process *>(php::native(proc));
+        proc.call("wait");
+        return proc_->out_.get();
+    }
+} // namespace flame::os
diff --git a/src/flame/os/os.h b/src/flame/os/os.h
new file mode 100644
index 0000000..f75b35c
--- /dev/null
+++ b/src/flame/os/os.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::os {
+    
+    void declare(php::extension_entry &ext);
+    php::value interfaces(php::parameters &params);
+    php::value spawn(php::parameters &params);
+    php::value exec(php::parameters &params);
+
+} // namespace flame::os
diff --git a/src/flame/os/process.cpp b/src/flame/os/process.cpp
new file mode 100644
index 0000000..17ae03d
--- /dev/null
+++ b/src/flame/os/process.cpp
@@ -0,0 +1,71 @@
+#include "../coroutine.h"
+#include "process.h"
+
+namespace flame::os {
+
+    void process::declare(php::extension_entry &ext) {
+        ext
+            .constant({"flame\\os\\SIGTERM", SIGTERM})
+            .constant({"flame\\os\\SIGKILL", SIGKILL})
+            .constant({"flame\\os\\SIGINT", SIGINT})
+            .constant({"flame\\os\\SIGUSR1", SIGUSR1})
+            .constant({"flame\\os\\SIGUSR2", SIGUSR2});
+
+        php::class_entry<process> class_process("flame\\os\\process");
+        class_process
+            .property({"pid", 0})
+            .method<&process::__construct>("__construct", {}, php::PRIVATE)
+            .method<&process::__destruct>("__destruct")
+            .method<&process::kill>("kill", {
+                {"signal", php::TYPE::INTEGER, false, true}
+            })
+            .method<&process::detach>("detach")
+            .method<&process::wait>("wait")
+            .method<&process::stdout>("stdout")
+            .method<&process::stderr>("stderr");
+        ext.add(std::move(class_process));
+    }
+
+    php::value process::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value process::__destruct(php::parameters& params) {
+        if (!detach_) {
+            if (!c_.wait_for(std::chrono::milliseconds(10000))) c_.terminate();
+            if (c_.joinable()) c_.join();
+        }
+        return nullptr;
+    }
+
+    php::value process::kill(php::parameters &params) {
+        if (params.size() > 0) ::kill(get("pid"), params[0].to_integer());
+        else ::kill(get("pid"), SIGTERM);
+        return nullptr;
+    }
+
+    php::value process::detach(php::parameters& params) {
+        detach_ = true;
+        c_.detach();
+        return nullptr;
+    }
+
+    php::value process::wait(php::parameters &params) {
+        if (!exit_) {
+            ch_.reset(coroutine::current);
+            ch_.suspend();
+            c_.join();
+        }
+        return nullptr;
+    }
+
+    php::value process::stdout(php::parameters &params) {
+        wait(params);
+        return out_.get();
+    }
+
+    php::value process::stderr(php::parameters &params) {
+        wait(params);
+        return out_.get();
+    }
+} // namespace flame::os
diff --git a/src/flame/os/process.h b/src/flame/os/process.h
new file mode 100644
index 0000000..e58e32e
--- /dev/null
+++ b/src/flame/os/process.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
+
+namespace flame::os {
+    class process: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params);
+        php::value __destruct(php::parameters& params);
+        php::value kill(php::parameters& params);
+        php::value wait(php::parameters& params);
+        php::value detach(php::parameters& params);
+        php::value stdout(php::parameters& params);
+        php::value stderr(php::parameters& params);
+    private:
+        boost::process::child      c_;
+        coroutine_handler         ch_;
+        std::future<std::string> out_;
+        std::future<std::string> err_;
+        bool exit_ = false;
+        bool detach_ = false;
+        friend class php::value spawn(php::parameters& params);
+        friend class php::value exec(php::parameters& params);
+    };
+}
diff --git a/src/flame/queue.cpp b/src/flame/queue.cpp
new file mode 100644
index 0000000..38c95c8
--- /dev/null
+++ b/src/flame/queue.cpp
@@ -0,0 +1,67 @@
+#include "coroutine.h"
+#include "queue.h"
+
+namespace flame {
+    void queue::declare(php::extension_entry &ext) {
+        php::class_entry<queue> class_queue("flame\\queue");
+        class_queue
+            .method<&queue::__construct>("__construct", {
+                {"n", php::TYPE::INTEGER, false, true},
+            })
+            .method<&queue::push>("push", {
+                {"value", php::TYPE::UNDEFINED}
+            })
+            .method<&queue::pop>("pop")
+            .method<&queue::close>("close")
+            .method<&queue::is_closed>("is_closed");
+        ext
+            .add(std::move(class_queue))
+            .function<queue::select>("flame\\select");
+    }
+
+    php::value queue::__construct(php::parameters& params) {
+        if (params.size() > 0)
+            q_.reset(new coroutine_queue<php::value>(static_cast<int>(params[0])));
+        else
+            q_.reset(new coroutine_queue<php::value>(1));
+        return nullptr;
+    }
+
+    php::value queue::push(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        q_->push(params[0], ch);
+        return nullptr;
+    }
+
+    php::value queue::pop(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        auto x = q_->pop(ch);
+        if (x) return x.value();
+        else return nullptr;
+    }
+
+    php::value queue::close(php::parameters &params) {
+        q_->close();
+        return nullptr;
+    }
+
+    php::value queue::is_closed(php::parameters& params) {
+        return q_->is_closed();
+    }
+    
+    php::value queue::select(php::parameters& params) {
+        std::vector< std::shared_ptr<coroutine_queue<php::value>> > qs;
+        std::map< std::shared_ptr<coroutine_queue<php::value>>, php::object > mm;
+        for (auto i = 0; i < params.size(); ++i) {
+            if (!params[i].instanceof(php::class_entry<queue>::entry())) 
+                throw php::exception(zend_ce_type_error, "Failed to select: instanceof flame\\queue required", -1);
+            php::object obj = params[i];
+            queue* ptr = static_cast<queue*>(php::native(obj));
+            qs.push_back( ptr->q_ );
+            mm.insert({ptr->q_, obj});
+        }
+        coroutine_handler ch{coroutine::current};
+        std::shared_ptr<coroutine_queue<php::value>> q = select_queue(qs, ch);
+        return mm[q];
+    }    
+}
diff --git a/src/flame/queue.h b/src/flame/queue.h
new file mode 100644
index 0000000..90c6201
--- /dev/null
+++ b/src/flame/queue.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../vendor.h"
+#include "../coroutine_queue.h"
+
+namespace flame {
+
+    class queue: public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        static php::value select(php::parameters& params);
+        php::value __construct(php::parameters& params);
+        php::value push(php::parameters &params);
+        php::value pop(php::parameters &params);
+        php::value close(php::parameters &params);
+        php::value is_closed(php::parameters &params);
+    private:
+        std::shared_ptr<coroutine_queue<php::value>> q_;
+    };
+}
diff --git a/src/flame/rabbitmq/_client.cpp b/src/flame/rabbitmq/_client.cpp
new file mode 100644
index 0000000..de11754
--- /dev/null
+++ b/src/flame/rabbitmq/_client.cpp
@@ -0,0 +1,191 @@
+#include "../coroutine.h"
+#include "_client.h"
+#include "message.h"
+
+namespace flame::rabbitmq {
+    
+    static bool str2bool(const char *str){
+        return strncasecmp(str, "1", 1) == 0 || strncasecmp(str, "yes", 3) == 0 ||
+            strncasecmp(str, "yes", 3) == 0 || strncasecmp(str, "true", 4);
+    }
+
+#define CHECK_AND_SET_FLAG(xflag, yflag)                        \
+    for (auto i = u.query.find(#xflag); i != u.query.end();) {  \
+        const char *str = i->second.c_str();                    \
+        if (str2bool(str)) fl_ |= yflag;                      \
+    }
+
+    _client::_client(url u, coroutine_handler& ch)
+    : chl_(gcontroller->context_x)
+    , con_(&chl_, AMQP::Address(u.str(true, false).c_str(), u.str().size()))
+    , chn_(&con_)
+    , tm_(gcontroller->context_x)
+    , producer_cb_(false) {
+        chn_.onReady([this, &ch]() {
+            // ch_.onError([] (const char* message) {});
+            chn_.onError([this] (const char* message) {
+                error_.assign(message);
+                if (!consumer_tg_.empty()) consumer_close();
+            });
+            ch.resume();
+        });
+        chn_.onError([this, &ch](const char *message) {
+            error_ = message;
+            chn_.onReady(nullptr);
+            ch.resume();
+        });
+        ch.suspend();
+
+        if (!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to connect RabbitMQ server: %s") % err).str()
+                , -1);
+        }
+
+        auto i = u.query.find("prefetch");
+        if (i == u.query.end()) pf_ = 8;
+        else pf_ = std::min(std::max(std::atoi(i->second.c_str()), 1), 256);
+        chn_.setQos(pf_);
+
+        CHECK_AND_SET_FLAG(nolocal, AMQP::nolocal);
+        CHECK_AND_SET_FLAG(noack, AMQP::noack);
+        CHECK_AND_SET_FLAG(exclusive, AMQP::noack);
+        CHECK_AND_SET_FLAG(mandatory, AMQP::mandatory);
+        CHECK_AND_SET_FLAG(immediate, AMQP::immediate);
+
+        // 启动心跳
+        heartbeat();
+    }
+
+    void _client::heartbeat() {
+        tm_.expires_after(std::chrono::seconds(45));
+        tm_.async_wait([this] (const boost::system::error_code& error) {
+            if (error) return;
+            con_.heartbeat();
+            heartbeat();
+        });
+    }
+
+    bool _client::has_error() {
+        return !error_.empty();
+    }
+
+    const std::string& _client::error() {
+        return error_;
+    }
+
+    void _client::consume(const std::string &queue, coroutine_queue<php::object>& q, coroutine_handler &ch) {
+        php::object obj = nullptr;
+        const char* err = nullptr;
+        chn_.consume(queue)
+            .onReceived([this, &q, &obj, &ch](const AMQP::Message &m, std::uint64_t tag, bool redelivered) {
+                obj = php::object(php::class_entry<message>::entry());
+                message* ptr = static_cast<message*>(php::native(obj));
+                ptr->build_ex(m, tag);
+                ch.resume(); // ----> 2
+            })
+            .onSuccess([this, &ch](const std::string &t) {
+                consumer_tg_ = t;
+                ch.resume(); // ----> 2
+            })
+            .onError([&err, &ch](const char *message) {
+                err = message;
+                ch.resume(); // ----> 2
+            });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
+            , -1);
+        consumer_ch_ = ch;
+        ch.suspend();
+        // 非关闭恢复执行, 消息对象一定存在
+        while(!consumer_tg_.empty()) {
+            q.push(std::move(obj), ch);
+            ch.suspend(); // 2 <----
+        }
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % err).str()
+            , -1);
+        q.close();
+        consumer_ch_.reset();
+    }
+
+    void _client::confirm(std::uint64_t tag, coroutine_handler &ch) {
+        chn_.ack(tag, 0);
+    }
+
+    void _client::reject(std::uint64_t tag, int flags, coroutine_handler &ch) {
+        chn_.reject(tag, flags);
+    }
+
+    void _client::consumer_close() {
+        const char* err = nullptr;
+        chn_.cancel(consumer_tg_);
+        consumer_tg_.clear();
+        // 标记结束后，消费流程队将自行关闭
+        if (consumer_ch_) consumer_ch_.resume();
+    }
+
+    void _client::consumer_close(coroutine_handler& ch) {
+        const char* err = nullptr;
+        chn_.cancel(consumer_tg_)
+            .onSuccess([&ch] () {
+                ch.resume();
+            })
+            .onError([&err, &ch] (const char* message) {
+                err = message;
+                ch.resume();
+            });
+        ch.suspend();
+        consumer_tg_.clear();
+        if (consumer_ch_) consumer_ch_.resume(); // ----> 2  标记结束后，消费流程队将自行关闭
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to close RabbitMQ consumer: %s") % err).str()
+            , -1);
+    }
+
+    void _client::publish(const std::string &ex, const std::string &rk, const AMQP::Envelope &env, coroutine_handler& ch) {
+        /*auto& defer = */chn_.publish(ex, rk, env, fl_);
+        // producer_cb(defer, ch);
+        if (!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish to RabbitMQ: %s") % err).str()
+                , -1);
+        }
+    }
+
+    // void _client::producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch) {
+    //     producer_ch_ = ch;
+    //     if (!producer_cb_) {
+    //         producer_cb_ = true;
+    //         const char* err = nullptr;
+    //         defer.onSuccess([this]() {
+    //             if (producer_ch_) producer_ch_.resume();
+    //         }).onError([this](const char *message) {
+    //             error_ = message;
+    //             if (producer_ch_) producer_ch_.resume();
+    //         });
+    //     }
+    //     ch.suspend();
+    //     producer_ch_.reset();
+    //     if (!error_.empty()) {
+    //         std::string err = std::move(error_);
+    //         throw php::exception(zend_ce_error
+    //             , (boost::format("Failed to publish RabbitMQ message: %s") % err).str()
+    //             , -1);
+    //     }
+    // }
+
+    void _client::publish(const std::string &ex, const std::string &rk, const char *msg, size_t len, coroutine_handler& ch) {
+        /*auto& defer = */chn_.publish(ex, rk, msg, len, fl_);
+        // producer_cb(defer, ch);
+        if (!error_.empty()) {
+            std::string err = std::move(error_);
+            throw php::exception(zend_ce_exception
+                , (boost::format("Failed to publish RabbitMQ message: %s") % err).str()
+                , -1);
+        }
+    }
+}
diff --git a/src/flame/rabbitmq/_client.h b/src/flame/rabbitmq/_client.h
new file mode 100644
index 0000000..4843d00
--- /dev/null
+++ b/src/flame/rabbitmq/_client.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../url.h"
+#include "../../coroutine_queue.h"
+#include "../coroutine.h"
+#include "rabbitmq.h"
+
+namespace flame::rabbitmq {
+
+    class _client {
+    public:
+        _client(url u, coroutine_handler& ch);
+        
+        void consume(const std::string& queue, coroutine_queue<php::object>& q, coroutine_handler& ch);
+        void confirm(std::uint64_t tag, coroutine_handler& ch);
+        void reject(std::uint64_t tag, int flags, coroutine_handler& ch);
+        void consumer_close(coroutine_handler& ch);
+        void consumer_close();
+        void publish(const std::string& ex, const std::string& rk, const AMQP::Envelope& env, coroutine_handler& ch);
+        void publish(const std::string& ex, const std::string& rk, const char* msg, size_t len, coroutine_handler& ch);
+        // void producer_cb(AMQP::DeferredPublisher& defer, coroutine_handler& ch);
+        void heartbeat();
+        bool has_error();
+        const std::string& error();
+    private:
+        AMQP::LibBoostAsioHandler chl_;
+        AMQP::TcpConnection       con_;
+        AMQP::TcpChannel          chn_;
+        int                       pf_;
+        int                       fl_;
+        boost::asio::steady_timer tm_;
+        std::string       consumer_tg_;
+        coroutine_handler consumer_ch_;
+        bool              producer_cb_;
+        coroutine_handler producer_ch_;
+        std::string       error_;
+
+        friend class consumer;
+    };
+}
diff --git a/src/flame/rabbitmq/consumer.cpp b/src/flame/rabbitmq/consumer.cpp
new file mode 100644
index 0000000..3564755
--- /dev/null
+++ b/src/flame/rabbitmq/consumer.cpp
@@ -0,0 +1,96 @@
+#include "../coroutine.h"
+#include "consumer.h"
+#include "_client.h"
+#include "message.h"
+#include "../time/time.h"
+#include "../log/logger.h"
+
+namespace flame::rabbitmq  {
+    
+    void consumer::declare(php::extension_entry &ext) {
+        php::class_entry<consumer> class_consumer("flame\\rabbitmq\\consumer");
+        class_consumer
+            .method<&consumer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&consumer::__destruct>("__destruct")
+            .method<&consumer::run>("run", {
+                {"callable", php::TYPE::CALLABLE},
+            })
+            .method<&consumer::confirm>("confirm", {
+                {"message", "flame\\rabbitmq\\message"}
+            })
+            .method<&consumer::reject>("reject", {
+                {"message", "flame\\rabbitmq\\message"},
+                {"requeue", php::TYPE::BOOLEAN, false, true},
+            })
+            .method<&consumer::close>("close");
+        ext.add(std::move(class_consumer));
+    }
+
+    php::value consumer::__construct(php::parameters &params) {
+        return nullptr;
+    }
+
+    php::value consumer::__destruct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value consumer::run(php::parameters &params) {
+        cb_ = params[0];
+
+        coroutine_handler ch {coroutine::current};
+        // 下述 q 需要在堆分配, 否则当 cc_->consume() 结束后可能与被提前销毁 (协程销毁稍晚)
+        coroutine_queue<php::object> q(cc_->pf_);
+        // 启动若干协程, 然后进行"并行>"消费
+        int count = (int)std::ceil(cc_->pf_ / 2.0);
+        for (int i = 0; i < count; ++i) {
+            // 启动协程开始消费
+            coroutine::start(php::value([this, &q, &count, &ch, i] (php::parameters &params) -> php::value {
+                coroutine_handler cch {coroutine::current};
+                while(auto x = q.pop(cch)) {
+                    try {
+                        cb_.call( {x.value()} );
+                    } catch(const php::exception& ex) {
+                        // 调用用户异常回调
+                        gcontroller->event("exception", {ex});
+                        // 记录错误信息
+                        php::object obj = ex;
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in RabbitMQ consumer: " << obj.call("__toString") << std::endl;
+                    }
+                }
+                if (--count == 0)  ch.resume(); // ----> 1
+                return nullptr;
+            }));
+        }
+        cc_->consume(qn_, q, ch);
+        ch.suspend(); // 1 <----
+        cb_ = nullptr;
+        if (cc_->has_error()) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to consume RabbitMQ queue: %s") % cc_->error()).str()
+            , -1);
+        return nullptr;
+    }
+
+    php::value consumer::confirm(php::parameters &params) {
+        php::object obj = params[0];
+        message *ptr = static_cast<message *>(php::native(obj));
+        coroutine_handler ch {coroutine::current};
+        cc_->confirm(ptr->tag_, ch);
+        return nullptr;
+    }
+
+    php::value consumer::reject(php::parameters &params) {
+        php::object obj = params[0];
+        message *ptr = static_cast<message *>(php::native(obj));
+        int flags = 0;
+        if (params.size() > 1 && params[1].to_boolean()) flags |= AMQP::requeue;
+        coroutine_handler ch {coroutine::current};
+        cc_->reject(ptr->tag_, flags, ch);
+        return nullptr;
+    }
+
+    php::value consumer::close(php::parameters &params) {
+        coroutine_handler ch {coroutine::current};
+        cc_->consumer_close(ch);
+        return nullptr;
+    }
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/consumer.h b/src/flame/rabbitmq/consumer.h
new file mode 100644
index 0000000..6e0ff24
--- /dev/null
+++ b/src/flame/rabbitmq/consumer.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "../../vendor.h"
+#include "rabbitmq.h"
+
+namespace flame::rabbitmq {
+
+    class _client;
+    class consumer : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __destruct(php::parameters& params);
+        php::value run(php::parameters &params);
+        php::value confirm(php::parameters &params);
+        php::value reject(php::parameters &params);
+        php::value close(php::parameters &params);
+    private:
+        // 实际的客户端对象可能超过当前对象的生存期
+        std::shared_ptr<_client> cc_;
+        php::callable            cb_;
+        std::string              qn_;
+        int              concurrent_;
+
+        friend php::value consume(php::parameters &params);
+    };
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/message.cpp b/src/flame/rabbitmq/message.cpp
new file mode 100644
index 0000000..8ebe499
--- /dev/null
+++ b/src/flame/rabbitmq/message.cpp
@@ -0,0 +1,106 @@
+#include "../coroutine.h"
+#include "rabbitmq.h"
+#include "message.h"
+
+namespace flame::rabbitmq {
+
+    void message::declare(php::extension_entry &ext) {
+        php::class_entry<message> class_message("flame\\rabbitmq\\message");
+        class_message
+            .implements(&php_json_serializable_ce)
+            .property({"routing_key", ""})
+            .property({"body", ""})
+            .property({"expiration", ""})
+            .property({"reply_to", ""})
+            .property({"correlation_id", ""})
+            .property({"priority", 0})
+            .property({"delivery_mode", 0})
+            .property({"header", nullptr})
+            .property({"content_encoding", ""})
+            .property({"content_type", ""})
+            .property({"cluster_id", ""})
+            .property({"app_id", ""})
+            .property({"user_id", ""})
+            .property({"type_name", ""})
+            .property({"timestamp", 0})
+            .property({"message_id", ""})
+            .method<&message::__construct>("__construct", {
+                {"body", php::TYPE::STRING, false, true},
+            })
+            .method<&message::to_string>("__toString")
+            .method<&message::to_json>("jsonSerialize");
+
+        ext.add(std::move(class_message));
+    }
+
+    php::value message::__construct(php::parameters &params) {
+        if (params.size() > 0) set("body", params[0]);
+        if (params.size() > 1) set("routing_key", params[1]);
+        return nullptr;
+    }
+
+    php::value message::to_json(php::parameters &params) {
+        php::array data(16);
+        data.set("routing_key", get("routing_key"));
+        data.set("body", get("body"));
+        data.set("timestamp", get("timestamp"));
+        return std::move(data);
+    }
+
+    php::value message::to_string(php::parameters &params) {
+        return get("body");
+    }
+
+    void message::build_ex(const AMQP::Message &msg, std::uint64_t tag) {
+        tag_ = tag;
+        set("routing_key", msg.routingkey());
+        set("body", php::string(msg.body(), msg.bodySize()));
+        if (msg.hasExpiration()) set("expiration", msg.expiration());
+        if (msg.hasReplyTo()) set("reply_to", msg.replyTo());
+        if (msg.hasCorrelationID()) set("correlation_id", msg.correlationID());
+        if (msg.hasPriority()) set("priority", msg.priority());
+        if (msg.hasDeliveryMode()) set("delivery_mode", msg.deliveryMode());
+        if (msg.hasHeaders()) set("header", table2array(msg.headers()));
+        if (msg.hasContentEncoding()) set("content_encoding", msg.contentEncoding());
+        if (msg.hasContentType()) set("content_type", msg.contentType());
+        if (msg.hasClusterID()) set("cluster_id", msg.clusterID());
+        if (msg.hasAppID()) set("app_id", msg.appID());
+        if (msg.hasUserID()) set("user_id", msg.userID());
+        if (msg.hasTypeName()) set("type_name", msg.typeName());
+        if (msg.hasTimestamp()) set("timestamp", msg.timestamp());
+        if (msg.hasMessageID()) set("message_id", msg.messageID());
+    }
+
+    void message::build_ex(AMQP::Envelope &env) {
+        php::string v;
+
+        v = get("expiration");
+        if (!v.empty()) env.setExpiration(v.to_string());
+        v = get("reply_to");
+        if (!v.empty()) env.setReplyTo(v.to_string());
+        v = get("correlation_id");
+        if (!v.empty()) env.setCorrelationID(v.to_string());
+        v = get("priority");
+        if (!v.empty()) env.setPriority(v.to_integer());
+        v = get("delivery_mode");
+        if (!v.empty()) env.setDeliveryMode(v.to_integer());
+        v = get("header");
+        if (!v.empty()) env.setHeaders(array2table(v));
+        v = get("content_encoding");
+        if (!v.empty()) env.setContentEncoding(v.to_string());
+        v = get("content_type");
+        if (!v.empty()) env.setContentType(v.to_string());
+        v = get("cluster_id");
+        if (!v.empty()) env.setClusterID(v.to_string());
+        v = get("app_id");
+        if (!v.empty()) env.setAppID(v.to_string());
+        v = get("user_id");
+        if (!v.empty()) env.setUserID(v.to_string());
+        v = get("type_name");
+        if (!v.empty()) env.setTypeName(v.to_string());
+        v = get("timestamp");
+        if (!v.empty()) env.setTimestamp(v.to_integer());
+        v = get("message_id");
+        if (!v.empty()) env.setMessageID(v.to_string());
+    }
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/message.h b/src/flame/rabbitmq/message.h
new file mode 100644
index 0000000..9bc9c1e
--- /dev/null
+++ b/src/flame/rabbitmq/message.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "../../vendor.h"
+#include "rabbitmq.h"
+
+namespace flame::rabbitmq {
+
+    class message : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value to_json(php::parameters &params);
+        php::value to_string(php::parameters &params);
+
+        void build_ex(const AMQP::Message &msg, std::uint64_t tag);
+        void build_ex(AMQP::Envelope &env);
+        std::uint64_t tag_;
+    };
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/producer.cpp b/src/flame/rabbitmq/producer.cpp
new file mode 100644
index 0000000..440b24f
--- /dev/null
+++ b/src/flame/rabbitmq/producer.cpp
@@ -0,0 +1,49 @@
+#include "../coroutine.h"
+#include "producer.h"
+#include "_client.h"
+#include "message.h"
+
+namespace flame::rabbitmq {
+
+    void producer::declare(php::extension_entry &ext) {
+        php::class_entry<producer> class_producer("flame\\rabbitmq\\producer");
+        class_producer
+            .method<&producer::__construct>("__construct", {}, php::PRIVATE)
+            .method<&producer::publish>("publish", {
+                {"exchange", php::TYPE::STRING},
+                {"message", php::TYPE::UNDEFINED},
+            });
+        ext.add(std::move(class_producer));
+    }
+
+    php::value producer::__construct(php::parameters &params) {
+        return nullptr;
+    }
+
+    php::value producer::publish(php::parameters &params) {
+        std::string exch = params[0];
+        std::string routing_key;
+        if (params.size() > 2) routing_key = params[2].to_string();
+        
+        coroutine_handler ch{coroutine::current};
+        if (params[1].instanceof(php::class_entry<message>::entry())) {
+            php::object msg = params[1];
+            message *msg_ = static_cast<message *>(php::native(msg));
+            php::string body = msg.get("body");
+            body.to_string();
+            AMQP::Envelope env(body.c_str(), body.size());
+            msg_->build_ex(env);
+            // 未指定时使用 message 默认的 routing_key
+            if (routing_key.empty()) routing_key = msg.get("routing_key").to_string();
+
+            coroutine_handler ch {coroutine::current};
+            cc_->publish(exch, routing_key, env, ch);
+        }
+        else {
+            php::string msg = params[1];
+            msg.to_string();
+            cc_->publish(exch, routing_key, msg.c_str(), msg.size(), ch);
+        }
+        return nullptr;
+    }
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/producer.h b/src/flame/rabbitmq/producer.h
new file mode 100644
index 0000000..53fee1f
--- /dev/null
+++ b/src/flame/rabbitmq/producer.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+#include "rabbitmq.h"
+
+namespace flame::rabbitmq {
+    class _client;
+    class producer: public php::class_base {
+    public:
+        static void declare(php::extension_entry& ext);
+        php::value __construct(php::parameters& params); // 私有
+        php::value publish(php::parameters& params);
+    private:
+        // 实际的客户端对象可能超过当前对象的生存期
+        std::shared_ptr<_client> cc_;
+        
+        friend php::value produce(php::parameters& params);
+        friend class _client;
+    };
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/rabbitmq.cpp b/src/flame/rabbitmq/rabbitmq.cpp
new file mode 100644
index 0000000..5d240f0
--- /dev/null
+++ b/src/flame/rabbitmq/rabbitmq.cpp
@@ -0,0 +1,83 @@
+#include "../../url.h"
+#include "../coroutine.h"
+#include "_client.h"
+#include "rabbitmq.h"
+#include "consumer.h"
+#include "producer.h"
+#include "message.h"
+
+namespace flame::rabbitmq {
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<consume>("flame\\rabbitmq\\consume", {
+                {"address", php::TYPE::STRING},
+                {"queue", php::TYPE::STRING},
+            })
+            .function<produce>("flame\\rabbitmq\\produce", {
+                {"address", php::TYPE::STRING},
+            });
+
+        consumer::declare(ext);
+        producer::declare(ext);
+        message::declare(ext);
+    }
+
+    php::value consume(php::parameters &params) {
+        coroutine_handler ch {coroutine::current};
+        url u(params[0]);
+        //  u.query.clear();
+        std::shared_ptr<_client> cc = std::make_shared<_client>(u, ch);
+
+        // cc->connect(ch);
+
+        php::object obj(php::class_entry<consumer>::entry());
+        consumer* ptr = static_cast<consumer*>(php::native(obj));
+        ptr->cc_ = cc;
+        ptr->qn_ = static_cast<std::string>(params[1]);
+        return std::move(obj);
+    }
+
+    php::value produce(php::parameters &params) {
+        coroutine_handler ch{coroutine::current};
+        url u(params[0]);
+        // u.query.clear();
+        std::shared_ptr<_client> cc = std::make_shared<_client>(u, ch);
+        
+        // cc->connect(ch);
+
+        php::object obj(php::class_entry<producer>::entry());
+        producer *ptr = static_cast<producer*>(php::native(obj));
+        ptr->cc_ = cc;
+        return std::move(obj);
+    }
+    // 仅支持一维数组
+    php::array table2array(const AMQP::Table& table) {
+        php::array data(table.size());
+        for (auto key : table.keys())  {
+            const AMQP::Field &field = table.get(key);
+            if (field.isBoolean()) data.set(key, static_cast<uint8_t>(field) ? true : false);
+            else if (field.isInteger()) data.set(key, static_cast<int64_t>(field));
+            else if (field.isDecimal()) data.set(key, static_cast<double>(field));
+            else if (field.isString()) data.set(key, (const std::string &)field);
+            else if (field.typeID() == 'd') data.set(key, static_cast<double>(field));
+            else if (field.typeID() == 'f') data.set(key, static_cast<float>(field));
+            // TODO 记录警告信息?
+        }
+        return std::move(data);
+    }
+    // 仅支持一维数组
+    AMQP::Table array2table(const php::array& table) {
+        AMQP::Table data;
+        if (!table.type_of(php::TYPE::ARRAY) || table.empty()) return std::move(data);
+        for (auto i = table.begin(); i != table.end(); ++i) {
+            if (i->second.type_of(php::TYPE::BOOLEAN)) data.set(i->first.to_string(), i->second.to_boolean());
+            else if (i->second.type_of(php::TYPE::INTEGER)) data.set(i->first.to_string(), i->second.to_integer());
+            else if (i->second.type_of(php::TYPE::STRING)) data.set(i->first.to_string(), i->second.to_string());
+            else if (i->second.type_of(php::TYPE::FLOAT)) data.set(i->first.to_string(), AMQP::Double{i->second.to_float()});
+            else throw php::exception(zend_ce_type_error
+                , (boost::format("Failed to convert array: unsupported type '%s'") % i->second.type_of().name()).str()
+                , -1);
+        }
+        return std::move(data);
+    }
+} // namespace flame::rabbitmq
diff --git a/src/flame/rabbitmq/rabbitmq.h b/src/flame/rabbitmq/rabbitmq.h
new file mode 100644
index 0000000..09bb7dc
--- /dev/null
+++ b/src/flame/rabbitmq/rabbitmq.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "../../vendor.h"
+#include <amqpcpp.h>
+#include <amqpcpp/libboostasio.h>
+
+namespace flame::rabbitmq {
+
+    void declare(php::extension_entry &ext);
+    php::value consume(php::parameters &params);
+    php::value produce(php::parameters &params);
+
+    php::array  table2array(const AMQP::Table &table);
+    AMQP::Table array2table(const php::array &table);
+} // namespace flame::rabbitmq
diff --git a/src/flame/redis/_connection_base.cpp b/src/flame/redis/_connection_base.cpp
new file mode 100644
index 0000000..de052cd
--- /dev/null
+++ b/src/flame/redis/_connection_base.cpp
@@ -0,0 +1,81 @@
+#include "_connection_base.h"
+
+namespace flame::redis {
+    
+    static php::value simple2value(redisReply* rp) {
+        if (!rp) return nullptr;
+        switch (rp->type) {
+        case REDIS_REPLY_STATUS:
+        case REDIS_REPLY_ERROR:
+        case REDIS_REPLY_STRING:
+            return php::string(rp->str, rp->len);
+        case REDIS_REPLY_INTEGER:
+            return static_cast<std::int64_t>(rp->integer);
+        case REDIS_REPLY_ARRAY: {
+            php::array data(rp->elements);
+            for(int i=0;i<rp->elements;++i) {
+                data.set(data.size(), simple2value(rp->element[i]));
+            }
+            return data;
+        }
+        case REDIS_REPLY_NIL:
+        default:
+            return nullptr;
+        }
+    }
+
+    php::value _connection_base::reply2value(redisReply* rp, php::array &argv, reply_type rt) {
+        switch(rt) {
+            case reply_type::ASSOC_ARRAY_1: {
+                php::array data(rp->elements/2 + 1);
+                for (int i = 0; i < rp->elements; i += 2) {
+                    php::string key = simple2value(rp->element[i]).to_string();
+                    data.set(key, simple2value(rp->element[i+1]));
+                }
+                return data;
+            }
+            case reply_type::ASSOC_ARRAY_2: {
+                assert(rp->elements == 2 && rp->element[0]->type == REDIS_REPLY_STRING && rp->element[1]->type == REDIS_REPLY_ARRAY);
+                php::array wrap(2);
+                php::array data(rp->element[1]->elements/2 + 1);
+                wrap.set(0, simple2value(rp->element[0]));
+                for (int i = 0; i < rp->element[1]->elements; i += 2) {
+                    php::string key = simple2value(rp->element[1]->element[i]).to_string();
+                    data.set(key, simple2value(rp->element[1]->element[i+1]));
+                }
+                wrap.set(1, data); 
+                return wrap;
+            }
+            case reply_type::COMBINE_1: {
+                php::array data(rp->elements);
+                for (int i = 0; i < rp->elements; ++i) {
+                    php::string key = argv[i].to_string();
+                    data.set(key, simple2value(rp->element[i]));
+                }
+                return data;
+            }
+            case reply_type::COMBINE_2: {
+                php::array data(rp->elements);
+                for (int i = 0; i < rp->elements; ++i) {
+                    php::string key = argv[i+1].to_string();
+                    data.set(key, simple2value(rp->element[i]));
+                }
+                return data;
+            }
+            case reply_type::SIMPLE:
+            default:
+                return simple2value(rp);
+        }
+    }
+
+    std::string _connection_base::format(php::string& name, php::array& argv) {
+        std::ostringstream ss;
+        ss << "*" << 1 + argv.size() << "\r\n$" << name.size() << "\r\n" << name << "\r\n";
+        for (auto i = argv.begin(); i != argv.end(); ++i) {
+            php::string v = i->second.to_string();
+            if (v.size() > 0) ss << "$" << v.size() << "\r\n" << v << "\r\n";
+            else ss << "$0\r\n\r\n";
+        }
+        return ss.str();
+    }
+}
\ No newline at end of file
diff --git a/src/flame/redis/_connection_base.h b/src/flame/redis/_connection_base.h
new file mode 100644
index 0000000..fe2f4eb
--- /dev/null
+++ b/src/flame/redis/_connection_base.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "redis.h"
+
+namespace flame::redis {
+
+    enum class reply_type {
+        SIMPLE        = 0x00,  // 默认返回处理
+        ASSOC_ARRAY_1 = 0x01,  // 返回数据多项按 KEY VAL 生成关联数组
+        ASSOC_ARRAY_2 = 0x02,  // 第一层普通数组, 第二层关联数组 (HSCAN/ZSCAN)
+        COMBINE_1     = 0x04,  // 与参数结合生成关联数组
+        COMBINE_2     = 0x08,  // 同上, 但偏移错位 1 个参数
+        EXEC          = 0x100, // 事务特殊处理方式（需要结合上面几种方式）
+        PIPE          = 0x200, // 与事务形式相同
+    };
+
+    class _connection_base {
+    public:
+        virtual std::shared_ptr<redisContext> acquire(coroutine_handler &ch) = 0;
+        php::value reply2value(redisReply *rp, php::array &argv, reply_type rt);
+        std::string format(php::string& name, php::array& argv);
+    };
+} // namespace flame::mysql
diff --git a/src/flame/redis/_connection_lock.cpp b/src/flame/redis/_connection_lock.cpp
new file mode 100644
index 0000000..6fd257c
--- /dev/null
+++ b/src/flame/redis/_connection_lock.cpp
@@ -0,0 +1,52 @@
+#include "../coroutine.h"
+#include "_connection_base.h"
+#include "_connection_lock.h"
+
+namespace flame::redis {
+
+    _connection_lock::_connection_lock(std::shared_ptr<redisContext> c)
+    : conn_(c) {
+    }
+
+    _connection_lock::~_connection_lock() {
+    }
+
+    std::shared_ptr<redisContext> _connection_lock::acquire(coroutine_handler &ch) {
+        return conn_;
+    }
+
+    void _connection_lock::push(php::string &name, php::array &argv, reply_type type) {
+        // 暂未提交, 仅记录
+        // TODO 优化: 是否可以在提交前在进行 format 操作 (减少内存占用的持续时间) ?
+        cmds_.push_back({name, argv, type, format(name, argv)});
+    }
+
+    void _connection_lock::push(php::string &name, php::parameters& params, reply_type type) {
+        php::array argv {params};
+        push(name, argv, type);
+    }
+
+    php::array _connection_lock::exec(coroutine_handler &ch)  {
+        boost::asio::post(gcontroller->context_x, [this, &ch] () {
+            // 在工作线程中, 提交所有待执行命令
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i)
+                redisAppendFormattedCommand(conn_.get(), i->strs.c_str(), i->strs.size());
+            // 读取对应的返回值
+            for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) 
+                redisGetReply(conn_.get(), (void**)&i->reply);
+            // 回到主线程
+            ch.resume();
+        });
+        ch.suspend();
+        // TODO 若执行过程当中存在异常，如何上报？（似乎 Redis 原则上不会部分失败）
+        // 整合个命令返回值
+        php::array data(cmds_.size());
+        for(auto i=cmds_.begin(); i!=cmds_.end(); ++i) {
+            data.set(data.size(), reply2value(i->reply, i->argv, i->type));
+            freeReplyObject(i->reply);
+        }
+        // 命令执行完毕 (所有返回值处理完成)
+        cmds_.clear();
+        return std::move(data);
+    }
+} // namespace flame::redis
diff --git a/src/flame/redis/_connection_lock.h b/src/flame/redis/_connection_lock.h
new file mode 100644
index 0000000..d4cb379
--- /dev/null
+++ b/src/flame/redis/_connection_lock.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../../vendor.h"
+#include "../coroutine.h"
+#include "redis.h"
+#include "_connection_base.h"
+
+namespace flame::redis {
+
+    class _connection_lock : public _connection_base, public std::enable_shared_from_this<_connection_lock> {
+    public:
+        _connection_lock(std::shared_ptr<redisContext> c);
+        ~_connection_lock();
+        std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
+        void push(php::string &name, php::array &argv, reply_type rt);
+        void push(php::string &name, php::parameters &argv, reply_type rt);
+        php::array exec(coroutine_handler& ch);
+        struct command_t {
+            php::string name;
+            php::array  argv;
+            reply_type  type;
+            std::string strs;
+            redisReply* reply;
+        };
+    private:
+        std::shared_ptr<redisContext> conn_;
+        std::list<command_t> cmds_;
+    };
+} // namespace flame::redis
diff --git a/src/flame/redis/_connection_pool.cpp b/src/flame/redis/_connection_pool.cpp
new file mode 100644
index 0000000..3f051d6
--- /dev/null
+++ b/src/flame/redis/_connection_pool.cpp
@@ -0,0 +1,177 @@
+#include "../controller.h"
+#include "redis.h"
+#include "_connection_pool.h"
+
+namespace flame::redis {
+
+    _connection_pool::_connection_pool(url u)
+    : url_(std::move(u)), min_(1), max_(6)
+    , size_(0)
+    , guard_(gcontroller->context_y)
+    , tm_(gcontroller->context_y) {
+        if (url_.port < 10) url_.port = 6379;
+    }
+
+    _connection_pool::~_connection_pool() {
+        while (!conn_.empty()) {
+            redisFree(conn_.front().conn);
+            conn_.pop_front();
+        }
+    }
+
+    std::shared_ptr<redisContext> _connection_pool::acquire(coroutine_handler &ch) {
+        std::shared_ptr<redisContext> conn;
+        std::string err;
+        // 提交异步任务
+        boost::asio::post(guard_, [this, &conn, &err, &ch]() {
+            // 设置对应的回调, 在获取连接后恢复协程
+            await_.push_back([&conn, &ch](std::shared_ptr<redisContext> c) {
+                conn = c;
+                // RESUME 需要在主线程进行
+                ch.resume();
+            });
+            auto now = std::chrono::steady_clock::now();
+            while (!conn_.empty())  {
+                if (now - conn_.front().ttl < std::chrono::seconds(15) || ping(conn_.front().conn)) {
+                    // 可用连接
+                    redisContext *c = conn_.front().conn;
+                    conn_.pop_front();
+                    release(c);
+                    return;
+                }
+                else { // 连接已丢失，回收资源
+                    redisFree(conn_.front().conn);
+                    conn_.pop_front();
+                    --size_;
+                }
+            }
+            if (size_ >= max_) return; // 已建立了足够多的连接, 需要等待已分配连接释放
+
+            redisContext *c = create(err);
+            if (c == nullptr) { // 创建新连接失败
+                await_.pop_back();
+                ch.resume();
+            }
+            else {
+                ++size_; // 当前还存在的连接数量
+                release(c);
+            }
+        });
+        // 暂停, 等待连接获取(异步任务)
+        ch.suspend();
+        if (!conn) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect to Redis server: %s") % err).str()
+            , -1);
+        // 恢复, 已经填充连接
+        return conn;
+    }
+
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler& ch) {
+        std::string ss = format(name, argv);
+        redisReply *rp = nullptr;
+        boost::asio::post(gcontroller->context_y, [&rc, &ss, &ch, &rp]() {
+            redisAppendFormattedCommand(rc.get(), ss.c_str(), ss.size());
+            redisGetReply(rc.get(), (void **)&rp);
+            ch.resume();
+        });
+        ch.suspend();
+        if (rp) {
+            php::value rv = reply2value(rp, argv, rt);
+            freeReplyObject(rp);
+            return std::move(rv);
+        }
+        else if (rc->err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to exec Redis command: %s") % rc->errstr).str()
+            , rc->err);
+        else return nullptr;
+    }
+
+    php::value _connection_pool::exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler& ch) {
+        php::array data {argv};
+        return exec(rc, name, data, rt, ch);
+    }
+
+    redisContext *_connection_pool::create(std::string& err) {
+        struct timeval tv {5, 0};
+        std::unique_ptr<redisContext, void(*)(redisContext*)> c {
+            redisConnectWithTimeout(url_.host.c_str(), url_.port, tv), redisFree };
+        if (!c) {
+            err.assign("connection failed");
+            return nullptr;
+        }
+        else if (c->err) {
+            err.assign(c->errstr);
+            return nullptr;
+        }
+        if (!url_.pass.empty()) { // 认证
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp {
+                (redisReply*) redisCommand(c.get(), "AUTH %s", url_.pass.c_str()),
+                (void (*)(redisReply*)) freeReplyObject };
+
+            if (!rp) {
+                err.assign(c->errstr);
+                return nullptr;
+            }
+            if (rp->type == REDIS_REPLY_ERROR) {
+                err.assign(rp->str, rp->len);
+                return nullptr;
+            }
+        }
+        if (url_.path.length() > 1) { // 指定数据库
+            std::unique_ptr<redisReply, void(*)(redisReply*)> rp {
+                (redisReply *) redisCommand(c.get(), "SELECT %d", std::atoi(url_.path.c_str() + 1)),
+                (void (*)(redisReply*)) freeReplyObject };
+            if (!rp) {
+                err.assign(c->errstr);
+                return nullptr;
+            }
+            if (rp->type == REDIS_REPLY_ERROR) {
+                err.assign(rp->str, rp->len);
+                return nullptr;
+            }
+        }
+        return c.release();
+    }
+
+    void _connection_pool::release(redisContext *c) {
+        if (c->err) --size_;  // 出现上下文异常的连接直接抛弃
+        else if (await_.empty())
+            conn_.push_back({c, std::chrono::steady_clock::now()}); // 无等待分配的请求
+        else { // 立刻分配使用
+            std::function<void(std::shared_ptr<redisContext> c)> cb = await_.front();
+            await_.pop_front();
+            auto self = this->shared_from_this();
+            // 释放回调函数须持有当前对象引用 self (否则连接池可能先于连接归还被销毁)
+            cb(std::shared_ptr<redisContext>(c, [this, self](redisContext *c) {
+                boost::asio::post(guard_, std::bind(&_connection_pool::release, self, c));
+            }));
+        }
+    }
+
+    bool _connection_pool::ping(redisContext* c) {
+        std::unique_ptr<redisReply, void(*)(redisReply*)> rp((redisReply *)redisCommand(c, "PING"), (void (*)(redisReply*))freeReplyObject);
+        if (!rp || rp->type == REDIS_REPLY_ERROR) return false;
+        else return true;
+    }
+
+    void _connection_pool::sweep() {
+        tm_.expires_from_now(std::chrono::seconds(60));
+        // 注意, 实际的清理流程需要保证 guard_ 串行流程
+        tm_.async_wait(boost::asio::bind_executor(guard_, [this](const boost::system::error_code &error) {
+            if (error) return; // 当前对象销毁时会发生对应的 abort 错误
+            auto now = std::chrono::steady_clock::now();
+            for (auto i = conn_.begin(); i != conn_.end() && size_ > min_;)  {
+                // 超低水位，关闭不活跃连接
+                auto duration = now - (*i).ttl;
+                if (duration > std::chrono::seconds(60) || !ping((*i).conn)) {
+                    redisFree((*i).conn);
+                    --size_;
+                    i = conn_.erase(i);
+                }
+                else ++i;
+            }
+            sweep(); // 再次启动
+        }));
+    }
+
+} // namespace flame::redis
diff --git a/src/flame/redis/_connection_pool.h b/src/flame/redis/_connection_pool.h
new file mode 100644
index 0000000..70e906c
--- /dev/null
+++ b/src/flame/redis/_connection_pool.h
@@ -0,0 +1,37 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../url.h"
+#include "../coroutine.h"
+#include "redis.h"
+#include "_connection_base.h"
+
+
+namespace flame::redis {
+    class _connection_pool : public _connection_base, public std::enable_shared_from_this<_connection_pool> {
+    public:
+        _connection_pool(url u);
+        ~_connection_pool();
+        std::shared_ptr<redisContext> acquire(coroutine_handler &ch) override;
+        php::value exec(std::shared_ptr<redisContext> rc, php::string &name, php::array &argv, reply_type rt, coroutine_handler &ch);
+        php::value exec(std::shared_ptr<redisContext> rc, php::string &name, php::parameters &argv, reply_type rt, coroutine_handler &ch);
+        void sweep();
+    private:
+        url                               url_;
+        const std::uint16_t               min_;
+        const std::uint16_t               max_;
+        std::uint16_t                    size_;
+
+        boost::asio::io_context::strand guard_; // 防止对下面队列操作发生多线程问题;
+        std::list<std::function<void(std::shared_ptr<redisContext>)>> await_;
+        struct connection_t {
+            redisContext *conn;
+            std::chrono::time_point<std::chrono::steady_clock> ttl;
+        };
+        std::list<connection_t> conn_;
+        boost::asio::steady_timer tm_;
+
+        bool ping(redisContext* c);
+        redisContext *create(std::string& err);
+        void release(redisContext *c);
+    };
+} // namespace flame::mysql
diff --git a/src/flame/redis/client.cpp b/src/flame/redis/client.cpp
new file mode 100644
index 0000000..7560f23
--- /dev/null
+++ b/src/flame/redis/client.cpp
@@ -0,0 +1,181 @@
+#include "../coroutine.h"
+#include "client.h"
+#include "_connection_pool.h"
+#include "tx.h"
+#include "_connection_lock.h"
+
+namespace flame::redis {
+    void client::declare(php::extension_entry& ext) {
+        php::class_entry<client> class_client("flame\\redis\\client");
+        class_client
+            .method<&client::__construct>("__construct", {}, php::PRIVATE)
+            .method<&client::__call>("__call", {
+                {"cmd", php::TYPE::STRING},
+                {"arg", php::TYPE::ARRAY},
+            })
+            .method<&client::__isset>("__isset", {
+                {"cmd", php::TYPE::STRING},
+            })
+            .method<&client::mget>("mget", {
+                {"key", php::TYPE::STRING},
+            })
+            .method<&client::hmget>("hmget", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&client::hgetall>("hgetall", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&client::hscan>("hscan", {
+                {"hash", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&client::zscan>("zscan", {
+                {"zset", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&client::zrange>("zrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&client::zrevrange>("zrevrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&client::zrangebyscore>("zrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&client::zrevrangebyscore>("zrevrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&client::multi>("multi")
+            // 以下暂未实现
+            .method<&client::unimplement>("subscribe")
+            .method<&client::unimplement>("psubscribe")
+            .method<&client::unimplement>("unsubscribe")
+            .method<&client::unimplement>("punsubscribe");
+
+        ext.add(std::move(class_client));
+    }
+
+    php::value client::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value client::__call(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name = params[0];
+        php::array  argv = params[1];
+        return cp_->exec(rc, name, argv, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::__isset(php::parameters& params) {
+        return true;
+    }
+
+    php::value client::mget(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("MGET", 4);
+        return cp_->exec(rc, name, params, reply_type::COMBINE_1, ch);
+    }
+
+    php::value client::hmget(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HMGET", 5);
+        return cp_->exec(rc, name, params, reply_type::COMBINE_2, ch);
+    }
+
+    php::value client::hgetall(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HGETALL", 7);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+    }
+
+    php::value client::hscan(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("HSCAN", 5);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+    }
+
+    php::value client::zscan(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZSCAN", 5);
+        return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_2, ch);
+    }
+
+    php::value client::zrange(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZRANGE", 6);
+        php::string last = params[params.size()-1];
+        if (last.type_of(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+        else
+            return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrevrange(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZREVRANGE", 9);
+        php::string last = params[params.size() - 1];
+        if (last.type_of(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+        else
+            return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrangebyscore(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZRANGEBYSCORE", 13);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
+                    return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+            }
+        }
+        return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::zrevrangebyscore(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto rc = cp_->acquire(ch);
+        php::string name("ZREVRANGEBYSCORE", 16);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0)
+                    return cp_->exec(rc, name, params, reply_type::ASSOC_ARRAY_1, ch);
+            }
+        }
+        return cp_->exec(rc, name, params, reply_type::SIMPLE, ch);
+    }
+
+    php::value client::multi(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        auto conn_ = cp_->acquire(ch);
+        php::object obj(php::class_entry<tx>::entry());
+        tx *ptr = static_cast<tx *>(php::native(obj));
+        ptr->cl_.reset(new _connection_lock(conn_));
+        return std::move(obj);
+    }
+    
+    php::value client::unimplement(php::parameters& params) {
+        throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
+    }
+} // namespace flame::redis
diff --git a/src/flame/redis/client.h b/src/flame/redis/client.h
new file mode 100644
index 0000000..dbbdcec
--- /dev/null
+++ b/src/flame/redis/client.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "../../vendor.h"
+#include "redis.h"
+
+namespace flame::redis {
+    class _connection_pool;
+    class client : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value __call(php::parameters &params);
+        php::value __isset(php::parameters& params);
+        // 处理特殊情况的命令
+        php::value mget(php::parameters &params);
+        php::value hmget(php::parameters &params);
+        php::value hgetall(php::parameters &params);
+        php::value hscan(php::parameters &params);
+        php::value sscan(php::parameters &params);
+        php::value zscan(php::parameters &params);
+        php::value zrange(php::parameters &params);
+        php::value zrevrange(php::parameters &params);
+        php::value zrangebyscore(php::parameters &params);
+        php::value zrevrangebyscore(php::parameters &params);
+        php::value unsubscribe(php::parameters &params);
+        php::value punsubscribe(php::parameters &params);
+        // 批量
+        php::value multi(php::parameters &params);
+        // 用于标记不实现的功能
+        php::value unimplement(php::parameters &params);
+    private:
+        std::shared_ptr<_connection_pool> cp_;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::redis
diff --git a/src/flame/redis/redis.cpp b/src/flame/redis/redis.cpp
new file mode 100644
index 0000000..82a40d6
--- /dev/null
+++ b/src/flame/redis/redis.cpp
@@ -0,0 +1,25 @@
+#include "redis.h"
+#include "_connection_pool.h"
+#include "client.h"
+#include "tx.h"
+
+namespace flame::redis {
+    
+    void declare(php::extension_entry& ext) {
+        ext.function<connect>("flame\\redis\\connect", {
+            {"uri", php::TYPE::STRING}
+        });
+        client::declare(ext);
+        tx::declare(ext);
+    }
+
+    php::value connect(php::parameters &params) {
+        url u(params[0]);
+        php::object obj {php::class_entry<client>::entry()};
+        client *ptr = static_cast<client *>(php::native(obj));
+        ptr->cp_.reset(new _connection_pool(u));
+        ptr->cp_->sweep(); // 启动自动清理扫描
+        // TODO 优化: 确认第一个连接建立 ?
+        return std::move(obj);
+    }
+}
diff --git a/src/flame/redis/redis.h b/src/flame/redis/redis.h
new file mode 100644
index 0000000..988604d
--- /dev/null
+++ b/src/flame/redis/redis.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../../vendor.h"
+#include <hiredis/hiredis.h>
+
+namespace flame::redis {
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+} // namespace flame::redis
diff --git a/src/flame/redis/tx.cpp b/src/flame/redis/tx.cpp
new file mode 100644
index 0000000..6b331b1
--- /dev/null
+++ b/src/flame/redis/tx.cpp
@@ -0,0 +1,163 @@
+#include "../coroutine.h"
+#include "tx.h"
+#include "_connection_lock.h"
+
+namespace flame::redis {
+    void tx::declare(php::extension_entry& ext) {
+        php::class_entry<tx> class_tx("flame\\redis\\tx");
+        class_tx
+            .method<&tx::__construct>("__construct", {}, php::PRIVATE)
+            .method<&tx::exec>("exec")
+            .method<&tx::__call>("__call", {
+                {"cmd", php::TYPE::STRING},
+                {"arg", php::TYPE::ARRAY},
+            })
+            .method<&tx::mget>("mget", {
+                {"key", php::TYPE::STRING},
+            })
+            .method<&tx::hmget>("hmget", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&tx::hgetall>("hgetall", {
+                {"hash", php::TYPE::STRING},
+            })
+            .method<&tx::hscan>("hscan", {
+                {"hash", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&tx::zscan>("zscan", {
+                {"zset", php::TYPE::STRING},
+                {"cursor", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrange>("zrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrevrange>("zrevrange", {
+                {"zset", php::TYPE::STRING},
+                {"start", php::TYPE::INTEGER},
+                {"stop", php::TYPE::INTEGER},
+            })
+            .method<&tx::zrangebyscore>("zrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&tx::zrevrangebyscore>("zrevrangebyscore", {
+                {"zset", php::TYPE::STRING},
+                {"min", php::TYPE::STRING},
+                {"max", php::TYPE::STRING},
+            })
+            .method<&tx::unimplement>("multi")
+            // 以下暂未实现
+            .method<&tx::unimplement>("subscribe")
+            .method<&tx::unimplement>("psubscribe")
+            .method<&tx::unimplement>("unsubscribe")
+            .method<&tx::unimplement>("punsubscribe");
+        
+        ext.add(std::move(class_tx));
+    }
+
+    php::value tx::__construct(php::parameters& params) {
+        return nullptr;
+    }
+
+    php::value tx::exec(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        return cl_->exec(ch);
+    }
+
+    php::value tx::__call(php::parameters& params) {
+        php::string name = params[0];
+        php::array  argv = params[1];
+        cl_->push(name, argv, reply_type::SIMPLE);
+        return this;
+    }
+
+    php::value tx::mget(php::parameters& params) {
+        php::string name("MGET", 4);
+        cl_->push(name, params, reply_type::COMBINE_1);
+        return this;
+    }
+
+    php::value tx::hmget(php::parameters& params) {
+        php::string name("HMGET", 5);
+        cl_->push(name, params, reply_type::COMBINE_2);
+        return this;
+    }
+
+    php::value tx::hgetall(php::parameters& params) {
+        php::string name("HGETALL", 7);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        return this;
+    }
+
+    php::value tx::hscan(php::parameters& params) {
+        php::string name("HSCAN", 5);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
+        return this;
+    }
+
+    php::value tx::zscan(php::parameters& params) {
+        php::string name("ZSCAN", 5);
+        cl_->push(name, params, reply_type::ASSOC_ARRAY_2);
+        return this;
+    }
+
+    php::value tx::zrange(php::parameters& params) {
+        php::string name("ZRANGE", 6);
+        php::string last = params[params.size()-1];
+        if (last.type_of(php::TYPE::STRING) && last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        else
+            cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    } 
+
+    php::value tx::zrevrange(php::parameters& params) {
+        php::string name("ZREVRANGE", 9);
+        php::string last = params[params.size() - 1];
+        if (last.type_of(php::TYPE::STRING) &&last.size() == 10 && strncasecmp("WITHSCORES", last.c_str(), 10) == 0)
+            cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+        else
+            cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    }
+
+    php::value tx::zrangebyscore(php::parameters& params) {
+        php::string name("ZRANGEBYSCORE", 13);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+                    cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+                    return this;
+                }
+            }
+        }
+        cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    }
+    
+    php::value tx::zrevrangebyscore(php::parameters& params) {
+        php::string name("ZREVRANGEBYSCORE", 16);
+
+        for(int i=3; i<params.size(); ++i) {
+            if (params[i].type_of(php::TYPE::STRING) && params[i].size() == 10) {
+                php::string arg = params[i];
+                if (strncasecmp("WITHSCORES", arg.c_str(), 10) == 0) {
+                    cl_->push(name, params, reply_type::ASSOC_ARRAY_1);
+                    return this;
+                }
+            }
+        }
+        cl_->push(name, params, reply_type::SIMPLE);
+        return this;
+    }
+    
+    php::value tx::unimplement(php::parameters& params) {
+        throw php::exception(zend_ce_error_exception, "This redis command is NOT yet implemented");
+    }
+} // namespace flame::redis
diff --git a/src/flame/redis/tx.h b/src/flame/redis/tx.h
new file mode 100644
index 0000000..475aa69
--- /dev/null
+++ b/src/flame/redis/tx.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "../../vendor.h"
+#include "redis.h"
+
+namespace flame::redis {
+
+    class _connection_lock;
+    class tx : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        php::value __construct(php::parameters &params); // 私有
+        php::value exec(php::parameters& params);
+        php::value __call(php::parameters &params);
+        // 处理特殊情况的命令
+        php::value mget(php::parameters &params);
+        php::value hmget(php::parameters &params);
+        php::value hgetall(php::parameters &params);
+        php::value hscan(php::parameters &params);
+        php::value sscan(php::parameters &params);
+        php::value zscan(php::parameters &params);
+        php::value zrange(php::parameters &params);
+        php::value zrevrange(php::parameters &params);
+        php::value zrangebyscore(php::parameters &params);
+        php::value zrevrangebyscore(php::parameters &params);
+        php::value unsubscribe(php::parameters &params);
+        php::value punsubscribe(php::parameters &params);
+        // 批量
+        php::value multi(php::parameters &params);
+        // 用于标记不实现的功能
+        php::value unimplement(php::parameters &params);
+    private:
+        std::shared_ptr<_connection_lock> cl_;
+        friend class client;
+    };
+} // namespace flame::redis
diff --git a/src/flame/tcp/server.cpp b/src/flame/tcp/server.cpp
new file mode 100644
index 0000000..7253510
--- /dev/null
+++ b/src/flame/tcp/server.cpp
@@ -0,0 +1,108 @@
+#include "../coroutine.h"
+#include "../udp/udp.h"
+#include "../time/time.h"
+#include "server.h"
+#include "tcp.h"
+#include "socket.h"
+#include "../log/logger.h"
+
+namespace flame::tcp {
+
+    void server::declare(php::extension_entry &ext) {
+        php::class_entry<server> class_server("flame\\tcp\\server");
+        class_server
+            .method<&server::__construct>("__construct", {
+                {"bind", php::TYPE::STRING},
+            })
+            .method<&server::run>("run", {
+                {"cb", php::TYPE::CALLABLE},
+            })
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
+    }
+
+    server::server()
+    : acceptor_(gcontroller->context_x)
+    , socket_(gcontroller->context_x) {
+
+    }
+
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value server::__construct(php::parameters &params) {
+        closed_ = false;
+        std::string str_addr = params[0];
+        auto pair = udp::addr2pair(str_addr);
+        if (pair.first.empty() || pair.second.empty())
+            throw php::exception(zend_ce_error_exception
+                , "Failed to bind TCP socket: address malformed"
+                , -1);
+        boost::asio::ip::address addr = boost::asio::ip::make_address(pair.first);
+        addr_.address(addr);
+        addr_.port(std::atoi(pair.second.c_str()));
+
+        set("address", str_addr);
+        acceptor_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        acceptor_.set_option(opt1);
+        reuse_port opt2(true);
+        acceptor_.set_option(opt2);
+
+        boost::system::error_code err;
+        acceptor_.bind(addr_, err);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind TCP socket: %s") % err.message()).str()
+            , err.value());
+
+        acceptor_.listen(boost::asio::socket_base::max_listen_connections, err);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to listen TCP socket: %s") % err.message()).str()
+            , err.value());
+        return nullptr;
+    }
+
+    php::value server::run(php::parameters &params) {
+        cb_ = params[0];
+        coroutine_handler ch {coroutine::current};
+        boost::system::error_code err;
+        while(!closed_) {
+            acceptor_.async_accept(socket_, ch[err]);
+            if (err == boost::asio::error::operation_aborted) break;
+            else if (err) throw php::exception(zend_ce_error_exception
+                , (boost::format("Failed to accept TCP socket: %s") % err.message()).str()
+                , err.value());
+            else{
+                php::object obj(php::class_entry<socket>::entry());
+                socket* ptr = static_cast<socket*>(php::native(obj));
+                ptr->socket_ = std::move(socket_);
+                obj.set("local_address",
+                    (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+                obj.set("remote_address",
+                    (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+
+                coroutine::start(php::callable([obj, cb = cb_] (php::parameters& params) -> php::value {
+                    try {
+                        cb.call({obj});
+                    } catch(const php::exception& ex) {
+                        // 调用用户异常回调
+                        gcontroller->event("exception", {ex});
+                        // 记录错误信息
+                        php::object obj = ex;
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in TCP handler: "<< obj.call("__toString") << std::endl;
+                    }
+                    return nullptr;
+                }));
+            }
+        }
+        cb_ = nullptr;
+        return nullptr;
+    }
+    
+    php::value server::close(php::parameters &params) {
+        if (!closed_) {
+            closed_ = true;
+            acceptor_.cancel();
+        }
+        return nullptr;
+    }
+}
diff --git a/src/flame/tcp/server.h b/src/flame/tcp/server.h
new file mode 100644
index 0000000..940a81a
--- /dev/null
+++ b/src/flame/tcp/server.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::tcp {
+    class server : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        server();
+        php::value __construct(php::parameters &params);
+        php::value run(php::parameters &params);
+        php::value close(php::parameters &params);
+    private:
+        boost::asio::ip::tcp::acceptor acceptor_;
+		boost::asio::ip::tcp::socket     socket_;
+        boost::asio::ip::tcp::endpoint     addr_;
+        php::callable                        cb_;
+        bool                             closed_;
+    };
+} // namespace flame::tcp
diff --git a/src/flame/tcp/socket.cpp b/src/flame/tcp/socket.cpp
new file mode 100644
index 0000000..0db883d
--- /dev/null
+++ b/src/flame/tcp/socket.cpp
@@ -0,0 +1,88 @@
+#include "../coroutine.h"
+#include "socket.h"
+
+namespace flame::tcp {
+
+    void socket::declare(php::extension_entry& ext) {
+        php::class_entry<socket> class_socket("flame\\tcp\\socket");
+        class_socket
+            .property({"local_address", ""})
+            .property({"remote_address", ""})
+            .method<&socket::read>("read", {
+                {"completion", php::TYPE::UNDEFINED, false, true}
+            })
+            .method<&socket::write>("write", {
+                {"data", php::TYPE::STRING}
+            })
+            .method<&socket::close>("close");
+        ext.add(std::move(class_socket));
+    }
+
+    socket::socket()
+    : socket_(gcontroller->context_x) {
+
+    }
+    
+    php::value socket::read(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+
+        boost::system::error_code err;
+        std::size_t len;
+        if (params.size() == 0) {// 1. 随意读取一段数据
+            len = socket_.async_read_some(boost::asio::buffer(buffer_.prepare(8192), 8192), ch[err]);
+            buffer_.commit(len);
+        }
+        else if (params[0].type_of(php::TYPE::STRING)) { // 2. 读取到指定的结束符
+            std::string delim = params[0];
+            len = boost::asio::async_read_until(socket_, buffer_, delim, ch[err]);
+        }
+        else if (params[0].type_of(php::TYPE::INTEGER)) { // 3. 读取指定长度
+            std::size_t want = params[0].to_integer();
+            if (buffer_.size() >= want) {
+                len = want;
+                goto RETURN_DATA;
+            }
+            // 读取指定长度 (剩余的缓存数据也算在长度中)
+            len = boost::asio::async_read(socket_, buffer_, boost::asio::transfer_exactly(want - buffer_.size()), ch[err]);
+            len = want;
+        }
+        else throw php::exception(zend_ce_type_error
+            , "Failed to read TCP socket: unknown completion type"
+            , -1); // 未知读取方式
+RETURN_DATA:
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to read TCP socket: %s") % err.message()).str()
+            , err.value());
+        else {
+            php::string data(len);
+            boost::asio::buffer_copy(boost::asio::buffer(data.data(), len), buffer_.data(), len);
+            buffer_.consume(len);
+            return std::move(data);
+        }
+    }
+
+    php::value socket::write(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时写入
+        coroutine_guard guard(wmutex_, ch);
+        
+        boost::system::error_code err;
+        std::string data = params[0];
+        std::size_t len = boost::asio::async_write(socket_, boost::asio::buffer(data), ch);
+
+        if (!err || err == boost::asio::error::operation_aborted) return nullptr;
+        else throw php::exception(zend_ce_exception
+            , (boost::format("Failed to write TCP socket: %s") % err.message()).str()
+            , err.value());
+    }
+
+    php::value socket::close(php::parameters& params) {
+        socket_.shutdown(boost::asio::socket_base::shutdown_both);
+        return nullptr;
+    }
+}
+  
\ No newline at end of file
diff --git a/src/flame/tcp/socket.h b/src/flame/tcp/socket.h
new file mode 100644
index 0000000..4ecd4f3
--- /dev/null
+++ b/src/flame/tcp/socket.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
+#include "../coroutine.h"
+
+namespace flame::tcp {
+
+    class socket : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        socket();
+        php::value read(php::parameters &param);
+        php::value write(php::parameters &params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::tcp::socket socket_;
+        boost::asio::streambuf       buffer_;
+        coroutine_mutex              rmutex_;
+        coroutine_mutex              wmutex_;
+
+        friend class server;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::tcp
diff --git a/src/flame/tcp/tcp.cpp b/src/flame/tcp/tcp.cpp
new file mode 100644
index 0000000..ed297c2
--- /dev/null
+++ b/src/flame/tcp/tcp.cpp
@@ -0,0 +1,66 @@
+#include "../coroutine.h"
+#include "../udp/udp.h"
+#include "tcp.h"
+#include "socket.h"
+#include "server.h"
+
+namespace flame::tcp {
+    static std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
+    void declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
+            resolver_.reset(new boost::asio::ip::tcp::resolver(gcontroller->context_x));
+        })->on_stop([] () {
+            resolver_.reset();
+        });
+        ext
+            .function<connect>("flame\\tcp\\connect", {
+                {"address", php::TYPE::STRING},
+            });
+        socket::declare(ext);
+        server::declare(ext);
+    }
+
+    php::value connect(php::parameters& params) {
+        php::object obj(php::class_entry<socket>::entry());
+        socket *ptr = static_cast<socket *>(php::native(obj));
+
+        std::string str = params[0];
+        auto pair = udp::addr2pair(str);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to connect TCP socket: illegal address format"
+            , -1);
+
+        coroutine_handler ch{coroutine::current};
+        boost::system::error_code err;
+        boost::asio::ip::tcp::resolver::results_type eps;
+        // DNS 地址解析
+        resolver_->async_resolve(pair.first, pair.second, [&err, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::tcp::resolver::results_type results) {
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
+
+        // 连接
+        boost::asio::async_connect(ptr->socket_, eps, [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::tcp::endpoint &edp) {
+            if (error) err = error;
+            else {
+                obj.set("local_address", (boost::format("%s:%d")
+                    % ptr->socket_.local_endpoint().address().to_string()
+                    % ptr->socket_.local_endpoint().port() ).str());
+                obj.set("remote_address", (boost::format("%s:%d")
+                    % ptr->socket_.remote_endpoint().address().to_string()
+                    % ptr->socket_.remote_endpoint().port() ).str());
+            }
+            ch.resume();
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect TCP socket: %s") % err.message()).str()
+            , err.value());
+        return std::move(obj);
+    }
+} // namespace flame::tcp
diff --git a/src/flame/tcp/tcp.h b/src/flame/tcp/tcp.h
new file mode 100644
index 0000000..9b21a4b
--- /dev/null
+++ b/src/flame/tcp/tcp.h
@@ -0,0 +1,7 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::tcp {
+    void declare(php::extension_entry &ext);
+    php::value connect(php::parameters &params);
+} // namespace flame::tcp
diff --git a/src/flame/time/time.cpp b/src/flame/time/time.cpp
new file mode 100644
index 0000000..7913e94
--- /dev/null
+++ b/src/flame/time/time.cpp
@@ -0,0 +1,64 @@
+#include "time.h"
+#include "../coroutine.h"
+
+namespace flame::time {
+
+    static std::chrono::time_point<std::chrono::system_clock> time_system;
+    static std::chrono::time_point<std::chrono::steady_clock> time_steady;
+
+    static php::value sleep(php::parameters& params) {
+        coroutine_handler ch {coroutine::current};
+        boost::asio::steady_timer tm(gcontroller->context_x);
+        int ms = static_cast<int>(params[0]);
+        if (ms < 0) ms = 1;
+        tm.expires_from_now(std::chrono::milliseconds(ms));
+        tm.async_wait(ch);
+        return nullptr;
+    }
+
+    std::chrono::time_point<std::chrono::system_clock> now() {
+        std::chrono::milliseconds diff = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - time_steady);
+        return time_system + diff;
+    }
+
+    php::string iso(const std::chrono::time_point<std::chrono::system_clock>& now) {
+        php::string data(19);
+        std::time_t t = std::chrono::system_clock::to_time_t(now);
+        struct tm *m = std::localtime(&t);
+        sprintf(data.data(), "%04d-%02d-%02d %02d:%02d:%02d",
+                1900 + m->tm_year,
+                1 + m->tm_mon,
+                m->tm_mday,
+                m->tm_hour,
+                m->tm_min,
+                m->tm_sec);
+        return std::move(data);
+    }
+
+    php::string iso() {
+        return iso(now());
+    }
+
+    static php::value now(php::parameters &params) {
+        return std::chrono::duration_cast<std::chrono::milliseconds>(now().time_since_epoch()).count();
+    }
+
+    static php::value iso(php::parameters &params) {
+        return iso();
+    }
+
+    void declare(php::extension_entry& ext) {
+        time_steady = std::chrono::steady_clock::now();
+        time_system = std::chrono::system_clock::now();
+
+        ext
+            .function<sleep>("flame\\time\\sleep", {
+                {"duration", php::TYPE::INTEGER},
+            })
+            // 毫秒时间戳
+            .function<now>("flame\\time\\now")
+            // 标准时间 YYYY-mm-dd HH:ii:ss
+            .function<iso>("flame\\time\\iso");
+    }
+}
diff --git a/src/flame/time/time.h b/src/flame/time/time.h
new file mode 100644
index 0000000..f3e666f
--- /dev/null
+++ b/src/flame/time/time.h
@@ -0,0 +1,8 @@
+#include "../../vendor.h"
+
+namespace flame::time {
+    void declare(php::extension_entry &ext);
+    std::chrono::time_point<std::chrono::system_clock> now();
+    php::string iso();
+    php::string iso(const std::chrono::time_point<std::chrono::system_clock>& now);
+}
diff --git a/src/flame/toml/_decode.cpp b/src/flame/toml/_decode.cpp
new file mode 100755
index 0000000..744999f
--- /dev/null
+++ b/src/flame/toml/_decode.cpp
@@ -0,0 +1,7 @@
+#include "_decode.h"
+
+namespace flame::toml {
+    void decode_inplace(php::string& str) {
+        php_stripcslashes(str);
+    }
+}
\ No newline at end of file
diff --git a/src/flame/toml/_decode.h b/src/flame/toml/_decode.h
new file mode 100755
index 0000000..7af516c
--- /dev/null
+++ b/src/flame/toml/_decode.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::toml {
+    void decode_inplace(php::string& str);
+}
\ No newline at end of file
diff --git a/src/flame/toml/_executor.cpp b/src/flame/toml/_executor.cpp
new file mode 100755
index 0000000..4ef63e0
--- /dev/null
+++ b/src/flame/toml/_executor.cpp
@@ -0,0 +1,70 @@
+#include "_executor.h"
+#include "_decode.h"
+
+namespace flame::toml {
+
+void _executor::operator ()(const toml_parser::parser& p, std::string_view chunk) {
+    buffer_.append(chunk.data(), chunk.size());
+}
+
+void _executor::operator ()(const toml_parser::parser& p) {
+    php::string raw = std::move(buffer_);
+    // std::cout << "(" << (int)p.value_type() << ") " << p.field() << " => [" << raw << "]\n";
+    php::value  val = _executor::restore(raw, p.value_type());
+    
+    // switch(p.container_type()) {
+    // case toml_parser::CONTAINER_TYPE_ARRAY:
+    // break;
+    // case toml_parser::CONTAINER_TYPE_ARRAY_TABLE:
+    // break;
+    // case toml_parser::CONTAINER_TYPE_TABLE:
+    // default:
+    //     ;
+    // }
+    set(root_, p.field(), p.value_array_index(), val);
+}
+
+php::value _executor::restore(php::string& raw, std::uint8_t value_type) {
+    php::value val;
+    // 根据类型还原数据
+    switch(value_type) {
+    case toml_parser::VALUE_TYPE_BOOLEAN:
+        val = raw.to_boolean();
+        break;
+    case toml_parser::VALUE_TYPE_INTEGER:
+        val = raw.to_integer();
+        break;
+    case toml_parser::VALUE_TYPE_HEX_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 16);
+        break;
+    case toml_parser::VALUE_TYPE_OCT_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 8);
+        break;
+    case toml_parser::VALUE_TYPE_BIN_INTEGER:
+        val = std::strtol(raw.data(), nullptr, 2);
+        break;
+    case toml_parser::VALUE_TYPE_INFINITY:
+        val = raw.data()[0] == '-' ? - std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+        break;
+    case toml_parser::VALUE_TYPE_NAN:
+        val = NAN;
+        break;
+    case toml_parser::VALUE_TYPE_FLOAT:
+        val = raw.to_float();
+        break;
+    case toml_parser::VALUE_TYPE_DATE:
+        val = php::datetime(raw.c_str());
+        break;
+    case toml_parser::VALUE_TYPE_BASIC_STRING:
+        decode_inplace(raw);
+        // fallthrough
+    case toml_parser::VALUE_TYPE_UNKNOWN: // 未知类型的数据按原始字符串处理
+    case toml_parser::VALUE_TYPE_RAW_STRING:
+    default:
+        val = raw;
+    }
+
+    return val;
+}
+
+}
diff --git a/src/flame/toml/_executor.h b/src/flame/toml/_executor.h
new file mode 100755
index 0000000..ce8a2cb
--- /dev/null
+++ b/src/flame/toml/_executor.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../../vendor.h"
+#include "toml.h"
+
+namespace flame::toml {
+
+    class _executor {
+    public:
+        _executor(php::array& root) noexcept
+        : root_(root) {}
+        void operator ()(const toml_parser::parser& p, std::string_view chunk);
+        void operator ()(const toml_parser::parser& p);
+
+    private:
+        php::array&   root_;
+        php::buffer buffer_;
+
+        static php::value restore(php::string& raw, std::uint8_t value_type);
+    };
+}
diff --git a/src/flame/toml/toml.cpp b/src/flame/toml/toml.cpp
new file mode 100755
index 0000000..5b27819
--- /dev/null
+++ b/src/flame/toml/toml.cpp
@@ -0,0 +1,120 @@
+#include "toml.h"
+#include "_executor.h"
+#include <fstream>
+
+namespace flame::toml {
+
+    void set(php::array& root, std::string_view prefix, std::size_t index, const php::value& v) {
+        std::size_t pe = -1, ps = -1;
+        zval x;
+        ZVAL_ARR(&x, static_cast<zend_array*>(root));
+        php::array ctr (&x, true); // 纯引用：容器数组
+        std::string_view field = prefix.substr( prefix.find_last_of('.') + 1 );
+        prefix = prefix.substr(0, prefix.size() - field.size() - 1);
+        do {
+            ps = pe = pe + 1;
+            pe = prefix.find_first_of('.', ps); // prefix 存在，但不含有 . 也需要进行下面动作
+            auto sv = prefix.substr(ps, pe - ps);
+            if (sv.size() == 0 || sv == "$") {
+                // root 元素不做处理
+            }
+            else if (sv == "#") {
+                php::value y = ctr.get(index);
+                if(y.type_of(php::TYPE::UNDEFINED)) {
+                    php::value z = php::array(4);
+                    ctr.set(index, z, /* seperate = */false); // 更新原数组
+                    ZVAL_ARR(&x, static_cast<zend_array*>(z));
+                }
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to set: container (index) is not an array", -1);
+            }
+            else {
+                php::string key {sv.data(), sv.size()};
+                php::value y = ctr.get(key);
+                if(y.type_of(php::TYPE::UNDEFINED)) {
+                    php::value z = php::array(4);
+                    ctr.set(key, z, /* seperate = */false); // 更新原数组
+                    ZVAL_ARR(&x, static_cast<zend_array*>(z));
+                }
+                else if(y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to set: container (field) is not an array", -1);
+            }
+        } while(pe != prefix.npos);
+
+        if(field.empty()) ctr.set(ctr.size(), v);
+        else ctr.set({field.data(), field.size()}, v);
+    }
+
+    php::value get(php::array root, std::string_view prefix, std::size_t index) {
+        std::size_t pe = -1, ps = -1;
+        zval x;
+        ZVAL_ARR(&x, static_cast<zend_array*>(root));
+        php::array ctr (&x, true); // 指向容器数组
+        std::string_view field = prefix.substr( prefix.find_last_of('.') + 1 );
+        prefix = prefix.substr(0, prefix.size() - field.size() - 1);
+        do {
+            ps = pe = pe + 1;
+            pe = prefix.find_first_of('.', ps); // prefix 存在，但不含有 . 也需要进行下面动作
+            auto sv = prefix.substr(ps, pe - ps);
+            if (sv.size() == 0 || sv == "$") {
+                // root 元素不做处理
+                // 无 prefix 不做处理
+            }
+            else if (sv == "#") {
+                php::value y = ctr.get(index);
+                if (y.type_of(php::TYPE::UNDEFINED)) return php::value();
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to get: container (index) is not an array", -1);
+            }
+            else {
+                php::value y = ctr.get({sv.data(), sv.size()});
+                if (y.type_of(php::TYPE::UNDEFINED)) return php::value();
+                else if (y.type_of(php::TYPE::ARRAY)) ZVAL_ARR(&x, static_cast<zend_array*>(y));
+                else throw php::exception(zend_ce_type_error, "Failed to get: container (field) is not an array", -1);
+            }
+        } while(pe != prefix.npos);
+
+        if(field.empty()) return ctr;
+        else return ctr.get({field.data(), field.size()});
+    }
+
+    static php::value parse_string(php::parameters& params) {
+        php::string s = params[0];
+        php::array r(8);
+        _executor  e(r);
+        llparse::toml::parser p({std::ref(e), std::ref(e)});
+        p.parse({s.data(), s.size()});
+        return r;
+    }
+
+    static php::value parse_file(php::parameters& params) {
+        std::string   f = params[0];
+        std::ifstream s {f.c_str()};
+        if (!s.is_open()) 
+            throw php::exception(zend_ce_error_exception, (boost::format("Failed to parse toml: unable to open file '%s'") % f).str(), -1);
+        
+        php::array r(8);
+        _executor  e(r);
+        llparse::toml::parser p({std::ref(e), std::ref(e)});
+        char        buffer[2048];
+        std::size_t length = 0;
+        while(!s.eof()) {
+            s.read(buffer, sizeof(buffer));
+            length = s.gcount();
+            if (length > 0) p.parse({buffer, length});
+        }
+        return r;
+    }
+
+    
+
+    void declare(php::extension_entry &ext) {
+        ext
+            .function<parse_string>("flame\\toml\\parse_string", {
+                {"toml", php::TYPE::STRING},
+            })
+            .function<parse_file>("flame\\toml\\parse_file", {
+                {"file", php::TYPE::STRING},
+            });
+    }
+}
\ No newline at end of file
diff --git a/src/flame/toml/toml.h b/src/flame/toml/toml.h
new file mode 100755
index 0000000..c0dab8b
--- /dev/null
+++ b/src/flame/toml/toml.h
@@ -0,0 +1,10 @@
+#include "../../vendor.h"
+#include <toml/toml.hpp>
+namespace toml_parser = llparse::toml;
+
+namespace flame::toml {
+    void declare(php::extension_entry &ext);
+
+          void set(php::array& r, std::string_view prefix, std::size_t index, const php::value& v);
+    php::value get(php::array  r, std::string_view prefix, std::size_t index);
+}
diff --git a/src/flame/udp/server.cpp b/src/flame/udp/server.cpp
new file mode 100644
index 0000000..945ff3a
--- /dev/null
+++ b/src/flame/udp/server.cpp
@@ -0,0 +1,165 @@
+#include "../../coroutine_queue.h"
+#include "../coroutine.h"
+#include "../time/time.h"
+#include "udp.h"
+#include "server.h"
+#include "../log/logger.h"
+
+namespace flame::udp {
+    
+    void server::declare(php::extension_entry& ext) {
+        php::class_entry<server> class_server("flame\\udp\\server");
+        class_server
+            .method<&server::__construct>("__construct", {
+                {"address", php::TYPE::STRING, false, false},
+                {"options", php::TYPE::ARRAY, false, true},
+            })
+            .method<&server::run>("run", {
+                {"length", php::TYPE::CALLABLE, false, false},
+            })
+            .method<&server::send_to>("send_to", {
+                {"data", php::TYPE::STRING, false, false},
+                {"address", php::TYPE::STRING, false, false},
+            })
+            .method<&server::close>("close");
+        ext.add(std::move(class_server));
+    }
+    server::server()
+    : socket_(gcontroller->context_x)
+    , closed_(false)
+    , concurrent_(8)
+    , max_(64 * 1024) {
+
+    }
+
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value server::__construct(php::parameters& params) {
+        auto pair = addr2pair(params[0]);
+        boost::system::error_code err;
+        boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind: %s") % err.message()).str()
+            , err.value());
+        std::uint16_t port = std::stoi(pair.second);
+        addr_.address(address);
+        addr_.port(port);
+
+        socket_.open(addr_.protocol());
+        boost::asio::socket_base::reuse_address opt1(true);
+        socket_.set_option(opt1);
+        reuse_port opt2(true);
+        socket_.set_option(opt2);
+        socket_.bind(addr_, err);
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to bind: %s") % err.message()).str()
+            , err.value());
+
+        if (params.size() > 1) {
+            php::array options = params[1];
+            if (options.exists("concurrent"))
+                concurrent_ = std::max(std::min(static_cast<int>(options.get("concurrent")), 256), 1);
+            if (options.exists("max"))
+                max_ = std::min(std::max(static_cast<int>(options.get("max")) , 1), 64 * 1024);
+        }
+        return nullptr;
+    }
+
+    php::value server::run(php::parameters& params) {
+        php::callable cb_ = params[0];
+
+        coroutine_handler ch {coroutine::current};
+        coroutine_queue<std::pair<php::string, php::string>> q(128);
+        // 启动若干协程, 然后进行"并行>"消费
+        int count = concurrent_;
+        for (int i = 0; i < concurrent_; ++i) {
+            // 启动协程开始消费
+            coroutine::start(php::value([&q, &count, &ch, &cb_, i] (php::parameters &params) -> php::value {
+                coroutine_handler cch {coroutine::current};
+                while(auto x = q.pop(cch)) {
+                    try {
+                        cb_.call({x->first, x->second});
+                    } catch(const php::exception& ex) {
+                        // 调用用户异常回调
+                        gcontroller->event("exception", {ex});
+                        // 记录错误信息
+                        php::object obj = ex;
+                        log::logger_->stream() << "[" << time::iso() << "] (ERROR) Uncaught Exception in UDP handler: " << obj.call("__toString") << std::endl;
+                    }
+                }
+                if (--count == 0) ch.resume();
+                return nullptr;
+            }));
+        }
+        // 生产
+        std::size_t len = 0;
+        boost::system::error_code err;
+        boost::asio::ip::udp::endpoint ep;
+        php::buffer buffer;
+        while(!closed_) {
+            socket_.async_receive_from(boost::asio::buffer(buffer.prepare(max_), max_), ep
+                , [&len, &err, &ch] (const boost::system::error_code& error, std::size_t nread) {
+
+                if (error) err = error;
+                else len = nread;
+                ch.resume();
+            });
+            ch.suspend();
+            // 存在可能被关闭, 直接返回
+            if (err == boost::asio::error::operation_aborted) goto CLOSING;
+            else if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to read TCP socket: %s") % err.message()).str()
+                , err.value());
+            buffer.commit(len);
+            q.push(std::make_pair<php::string, php::string>(php::string(std::move(buffer))
+                , (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str()), ch);
+        }
+CLOSING:
+        q.close();
+        ch.suspend();
+        return nullptr;
+    }
+
+    php::value server::send_to(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+        php::string data = params[0];
+        auto pair = addr2pair(params[1]);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to send udp packet: illegal address format"
+            , -1);
+
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second
+            , [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err == boost::asio::error::operation_aborted) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
+
+        // 发送
+        int sent = 0;
+        for(auto i=eps.begin(); i!=eps.end(); ++i) {
+            socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+            if (!err) return nullptr;
+        }
+
+        throw php::exception(zend_ce_exception
+            , (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+            , err.value());
+    }
+
+    php::value server::close(php::parameters& params) {
+        closed_ = true;
+        boost::system::error_code err;
+        socket_.close(err);
+        return true;
+    }
+}
diff --git a/src/flame/udp/server.h b/src/flame/udp/server.h
new file mode 100644
index 0000000..d3b0d4a
--- /dev/null
+++ b/src/flame/udp/server.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
+#include "../coroutine.h"
+
+namespace flame::udp {
+    class server: public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        server();
+        php::value __construct(php::parameters& params);
+        php::value run(php::parameters& params);
+        php::value send_to(php::parameters& params);
+        php::value close(php::parameters& params);
+    private:
+        boost::asio::ip::udp::socket socket_;
+        boost::asio::ip::udp::endpoint addr_;
+        int concurrent_;
+        int max_;
+        bool closed_;
+    };
+}
diff --git a/src/flame/udp/socket.cpp b/src/flame/udp/socket.cpp
new file mode 100644
index 0000000..25351a1
--- /dev/null
+++ b/src/flame/udp/socket.cpp
@@ -0,0 +1,177 @@
+#include "../coroutine.h"
+#include "udp.h"
+#include "socket.h"
+
+namespace flame::udp {
+
+    void socket::declare(php::extension_entry& ext)  {
+        php::class_entry<socket> class_socket("flame\\udp\\socket");
+        class_socket
+            .property({"local_address", ""})
+            .property({"remote_address", ""})
+            .method<&socket::__construct>("__construct", {
+                {"address", php::TYPE::STRING, false, true},
+            })
+            .method<&socket::recv_from>("recv_from", {
+                {"address", php::TYPE::STRING, true, true},
+            })
+            .method<&socket::recv>("recv")
+            .method<&socket::send_to>("send_to", {
+                {"data", php::TYPE::STRING, false, false},
+                {"address", php::TYPE::STRING, false, false},
+            })
+            .method<&socket::send>("send")
+            .method<&socket::close>("close");
+        ext.add(std::move(class_socket));
+    }
+
+    socket::socket()
+    : socket_(gcontroller->context_x)
+    , connected_(false)
+    , max_(64 * 1024) {
+
+    }
+
+    typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
+
+    php::value socket::__construct(php::parameters& params) {
+        boost::system::error_code err;
+        php::array option;
+        if (params.size() > 0 && params[0].type_of(php::TYPE::STRING)) {
+            auto pair = addr2pair(params[0]);
+            boost::asio::ip::address address = boost::asio::ip::make_address(std::string_view(pair.first.data(), pair.first.size()), err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to bind: %s") % err.message()).str()
+                , err.value());
+            std::uint16_t port = std::stoi(pair.second);
+            auto ep = boost::asio::ip::udp::endpoint(address, port);
+            socket_.open(ep.protocol());
+            boost::asio::socket_base::reuse_address opt1(true);
+            socket_.set_option(opt1);
+            reuse_port opt2(true);
+            socket_.set_option(opt2);
+            socket_.bind(ep, err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("failed to bind: %s") % err.message()).str()
+                , err.value());
+            if (params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) {
+                option = params[0];
+                goto PARSE_OPTION;
+            }
+        }
+        else {
+            socket_.open(boost::asio::ip::udp::v6(), err);
+            if (err) socket_.open(boost::asio::ip::udp::v4(), err);
+            if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to bind: %s") % err.message()).str()
+                , err.value());
+            if (params.size() > 0 && params[0].type_of(php::TYPE::ARRAY)) {
+                option = params[0];
+                goto PARSE_OPTION;
+            }
+        }
+        return nullptr;
+PARSE_OPTION:
+        if (option.exists("max"))
+            max_ = std::min(std::max(static_cast<int>(option.get("max")), 64), 64 * 1024);
+        return nullptr;
+    }
+
+    php::value socket::recv(php::parameters& params) {
+        if (!connected_) throw php::exception(zend_ce_error_exception
+            , "Failed to send: socket not connected"
+            , -1);
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+        boost::system::error_code err;
+        std::size_t len = 0;
+        len = socket_.async_receive(boost::asio::buffer(buffer_.prepare(max_), max_), ch[err]);
+        buffer_.commit(len);
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to recv UDP packet: %s") % err.message()).str()
+            , err.value());
+        else return php::string(std::move(buffer_));
+    }
+
+    php::value socket::recv_from(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+        // 使用下面锁保证不会同时进行读取
+        coroutine_guard guard(rmutex_, ch);
+
+        boost::system::error_code err;
+        std::size_t len = 0;
+        boost::asio::ip::udp::endpoint ep;
+        len = socket_.async_receive_from(boost::asio::buffer(buffer_.prepare(max_), max_), ep, ch[err]);
+        buffer_.commit(len);
+        // 数据返回
+        if (err == boost::asio::error::operation_aborted || err == boost::asio::error::eof) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("failed to read UDP socket: %s") % err.message()).str()
+            , err.value());
+        if (params.size() > 0) params[0] = (boost::format("%s:%d") % ep.address().to_string(err) % ep.port()).str();
+        return php::string(std::move(buffer_));
+    }
+
+    php::value socket::send(php::parameters& params) {
+        if (!connected_) throw php::exception(zend_ce_error_exception
+            , "Failed to send: connected socket required"
+            , -1);
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+        std::string data = params[0];
+        socket_.async_send(boost::asio::buffer(data), ch[err]);
+
+        if (!err || err == boost::asio::error::operation_aborted
+            || err == boost::asio::error::connection_refused) return nullptr;
+        else throw php::exception(zend_ce_exception
+            , (boost::format("Failed to write UDP socket: %s") % err.message()).str()
+            , err.value());
+        return nullptr;
+    }
+
+    php::value socket::send_to(php::parameters& params) {
+        coroutine_handler ch{coroutine::current};
+
+        boost::system::error_code err;
+        php::string data = params[0];
+        auto pair = addr2pair(params[1]);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to send udp packet: illegal address format"
+            , -1);
+
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second, 
+            [&ch, &eps, &err] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) {
+
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err == boost::asio::error::operation_aborted) return nullptr;
+        else if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to resolve address: %s") % err.message()).str()
+            , err.value());
+
+        // 发送
+        int sent = 0;
+        for(auto i=eps.begin(); i!=eps.end(); ++i) {
+            socket_.async_send_to(boost::asio::buffer(data.c_str(), data.size()), *i, ch[err]);
+            if (!err) return nullptr;
+        }
+
+        throw php::exception(zend_ce_exception
+            , (boost::format("Failed to send UDP packet: %s") % err.message()).str()
+            , err.value());
+    }
+    
+    php::value socket::close(php::parameters& params) {
+        connected_ = false;
+        socket_.shutdown(boost::asio::socket_base::shutdown_both);
+        return nullptr;
+    }
+}
diff --git a/src/flame/udp/socket.h b/src/flame/udp/socket.h
new file mode 100644
index 0000000..8e95ae5
--- /dev/null
+++ b/src/flame/udp/socket.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../../vendor.h"
+#include "../../coroutine_mutex.h"
+#include "../coroutine.h"
+
+namespace flame::udp {
+    
+    class socket : public php::class_base {
+    public:
+        static void declare(php::extension_entry &ext);
+        socket();
+        php::value __construct(php::parameters &param);
+        php::value recv_from(php::parameters &param);
+        php::value recv(php::parameters& params);
+        php::value send_to(php::parameters &params);
+        php::value send(php::parameters& params);
+        php::value close(php::parameters &params);
+
+    private:
+        boost::asio::ip::udp::socket socket_;
+        php::buffer                  buffer_;
+        coroutine_mutex              rmutex_;
+        bool                      connected_;
+        int                             max_;
+
+        friend class server;
+        friend php::value connect(php::parameters &params);
+    };
+} // namespace flame::udp
diff --git a/src/flame/udp/udp.cpp b/src/flame/udp/udp.cpp
new file mode 100644
index 0000000..63f7e62
--- /dev/null
+++ b/src/flame/udp/udp.cpp
@@ -0,0 +1,75 @@
+#include "../coroutine.h"
+#include "udp.h"
+#include "socket.h"
+#include "server.h"
+
+namespace flame::udp {
+
+    std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
+
+    void declare(php::extension_entry& ext) {
+        gcontroller->on_init([] (const php::array& options) {
+            resolver_.reset(new boost::asio::ip::udp::resolver(gcontroller->context_x));
+        })->on_stop([] () {
+            resolver_.reset();
+        });
+        ext
+            .function<connect>("flame\\udp\\connect", {
+                {"address", php::TYPE::STRING},
+            });
+        socket::declare(ext);
+        server::declare(ext);
+    }
+
+    php::value connect(php::parameters& params) {
+        php::object obj(php::class_entry<socket>::entry());
+        socket *ptr = static_cast<socket *>(php::native(obj));
+
+        std::string str = params[0];
+        auto pair = addr2pair(str);
+        if (pair.first.empty() || pair.second.empty()) throw php::exception(zend_ce_type_error
+            , "Failed to connect UDP socket: illegal address format"
+            , -1);
+
+        coroutine_handler ch{coroutine::current};
+        boost::system::error_code err;
+        boost::asio::ip::udp::resolver::results_type eps;
+        resolver_->async_resolve(pair.first, pair.second
+            , [&err, &obj, &ptr, &eps, &ch] (const boost::system::error_code& error, boost::asio::ip::udp::resolver::results_type results) { // DNS 地址解析
+
+            if (error) err = error;
+            else eps = results;
+            ch.resume();
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
+                , (boost::format("Failed to resolve address: %s") % err.message()).str()
+                , err.value());
+
+        boost::asio::async_connect(ptr->socket_, eps
+            , [&err, &obj, &ptr, &ch](const boost::system::error_code &error, const boost::asio::ip::udp::endpoint &edp) { // 连接
+
+            if (error) err = error;
+            else {
+                obj.set("local_address", (boost::format("%s:%d") % ptr->socket_.local_endpoint().address().to_string() % ptr->socket_.local_endpoint().port()).str());
+                obj.set("remote_address", (boost::format("%s:%d") % ptr->socket_.remote_endpoint().address().to_string() % ptr->socket_.remote_endpoint().port()).str());
+            }
+            ch.resume();
+        });
+        ch.suspend();
+        if (err) throw php::exception(zend_ce_exception
+            , (boost::format("Failed to connect UDP socket: %s") % err.message()).str()
+            , err.value());
+        ptr->connected_ = true;
+        return std::move(obj);
+    }
+    
+    std::pair<std::string, std::string> addr2pair(const std::string &addr) {
+        char *s = const_cast<char*>(addr.data()), *p, *e = s + addr.size();
+        for (p = e - 1; p > s; --p) if (*p == ':') break; // 分离 地址与端口
+        if (*p != ':') return std::make_pair<std::string, std::string>(std::string(addr), "");
+        else return std::make_pair<std::string, std::string>(
+                std::string(s, p - s), std::string(p+1, e-p-1));
+    }
+
+} // namespace flame::tcp
diff --git a/src/flame/udp/udp.h b/src/flame/udp/udp.h
new file mode 100644
index 0000000..b61a209
--- /dev/null
+++ b/src/flame/udp/udp.h
@@ -0,0 +1,10 @@
+#pragma once
+#include "../../vendor.h"
+
+namespace flame::udp {
+    extern std::unique_ptr<boost::asio::ip::udp::resolver> resolver_;
+    void declare(php::extension_entry &ext);
+    php::value bind_and_listen(php::parameters& params);
+    php::value connect(php::parameters &params);
+    std::pair<std::string, std::string> addr2pair(const std::string& addr);
+} // namespace flame::tcp
diff --git a/src/flame/version.cpp b/src/flame/version.cpp
new file mode 100644
index 0000000..c1af73a
--- /dev/null
+++ b/src/flame/version.cpp
@@ -0,0 +1,43 @@
+#include "version.h"
+#include <boost/version.hpp>
+#include <hiredis/hiredis.h>
+#include <nghttp2/nghttp2ver.h>
+#include <openssl/opensslv.h>
+#include <mariadb/mariadb_version.h>
+#include <librdkafka/rdkafka.h>
+// #include <mongoc/mongoc-version.h>
+#include <mongoc/mongoc.h>
+#include <curl/curlver.h>
+
+namespace flame {
+
+#define VERSION_MACRO(major, minor, patch) VERSION_JOIN(major, minor, patch)
+#define VERSION_JOIN(major, minor, patch) #major"."#minor"."#patch
+
+static std::string openssl_version_str() {
+    std::string version = (boost::format("%d.%d.%d")
+        % ((OPENSSL_VERSION_NUMBER & 0xff0000000L) >> 28)
+        % ((OPENSSL_VERSION_NUMBER & 0x00ff00000L) >> 20)
+        % ((OPENSSL_VERSION_NUMBER & 0x0000ff000L) >> 12) ).str();
+    char status = (OPENSSL_VERSION_NUMBER & 0x000000ff0L) >>  4;
+    if (status > 0) {
+        version.push_back('a' + status - 1);
+    }
+    return version;
+}
+
+    void version::declare(php::extension_entry& ext) {
+        ext
+            .desc({"vendor/openssl", openssl_version_str()})
+            .desc({"vendor/boost", BOOST_LIB_VERSION})
+            .desc({"vendor/phpext", PHPEXT_LIB_VERSION})
+            .desc({"vendor/hiredis", VERSION_MACRO(HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH)})
+            // .desc({"vendor/mysqlc", mysql_get_client_info()})
+            .desc({"vendor/mariac", MARIADB_PACKAGE_VERSION})
+            .desc({"vendor/amqpcpp", "4.1.5"})
+            .desc({"vendor/rdkafka", rd_kafka_version_str()})
+            .desc({"vendor/mongoc", MONGOC_VERSION_S})
+            .desc({"vendor/nghttp2", NGHTTP2_VERSION})
+            .desc({"vendor/curl", LIBCURL_VERSION});
+    }
+}
\ No newline at end of file
diff --git a/src/flame/version.h b/src/flame/version.h
new file mode 100644
index 0000000..471fd7f
--- /dev/null
+++ b/src/flame/version.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "../vendor.h"
+
+namespace flame {
+    class version {
+    public:
+        static void declare(php::extension_entry& ext);
+    };   
+}
\ No newline at end of file
diff --git a/src/flame/worker.cpp b/src/flame/worker.cpp
new file mode 100644
index 0000000..018c1ae
--- /dev/null
+++ b/src/flame/worker.cpp
@@ -0,0 +1,333 @@
+#include "../util.h"
+#include "../worker_logger.h"
+#include "controller.h"
+#include "worker.h"
+#include "coroutine.h"
+
+#include "queue.h"
+#include "mutex.h"
+#include "log/log.h"
+#include "log/logger.h"
+#include "os/os.h"
+#include "time/time.h"
+#include "mysql/mysql.h"
+#include "redis/redis.h"
+#include "mongodb/mongodb.h"
+#include "kafka/kafka.h"
+#include "rabbitmq/rabbitmq.h"
+#include "tcp/tcp.h"
+#include "udp/udp.h"
+#include "http/http.h"
+#include "hash/hash.h"
+#include "encoding/encoding.h"
+#include "compress/compress.h"
+#include "toml/toml.h"
+
+namespace flame {
+    std::shared_ptr<worker> worker::ww_;
+
+    void worker::declare(php::extension_entry& ext) {
+        ext
+        .on_request_shutdown([] (php::extension_entry& ext) -> bool {
+            if (gcontroller->status & controller::STATUS_INITIALIZED) {
+                if (php::error::exists()) 
+                    log::logger_->stream() << "[" << util::system_time() << "] (WARNING) process exited prematurely: uncaught exception / error\n" << std::endl;
+                if (!(gcontroller->status & controller::STATUS_RUN)) 
+                    log::logger_->stream() << "[" << util::system_time() << "] (WARNING) process exited prematurely: missing 'flame\\run();'" << std::endl;
+                
+                gcontroller->status & controller::STATUS_EXCEPTION ? _exit(-1) : _exit(0);
+            }
+            return true;
+        })
+        .function<worker::init>("flame\\init", {
+            {"process_name", php::TYPE::STRING},
+            {"options", php::TYPE::ARRAY, false, true},
+        })
+        
+        .function<worker::go>("flame\\go", {
+            {"coroutine", php::TYPE::CALLABLE},
+        })
+        .function<worker::on>("flame\\on", {
+            {"event", php::TYPE::STRING},
+            {"callback", php::TYPE::CALLABLE},
+        })
+        .function<worker::off>("flame\\off", {
+            {"event", php::TYPE::STRING},
+        })
+        .function<worker::run>("flame\\run")
+        .function<worker::quit>("flame\\quit")
+        .function<worker::co_id>("flame\\co_id")
+        .function<worker::co_ct>("flame\\co_ct")
+        .function<worker::co_ct>("flame\\co_count")
+        .function<worker::set>("flame\\set", {
+            {"target", php::TYPE::ARRAY, true},
+            {"fields", php::TYPE::STRING},
+            {"values", php::TYPE::UNDEFINED},
+        })
+        .function<worker::get>("flame\\get", {
+            {"target", php::TYPE::ARRAY},
+            {"fields", php::TYPE::STRING},
+        })
+        .function<worker::send>("flame\\send", {
+            {"target", php::TYPE::INTEGER},
+            {"data", php::TYPE::UNDEFINED},
+        });
+        // 顶级命名空间
+        flame::mutex::declare(ext);
+        flame::queue::declare(ext);
+        // 子命名空间模块注册
+        flame::log::declare(ext);
+        flame::os::declare(ext);
+        flame::time::declare(ext);
+        flame::mysql::declare(ext);
+        flame::redis::declare(ext);
+        flame::mongodb::declare(ext);
+        flame::kafka::declare(ext);
+        flame::rabbitmq::declare(ext);
+        flame::tcp::declare(ext);
+        flame::udp::declare(ext);
+        flame::http::declare(ext);
+        flame::hash::declare(ext);
+        flame::encoding::declare(ext);
+        flame::compress::declare(ext);
+        flame::toml::declare(ext);
+    }
+
+    php::value worker::init(php::parameters& params) {
+        php::array options = php::array(0);
+        if(params.size() > 1 && params[1].type_of(php::TYPE::ARRAY)) options = params[1];
+        if (options.exists("timeout"))
+            gcontroller->worker_quit = std::min(std::max(static_cast<int>(options.get("timeout")), 200), 100000);
+        else
+            gcontroller->worker_quit = 3000;
+        
+        gcontroller->status |= controller::STATUS_INITIALIZED;
+        // 设置进程标题
+        std::string title = params[0];
+        if (gcontroller->worker_size > 0) {
+            std::string index = gcontroller->env["FLAME_CUR_WORKER"].to_string();
+            php::callable("cli_set_process_title").call({title + " (php-flame/" + index + ")"});
+        }
+        else
+            php::callable("cli_set_process_title").call({title + " (php-flame/w)"});
+
+        worker::ww_.reset(new worker(gcontroller->worker_idx));
+
+        // 信号监听启动
+        worker::ww_->sw_watch();
+        if(gcontroller->worker_size > 0) worker::ww_->ipc_start();
+
+        gcontroller->init(options); // 首个 logger 的初始化过程在 logger 注册 on_init 回调中进行（异步的）
+        return nullptr;
+    }
+
+    php::value worker::go(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        coroutine::start(php::callable([fn = php::callable(params[0])] (php::parameters& params) -> php::value {
+            try { // 函数调用产生了堆栈过程，可以捕获异常
+                fn.call(); 
+            } catch (const php::exception &ex) {
+                gcontroller->event("exception", {ex}); // 调用用户异常回调
+                php::object obj = ex; // 记录错误信息
+                log::logger_->stream() << "[" << time::iso() << "] (FATAL) " << obj.call("__toString") << std::endl;
+
+                boost::asio::post(gcontroller->context_x, [] () {
+                    gcontroller->status |= controller::STATUS_EXCEPTION;
+                    gcontroller->context_x.stop();
+                    gcontroller->context_y.stop();
+                    gcontroller->context_z.stop();
+                });
+            }
+            return nullptr;
+        }));
+        // 如下形式无法在内部无法捕获到 PHP 错误（顶层栈）
+        // flame::coroutine::start(params[0]);
+        return nullptr;
+    }
+
+    php::value worker::on(php::parameters &params) {
+
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        std::string event = params[0].to_string();
+        if (!params[1].type_of(php::TYPE::CALLABLE)) throw php::exception(zend_ce_type_error, "Failed to set callback: callable required", -1);
+        // message 事件需要启动协程辅助，故需要对应的停止机制
+        if(event == "message" && gcontroller->cnt_event(event) == 0) {
+            worker::ww_->msg_start();
+        }
+        gcontroller->add_event(event, params[1]);
+        return nullptr;
+    }
+
+    php::value worker::off(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        std::string event = params[0].to_string();
+        gcontroller->del_event(event);
+        // message 事件启动了额外的协程，需要停止
+        if(event == "message"/*&& gcontroller->cnt_event(event) == 0 */) {
+            worker::ww_->msg_close();
+        }
+        return nullptr;
+    }
+
+    php::value worker::run(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_INITIALIZED) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        gcontroller->status |= controller::STATUS_RUN;
+
+        auto ywork = boost::asio::make_work_guard(gcontroller->context_y);
+        auto zwork = boost::asio::make_work_guard(gcontroller->context_z);
+        std::thread ts[4];
+        for (int i=0; i<3; ++i) {
+            ts[i] = std::thread([] {
+                gcontroller->context_y.run();
+            });
+        }
+        ts[3] = std::thread([] {
+            gcontroller->context_z.run();
+        });
+        gcontroller->context_x.run();
+        ywork.reset();
+        zwork.reset();
+
+        worker::ww_->sw_close();
+        worker::ww_->ipc_close();
+        worker::ww_->lm_close();
+
+        for (int i=0; i<4; ++i) {
+            ts[i].join();
+        }
+        gcontroller->stop();
+        worker::ww_.reset();
+        return nullptr;
+    }
+
+    php::value worker::quit(php::parameters& params) {
+        if ((gcontroller->status & controller::STATUS_RUN) == 0)
+            throw php::exception(zend_ce_parse_error, "Failed to run flame: exception or missing 'flame\\init()' ?", -1);
+
+        gcontroller->context_x.stop();
+        gcontroller->context_y.stop();
+        gcontroller->context_z.stop();
+        return nullptr;
+    }
+
+    php::value worker::co_id(php::parameters& params) {
+        return reinterpret_cast<uintptr_t>(coroutine::current.get());
+    }
+
+    php::value worker::co_ct(php::parameters& params) {
+        return coroutine::count;
+    }
+
+    php::value worker::get(php::parameters& params) {
+        php::array  target = params[0];
+        php::string fields = params[1];
+        return flame::toml::get(target, { fields.data(), fields.size() }, 0);
+    }
+
+    php::value worker::set(php::parameters& params) {
+        php::array  target = params.get(0, true);
+        php::string fields = params[1];
+        php::value  values = params[2];
+        flame::toml::set(target, {fields.data(), fields.size()}, 0, values);
+        return nullptr;
+    }
+
+    php::value worker::send(php::parameters& params) {
+        worker::ww_->ipc_notify(static_cast<int>(params[0]), params[1]);
+        return nullptr;
+    }
+
+    worker::worker(std::uint8_t idx)
+    : signal_watcher(gcontroller->context_z)
+    , worker_ipc(gcontroller->context_z, idx)
+    , worker_logger_manager(this) {
+
+    }
+
+    std::ostream& worker::output() {
+        return log::logger_ ? log::logger_->stream() : std::clog;
+    }
+
+    // !!! 此函数在工作线程中运行
+    bool worker::on_signal(int sig) {
+        switch(sig) {
+        case SIGINT:
+            if(gcontroller->worker_size > 0) break; // 多进程模式忽略
+            [[fallthrough]]; // 单进程模式通 SIGQUIT 处理
+        case SIGQUIT:
+            boost::asio::post(gcontroller->context_x, [] () {
+                if(gcontroller->worker_size > 0) {
+                    gcontroller->status |= controller::STATUS_EXCEPTION;
+                }
+                gcontroller->context_x.stop();
+                gcontroller->context_y.stop();
+                gcontroller->context_z.stop();
+            });
+            return false; // 多次停止信号立即结束
+        break;
+        case SIGTERM:
+            coroutine::start(php::callable([] (php::parameters& params) -> php::value { // 通知用户退出(超时后主进程将杀死子进程)
+                gcontroller->event("quit");
+                return nullptr;
+            }));
+        case SIGUSR1:
+            boost::asio::post(gcontroller->context_x, [] () {
+                gcontroller->status ^= controller::STATUS_CLOSECONN;
+            });
+            break;
+        case SIGUSR2: // 日志重载, 与子进程无关
+            break;
+        }
+        return true;
+    }
+    // !!! 此函数在工作线程中工作
+    bool worker::on_message(std::shared_ptr<ipc::message_t> msg) {
+        switch(msg->command) {
+        case ipc::COMMAND_MESSAGE_STRING:
+            boost::asio::post(gcontroller->context_x, [this, self = worker::ww_, msg] () {
+                msgq_.push_back(php::string(&msg->payload[0], msg->length));
+                if(msgq_.size() == 1 && msgc_) msgc_.resume();
+            });
+        break;
+        case ipc::COMMAND_MESSAGE_JSON: // !!! json_decode('"abc"') === null !!!
+            boost::asio::post(gcontroller->context_x, [this, self = worker::ww_, msg] () {
+                msgq_.push_back(php::json_decode(&msg->payload[0], msg->length));
+                if(msgq_.size() == 1 && msgc_) msgc_.resume();
+            });
+        break;
+        }
+        return true;
+    }
+
+    void worker::msg_start() {
+        coroutine::start(php::callable([this, self = worker::ww_] (php::parameters& params) -> php::value {
+            coroutine_handler ch {coroutine::current};
+            while(true) {
+                while(msgq_.empty()) {
+                    msgc_.reset(ch);
+                    ch.suspend();
+                    msgc_.reset();
+                }
+                php::value v = msgq_.front();
+                msgq_.pop_front();
+
+                if(v.type_of(php::TYPE::UNDEFINED)) break;
+                gcontroller->event("message", {v});
+            }
+            return nullptr;
+        }));
+    }
+
+    void worker::msg_close() {
+        msgq_.push_back(php::value());
+        if(msgq_.size() == 1 && msgc_) msgc_.resume();
+    }
+}
\ No newline at end of file
diff --git a/src/flame/worker.h b/src/flame/worker.h
new file mode 100644
index 0000000..2ff56f1
--- /dev/null
+++ b/src/flame/worker.h
@@ -0,0 +1,53 @@
+#pragma once
+#include "../vendor.h"
+#include "../worker_logger_manager.h"
+#include "../signal_watcher.h"
+#include "../worker_ipc.h"
+#include "../coroutine_queue.h"
+
+namespace flame {
+    class worker: public std::enable_shared_from_this<worker>, public signal_watcher, public worker_ipc, public worker_logger_manager {
+    public:
+        static inline std::shared_ptr<worker> get() {
+            return worker::ww_;
+        }
+        static void declare(php::extension_entry& ext);
+        // 主流程相关
+        static php::value init(php::parameters& params);
+        static php::value go(php::parameters& params);
+        static php::value on(php::parameters &params);
+        static php::value off(php::parameters& params);
+        static php::value run(php::parameters& params);
+        static php::value quit(php::parameters& params);
+        // 协程相关
+        static php::value co_id(php::parameters& params);
+        static php::value co_ct(php::parameters& params);
+        // 级联数组设置、读取
+        static php::value get(php::parameters& params);
+        static php::value set(php::parameters& params);
+        // 传递消息到其他工作进程
+        static php::value send(php::parameters& params);
+        
+        worker(std::uint8_t idx);
+        std::ostream& output() override;
+    protected:
+        virtual std::shared_ptr<signal_watcher> sw_self() override {
+            return std::static_pointer_cast<signal_watcher>(shared_from_this());
+        }
+        virtual std::shared_ptr<worker_ipc> ipc_self() override {
+            return std::static_pointer_cast<worker_ipc>(shared_from_this());
+        }
+        virtual std::shared_ptr<worker_logger_manager> lm_self() override {
+            return std::static_pointer_cast<worker_logger_manager>(shared_from_this());
+        }
+        bool on_signal(int sig) override;
+        bool on_message(std::shared_ptr<ipc::message_t> msg) override;
+    private:
+        static std::shared_ptr<worker> ww_;
+        std::list<php::value> msgq_; // notify queue
+        coroutine_handler     msgc_; // notify coroutine
+
+        void msg_start();
+        void msg_close();
+    };
+}
\ No newline at end of file
diff --git a/src/ipc.cpp b/src/ipc.cpp
new file mode 100644
index 0000000..da656a5
--- /dev/null
+++ b/src/ipc.cpp
@@ -0,0 +1,34 @@
+#include "ipc.h"
+
+static boost::pool<> pool_(ipc::MESSAGE_INIT_CAPACITY);
+
+static void free_message(ipc::message_t* msg) {
+    pool_.free(msg);
+}
+
+std::shared_ptr<ipc::message_t> ipc::create_message(std::uint16_t length) {
+    if(length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+        ipc::message_t* msg = (ipc::message_t*)pool_.malloc();
+        msg->length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t);
+        return std::shared_ptr<ipc::message_t>(msg, free_message);
+    }
+    else {
+        length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1);
+        ipc::message_t* msg = (ipc::message_t*)malloc(length);
+        msg->length = length - sizeof(ipc::message_t);
+        return std::shared_ptr<ipc::message_t>(msg, free);
+    }
+}
+
+void ipc::relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length) {
+    length = (sizeof(ipc::message_t) + length + 4096 - 1) & ~(4096-1);
+
+    ipc::message_t* m = (ipc::message_t*)malloc(length);
+    std::memcpy(m, msg.get(), sizeof(ipc::message_t) + copy);
+    msg.reset(m, free);
+}
+
+std::uint32_t ipc::create_uid() {
+    static std::uint32_t start = 0;
+    return ++start;
+}
diff --git a/src/ipc.h b/src/ipc.h
new file mode 100644
index 0000000..c971ff3
--- /dev/null
+++ b/src/ipc.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+
+class ipc {
+public:
+    // 传输协议：消息格式
+    struct message_t {
+        std::uint8_t  command;
+        std::uint8_t  source;
+        std::uint8_t  target;
+        std::uint8_t  xdata[3];
+        std::uint16_t length;
+        std::uint32_t unique_id;
+        char payload[0];
+    };
+    // 传输协议：命令
+    enum command_t {
+        MESSAGE_INIT_CAPACITY = 2048,
+
+        COMMAND_REGISTER       = 0x01,
+        COMMAND_LOGGER_CONNECT = 0x02,
+        COMMAND_LOGGER_DESTROY = 0x03,
+        COMMAND_LOGGER_DATA    = 0x04,
+
+        COMMAND_TRANSFER_TO_CHILD = 0x10,
+        COMMAND_MESSAGE_STRING = 0x10,
+        COMMAND_MESSAGE_JSON   = 0x11,
+    };
+    struct callback_t {
+        coroutine_handler&               cch;
+        std::shared_ptr<ipc::message_t>& res;
+    };
+    // 消息容器构建
+    static std::shared_ptr<message_t> create_message(std::uint16_t length = ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t));
+    // 消息容器长度调整
+    static void relloc_message(std::shared_ptr<ipc::message_t>& msg, std::uint16_t copy, std::uint16_t length);
+    // 生成消息ID
+    static std::uint32_t create_uid();
+    // 请求并等待响应   
+    virtual std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> data, coroutine_handler& ch) = 0;
+    // 请求（无响应）
+    virtual void ipc_request(std::shared_ptr<ipc::message_t> data) = 0;
+};
diff --git a/src/master_ipc.cpp b/src/master_ipc.cpp
new file mode 100644
index 0000000..54cb38e
--- /dev/null
+++ b/src/master_ipc.cpp
@@ -0,0 +1,122 @@
+#include "master_ipc.h"
+#include "master_logger.h"
+#include "util.h"
+#include "coroutine.h"
+
+master_ipc::master_ipc(boost::asio::io_context& io)
+: io_(io)
+, server_(io, boost::asio::local::stream_protocol()) {
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getpid()).str();
+}
+
+master_ipc::~master_ipc() {
+    ipc_close();
+    std::filesystem::remove(svrsck_);
+}
+
+void master_ipc::ipc_start() {
+    ::coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_run, ipc_self(), std::placeholders::_1));
+}
+// !!! 工作线程中的协程
+void master_ipc::ipc_run(coroutine_handler ch) {
+    std::error_code ec;
+    std::filesystem::remove(svrsck_, ec);
+    boost::asio::local::stream_protocol::endpoint addr(svrsck_);
+    server_.bind(addr);
+    server_.listen();
+    boost::system::error_code error;
+    while(true) {
+        auto sock = std::make_shared<boost::asio::local::stream_protocol::socket>(io_);
+        server_.async_accept(*sock, ch[error]);
+        if (error) break;
+        coroutine::start(io_.get_executor(), std::bind(&master_ipc::ipc_read, ipc_self(), sock, std::placeholders::_1));
+    }
+    if (error && error != boost::asio::error::operation_aborted)
+        output() << "[" << util::system_time() << "] (ERROR) Failed to accept ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    
+    server_.close(error);
+}
+
+void master_ipc::ipc_close() {
+    boost::system::error_code error;
+    server_.close(error);
+    for(auto i=socket_.begin();i!=socket_.end();++i) {
+        i->second->close(error);
+    }
+}
+
+void master_ipc::ipc_read(socket_ptr sock, coroutine_handler ch) {
+    boost::system::error_code error;
+    while(true) {
+        auto msg = ipc::create_message();
+        boost::asio::async_read(*sock, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
+        if (error) break;
+        if (msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+            ipc::relloc_message(msg, 0, msg->length);
+        }
+        boost::asio::async_read(*sock, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
+        if (error) break;
+        if (!on_message(msg, sock)) break;
+    }
+    if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
+        output() << "[" << util::system_time() << "] (ERROR) Failed to read ipc connection: (" << error.value() << ") " << error.message() << "\n";
+    // 发生次数较少，效率不太重要了
+    for(auto i=socket_.begin();i!=socket_.end();++i) {
+        if(i->second == sock) {
+            socket_.erase(i);
+            break;
+        }
+    }
+    sock->close(error);
+}
+
+bool master_ipc::on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock) {
+    if(msg->command >= ipc::COMMAND_TRANSFER_TO_CHILD) send(msg);
+    else if(msg->command == ipc::COMMAND_REGISTER) socket_[msg->source] = sock;
+    return true;
+}
+
+std::shared_ptr<ipc::message_t> master_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
+    std::shared_ptr<ipc::message_t> res;
+    callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    req->source = 0;
+    send(req);
+    ch.suspend();
+    return res;
+}
+
+void master_ipc::ipc_request(std::shared_ptr<ipc::message_t> req) {
+    req->source = 0;
+    send(req);
+}
+
+void master_ipc::send(std::shared_ptr<ipc::message_t> msg) {
+    boost::asio::post(io_, [this, self = ipc_self(), msg] () {
+        if(sendq_.empty()) {
+            sendq_.push_back(msg);
+            send_next();
+        }else
+            sendq_.push_back(msg);
+    });
+}
+
+void master_ipc::send_next() {
+    if (sendq_.empty()) return;
+    std::shared_ptr<ipc::message_t> msg = sendq_.front();
+    auto i = socket_.find(msg->target);
+    if(i == socket_.end()) {
+        // std::cout << "message_dropped: " << (int) msg->command << " => " << (int) msg->target << "\n";
+        sendq_.pop_front();
+        boost::asio::post(io_, std::bind(&master_ipc::send_next, ipc_self()));
+    }
+    else {
+        boost::asio::async_write(*i->second, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
+            if(error) {
+                output() << "[" << util::system_time() << "] [FATAL] Failed to write ipc message: (" << error.value() << ") " << error.message() << "\n";
+                return;
+            }
+            sendq_.pop_front();
+            send_next();
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/master_ipc.h b/src/master_ipc.h
new file mode 100644
index 0000000..2194185
--- /dev/null
+++ b/src/master_ipc.h
@@ -0,0 +1,37 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+#include "ipc.h"
+
+class master_ipc {
+public:
+    using socket_ptr = std::shared_ptr<boost::asio::local::stream_protocol::socket>;
+    master_ipc(boost::asio::io_context& io);
+    virtual ~master_ipc();
+    // 输出
+    virtual std::ostream& output() = 0;
+    void ipc_start();
+    // 监听连接及连接数据接收
+    void ipc_run(coroutine_handler ch);
+    void ipc_read(socket_ptr sock, coroutine_handler ch);
+    // void ipc_read(socket_ptr sock, coroutine_handler& ch);
+    void ipc_close();
+    // 请求并等待响应
+    std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch);
+    // 请求（无响应）
+    void ipc_request(std::shared_ptr<ipc::message_t> data);
+protected:
+    virtual bool on_message(std::shared_ptr<ipc::message_t> msg, socket_ptr sock);
+    virtual std::shared_ptr<master_ipc> ipc_self() = 0;
+private:
+    boost::asio::io_context&                          io_;
+    std::filesystem::path                         svrsck_;
+    boost::asio::local::stream_protocol::acceptor server_;
+    std::map<std::uint8_t, socket_ptr>            socket_;
+    std::map<std::uint32_t, ipc::callback_t>    callback_;
+    std::list<std::shared_ptr<ipc::message_t>>     sendq_;
+    // 连接数据接收
+    
+    void send(std::shared_ptr<ipc::message_t> msg);
+    void send_next();
+};
diff --git a/src/master_logger.cpp b/src/master_logger.cpp
new file mode 100644
index 0000000..528174c
--- /dev/null
+++ b/src/master_logger.cpp
@@ -0,0 +1,26 @@
+#include "master_logger.h"
+#include "master_logger_buffer.h"
+#include "util.h"
+
+void master_logger::reload(boost::asio::io_context& io) {
+    oss_.reset(&std::clog, boost::null_deleter());
+
+    if(path_.string() != "<clog>") {
+        auto fb = new master_logger_buffer(io, path_);
+        // fb->open(path_, std::ios_base::app);
+        if(fb->is_open()) {
+            ssb_.reset(fb);
+            oss_.reset(new std::ostream(fb));
+            fb->persync(); // 启动周期性文件缓冲刷新服务
+        }
+        else { // 文件打开失败时不会抛出异常，需要额外的状态检查
+            std::cerr << "[" << util::system_time() << "] (WARNING) Failed to access / create logger file, fallback to 'clog'" << std::endl;
+        }
+    }
+}
+
+void master_logger::close() {
+    oss_.reset(&std::clog, boost::null_deleter());
+    // ssb_->close();
+    ssb_.reset();
+}
\ No newline at end of file
diff --git a/src/master_logger.h b/src/master_logger.h
new file mode 100644
index 0000000..382ec36
--- /dev/null
+++ b/src/master_logger.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "vendor.h"
+#include <fstream>
+
+class master_logger {
+private:
+    std::uint8_t                     idx_;
+    unsigned int                     ref_;
+    std::shared_ptr<std::ostream>    oss_;
+    std::unique_ptr<std::streambuf>  ssb_;
+    std::filesystem::path           path_;
+public:
+    master_logger(std::filesystem::path path, int index): idx_(index), ref_(1), path_(path) {}
+    std::uint8_t index() {
+        return idx_;
+    }
+    void reload(boost::asio::io_context& io);
+    std::ostream& stream() {
+        return *oss_;
+    }
+    void close();
+    friend class master_logger_manager;
+};
diff --git a/src/master_logger_buffer.cpp b/src/master_logger_buffer.cpp
new file mode 100644
index 0000000..c570455
--- /dev/null
+++ b/src/master_logger_buffer.cpp
@@ -0,0 +1,45 @@
+#include "master_logger_buffer.h"
+#include "master_logger_manager.h"
+
+master_logger_buffer::master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path)
+: tms_(io) {
+    open(path, std::ios_base::app);
+}
+
+int master_logger_buffer::overflow(int ch) {
+    char c = ch;
+    ch = std::filebuf::overflow(ch);
+    
+    if(c == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新
+        lns_ = 0;
+        pubsync();
+        persync();
+    }
+    return ch;
+}
+
+long master_logger_buffer::xsputn(const char* s, long c) {
+    c = std::filebuf::xsputn(s, c);
+
+    if(s[c-1] == '\n' && ++lns_ > 64) { // 积攒的行数超过 64 行刷新
+        lns_ = 0;
+        pubsync();
+        persync();
+    }
+    return c;
+}
+
+
+void master_logger_buffer::persync() {
+    tms_.cancel();
+    tms_.expires_after(std::chrono::milliseconds(2400));
+    tms_.async_wait([this] (const boost::system::error_code& error) {
+        if(error) return;
+        pubsync();
+        persync(); // 每 2400 毫秒对文件进行一次刷新
+    });
+}
+
+// void master_logger_buffer::close() {
+//     tms_.cancel();
+// }
\ No newline at end of file
diff --git a/src/master_logger_buffer.h b/src/master_logger_buffer.h
new file mode 100644
index 0000000..a0e93cf
--- /dev/null
+++ b/src/master_logger_buffer.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <memory>
+#include <streambuf>
+#include "ipc.h"
+
+class master_logger_manager;
+// 实现行数或周期为同步、刷写标志的缓冲区
+class master_logger_buffer: public std::filebuf {
+public:
+    master_logger_buffer(boost::asio::io_context& io, std::filesystem::path path);
+    void persync();
+    // void close();
+protected:
+    int  overflow(int ch = EOF) override;
+    long xsputn(const char* s, long c) override;
+private:
+    master_logger_manager*          mgr_;
+    boost::asio::steady_timer       tms_;
+    unsigned int                    lns_ = 0;
+};
diff --git a/src/master_logger_manager.cpp b/src/master_logger_manager.cpp
new file mode 100644
index 0000000..a7df097
--- /dev/null
+++ b/src/master_logger_manager.cpp
@@ -0,0 +1,41 @@
+#include "master_logger_manager.h"
+
+master_logger_manager::master_logger_manager(boost::asio::io_context& io)
+: io_(io) {
+
+}
+
+master_logger_manager::~master_logger_manager() {
+    // std::cout << "~master_logger_manager\n";
+}
+
+master_logger* master_logger_manager::lm_connect(std::string_view filepath) {
+    std::filesystem::path path = filepath;
+    
+    for(auto i=logger_.begin();i!=logger_.end();++i) {
+        if(static_cast<master_logger*>(i->second.get())->path_ == path) {
+            ++i->second->ref_;
+            return i->second.get();
+        }
+    }
+    auto p = logger_.insert({index_, std::make_unique<master_logger>(path, index_)});
+    ++index_;
+    p.first->second->reload(io_);
+    return p.first->second.get();
+}
+
+void master_logger_manager::lm_destroy(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    if(i == logger_.end()) return;
+    assert(i->second->ref_ > 0 && "引用计数异常");
+    if(--i->second->ref_ > 0) return;
+    logger_.erase(i);
+}
+
+void master_logger_manager::lm_reload() {
+    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->reload(io_);
+}
+
+void master_logger_manager::lm_close() {
+    for(auto i=logger_.begin();i!=logger_.end();++i) i->second->close();
+}
\ No newline at end of file
diff --git a/src/master_logger_manager.h b/src/master_logger_manager.h
new file mode 100644
index 0000000..d1e26d3
--- /dev/null
+++ b/src/master_logger_manager.h
@@ -0,0 +1,33 @@
+#pragma once
+#include "vendor.h"
+#include "master_logger.h"
+
+class master_logger_manager {
+public:
+    master_logger_manager(boost::asio::io_context& io);
+    virtual ~master_logger_manager();
+    // 日志引用
+    master_logger* lm_connect(std::string_view filepath);
+    master_logger* lm_get(std::uint8_t idx);
+    std::ostream&  lm_get(std::uint8_t idx, bool output);
+    // 引用清理
+    void lm_destroy(std::uint8_t idx);
+    // 日志重载
+    void lm_reload();
+    void lm_close();
+    virtual std::shared_ptr<master_logger_manager> lm_self() = 0;
+private:
+    boost::asio::io_context&                                   io_;
+    std::uint8_t                                            index_ = 0;
+    std::map<unsigned int, std::unique_ptr<master_logger>> logger_;
+};
+
+inline master_logger* master_logger_manager::lm_get(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    return i == logger_.end() ? nullptr : i->second.get();
+}
+
+inline std::ostream& master_logger_manager::lm_get(std::uint8_t idx, bool output) {
+    auto i = logger_.find(idx);
+    return i == logger_.end() ? std::clog : i->second.get()->stream();
+}
diff --git a/src/master_process.cpp b/src/master_process.cpp
new file mode 100755
index 0000000..9f42768
--- /dev/null
+++ b/src/master_process.cpp
@@ -0,0 +1,67 @@
+#include "master_process.h"
+#include "master_process_manager.h"
+#include "master_logger.h"
+#include "util.h"
+
+extern "C" {
+    PHPAPI extern char *php_ini_opened_path;
+}
+
+static std::string php_cmd() {
+    std::ostringstream ss;
+    ss << php::constant("PHP_BINARY");
+    ss << " -c " << php_ini_opened_path;
+    php::array argv = php::server("argv");
+    for (auto i = argv.begin(); i != argv.end(); ++i) ss << " " << i->second;
+    return ss.str();
+}
+
+master_process::master_process(boost::asio::io_context& io, master_process_manager* mgr, std::uint8_t idx)
+: mgr_(mgr), idx_(idx)
+, sout_(io), eout_(io) {
+    // 准备命令行及环境变量（完全重新启动一个新的 PHP， 通过环境变量标识其为工作进程）
+    boost::process::environment env = boost::this_process::environment();
+    env["FLAME_CUR_WORKER"] = std::to_string(idx + 1);
+    // 构造进程
+    proc_ = boost::process::child(io, php_cmd(), env,
+        boost::process::std_out > sout_, boost::process::std_err > eout_,
+        // boost::process::std_out > boost::process::null, boost::process::std_err > boost::process::null,
+        // boost::process::std_out > stdout, boost::process::std_err > stdout,
+        // 结束回调
+        boost::process::on_exit = [this, &io] (int exit_code, const std::error_code &error) {
+            if (error.value() == static_cast<int>(std::errc::no_child_process)) return;
+            boost::asio::post(io, [exit_code, this] () {
+                mgr_->on_child_close(this, exit_code == 0);
+            });
+        });
+    redirect_output(sout_, sbuf_);
+    redirect_output(eout_, ebuf_);
+
+    boost::asio::post(io, [this] () {
+        mgr_->on_child_start(this);
+    });
+}
+
+void master_process::redirect_output(boost::process::async_pipe& pipe, std::string& data) {
+    boost::asio::async_read_until(pipe, boost::asio::dynamic_buffer(data), '\n', [this, &pipe, &data] (const boost::system::error_code &error, std::size_t nread) {
+        if (error == boost::asio::error::operation_aborted || error == boost::asio::error::eof)
+            ; // std::cout << "redirect_output stopped" << std::endl; // 忽略
+        else if (error) {
+            mgr_->output() << "[" << util::system_time() << "] (ERROR) Failed to read from worker process: (" << error.value() << ") " << error.message() << "\n";
+        }
+        else {
+            mgr_->output() << "-----" << std::string_view(data.data(), nread);
+            data.erase(0, nread);
+            redirect_output(pipe, data);
+        }
+    });
+}
+
+void master_process::close(bool force) {
+    if(force) proc_.terminate();
+    else ::kill(proc_.id(), SIGQUIT);
+}
+
+void master_process::signal(int sig) {
+    ::kill(proc_.id(), sig);
+}
\ No newline at end of file
diff --git a/src/master_process.h b/src/master_process.h
new file mode 100755
index 0000000..41c5e77
--- /dev/null
+++ b/src/master_process.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "vendor.h"
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
+
+class master_process_manager;
+class master_process {
+public:
+    master_process(boost::asio::io_context& io, master_process_manager* m, std::uint8_t idx);
+    void close(bool force = false);
+    void signal(int sig);
+private:
+    master_process_manager*     mgr_;
+    std::uint8_t                idx_;
+
+    boost::process::child      proc_;
+    boost::process::async_pipe sout_;
+    boost::process::async_pipe eout_;
+    std::string                sbuf_;
+    std::string                ebuf_;
+    void redirect_output(boost::process::async_pipe& pipe, std::string& buffer);
+
+    friend class master_process_manager;
+};
\ No newline at end of file
diff --git a/src/master_process_manager.cpp b/src/master_process_manager.cpp
new file mode 100644
index 0000000..633d31f
--- /dev/null
+++ b/src/master_process_manager.cpp
@@ -0,0 +1,108 @@
+#include "master_process_manager.h"
+#include "master_process.h"
+#include "coroutine.h"
+#include "util.h"
+
+master_process_manager::master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int close)
+: io_(io)
+, cmax_(count)
+, crun_(0)
+, tquit_(close)
+, child_(count)
+, timer_(io)
+, status_(0) {
+
+}
+
+master_process_manager::~master_process_manager() {
+    // std::cout << "~master_process_manager\n";
+}
+
+void master_process_manager::pm_start(boost::asio::io_context& io) {
+    // 这个 work_guard 为了使得主线程在子进程未全部退出前保持运行
+    // 实际主线程并没有工作量
+    work_.reset(new boost::asio::io_context::work(io));
+    for(int i=0;i<cmax_;++i) {
+        child_[i].reset(new master_process(io_, this, i));
+    }
+}
+
+void master_process_manager::pm_reset(coroutine_handler& ch) {
+    if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return;
+
+    std::vector<std::unique_ptr<master_process>> start(cmax_);
+    for(int i=0;i<cmax_;++i) {
+        start[i].reset(new master_process(io_, this, i));
+        ch_start_.reset(ch);
+        ch_start_.suspend(); // 等待进程启动回调 on_child_event
+        ch_start_.reset();
+        // 启动间隔
+        util::co_sleep(io_, std::chrono::milliseconds(140), ch);
+        // 新进程已经启动 1/4 后，开始停止原进程（一次性动作）
+        if(i == cmax_ / 4) coroutine::start(io_.get_executor(), [this] (coroutine_handler ch) {
+            pm_close(ch);
+        });
+    }
+    child_ = std::move(start);
+}
+
+void master_process_manager::pm_close(coroutine_handler& ch, bool now) {
+    if(status_ & (STATUS_RSETING | STATUS_ACLOSED)) return;
+
+    if(status_ & STATUS_CLOSING) {
+        timer_.cancel(); // 提前强制关闭 ---> (3)
+        return;
+    }
+    status_ |= STATUS_CLOSING;
+    for(int i=0;i<cmax_;++i) child_[i]->close(now);
+    if (now) {
+        status_ |= STATUS_ACLOSED;
+        timer_.cancel(); // 可能有异常重启中的进程存在
+        goto SHUTDOWN;
+    }
+    {
+        int  stopped = cmax_;
+        bool timeout = false;
+        timer_.expires_after(std::chrono::milliseconds(tquit_));
+        timer_.async_wait([&ch, &timeout, &stopped, this, self = pm_self()] (const boost::system::error_code& error) mutable {
+            if(status_ & STATUS_ACLOSED) return;  // (2) 结束后栈数据丢失（ch/timeout/stopped)
+            timeout = true; // (3) 还未完全关闭时超时或提前强制关闭
+            ch.resume();
+        });
+    WAIT_FOR_CLOSE:
+        ch_close_.reset(ch);
+        ch_close_.suspend(); // 等待回调 on_child_event
+        ch_close_.reset();
+        if(timeout) return pm_close(ch, true);
+        else if(--stopped > 0) goto WAIT_FOR_CLOSE;
+    }
+SHUTDOWN:
+    status_ |= STATUS_ACLOSED;   // -------> (2)
+    timer_.cancel();
+    work_.reset();
+}
+
+void master_process_manager::pm_kills(int sig) {
+    for(int i=0;i<cmax_;++i) child_[i]->signal(sig);
+}
+
+void master_process_manager::on_child_start(master_process* w) {
+    ++crun_;
+    if (ch_start_) ch_start_.resume(); // 陆续起停流程
+}
+
+void master_process_manager::on_child_close(master_process* w, bool normal) {
+    --crun_;
+    if (!(status_ & STATUS_CLOSING) && !normal) {
+        unsigned int rand = std::rand() % 2000 + 1000;
+        output() << "[" << util::system_time() << "] (WARNING) unexpected worker process stopping, restart in " << int(rand/1000) << "s ...\n";
+        timer_.expires_after(std::chrono::milliseconds(rand));
+        timer_.async_wait([this, self = pm_self(), idx = w->idx_] (const boost::system::error_code& error) {
+            if(error) return;
+            child_[idx].reset(new master_process(io_, this, idx));
+        });
+    }
+    if (ch_close_) ch_close_.resume(); // 陆续起停流程
+    if (crun_ == 0 && normal) work_.reset(); // 正常退出了最后一个进程
+    // 这里不太严格：三个进程 1、2 异常退出，3正常退出时也结束了程序
+}
diff --git a/src/master_process_manager.h b/src/master_process_manager.h
new file mode 100644
index 0000000..9bac11a
--- /dev/null
+++ b/src/master_process_manager.h
@@ -0,0 +1,51 @@
+#pragma once
+#include "vendor.h"
+#include "coroutine.h"
+
+class master_process;
+class master_process_manager {
+public:
+    master_process_manager(boost::asio::io_context& io, unsigned int count, unsigned int timeout_ms);
+    virtual ~master_process_manager();
+    // 输出
+    virtual std::ostream& output() = 0;
+protected:
+    virtual std::shared_ptr<master_process_manager> pm_self() = 0;
+    // 启动子进程
+    void pm_start(boost::asio::io_context& io); // 此 io 非彼 io
+    // 重启子进程（陆续）
+    void pm_reset(coroutine_handler& ch);
+    // 停止子进程
+    void pm_close(coroutine_handler& ch, bool now = false);
+    // 发送信号
+    void pm_kills(int sig);
+    // 进程数量
+    std::uint8_t pm_count() {
+        return cmax_;
+    }
+protected:
+    // 进程启动回调
+    virtual void on_child_start(master_process* w);
+    // 进程停止回调
+    virtual void on_child_close(master_process* w, bool normal);
+private:
+    std::unique_ptr<boost::asio::io_context::work> work_;
+    boost::asio::io_context&                        io_;
+    unsigned int                                 cmax_;
+    unsigned int                                 crun_;
+    unsigned int                                 tquit_;
+
+    std::vector<std::unique_ptr<master_process>> child_;
+    boost::asio::steady_timer                    timer_;
+    coroutine_handler ch_close_;
+    coroutine_handler ch_start_;
+    int status_;
+    enum {
+        STATUS_CLOSING = 0x01,
+        STATUS_RSETING = 0x02,
+        STATUS_QUITING = 0x04,
+        STATUS_ACLOSED = 0x08,
+    };
+    
+    friend class master_process;
+};
diff --git a/src/net/init.cpp b/src/net/init.cpp
deleted file mode 100644
index 58617f8..0000000
--- a/src/net/init.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "../vendor.h"
-#include "udp_socket.h"
-#include "tcp_socket.h"
-#include "tcp_server.h"
-
-namespace net {
-	void init(php::extension_entry& extension) {
-		udp_socket::init(extension);
-		tcp_socket::init(extension);
-		tcp_server::init(extension);
-	}
-	address addr_from_str(const std::string& addr, bool use_ipv6) {
-		auto addr_ = address::from_string(addr);
-		if(use_ipv6 && addr_.is_v4()) {
-			try{
-				addr_ = boost::asio::ip::address_v6::v4_mapped(addr_.to_v4());
-			}catch(std::bad_cast& ex) {
-				throw php::exception("illegal address: using IPv6 but IPv4 addr given", 0);
-			}
-		}else if(!use_ipv6 && addr_.is_v6()) {
-			try{
-				addr_ = addr_.to_v6().to_v4();
-			}catch(std::bad_cast& ex) {
-				throw php::exception("illegal address: using IPv4 but IPv6 addr given", 0);
-			}
-		}
-		return addr_;
-	}
-}
diff --git a/src/net/init.h b/src/net/init.h
deleted file mode 100644
index b5adeaa..0000000
--- a/src/net/init.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-namespace net {
-	void init(php::extension_entry& extension);
-	address addr_from_str(const std::string& addr, bool use_ipv6);
-}
diff --git a/src/net/tcp_server.cpp b/src/net/tcp_server.cpp
deleted file mode 100644
index a19ef89..0000000
--- a/src/net/tcp_server.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-#include "../vendor.h"
-#include "tcp_server.h"
-#include "../core.h"
-#include "init.h"
-#include "tcp_socket.h"
-
-namespace net {
-
-	void tcp_server::init(php::extension_entry& extension) {
-		php::class_entry<tcp_server> ce_tcp_server("flame\\net\\tcp_server");
-		ce_tcp_server.add<&tcp_server::__construct>("__construct");
-		ce_tcp_server.add<&tcp_server::__destruct>("__destruct");
-		ce_tcp_server.add<&tcp_server::listen>("listen", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		ce_tcp_server.add<&tcp_server::accept>("accept");
-		ce_tcp_server.add(php::property_entry("local_addr", nullptr));
-		ce_tcp_server.add(php::property_entry("local_port", nullptr));
-		ce_tcp_server.add<&tcp_server::close>("close");
-		extension.add(std::move(ce_tcp_server));
-	}
-	tcp_server::tcp_server()
-	: acceptor_(core::io())
-	, is_ipv6_(true) // 默认按 IPv6
-	, listened_(false) {
-
-	}
-	php::value tcp_server::__construct(php::parameters& params) {
-		boost::system::error_code err;
-		acceptor_.open(tcp::v6(), err); // 优先使用 v6 协议（能够兼容 v4）
-		if(err) {
-			is_ipv6_ = false;
-			acceptor_.open(tcp::v4(), err);
-		}
-		if(err) {
-			throw php::exception("failed to create: " + err.message(), err.value());
-		}
-		return nullptr;
-	}
-	php::value tcp_server::listen(php::parameters& params) {
-		boost::system::error_code err;
-		std::string addr = params[0].is_null() ? "" : params[0];
-		int port = params[1];
-#ifdef SO_REUSEPORT
-		// 服务端需要启用下面选项，以支持更高性能的多进程形式
-		int opt = 1;
-		if(0 != setsockopt(acceptor_.native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof (opt))) {
-			throw php::exception("failed to bind (SO_REUSEPORT)", errno);
-		}
-#endif
-		acceptor_.bind(tcp::endpoint(addr_from_str(addr, is_ipv6_), port), err);
-		if(err) {
-			throw php::exception("failed to bind: " + err.message(), err.value());
-		}
-		acceptor_.listen();
-		set_prop_local_addr();
-		listened_ = true;
-		return nullptr;
-	}
-	void tcp_server::set_prop_local_addr() {
-		auto ep = acceptor_.local_endpoint();
-		prop("local_addr") = ep.address().to_string();
-		prop("local_port") = ep.port();
-	}
-	php::value tcp_server::__destruct(php::parameters& params) {
-		boost::system::error_code err;
-		acceptor_.close(err); // 存在重复关闭的可能，排除错误
-		return nullptr;
-	}
-	php::value tcp_server::accept(php::parameters& params) {
-		if(!listened_) throw php::exception("accept failed: not listened");
-		return php::value([this] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-
-			php::object cli = php::object::create<tcp_socket>();
-			tcp_socket* cli_= cli.native<tcp_socket>();
-			acceptor_.async_accept(cli_->socket_, cli_->remote_,
-				[this, done, cli, cli_] (const boost::system::error_code& err) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						cli_->connected_ = true;
-						done(nullptr, cli);
-					}
-				});
-			return nullptr;
-		});
-	}
-	php::value tcp_server::close(php::parameters& params) {
-		boost::system::error_code err;
-		acceptor_.close(err); // 存在重复关闭的可能，排除错误
-		listened_ = false;
-		return nullptr;
-	}
-}
diff --git a/src/net/tcp_server.h b/src/net/tcp_server.h
deleted file mode 100644
index dae19e6..0000000
--- a/src/net/tcp_server.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-namespace net {
-	class tcp_server: public php::class_base {
-	public:
-		static void init(php::extension_entry& extension);
-		tcp_server();
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value listen(php::parameters& params);
-		php::value accept(php::parameters& params);
-		php::value close(php::parameters& params);
-	private:
-		tcp::acceptor     acceptor_;
-		bool              is_ipv6_;
-		bool              listened_;
-		void set_prop_local_addr();
-	};
-}
diff --git a/src/net/tcp_socket.cpp b/src/net/tcp_socket.cpp
deleted file mode 100644
index 5b7c1f2..0000000
--- a/src/net/tcp_socket.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-#include "../vendor.h"
-#include "tcp_socket.h"
-#include "../core.h"
-#include "init.h"
-
-namespace net {
-
-	void tcp_socket::init(php::extension_entry& extension) {
-		php::class_entry<tcp_socket> ce_tcp_socket("flame\\net\\tcp_socket");
-		ce_tcp_socket.add<&tcp_socket::__construct>("__construct");
-		ce_tcp_socket.add<&tcp_socket::__destruct>("__destruct");
-		ce_tcp_socket.add<&tcp_socket::connect>("connect", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		ce_tcp_socket.add<&tcp_socket::remote_addr>("remote_addr");
-		ce_tcp_socket.add<&tcp_socket::remote_port>("remote_port");
-		ce_tcp_socket.add<&tcp_socket::read>("read");
-		ce_tcp_socket.add<&tcp_socket::write>("write", {
-			php::of_string("data"),
-		});
-		ce_tcp_socket.add(php::property_entry("local_addr", nullptr));
-		ce_tcp_socket.add(php::property_entry("local_port", nullptr));
-		ce_tcp_socket.add<&tcp_socket::close>("close");
-		extension.add(std::move(ce_tcp_socket));
-	}
-	tcp_socket::tcp_socket(bool connected)
-	: socket_(core::io())
-	, buffer_(8*1024*1024) // TODO buffer_ 最大尺寸？
-	, connected_(connected)
-	, is_ipv6_(true) { // 默认按 IPv6
-
-	}
-	php::value tcp_socket::__construct(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.open(tcp::v6(), err); // 优先使用 v6 协议（一般来说能够兼容 v4）
-		if(err) {
-			is_ipv6_ = false;
-			socket_.open(tcp::v4(), err);
-		}
-		if(err) {
-			throw php::exception("failed to create: " + err.message(), err.value());
-		}
-		return nullptr;
-	}
-	php::value tcp_socket::connect(php::parameters& params) {
-		boost::system::error_code err;
-		std::string addr = params[0];
-		int port = params[1];
-		remote_ = tcp::endpoint(addr_from_str(addr, is_ipv6_), port);
-		return php::value([this] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			socket_.async_connect(remote_, [this, done] (const boost::system::error_code& err) mutable {
-				if(err) {
-					done(core::error_to_exception(err));
-				}else{
-					connected_ = true;
-					set_prop_local_addr();
-					done(nullptr);
-				}
-			});
-		});
-	}
-	void tcp_socket::set_prop_local_addr() {
-		auto ep = socket_.local_endpoint();
-		prop("local_addr") = ep.address().to_string();
-		prop("local_port") = ep.port();
-	}
-	php::value tcp_socket::__destruct(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.close(err); // 存在重复关闭的可能，排除错误
-		return nullptr;
-	}
-	php::value tcp_socket::remote_addr(php::parameters& params) {
-		return remote_.address().to_string();
-	}
-	php::value tcp_socket::remote_port(php::parameters& params) {
-		return remote_.port();
-	}
-	php::value tcp_socket::read(php::parameters& params) {
-		if(!connected_) throw php::exception("read failed: not connected");
-		// 考虑提供两种方式的数据读取机制：
-		if(params[0].is_long()) { // 1. 读取指定长度
-			std::size_t length = static_cast<std::int64_t>(params[0]);
-			return php::value([this, length] (php::parameters& params) -> php::value {
-				auto buf = buffer_.prepare(length);
-				php::callable done = params[0];
-				boost::asio::async_read(socket_, buf, [this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					buffer_.commit(n);
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						php::string buf(n);
-						buffer_.sgetn(buf.data(), n);
-						done(nullptr, buf);
-					}
-				});
-			});
-		}else if(params[0].is_string()) { // 2. 读取到指定分隔符
-			std::string delim = params[0];
-			return php::value([this, delim] (php::parameters& params) -> php::value {
-				php::callable done = params[0];
-				boost::asio::async_read_until(socket_, buffer_, delim, [this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						php::string buf(n);
-						buffer_.sgetn(buf.data(), n);
-						buffer_.consume(n);
-						done(nullptr, buf);
-					}
-				});
-			});
-		}else{
-			throw php::exception("failed to read: unknown read method");
-		}
-	}
-	php::value tcp_socket::write(php::parameters& params) {
-		if(!connected_) throw php::exception("write failed: not connected");
-		zend_string* data = params[0];
-		return php::value([this, data] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			// 需要进行类型转换，否则 asio::buffer 会将 zend_string -> val 长度解为 1
-			boost::asio::async_write(
-				socket_,
-				boost::asio::buffer(reinterpret_cast<char*>(data->val), data->len),
-				[this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						done(nullptr, static_cast<std::int64_t>(n));
-					}
-				});
-			return nullptr;
-		});
-	}
-	php::value tcp_socket::close(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.close(err); // 存在重复关闭的可能，排除错误
-		connected_ = false;
-		return nullptr;
-	}
-}
diff --git a/src/net/tcp_socket.h b/src/net/tcp_socket.h
deleted file mode 100644
index e2ba5c8..0000000
--- a/src/net/tcp_socket.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-namespace net {
-	class tcp_server;
-    namespace http { class request; }
-	class tcp_socket: public php::class_base {
-	public:
-		static void init(php::extension_entry& extension);
-		tcp_socket(bool connected = false);
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value connect(php::parameters& params);
-		php::value remote_addr(php::parameters& params);
-		php::value remote_port(php::parameters& params);
-		php::value close(php::parameters& params);
-		php::value read(php::parameters& params);
-		php::value write(php::parameters& params);
-	private:
-		tcp::socket     socket_;
-		boost::asio::streambuf buffer_;
-		tcp::endpoint   remote_;
-		bool            connected_;
-		bool            is_ipv6_;
-		void set_prop_local_addr();
-		friend class tcp_server;
-        friend class http::request;
-	};
-}
diff --git a/src/net/udp_socket.cpp b/src/net/udp_socket.cpp
deleted file mode 100644
index 3b86451..0000000
--- a/src/net/udp_socket.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-#include "../vendor.h"
-#include "udp_socket.h"
-#include "../core.h"
-#include "init.h"
-
-namespace net {
-
-	void udp_socket::init(php::extension_entry& extension) {
-		php::class_entry<udp_socket> ce_udp_socket("flame\\net\\udp_socket");
-		php::class_entry<udp_socket> ce_udp_server("flame\\net\\udp_server");
-		ce_udp_socket.add<&udp_socket::__construct>("__construct");
-		ce_udp_server.add<&udp_socket::__construct>("__construct");
-		ce_udp_socket.add<&udp_socket::__destruct>("__destruct");
-		ce_udp_server.add<&udp_socket::__destruct>("__destruct");
-		// ce_udp_socket 不提供 bind 方法
-		ce_udp_server.add<&udp_socket::bind>("bind", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		ce_udp_socket.add<&udp_socket::connect>("connect", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		// ce_udp_server 不提供 connect 方法
-		ce_udp_socket.add<&udp_socket::remote_addr>("remote_addr");
-		ce_udp_server.add<&udp_socket::remote_addr>("remote_addr");
-		ce_udp_socket.add<&udp_socket::remote_port>("remote_port");
-		ce_udp_server.add<&udp_socket::remote_port>("remote_port");
-		ce_udp_socket.add<&udp_socket::read>("read");
-		ce_udp_server.add<&udp_socket::read>("read");
-		ce_udp_socket.add<&udp_socket::write>("write", {
-			php::of_string("data"),
-		});
-		// ce_udp_server 不提供 write 方法（仅提供 write_to）
-		ce_udp_socket.add<&udp_socket::write_to>("write_to", {
-			php::of_string("data"),
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		ce_udp_server.add<&udp_socket::write_to>("write_to", {
-			php::of_string("data"),
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		ce_udp_socket.add(php::property_entry("local_addr", nullptr));
-		ce_udp_server.add(php::property_entry("local_addr", nullptr));
-		ce_udp_socket.add(php::property_entry("local_port", nullptr));
-		ce_udp_server.add(php::property_entry("local_port", nullptr));
-		ce_udp_socket.add<&udp_socket::close>("close");
-		ce_udp_server.add<&udp_socket::close>("close");
-		extension.add(std::move(ce_udp_socket));
-		extension.add(std::move(ce_udp_server));
-	}
-
-	udp_socket::udp_socket()
-	: socket_(core::io())
-	, connected_(false)
-	, is_ipv6_(true) { // 默认按 IPv6
-
-	}
-	php::value udp_socket::__construct(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.open(udp::v6(), err); // 优先使用 v6 协议（一般来说能够兼容 v4）
-		if(err) {
-			is_ipv6_ = false;
-			socket_.open(udp::v4(), err);
-		}
-		if(err) {
-			throw php::exception("failed to create: " + err.message(), err.value());
-		}
-		return nullptr;
-	}
-
-	php::value udp_socket::bind(php::parameters& params) {
-		boost::system::error_code err;
-		std::string addr = params[0].is_null() ? "" : params[0];
-		int port = params[1];
-#ifdef SO_REUSEPORT
-		// 服务端需要启用下面选项，以支持更高性能的多进程形式
-		int opt = 1;
-		if(0 != setsockopt(socket_.native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof (opt))) {
-			throw php::exception("failed to bind (SO_REUSEPORT)", errno);
-		}
-#endif
-		socket_.bind(udp::endpoint(addr_from_str(addr, is_ipv6_), port), err);
-		if(err) {
-			throw php::exception("failed to bind: " + err.message(), err.value());
-		}
-		set_prop_local_addr();
-		connected_ = true;
-		return nullptr;
-	}
-	php::value udp_socket::connect(php::parameters& params) {
-		boost::system::error_code err;
-		std::string addr = params[0];
-		int port = params[1];
-		remote_ = udp::endpoint(addr_from_str(addr, is_ipv6_), port);
-		return php::value([this] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			socket_.async_connect(remote_, [this, done] (const boost::system::error_code& err) mutable {
-				if(err) {
-					done(core::error_to_exception(err));
-				}else{
-					connected_ = true;
-					set_prop_local_addr();
-					done(nullptr);
-				}
-			});
-		});
-	}
-	void udp_socket::set_prop_local_addr() {
-		auto ep = socket_.local_endpoint();
-		prop("local_addr") = ep.address().to_string();
-		prop("local_port") = ep.port();
-	}
-	php::value udp_socket::__destruct(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.close(err); // 存在重复关闭的可能，排除错误
-		return nullptr;
-	}
-	php::value udp_socket::remote_addr(php::parameters& params) {
-		return remote_.address().to_string();
-	}
-	php::value udp_socket::remote_port(php::parameters& params) {
-		return remote_.port();
-	}
-	php::value udp_socket::read(php::parameters& params) {
-		if(connected_) throw php::exception("connected socket should use 'write' instead of 'write_to'");
-		return php::value([this] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			socket_.async_receive_from(
-				boost::asio::buffer(buffer_), remote_,
-				[this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						done(nullptr, php::value(buffer_, n));
-					}
-				});
-			return nullptr;
-		});
-	}
-	php::value udp_socket::write_to(php::parameters& params) {
-		if(connected_) throw php::exception("connected socket should use 'write' instead of 'write_to'");
-		zend_string* data = params[0];
-		zend_string* addr = params[1];
-		int          port = params[2];
-		remote_.address(address::from_string(std::string(addr->val, addr->len)));
-		remote_.port(port);
-		return php::value([this, data] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			// 需要进行类型转换，否则 asio::buffer 会将 zend_string -> val 长度解为 1
-			socket_.async_send_to(
-				boost::asio::buffer(reinterpret_cast<char*>(data->val), data->len),
-				remote_, [this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						done(nullptr, static_cast<std::int64_t>(n));
-					}
-				});
-			return nullptr;
-		});
-	}
-	php::value udp_socket::write(php::parameters& params) {
-		zend_string* data = params[0];
-		return php::value([this, data] (php::parameters& params) -> php::value {
-			php::callable done = params[0];
-			// asio::buffer 会将 zend_string -> val 长度解为 1
-			socket_.async_send(
-				boost::asio::buffer(reinterpret_cast<char*>(data->val), data->len),
-				[this, done] (const boost::system::error_code& err, std::size_t n) mutable {
-					if(err) {
-						done(core::error_to_exception(err));
-					}else{
-						done(nullptr, static_cast<std::int64_t>(n));
-					}
-				});
-			return nullptr;
-		});
-	}
-	php::value udp_socket::close(php::parameters& params) {
-		boost::system::error_code err;
-		socket_.close(err); // 存在重复关闭的可能，排除错误
-		connected_ = false;
-		return nullptr;
-	}
-}
diff --git a/src/net/udp_socket.h b/src/net/udp_socket.h
deleted file mode 100644
index 8693e6d..0000000
--- a/src/net/udp_socket.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-namespace net {
-	class udp_socket: public php::class_base {
-	public:
-		static void init(php::extension_entry& extension);
-		udp_socket();
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value bind(php::parameters& params);
-		php::value connect(php::parameters& params);
-		php::value remote_addr(php::parameters& params);
-		php::value remote_port(php::parameters& params);
-		php::value close(php::parameters& params);
-		php::value read(php::parameters& params);
-		php::value write_to(php::parameters& params);
-		php::value write(php::parameters& params);
-	private:
-		udp::socket     socket_;
-		char            buffer_[64*1024];
-		// 最近一次接收的来源
-		udp::endpoint   remote_;
-		bool            connected_;
-		bool            is_ipv6_;
-		void set_prop_local_addr();
-	};
-}
diff --git a/src/net_mill/addr.cpp b/src/net_mill/addr.cpp
deleted file mode 100644
index 7bbe2b3..0000000
--- a/src/net_mill/addr.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "../vendor.h"
-#include "addr.h"
-
-namespace net {
-
-	php::value addr_t::__toString(php::parameters& params) {
-		php::value r = php::value::string(MILL_IPADDR_MAXSTRLEN + 6);
-		zend_string* buffer = r;
-		mill_ipaddrstr(addr_, buffer->val);
-		buffer->len  = std::strlen(buffer->val);
-#define PHP_SPRINTF sprintf
-#undef sprintf
-		buffer->len += std::sprintf(buffer->val + buffer->len, ":%d", port_);
-#define sprintf PHP_SPRINTF
-		return std::move(r);
-	}
-
-	php::value addr_t::port(php::parameters& params) {
-		return port_;
-	}
-
-	php::value addr_t::host(php::parameters& params) {
-		php::value r = php::value::string(MILL_IPADDR_MAXSTRLEN);
-		zend_string* buffer = r;
-		mill_ipaddrstr(addr_, buffer->val);
-		buffer->len  = std::strlen(buffer->val);
-		return std::move(r);
-	}
-}
diff --git a/src/net_mill/addr.h b/src/net_mill/addr.h
deleted file mode 100644
index b7f5203..0000000
--- a/src/net_mill/addr.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-namespace net {
-	class udp_socket;
-	class tcp_socket;
-	class addr_t: public php::class_base {
-	public:
-		php::value __toString(php::parameters& params);
-		php::value port(php::parameters& params);
-		php::value host(php::parameters& params);
-	private:
-		mill_ipaddr addr_;
-		unsigned short port_;
-		friend class net::udp_socket;
-		friend class net::tcp_socket;
-	};
-}
diff --git a/src/net_mill/http/init.cpp b/src/net_mill/http/init.cpp
deleted file mode 100644
index f909045..0000000
--- a/src/net_mill/http/init.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "../../vendor.h"
-#include "init.h"
-#include "request.h"
-
-namespace net { namespace http {
-	void init(php::extension_entry& extension) {
-        request::init(extension);
-		/*php::class_entry<request> mill_http_request("mill\\http\\request");*/
-		//mill_http_request.add<request::parse>("parse");
-		//mill_http_request.add<&request::__construct>("__construct");
-		//mill_http_request.add(php::property_entry("version", "HTTP/1.1"));
-		//mill_http_request.add(php::property_entry("method", "GET"));
-		//mill_http_request.add(php::property_entry("uri", "/"));
-		//mill_http_request.add(php::property_entry("body", nullptr));
-		//mill_http_request.add(php::property_entry("header", nullptr));
-		//mill_http_request.add(php::property_entry("query", nullptr));
-		//mill_http_request.add(php::property_entry("cookie", nullptr));
-		/*extension.add(std::move(mill_http_request));*/
-	}
-} }
diff --git a/src/net_mill/http/init.h b/src/net_mill/http/init.h
deleted file mode 100644
index 1a99bde..0000000
--- a/src/net_mill/http/init.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-namespace net { namespace http {
-	void init(php::extension_entry& extension);
-} }
diff --git a/src/net_mill/http/request.cpp b/src/net_mill/http/request.cpp
deleted file mode 100644
index 85e534b..0000000
--- a/src/net_mill/http/request.cpp
+++ /dev/null
@@ -1,229 +0,0 @@
-#include "../../vendor.h"
-#include "../../core.h"
-#include "../../net/tcp_socket.h"
-#include "request.h"
-
-namespace net { namespace http {
-
-    void request::init(php::extension_entry& extension) {
-        php::class_entry<request> http_request("flame\\net\\http\\request");
-        http_request.add<&request::__construct>("__construct");
-        http_request.add(php::property_entry("method", nullptr));
-        http_request.add(php::property_entry("uri", nullptr));
-        http_request.add(php::property_entry("version", nullptr));
-        http_request.add(php::property_entry("header", nullptr));
-        http_request.add(php::property_entry("query", nullptr));
-        http_request.add(php::property_entry("cookie", nullptr));
-        http_request.add(php::property_entry("body", nullptr));
-        http_request.add<&request::parse>("parse");
-        extension.add(std::move(http_request));
-        return ;
-    }
-
-	static void parse_first(char* buffer, std::size_t n, php::object& req) {
-		for(int p=0,x=0,i=1;i<n;++i) {
-			switch(x) {
-			case 0: // METHOD
-				if(std::isspace(buffer[i])) {
-					req.prop("method") = php::value(php::strtoupper(buffer+p,i-p), i-p);
-					p=i+1;
-					++x;
-				}
-			break;
-			case 1: // URI
-				if(std::isspace(buffer[i])) {
-					req.prop("uri") = php::value(buffer+p, i-p);
-                    php::value   uri_= req.prop("uri");
-                    zend_string* uri = uri_;
-                    auto url = php::parse_url(uri->val, uri->len);
-                    if(url->query !=nullptr) {
-                        req.prop("query") = php::parse_str('&', url->query, std::strlen(url->query));
-                    }else{
-                        req.prop("query") = php::array((size_t)0);
-                    }
-                    p=i+1;
-					++x;
-				}
-			break;
-			case 2: // VERSION
-				if(std::isspace(buffer[i])) {
-					req.prop("version") = php::value(php::strtoupper(buffer+p, i-p), i-p);
-					// TODO 确认协议形式是否正确？
-					return; // 解析结束
-				}
-			break;
-			}
-		}
-		// TODO 解析失败
-	}
-
-	static void parse_header(char* buffer, std::size_t n, php::array& hdr, php::object& req) {
-		//php::array item(nullptr);
-		//int   len;
-        int key_beg, key_end, key_len;
-        int val_beg, val_end, val_len;
-        char* key;
-        char* val;
-		for(int x = 0,i = 0; i < n; ++i) {
-                
-			switch(x) {
-			case 0:
-				if(!std::isspace(buffer[i]) && buffer[i] != ':') {
-					key_beg = i;
-					++x;
-				}
-				break;
-			case 1:
-				if(!std::isspace(buffer[i]) && buffer[i] != ':') {
-                    continue;
-				}else if(buffer[i] == ':') {
-					key_end = i;
-					key_len = key_end - key_beg;
-					php::strtolower(buffer + key_beg, key_len);
-
-					key = buffer + key_beg;
-                    //item.at(key, len);
-					//item.reset( hdr.item(buffer+p, len) );
-					//item = php::value("", 0);
-					++x;
-				}
-				break;
-			case 2:
-				if(!std::isspace(buffer[i])) {
-					val_beg = i;
-					++x;
-				}
-				break;
-			case 3:
-                val_end = i;
-				if(!std::isspace(buffer[i])) {
-                    continue;
-				}else if(buffer[i] == '\r') {
-					hdr.at(key, key_len) = php::value(buffer + val_beg, val_end - val_beg);
-					if(std::memcmp(key, "cookie", key_len) == 0) {
-						req.prop("cookie") = php::parse_str(';', buffer + val_beg, val_end - val_beg);
-					}
-                    i+=1;
-                    x=0;
-				}
-			}
-		}
-        return; // 解析完成
-		// TODO 解析失败？
-	}
-
-	static std::size_t parse_content_length(php::array& hdr) {
-        php::array h = hdr;
-		php::value len = h.at("content-length", 14);
-		if(len.is_null()) {
-			return 0;
-		}
-		// TODO 限制 BODY 的最大大小？
-        return /*std::atoi(static_cast<const char*>(len.to_string()))*/len.to_long();
-	}
-
-	void request::parse_request_line(php::object& req, size_t n) {
-        php::string request_line(n);
-        buffer_.sgetn(request_line.data(), n);
-        //buffer_.consume(n);
-        parse_first(request_line.data(), n, req);
-        //char* p = strstr(request_line.data(), "\r\n") + 2;
-        //header.rev(request_line.data() - p);
-        //size_t len = request_line.data() + request_line.length() - p;
-        //if(len) std::memcpy(header.rev(len), p, len);
-        size_t len = buffer_.size();
-        if(len) {
-            buffer_.sgetn(header.put(len), len);
-            //buffer_.consume(len);
-        }
-    }
-
-    void request::parse_request_header(php::object& req, size_t n) {
-        // header string 
-        buffer_.sgetn(header.put(n), n);
-        //buffer_.consume(n);
-
-        // parse header
-        parse_header(((zend_string*)header)->val, header.size(), hdr, req);
-        req.prop("header") = hdr;
-    }
-
-	void request::parse_request_body(php::object& req, size_t n) {
-        php::string body(n);
-        buffer_.commit(n);
-        buffer_.sgetn(body.data(), n);
-
-        php::value   type_ = hdr.at("content-type", 12);
-        // 下述两种 content-type 自动解析：
-        // 1. application/x-www-form-urlencoded 33
-        // 2. application/json 16
-        zend_string* type = type_;
-        php::strtolower(type->val, type->len);
-        if(type->len >=33 && std::memcmp(type->val, "application/x-www-form-urlencoded", 33) == 0) {
-            //zend_string* str = body.c_str();
-            req.prop("body") = php::parse_str('&', body.data(), body.length());
-        }else if(type->len >=16 && std::memcmp(type->val, "application/json", 16) == 0) {
-            //zend_string* str = body.c_str();
-            req.prop("body") = php::json_decode(body.data(), body.length());
-        }else{
-            // 其他 content-type 返回原始 body 内容
-            req.prop("body") = body;
-        }
-    }
-
-    php::value request::parse(php::parameters& params) {
-        php::value    tcp_= params[0];
-        if(!tcp_.is_object() || !tcp_.is_instance_of<net::tcp_socket>()) {
-            throw php::exception("type error: object of mill\\net\\tcp_socket expected");
-        }
-
-        php::object req = php::object::create<request>(); /*= php::value::object<request>()*/
-        request* r = req.native<request>();
-        r->tcp = tcp_.native<net::tcp_socket>();
-
-        return php::value([r, req](php::parameters& params) mutable -> php::value {
-                php::callable done = params[0];
-                    // parse request line
-                boost::asio::async_read_until(r->tcp->socket_, r->buffer_, "\r\n", 
-                    [r, req, done](const boost::system::error_code err, std::size_t n) mutable {
-                    if(err) {
-                        done(core::error_to_exception(err));
-                    } else {
-                        r->parse_request_line(req, n);
-                        // parse header
-                        boost::asio::async_read_until(r->tcp->socket_, r->buffer_, "\r\n\r\n", 
-                            [r, req, done](const boost::system::error_code err, std::size_t n) mutable {
-                            if(err) {
-                                done(core::error_to_exception(err));
-                            } else {
-                                r->parse_request_header(req, n);
-                                std::size_t content_length = parse_content_length(r->hdr);
-                                if(content_length > 0) {
-                                    auto body_buff = r->buffer_.prepare(content_length);
-                                    // parse body
-                                    //boost::asio::async_read_until(tcp, body_buff, "\r\n", 
-                                    //[r, req](const boost::system::error_code err, std::size_t n) mutable {
-                                    boost::asio::async_read(r->tcp->socket_, body_buff, 
-                                            [r, req, done] (const boost::system::error_code err, std::size_t n) mutable {
-                                        if(err) {
-                                            done(core::error_to_exception(err));
-                                        } else {
-                                            r->parse_request_body(req, n);
-                                            done(nullptr, req);
-                                        }
-                                        });
-                                } else {
-                                    done(nullptr, req);
-                                }
-                            }
-                            });
-
-                    }
-                    });
-        });
-
-	}
-	php::value request::__construct(php::parameters& params) {
-		return nullptr;
-	}
-}}
diff --git a/src/net_mill/http/request.h b/src/net_mill/http/request.h
deleted file mode 100644
index 789fcf5..0000000
--- a/src/net_mill/http/request.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-namespace net { 
-    class tcp_socket;
-    namespace http {
-	class request: public php::class_base {
-	public:
-        //request() = default ;
-        static void init(php::extension_entry& extension);
-		static php::value parse(php::parameters& params);
-		php::value __construct(php::parameters& params);
-        void parse_request_line(php::object& req, size_t n);
-        void parse_request_header(php::object& req, size_t n);
-        void parse_request_body(php::object& req, size_t n);
-
-    private:
-        php::buffer header;
-        php::array  hdr;
-        boost::asio::streambuf buffer_;
-        net::tcp_socket* tcp;
-        php::value done;
-	};
-} }
diff --git a/src/net_mill/init.cpp b/src/net_mill/init.cpp
deleted file mode 100644
index cff9a32..0000000
--- a/src/net_mill/init.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "../vendor.h"
-#include "init.h"
-#include "addr.h"
-#include "udp_socket.h"
-#include "tcp_socket.h"
-#include "tcp_server.h"
-#include "http/init.h"
-
-namespace net {
-	void init(php::extension_entry& extension) {
-		// mill_address
-		// ---------------------------------------------------------------------
-		php::class_entry<addr_t> mill_addr("mill\\addr");
-		mill_addr.add<&addr_t::host>("host");
-		mill_addr.add<&addr_t::port>("port");
-		mill_addr.add<&addr_t::__toString>("__toString");
-		extension.add(std::move(mill_addr));
-		// mill_udp_socket
-		// ---------------------------------------------------------------------
-		php::class_entry<udp_socket> mill_udp_socket("mill\\net\\udp_socket");
-		mill_udp_socket.add<&udp_socket::__construct>("__construct", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		mill_udp_socket.add<&udp_socket::__destruct>("__destruct");
-		mill_udp_socket.add<&udp_socket::remote_addr>("remote_addr");
-		mill_udp_socket.add<&udp_socket::close>("close");
-		mill_udp_socket.add<&udp_socket::recv>("recv");
-		mill_udp_socket.add<&udp_socket::send>("send", {
-			php::of_string("data"),
-			php::of_string("host"),
-			php::of_integer("port"),
-		});
-		mill_udp_socket.add(php::property_entry("local_port", nullptr));
-		mill_udp_socket.add(php::property_entry("closed", true));
-		extension.add(std::move(mill_udp_socket));
-		// mill_tcp_socket
-		// ---------------------------------------------------------------------
-		php::class_entry<tcp_socket> mill_tcp_socket("mill\\net\\tcp_socket");
-		mill_tcp_socket.add<&tcp_socket::__construct>("__construct", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-			php::of_integer("deadline"),
-		});
-		mill_tcp_socket.add<&tcp_socket::__destruct>("__destruct");
-		mill_tcp_socket.add<&tcp_socket::remote_addr>("remote_addr");
-		mill_tcp_socket.add<&tcp_socket::close>("close");
-		mill_tcp_socket.add<&tcp_socket::recv>("recv", {
-			php::of_mixed("stop"), // integer or string
-			php::of_integer("deadline"),
-		});
-		mill_tcp_socket.add<&tcp_socket::send>("send", {
-			php::of_string("data"),
-			php::of_integer("deadline"),
-		});
-		mill_tcp_socket.add<&tcp_socket::send_buffer>("send_buffer", {
-			php::of_string("data"),
-			php::of_integer("deadline"),
-		});
-		mill_tcp_socket.add<&tcp_socket::flush>("flush_buffer", {
-			php::of_integer("deadline"),
-		});
-		mill_tcp_socket.add(php::property_entry("closed", true));
-		extension.add(std::move(mill_tcp_socket));
-		// mill_tcp_socket
-		// ---------------------------------------------------------------------
-		php::class_entry<tcp_server> mill_tcp_server("mill\\net\\tcp_server");
-		mill_tcp_server.add<&tcp_server::__construct>("__construct", {
-			php::of_string("addr"),
-			php::of_integer("port"),
-		});
-		mill_tcp_server.add<&tcp_server::__destruct>("__destruct");
-		mill_tcp_server.add<&tcp_server::close>("close");
-		mill_tcp_server.add<&tcp_server::accept>("accept");
-		mill_tcp_server.add(php::property_entry("local_port", nullptr));
-		mill_tcp_server.add(php::property_entry("closed", true));
-		extension.add(std::move(mill_tcp_server));
-
-		http::init(extension);
-	}
-}
diff --git a/src/net_mill/init.h b/src/net_mill/init.h
deleted file mode 100644
index 5820423..0000000
--- a/src/net_mill/init.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-namespace net {
-	void init(php::extension_entry& extension);
-}
diff --git a/src/net_mill/tcp_server.cpp b/src/net_mill/tcp_server.cpp
deleted file mode 100644
index e735956..0000000
--- a/src/net_mill/tcp_server.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include "../vendor.h"
-#include "tcp_socket.h"
-#include "tcp_server.h"
-
-namespace net {
-	php::value tcp_server::__construct(php::parameters& params) {
-		local_addr_ = mill_iplocal(params[0], params[1], 0);
-		if(errno != 0) {
-			throw php::exception("failed to create tcp server (iplocal)", errno);
-		}
-		server_ = mill_tcplisten(local_addr_, 2048);
-		if(errno != 0) {
-			throw php::exception("failed to create tcp server (tcplisten)", errno);
-		}
-		prop("local_port") = mill_tcpport(server_);
-		closed_ = false;
-		prop("closed", closed_);
-		return nullptr;
-	}
-	php::value tcp_server::__destruct(php::parameters& params) {
-		if(!closed_) {
-			closed_ = true;
-			mill_tcpclose(server_);
-		}
-		return nullptr;
-	}
-	php::value tcp_server::accept(php::parameters& params) {
-		std::int64_t dead = -1;
-		if(params.length()>0) {
-			dead = mill_now() + static_cast<std::int64_t>(params[0]);
-		}
-		php::value  sock_= php::value::object<tcp_socket>();
-		tcp_socket* sock = sock_.native<tcp_socket>();
-		sock->socket_ = mill_tcpaccept(server_, dead);
-		if(errno != 0) {
-			throw php::exception("failed to accept tcp socket", errno);
-		}
-		sock->remote_addr_ = mill_tcpaddr(sock->socket_);
-		sock->closed_ = false;
-		sock->prop("closed") = sock->closed_;
-		std::printf("accepted: %08x %08x\n", sock, sock->socket_);
-		return std::move(sock_);
-	}
-	php::value tcp_server::close(php::parameters& params) {
-		if(!closed_) {
-			closed_ = true;
-			prop("closed", closed_);
-			mill_tcpclose(server_);
-		}
-		return nullptr;
-	}
-}
diff --git a/src/net_mill/tcp_server.h b/src/net_mill/tcp_server.h
deleted file mode 100644
index 72a5917..0000000
--- a/src/net_mill/tcp_server.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-namespace net {
-	class tcp_server: public php::class_base {
-	public:
-		tcp_server():closed_(true) {}
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value accept(php::parameters& params);
-		php::value close(php::parameters& params);
-	private:
-		mill_tcpsock server_;
-		bool         closed_;
-		mill_ipaddr local_addr_;
-	};
-}
diff --git a/src/net_mill/tcp_socket.cpp b/src/net_mill/tcp_socket.cpp
deleted file mode 100644
index d832d26..0000000
--- a/src/net_mill/tcp_socket.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-#include "../vendor.h"
-#include "addr.h"
-#include "tcp_socket.h"
-
-namespace net {
-	php::value tcp_socket::__construct(php::parameters& params) {
-		std::int64_t dead = -1;
-		if(params.length() > 2) {
-			dead = mill_now() + static_cast<std::int64_t>(params[3]);
-		}
-		remote_addr_ = mill_ipremote(params[0], params[1], 0, dead);
-		if(errno != 0) {
-			throw php::exception("failed to connect (dns)", errno);
-		}
-		std::printf("tcp_socket::__construct\n");
-		socket_ = mill_tcpconnect(remote_addr_, dead);
-		if(errno != 0) {
-			throw php::exception("failed to connect (tcp)", errno);
-		}
-		closed_ = false;
-		prop("closed", closed_);
-		return nullptr;
-	}
-	php::value tcp_socket::__destruct(php::parameters& params) {
-		if(!closed_) {
-			closed_ = true;
-			std::printf("before close: %08x %08x\n", this, socket_);
-			mill_tcpclose(socket_);
-			std::printf("after close: %08x %08x\n", this, socket_);
-		}
-		return nullptr;
-	}
-	php::value tcp_socket::remote_addr(php::parameters& params) {
-		php::value addr = php::value::object<net::addr_t>();
-		addr.native<net::addr_t>()->addr_ = remote_addr_;
-		addr.native<net::addr_t>()->port_ = mill_ipport(remote_addr_);
-		return std::move(addr);
-	}
-	php::value tcp_socket::recv(php::parameters& params) {
-		php::value   stop = params[0];
-		std::int64_t dead = -1;
-		if(params.length() > 1) {
-			dead = mill_now() + static_cast<std::int64_t>(params[1]);
-		}
-		if(stop.is_long()) { // 接收一定量
-			return recv_length(stop, dead);
-		}else if(stop.is_string()) { // 接收到指定任意停止符号
-			return recv_delims(stop, dead);
-		}
-		throw php::exception("unsupported recv condition: long for specified length or string for specified delim expected");
-	}
-	php::value tcp_socket::recv_length(int length, std::int64_t dead) {
-		php::value   r = php::value::string(length);
-		zend_string* b = r;
-		b->len = mill_tcprecv(socket_, b->val, length, dead);
-		if(errno != 0) {
-			throw php::exception("failed to recv length", errno);
-		}
-		return std::move(r);
-	}
-	php::value tcp_socket::recv_delims(zend_string* delims, std::int64_t dead) {
-		php::buffer b(1024); // 初始容量
-AGAIN:
-		char* x = b.rev(512);
-		std::size_t n = mill_tcprecvuntil(socket_, x, 512, delims->val, delims->len, dead);
-		b.put(n);
-		if(errno == ENOBUFS) { // 未遇到结束符
-			goto AGAIN;
-		}else if(errno != 0) {
-			throw php::exception("failed to recv delim", errno);
-		}
-		return std::move(b);
-	}
-	php::value tcp_socket::send_buffer(php::parameters& params) {
-		zend_string*  b = params[0];
-		std::int64_t  d = -1;
-		if(params.length() > 1) {
-			d = mill_now() + static_cast<std::int64_t>(params[1]);
-		}
-		std::size_t n = mill_tcpsend(socket_, b->val, b->len, d);
-		if(errno != 0) {
-			throw php::exception("failed to send_buffer", errno);
-		}
-		return php::value((std::int64_t)n);
-	}
-	php::value tcp_socket::flush(php::parameters& params) {
-		std::int64_t d = -1;
-		if(params.length() > 1) {
-			d = mill_now() + static_cast<std::int64_t>(params[1]);
-		}
-		mill_tcpflush(socket_, d);
-		if(errno != 0) {
-			throw php::exception("failed to flush", 0);
-		}
-		return nullptr;
-	}
-	php::value tcp_socket::send(php::parameters& params) {
-		std::printf("socket::send: %08x %08x\n", this, socket_);
-		zend_string* b = params[0];
-		std::int64_t d = -1;
-		if(params.length() > 1) {
-			d = mill_now() + static_cast<std::int64_t>(params[1]);
-		}
-		std::size_t n = mill_tcpsend(socket_, b->val, b->len, d);
-		if(errno != 0) {
-			throw php::exception("failed to send (send)", errno);
-		}
-		mill_tcpflush(socket_, d);
-		if(errno != 0) {
-			throw php::exception("failed to send (flush)", errno);
-		}
-		return php::value((std::int64_t)n);
-	}
-	php::value tcp_socket::close(php::parameters& params) {
-		if(!closed_) {
-			closed_ = true;
-			prop("closed", closed_);
-			std::printf("before close: %08x %08x\n", this, socket_);
-			mill_tcpclose(socket_);
-			std::printf("after close: %08x %08x\n", this, socket_);
-		}
-		return nullptr;
-	}
-}
diff --git a/src/net_mill/tcp_socket.h b/src/net_mill/tcp_socket.h
deleted file mode 100644
index e9345a2..0000000
--- a/src/net_mill/tcp_socket.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-namespace net {
-	namespace http {
-		class request;
-	}
-	class tcp_server;
-	class tcp_socket: public php::class_base {
-	public:
-		tcp_socket():socket_(nullptr),closed_(true) {}
-		// 通过指定连接地址构造一个 tcp_socket，支持指定超时时间
-		php::value __construct(php::parameters& params);
-		php::value __destruct(php::parameters& params);
-		php::value remote_addr(php::parameters& params);
-		// recv 支持两种形式：
-		// 1. 接收指定大小
-		// 2. 接收指定任意停止符
-		// 两种形式都支持指定超时
-		php::value recv(php::parameters& params);
-		php::value send_buffer(php::parameters& params);
-		php::value flush(php::parameters& params);
-		php::value send(php::parameters& params);
-		php::value close(php::parameters& params);
-	private:
-		php::value recv_length(int length, std::int64_t dead);
-		php::value recv_delims(zend_string* delims, std::int64_t dead);
-		mill_tcpsock socket_;
-		bool         closed_;
-		mill_ipaddr remote_addr_;
-		friend class tcp_server;
-		friend class http::request;
-	};
-}
diff --git a/src/os/signal_watcher.cpp b/src/os/signal_watcher.cpp
deleted file mode 100644
index 8bff649..0000000
--- a/src/os/signal_watcher.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "../vendor.h"
-#include "signal_watcher.h"
-
-signal_watcher::signal_watcher(std::initializer_list<int> signals) {
-	sigemptyset(&mask_);
-	for(auto i=signals.begin();i!=signals.end();++i) {
-		sigaddset(&mask_, *i);
-	}
-	sigprocmask(SIG_BLOCK, &mask_, nullptr);
-	fd_ = signalfd(-1, &mask_, SFD_NONBLOCK);
-	if(fd_ == -1) {
-		perror("failed to set signal watcher");
-		exit(errno);
-	}
-}
-
-int signal_watcher::next() {
-	int events = mill_fdwait(fd_, MILL_FDW_IN, -1);
-	if(events & MILL_FDW_ERR) {
-		// TODO 错误处理？
-	}
-	if(events & MILL_FDW_IN) {
-		read(fd_, buffer_, sizeof(buffer_));
-		return *reinterpret_cast<std::uint32_t*>(buffer_);
-	}
-}
diff --git a/src/os/signal_watcher.h b/src/os/signal_watcher.h
deleted file mode 100644
index 4477ac8..0000000
--- a/src/os/signal_watcher.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-class signal_watcher {
-public:
-	signal_watcher(std::initializer_list<int> signals);
-	int next();
-private:
-	sigset_t mask_;
-	int      fd_;
-	char     buffer_[sizeof(struct signalfd_siginfo)];
-};
\ No newline at end of file
diff --git a/src/signal_watcher.h b/src/signal_watcher.h
new file mode 100644
index 0000000..93ea894
--- /dev/null
+++ b/src/signal_watcher.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "vendor.h"
+
+class logger;
+class signal_watcher {
+public:
+    explicit signal_watcher(boost::asio::io_context& io)
+    : ss_(new boost::asio::signal_set(io)) {
+        ss_->add(SIGINT);
+        ss_->add(SIGTERM);
+        ss_->add(SIGUSR1);
+        ss_->add(SIGUSR2);
+        ss_->add(SIGQUIT);
+    }
+    virtual ~signal_watcher() {
+        ss_.reset();
+        // std::cout << "~signal_watcher\n";
+    }
+    void sw_watch() {
+        ss_->async_wait([this, self = sw_self()] (const boost::system::error_code& error, int sig) {
+            if (error) return;
+            if (!on_signal(sig)) return;
+            sw_watch();
+        });
+    }
+    void sw_close() {
+        ss_.reset();
+    }
+    virtual std::shared_ptr<signal_watcher> sw_self() = 0;
+protected:
+    virtual bool on_signal(int sig) = 0;
+private:
+    std::unique_ptr<boost::asio::signal_set> ss_;
+};
\ No newline at end of file
diff --git a/src/url.cpp b/src/url.cpp
new file mode 100644
index 0000000..82dd653
--- /dev/null
+++ b/src/url.cpp
@@ -0,0 +1,62 @@
+#include "vendor.h"
+#include "url.h"
+#include <curl/curl.h>
+
+typedef parser::separator_parser<std::string, std::string> parser_t;
+
+url::url(const php::string &str, bool parse_query)
+: raw_(str)
+, port(0) { // 原始类型 port 初始值可能随机
+    auto u = curl_url();
+    curl_url_set(u, CURLUPART_URL, str.c_str(), CURLU_NON_SUPPORT_SCHEME);
+    char * tmp;
+    if(curl_url_get(u, CURLUPART_SCHEME, &tmp, 0) == CURLUE_OK) { // SCHEME
+        schema.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_USER, &tmp, 0) == CURLUE_OK) { // USER
+        user.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PASSWORD, &tmp, 0) == CURLUE_OK) { // PASS
+        pass.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_HOST, &tmp, 0) == CURLUE_OK) { // USER
+        host.assign(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PORT, &tmp, 0) == CURLUE_OK) { // PORT
+        port = std::atoi(tmp);
+        curl_free(tmp);
+    }
+    if(curl_url_get(u, CURLUPART_PATH, &tmp, 0) == CURLUE_OK) { // PATH
+        path.assign(tmp);
+        curl_free(tmp);
+    }
+    if(parse_query && curl_url_get(u, CURLUPART_QUERY, &tmp, 0) == CURLUE_OK) { // QUERY
+        parser_t p('\0', '\0', '=', '\0', '\0', '&', [this](parser_t::entry_type et) {
+            php::url_decode_inplace(et.second.data(), et.second.size());
+            query[et.first] = et.second;
+        });
+        p.parse(tmp, strlen(tmp));
+        p.end();
+        curl_free(tmp);
+    }
+    curl_url_cleanup(u);
+}
+
+std::string url::str(bool with_query, bool update) {
+    if (update) {
+        std::ostringstream ss;
+        ss << schema << "://" << user << ":" << pass << "@" << host << ":" << port << path;
+        if (with_query) {
+            ss << "?";
+            for (auto i=query.begin();i!=query.end();++i) {
+                ss << i->first << "=" << php::url_encode(i->second.c_str(), i->second.size()) << "&";
+            }
+        }
+        raw_ = ss.str();
+    }
+    return raw_;
+}
diff --git a/src/url.h b/src/url.h
new file mode 100644
index 0000000..cc030e9
--- /dev/null
+++ b/src/url.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "vendor.h"
+
+class url {
+public:
+    url(const php::string& str, bool parse_query=true);
+    std::string schema;
+    std::string   host;
+    std::uint16_t port;
+    std::string   path;
+    std::map<std::string, std::string> query;
+    std::string   user;
+    std::string   pass;
+
+    std::string   str(bool update = false, bool with_query = true);
+private:
+    std::string raw_;
+};
\ No newline at end of file
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..36c9358
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,23 @@
+#include "util.h"
+#include "coroutine.h"
+
+const char* util::system_time() {
+    static char buffer[24] = {0};
+    std::time_t t = std::time(nullptr);
+    struct tm *m = std::localtime(&t);
+    sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
+            1900 + m->tm_year,
+            1 + m->tm_mon,
+            m->tm_mday,
+            m->tm_hour,
+            m->tm_min,
+            m->tm_sec);
+    return buffer;
+}
+
+
+void util::co_sleep(boost::asio::io_context& io, std::chrono::milliseconds ms, coroutine_handler& ch) {
+    boost::asio::steady_timer tm(io);
+    tm.expires_after(ms);
+    tm.async_wait(ch);
+}
\ No newline at end of file
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..3b775a4
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "vendor.h"
+
+class coroutine_handler;
+class util {
+public:
+    static const char* system_time();
+    static void co_sleep(boost::asio::io_context& io, std::chrono::milliseconds ms, coroutine_handler& ch);
+};
diff --git a/src/vendor.h b/src/vendor.h
index 6aa2bd7..c24d050 100644
--- a/src/vendor.h
+++ b/src/vendor.h
@@ -1,21 +1,37 @@
 #pragma once
 
-#include <cstring>
+#define EXTENSION_NAME "flame"
+#define EXTENSION_VERSION "0.15.0"
+
+
+#include <algorithm>
 #include <cctype>
-#include <cstdint>
-#include <cerrno>
+#include <cstdio>
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <ostream>
+#include <limits>
 #include <memory>
-#include <thread>
-#include <vector>
-// #include <forward_list>
-
-#include <sys/signalfd.h>
-#include <signal.h>
-#include <unistd.h>
+#include <set>
+#include <stack>
+#include <string_view>
+#include <optional>
+#include <unordered_set>
+#include <utility>
 
-#include <phpext.h>
+#include <phpext/phpext.h>
+#include <parser/separator_parser.hpp>
+#include <parser/multipart_parser.hpp>
+#include <boost/core/null_deleter.hpp>
+#include <boost/context/fiber.hpp>
+#include <boost/context/segmented_stack.hpp>
+#include <boost/format.hpp>
+#include <boost/logic/tribool.hpp>
+#include <boost/logic/tribool_io.hpp>
 #include <boost/asio.hpp>
-#include <boost/asio/steady_timer.hpp>
 using boost::asio::ip::tcp;
 using boost::asio::ip::udp;
-using boost::asio::ip::address;
+#include <boost/process.hpp>
+#include <boost/process/async.hpp>
+#include <boost/pool/pool.hpp>
diff --git a/src/worker_ipc.cpp b/src/worker_ipc.cpp
new file mode 100644
index 0000000..28e2cfb
--- /dev/null
+++ b/src/worker_ipc.cpp
@@ -0,0 +1,142 @@
+#include "worker_ipc.h"
+#include "util.h"
+
+worker_ipc::worker_ipc(boost::asio::io_context& io, std::uint8_t idx)
+: io_(io)
+, socket_(new boost::asio::local::stream_protocol::socket(io))
+, idx_(idx) {
+    svrsck_ = (boost::format("/tmp/flame_ipc_%d.sock") % ::getppid()).str();
+}
+
+worker_ipc::~worker_ipc() {
+    // std::cout << "~worker_ipc\n";
+    ipc_close();
+}
+
+std::shared_ptr<ipc::message_t> worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch) {
+    std::shared_ptr<ipc::message_t> res;
+    callback_.emplace(req->unique_id, ipc::callback_t {ch, res});
+    req->source = idx_;
+    send(req);
+    ch.suspend();
+    return res;
+}
+
+void worker_ipc::ipc_request(std::shared_ptr<ipc::message_t> req) {
+    req->source = idx_;
+    send(req);
+}
+
+static void free_json_message(ipc::message_t* msg) {
+    zend_string* ptr = reinterpret_cast<zend_string*>((char*)(msg) - offsetof(zend_string, val));
+    smart_str str { ptr, 0 };
+    smart_str_free(&str);
+}
+
+void worker_ipc::ipc_notify(std::uint8_t target, php::value data) {
+    if(data.type_of(php::TYPE::STRING)) {
+        php::string str = data;
+        auto msg = ipc::create_message(str.size());
+        msg->command = ipc::COMMAND_MESSAGE_STRING;
+        msg->source  = idx_;
+        msg->target  = target;
+        std::memcpy(&msg->payload[0], str.data(), str.size());
+        msg->length = str.size();
+        send(msg);
+    }
+    else {
+        smart_str str {nullptr, 0}; // !!!! 避免 JSON 文本的 COPY 复制
+        smart_str_alloc(&str, 1, false);
+        ipc::message_t* msg = (ipc::message_t*)str.s->val;
+        msg->command = ipc::COMMAND_MESSAGE_JSON;
+        msg->source  = idx_;
+        msg->target  = target;
+        str.s->len = sizeof(ipc::message_t);
+        php::json_encode_to(&str, data);
+        msg->length = str.s->len - sizeof(ipc::message_t);
+        send(std::shared_ptr<ipc::message_t>(msg, free_json_message));
+    }
+}
+
+void worker_ipc::ipc_start() {
+    // !!!! 注意本地日志的判定机制与时序、时间有关
+    ++status_; // 0
+    coroutine::start(io_.get_executor(), 
+            std::bind(&worker_ipc::ipc_run, ipc_self(), std::placeholders::_1)); // 由于线程池影响，特殊的启动方式
+}
+// !!!! 工作线程
+void worker_ipc::ipc_run(coroutine_handler ch) {
+    boost::system::error_code error;
+    std::shared_ptr<ipc::message_t> msg;
+    std::uint64_t tmp;
+    
+    // 连接主进程 IPC 通道
+    boost::asio::local::stream_protocol::endpoint addr(svrsck_);
+    socket_->async_connect(addr, ch[error]);
+    if (error) goto IPC_FAILED;
+    ++status_; // 1
+    // 发送注册消息
+    msg = ipc::create_message();
+    msg->command = ipc::COMMAND_REGISTER;
+    msg->source  = idx_;
+    msg->target  = 0;
+    msg->length  = 0;
+    boost::asio::async_write(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), ch[error]);
+    if (error) goto IPC_FAILED;
+    ++status_; // 2
+    // 已经缓冲在队列中的数据开始发送
+    send_next();
+    while(true) {
+        msg = ipc::create_message();
+        boost::asio::async_read(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t)), ch[error]);
+        if(error) break;
+        if(msg->length > ipc::MESSAGE_INIT_CAPACITY - sizeof(ipc::message_t)) {
+            ipc::relloc_message(msg, 0, msg->length);
+        }
+        if(msg->length > 0) {
+            boost::asio::async_read(*socket_, boost::asio::buffer(&msg->payload[0], msg->length), ch[error]);
+            if(error) break;
+        }
+        if(!on_message(msg)) break;
+        auto pcb = callback_.extract(msg->unique_id);
+        if(!pcb.empty()) {
+            pcb.mapped().res = msg;
+            pcb.mapped().cch.resume();
+        }
+    }
+IPC_FAILED:
+    if (error && error != boost::asio::error::operation_aborted && error != boost::asio::error::eof) 
+        output() << "[" << util::system_time() << "] (ERROR) Failed during IPC with master: (" << error.value() << ") " << error.message() << "\n";
+    socket_->close(error);
+}
+
+void worker_ipc::ipc_close() {
+    socket_->close();
+}
+
+bool worker_ipc::ipc_enabled() {
+    return status_ > -1;
+}
+// 由于实际调用者来自主线程，需要线性转接到（多个）工作线程中
+void worker_ipc::send(std::shared_ptr<ipc::message_t> msg) {
+    assert(status_ > -1);
+    boost::asio::post(io_, [this, self = ipc_self(), msg] () {
+        sendq_.push_back(msg);
+        if(status_ > 1 && sendq_.size() == 1) send_next();
+        // C++11 后， std::list::size() 是常数及时间复杂度
+    });
+}
+
+void worker_ipc::send_next() {
+    if (sendq_.empty()) return;
+    std::shared_ptr<ipc::message_t> msg = sendq_.front();
+
+    boost::asio::async_write(*socket_, boost::asio::buffer(msg.get(), sizeof(ipc::message_t) + msg->length), [this] (const boost::system::error_code& error, std::size_t size) {
+        if(error) {
+            output() << "[" << util::system_time() << "] [FATAL] Failed during IPC with master: (" << error.value() << ") " << error.message() << "\n";
+            return;
+        }
+        sendq_.pop_front();
+        send_next();
+    });
+}
diff --git a/src/worker_ipc.h b/src/worker_ipc.h
new file mode 100644
index 0000000..cdc8671
--- /dev/null
+++ b/src/worker_ipc.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "vendor.h"
+#include "ipc.h"
+
+class worker_ipc {
+public:
+    worker_ipc(boost::asio::io_context& io, std::uint8_t idx);
+    virtual ~worker_ipc();
+    using socket_ptr = std::shared_ptr<boost::asio::local::stream_protocol::socket>;
+    
+    virtual std::ostream& output() = 0;
+    // 请求并等待响应
+    std::shared_ptr<ipc::message_t> ipc_request(std::shared_ptr<ipc::message_t> req, coroutine_handler& ch);
+    // 请求（无响应）
+    void ipc_request(std::shared_ptr<ipc::message_t> data);
+    // 简化 notify 请求
+    void ipc_notify(std::uint8_t target, php::value data);
+    void ipc_start();
+    // 读取及消息监听
+    void ipc_run(coroutine_handler ch);
+    // 关闭通道
+    void ipc_close();
+    // IPC 检测
+    bool ipc_enabled();
+protected:
+    virtual std::shared_ptr<worker_ipc> ipc_self() = 0;
+    virtual bool on_message(std::shared_ptr<ipc::message_t> msg) = 0;
+private:
+    boost::asio::io_context&                       io_;
+    std::filesystem::path                      svrsck_;
+    std::uint8_t                                  idx_;
+    socket_ptr                                 socket_;
+    std::map<std::uint32_t, ipc::callback_t> callback_;
+    std::list<std::shared_ptr<ipc::message_t>>  sendq_;
+    int                                        status_ = -1;
+
+    void send(std::shared_ptr<ipc::message_t> msg);
+    void send_next();
+};
diff --git a/src/worker_logger.cpp b/src/worker_logger.cpp
new file mode 100644
index 0000000..9794049
--- /dev/null
+++ b/src/worker_logger.cpp
@@ -0,0 +1,26 @@
+#include "worker_logger.h"
+#include "worker_logger_manager.h"
+#include "worker_logger_buffer.h"
+
+worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx) 
+: idx_(0)
+, path_(path)
+, wlb_(new worker_logger_buffer(mgr, idx))
+, oss_(new std::ostream(wlb_.get())) {
+    
+}
+
+worker_logger::worker_logger(worker_logger_manager* mgr, const std::filesystem::path& path, std::uint8_t idx, bool local)
+: idx_(0)
+, path_(path) {
+    if(path.string() != "<clog>") {
+        auto fb = new std::filebuf();
+        fb->open(path, std::ios_base::app);
+        oss_.reset(new std::ostream(fb));
+        wlb_.reset(fb);
+    }
+}
+
+std::ostream& worker_logger::stream() {
+    return oss_ ? (*oss_) : std::clog;
+}
diff --git a/src/worker_logger.h b/src/worker_logger.h
new file mode 100644
index 0000000..c9a0811
--- /dev/null
+++ b/src/worker_logger.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "vendor.h"
+#include <filesystem>
+
+class coroutine_handler;
+class worker_logger_manager;
+class worker_logger_buffer;
+class worker_logger {
+public:
+    worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index);
+    worker_logger(worker_logger_manager* mgr, const std::filesystem::path& file, std::uint8_t index, bool local);
+    std::ostream& stream();
+private:
+    std::uint8_t           idx_;
+    std::filesystem::path path_;
+    std::unique_ptr<std::streambuf> wlb_;
+    std::unique_ptr<std::ostream>   oss_;
+    friend class worker_logger_manager;
+};
+
diff --git a/src/worker_logger_buffer.cpp b/src/worker_logger_buffer.cpp
new file mode 100644
index 0000000..7961d07
--- /dev/null
+++ b/src/worker_logger_buffer.cpp
@@ -0,0 +1,43 @@
+#include "worker_logger_buffer.h"
+#include "worker_logger_manager.h"
+#include "worker_ipc.h"
+
+worker_logger_buffer::worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx)
+: mgr_(mgr)
+, msg_(ipc::create_message())
+, idx_(idx) {
+    cap_ = msg_->length;
+    msg_->xdata[0]  = idx_;
+    msg_->length = 0;
+}
+
+int worker_logger_buffer::overflow(int ch) {
+    char c = ch;
+    msg_->payload[msg_->length] = c;
+    ++msg_->length;
+    if(c == '\n') transfer_msg();
+    return ch;
+}
+
+long worker_logger_buffer::xsputn(const char* s, long c) {
+    if(cap_ - msg_->length < c) {
+        assert(cap_ < 2048 * 1024);
+        ipc::relloc_message(msg_, msg_->length, msg_->length + c);
+    }
+    std::memcpy(msg_->payload + msg_->length, s, c);
+    msg_->length += c;
+
+    if(s[c-1] == '\n') transfer_msg();
+    return c;
+}
+
+void worker_logger_buffer::transfer_msg() {
+    msg_->command = ipc::COMMAND_LOGGER_DATA;
+    msg_->target  = 0;
+    mgr_->ipc_->ipc_request(msg_);
+
+    msg_ = ipc::create_message();
+    cap_ = msg_->length;
+    msg_->xdata[0]   = idx_; // 实际写入的日志文件
+    msg_->length  = 0; // 以 length 累计当前需要发送的数据
+}
\ No newline at end of file
diff --git a/src/worker_logger_buffer.h b/src/worker_logger_buffer.h
new file mode 100644
index 0000000..362da50
--- /dev/null
+++ b/src/worker_logger_buffer.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <memory>
+#include <streambuf>
+#include "ipc.h"
+
+class worker_logger_manager;
+class worker_logger_buffer: public std::streambuf {
+public:
+    worker_logger_buffer(worker_logger_manager* mgr, std::uint8_t idx);
+protected:
+    int overflow(int ch = EOF) override;
+    long xsputn(const char* s, long c) override;
+private:
+    worker_logger_manager*          mgr_;
+    std::shared_ptr<ipc::message_t> msg_;
+    std::uint16_t                   cap_;
+    std::uint8_t                    idx_;
+
+    void transfer_msg();
+};
diff --git a/src/worker_logger_manager.cpp b/src/worker_logger_manager.cpp
new file mode 100644
index 0000000..0cb3dcf
--- /dev/null
+++ b/src/worker_logger_manager.cpp
@@ -0,0 +1,61 @@
+#include "worker_logger_manager.h"
+#include "worker_logger.h"
+#include "worker_ipc.h"
+
+worker_logger_manager::~worker_logger_manager() {
+    // std::cout << "~worker_logger_manager\n";
+}
+
+std::shared_ptr<worker_logger> worker_logger_manager::lm_connect(const std::string& file, coroutine_handler& ch) {
+    std::filesystem::path path = file;
+    path = path.lexically_normal();
+
+    for (auto i=logger_.begin();i!=logger_.end();) {
+        if(i->second.expired()) i = logger_.erase(i);
+        else {
+            std::shared_ptr<worker_logger> lg = i->second.lock();
+            if(lg->path_ == path) return lg;
+            ++i;
+        }
+    }
+    
+    std::shared_ptr<worker_logger> wl;
+    std::uint8_t index = 0;
+    if (ipc_->ipc_enabled()) {
+        auto msg = ipc::create_message();
+        msg->command   = ipc::COMMAND_LOGGER_CONNECT;
+        msg->target    = 0; // 发送给主进程的消息
+        msg->unique_id = ipc::create_uid();
+        msg->length = file.size();
+        std::memcpy(msg->payload, file.data(), msg->length);
+        msg = ipc_->ipc_request(msg, ch);
+        index = msg->xdata[0];
+        // 使用该日志文件标号创建 LOGGER 对象，以支持实际日志发送
+        wl = std::make_shared<worker_logger>(this, file, index);
+    }
+    else {
+        index = lindex_;
+        wl = std::make_shared<worker_logger>(this, file, index, true);
+        ++lindex_;
+    }
+    
+    logger_.emplace(index, wl);
+    return wl;
+}
+
+void worker_logger_manager::lm_destroy(std::uint8_t idx) {
+    auto i = logger_.find(idx);
+    if(i == logger_.end()) return;
+    logger_.erase(i);
+
+    // IPC: 通知父进程日志器不再使用
+    auto msg = ipc::create_message();
+    msg->command = ipc::COMMAND_LOGGER_DESTROY;
+    msg->target  = idx;
+    msg->length  = 0;
+    ipc_->ipc_request(msg);
+}
+
+void worker_logger_manager::lm_close() {
+
+}
\ No newline at end of file
diff --git a/src/worker_logger_manager.h b/src/worker_logger_manager.h
new file mode 100644
index 0000000..8f1d564
--- /dev/null
+++ b/src/worker_logger_manager.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "ipc.h"
+
+class worker_ipc;
+class worker_logger;
+class worker_logger_manager {
+public:
+    worker_logger_manager(worker_ipc* c): ipc_(c) {}
+    virtual ~worker_logger_manager();
+    // 连接到指定的日志文件
+    std::shared_ptr<worker_logger> lm_connect(const std::string& filepath, coroutine_handler& ch);
+    void                           lm_destroy(std::uint8_t idx);
+    void lm_close();
+protected:
+    virtual std::shared_ptr<worker_logger_manager> lm_self() = 0;
+private:
+    std::map<std::uint8_t, std::weak_ptr<worker_logger>> logger_;
+    std::uint8_t                                         lindex_ = 0;
+    worker_ipc* ipc_; // 目前的继承实现方式，此字段与 this 相等
+    friend class worker_logger;
+    friend class worker_logger_buffer;
+};
diff --git a/test/compress_1.php b/test/compress_1.php
new file mode 100644
index 0000000..b2e5487
--- /dev/null
+++ b/test/compress_1.php
@@ -0,0 +1,15 @@
+<?php
+flame\init("compress_1");
+
+flame\go(function() {
+    $x = flame\compress\snappy_compress(json_encode(["a"=>1234567890123,"b"=>1234567890123,"x"=>"中文","y"=>"中文"], JSON_UNESCAPED_UNICODE));
+    echo "(", strlen($x), ") ", bin2hex($x), "\n";
+    $y = flame\compress\snappy_uncompress($x);
+    echo "(", strlen($y), ") ", print_r(json_decode($y,true)), "\n";
+
+    $x = "3f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d842256000000000000000000000400000000000000003f507b2261223a313233343536373839303132332c22624212006078223a22e4b8ade69687222c2279223a22e4b8ade69687227d22560000b0a0af832256000090a1af832256000010a0af8322560000e0239d84225600000000000000000000040000000000000000";
+    $y = flame\compress\snappy_compress($x);
+    echo strlen($x), " < ", strlen($y), "\n";
+});
+
+flame\run();
diff --git a/test/core.php b/test/core.php
deleted file mode 100644
index b6f1532..0000000
--- a/test/core.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-dl("flame.so");
-flame\fork(1);
-// 启动 “协程”
-// “协程” 将在 run 之后被分别调度执行
-flame\go(test1());
-// “协程” 允许使用以下两种形式：
-// 1. 带有 yield 形式的函数定义（调用后会返回 Generator 对象）;
-// 2. Generator 对象；
-flame\go(test2);
-// 下面两行 go + run 可以合并为一行：
-// flame\run(test3());
-flame\go(test3());
-flame\run();
-function test1() {
-	for($i=0;$i<10;++$i) {
-		// 需要使用 yield 形式调用 flame 提供的 sleep 函数，以此来实现 “调度式阻塞” 并行逻辑
-		yield flame\sleep(500);
-		echo "[1] ", time(), " -> ", $i,"\n";
-	}
-}
-function test2() {
-	for($i=0;$i<10;++$i) {
-		yield flame\sleep(700);
-		echo "[2] ", time(), " -> ", $i,"\n";
-	}
-}
-
-function test3() {
-	for($i=0;$i<10;++$i) {
-		yield $i;
-		sleep(1); // PHP 原生提供的大部分函数均是阻塞形式，无法行程 “调度”，是真实的阻塞
-		echo "[3] ", time(), " -> ", $i,"\n";
-	}
-}
diff --git a/test/coroutine_1.php b/test/coroutine_1.php
new file mode 100644
index 0000000..19ad46b
--- /dev/null
+++ b/test/coroutine_1.php
@@ -0,0 +1,20 @@
+<?php
+flame\init("coroutine_1");
+
+flame\go(function() {
+    flame\time\sleep(100);
+    echo "c1:", flame\co_id(), "\n";
+    flame\time\sleep(200);
+    echo "c1:", flame\co_id(), "\n";
+});
+
+
+flame\go(function() {
+    flame\time\sleep(200);
+    echo "c2:", flame\co_id(), "\n";
+    flame\time\sleep(100);
+    echo "c2:", flame\co_id(), "\n";
+});
+
+
+flame\run();
\ No newline at end of file
diff --git a/test/coroutine_2.php b/test/coroutine_2.php
new file mode 100644
index 0000000..0d26680
--- /dev/null
+++ b/test/coroutine_2.php
@@ -0,0 +1,23 @@
+<?php
+flame\init("coroutine_1");
+
+flame\go(function() {
+    flame\go(function() {
+        for($i=0;$i<1000;++$i) {
+            flame\time\sleep(1);
+            flame\go(function() use($i) {
+                echo "x: ".flame\co_id()."\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "a: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "b: $i\n";
+                flame\time\sleep(mt_rand(10, 50));
+                echo "c: $i\n";
+            });
+        }
+        echo "done2\n";
+    });
+    echo "done1\n";
+});
+
+flame\run();
\ No newline at end of file
diff --git a/test/encoding_1.php b/test/encoding_1.php
new file mode 100644
index 0000000..4b7d247
--- /dev/null
+++ b/test/encoding_1.php
@@ -0,0 +1,11 @@
+<?php
+flame\init("hash_1");
+
+flame\go(function() {
+    $x = flame\encoding\bson_encode(["a"=>1234567890123,"x"=>"中文"]);
+    echo "(",strlen($x),") ", bin2hex($x), "\n";
+    $y = flame\encoding\bson_decode($x);
+    var_dump( $y );
+});
+
+flame\run();
diff --git a/test/exception_1.php b/test/exception_1.php
new file mode 100644
index 0000000..ca46bfd
--- /dev/null
+++ b/test/exception_1.php
@@ -0,0 +1,24 @@
+<?php
+flame\init("exception_1");
+
+function test() {
+    flame\time\sleep(1000);
+    throw new Exception("Aaaaaaa");
+}
+function test2() {
+    test();
+}
+
+flame\go(function() {
+    flame\time\sleep(1000);
+    test2();
+});
+
+// 可选
+flame\on("exception", function($ex) {
+    echo "uncaught exception: ", $ex->getMessage() , ", you can do something with it.\n";
+    flame\time\sleep(1000);
+    echo "quit\n";
+});
+
+flame\run();
diff --git a/test/hash_1.php b/test/hash_1.php
new file mode 100644
index 0000000..8993905
--- /dev/null
+++ b/test/hash_1.php
@@ -0,0 +1,14 @@
+<?php
+flame\init("hash_1");
+
+flame\go(function() {
+    var_dump( flame\hash\murmur2("abcdef", true) );
+    var_dump( flame\hash\xxh64("abcdef", 0, true) );
+    var_dump( flame\hash\crc64("abcdef", true) );
+
+    var_dump( flame\hash\murmur2("abcdef") );
+    var_dump( flame\hash\xxh64("abcdef") );
+    var_dump( flame\hash\crc64("abcdef") );
+});
+
+flame\run();
diff --git a/test/http_1.php b/test/http_1.php
new file mode 100644
index 0000000..443bba3
--- /dev/null
+++ b/test/http_1.php
@@ -0,0 +1,21 @@
+<?php
+flame\init("http_1");
+
+for($i=0;$i<10;++$i) {
+    flame\go(function() {
+        flame\http\get("http://www.baidu.com/", 8000);
+        flame\http\get("https://www.baidu.com/", 8000);
+        flame\http\get("https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/home/img/qrcode/zbios_efde696.png", 3000);
+        // var_dump( flame\http\put("http://127.0.0.1:8687/", "this is the data for put request", 800000) );
+        // var_dump( flame\http\get("http://127.0.0.1:8687/") );
+        $req = new flame\http\client_request('https://www.baidu.com/');
+        // $req = new flame\http\client_request('http://127.0.0.1:8687/');
+        $req->method = "POST";
+        $req->header["test1"] = "string";
+        $req->header["test2"] = 123456;
+        // $req->http_version(flame\http\client_request::HTTP_VERSION_2_PRI);
+        var_dump(flame\http\exec($req));
+        echo "done.\n";
+    });
+}
+flame\run();
diff --git a/test/http_2.php b/test/http_2.php
new file mode 100644
index 0000000..9d3ca42
--- /dev/null
+++ b/test/http_2.php
@@ -0,0 +1,69 @@
+<?php
+flame\init("http_2");
+
+class JsonObject implements JsonSerializable {
+    function jsonSerialize()
+    {
+        $date = new DateTime();
+        return ["date" => $date->unix()];
+    }
+}
+
+flame\go(function() {
+    $server = new flame\http\server(":::56101");
+    $q = new flame\queue();
+
+    $server->before(function($req, $res, $m) {
+        $req->data["start"] = flame\time\now();
+        if(!$m) {
+            $res->status = intval(substr($req->path, 1));
+            $res->header["Access-Control-Allow-Origin"] = "*";
+            $res->header["Content-Type"] = "application/json";
+            $res->body = ["a" => "bbbb"];
+            return false;
+        }
+    })->get("/", function($req, $res) {
+        $req->header["test1"] = "string";
+        $req->header["test2"] = 123456;
+        $res->set_cookie("a","b", 3600);
+        $res->body = json_encode($req);
+    })->post("/", function($req, $res) {
+        $res->status = 200;
+        $res->header["Content-Type"] = "application/json";
+        $res->body = $req->body;
+        // 文件上传
+        var_dump($req->file);
+        // return $res->body = json_encode($req);
+    })->get("/poll", function($req, $res) use($q) {
+        $res->header["Content-Type"] = "text/event-stream";
+        $res->header["Cache-Control"] = "no-cache";
+        $res->write_header(200);
+        while($data = $q->pop()) {
+            $res->write($data);
+        }
+    })->get("/push", function($req, $res) use($q) {
+        $q->push("event: time\ndata: ".flame\time\now()."\n\n");
+        $res->body = "done";
+    })->get("/file", function($req, $res) {
+        $res->header["Content-Type"] = "text/html";
+        $res->file(__DIR__, "/coroutine_1.php");
+    })->get("/exception", function($req, $res) {
+        throw new exception("Some Exception");
+        $res->body = "done";
+    })->get("/json", function($req, $res) {
+        $res->header["content-type"] = "application/json";
+        $res->body = ["data" => new JsonObject()];
+    })->after(function($req, $res, $m) {
+        $end = flame\time\now();
+        // echo "elapsed: ", ($end - $req->data["start"]), "ms\n";5
+    });
+    $server->run();
+    echo "done2\n";
+});
+
+// flame\on("quit", function() use($server) {
+//     echo "quit\n";
+//     $server->close();
+// });
+
+flame\run();
diff --git a/test/kafka_1.php b/test/kafka_1.php
new file mode 100644
index 0000000..d199ab6
--- /dev/null
+++ b/test/kafka_1.php
@@ -0,0 +1,21 @@
+<?php
+flame\init("kafka_1");
+
+flame\go(function() {
+    $producer = flame\kafka\produce([
+        "bootstrap.servers" => "host1:port1,host2:port2",
+    ], ["test"]);
+
+    for($i=0;$i<10000;++$i) {
+        echo $i, "\n";
+        $message = new flame\kafka\message("this is the payload", "this is the key");
+        $message->header["key"] = "value";
+        $producer->publish("test", $message);
+        flame\time\sleep(50);
+        $producer->publish("test", "this is the payload", "this is the key");
+        flame\time\sleep(50);
+    }
+    $producer->flush();
+});
+
+flame\run();
diff --git a/test/kafka_2.php b/test/kafka_2.php
new file mode 100644
index 0000000..e21164b
--- /dev/null
+++ b/test/kafka_2.php
@@ -0,0 +1,23 @@
+<?php
+flame\init("kafka_1");
+
+flame\go(function() {
+    $consumer = flame\kafka\consume([
+        "bootstrap.servers" => "host1:port1,host2:port2",
+        "group.id" => "flame-test-consumer",
+        "auto.offset.reset" => "smallest",
+    ], ["test"]);
+    flame\go(function() use($consumer) {
+        // 60 秒后关闭消费者
+        flame\time\sleep(60000);
+        $consumer->close();
+    });
+    $consumer->run(function($msg) {
+        var_dump($msg);
+        flame\time\sleep(50);
+    });
+    echo "done.\n";
+    unset($consumer);
+});
+
+flame\run();
diff --git a/test/logger_1.php b/test/logger_1.php
new file mode 100644
index 0000000..5f8013d
--- /dev/null
+++ b/test/logger_1.php
@@ -0,0 +1,12 @@
+<?php
+flame\init("logger_1", [
+    "logger" => __DIR__."/logger_1.log"
+]);
+
+flame\go(function() {
+    flame\log\warning("This is a warning message");
+    flame\time\sleep(1000);
+    flame\log\warn("This is another warning message");
+});
+
+flame\run();
diff --git a/test/logger_2.php b/test/logger_2.php
new file mode 100644
index 0000000..5dd0716
--- /dev/null
+++ b/test/logger_2.php
@@ -0,0 +1,22 @@
+<?php
+flame\init("logger_2", [
+    "logger" => __DIR__."/logger_2.log", // 目标日志文件
+]);
+
+flame\go(function() {
+    for($i=0;$i<10000;++$i) {
+        flame\time\sleep(rand(4000, 5000));
+        flame\log\trace("WORKING:", intval(getenv("FLAME_CUR_WORKER")));
+        flame\time\sleep(rand(4000, 5000));
+        // 标准、错误输出在多进程模式重定向到默认日志文件
+        echo "[", flame\time\iso(), "] (TRACE) WORKING: ", intval(getenv("FLAME_CUR_WORKER")), "\n";
+    }
+    flame\log\trace("DONE:", intval(getenv("FLAME_CUR_WORKER")));
+});
+
+flame\on("quit", function() {
+    flame\log\trace("QUITING:", intval(getenv("FLAME_CUR_WORKER")));
+    // flame\quit();
+});
+
+flame\run();
diff --git a/test/logger_3.php b/test/logger_3.php
new file mode 100644
index 0000000..b41b4f3
--- /dev/null
+++ b/test/logger_3.php
@@ -0,0 +1,14 @@
+<?php
+flame\init("logger_3", [
+    "logger" => __DIR__."/logger_3.log"
+]);
+
+flame\go(function() {
+    $logger = flame\log\connect(__DIR__."/logger_3.log.extra");
+    for($i=0;$i<10000;++$i) { 
+        $logger->write("无前缀的日志数据：", flame\time\now());
+        flame\time\sleep(rand(4000, 5000));
+    }
+});
+
+flame\run();
diff --git a/test/mongodb_1.php b/test/mongodb_1.php
new file mode 100644
index 0000000..c015991
--- /dev/null
+++ b/test/mongodb_1.php
@@ -0,0 +1,27 @@
+<?php
+flame\init("mongodb_1");
+
+flame\go(function() {
+    $cli = flame\mongodb\connect("mongodb://username:password@host1:port1,host2:port2/database?replicaSet=rs1");
+    for($i=0;$i<10;++$i) {
+        flame\go(function() use($cli, $i) {
+            $cs = $cli->execute([
+                "find"   => "photo",
+                "filter" => ["uid" => 25865119921602568],
+                "limit"  => 10,
+            ]);
+            echo "execute\n";
+            // var_dump( $cs->fetch_row() );
+            $cs->fetch_all();
+            flame\time\sleep(100);
+            // unset($cs);
+            echo "sleep\n";
+            $cs = $cli->photo->find(["uid" => 25865119921602568]);
+            // var_dump( $cs->fetch_all() );
+            echo "done: ". $i. "\n";
+        });
+    }
+});
+
+
+flame\run();
diff --git a/test/mutex_1.php b/test/mutex_1.php
new file mode 100644
index 0000000..b3e3d0b
--- /dev/null
+++ b/test/mutex_1.php
@@ -0,0 +1,32 @@
+<?php
+flame\init("mutex_1");
+
+flame\go(function() {
+    $mutex = new flame\mutex();
+    flame\go(function() use($mutex) {
+        echo "(2) awaiting lock\n";
+        $guard = new flame\guard($mutex);
+        echo "(2) lock acquired\n";
+        flame\time\sleep(5000);
+        echo "(2) protected echo\n";
+        echo "(2) lock releasing\n";
+    });
+    flame\go(function() use($mutex) {
+        echo "(3) awaiting lock\n";
+        $mutex->lock();
+        echo "(3) lock acquired\n";
+        flame\time\sleep(1000);
+        echo "(3) protected echo\n";
+        $mutex->unlock();
+        echo "(3) lock released\n";
+    });
+    echo "(1) awaiting lock\n";
+    $mutex->lock();
+    echo "(1) lock acquired\n";
+    flame\time\sleep(2000);
+    echo "(1) protected echo\n";
+    $mutex->unlock();
+    echo "(1) lock released\n";
+});
+
+flame\run();
diff --git a/test/mysql_1.php b/test/mysql_1.php
new file mode 100644
index 0000000..f09f052
--- /dev/null
+++ b/test/mysql_1.php
@@ -0,0 +1,28 @@
+<?php
+
+flame\init("mysql_1");
+
+for($i=0;$i<5;++$i) {
+    flame\go(function() {
+        $cli = flame\mysql\connect("mysql://user:password@host:port/database");
+        var_dump( $cli->escape('a.b', '`') );
+        $rs = $cli->query("SELECT * FROM `test_0` LIMIT 3");
+        $row = $rs->fetch_row();
+        var_dump($row);
+        $row = $rs->fetch_row();
+        var_dump($row);
+        $data = $rs->fetch_all();
+        var_dump($data);
+        $tx = $cli->begin_tx();
+        try{
+            $tx->insert("test_0", ["key"=>123, "val"=>"456"]);
+            $tx->update("test_0", ["key"=>123], ["val"=>"567"]);
+            $tx->commit();
+        }catch(Exception $ex) {
+            $tx->rollback();
+            return;
+        }    
+    });
+}
+
+flame\run();
diff --git a/test/net_http_server.php b/test/net_http_server.php
deleted file mode 100644
index 70a0bcf..0000000
--- a/test/net_http_server.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-dl("flame.so");
-
-flame\run(function() {
-	$server = new flame\net\tcp_server();
-	$server->listen("127.0.0.1", 6676);
-	while(true) {
-		$socket = yield $server->accept();
-		// 启动“协程”，不阻塞 accept 过程
-		flame\go(function() use($socket) {
-            while(true) {
-                echo "request ......\n";
-                //throw new Exception("aaaaa");
-                try {
-                $req = yield flame\net\http\request::parse($socket);
-                } catch (exception $ex) {
-                    var_dump($ex);
-                    //throw $ex;
-                    break;
-                }
-                echo "process complete ......\n";
-                var_dump($req);
-                //var_dump($req->head["cookie"]);
-            }
-		});
-	}
-});
-
diff --git a/test/net_tcp_server.php b/test/net_tcp_server.php
deleted file mode 100644
index 880e9d8..0000000
--- a/test/net_tcp_server.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-dl("flame.so");
-
-flame\run(function() {
-	$server = new flame\net\tcp_server();
-	$server->listen("127.0.0.1", 6676);
-	while(true) {
-		$socket = yield $server->accept();
-		// 启动“协程”，不阻塞 accept 过程
-		flame\go(function() use($socket) {
-			while(true) {
-				/*$packet = yield $socket->read("]");*/
-				//echo $socket->remote_addr(), ":", $socket->remote_port(), " -> ", $packet, "\n";
-				//yield $socket->write($packet);
-				//echo "-> ", $packet, "\n";
-				//$packet = yield $socket->read("\n");
-				//echo $socket->remote_addr(), ":", $socket->remote_port(), " -> ", $packet;
-				//yield $socket->write($packet);
-				/*echo "-> ", $packet;*/
-                $req = yeild $request($socket);
-			}
-		});
-	}
-	// $server->close();
-	// close 可选，对象会自动销毁
-});
diff --git a/test/net_tcp_socket.php b/test/net_tcp_socket.php
deleted file mode 100644
index 8c82af7..0000000
--- a/test/net_tcp_socket.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-dl("flame.so");
-
-flame\run(function() {
-	$socket = new flame\net\tcp_socket();
-	yield $socket->connect("127.0.0.1", 6676);
-	echo "local_addr: ", $socket->local_addr, ":", $socket->local_port, "\n";
-	while(true) {
-		$packet = "method-length[".rand()."]";
-		echo "<- ", $packet, "\n";
-		$n = yield $socket->write($packet);
-		// 读取方式 1. 读取指定长度的数据
-		$packet = yield $socket->read($n);
-		echo "-> ", $packet, "\n";
-		yield flame\sleep(1000);
-		$packet = "method-delim[".rand()."]\n";
-		echo "<- ", $packet;
-		yield $socket->write($packet);
-		// 读取方式 2. 读取指定结束符标识的数据
-		$packet = yield $socket->read("\n");
-		echo "-> ", $packet;
-		yield flame\sleep(3000);
-	}
-	// close 过程可选，$socket 对象析构时会自动关闭
-	// $socket->close();
-});
diff --git a/test/net_udp_client.php b/test/net_udp_client.php
deleted file mode 100644
index 41817f3..0000000
--- a/test/net_udp_client.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-dl("flame.so");
-flame\run(function() {
-	$socket = new flame\net\udp_socket();
-	// "连接" 指定地址发送内容，注意 connect 后不能调用 write_to 函数
-	yield $socket->connect("127.0.0.1", 6676);
-	echo "local_addr: ", $socket->local_addr, ":", $socket->local_port, "\n";
-	while(true) {
-		try{
-			// connect 方式 write 可能发生异常 connection refused
-			yield $socket->write("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-			// 上面 connect + write 可以考虑替换为：
-			// yield $socket->write_to("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "127.0.0.1", 6676);
-		}catch(Exception $e) {
-			var_dump($e);
-		}
-		yield flame\sleep(10);
-	}
-	// close 操作可选，析构时会自动清理
-	// $socket->close();
-});
diff --git a/test/net_udp_server.php b/test/net_udp_server.php
deleted file mode 100644
index 818c2b3..0000000
--- a/test/net_udp_server.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-dl("flame.so");
-// 启动 2 个子进程
-// flame\fork(1);
-// 共计 3 个进程会执行下面代码
-flame\run(function() {
-	$server = new flame\net\udp_server();
-	// 接收来自指定端口的数据
-	try{
-		$server->bind("0.0.0.0", 6676);
-	}catch(Exception $ex) {
-		echo $ex, "\n";
-		exit;
-	}
-	while(true) {
-		$packet = yield $server->read();
-		// remote_addr() / remote_port() 与 local_* 不同，前者为方法，后者为属性
-	 	echo "from: ", $server->remote_addr(), ":", $server->remote_port(), " => ", $packet, "\n";
-	}
-	// close 动作可选，对象销毁时会自动 close 底层的 socket
-	// $socket->close();
-});
diff --git a/test/os_1.php b/test/os_1.php
new file mode 100644
index 0000000..e861013
--- /dev/null
+++ b/test/os_1.php
@@ -0,0 +1,29 @@
+<?php
+flame\init("os_1");
+flame\go(function() {
+	$info = flame\os\interfaces();
+	var_dump($info);
+	$proc = flame\os\spawn("ls", ["-l"], [
+		"cwd" => "/data/htdocs",
+	]);
+	$proc->wait();
+	var_dump( $proc->stdout() );
+
+	$proc = flame\os\spawn("ping", ["-c", 5, "www.baidu.com"], [
+		"cwd" => "/data/htdocs",
+	]);
+	flame\time\sleep(1000);
+	$proc->wait();
+	
+	
+	for($i = 0; $i<10; ++$i) {
+		flame\go(function() {
+			for($j=0;$j<100;++$j) {
+				flame\os\exec("ping", ["-c", 1, "www.baidu.com"]);
+			}
+		});
+	}
+	flame\time\sleep(60000);
+	echo "done.\n";
+});
+flame\run();
diff --git a/test/queue_1.php b/test/queue_1.php
new file mode 100644
index 0000000..c7c5931
--- /dev/null
+++ b/test/queue_1.php
@@ -0,0 +1,59 @@
+<?php
+flame\init("queue_1");
+
+flame\go(function() {
+    $q1 = new flame\queue();
+    $q2 = new flame\queue();
+    $q3 = new flame\queue();
+    flame\go(function() use($q1) {
+        for($i=1;$i<10;++$i) {
+            $q1->push($i);
+            flame\time\sleep(rand(50, 100));
+        }
+        $q1->close();
+    });
+    flame\go(function() use($q2) {
+        for($i=1;$i<10;++$i) {
+            $q2->push($i + 10);
+            flame\time\sleep(rand(50, 150));
+        }
+        $q2->close();
+    });
+    flame\go(function() use($q3) {
+        for($i=1;$i<10;++$i) {
+            $q3->push($i + 100);
+            flame\time\sleep(rand(150, 250));
+        }
+        $q3->close();
+    });
+    flame\go(function() use($q1) {
+        while($x = $q1->pop()) {
+            echo "(1) q1: ", $x, "\n";
+            flame\time\sleep(rand(100, 200));
+        }
+        echo "(1) q1: consume done\n";
+    });
+    flame\go(function() use($q1) {
+        while($x = $q1->pop()) {
+            echo "(2) q1: ", $x, "\n";
+            flame\time\sleep(rand(100, 200));
+        }
+        echo "(2) q1: consume done\n";
+    });
+    flame\go(function() use($q2, $q3) {
+        while($q = flame\select($q2, $q3)) {
+            // 必须使用 全等 符号比较对象
+            if($q === $q2) {
+                echo "q2: ", $q->pop(), "\n";
+            }else if($q === $q3) {
+                echo "q3: ", $q->pop(), "\n";
+            }else{
+                // 
+            }
+        }
+        echo "q2: consume done\n";
+        echo "q3: consume done\n";
+    });
+});
+
+flame\run();
diff --git a/test/rabbitmq_1.php b/test/rabbitmq_1.php
new file mode 100644
index 0000000..f706ec1
--- /dev/null
+++ b/test/rabbitmq_1.php
@@ -0,0 +1,34 @@
+<?php
+flame\init("rabbitmq_1");
+
+flame\go(function() {
+    $consumer = flame\rabbitmq\consume("amqp://username:password@host:port/vhost", "test_q1");
+    flame\go(function() use($consumer) {
+        flame\time\sleep(5000);
+        echo "close\n";
+        $consumer->close();
+    });
+    $consumer->run(function($msg) use($consumer) {
+        var_dump($msg);
+        $consumer->confirm($msg);
+    });
+    echo "consumer done\n";
+});
+
+flame\go(function() {
+    $producer = flame\rabbitmq\produce("amqp://username:password@host:port/vhost");
+    for($i=0;$i<20;++$i) {
+        flame\time\sleep(20);
+        echo $i * 2, "\n";
+        $producer->publish("", "this is the data", "test_q1");
+        flame\time\sleep(20);
+        $message = new flame\rabbitmq\message("abcdefg", "test_q1");
+        $message->header["a"] = 1234567890123;
+        $message->header["b"] = "Asd";
+        $message->header["c"] = 123.456;
+        echo $i * 2 +1, "\n";
+        $producer->publish("", $message);
+    }
+});
+
+flame\run();
diff --git a/test/redis_1.php b/test/redis_1.php
new file mode 100644
index 0000000..c2cff4d
--- /dev/null
+++ b/test/redis_1.php
@@ -0,0 +1,25 @@
+<?php
+flame\init("redis_1");
+
+flame\go(function() {
+    $cli = flame\redis\connect("redis://auth:password@host:port/database");
+    var_dump( $cli->set("test1", "this is a text") );
+    var_dump( $cli->get("test1") );
+    var_dump( $cli->get("test1") );
+    var_dump( $cli->set("test2", 123456) );
+    var_dump( $cli->get("test2") );
+    var_dump( $cli->incr("test2") );
+
+    var_dump( $cli->zrange("rank:relay_20181122", 0, 30, "WITHSCORES") );
+    var_dump( $cli->hgetall("rank:relay_201811") );
+
+    $rv = $cli->multi()
+        ->get("test1")
+        ->get("test2")
+        ->del("test1")
+        ->del("test2")
+        ->exec();
+    var_dump( $rv );
+});
+
+flame\run();
diff --git a/test/sleep_1.php b/test/sleep_1.php
new file mode 100644
index 0000000..620c891
--- /dev/null
+++ b/test/sleep_1.php
@@ -0,0 +1,24 @@
+<?php
+flame\init("sleep_1");
+
+function mySleep1() {
+    mySleep2(rand(10, 30));
+}
+
+function mySleep2($ms) {
+    flame\time\sleep($ms);
+}
+
+for($i=0;$i<200;++$i) {
+    flame\go(function() {
+        $i = 0;
+        for($x=0;$x<2000;++$x) {
+            echo ++$i, "\n";
+            mySleep1();
+            // flame\time\sleep(10);
+        }
+    });
+}
+
+
+flame\run();
diff --git a/test/tcp_1.php b/test/tcp_1.php
new file mode 100644
index 0000000..bb7c15a
--- /dev/null
+++ b/test/tcp_1.php
@@ -0,0 +1,34 @@
+<?php
+flame\init("tcp_1");
+
+flame\go(function() {
+    $server = new flame\tcp\server("127.0.0.1:8687");
+    flame\go(function() use($server) {
+        flame\time\sleep(10000);
+        echo "closing\n";
+        $server->close();
+    });
+    $server->run(function($socket) {
+        echo "connection accepted\n";
+        while($line = $socket->read("\n")) {
+            var_dump($line);
+            $socket->write($line);
+        }
+        echo "disconnected\n";
+    });
+    echo "server closed\n";
+});
+flame\go(function() {
+    flame\time\sleep(1000);
+    $socket = flame\tcp\connect("127.0.0.1:8687");
+    
+    for($i=0;$i<10;++$i) {
+        $socket->write("aaaaaa\nbbbbbb\n");
+        $socket->read("\n");
+        flame\time\sleep(100);
+        $socket->read("\n");
+        flame\time\sleep(100);
+    }
+});
+
+flame\run();
diff --git a/test/tcp_2.php b/test/tcp_2.php
new file mode 100644
index 0000000..03fdff2
--- /dev/null
+++ b/test/tcp_2.php
@@ -0,0 +1,25 @@
+<?php
+flame\init("tcp_2");
+
+flame\go(function() {
+    $server = new flame\tcp\server("0.0.0.0:8687");
+    $server->run(function($socket) {
+        $length = 0;
+        while( ($line = $socket->read("\n")) !== null) {
+            echo $line;
+            if(substr($line, 0, 14) == "Content-Length") {
+                $length = intval(substr($line, 16));
+            }else if($line == "\r\n") {
+                if($length > 0) {
+                    $body = $socket->read($length);
+                    echo $body, "\n";
+                }
+                flame\time\sleep(200);
+                $socket->write("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nok");
+            }
+        }
+    });
+    echo "done.\n";
+});
+
+flame\run();
diff --git a/test/time_1.php b/test/time_1.php
new file mode 100644
index 0000000..675a04f
--- /dev/null
+++ b/test/time_1.php
@@ -0,0 +1,9 @@
+<?php
+flame\init("time_1");
+
+flame\go(function() {
+    echo flame\time\now(), "\n";
+    echo flame\time\iso(), "\n";
+});
+
+flame\run();
diff --git a/test/toml_1.php b/test/toml_1.php
new file mode 100755
index 0000000..c36eb37
--- /dev/null
+++ b/test/toml_1.php
@@ -0,0 +1,22 @@
+<?php
+
+
+$x = [
+    "a" => [
+        "b" => "111111",
+    ]
+];
+$y = flame\get($x, "a.b");
+var_dump($y);
+flame\set($x, "a.c", "222222");
+flame\set($x, "a.x.d", "333333");
+flame\set($x, "e.f", "444444");
+flame\set($x, "g.h.i", "555555");
+var_dump($x);
+
+
+$r = flame\toml\parse_file(__DIR__."/toml_1.txt");
+var_dump($r);
+$data = file_get_contents(__DIR__."/toml_1.txt");
+$r = flame\toml\parse_string($data);
+var_dump($r);
diff --git a/test/toml_1.txt b/test/toml_1.txt
new file mode 100644
index 0000000..9bff83a
--- /dev/null
+++ b/test/toml_1.txt
@@ -0,0 +1,36 @@
+
+s2 = "234"
+s3 = '345'
+s4 = """4 \
+5 \
+6""" # commment 1
+s5 = '''5
+6
+7'''
+
+f1 = 1.2 #comment 2
+f2 = 1e+10
+f3 = 2.22e-5
+
+#comment 3
+
+[a.b]
+c = [ x, {y = 1, z = 2} ]
+# comment 4
+d = 3
+e = {f = 4, g = 5}
+
+[[h.i]]
+#comment 5
+x = 1
+y = 2019-01-01 01:01:01
+z = 01:01:01
+
+[[h.i]]
+x = 2
+y = 2019-02-22 02:02:02
+z = 02:02:02
+
+[c] #comment 6
+a = true
+b.c = false
\ No newline at end of file
diff --git a/test/udp_1.php b/test/udp_1.php
new file mode 100644
index 0000000..e32a0db
--- /dev/null
+++ b/test/udp_1.php
@@ -0,0 +1,34 @@
+<?php
+flame\init("udp_1");
+
+flame\go(function() {
+    $server = new flame\udp\server(":::6666");
+    flame\go(function() use($server) {
+        flame\time\sleep(1000);
+        $socket = new flame\udp\socket();
+        $socket->send_to("hello", "127.0.0.1:6666"); // 服务器 IPv4 兼容, 也可以收到
+        $socket->send_to("world", "::1:6666");
+        $socket->send_to("!", "localhost:6666");
+        flame\time\sleep(1000);
+        $socket = flame\udp\connect("127.0.0.1:6666");
+        $socket->send("hello");
+        $socket->send("world!");
+        echo "received: ". $socket->recv()."\n";
+        flame\time\sleep(10000);
+        $server->close();
+    });
+    $server->run(function($data, $from) use($server) {
+        echo "(".$from.") [".$data."]\n";
+        $server->send_to($data, $from); // echo back
+    });
+});
+
+flame\go(function() {
+    $socket = flame\udp\connect("127.0.0.1:6666");
+    for($i=0;$i<10;++$i) {
+        $socket->send("hello");
+        flame\time\sleep(100);
+    }
+});
+
+flame\run();
diff --git a/test/warning_1.php b/test/warning_1.php
new file mode 100644
index 0000000..ab7b2c4
--- /dev/null
+++ b/test/warning_1.php
@@ -0,0 +1,9 @@
+<?php
+flame\init("warning_1");
+
+flame\go(function() {
+    flame\time\sleep(1000);
+    $a = $b; // WARNING
+});
+
+flame\run();
\ No newline at end of file
