├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README_EN.md ├── branch.txt ├── obj └── .keep ├── sample_http.ini ├── sample_normal.ini ├── sample_tiny.ini ├── src ├── ae.c ├── ae.h ├── ae_epoll.c ├── ae_evport.c ├── ae_kqueue.c ├── ae_select.c ├── aprintf.c ├── aprintf.h ├── atomicvar.h ├── config.h ├── http_parser.c ├── http_parser.h ├── islog.c ├── islog.h ├── isstr.c ├── isstr.h ├── istime.c ├── istime.h ├── main.h ├── net.c ├── net.h ├── output.c ├── output.h ├── outputhtml.h ├── stats.c ├── stats.h ├── tcpini.c ├── tcpini.h ├── units.c ├── units.h ├── version.h ├── wrktcp.c ├── wrktcp.h ├── zmalloc.c └── zmalloc.h └── test ├── Makefile ├── istcp.c ├── istcp.h ├── test.sh └── testserver.c /.gitignore: -------------------------------------------------------------------------------- 1 | obj/* 2 | wrktcp 3 | /test/testserver 4 | *.html 5 | .vscode/* 6 | .idea/* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Modified Apache 2.0 License 3 | Version 2.0.1, February 2015 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | (e) If the Derivative Work includes substantial changes to features 124 | or functionality of the Work, then you must remove the name of 125 | the Work, and any derivation thereof, from all copies that you 126 | distribute, whether in Source or Object form, except as required 127 | in copyright, patent, trademark, and attribution notices. 128 | 129 | You may add Your own copyright statement to Your modifications and 130 | may provide additional or different license terms and conditions 131 | for use, reproduction, or distribution of Your modifications, or 132 | for any such Derivative Works as a whole, provided Your use, 133 | reproduction, and distribution of the Work otherwise complies with 134 | the conditions stated in this License. 135 | 136 | 5. Submission of Contributions. Unless You explicitly state otherwise, 137 | any Contribution intentionally submitted for inclusion in the Work 138 | by You to the Licensor shall be under the terms and conditions of 139 | this License, without any additional terms or conditions. 140 | Notwithstanding the above, nothing herein shall supersede or modify 141 | the terms of any separate license agreement you may have executed 142 | with Licensor regarding such Contributions. 143 | 144 | 6. Trademarks. This License does not grant permission to use the trade 145 | names, trademarks, service marks, or product names of the Licensor, 146 | except as required for reasonable and customary use in describing the 147 | origin of the Work and reproducing the content of the NOTICE file. 148 | 149 | 7. Disclaimer of Warranty. Unless required by applicable law or 150 | agreed to in writing, Licensor provides the Work (and each 151 | Contributor provides its Contributions) on an "AS IS" BASIS, 152 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 153 | implied, including, without limitation, any warranties or conditions 154 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 155 | PARTICULAR PURPOSE. You are solely responsible for determining the 156 | appropriateness of using or redistributing the Work and assume any 157 | risks associated with Your exercise of permissions under this License. 158 | 159 | 8. Limitation of Liability. In no event and under no legal theory, 160 | whether in tort (including negligence), contract, or otherwise, 161 | unless required by applicable law (such as deliberate and grossly 162 | negligent acts) or agreed to in writing, shall any Contributor be 163 | liable to You for damages, including any direct, indirect, special, 164 | incidental, or consequential damages of any character arising as a 165 | result of this License or out of the use or inability to use the 166 | Work (including but not limited to damages for loss of goodwill, 167 | work stoppage, computer failure or malfunction, or any and all 168 | other commercial damages or losses), even if such Contributor 169 | has been advised of the possibility of such damages. 170 | 171 | 9. Accepting Warranty or Additional Liability. While redistributing 172 | the Work or Derivative Works thereof, You may choose to offer, 173 | and charge a fee for, acceptance of support, warranty, indemnity, 174 | or other liability obligations and/or rights consistent with this 175 | License. However, in accepting such obligations, You may act only 176 | on Your own behalf and on Your sole responsibility, not on behalf 177 | of any other Contributor, and only if You agree to indemnify, 178 | defend, and hold each Contributor harmless for any liability 179 | incurred by, or claims asserted against, such Contributor by reason 180 | of your accepting any such warranty or additional liability. 181 | 182 | END OF TERMS AND CONDITIONS 183 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #CFLAGS += -std=c99 -Wall -O2 -D_REENTRANT 2 | CFLAGS += -Wall -std=c99 3 | 4 | LIBS := -lm -lpthread 5 | 6 | TARGET := $(shell uname -s | tr '[A-Z]' '[a-z]' 2>/dev/null || echo unknown) 7 | 8 | ifeq ($(TARGET), sunos) 9 | CFLAGS += -D_PTHREADS -D_POSIX_C_SOURCE=200112L 10 | LIBS += -lsocket 11 | else ifeq ($(TARGET), darwin) 12 | # LDFLAGS += -pagezero_size 10000 -image_base 100000000 13 | export MACOSX_DEPLOYMENT_TARGET = $(shell sw_vers -productVersion) 14 | else ifeq ($(TARGET), linux) 15 | CFLAGS += -D_POSIX_C_SOURCE=200112L -D_BSD_SOURCE -D_DEFAULT_SOURCE 16 | LIBS += -ldl 17 | LDFLAGS += -Wl,-E 18 | else ifeq ($(TARGET), freebsd) 19 | CFLAGS += -D_DECLARE_C99_LDBL_MATH 20 | LDFLAGS += -Wl,-E 21 | endif 22 | 23 | SRC := wrktcp.c net.c aprintf.c stats.c units.c \ 24 | ae.c zmalloc.c islog.c tcpini.c istime.c \ 25 | output.c isstr.c http_parser.c 26 | BIN := wrktcp 27 | 28 | ODIR := obj 29 | OBJ := $(patsubst %.c,$(ODIR)/%.o,$(SRC)) 30 | LIBS := $(LIBS) 31 | 32 | DEPS := 33 | CFLAGS += -I$(ODIR)/include 34 | #LDFLAGS += -L$(ODIR)/lib 35 | LDFLAGS += 36 | 37 | all: $(BIN) test 38 | 39 | clean: 40 | $(RM) -rf $(BIN) obj/* *.log *.html 41 | cd ./test && make clean 42 | 43 | $(BIN): $(OBJ) 44 | @echo LINK $(BIN) 45 | @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 46 | 47 | .PHONY:test 48 | test: 49 | @cd ./test && make clean && make 50 | 51 | $(ODIR): 52 | @mkdir -p $@ 53 | 54 | $(ODIR)/%.o : %.c %.h 55 | @echo CC $< 56 | @$(CC) $(CFLAGS) -c -o $@ $< 57 | 58 | .PHONY: all clean 59 | 60 | .SUFFIXES: 61 | .SUFFIXES: .c .o 62 | 63 | vpath %.c src 64 | vpath %.h src 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wrktcp - 无lua依赖的tcp协议压测wrk工具 2 | [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 3 | 4 | ## 一、项目介绍 5 | 6 | wrktcp是一个基于wrk、支持tcp协议、应答结果敏感的轻量化压测工具,他有如下的几个特点: 7 | 8 | - **高并发低损耗**, 基于wrk&redis的io多路复用模型,能使用较少线程生成大量并发请求. 9 | - **安装部署简单**, 仅单个可执行文件,无需复杂的安装部署. 10 | - **应答结果敏感**, 不同于wrk和ab等对于返回值不敏感,wrktcp对于返回内容的正确性有准确的校验. 11 | - **tcp协议兼容**, 在wrk的仅http协议基础上,增加对tcp协议的支持. 12 | - **丰富配置参数**, 无需学习lua脚本,使用sample.ini文件即可实现等同大型压测软件的功能. 13 | - **压测结果细化**, 可追踪压测过程中的整体趋势信息,更能实现对压测结果进行html可视化输出. 14 | 15 | ## 二、项目背景 16 | 17 | 在10多年的金融软件开发过程中,对于loadrunner、wrk、Jmeter等压测工具使用较多,但是一直困扰于如下几个问题: 18 | 1. loadrunner过重,安装配置复杂,脚本编写复杂,虽然可以通过loadagent来增加压测节点,但是整体对压测环境要求过高. 19 | 2. Jmter由于java编写,压力一般无法满足要求,经常是jemter自己先把本机压力跑满了。 20 | 3. wrk高并发压力很好,但是对返回结果不敏感(lua勉强可以解决),而且无法支持tcp协议,行业内很大一部分的系统使用tcp协议. 21 | 22 | 正式为了解决以上的使用问题,计划基于wrk的高并发低损耗的基础上,增加tcp协议支持以及对应答结果的正确性进行校验, 同时根据loadrunner等工具的 23 | 日常使用经验,以及调试脚本等各种方面的需求,编写了wrktcp来满足自己、服务大家. 24 | 25 | ## 三、主要功能 26 | 27 | 1. 整体框架基于wrk做的扩展,统计、大部分命令、输出结果沿用的wrk,增加了如下参数。 28 | 29 | 1. 增加 --test参数,用于调试ini文件是否正确,并可输出test日志。 30 | 2. 增加 --html 参数,用于输出html结果文件。 31 | 3. 增加 --trace参数,用于在命令行打印时间趋势信息。 32 | 33 | 2. 支持tcp协议的压力测试,可自定义报文头、报文体格式,支持固定格式、xml、tag、fixed、json等常见报文格式。 34 | 35 | 3. 支持定义发送报文信息,应答报文信息,以及应答的成功响应的结果判断对比配置。 36 | 37 | 4. 支持请求报文的参数变量。参数类似loadrunner,有如下四种: 38 | 1. COUNTER, 迭代器,如唯一流水等,可支持定义范围,定义步长。 39 | 2. DATETIME,时间模式,格式与strtime完全一致。 40 | 3. CONNECTID,连接唯一ID,一般用于做为终端号等使用。 41 | 4. FILE,文件模式,支持从文件中顺序读取内容进行内容赋值。 42 | 43 | 5. 支持压测过程中,命令行动态刷新TPS和延时。 44 | 45 | 6. 支持记录压测过程信息,并生成html页面进行展示。 46 | 47 | 7. (v1.2)支持http,配置可以参加sample_http.ini。 48 | 49 | ## 四、编译安装 50 | 51 | - macos 52 | 53 | cd wrktcp路径 54 | 执行make 55 | 56 | - linux 57 | 58 | cd wrktcp路径 59 | 执行make 60 | 61 | - windows 62 | 63 | 需要下载MINGW编译器,再进行编译。 64 | 65 | 66 | ## 五、快速使用 67 | 68 | 1. 修改sample_tiny.ini配置 69 | 70 | ```ini 71 | [common] 72 | host = 127.0.0.1 73 | port = 8000 74 | 75 | [request] 76 | req_body = this is a test 77 | ``` 78 | 79 | 2. 调试配置信息 80 | ```sh 81 | wrktcp --test sample_tiny.ini 82 | ``` 83 | 84 | 3. 执行压测命令 85 | 86 | ```sh 87 | wrktcp -t2 -c20 -d10s --latency sample_tiny.ini 88 | ``` 89 | 90 | 和wrk的用法类似,本命令代表如下意义: 91 | 使用2个线程(-t2),并发20个连接(-c20),保持运行5秒(-d5s),使用配置sample_tiny.ini来运行. 92 | --latency用来输出响应时间的分布情况。 93 | 94 | 4. 输出结果 95 | 96 | ``` 97 | /Users/suitm/code/c/wrktcp>wrktcp -t2 -c20 -d10s --latency sample_tiny.ini 98 | Running 10s loadtest @ 127.0.0.1:8000 using sample_tiny.ini 99 | 2 threads and 20 connections 100 | Time:10s TPS:2347.30/0.00 Latency:5.90ms BPS:48.14KB Error:0 101 | Thread Stats Avg Stdev Max +/- Stdev 102 | Latency 64.19ms 185.36ms 1.11s 91.91% 103 | Req/Sec 1.42k 417.52 1.92k 73.46% 104 | Latency Distribution 105 | 50% 6.03ms 106 | 75% 8.17ms 107 | 90% 168.91ms 108 | 99% 983.51ms 109 | 23503 requests in 10.09s, 482.00KB read 110 | Requests/sec: 2328.22 (Success:2328.22/Failure:0.00) 111 | Transfer/sec: 47.75KB 112 | ``` 113 | 114 | - 命令的详细说明 115 | 116 | > ``` 117 | > -t, --threads: 使用线程总数,一般推荐使用CPU核数的2倍-1 118 | > -c, --connections: 连接总数,与线程无关。每个线程的连接数为connections/threads 119 | > -d, --duration: 压力测试时间, 可以写 2s, 2m, 2h 120 | > --latency: 打印延迟分布情况 121 | > --timeout: 指定超时时间,默认是5000毫秒,越长占用统计内存越大。 122 | > --trace: 打印出分布图 123 | > --html: 将压测的结果数据,输出到html文件中。 124 | > --test: 每个连接只会执行一次,一般用于测试配置是否正确。 125 | > -v --version: 打印版本信息 126 | > ``` 127 | 128 | - 结果的详细解释 129 | 130 | > Running 10s loadtest @ 127.0.0.1:8000 using sample_tiny.ini 131 | > 2 threads and 20 connections 132 | 133 | 代表命令的基本情况以及连接的基本情况,程序执行加载完成配置后立刻打印。 134 | 135 | 136 | 137 | > Thread Stats Avg Stdev Max +/- Stdev 138 | > Latency 64.19ms 185.36ms 1.11s 91.91% 139 | > Req/Sec 1.42k 417.52 1.92k 73.46% 140 | 141 | Latency代表延时时间,Req/Sec代表每秒的成功应答笔数(TPS),需要注意的是这部分统计是单个线程的统计数据。 142 | 143 | 说明依次是:线程状态名(Thread Stats)、平均值(Avg)、标准差(Stdev)、最大值(Max)、正负一个标准差内数据所占比例(+/- Stdev) 144 | 145 | 如果压测系统稳定,平均值和最大值是期望相等的, 标准差的期望值是0,+/-标准差的期望值是100%。 146 | 147 | 148 | 149 | > Latency Distribution 150 | > 50% 12.99ms 151 | > 75% 14.02ms 152 | > 90% 15.74ms 153 | > 99% 274.89ms 154 | 155 | 延迟分布的统计数据,50% - 99%是按相应时间进行排序的。50%代表第50%的数据延时是多少99%代表第99%的数据延时是多少。 156 | 157 | 158 | 159 | > 23503 requests in 10.09s, 482.00KB read 160 | >Requests/sec: 2328.22 (Success:2328.22/Failure:0.00) 161 | > Transfer/sec: 47.75KB 162 | 163 | 第一行代表: 在10.11秒内有6862个请求,一共读取了140.72KB的数据。 164 | 165 | Requests/sec: 678.90 总请求TPS,包含成功和失败的总数,和wrk相同。 166 | 167 | Requests/sec-Success和Requests/sec-Failure : 是区分业务应答是否成功的,可能正常应答了,但是返回的信息是错误信息。 168 | 169 | Transfer/sec: 每秒的传输数据大小。 170 | 171 | - 配置文件的详细说明 172 | 173 | ```ini 174 | [common] 175 | host = 127.0.0.1 176 | port = 8000 177 | 178 | [request] # 报文长度的长度,默认是8 179 | req_len_len = 8 180 | # 报文长度是全部还是只计算报文体,默认是body可选配置为total 181 | req_len_type = body 182 | # 报文头配置,默认只有长度 183 | req_head = XMLHEAD$(length)20201231 184 | # 必须配置,报文体长度 185 | req_body = \ 186 | \ 187 | \ 188 | $(DATE)\ 189 | $(BRANCH)\ 190 | $(TRACENO)\ 191 | $(TERMNO)\ 192 | 313233000017\ 193 | 张三\ 194 | \ 195 | \ 196 | HELLO,this is a test\ 197 | \ 198 | 199 | 200 | [response] 201 | # 报文头长度,默认是8 202 | rsp_headlen = 23 203 | # 报文头中的报文长度位置,默认是1 204 | rsp_len_beg = 8 205 | # 报文头中的报文长度的长度,默认是8 206 | rsp_len_len = 8 207 | # 报文长度是全部还是只计算报文体,默认是body可选配置为total 208 | rsp_len_type = body 209 | 210 | # 响应码类型,默认是fixed 211 | rsp_code_type = xml 212 | # 响应码位置,默认是body 213 | rsp_code_location = body 214 | # 响应码tag,默认是1 6 215 | rsp_code_location_tag = 216 | # 成功响应码,默认是000000 217 | rsp_code_success = 000000 218 | 219 | [parameters] 220 | TRACENO = COUNTER, 1, 100000, 1, %08ld 221 | BRANCH = FILE, branch.txt 222 | TERMNO = CONNECTID, %08ld 223 | DATE = DATETIME, %H:%M:%S 224 | ``` 225 | 226 | ## 六、与wrk的区别 227 | 228 | 1. 取消:lua的依赖,自定义的内容和判断不用学习lua,一个配置文件即可。 229 | 230 | 2. 取消:不依赖任何第三方库,取消ssl和luajit的deps。因此这是一个纯净的(pure)C的工程。 231 | 232 | 3. 增加:支持tcp协议。使用ini配置文件来配置tcp报文的格式。连接地址、报文约定、请求报文、应答报文信息都在该文件中配置。 233 | 234 | 4. 增加:动态刷新TPS和延时,在较长时间的压测过程中也能实时的观察压测情况。 235 | 236 | 5. 增加:直接生成html报表。 237 | 238 | 6. 增加:执行过程中的实时TPS信息统计刷新。 239 | 240 | 7. 增加:每个连接只连接一次的测试参数,用于调试ini文件。 241 | 242 | 8. 改进:thread stat的分布统计,把最大支持TPS从1000W缩减到100W,TPS精度从1增加到0.1。 243 | 244 | 9. 改进:wrk输出结果,增加了成功和失败的分类,充分考虑了业务级错误。 245 | 246 | 247 | ## 七、关于为什么不使用wrk2做扩展 248 | 249 | 关于解决**协调遗漏(Coordinated Omisson)**的wrk2的文章:https://blog.csdn.net/minxihou/article/details/97318121 250 | 251 | wrk2作者从技术角度的分析解决方案非常的专业而且正确,但是从实际项目工程角度考虑,我没有使用wrk2进行扩展,主要有如下两点原因: 252 | 第一、一般情况下,压测都是希望有一个相对较好的结果。 253 | 第二、主流的压测软件都存在的统计口径问题,即使这个统计方式有问题,从业界统一性和标准化上考虑,结果也是可信赖的。 254 | 255 | 因此,相对于每次讨论TPS都需要明确使用何种软件并且是否排除了协调遗漏,这种复杂的前提造成的沟通困难来说,在工程领域统一方法更为重要。 256 | 257 | ## 八、开源协议的相关要求须知 258 | 259 | wrktcp是基于wrk的扩展开发。当然wrk也是使用了大量的redis、nignx的源码。在使用和扩展的时候,请注意对应的开源库的相关协议。 260 | 261 | ## 九、压测的参数技巧 262 | 263 | macos下必须调整的参数: 264 | ``` 265 | # 最大连接数,mac默认是128,不调整的话连接经常失败 266 | sysctl kern.ipc.somaxconn 267 | sudo sysctl -w kern.ipc.somaxconn=1024 268 | 269 | # TIME_WAIT的回收时间,默认是15秒,压测的时候,需要改成1秒,否则超过17k的随机端口未释放,mac就会报错(linux是30k左右) 270 | sysctl net.inet.tcp.msl 271 | sudo sysctl -w net.inet.tcp.msl=1000 272 | 273 | ``` 274 | 275 | linux下的参数调整: 276 | ``` 277 | 修改/etc/sysctl.conf, 是否可以开启需要根据实际情况,总结来说,如果有F5或者负载均衡器,则不能开启tcp_tw_recycle 278 | 279 | M.16/root/wrktcp/wrktcp>sysctl -p 280 | net.ipv4.tcp_tw_reuse = 1 281 | net.ipv4.tcp_tw_recycle = 1 282 | net.ipv4.tcp_timestamps = 1 283 | ``` 284 | 285 | ## 十、版本更新说明 286 | - V1.1 2021-06-15 287 | 针对test模式优化,不必等待-d时间、无需输入-t和-c默认为1和1、针对test模式增加更多的输出日志。 288 | 优化了一些日志html显示的小细节 289 | 290 | - v1.2 2021-06-18 291 | 追加了http的更加简单的配置,实现压力测试的all in one处理. 292 | 293 | - v1.2.1 2021-08-19 294 | 更新了istime的版本,保持跨系统编译的兼容性. 295 | 修正了一下warning 296 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # wrktcp - support TCP protocol without lua 2 | 3 | *你可以查看中文版的说明:README_CN.md* 4 | 5 | [TOC] 6 | 7 | This procedure is mainly based on WRK to remove SSL and Lua dependence, using TCPINI configuration to achieve the TCP protocol under the stress test. 8 | 9 | ## Functions overview 10 | 11 | 1. The overall framework is based on WRK extension. Statistics, most commands and output results follow WRK, but some adjustments have been made. 12 | 2. Support the stress test of TCP protocol, including various protocols and communication formats. 13 | 3. Support the definition of sending message information, reply message information, and reply successful response information comparison configuration. 14 | 4. Support parameter variables of request message. Parameters are similar to loadRunner, with the following four types: 15 | 1. COUNTER, iterator, such as unique flow, can define scope and define step size. 16 | 2. DATETIME, is exactly the same format as the Strtime. 17 | 3. CONNECTID, a unique ID for a connection, generally used as a terminal number, etc. 18 | 4. FILE, FILE mode, supports sequential reading of contents from files for content assignment. 19 | 5. Support dynamic refresh of TPS and delay during pressure measurement. TODO 20 | 6. Support recording pressure test process information, and generate HTML pages for display. TODO 21 | 7. In principle, HTTP is also supported, because HTTP is also a kind of TCP protocol. 22 | 23 | 24 | 25 | ## The difference with wrk 26 | 27 | 1. Added support for TCP protocol. Use the INI profile to configure the TCP packet format. The connection address, message convention, request message, and reply message information are all configured in this file. 28 | 3. Remove Lua dependencies, customize content and judgment without learning Lua, a configuration file will do. 29 | 4. Do not rely on any third-party libraries and deps SSL and Luajit. So this is a pure C project. 30 | 4. Distribution statistics for Thread Stat reduce the maximum TPS support from 1000W to 100W, and increase TPS accuracy from 1 to 0.1. 31 | 5. The output basically follows WRK, increasing the classification of success and failure. Because the original WRK did not consider business-level errors, configuration was added. 32 | 6. TODO: dynamic refresh TPS and delay are added, so that real-time observation of pressure measurement can also be made in the process of pressure measurement for a long time. 33 | 7. TODO: generate HTML reports and perform process monitoring. 34 | 35 | 36 | 37 | ## install 38 | 39 | - macos 40 | 41 | cd wrktcp && make 42 | 43 | - linux 44 | 45 | cd wrktcp && make 46 | 47 | - windows 48 | 49 | 需要下载编译器,或者直接下载release版本。 50 | 51 | 52 | 53 | ## Quick Start 54 | 55 | - Modify the sample_tiny.ini configuration 56 | 57 | ```ini 58 | [common] 59 | host = 127.0.0.1 60 | port = 8000 61 | 62 | [request] 63 | req_body = this is a test 64 | ``` 65 | 66 | - run the command 67 | 68 | ```sh 69 | wrktcp -t2 -c20 -d10s --latency sample_tiny.ini 70 | ``` 71 | 72 | Similar to WRK, this command represents the following: 73 | Use 2 threads (-T2), 20 concurrent connections (-c20), keep running for 5 seconds (-d5s), run with configuration sample_tiny.ini. 74 | 75 | 76 | - output 77 | ``` 78 | Running 10s test @ 127.0.0.1:8000 using sample_tiny.ini 79 | 2 threads and 10 connections 80 | Thread Stats Avg Stdev Max +/- Stdev 81 | Latency 23.02ms 43.78ms 356.62ms 94.88% 82 | Req/Sec 354.37 74.31 454.50 90.62% 83 | Latency Distribution 84 | 50% 12.99ms 85 | 75% 14.02ms 86 | 90% 15.74ms 87 | 99% 274.89ms 88 | 6862 requests in 10.11s, 140.72KB read 89 | Requests/sec: 678.90 90 | Requests/sec-Success: 678.90 91 | Requests/sec-Failure: 0.00 92 | Transfer/sec: 13.92KB 93 | ``` 94 | 95 | - command detail description 96 | 97 | ``` 98 | -t, --threads: Use the total number of threads, generally recommended to use 2 times the number of CPU cores -1 99 | -c, --connections: Total number of connections, regardless of thread. The number of connections per thread is connections/ Threads 100 | -d, --duration: write 2S, 2m, 2h 101 | --latency: distribution of printing delay 102 | --timeout: Specify timeout, default is 5 minutes, longer takes up more statistical memory. --realtime refreshes TPS information in realtime and records TPS and delayed process data to generate HTML 103 | -v --version: Print version information 104 | ``` 105 | 106 | 107 | - output detail description 108 | 109 | > Running 10s test @ 127.0.0.1:8000 using sample_tiny.ini 110 | > 2 threads and 10 connections 111 | 112 | Represents the base case of the command and the base case of the connection. The program executes the load and prints as soon as it completes the configuration. 113 | 114 | > Thread Stats Avg Stdev Max +/- Stdev 115 | > Latency 23.02ms 43.78ms 356.62ms 94.88% 116 | > Req/Sec 354.37 74.31 454.50 90.62% 117 | 118 | Latency represents Latency time, and Req/Sec represents the number of successful response strokes per second (TPS). It should be noted that this part of the Latency data is the data of a single thread. 119 | 120 | Description: Thread Stats, mean (Avg), standard deviation (Stdev), maximum (Max), percentage of data within one standard deviation (+/ -stdev) 121 | 122 | If the pressure measuring system is stable, the mean and the maximum are expected to be the same, the expected standard deviation is 0, and the expected standard deviation is 100%. 123 | 124 | 125 | 126 | > Latency Distribution 127 | > 50% 12.99ms 128 | > 75% 14.02ms 129 | > 90% 15.74ms 130 | > 99% 274.89ms 131 | 132 | The statistics of the delay distribution, 50% to 99%, are sorted by corresponding time. So 50% is what's the 50th data delay and 99% is what's the 99th data delay. 133 | 134 | 135 | 136 | > 6862 requests in 10.11s, 140.72KB read 137 | > 138 | > Requests/sec: 678.90 139 | > Requests/sec-Success: 678.90 140 | > Requests/sec-Failure: 0.00 141 | > Transfer/sec: 13.92KB 142 | 143 | The first line represents: 6862 requests in 10.11 seconds, with a total of 140.72KB read. 144 | 145 | Requests/sec: 678.90 The total request TPS, containing the total number of successes and failures, is the same as WRK. 146 | 147 | Requests/sec-Success & Requests/sec-Failure : Is to distinguish whether the business reply was successful or not, it may have been correctly answered, but the message returned is an error message. 148 | 149 | Transfer/sec: The size of the transmitted data per second. 150 | 151 | - ini configuration desrciption 152 | 153 | ```ini 154 | [common] 155 | host = 127.0.0.1 156 | port = 8000 157 | 158 | [request] 159 | # The length of the message's length, 8 by default 160 | req_len_len = 8 161 | # length is all or only calculated body, the default is the optional body configuration of total 162 | req_len_type = body 163 | # request head, default length only 164 | req_head = $(length) 165 | # The content of the request body 166 | req_body = \ 167 | \ 168 | $(DATE)\ 169 | $(BRANCH)\ 170 | 20201222$(TRACENO)\ 171 | $(TERMNO)\ 172 | 313233000017\ 173 | sam\ 174 | 175 | 176 | [response] 177 | # Then head length 178 | rsp_headlen = 16 179 | # The length's location in the message ,1 is default 180 | rsp_len_beg = 5 181 | # The length of the message's length, 8 by default 182 | rsp_len_len = 8 183 | # length is all or only calculated body, the default is the optional body configuration of total 184 | rsp_len_type = body 185 | 186 | # response code type, default is fixed, optional is xml,json and to be continued 187 | rsp_code_type = xml 188 | # response code location, body or head 189 | rsp_code_location = body 190 | # the tag of the response code 191 | rsp_code_localtion_tag = 192 | # response code success 193 | rsp_code_success = 000000 194 | 195 | [paramters] 196 | DATE = DATETIME, "%H:%M:S" 197 | BRANCH = FILE, branch.txt 198 | TRACENO = COUNTER, 100, 100000, 2, "%08ld" 199 | TERMNO = CONNECTID 200 | ``` 201 | 202 | 203 | 204 | ## why not use WRK2 205 | 206 | here are sharp contrasts in the WRK2 article on addressing the Coordinated omission of :https://blog.csdn.net/minxihou/article/details/97318121 207 | 208 | The reasons for not extending from WRK2, personal point of view is that the author is technically correct, but has no practical value from a practical engineering point of view. For two reasons: 209 | First, under normal circumstances, pressure measurement is expected to have a better result. 210 | Second, if it is the pressure measurement software will have the statistical caliber problem, then even if the statistical method has problems, but because it is a unified statistical caliber and method, so it is reliable. 211 | 212 | Therefore, it is more important to have a unified approach to communication than to have a clear understanding of what software to use and whether coordination omissions are eliminated in every discussion of TPS. 213 | 214 | 215 | 216 | ## Acknowledgements 217 | 218 | WRKTCP is an extended development based on WRK. Of course, WRK is also the use of a large number of Redis, Nignx, Luajit source code, use and extension, please pay attention to the corresponding open source library protocols. 219 | 220 | -------------------------------------------------------------------------------- /branch.txt: -------------------------------------------------------------------------------- 1 | 11111 2 | 22222 3 | 33333 4 | 44444 5 | 55555 6 | 66666 7 | 77777 8 | 88888 9 | 99999 10 | 00000 11 | AAAAA 12 | BBBBB 13 | -------------------------------------------------------------------------------- /obj/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesky1stm/wrktcp/af4c5215117416dee70cfa0424e61231da349f71/obj/.keep -------------------------------------------------------------------------------- /sample_http.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | host = 81.70.244.68 3 | port = 8080 4 | 5 | # 如果是http,必须定义该行 6 | http_line = POST /wrktcp HTTP/1.1 7 | 8 | [request] 9 | req_head = Host: www.wrktcp.com 10 | req_head = Content-Length: $(length) 11 | 12 | # 报文头配置,默认只有长度 13 | req_body = \ 14 | {\ 15 | "name":"张三",\ 16 | "msgTp":"uops.011.000.01",\ 17 | "msgId":"2018042713032100000000000000000000000000000000000000000000000001"\ 18 | }\ 19 | 20 | [response] 21 | # 响应码类型,默认是fixed 22 | rsp_code_type = json 23 | # 响应码位置,默认是body 24 | rsp_code_location = body 25 | # 响应码tag,默认是1 6 26 | rsp_code_location_tag = "rspCd" 27 | # 成功响应码,默认是000000 28 | rsp_code_success = 0000 29 | 30 | [parameters] 31 | TRACENO = COUNTER, 1, 100000, 1, %08ld 32 | BRANCH = FILE, branch.txt 33 | TERMNO = CONNECTID, %08ld 34 | DATE = DATETIME, %H:%M:%S -------------------------------------------------------------------------------- /sample_normal.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | host = 127.0.0.1 3 | port = 8000 4 | 5 | [request] # 报文长度的长度,默认是8 6 | req_len_len = 8 7 | # 报文长度是全部还是只计算报文体,默认是body可选配置为total 8 | req_len_type = body 9 | # 报文头配置,默认只有长度 10 | req_head = XMLHEAD$(length)20201231 11 | # 必须配置,报文体长度 12 | req_body = \ 13 | \ 14 | \ 15 | $(DATE)\ 16 | $(BRANCH)\ 17 | $(TRACENO)\ 18 | $(TERMNO)\ 19 | 313233000017\ 20 | 张三\ 21 | \ 22 | \ 23 | HELLO,this is a test\ 24 | \ 25 | 26 | 27 | [response] 28 | # 报文头长度,默认是8 29 | rsp_headlen = 23 30 | # 报文头中的报文长度位置,默认是1 31 | rsp_len_beg = 8 32 | # 报文头中的报文长度的长度,默认是8 33 | rsp_len_len = 8 34 | # 报文长度是全部还是只计算报文体,默认是body可选配置为total 35 | rsp_len_type = body 36 | 37 | # 响应码类型,默认是fixed 38 | rsp_code_type = xml 39 | # 响应码位置,默认是body 40 | rsp_code_location = body 41 | # 响应码tag,默认是1 6 42 | rsp_code_location_tag = 43 | # 成功响应码,默认是000000 44 | rsp_code_success = 000000 45 | 46 | [parameters] 47 | TRACENO = COUNTER, 1, 100000, 1, %08ld 48 | BRANCH = FILE, branch.txt 49 | TERMNO = CONNECTID, %08ld 50 | DATE = DATETIME, %H:%M:%S 51 | -------------------------------------------------------------------------------- /sample_tiny.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | # ip & port 3 | host = 127.0.0.1 4 | port = 8000 5 | 6 | [request] 7 | req_body = this is a test 8 | -------------------------------------------------------------------------------- /src/ae.c: -------------------------------------------------------------------------------- 1 | /* A simple event-driven programming library. Originally I wrote this code 2 | * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated 3 | * it in form of a library for easy reuse. 4 | * 5 | * Copyright (c) 2006-2010, Salvatore Sanfilippo 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "ae.h" 44 | #include "zmalloc.h" 45 | #include "config.h" 46 | 47 | /* Include the best multiplexing layer supported by this system. 48 | * The following should be ordered by performances, descending. */ 49 | #ifdef HAVE_EVPORT 50 | #include "ae_evport.c" 51 | #else 52 | #ifdef HAVE_EPOLL 53 | #include "ae_epoll.c" 54 | #else 55 | #ifdef HAVE_KQUEUE 56 | #include "ae_kqueue.c" 57 | #else 58 | #include "ae_select.c" 59 | #endif 60 | #endif 61 | #endif 62 | 63 | aeEventLoop *aeCreateEventLoop(int setsize) { 64 | aeEventLoop *eventLoop; 65 | int i; 66 | 67 | if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; 68 | eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize); 69 | eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize); 70 | if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; 71 | eventLoop->setsize = setsize; 72 | eventLoop->lastTime = time(NULL); 73 | eventLoop->timeEventHead = NULL; 74 | eventLoop->timeEventNextId = 0; 75 | eventLoop->stop = 0; 76 | eventLoop->maxfd = -1; 77 | eventLoop->beforesleep = NULL; 78 | if (aeApiCreate(eventLoop) == -1) goto err; 79 | /* Events with mask == AE_NONE are not set. So let's initialize the 80 | * vector with it. */ 81 | for (i = 0; i < setsize; i++) 82 | eventLoop->events[i].mask = AE_NONE; 83 | return eventLoop; 84 | 85 | err: 86 | if (eventLoop) { 87 | zfree(eventLoop->events); 88 | zfree(eventLoop->fired); 89 | zfree(eventLoop); 90 | } 91 | return NULL; 92 | } 93 | 94 | /* Return the current set size. */ 95 | int aeGetSetSize(aeEventLoop *eventLoop) { 96 | return eventLoop->setsize; 97 | } 98 | 99 | /* Resize the maximum set size of the event loop. 100 | * If the requested set size is smaller than the current set size, but 101 | * there is already a file descriptor in use that is >= the requested 102 | * set size minus one, AE_ERR is returned and the operation is not 103 | * performed at all. 104 | * 105 | * Otherwise AE_OK is returned and the operation is successful. */ 106 | int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) { 107 | int i; 108 | 109 | if (setsize == eventLoop->setsize) return AE_OK; 110 | if (eventLoop->maxfd >= setsize) return AE_ERR; 111 | if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR; 112 | 113 | eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize); 114 | eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize); 115 | eventLoop->setsize = setsize; 116 | 117 | /* Make sure that if we created new slots, they are initialized with 118 | * an AE_NONE mask. */ 119 | for (i = eventLoop->maxfd+1; i < setsize; i++) 120 | eventLoop->events[i].mask = AE_NONE; 121 | return AE_OK; 122 | } 123 | 124 | void aeDeleteEventLoop(aeEventLoop *eventLoop) { 125 | aeApiFree(eventLoop); 126 | zfree(eventLoop->events); 127 | zfree(eventLoop->fired); 128 | zfree(eventLoop); 129 | } 130 | 131 | void aeStop(aeEventLoop *eventLoop) { 132 | eventLoop->stop = 1; 133 | } 134 | 135 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 136 | aeFileProc *proc, void *clientData) 137 | { 138 | if (fd >= eventLoop->setsize) { 139 | errno = ERANGE; 140 | return AE_ERR; 141 | } 142 | aeFileEvent *fe = &eventLoop->events[fd]; 143 | 144 | if (aeApiAddEvent(eventLoop, fd, mask) == -1) 145 | return AE_ERR; 146 | fe->mask |= mask; 147 | if (mask & AE_READABLE) fe->rfileProc = proc; 148 | if (mask & AE_WRITABLE) fe->wfileProc = proc; 149 | fe->clientData = clientData; 150 | if (fd > eventLoop->maxfd) 151 | eventLoop->maxfd = fd; 152 | return AE_OK; 153 | } 154 | 155 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) 156 | { 157 | if (fd >= eventLoop->setsize) return; 158 | aeFileEvent *fe = &eventLoop->events[fd]; 159 | if (fe->mask == AE_NONE) return; 160 | 161 | aeApiDelEvent(eventLoop, fd, mask); 162 | fe->mask = fe->mask & (~mask); 163 | if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { 164 | /* Update the max fd */ 165 | int j; 166 | 167 | for (j = eventLoop->maxfd-1; j >= 0; j--) 168 | if (eventLoop->events[j].mask != AE_NONE) break; 169 | eventLoop->maxfd = j; 170 | } 171 | } 172 | 173 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { 174 | if (fd >= eventLoop->setsize) return 0; 175 | aeFileEvent *fe = &eventLoop->events[fd]; 176 | 177 | return fe->mask; 178 | } 179 | 180 | static void aeGetTime(long *seconds, long *milliseconds) 181 | { 182 | struct timeval tv; 183 | 184 | gettimeofday(&tv, NULL); 185 | *seconds = tv.tv_sec; 186 | *milliseconds = tv.tv_usec/1000; 187 | } 188 | 189 | static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { 190 | long cur_sec, cur_ms, when_sec, when_ms; 191 | 192 | aeGetTime(&cur_sec, &cur_ms); 193 | when_sec = cur_sec + milliseconds/1000; 194 | when_ms = cur_ms + milliseconds%1000; 195 | if (when_ms >= 1000) { 196 | when_sec ++; 197 | when_ms -= 1000; 198 | } 199 | *sec = when_sec; 200 | *ms = when_ms; 201 | } 202 | 203 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 204 | aeTimeProc *proc, void *clientData, 205 | aeEventFinalizerProc *finalizerProc) 206 | { 207 | long long id = eventLoop->timeEventNextId++; 208 | aeTimeEvent *te; 209 | 210 | te = zmalloc(sizeof(*te)); 211 | if (te == NULL) return AE_ERR; 212 | te->id = id; 213 | aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); 214 | te->timeProc = proc; 215 | te->finalizerProc = finalizerProc; 216 | te->clientData = clientData; 217 | te->next = eventLoop->timeEventHead; 218 | eventLoop->timeEventHead = te; 219 | return id; 220 | } 221 | 222 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) 223 | { 224 | aeTimeEvent *te = eventLoop->timeEventHead; 225 | while(te) { 226 | if (te->id == id) { 227 | te->id = AE_DELETED_EVENT_ID; 228 | return AE_OK; 229 | } 230 | te = te->next; 231 | } 232 | return AE_ERR; /* NO event with the specified ID found */ 233 | } 234 | 235 | /* Search the first timer to fire. 236 | * This operation is useful to know how many time the select can be 237 | * put in sleep without to delay any event. 238 | * If there are no timers NULL is returned. 239 | * 240 | * Note that's O(N) since time events are unsorted. 241 | * Possible optimizations (not needed by Redis so far, but...): 242 | * 1) Insert the event in order, so that the nearest is just the head. 243 | * Much better but still insertion or deletion of timers is O(N). 244 | * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). 245 | */ 246 | static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) 247 | { 248 | aeTimeEvent *te = eventLoop->timeEventHead; 249 | aeTimeEvent *nearest = NULL; 250 | 251 | while(te) { 252 | if (!nearest || te->when_sec < nearest->when_sec || 253 | (te->when_sec == nearest->when_sec && 254 | te->when_ms < nearest->when_ms)) 255 | nearest = te; 256 | te = te->next; 257 | } 258 | return nearest; 259 | } 260 | 261 | /* Process time events */ 262 | static int processTimeEvents(aeEventLoop *eventLoop) { 263 | int processed = 0; 264 | aeTimeEvent *te, *prev; 265 | long long maxId; 266 | time_t now = time(NULL); 267 | 268 | /* If the system clock is moved to the future, and then set back to the 269 | * right value, time events may be delayed in a random way. Often this 270 | * means that scheduled operations will not be performed soon enough. 271 | * 272 | * Here we try to detect system clock skews, and force all the time 273 | * events to be processed ASAP when this happens: the idea is that 274 | * processing events earlier is less dangerous than delaying them 275 | * indefinitely, and practice suggests it is. */ 276 | if (now < eventLoop->lastTime) { 277 | te = eventLoop->timeEventHead; 278 | while(te) { 279 | te->when_sec = 0; 280 | te = te->next; 281 | } 282 | } 283 | eventLoop->lastTime = now; 284 | 285 | prev = NULL; 286 | te = eventLoop->timeEventHead; 287 | maxId = eventLoop->timeEventNextId-1; 288 | while(te) { 289 | long now_sec, now_ms; 290 | long long id; 291 | 292 | /* Remove events scheduled for deletion. */ 293 | if (te->id == AE_DELETED_EVENT_ID) { 294 | aeTimeEvent *next = te->next; 295 | if (prev == NULL) 296 | eventLoop->timeEventHead = te->next; 297 | else 298 | prev->next = te->next; 299 | if (te->finalizerProc) 300 | te->finalizerProc(eventLoop, te->clientData); 301 | zfree(te); 302 | te = next; 303 | continue; 304 | } 305 | 306 | /* Make sure we don't process time events created by time events in 307 | * this iteration. Note that this check is currently useless: we always 308 | * add new timers on the head, however if we change the implementation 309 | * detail, this check may be useful again: we keep it here for future 310 | * defense. */ 311 | if (te->id > maxId) { 312 | te = te->next; 313 | continue; 314 | } 315 | aeGetTime(&now_sec, &now_ms); 316 | if (now_sec > te->when_sec || 317 | (now_sec == te->when_sec && now_ms >= te->when_ms)) 318 | { 319 | int retval; 320 | 321 | id = te->id; 322 | retval = te->timeProc(eventLoop, id, te->clientData); 323 | processed++; 324 | if (retval != AE_NOMORE) { 325 | aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); 326 | } else { 327 | te->id = AE_DELETED_EVENT_ID; 328 | } 329 | } 330 | prev = te; 331 | te = te->next; 332 | } 333 | return processed; 334 | } 335 | 336 | /* Process every pending time event, then every pending file event 337 | * (that may be registered by time event callbacks just processed). 338 | * Without special flags the function sleeps until some file event 339 | * fires, or when the next time event occurs (if any). 340 | * 341 | * If flags is 0, the function does nothing and returns. 342 | * if flags has AE_ALL_EVENTS set, all the kind of events are processed. 343 | * if flags has AE_FILE_EVENTS set, file events are processed. 344 | * if flags has AE_TIME_EVENTS set, time events are processed. 345 | * if flags has AE_DONT_WAIT set the function returns ASAP until all 346 | * the events that's possible to process without to wait are processed. 347 | * 348 | * The function returns the number of events processed. */ 349 | int aeProcessEvents(aeEventLoop *eventLoop, int flags) 350 | { 351 | int processed = 0, numevents; 352 | 353 | /* Nothing to do? return ASAP */ 354 | if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; 355 | 356 | /* Note that we want call select() even if there are no 357 | * file events to process as long as we want to process time 358 | * events, in order to sleep until the next time event is ready 359 | * to fire. */ 360 | if (eventLoop->maxfd != -1 || 361 | ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { 362 | int j; 363 | aeTimeEvent *shortest = NULL; 364 | struct timeval tv, *tvp; 365 | 366 | if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) 367 | shortest = aeSearchNearestTimer(eventLoop); 368 | if (shortest) { 369 | long now_sec, now_ms; 370 | 371 | aeGetTime(&now_sec, &now_ms); 372 | tvp = &tv; 373 | 374 | /* How many milliseconds we need to wait for the next 375 | * time event to fire? */ 376 | long long ms = 377 | (shortest->when_sec - now_sec)*1000 + 378 | shortest->when_ms - now_ms; 379 | 380 | if (ms > 0) { 381 | tvp->tv_sec = ms/1000; 382 | tvp->tv_usec = (ms % 1000)*1000; 383 | } else { 384 | tvp->tv_sec = 0; 385 | tvp->tv_usec = 0; 386 | } 387 | } else { 388 | /* If we have to check for events but need to return 389 | * ASAP because of AE_DONT_WAIT we need to set the timeout 390 | * to zero */ 391 | if (flags & AE_DONT_WAIT) { 392 | tv.tv_sec = tv.tv_usec = 0; 393 | tvp = &tv; 394 | } else { 395 | /* Otherwise we can block */ 396 | tvp = NULL; /* wait forever */ 397 | } 398 | } 399 | 400 | numevents = aeApiPoll(eventLoop, tvp); 401 | for (j = 0; j < numevents; j++) { 402 | aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; 403 | int mask = eventLoop->fired[j].mask; 404 | int fd = eventLoop->fired[j].fd; 405 | int rfired = 0; 406 | 407 | /* note the fe->mask & mask & ... code: maybe an already processed 408 | * event removed an element that fired and we still didn't 409 | * processed, so we check if the event is still valid. */ 410 | if (fe->mask & mask & AE_READABLE) { 411 | rfired = 1; 412 | fe->rfileProc(eventLoop,fd,fe->clientData,mask); 413 | } 414 | if (fe->mask & mask & AE_WRITABLE) { 415 | if (!rfired || fe->wfileProc != fe->rfileProc) 416 | fe->wfileProc(eventLoop,fd,fe->clientData,mask); 417 | } 418 | processed++; 419 | } 420 | } 421 | /* Check time events */ 422 | if (flags & AE_TIME_EVENTS) 423 | processed += processTimeEvents(eventLoop); 424 | 425 | return processed; /* return the number of processed file/time events */ 426 | } 427 | 428 | /* Wait for milliseconds until the given file descriptor becomes 429 | * writable/readable/exception */ 430 | int aeWait(int fd, int mask, long long milliseconds) { 431 | struct pollfd pfd; 432 | int retmask = 0, retval; 433 | 434 | memset(&pfd, 0, sizeof(pfd)); 435 | pfd.fd = fd; 436 | if (mask & AE_READABLE) pfd.events |= POLLIN; 437 | if (mask & AE_WRITABLE) pfd.events |= POLLOUT; 438 | 439 | if ((retval = poll(&pfd, 1, milliseconds))== 1) { 440 | if (pfd.revents & POLLIN) retmask |= AE_READABLE; 441 | if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; 442 | if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; 443 | if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; 444 | return retmask; 445 | } else { 446 | return retval; 447 | } 448 | } 449 | 450 | void aeMain(aeEventLoop *eventLoop) { 451 | eventLoop->stop = 0; 452 | while (!eventLoop->stop) { 453 | if (eventLoop->beforesleep != NULL) 454 | eventLoop->beforesleep(eventLoop); 455 | aeProcessEvents(eventLoop, AE_ALL_EVENTS); 456 | } 457 | } 458 | 459 | char *aeGetApiName(void) { 460 | return aeApiName(); 461 | } 462 | 463 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { 464 | eventLoop->beforesleep = beforesleep; 465 | } 466 | -------------------------------------------------------------------------------- /src/ae.h: -------------------------------------------------------------------------------- 1 | /* A simple event-driven programming library. Originally I wrote this code 2 | * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated 3 | * it in form of a library for easy reuse. 4 | * 5 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __AE_H__ 34 | #define __AE_H__ 35 | 36 | #include 37 | 38 | #define AE_OK 0 39 | #define AE_ERR -1 40 | 41 | #define AE_NONE 0 42 | #define AE_READABLE 1 43 | #define AE_WRITABLE 2 44 | 45 | #define AE_FILE_EVENTS 1 46 | #define AE_TIME_EVENTS 2 47 | #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) 48 | #define AE_DONT_WAIT 4 49 | 50 | #define AE_NOMORE -1 51 | #define AE_DELETED_EVENT_ID -1 52 | 53 | /* Macros */ 54 | #define AE_NOTUSED(V) ((void) V) 55 | 56 | struct aeEventLoop; 57 | 58 | /* Types and data structures */ 59 | typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); 60 | typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); 61 | typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); 62 | typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); 63 | 64 | /* File event structure */ 65 | typedef struct aeFileEvent { 66 | int mask; /* one of AE_(READABLE|WRITABLE) */ 67 | aeFileProc *rfileProc; 68 | aeFileProc *wfileProc; 69 | void *clientData; 70 | } aeFileEvent; 71 | 72 | /* Time event structure */ 73 | typedef struct aeTimeEvent { 74 | long long id; /* time event identifier. */ 75 | long when_sec; /* seconds */ 76 | long when_ms; /* milliseconds */ 77 | aeTimeProc *timeProc; 78 | aeEventFinalizerProc *finalizerProc; 79 | void *clientData; 80 | struct aeTimeEvent *next; 81 | } aeTimeEvent; 82 | 83 | /* A fired event */ 84 | typedef struct aeFiredEvent { 85 | int fd; 86 | int mask; 87 | } aeFiredEvent; 88 | 89 | /* State of an event based program */ 90 | typedef struct aeEventLoop { 91 | int maxfd; /* highest file descriptor currently registered */ 92 | int setsize; /* max number of file descriptors tracked */ 93 | long long timeEventNextId; 94 | time_t lastTime; /* Used to detect system clock skew */ 95 | aeFileEvent *events; /* Registered events */ 96 | aeFiredEvent *fired; /* Fired events */ 97 | aeTimeEvent *timeEventHead; 98 | int stop; 99 | void *apidata; /* This is used for polling API specific data */ 100 | aeBeforeSleepProc *beforesleep; 101 | } aeEventLoop; 102 | 103 | /* Prototypes */ 104 | aeEventLoop *aeCreateEventLoop(int setsize); 105 | void aeDeleteEventLoop(aeEventLoop *eventLoop); 106 | void aeStop(aeEventLoop *eventLoop); 107 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 108 | aeFileProc *proc, void *clientData); 109 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); 110 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd); 111 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 112 | aeTimeProc *proc, void *clientData, 113 | aeEventFinalizerProc *finalizerProc); 114 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); 115 | int aeProcessEvents(aeEventLoop *eventLoop, int flags); 116 | int aeWait(int fd, int mask, long long milliseconds); 117 | void aeMain(aeEventLoop *eventLoop); 118 | char *aeGetApiName(void); 119 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); 120 | int aeGetSetSize(aeEventLoop *eventLoop); 121 | int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /src/ae_epoll.c: -------------------------------------------------------------------------------- 1 | /* Linux epoll(2) based ae.c module 2 | * 3 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | 34 | typedef struct aeApiState { 35 | int epfd; 36 | struct epoll_event *events; 37 | } aeApiState; 38 | 39 | static int aeApiCreate(aeEventLoop *eventLoop) { 40 | aeApiState *state = zmalloc(sizeof(aeApiState)); 41 | 42 | if (!state) return -1; 43 | state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize); 44 | if (!state->events) { 45 | zfree(state); 46 | return -1; 47 | } 48 | state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */ 49 | if (state->epfd == -1) { 50 | zfree(state->events); 51 | zfree(state); 52 | return -1; 53 | } 54 | eventLoop->apidata = state; 55 | return 0; 56 | } 57 | 58 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 59 | aeApiState *state = eventLoop->apidata; 60 | 61 | state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize); 62 | return 0; 63 | } 64 | 65 | static void aeApiFree(aeEventLoop *eventLoop) { 66 | aeApiState *state = eventLoop->apidata; 67 | 68 | close(state->epfd); 69 | zfree(state->events); 70 | zfree(state); 71 | } 72 | 73 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 74 | aeApiState *state = eventLoop->apidata; 75 | struct epoll_event ee = {0}; /* avoid valgrind warning */ 76 | /* If the fd was already monitored for some event, we need a MOD 77 | * operation. Otherwise we need an ADD operation. */ 78 | int op = eventLoop->events[fd].mask == AE_NONE ? 79 | EPOLL_CTL_ADD : EPOLL_CTL_MOD; 80 | 81 | ee.events = 0; 82 | mask |= eventLoop->events[fd].mask; /* Merge old events */ 83 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 84 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 85 | ee.data.fd = fd; 86 | if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; 87 | return 0; 88 | } 89 | 90 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) { 91 | aeApiState *state = eventLoop->apidata; 92 | struct epoll_event ee = {0}; /* avoid valgrind warning */ 93 | int mask = eventLoop->events[fd].mask & (~delmask); 94 | 95 | ee.events = 0; 96 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 97 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 98 | ee.data.fd = fd; 99 | if (mask != AE_NONE) { 100 | epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee); 101 | } else { 102 | /* Note, Kernel < 2.6.9 requires a non null event pointer even for 103 | * EPOLL_CTL_DEL. */ 104 | epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee); 105 | } 106 | } 107 | 108 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 109 | aeApiState *state = eventLoop->apidata; 110 | int retval, numevents = 0; 111 | 112 | retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, 113 | tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1); 114 | if (retval > 0) { 115 | int j; 116 | 117 | numevents = retval; 118 | for (j = 0; j < numevents; j++) { 119 | int mask = 0; 120 | struct epoll_event *e = state->events+j; 121 | 122 | if (e->events & EPOLLIN) mask |= AE_READABLE; 123 | if (e->events & EPOLLOUT) mask |= AE_WRITABLE; 124 | if (e->events & EPOLLERR) mask |= AE_WRITABLE; 125 | if (e->events & EPOLLHUP) mask |= AE_WRITABLE; 126 | eventLoop->fired[j].fd = e->data.fd; 127 | eventLoop->fired[j].mask = mask; 128 | } 129 | } 130 | return numevents; 131 | } 132 | 133 | static char *aeApiName(void) { 134 | return "epoll"; 135 | } 136 | -------------------------------------------------------------------------------- /src/ae_evport.c: -------------------------------------------------------------------------------- 1 | /* ae.c module for illumos event ports. 2 | * 3 | * Copyright (c) 2012, Joyent, Inc. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | static int evport_debug = 0; 42 | 43 | /* 44 | * This file implements the ae API using event ports, present on Solaris-based 45 | * systems since Solaris 10. Using the event port interface, we associate file 46 | * descriptors with the port. Each association also includes the set of poll(2) 47 | * events that the consumer is interested in (e.g., POLLIN and POLLOUT). 48 | * 49 | * There's one tricky piece to this implementation: when we return events via 50 | * aeApiPoll, the corresponding file descriptors become dissociated from the 51 | * port. This is necessary because poll events are level-triggered, so if the 52 | * fd didn't become dissociated, it would immediately fire another event since 53 | * the underlying state hasn't changed yet. We must re-associate the file 54 | * descriptor, but only after we know that our caller has actually read from it. 55 | * The ae API does not tell us exactly when that happens, but we do know that 56 | * it must happen by the time aeApiPoll is called again. Our solution is to 57 | * keep track of the last fds returned by aeApiPoll and re-associate them next 58 | * time aeApiPoll is invoked. 59 | * 60 | * To summarize, in this module, each fd association is EITHER (a) represented 61 | * only via the in-kernel association OR (b) represented by pending_fds and 62 | * pending_masks. (b) is only true for the last fds we returned from aeApiPoll, 63 | * and only until we enter aeApiPoll again (at which point we restore the 64 | * in-kernel association). 65 | */ 66 | #define MAX_EVENT_BATCHSZ 512 67 | 68 | typedef struct aeApiState { 69 | int portfd; /* event port */ 70 | int npending; /* # of pending fds */ 71 | int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */ 72 | int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */ 73 | } aeApiState; 74 | 75 | static int aeApiCreate(aeEventLoop *eventLoop) { 76 | int i; 77 | aeApiState *state = zmalloc(sizeof(aeApiState)); 78 | if (!state) return -1; 79 | 80 | state->portfd = port_create(); 81 | if (state->portfd == -1) { 82 | zfree(state); 83 | return -1; 84 | } 85 | 86 | state->npending = 0; 87 | 88 | for (i = 0; i < MAX_EVENT_BATCHSZ; i++) { 89 | state->pending_fds[i] = -1; 90 | state->pending_masks[i] = AE_NONE; 91 | } 92 | 93 | eventLoop->apidata = state; 94 | return 0; 95 | } 96 | 97 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 98 | /* Nothing to resize here. */ 99 | return 0; 100 | } 101 | 102 | static void aeApiFree(aeEventLoop *eventLoop) { 103 | aeApiState *state = eventLoop->apidata; 104 | 105 | close(state->portfd); 106 | zfree(state); 107 | } 108 | 109 | static int aeApiLookupPending(aeApiState *state, int fd) { 110 | int i; 111 | 112 | for (i = 0; i < state->npending; i++) { 113 | if (state->pending_fds[i] == fd) 114 | return (i); 115 | } 116 | 117 | return (-1); 118 | } 119 | 120 | /* 121 | * Helper function to invoke port_associate for the given fd and mask. 122 | */ 123 | static int aeApiAssociate(const char *where, int portfd, int fd, int mask) { 124 | int events = 0; 125 | int rv, err; 126 | 127 | if (mask & AE_READABLE) 128 | events |= POLLIN; 129 | if (mask & AE_WRITABLE) 130 | events |= POLLOUT; 131 | 132 | if (evport_debug) 133 | fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events); 134 | 135 | rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, 136 | (void *)(uintptr_t)mask); 137 | err = errno; 138 | 139 | if (evport_debug) 140 | fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err)); 141 | 142 | if (rv == -1) { 143 | fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err)); 144 | 145 | if (err == EAGAIN) 146 | fprintf(stderr, "aeApiAssociate: event port limit exceeded."); 147 | } 148 | 149 | return rv; 150 | } 151 | 152 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 153 | aeApiState *state = eventLoop->apidata; 154 | int fullmask, pfd; 155 | 156 | if (evport_debug) 157 | fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask); 158 | 159 | /* 160 | * Since port_associate's "events" argument replaces any existing events, we 161 | * must be sure to include whatever events are already associated when 162 | * we call port_associate() again. 163 | */ 164 | fullmask = mask | eventLoop->events[fd].mask; 165 | pfd = aeApiLookupPending(state, fd); 166 | 167 | if (pfd != -1) { 168 | /* 169 | * This fd was recently returned from aeApiPoll. It should be safe to 170 | * assume that the consumer has processed that poll event, but we play 171 | * it safer by simply updating pending_mask. The fd will be 172 | * re-associated as usual when aeApiPoll is called again. 173 | */ 174 | if (evport_debug) 175 | fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd); 176 | state->pending_masks[pfd] |= fullmask; 177 | return 0; 178 | } 179 | 180 | return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask)); 181 | } 182 | 183 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 184 | aeApiState *state = eventLoop->apidata; 185 | int fullmask, pfd; 186 | 187 | if (evport_debug) 188 | fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask); 189 | 190 | pfd = aeApiLookupPending(state, fd); 191 | 192 | if (pfd != -1) { 193 | if (evport_debug) 194 | fprintf(stderr, "deleting event from pending fd %d\n", fd); 195 | 196 | /* 197 | * This fd was just returned from aeApiPoll, so it's not currently 198 | * associated with the port. All we need to do is update 199 | * pending_mask appropriately. 200 | */ 201 | state->pending_masks[pfd] &= ~mask; 202 | 203 | if (state->pending_masks[pfd] == AE_NONE) 204 | state->pending_fds[pfd] = -1; 205 | 206 | return; 207 | } 208 | 209 | /* 210 | * The fd is currently associated with the port. Like with the add case 211 | * above, we must look at the full mask for the file descriptor before 212 | * updating that association. We don't have a good way of knowing what the 213 | * events are without looking into the eventLoop state directly. We rely on 214 | * the fact that our caller has already updated the mask in the eventLoop. 215 | */ 216 | 217 | fullmask = eventLoop->events[fd].mask; 218 | if (fullmask == AE_NONE) { 219 | /* 220 | * We're removing *all* events, so use port_dissociate to remove the 221 | * association completely. Failure here indicates a bug. 222 | */ 223 | if (evport_debug) 224 | fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd); 225 | 226 | if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) { 227 | perror("aeApiDelEvent: port_dissociate"); 228 | abort(); /* will not return */ 229 | } 230 | } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd, 231 | fullmask) != 0) { 232 | /* 233 | * ENOMEM is a potentially transient condition, but the kernel won't 234 | * generally return it unless things are really bad. EAGAIN indicates 235 | * we've reached an resource limit, for which it doesn't make sense to 236 | * retry (counter-intuitively). All other errors indicate a bug. In any 237 | * of these cases, the best we can do is to abort. 238 | */ 239 | abort(); /* will not return */ 240 | } 241 | } 242 | 243 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 244 | aeApiState *state = eventLoop->apidata; 245 | struct timespec timeout, *tsp; 246 | int mask, i; 247 | uint_t nevents; 248 | port_event_t event[MAX_EVENT_BATCHSZ]; 249 | 250 | /* 251 | * If we've returned fd events before, we must re-associate them with the 252 | * port now, before calling port_get(). See the block comment at the top of 253 | * this file for an explanation of why. 254 | */ 255 | for (i = 0; i < state->npending; i++) { 256 | if (state->pending_fds[i] == -1) 257 | /* This fd has since been deleted. */ 258 | continue; 259 | 260 | if (aeApiAssociate("aeApiPoll", state->portfd, 261 | state->pending_fds[i], state->pending_masks[i]) != 0) { 262 | /* See aeApiDelEvent for why this case is fatal. */ 263 | abort(); 264 | } 265 | 266 | state->pending_masks[i] = AE_NONE; 267 | state->pending_fds[i] = -1; 268 | } 269 | 270 | state->npending = 0; 271 | 272 | if (tvp != NULL) { 273 | timeout.tv_sec = tvp->tv_sec; 274 | timeout.tv_nsec = tvp->tv_usec * 1000; 275 | tsp = &timeout; 276 | } else { 277 | tsp = NULL; 278 | } 279 | 280 | /* 281 | * port_getn can return with errno == ETIME having returned some events (!). 282 | * So if we get ETIME, we check nevents, too. 283 | */ 284 | nevents = 1; 285 | if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents, 286 | tsp) == -1 && (errno != ETIME || nevents == 0)) { 287 | if (errno == ETIME || errno == EINTR) 288 | return 0; 289 | 290 | /* Any other error indicates a bug. */ 291 | perror("aeApiPoll: port_get"); 292 | abort(); 293 | } 294 | 295 | state->npending = nevents; 296 | 297 | for (i = 0; i < nevents; i++) { 298 | mask = 0; 299 | if (event[i].portev_events & POLLIN) 300 | mask |= AE_READABLE; 301 | if (event[i].portev_events & POLLOUT) 302 | mask |= AE_WRITABLE; 303 | 304 | eventLoop->fired[i].fd = event[i].portev_object; 305 | eventLoop->fired[i].mask = mask; 306 | 307 | if (evport_debug) 308 | fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", 309 | (int)event[i].portev_object, mask); 310 | 311 | state->pending_fds[i] = event[i].portev_object; 312 | state->pending_masks[i] = (uintptr_t)event[i].portev_user; 313 | } 314 | 315 | return nevents; 316 | } 317 | 318 | static char *aeApiName(void) { 319 | return "evport"; 320 | } 321 | -------------------------------------------------------------------------------- /src/ae_kqueue.c: -------------------------------------------------------------------------------- 1 | /* Kqueue(2)-based ae.c module 2 | * 3 | * Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | typedef struct aeApiState { 37 | int kqfd; 38 | struct kevent *events; 39 | } aeApiState; 40 | 41 | static int aeApiCreate(aeEventLoop *eventLoop) { 42 | aeApiState *state = zmalloc(sizeof(aeApiState)); 43 | 44 | if (!state) return -1; 45 | state->events = zmalloc(sizeof(struct kevent)*eventLoop->setsize); 46 | if (!state->events) { 47 | zfree(state); 48 | return -1; 49 | } 50 | state->kqfd = kqueue(); 51 | if (state->kqfd == -1) { 52 | zfree(state->events); 53 | zfree(state); 54 | return -1; 55 | } 56 | eventLoop->apidata = state; 57 | return 0; 58 | } 59 | 60 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 61 | aeApiState *state = eventLoop->apidata; 62 | 63 | state->events = zrealloc(state->events, sizeof(struct kevent)*setsize); 64 | return 0; 65 | } 66 | 67 | static void aeApiFree(aeEventLoop *eventLoop) { 68 | aeApiState *state = eventLoop->apidata; 69 | 70 | close(state->kqfd); 71 | zfree(state->events); 72 | zfree(state); 73 | } 74 | 75 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 76 | aeApiState *state = eventLoop->apidata; 77 | struct kevent ke; 78 | 79 | if (mask & AE_READABLE) { 80 | EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); 81 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 82 | } 83 | if (mask & AE_WRITABLE) { 84 | EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); 85 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 86 | } 87 | return 0; 88 | } 89 | 90 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 91 | aeApiState *state = eventLoop->apidata; 92 | struct kevent ke; 93 | 94 | if (mask & AE_READABLE) { 95 | EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); 96 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 97 | } 98 | if (mask & AE_WRITABLE) { 99 | EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); 100 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 101 | } 102 | } 103 | 104 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 105 | aeApiState *state = eventLoop->apidata; 106 | int retval, numevents = 0; 107 | 108 | if (tvp != NULL) { 109 | struct timespec timeout; 110 | timeout.tv_sec = tvp->tv_sec; 111 | timeout.tv_nsec = tvp->tv_usec * 1000; 112 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 113 | &timeout); 114 | } else { 115 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 116 | NULL); 117 | } 118 | 119 | if (retval > 0) { 120 | int j; 121 | 122 | numevents = retval; 123 | for(j = 0; j < numevents; j++) { 124 | int mask = 0; 125 | struct kevent *e = state->events+j; 126 | 127 | if (e->filter == EVFILT_READ) mask |= AE_READABLE; 128 | if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE; 129 | eventLoop->fired[j].fd = e->ident; 130 | eventLoop->fired[j].mask = mask; 131 | } 132 | } 133 | return numevents; 134 | } 135 | 136 | static char *aeApiName(void) { 137 | return "kqueue"; 138 | } 139 | -------------------------------------------------------------------------------- /src/ae_select.c: -------------------------------------------------------------------------------- 1 | /* Select()-based ae.c module. 2 | * 3 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | #include 34 | 35 | typedef struct aeApiState { 36 | fd_set rfds, wfds; 37 | /* We need to have a copy of the fd sets as it's not safe to reuse 38 | * FD sets after select(). */ 39 | fd_set _rfds, _wfds; 40 | } aeApiState; 41 | 42 | static int aeApiCreate(aeEventLoop *eventLoop) { 43 | aeApiState *state = zmalloc(sizeof(aeApiState)); 44 | 45 | if (!state) return -1; 46 | FD_ZERO(&state->rfds); 47 | FD_ZERO(&state->wfds); 48 | eventLoop->apidata = state; 49 | return 0; 50 | } 51 | 52 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 53 | /* Just ensure we have enough room in the fd_set type. */ 54 | if (setsize >= FD_SETSIZE) return -1; 55 | return 0; 56 | } 57 | 58 | static void aeApiFree(aeEventLoop *eventLoop) { 59 | zfree(eventLoop->apidata); 60 | } 61 | 62 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 63 | aeApiState *state = eventLoop->apidata; 64 | 65 | if (mask & AE_READABLE) FD_SET(fd,&state->rfds); 66 | if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds); 67 | return 0; 68 | } 69 | 70 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 71 | aeApiState *state = eventLoop->apidata; 72 | 73 | if (mask & AE_READABLE) FD_CLR(fd,&state->rfds); 74 | if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds); 75 | } 76 | 77 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 78 | aeApiState *state = eventLoop->apidata; 79 | int retval, j, numevents = 0; 80 | 81 | memcpy(&state->_rfds,&state->rfds,sizeof(fd_set)); 82 | memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); 83 | 84 | retval = select(eventLoop->maxfd+1, 85 | &state->_rfds,&state->_wfds,NULL,tvp); 86 | if (retval > 0) { 87 | for (j = 0; j <= eventLoop->maxfd; j++) { 88 | int mask = 0; 89 | aeFileEvent *fe = &eventLoop->events[j]; 90 | 91 | if (fe->mask == AE_NONE) continue; 92 | if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds)) 93 | mask |= AE_READABLE; 94 | if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds)) 95 | mask |= AE_WRITABLE; 96 | eventLoop->fired[numevents].fd = j; 97 | eventLoop->fired[numevents].mask = mask; 98 | numevents++; 99 | } 100 | } 101 | return numevents; 102 | } 103 | 104 | static char *aeApiName(void) { 105 | return "select"; 106 | } 107 | -------------------------------------------------------------------------------- /src/aprintf.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char *aprintf(char **s, const char *fmt, ...) { 9 | char *c = NULL; 10 | int n, len; 11 | va_list ap; 12 | 13 | va_start(ap, fmt); 14 | n = vsnprintf(NULL, 0, fmt, ap) + 1; 15 | va_end(ap); 16 | 17 | len = *s ? strlen(*s) : 0; 18 | 19 | if ((*s = realloc(*s, (len + n) * sizeof(char)))) { 20 | c = *s + len; 21 | va_start(ap, fmt); 22 | vsnprintf(c, n, fmt, ap); 23 | va_end(ap); 24 | } 25 | 26 | return c; 27 | } 28 | -------------------------------------------------------------------------------- /src/aprintf.h: -------------------------------------------------------------------------------- 1 | #ifndef APRINTF_H 2 | #define APRINTF_H 3 | 4 | char *aprintf(char **, const char *, ...); 5 | 6 | #endif /* APRINTF_H */ 7 | -------------------------------------------------------------------------------- /src/atomicvar.h: -------------------------------------------------------------------------------- 1 | /* This file implements atomic counters using __atomic or __sync macros if 2 | * available, otherwise synchronizing different threads using a mutex. 3 | * 4 | * The exported interaface is composed of three macros: 5 | * 6 | * atomicIncr(var,count) -- Increment the atomic counter 7 | * atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter 8 | * atomicDecr(var,count) -- Decrement the atomic counter 9 | * atomicGet(var,dstvar) -- Fetch the atomic counter value 10 | * atomicSet(var,value) -- Set the atomic counter value 11 | * 12 | * The variable 'var' should also have a declared mutex with the same 13 | * name and the "_mutex" postfix, for instance: 14 | * 15 | * long myvar; 16 | * pthread_mutex_t myvar_mutex; 17 | * atomicSet(myvar,12345); 18 | * 19 | * If atomic primitives are availble (tested in config.h) the mutex 20 | * is not used. 21 | * 22 | * Never use return value from the macros, instead use the AtomicGetIncr() 23 | * if you need to get the current value and increment it atomically, like 24 | * in the followign example: 25 | * 26 | * long oldvalue; 27 | * atomicGetIncr(myvar,oldvalue,1); 28 | * doSomethingWith(oldvalue); 29 | * 30 | * ---------------------------------------------------------------------------- 31 | * 32 | * Copyright (c) 2015, Salvatore Sanfilippo 33 | * All rights reserved. 34 | * 35 | * Redistribution and use in source and binary forms, with or without 36 | * modification, are permitted provided that the following conditions are met: 37 | * 38 | * * Redistributions of source code must retain the above copyright notice, 39 | * this list of conditions and the following disclaimer. 40 | * * Redistributions in binary form must reproduce the above copyright 41 | * notice, this list of conditions and the following disclaimer in the 42 | * documentation and/or other materials provided with the distribution. 43 | * * Neither the name of Redis nor the names of its contributors may be used 44 | * to endorse or promote products derived from this software without 45 | * specific prior written permission. 46 | * 47 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 48 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 49 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 50 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 51 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 52 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 53 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 54 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 55 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 56 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 57 | * POSSIBILITY OF SUCH DAMAGE. 58 | */ 59 | 60 | #include 61 | 62 | #ifndef __ATOMIC_VAR_H 63 | #define __ATOMIC_VAR_H 64 | 65 | /* To test Redis with Helgrind (a Valgrind tool) it is useful to define 66 | * the following macro, so that __sync macros are used: those can be detected 67 | * by Helgrind (even if they are less efficient) so that no false positive 68 | * is reported. */ 69 | // #define __ATOMIC_VAR_FORCE_SYNC_MACROS 70 | 71 | #if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__ATOMIC_RELAXED) && !defined(__sun) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057) 72 | /* Implementation using __atomic macros. */ 73 | 74 | #define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED) 75 | #define atomicGetIncr(var,oldvalue_var,count) do { \ 76 | oldvalue_var = __atomic_fetch_add(&var,(count),__ATOMIC_RELAXED); \ 77 | } while(0) 78 | #define atomicDecr(var,count) __atomic_sub_fetch(&var,(count),__ATOMIC_RELAXED) 79 | #define atomicGet(var,dstvar) do { \ 80 | dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \ 81 | } while(0) 82 | #define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED) 83 | #define REDIS_ATOMIC_API "atomic-builtin" 84 | 85 | #elif defined(HAVE_ATOMIC) 86 | /* Implementation using __sync macros. */ 87 | 88 | #define atomicIncr(var,count) __sync_add_and_fetch(&var,(count)) 89 | #define atomicGetIncr(var,oldvalue_var,count) do { \ 90 | oldvalue_var = __sync_fetch_and_add(&var,(count)); \ 91 | } while(0) 92 | #define atomicDecr(var,count) __sync_sub_and_fetch(&var,(count)) 93 | #define atomicGet(var,dstvar) do { \ 94 | dstvar = __sync_sub_and_fetch(&var,0); \ 95 | } while(0) 96 | #define atomicSet(var,value) do { \ 97 | while(!__sync_bool_compare_and_swap(&var,var,value)); \ 98 | } while(0) 99 | #define REDIS_ATOMIC_API "sync-builtin" 100 | 101 | #else 102 | /* Implementation using pthread mutex. */ 103 | 104 | #define atomicIncr(var,count) do { \ 105 | pthread_mutex_lock(&var ## _mutex); \ 106 | var += (count); \ 107 | pthread_mutex_unlock(&var ## _mutex); \ 108 | } while(0) 109 | #define atomicGetIncr(var,oldvalue_var,count) do { \ 110 | pthread_mutex_lock(&var ## _mutex); \ 111 | oldvalue_var = var; \ 112 | var += (count); \ 113 | pthread_mutex_unlock(&var ## _mutex); \ 114 | } while(0) 115 | #define atomicDecr(var,count) do { \ 116 | pthread_mutex_lock(&var ## _mutex); \ 117 | var -= (count); \ 118 | pthread_mutex_unlock(&var ## _mutex); \ 119 | } while(0) 120 | #define atomicGet(var,dstvar) do { \ 121 | pthread_mutex_lock(&var ## _mutex); \ 122 | dstvar = var; \ 123 | pthread_mutex_unlock(&var ## _mutex); \ 124 | } while(0) 125 | #define atomicSet(var,value) do { \ 126 | pthread_mutex_lock(&var ## _mutex); \ 127 | var = value; \ 128 | pthread_mutex_unlock(&var ## _mutex); \ 129 | } while(0) 130 | #define REDIS_ATOMIC_API "pthread-mutex" 131 | 132 | #endif 133 | #endif /* __ATOMIC_VAR_H */ 134 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #if defined(__FreeBSD__) || defined(__APPLE__) 5 | #define HAVE_KQUEUE 6 | #elif defined(__linux__) 7 | #define HAVE_EPOLL 8 | #elif defined (__sun) 9 | #define HAVE_EVPORT 10 | #define _XPG6 11 | #define __EXTENSIONS__ 12 | #include 13 | #include 14 | #include 15 | #endif 16 | 17 | #endif /* CONFIG_H */ 18 | -------------------------------------------------------------------------------- /src/http_parser.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesky1stm/wrktcp/af4c5215117416dee70cfa0424e61231da349f71/src/http_parser.c -------------------------------------------------------------------------------- /src/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 7 30 | #define HTTP_PARSER_VERSION_PATCH 1 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && \ 34 | (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * Returning `2` from on_headers_complete will tell parser that it should not 80 | * expect neither a body nor any futher responses on this connection. This is 81 | * useful for handling responses to a CONNECT request which may not contain 82 | * `Upgrade` or `Connection: upgrade` headers. 83 | * 84 | * http_data_cb does not return data chunks. It will be called arbitrarily 85 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 86 | * each providing just a few characters more data. 87 | */ 88 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 89 | typedef int (*http_cb) (http_parser*); 90 | 91 | 92 | /* Status Codes */ 93 | #define HTTP_STATUS_MAP(XX) \ 94 | XX(100, CONTINUE, Continue) \ 95 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 96 | XX(102, PROCESSING, Processing) \ 97 | XX(200, OK, OK) \ 98 | XX(201, CREATED, Created) \ 99 | XX(202, ACCEPTED, Accepted) \ 100 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ 101 | XX(204, NO_CONTENT, No Content) \ 102 | XX(205, RESET_CONTENT, Reset Content) \ 103 | XX(206, PARTIAL_CONTENT, Partial Content) \ 104 | XX(207, MULTI_STATUS, Multi-Status) \ 105 | XX(208, ALREADY_REPORTED, Already Reported) \ 106 | XX(226, IM_USED, IM Used) \ 107 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 108 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 109 | XX(302, FOUND, Found) \ 110 | XX(303, SEE_OTHER, See Other) \ 111 | XX(304, NOT_MODIFIED, Not Modified) \ 112 | XX(305, USE_PROXY, Use Proxy) \ 113 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 114 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 115 | XX(400, BAD_REQUEST, Bad Request) \ 116 | XX(401, UNAUTHORIZED, Unauthorized) \ 117 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 118 | XX(403, FORBIDDEN, Forbidden) \ 119 | XX(404, NOT_FOUND, Not Found) \ 120 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 121 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 122 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 123 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 124 | XX(409, CONFLICT, Conflict) \ 125 | XX(410, GONE, Gone) \ 126 | XX(411, LENGTH_REQUIRED, Length Required) \ 127 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 128 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 129 | XX(414, URI_TOO_LONG, URI Too Long) \ 130 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 131 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 132 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 133 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 134 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 135 | XX(423, LOCKED, Locked) \ 136 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 137 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 138 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 139 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 140 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 141 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 142 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 143 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 144 | XX(502, BAD_GATEWAY, Bad Gateway) \ 145 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 146 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 147 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 148 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 149 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 150 | XX(508, LOOP_DETECTED, Loop Detected) \ 151 | XX(510, NOT_EXTENDED, Not Extended) \ 152 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ 153 | 154 | enum http_status 155 | { 156 | #define XX(num, name, string) HTTP_STATUS_##name = num, 157 | HTTP_STATUS_MAP(XX) 158 | #undef XX 159 | }; 160 | 161 | 162 | /* Request Methods */ 163 | #define HTTP_METHOD_MAP(XX) \ 164 | XX(0, DELETE, DELETE) \ 165 | XX(1, GET, GET) \ 166 | XX(2, HEAD, HEAD) \ 167 | XX(3, POST, POST) \ 168 | XX(4, PUT, PUT) \ 169 | /* pathological */ \ 170 | XX(5, CONNECT, CONNECT) \ 171 | XX(6, OPTIONS, OPTIONS) \ 172 | XX(7, TRACE, TRACE) \ 173 | /* WebDAV */ \ 174 | XX(8, COPY, COPY) \ 175 | XX(9, LOCK, LOCK) \ 176 | XX(10, MKCOL, MKCOL) \ 177 | XX(11, MOVE, MOVE) \ 178 | XX(12, PROPFIND, PROPFIND) \ 179 | XX(13, PROPPATCH, PROPPATCH) \ 180 | XX(14, SEARCH, SEARCH) \ 181 | XX(15, UNLOCK, UNLOCK) \ 182 | XX(16, BIND, BIND) \ 183 | XX(17, REBIND, REBIND) \ 184 | XX(18, UNBIND, UNBIND) \ 185 | XX(19, ACL, ACL) \ 186 | /* subversion */ \ 187 | XX(20, REPORT, REPORT) \ 188 | XX(21, MKACTIVITY, MKACTIVITY) \ 189 | XX(22, CHECKOUT, CHECKOUT) \ 190 | XX(23, MERGE, MERGE) \ 191 | /* upnp */ \ 192 | XX(24, MSEARCH, M-SEARCH) \ 193 | XX(25, NOTIFY, NOTIFY) \ 194 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 195 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 196 | /* RFC-5789 */ \ 197 | XX(28, PATCH, PATCH) \ 198 | XX(29, PURGE, PURGE) \ 199 | /* CalDAV */ \ 200 | XX(30, MKCALENDAR, MKCALENDAR) \ 201 | /* RFC-2068, section 19.6.1.2 */ \ 202 | XX(31, LINK, LINK) \ 203 | XX(32, UNLINK, UNLINK) \ 204 | 205 | enum http_method 206 | { 207 | #define XX(num, name, string) HTTP_##name = num, 208 | HTTP_METHOD_MAP(XX) 209 | #undef XX 210 | }; 211 | 212 | 213 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 214 | 215 | 216 | /* Flag values for http_parser.flags field */ 217 | enum flags 218 | { F_CHUNKED = 1 << 0 219 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 220 | , F_CONNECTION_CLOSE = 1 << 2 221 | , F_CONNECTION_UPGRADE = 1 << 3 222 | , F_TRAILING = 1 << 4 223 | , F_UPGRADE = 1 << 5 224 | , F_SKIPBODY = 1 << 6 225 | , F_CONTENTLENGTH = 1 << 7 226 | }; 227 | 228 | 229 | /* Map for errno-related constants 230 | * 231 | * The provided argument should be a macro that takes 2 arguments. 232 | */ 233 | #define HTTP_ERRNO_MAP(XX) \ 234 | /* No error */ \ 235 | XX(OK, "success") \ 236 | \ 237 | /* Callback-related errors */ \ 238 | XX(CB_message_begin, "the on_message_begin callback failed") \ 239 | XX(CB_url, "the on_url callback failed") \ 240 | XX(CB_header_field, "the on_header_field callback failed") \ 241 | XX(CB_header_value, "the on_header_value callback failed") \ 242 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 243 | XX(CB_body, "the on_body callback failed") \ 244 | XX(CB_message_complete, "the on_message_complete callback failed") \ 245 | XX(CB_status, "the on_status callback failed") \ 246 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 247 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 248 | \ 249 | /* Parsing-related errors */ \ 250 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 251 | XX(HEADER_OVERFLOW, \ 252 | "too many header bytes seen; overflow detected") \ 253 | XX(CLOSED_CONNECTION, \ 254 | "data received after completed connection: close message") \ 255 | XX(INVALID_VERSION, "invalid HTTP version") \ 256 | XX(INVALID_STATUS, "invalid HTTP status code") \ 257 | XX(INVALID_METHOD, "invalid HTTP method") \ 258 | XX(INVALID_URL, "invalid URL") \ 259 | XX(INVALID_HOST, "invalid host") \ 260 | XX(INVALID_PORT, "invalid port") \ 261 | XX(INVALID_PATH, "invalid path") \ 262 | XX(INVALID_QUERY_STRING, "invalid query string") \ 263 | XX(INVALID_FRAGMENT, "invalid fragment") \ 264 | XX(LF_EXPECTED, "LF character expected") \ 265 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 266 | XX(INVALID_CONTENT_LENGTH, \ 267 | "invalid character in content-length header") \ 268 | XX(UNEXPECTED_CONTENT_LENGTH, \ 269 | "unexpected content-length header") \ 270 | XX(INVALID_CHUNK_SIZE, \ 271 | "invalid character in chunk size header") \ 272 | XX(INVALID_CONSTANT, "invalid constant string") \ 273 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 274 | XX(STRICT, "strict mode assertion failed") \ 275 | XX(PAUSED, "parser is paused") \ 276 | XX(UNKNOWN, "an unknown error occurred") 277 | 278 | 279 | /* Define HPE_* values for each errno value above */ 280 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 281 | enum http_errno { 282 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 283 | }; 284 | #undef HTTP_ERRNO_GEN 285 | 286 | 287 | /* Get an http_errno value from an http_parser */ 288 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 289 | 290 | 291 | struct http_parser { 292 | /** PRIVATE **/ 293 | unsigned int type : 2; /* enum http_parser_type */ 294 | unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ 295 | unsigned int state : 7; /* enum state from http_parser.c */ 296 | unsigned int header_state : 7; /* enum header_state from http_parser.c */ 297 | unsigned int index : 7; /* index into current matcher */ 298 | unsigned int lenient_http_headers : 1; 299 | 300 | uint32_t nread; /* # bytes read in various scenarios */ 301 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 302 | 303 | /** READ-ONLY **/ 304 | unsigned short http_major; 305 | unsigned short http_minor; 306 | unsigned int status_code : 16; /* responses only */ 307 | unsigned int method : 8; /* requests only */ 308 | unsigned int http_errno : 7; 309 | 310 | /* 1 = Upgrade header was present and the parser has exited because of that. 311 | * 0 = No upgrade header present. 312 | * Should be checked when http_parser_execute() returns in addition to 313 | * error checking. 314 | */ 315 | unsigned int upgrade : 1; 316 | 317 | /** PUBLIC **/ 318 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 319 | }; 320 | 321 | 322 | struct http_parser_settings { 323 | http_cb on_message_begin; 324 | http_data_cb on_url; 325 | http_data_cb on_status; 326 | http_data_cb on_header_field; 327 | http_data_cb on_header_value; 328 | http_cb on_headers_complete; 329 | http_data_cb on_body; 330 | http_cb on_message_complete; 331 | /* When on_chunk_header is called, the current chunk length is stored 332 | * in parser->content_length. 333 | */ 334 | http_cb on_chunk_header; 335 | http_cb on_chunk_complete; 336 | }; 337 | 338 | 339 | enum http_parser_url_fields 340 | { UF_SCHEMA = 0 341 | , UF_HOST = 1 342 | , UF_PORT = 2 343 | , UF_PATH = 3 344 | , UF_QUERY = 4 345 | , UF_FRAGMENT = 5 346 | , UF_USERINFO = 6 347 | , UF_MAX = 7 348 | }; 349 | 350 | 351 | /* Result structure for http_parser_parse_url(). 352 | * 353 | * Callers should index into field_data[] with UF_* values iff field_set 354 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 355 | * because we probably have padding left over), we convert any port to 356 | * a uint16_t. 357 | */ 358 | struct http_parser_url { 359 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 360 | uint16_t port; /* Converted UF_PORT string */ 361 | 362 | struct { 363 | uint16_t off; /* Offset into buffer in which field starts */ 364 | uint16_t len; /* Length of run in buffer */ 365 | } field_data[UF_MAX]; 366 | }; 367 | 368 | 369 | /* Returns the library version. Bits 16-23 contain the major version number, 370 | * bits 8-15 the minor version number and bits 0-7 the patch level. 371 | * Usage example: 372 | * 373 | * unsigned long version = http_parser_version(); 374 | * unsigned major = (version >> 16) & 255; 375 | * unsigned minor = (version >> 8) & 255; 376 | * unsigned patch = version & 255; 377 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 378 | */ 379 | unsigned long http_parser_version(void); 380 | 381 | void http_parser_init(http_parser *parser, enum http_parser_type type); 382 | 383 | 384 | /* Initialize http_parser_settings members to 0 385 | */ 386 | void http_parser_settings_init(http_parser_settings *settings); 387 | 388 | 389 | /* Executes the parser. Returns number of parsed bytes. Sets 390 | * `parser->http_errno` on error. */ 391 | size_t http_parser_execute(http_parser *parser, 392 | const http_parser_settings *settings, 393 | const char *data, 394 | size_t len); 395 | 396 | 397 | /* If http_should_keep_alive() in the on_headers_complete or 398 | * on_message_complete callback returns 0, then this should be 399 | * the last message on the connection. 400 | * If you are the server, respond with the "Connection: close" header. 401 | * If you are the client, close the connection. 402 | */ 403 | int http_should_keep_alive(const http_parser *parser); 404 | 405 | /* Returns a string version of the HTTP method. */ 406 | const char *http_method_str(enum http_method m); 407 | 408 | /* Return a string name of the given error */ 409 | const char *http_errno_name(enum http_errno err); 410 | 411 | /* Return a string description of the given error */ 412 | const char *http_errno_description(enum http_errno err); 413 | 414 | /* Initialize all http_parser_url members to 0 */ 415 | void http_parser_url_init(struct http_parser_url *u); 416 | 417 | /* Parse a URL; return nonzero on failure */ 418 | int http_parser_parse_url(const char *buf, size_t buflen, 419 | int is_connect, 420 | struct http_parser_url *u); 421 | 422 | /* Pause or un-pause the parser; a nonzero value pauses */ 423 | void http_parser_pause(http_parser *parser, int paused); 424 | 425 | /* Checks if this is the final chunk of the body. */ 426 | int http_body_is_final(const http_parser *parser); 427 | 428 | #ifdef __cplusplus 429 | } 430 | #endif 431 | #endif 432 | -------------------------------------------------------------------------------- /src/islog.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/15. 本来想引用islog的,由于太复杂,还是算了 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "islog.h" 9 | 10 | #include 11 | 12 | /* 最大输出行数 */ 13 | #define ISLOG_LINE_MAXSIZE 1024 14 | /* log line maxsize */ 15 | 16 | char * islog_version(){ 17 | return ISLOG_VERSION_NO; 18 | } 19 | 20 | /* 外部定义的 */ 21 | #include "wrktcp.h" 22 | extern config cfg; 23 | 24 | void islog_print(char *file, long line, char *level, char *fmtstr, ...) 25 | { 26 | /* v1.1增加, test类型的话,只有istest模式才输出 */ 27 | if( strcmp(level, "test") == 0){ 28 | if( cfg.istest != 1) 29 | return; 30 | } 31 | va_list ap; 32 | char tmpstr[ISLOG_LINE_MAXSIZE]; 33 | 34 | memset(tmpstr, 0x00, sizeof(tmpstr)); 35 | 36 | va_start(ap, fmtstr); 37 | vsnprintf(tmpstr, sizeof(tmpstr), fmtstr, ap); 38 | va_end(ap); 39 | 40 | fprintf(stdout, "[%s][%s][%03ld]", level, file, line); 41 | fprintf(stdout, "%s\n", tmpstr); 42 | 43 | return ; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/islog.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/15. 3 | // 4 | 5 | #ifndef ISLOG_H 6 | #define ISLOG_H 7 | 8 | #define ISLOG_VERSION_NO "20.12.1" 9 | char * islog_version(); 10 | 11 | void islog_print(char *file, long line, char *level, char *fmtstr, ...); 12 | 13 | /** 通过宏的方式控制日志是否输出, 14 | * 因为在预编译时执行,对效率完全没有影响 15 | * 但是修改需要重新编译引用的头文件 **/ 16 | #define ISLOG_ERROR_ENABLE 17 | #define ISLOG_WARN_ENABLE 18 | #define ISLOG_INFO_ENABLE 19 | #define ISLOG_DEBUG_ENABLE_NO 20 | 21 | /** 定义各种类型的日志输出 **/ 22 | #ifdef ISLOG_ERROR_ENABLE 23 | #define islog_error(...) \ 24 | do{ \ 25 | islog_print(__FILE__,__LINE__, "error", __VA_ARGS__);\ 26 | }while(0) 27 | #else 28 | #define islog_error(...) 29 | #endif 30 | 31 | #ifdef ISLOG_WARN_ENABLE 32 | #define islog_warn(...) islog_print(__FILE__,__LINE__, "warn", __VA_ARGS__) 33 | #else 34 | #define islog_warn(...) 35 | #endif 36 | 37 | #ifdef ISLOG_INFO_ENABLE 38 | #define islog_info(...) islog_print(__FILE__,__LINE__, "info", __VA_ARGS__) 39 | #else 40 | #define islog_info(...) 41 | #endif 42 | 43 | 44 | #ifdef ISLOG_DEBUG_ENABLE 45 | #define islog_debug(...) islog_print(__FILE__,__LINE__, "debug", __VA_ARGS__) 46 | #else 47 | #define islog_debug(...) 48 | #endif 49 | 50 | #define islog_test(...) islog_print(__FILE__,__LINE__, "test", __VA_ARGS__) 51 | 52 | #endif //ISLOG_H 53 | -------------------------------------------------------------------------------- /src/isstr.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/28. 3 | // 4 | 5 | #include 6 | #include "isstr.h" 7 | 8 | char * isstr_version(){ 9 | return ISSTR_VERSION_NO; 10 | } 11 | 12 | /** 去除字符串的前后空格,中间的空格不会去掉 **/ 13 | char *isstr_trim(char *str) 14 | { 15 | int i; 16 | int b1, e1; 17 | 18 | if (strlen(str) == 0) return str; 19 | 20 | if (str) 21 | { 22 | for(i = 0; str[i] == ' '; i++); 23 | 24 | b1 = i; 25 | 26 | for(i = strlen(str) - 1; i >= b1 && str[i] == ' '; i--); 27 | 28 | e1 = i; 29 | 30 | if (e1 >= b1) 31 | memmove(str, str+b1, e1-b1+1); 32 | 33 | str[e1-b1+1] = 0; 34 | return str; 35 | } 36 | else return str; 37 | } 38 | 39 | char * isstr_split(char *str, char * sign, int num, char * value){ 40 | int i; 41 | char *p, *q; 42 | int sign_len; 43 | 44 | sign_len = strlen(sign); 45 | 46 | q = str; 47 | 48 | num--; 49 | 50 | /** 跳过前 num 个 sign,查找开始位置 **/ 51 | while(num != 0){ 52 | q = strstr( q, sign); 53 | if( q == NULL){ 54 | return NULL; 55 | } 56 | q = q + sign_len; 57 | num--; 58 | } 59 | 60 | /* 开始位置 */ 61 | p = q; 62 | 63 | /** 获取下一个sign, 或者结尾 **/ 64 | q = strstr(q, sign); 65 | if( q == NULL){ 66 | i = strlen(p); 67 | }else{ 68 | i = q - p; 69 | } 70 | 71 | strncpy(value, p, i); 72 | value[i] = 0x00; 73 | 74 | return value; 75 | 76 | } -------------------------------------------------------------------------------- /src/isstr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/28. 3 | // 4 | 5 | #ifndef ISSTR_H 6 | #define ISSTR_H 7 | 8 | #define ISSTR_VERSION_NO "20.12.2" 9 | char * isstr_version(); 10 | 11 | char *isstr_trim(char *str); 12 | char *isstr_split(char *str, char * sign, int num, char * value); 13 | 14 | #endif //WRKTCP_ISSTR_H 15 | -------------------------------------------------------------------------------- /src/istime.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/26. 3 | // 4 | /** 5 | uint32_t的长度(10-1位): 4294967296---------- 6 | uint64_t的长度(20-1位): 18446744073709551615 7 | **/ 8 | 9 | #include 10 | #include 11 | 12 | #include "istime.h" 13 | static void nolocks_localtime(time_t *p_t, struct tm *tmp); 14 | 15 | char * istime_version(){ 16 | return ISTIME_VERSION_NO; 17 | } 18 | 19 | /** 获取当前时间,到微秒 **/ 20 | uint64_t istime_us(){ 21 | struct timeval t; 22 | gettimeofday(&t, NULL); 23 | return (t.tv_sec * 1000000) + t.tv_usec; 24 | } 25 | 26 | /** 使用time_us作为输出,调用strftime **/ 27 | 28 | /* 29 | * format格式内容 30 | %a 星期几的简写 %A 星期几的全称 %b 月份的简写 %B 月份的全称 %c 标准的日期的时间串 %C 年份的后两位数字 31 | %d 十进制表示的每月的第几天 %D 月/天/年 %e 在两字符域中,十进制表示的每月的第几天 %F 年-月-日 32 | %g 年份的后两位数字,使用基于周的年 %G 年份,使用基于周的年 %h 简写的月份名 %H 24小时制的小时 33 | %I 12小时制的小时 %j 十进制表示的每年的第几天 %m 十进制表示的月份 %M 十时制表示的分钟数 34 | %n 新行符 %p 本地的AM或PM的等价显示 %r 12小时的时间 %R 显示小时和分钟:hh:mm 35 | %S 十进制的秒数 %t 水平 制表符 %T 显示时分秒:hh:mm:ss %u 每周的第几天,星期一为第一天 (值从1到7,星期一为1) 36 | %U 第年的第几周,把星期日作为第一天(值从0到53) %V 每年的第几周,使用基于周的年 %w 十进制表示的星期几(值从0到6,星期天为0) 37 | %W 每年的第几周,把星期一做为第一天(值从0到53) %x 标准的日期串 %X 标准的时间串 %y 不带世纪的十进制年份(值从0到99) 38 | %Y 带世纪部分的十制年份 %z,%Z 时区名称,如果不能得到时区名称则返回空字符。 %% 百分号 39 | 40 | */ 41 | uint32_t istime_strftime(char * strtime, uint32_t maxsize, const char * format, uint64_t time_us){ 42 | struct timeval t; 43 | t.tv_sec = time_us / 1000000; 44 | //t.tv_usec = time_us % 1000000; 45 | 46 | struct tm local_time; 47 | 48 | /*** TODO 可能会锁,后续改为redis的 nonblock_localtime 写法 ***/ 49 | nolocks_localtime( &t.tv_sec, &local_time); 50 | return strftime(strtime, maxsize, format, &local_time); 51 | } 52 | 53 | /** iso8601格式的 **/ 54 | char * istime_iso8601(char * strtime, uint32_t maxsize, uint64_t time_us){ 55 | char format[]="%Y-%m-%dT%H:%M:%S"; 56 | // 至少 19 位; 57 | // 2020-12-31T12:12:12 58 | istime_strftime( strtime, maxsize, format, time_us); 59 | return strtime; 60 | } 61 | 62 | long istime_longtime(){ 63 | time_t t = time(0); 64 | struct tm local_time; 65 | 66 | nolocks_localtime(&t, &local_time); 67 | return local_time.tm_hour*10000 + local_time.tm_min*100 + local_time.tm_sec; 68 | } 69 | 70 | long istime_longdate(){ 71 | time_t t = time(0); 72 | struct tm local_time; 73 | 74 | nolocks_localtime(&t, &local_time); 75 | return (local_time.tm_year+1900)*10000 + (local_time.tm_mon+1)*100 + local_time.tm_mday; 76 | } 77 | 78 | static int is_leap_year(time_t year) { 79 | if (year % 4) return 0; /* A year not divisible by 4 is not leap. */ 80 | else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */ 81 | else if (year % 400) return 0; /* If div by 100 *and* not by 400 is not leap. */ 82 | else return 1; /* If div by 100 and 400 is leap. */ 83 | } 84 | 85 | /** redis 重写的不会导致死锁的localtime **/ 86 | static void nolocks_localtime(time_t *p_t, struct tm *tmp) { 87 | /*** 在redis源码的基础上,做了修改 ***/ 88 | static int static_istime_tz_set = 0; 89 | int dst = 0; // 不支持夏令时,因为这是中国 90 | if( static_istime_tz_set == 0){ 91 | tzset(); 92 | static_istime_tz_set = 1; 93 | } 94 | 95 | /*** 获取时区信息 ***/ 96 | static unsigned long static_istime_timezone = -999; // 97 | if( static_istime_timezone == -999){ 98 | static_istime_timezone = istime_timezone(); 99 | } 100 | 101 | time_t t = *p_t; 102 | const time_t secs_min = 60; 103 | const time_t secs_hour = 3600; 104 | const time_t secs_day = 3600*24; 105 | 106 | t -= static_istime_timezone; /* Adjust for timezone. */ 107 | t += 3600*dst; /* Adjust for daylight time. */ 108 | time_t days = t / secs_day; /* Days passed since epoch. */ 109 | time_t seconds = t % secs_day; /* Remaining seconds. */ 110 | 111 | tmp->tm_isdst = dst; 112 | tmp->tm_hour = seconds / secs_hour; 113 | tmp->tm_min = (seconds % secs_hour) / secs_min; 114 | tmp->tm_sec = (seconds % secs_hour) % secs_min; 115 | 116 | /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure 117 | * where sunday = 0, so to calculate the day of the week we have to add 4 118 | * and take the modulo by 7. */ 119 | tmp->tm_wday = (days+4)%7; 120 | 121 | /* Calculate the current year. */ 122 | tmp->tm_year = 1970; 123 | while(1) { 124 | /* Leap years have one day more. */ 125 | time_t days_this_year = 365 + is_leap_year(tmp->tm_year); 126 | if (days_this_year > days) break; 127 | days -= days_this_year; 128 | tmp->tm_year++; 129 | } 130 | tmp->tm_yday = days; /* Number of day of the current year. */ 131 | 132 | /* We need to calculate in which month and day of the month we are. To do 133 | * so we need to skip days according to how many days there are in each 134 | * month, and adjust for the leap year that has one more day in February. */ 135 | int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 136 | mdays[1] += is_leap_year(tmp->tm_year); 137 | 138 | tmp->tm_mon = 0; 139 | while(days >= mdays[tmp->tm_mon]) { 140 | days -= mdays[tmp->tm_mon]; 141 | tmp->tm_mon++; 142 | } 143 | 144 | tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */ 145 | tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ 146 | } 147 | 148 | unsigned long istime_timezone(void) { 149 | /**TODO 考虑到系统兼容性,timezone全局变量后续再使用 150 | #if defined(__linux__) || defined(__sun) 151 | printf("timezone\n\n"); 152 | return timezone; 153 | #else 154 | **/ 155 | struct timeval tv; 156 | struct timezone tz; 157 | 158 | gettimeofday(&tv, &tz); 159 | 160 | return tz.tz_minuteswest * 60UL; 161 | //#endif 162 | } 163 | -------------------------------------------------------------------------------- /src/istime.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/26. 3 | // 4 | 5 | #ifndef ISTIME_H 6 | #define ISTIME_H 7 | #include 8 | #include 9 | #include 10 | 11 | #define ISTIME_VERSION_NO "21.08.2" 12 | char * istime_version(); 13 | 14 | /** 取当前时间的毫秒数 **/ 15 | uint64_t istime_us(); 16 | 17 | /** 根据time_us微秒信息,生成ISO8601的标准日期时间格式, 其实秒以下是舍弃的 **/ 18 | char * istime_iso8601(char * strtime, uint32_t maxsize, uint64_t time_us); 19 | 20 | /* 21 | * format格式内容 22 | %a 星期几的简写 %A 星期几的全称 %b 月份的简写 %B 月份的全称 %c 标准的日期的时间串 %C 年份的后两位数字 23 | %d 十进制表示的每月的第几天 %D 月/天/年 %e 在两字符域中,十进制表示的每月的第几天 %F 年-月-日 24 | %g 年份的后两位数字,使用基于周的年 %G 年份,使用基于周的年 %h 简写的月份名 %H 24小时制的小时 25 | %I 12小时制的小时 %j 十进制表示的每年的第几天 %m 十进制表示的月份 %M 十时制表示的分钟数 26 | %n 新行符 %p 本地的AM或PM的等价显示 %r 12小时的时间 %R 显示小时和分钟:hh:mm 27 | %S 十进制的秒数 %t 水平 制表符 %T 显示时分秒:hh:mm:ss %u 每周的第几天,星期一为第一天 (值从1到7,星期一为1) 28 | %U 第年的第几周,把星期日作为第一天(值从0到53) %V 每年的第几周,使用基于周的年 %w 十进制表示的星期几(值从0到6,星期天为0) 29 | %W 每年的第几周,把星期一做为第一天(值从0到53) %x 标准的日期串 %X 标准的时间串 %y 不带世纪的十进制年份(值从0到99) 30 | %Y 带世纪部分的十制年份 %z,%Z 时区名称,如果不能得到时区名称则返回空字符。 %% 百分号 31 | 32 | */ 33 | uint32_t istime_strftime(char * strtime, uint32_t maxsize, const char * format, uint64_t time_us); 34 | 35 | /** 获取当日的时间信息,6位长 **/ 36 | long istime_longtime(); 37 | 38 | /** 获取当日的日期,8位长 **/ 39 | long istime_longdate(); 40 | 41 | /** 获取当前时区的信息,返回秒东八区是-28800(-8*3600) **/ 42 | unsigned long istime_timezone(); 43 | 44 | #endif //ISTIME_H 45 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "version.h" 25 | #include "islog.h" 26 | #include "net.h" 27 | #include "output.h" 28 | #include "tcpini.h" 29 | 30 | #include "aprintf.h" 31 | #include "stats.h" 32 | #include "units.h" 33 | #include "zmalloc.h" 34 | #include "istime.h" 35 | 36 | static void *thread_main(void *); 37 | static int parse_args(config *, int, char **); 38 | static int running_sleep(config * lcfg ); 39 | 40 | static int connect_socket(thread *, connection *); 41 | static int reconnect_socket(thread *, connection *); 42 | static void socket_connected(aeEventLoop *, int, void *, int); 43 | static void socket_writeable(aeEventLoop *, int, void *, int); 44 | static void socket_readable(aeEventLoop *, int, void *, int); 45 | static int response_complete(void * data, char * buf, size_t n); 46 | 47 | static int record_rate(aeEventLoop *, long long, void *); 48 | 49 | 50 | #endif /* MAIN_H */ 51 | -------------------------------------------------------------------------------- /src/net.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "net.h" 8 | #include 9 | #include 10 | #include "islog.h" 11 | 12 | 13 | 14 | status sock_connect(connection *c, char *host) { 15 | return OK; 16 | } 17 | 18 | status sock_close(connection *c) { 19 | return OK; 20 | } 21 | 22 | status sock_read(connection *c, size_t *n) { 23 | ssize_t r = read(c->fd, c->buf, sizeof(c->buf)); 24 | *n = (size_t) r; 25 | if( r < 0){ 26 | islog_error("sock_read error : %s", strerror(errno)); 27 | } 28 | return r >= 0 ? OK : ERROR; 29 | } 30 | 31 | status sock_write(connection *c, char *buf, size_t len, size_t *n) { 32 | ssize_t r; 33 | if ((r = write(c->fd, buf, len)) == -1) { 34 | switch (errno) { 35 | case EAGAIN: return RETRY; 36 | default: 37 | islog_error("sock_write error : %s", strerror(errno)); 38 | return ERROR; 39 | } 40 | } 41 | *n = (size_t) r; 42 | return OK; 43 | } 44 | 45 | size_t sock_readable(connection *c) { 46 | int n, rc; 47 | rc = ioctl(c->fd, FIONREAD, &n); 48 | return rc == -1 ? 0 : n; 49 | } 50 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | #ifndef NET_H 2 | #define NET_H 3 | 4 | #include "config.h" 5 | #include 6 | #include "wrktcp.h" 7 | 8 | typedef enum { 9 | OK, 10 | ERROR, 11 | RETRY 12 | } status; 13 | 14 | struct sock { 15 | status ( *connect)(connection *, char *); 16 | status ( *close)(connection *); 17 | status ( *read)(connection *, size_t *); 18 | status ( *write)(connection *, char *, size_t, size_t *); 19 | size_t (*readable)(connection *); 20 | }; 21 | 22 | status sock_connect(connection *, char *); 23 | status sock_close(connection *); 24 | status sock_read(connection *, size_t *); 25 | status sock_write(connection *, char *, size_t, size_t *); 26 | size_t sock_readable(connection *); 27 | 28 | #endif /* NET_H */ 29 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/26. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "units.h" 13 | #include "output.h" 14 | #include "islog.h" 15 | #include "istime.h" 16 | #include "outputhtml.h" 17 | 18 | static void print_stats_header() { 19 | printf(" Thread Stats%6s%11s%8s%12s\n", "Avg", "Stdev", "Max", "+/- Stdev"); 20 | } 21 | 22 | static void print_units(long double n, char *(*fmt)(long double), int width) { 23 | char *msg = fmt(n); 24 | int len = strlen(msg), pad = 2; 25 | 26 | if (isalpha(msg[len-1])) pad--; 27 | if (isalpha(msg[len-2])) pad--; 28 | width -= pad; 29 | 30 | printf("%*.*s%.*s", width, width, msg, pad, " "); 31 | 32 | free(msg); 33 | } 34 | 35 | static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) { 36 | uint64_t max = stats->max; 37 | long double mean = stats_mean(stats); 38 | long double stdev = stats_stdev(stats, mean); 39 | 40 | printf(" %-10s", name); 41 | print_units(mean, fmt, 8); 42 | print_units(stdev, fmt, 10); 43 | print_units(max, fmt, 9); 44 | printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1)); 45 | } 46 | 47 | static void print_stats_latency(stats *stats) { 48 | long double percentiles[] = { 50.0, 75.0, 90.0, 99.0 }; 49 | printf(" Latency Distribution\n"); 50 | for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) { 51 | long double p = percentiles[i]; 52 | uint64_t n = stats_percentile(stats, p); 53 | printf("%7.0Lf%%", p); 54 | print_units(n, format_time_us, 10); 55 | printf("\n"); 56 | } 57 | } 58 | 59 | /** 60 | * output_console 输出结果到控制台上 61 | * @param lcfg 62 | * @return 63 | */ 64 | 65 | int output_console( config * lcfg){ 66 | errors * errors = &lcfg->result.errors; 67 | int64_t complete = lcfg->result.complete; 68 | int64_t bytes = lcfg->result.bytes; 69 | 70 | /** 输出分布统计信息头 **/ 71 | print_stats_header(); 72 | /* 缩短了10倍 */ 73 | print_stats("Latency", lcfg->statistics.latency, format_time_us); 74 | print_stats("Req/Sec", lcfg->statistics.requests, format_metric10); 75 | if (lcfg->islatency) print_stats_latency(lcfg->statistics.latency); 76 | 77 | char *runtime_msg = format_time_us(lcfg->result.runtime_us); 78 | 79 | printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); 80 | if (errors->connect || errors->read || errors->write || errors->timeout) { 81 | printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", 82 | errors->connect, errors->read, errors->write, errors->timeout); 83 | } 84 | 85 | if (errors->status) { 86 | printf(" Failure responses: %d\n", errors->status); 87 | } 88 | 89 | printf("Requests/sec: %9.2Lf ", lcfg->result.req_per_s); 90 | /** 新增失败的统计 **/ 91 | printf(" (Success:%.2Lf/", lcfg->result.req_success_per_s); 92 | printf("Failure:%.2Lf)\n", lcfg->result.req_fail_per_s); 93 | printf("Transfer/sec: %10sB\n", format_binary(lcfg->result.bytes_per_s)); 94 | 95 | /** 新增跟踪趋势统计 **/ 96 | if( lcfg->istrace){ 97 | int i = 0; 98 | printf(" Trace Details:\n"); 99 | printf(" Time Tps Latency\n"); 100 | for( i = 0; i < TRACE_MAX_POINT; i++ ){ 101 | if( lcfg->trace.tps[i] >= -0.005 && lcfg->trace.tps[i] <= 0.005){ 102 | break; 103 | } 104 | printf(" %4"PRIu64"s", i*lcfg->trace.step_time +1); 105 | printf(" %9.2lf", lcfg->trace.tps[i]); 106 | printf(" %10s", format_time_us(lcfg->trace.latency[i])); 107 | printf("\n"); 108 | } 109 | } 110 | return 0; 111 | } 112 | 113 | /** 114 | * output_html 输出到html文件中 115 | * @param lcfg 116 | * @return 117 | */ 118 | int output_html( config * lcfg){ 119 | FILE *fp = NULL; 120 | if( strlen(lcfg->htmlfile) == 0){ 121 | strcpy( lcfg->htmlfile, lcfg->tcpini.file); 122 | sprintf( strstr( lcfg->htmlfile, ".ini"), ".%08ld%06ld.html", istime_longdate(), istime_longtime()); 123 | } 124 | printf("Result html : %s\n", lcfg->htmlfile); 125 | 126 | fp = fopen( lcfg->htmlfile, "w"); 127 | if( fp == NULL){ 128 | islog_error(" open htmlfile error: %s", strerror(errno)); 129 | return -5; 130 | } 131 | fprintf(fp, "%s", g_wrk_output_html_head); 132 | /*** 表头数据 ***/ 133 | #if 0 134 | " tableData: [\n" 135 | " '1',\n" 136 | " '136服务器',\n" 137 | " '60S',\n" 138 | " '100',\n" 139 | " '1.245',\n" 140 | " '0.875',\n" 141 | " '0.763',\n" 142 | " '120',\n" 143 | " '130',\n" 144 | " '150',\n" 145 | " ]\n"; 146 | #endif 147 | fprintf(fp," tableData: [\n"); 148 | fprintf(fp," '%s', \n", "No.1"); 149 | fprintf(fp," '%s', \n", lcfg->tcpini.file); 150 | fprintf(fp," '%"PRIu64"s', \n", lcfg->duration); 151 | fprintf(fp," '%"PRIu64"', \n", lcfg->connections); 152 | /** tps 信息 **/ 153 | fprintf(fp," '%.2Lf', \n", lcfg->result.tps_min); 154 | fprintf(fp," '%.2Lf', \n", lcfg->result.req_success_per_s); 155 | fprintf(fp," '%.2Lf', \n", lcfg->result.tps_max); 156 | /** 响应时间信息 **/ 157 | fprintf(fp," '%s', \n", format_time_us(lcfg->statistics.latency->min)); 158 | fprintf(fp," '%s', \n", format_time_us(stats_mean(lcfg->statistics.latency))); 159 | fprintf(fp," '%s', \n", format_time_us(lcfg->statistics.latency->max)); 160 | fprintf(fp," ],\n"); 161 | /*** 横坐标轴,时间 ***/ 162 | #if 0 163 | " sendTime: [\n" 164 | " '00:00:00',\n" 165 | " '00:00:10',\n" 166 | " '00:00:20',\n" 167 | " '00:00:30',\n" 168 | " '00:00:40',\n" 169 | " '00:00:50',\n" 170 | " '00:01:00',\n" 171 | " '00:01:10',\n" 172 | " '00:01:20',\n" 173 | " '00:01:30',\n" 174 | " '00:01:40',\n" 175 | " '00:01:50',\n" 176 | " '00:02:00',\n" 177 | " '00:02:10',\n" 178 | " '00:02:20',\n" 179 | " '00:02:30',\n" 180 | " '00:02:40',\n" 181 | " '00:02:50',\n" 182 | " '00:03:00',\n" 183 | " ],\n" 184 | #endif 185 | fprintf(fp," sendTime: [\n"); 186 | uint64_t i = 0; 187 | for ( i = 0; i < 100; i++){ 188 | fprintf(fp," '%"PRIu64"s',\n", i*lcfg->trace.step_time + 1); 189 | } 190 | fprintf(fp," ],\n"); 191 | /** tps数据 **/ 192 | #if 0 193 | " tpsData: [\n" 194 | " '700',\n" 195 | " '800',\n" 196 | " '850',\n" 197 | " '900',\n" 198 | " '1110',\n" 199 | " '990',\n" 200 | " '950',\n" 201 | " '1000',\n" 202 | " '820',\n" 203 | " '970',\n" 204 | " '880',\n" 205 | " '990',\n" 206 | " '830',\n" 207 | " '770',\n" 208 | " '800',\n" 209 | " '720',\n" 210 | " '810',\n" 211 | " '700',\n" 212 | " '840',\n" 213 | " ],\n" 214 | #endif 215 | fprintf(fp," tpsData: [\n"); 216 | for ( i = 0; i < 100; i++){ 217 | fprintf(fp," '%.2lf',\n", lcfg->trace.tps[i]); 218 | } 219 | fprintf(fp," ],\n"); 220 | /** latency 数据 **/ 221 | #if 0 222 | " resTimeData: [\n" 223 | " '0.7',\n" 224 | " '0.8',\n" 225 | " '0.85',\n" 226 | " '0.9',\n" 227 | " '0.66',\n" 228 | " '0.99',\n" 229 | " '0.95',\n" 230 | " '0.78',\n" 231 | " '0.82',\n" 232 | " '0.97',\n" 233 | " '0.88',\n" 234 | " '0.99',\n" 235 | " '0.83',\n" 236 | " '0.77',\n" 237 | " '0.8',\n" 238 | " '0.72',\n" 239 | " '0.81',\n" 240 | " '0.7',\n" 241 | " '0.84',\n" 242 | " ],\n" 243 | #endif 244 | fprintf(fp," resTimeData: [\n"); 245 | for ( i = 0; i < 100; i++){ 246 | fprintf(fp," '%.2lf',\n", lcfg->trace.latency[i]/(double)1000); 247 | } 248 | fprintf(fp," ]\n"); 249 | 250 | /** 打印结尾 **/ 251 | fprintf(fp, "%s", g_wrk_output_html_tail); 252 | fclose(fp); 253 | return 0; 254 | }; -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/26. 3 | // 4 | 5 | #ifndef WRKTCP_OUTPUT_H 6 | #define WRKTCP_OUTPUT_H 7 | 8 | #include "wrktcp.h" 9 | 10 | int output_console(config * lcfg); 11 | int output_html(config * lcfg); 12 | 13 | #endif //WRKTCP_OUTPUT_H 14 | -------------------------------------------------------------------------------- /src/stats.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesky1stm/wrktcp/af4c5215117416dee70cfa0424e61231da349f71/src/stats.c -------------------------------------------------------------------------------- /src/stats.h: -------------------------------------------------------------------------------- 1 | #ifndef STATS_H 2 | #define STATS_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) 8 | #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) 9 | 10 | typedef struct { 11 | uint32_t connect; 12 | uint32_t read; 13 | uint32_t write; 14 | uint32_t status; 15 | uint32_t timeout; 16 | } errors; 17 | 18 | typedef struct { 19 | uint64_t count; 20 | uint64_t limit; 21 | uint64_t min; 22 | uint64_t max; 23 | uint64_t data[]; 24 | } stats; 25 | 26 | stats *stats_alloc(uint64_t); 27 | void stats_free(stats *); 28 | 29 | int stats_record(stats *, uint64_t); 30 | void stats_correct(stats *, int64_t); 31 | 32 | long double stats_mean(stats *); 33 | long double stats_stdev(stats *stats, long double); 34 | long double stats_within_stdev(stats *, long double, long double, uint64_t); 35 | uint64_t stats_percentile(stats *, long double); 36 | 37 | uint64_t stats_popcount(stats *); 38 | uint64_t stats_value_at(stats *stats, uint64_t, uint64_t *); 39 | 40 | #endif /* STATS_H */ 41 | -------------------------------------------------------------------------------- /src/tcpini.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesky1stm/wrktcp/af4c5215117416dee70cfa0424e61231da349f71/src/tcpini.h -------------------------------------------------------------------------------- /src/units.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "units.h" 9 | #include "aprintf.h" 10 | 11 | typedef struct { 12 | int scale; 13 | char *base; 14 | char *units[]; 15 | } units; 16 | 17 | units time_units_us = { 18 | .scale = 1000, 19 | .base = "us", 20 | .units = { "ms", "s", NULL } 21 | }; 22 | 23 | units time_units_s = { 24 | .scale = 60, 25 | .base = "s", 26 | .units = { "m", "h", NULL } 27 | }; 28 | 29 | units binary_units = { 30 | .scale = 1024, 31 | .base = "", 32 | .units = { "K", "M", "G", "T", "P", NULL } 33 | }; 34 | 35 | units metric_units = { 36 | .scale = 1000, 37 | .base = "", 38 | .units = { "k", "M", "G", "T", "P", NULL } 39 | }; 40 | 41 | static char *format_units(long double n, units *m, int p) { 42 | long double amt = n, scale; 43 | char *unit = m->base; 44 | char *msg = NULL; 45 | 46 | scale = m->scale * 0.85; 47 | 48 | for (int i = 0; m->units[i+1] && amt >= scale; i++) { 49 | amt /= m->scale; 50 | unit = m->units[i]; 51 | } 52 | 53 | aprintf(&msg, "%.*Lf%s", p, amt, unit); 54 | 55 | return msg; 56 | } 57 | 58 | static int scan_units(char *s, uint64_t *n, units *m) { 59 | uint64_t base, scale = 1; 60 | char unit[3] = { 0, 0, 0 }; 61 | int i, c; 62 | 63 | if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1; 64 | 65 | if (c == 2 && strncasecmp(unit, m->base, 3)) { 66 | for (i = 0; m->units[i] != NULL; i++) { 67 | scale *= m->scale; 68 | if (!strncasecmp(unit, m->units[i], 3)) break; 69 | } 70 | if (m->units[i] == NULL) return -1; 71 | } 72 | 73 | *n = base * scale; 74 | return 0; 75 | } 76 | 77 | char *format_binary(long double n) { 78 | return format_units(n, &binary_units, 2); 79 | } 80 | 81 | char *format_metric(long double n) { 82 | return format_units(n, &metric_units, 2); 83 | } 84 | 85 | char *format_metric10(long double n) { 86 | return format_units(n/10, &metric_units, 2); 87 | } 88 | 89 | char *format_time_us(long double n) { 90 | units *units = &time_units_us; 91 | if (n >= 1000000.0) { 92 | n /= 1000000.0; 93 | units = &time_units_s; 94 | } 95 | return format_units(n, units, 2); 96 | } 97 | 98 | char *format_time_s(long double n) { 99 | return format_units(n, &time_units_s, 0); 100 | } 101 | 102 | int scan_metric(char *s, uint64_t *n) { 103 | return scan_units(s, n, &metric_units); 104 | } 105 | 106 | int scan_time(char *s, uint64_t *n) { 107 | return scan_units(s, n, &time_units_s); 108 | } 109 | -------------------------------------------------------------------------------- /src/units.h: -------------------------------------------------------------------------------- 1 | #ifndef UNITS_H 2 | #define UNITS_H 3 | 4 | char *format_binary(long double); 5 | char *format_metric(long double); 6 | char *format_metric10(long double); 7 | char *format_time_us(long double); 8 | char *format_time_s(long double); 9 | 10 | int scan_metric(char *, uint64_t *); 11 | int scan_time(char *, uint64_t *); 12 | 13 | #endif /* UNITS_H */ 14 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by suitm on 2020/12/15. 3 | // 4 | 5 | #ifndef WRKTCP_VERSION_H 6 | #define WRKTCP_VERSION_H 7 | 8 | #define WRKVERSION "1.2.1" 9 | 10 | #define AUTHOR "icesky" 11 | 12 | #endif //WRKTCP_VERSION_H 13 | -------------------------------------------------------------------------------- /src/wrktcp.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include "main.h" 4 | #include "wrktcp.h" 5 | 6 | /** wrk的整体汇总数据 **/ 7 | config cfg; 8 | 9 | /** wrk这么写看起来像对象的属性,毫无意义 **/ 10 | static struct sock sock = { 11 | .connect = sock_connect, 12 | .close = sock_close, 13 | .read = sock_read, 14 | .write = sock_write, 15 | .readable = sock_readable 16 | }; 17 | 18 | /** ctrl +c 打断信号 **/ 19 | static volatile sig_atomic_t stop = 0; 20 | static void handler(int sig) { 21 | stop = 1; 22 | } 23 | 24 | /** 使用说明 **/ 25 | static void usage() { 26 | printf("Usage: wrktcp filename \n" 27 | " Necessary Options: \n" 28 | " -c, --connections Connections to keep open \n" 29 | " -d, --duration Duration of test \n" 30 | " -t, --threads Number of threads to use \n" 31 | 32 | " Condition Options: \n" 33 | " --timeout Socket/request timeout \n" 34 | " --latency Print latency statistics \n" 35 | " --trace Print tps/latency trace \n" 36 | " --html output a html chart \n" 37 | " --test just run once to test ini \n" 38 | 39 | " -v, --version Print version details \n" 40 | " \n" 41 | " Numeric arguments may include a SI unit (1k, 1M, 1G)\n" 42 | " Time arguments may include a time unit (2s, 2m, 2h) \n" 43 | " for example: \n" 44 | " wrktcp -t4 -c200 -d30s sample_tiny.ini \n" 45 | ); 46 | } 47 | 48 | /********************************************************* 49 | * wrktcp 主程序 50 | * @param argc 51 | * @param argv 52 | * @return 53 | *********************************************************/ 54 | int main(int argc, char **argv) { 55 | /** 设置信号忽略 **/ 56 | signal(SIGPIPE, SIG_IGN); 57 | signal(SIGINT, SIG_IGN); 58 | 59 | /** 解析入口参数 **/ 60 | if (parse_args(&cfg, argc, argv)) { 61 | islog_error("argv is errror!!!"); 62 | usage(); 63 | exit(1); 64 | } 65 | 66 | /** 设置统计信息初始值 **/ 67 | cfg.statistics.latency = stats_alloc(cfg.timeout * 1000); 68 | cfg.statistics.requests = stats_alloc(MAX_THREAD_RATE_S); 69 | cfg.p_threads = zcalloc(cfg.threads * sizeof(thread)); 70 | 71 | /** 解析tcpinifile文件 **/ 72 | if(tcpini_file_load(cfg.tcpini.file, &cfg.tcpini) != 0){ 73 | fprintf(stderr, "unable to load tcpinifile %s\n",cfg.tcpini.file); 74 | exit(1); 75 | } 76 | 77 | /** 开始创建线程 **/ 78 | for (uint64_t i = 0; i < cfg.threads; i++) { 79 | thread *t = &cfg.p_threads[i]; 80 | t->tno = i + 1; 81 | t->loop = aeCreateEventLoop(10 + cfg.connections * 3); 82 | t->connections = cfg.connections / cfg.threads; 83 | t->lcfg = &cfg; 84 | t->tcpini = &cfg.tcpini; 85 | if (i == 0) { 86 | /* 管道发送,目前一次连接一次通讯 */ 87 | cfg.pipeline = 1; 88 | /* 动态模板 */ 89 | if(cfg.tcpini.paras_pos != 0){ 90 | cfg.isdynamic = 1; 91 | } 92 | /* 延迟发送,暂不支持 */ 93 | cfg.isdelay = 0; 94 | } 95 | 96 | if (!t->loop || pthread_create(&t->thread, NULL, &thread_main, t)) { 97 | char *msg = strerror(errno); 98 | fprintf(stderr, "unable to create thread %"PRIu64": %s\n", i, msg); 99 | exit(2); 100 | } 101 | } 102 | 103 | /** 设置ctrl+c打断信号 **/ 104 | struct sigaction sa = { 105 | .sa_handler = handler, 106 | .sa_flags = 0, 107 | }; 108 | sigfillset(&sa.sa_mask); 109 | sigaction(SIGINT, &sa, NULL); 110 | 111 | /** 打印输出头 **/ 112 | printf("Running %s loadtest ", format_time_s(cfg.duration)); 113 | printf("@ %s:%d ", cfg.tcpini.host, cfg.tcpini.port); 114 | printf("using %s", cfg.tcpini.file); 115 | printf("\n"); 116 | printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); 117 | if( cfg.istest == 1){ 118 | printf("\n----TEST MODE, connect once and more log----\n"); 119 | } 120 | 121 | /** 初始化基本信息 **/ 122 | cfg.result.tm_start = istime_us(); 123 | cfg.result.complete = 0; 124 | cfg.result.bytes = 0; 125 | memset( &cfg.result.errors, 0x00, sizeof(errors)); 126 | 127 | /** 主进程等待后并设置停止标志 **/ 128 | running_sleep( &cfg); 129 | stop = 1; 130 | 131 | /** 开始记录统计结果信息 **/ 132 | for (uint64_t j = 0; j < cfg.threads; j++) { 133 | thread *t = &cfg.p_threads[j]; 134 | /* 挨个线程等待 */ 135 | pthread_join(t->thread, NULL); 136 | 137 | cfg.result.complete += t->complete; 138 | cfg.result.bytes += t->bytes; 139 | 140 | cfg.result.errors.connect += t->errors.connect; 141 | cfg.result.errors.read += t->errors.read; 142 | cfg.result.errors.write += t->errors.write; 143 | cfg.result.errors.timeout += t->errors.timeout; 144 | cfg.result.errors.status += t->errors.status; 145 | } 146 | 147 | cfg.result.tm_end = istime_us(); 148 | cfg.result.runtime_us = cfg.result.tm_end - cfg.result.tm_start; 149 | cfg.result.runtime_s = cfg.result.runtime_us / 1000000.0; 150 | cfg.result.req_per_s = cfg.result.complete / cfg.result.runtime_s; 151 | cfg.result.req_success_per_s = (cfg.result.complete-cfg.result.errors.status) / cfg.result.runtime_s; 152 | cfg.result.req_fail_per_s = cfg.result.errors.status / cfg.result.runtime_s; 153 | cfg.result.bytes_per_s = cfg.result.bytes / cfg.result.runtime_s; 154 | 155 | /** Coordinated Omission 状态修正, 我认为是瞎修正应该使用wrk2的方法,但是为了保持和wrk的结果一致暂时保留 **/ 156 | if (cfg.result.complete / cfg.connections > 0) { 157 | /* 计算一次连接平均延时多少 */ 158 | int64_t interval = cfg.result.runtime_us / (cfg.result.complete / cfg.connections); 159 | /* 进行修正,应该学习wrk2,增加-R参数 */ 160 | stats_correct(cfg.statistics.latency, interval); 161 | } 162 | 163 | if( cfg.istest == 1){ 164 | printf("----TEST MODE, connect once and more log----\n\n"); 165 | } 166 | /** 输出到屏幕上 **/ 167 | output_console(&cfg); 168 | 169 | /** 输出到html文件中 **/ 170 | if( cfg.ishtml ){ 171 | output_html(&cfg); 172 | } 173 | 174 | return 0; 175 | } 176 | 177 | /** 多线程的主程序,使用epoll创建和管理多个连接 **/ 178 | void *thread_main(void *arg) { 179 | thread *thread = arg; 180 | 181 | /** 如果不是动态模板,则只初始化一次发送信息,提高效率 **/ 182 | char *request = NULL; 183 | long length = 0; 184 | if ( !thread->lcfg->isdynamic ) { 185 | if(tcpini_request_parser(thread->tcpini, &request, &length, NULL) != 0){ 186 | fprintf(stderr, "parser ini content error !!!"); 187 | return NULL; 188 | } 189 | } 190 | 191 | thread->cs = zcalloc(thread->connections * sizeof(connection)); 192 | connection *c = thread->cs; 193 | 194 | for (uint64_t i = 0; i < thread->connections; i++, c++) { 195 | c->thread = thread; 196 | c->cno = thread->tno * 10000 + i + 1; 197 | c->request = request; 198 | c->length = length; 199 | c->delayed = thread->lcfg->isdelay; 200 | c->tcpini = thread->tcpini; 201 | c->lcfg = thread->lcfg; 202 | islog_debug("CREATE CONNECTION : cno = %ld", c->cno); 203 | if( c->tcpini->ishttp){ 204 | /* http结构体 */ 205 | http_parser_init(&c->http_parser, HTTP_RESPONSE); 206 | c->http_parser.data = c; 207 | } 208 | 209 | connect_socket(thread, c); 210 | } 211 | 212 | aeEventLoop *loop = thread->loop; 213 | aeCreateTimeEvent(loop, RECORD_INTERVAL_MS, record_rate, thread, NULL); 214 | 215 | thread->start = istime_us(); 216 | aeMain(loop); 217 | 218 | aeDeleteEventLoop(loop); 219 | zfree(thread->cs); 220 | 221 | return NULL; 222 | } 223 | 224 | static int connect_socket(thread *thread, connection *c) { 225 | islog_debug("CONNECTING cno:%ld", c->cno); 226 | islog_test("CONNECTING:[%s:%d],cno[%ld]", c->tcpini->host, c->tcpini->port, c->cno); 227 | 228 | struct sockaddr_in sin ; 229 | 230 | memset(&sin, 0x00, sizeof(struct sockaddr_in)); 231 | sin.sin_family = AF_INET; 232 | sin.sin_port = htons(c->tcpini->port); 233 | sin.sin_addr.s_addr = inet_addr(c->tcpini->host); 234 | memset(&(sin.sin_zero),0x00, 8); 235 | 236 | struct aeEventLoop *loop = thread->loop; 237 | int fd, flags; 238 | 239 | fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 240 | 241 | flags = fcntl(fd, F_GETFL, 0); 242 | fcntl(fd, F_SETFL, flags | O_NONBLOCK); 243 | 244 | if (connect(fd, ( struct sockaddr *) & sin, sizeof(sin)) == -1) { 245 | if (errno != EINPROGRESS) goto error; 246 | } 247 | 248 | flags = 1; 249 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)); 250 | 251 | flags = AE_READABLE | AE_WRITABLE; 252 | if (aeCreateFileEvent(loop, fd, flags, socket_connected, c) == AE_OK) { 253 | // c->parser.data = c; 254 | c->fd = fd; 255 | return fd; 256 | } 257 | 258 | error: 259 | thread->errors.connect++; 260 | close(fd); 261 | return -1; 262 | } 263 | 264 | static int reconnect_socket(thread *thread, connection *c) { 265 | islog_debug("RECONNECTING cno:%ld", c->cno); 266 | aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE); 267 | sock.close(c); 268 | close(c->fd); 269 | int ret = 0; 270 | if (stop != 1){ 271 | /*** v1.1修改 ***/ 272 | if( c->lcfg->istest == 1) { 273 | /* 如果是测试场景的话 */ 274 | islog_test("TESTMODE, THEN STOP,cno[%ld]", c->cno); 275 | stop = 1; 276 | }else{ 277 | ret = connect_socket(thread, c); 278 | } 279 | } 280 | return ret; 281 | } 282 | 283 | static int record_rate(aeEventLoop *loop, long long id, void *data) { 284 | thread *thread = data; 285 | 286 | if (thread->requests > 0) { 287 | /** 此处修改了统计方式,wrk的方式是按TPS为整数统计,最大是1000Wtps, 288 | * 这样会产生两个问题: 289 | * 1.10000000 * uint64 = 80M的内存占用 290 | * 2.如果TPS较低,比如响应时间在100ms以上的系统,基本上TPS统计非常的不准确,因为刷新间隔是100MS,导致大量TPS分布都是0 291 | * 这里TPS精度从1->0.1,为了不增加内存消耗,依旧使用1000W个分布,这样相当于缩减了最大TPS的支持到100W TPS 292 | * **/ 293 | uint64_t elapsed_ms = (istime_us() - thread->start) / 1000; 294 | uint64_t requests = (thread->requests * 10 / (double) elapsed_ms) * 1000; 295 | 296 | stats_record(cfg.statistics.requests, requests); 297 | 298 | thread->requests = 0; 299 | thread->start = istime_us(); 300 | } 301 | 302 | if (stop) aeStop(loop); 303 | 304 | return RECORD_INTERVAL_MS; 305 | } 306 | 307 | static int delay_request(aeEventLoop *loop, long long id, void *data) { 308 | connection *c = data; 309 | c->delayed = false; 310 | aeCreateFileEvent(loop, c->fd, AE_WRITABLE, socket_writeable, c); 311 | return AE_NOMORE; 312 | } 313 | 314 | static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) { 315 | connection *c = data; 316 | islog_debug("ALREADY CONNECTED cno:%ld", c->cno); 317 | islog_test("CONNECTED:[%s:%d],cno[%ld]", c->tcpini->host, c->tcpini->port, c->cno); 318 | 319 | switch (sock.connect(c, c->tcpini->host)) { 320 | case OK: break; 321 | case ERROR: goto error; 322 | case RETRY: return; 323 | } 324 | 325 | /** 初始化接收的信息 **/ 326 | if(tcpini_response_init(c) != 0){ 327 | islog_error(" response init error"); 328 | goto error; 329 | } 330 | c->written = 0; 331 | 332 | aeCreateFileEvent(c->thread->loop, fd, AE_READABLE, socket_readable, c); 333 | aeCreateFileEvent(c->thread->loop, fd, AE_WRITABLE, socket_writeable, c); 334 | 335 | return; 336 | 337 | error: 338 | c->thread->errors.connect++; 339 | reconnect_socket(c->thread, c); 340 | } 341 | 342 | static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) { 343 | connection *c = data; 344 | thread *thread = c->thread; 345 | islog_debug("WRITEABLE cno:%ld", c->cno); 346 | 347 | if (c->delayed) { 348 | // uint64_t delay = script_delay(thread->L); 349 | uint64_t delay = 1000; 350 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 351 | aeCreateTimeEvent(loop, delay, delay_request, c, NULL); 352 | return; 353 | } 354 | 355 | /** 等于0的时候,就是要发送的时候 **/ 356 | if (!c->written) { 357 | /*** 如果是动态模板,则每次写的时候都初始化,效率不高 ***/ 358 | if (c->lcfg->isdynamic) { 359 | if(tcpini_request_parser( c->tcpini, &c->request, &c->length, c) != 0){ 360 | fprintf(stderr, "writer parser ini content error !!!\n"); 361 | exit(1); 362 | // goto error; 363 | } 364 | } 365 | c->start = istime_us(); 366 | c->pending = c->lcfg->pipeline; 367 | } 368 | 369 | char *buf = c->request + c->written; 370 | size_t len = c->length - c->written; 371 | size_t n; 372 | 373 | switch (sock.write(c, buf, len, &n)) { 374 | case OK: break; 375 | case ERROR: goto error; 376 | case RETRY: return; 377 | } 378 | 379 | c->written += n; 380 | islog_debug("send msg [%s][%d]", buf, c->written); 381 | islog_test("SENDMSG:[%s][%d],cno[%ld]", buf, c->written, c->cno); 382 | 383 | /** 如果发送到了长度,则删除事件,等读取完成再注册 **/ 384 | if (c->written == c->length) { 385 | c->written = 0; 386 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 387 | } 388 | 389 | return; 390 | 391 | error: 392 | thread->errors.write++; 393 | reconnect_socket(thread, c); 394 | } 395 | 396 | static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) { 397 | connection *c = data; 398 | islog_debug("READABLE cno:%ld", c->cno); 399 | size_t n; 400 | 401 | do { 402 | switch (sock.read(c, &n)) { 403 | case OK: break; 404 | case ERROR: goto error; 405 | case RETRY: return; 406 | } 407 | islog_debug("read buf is [%s]", c->buf); 408 | islog_test("WHILE READ:[%s],cno[%ld]", c->buf, c->cno); 409 | 410 | if(tcpini_response_parser(c, c->buf, n) != 0){ 411 | islog_error("tcpini_response_parser error"); 412 | goto error; 413 | } 414 | 415 | /** 如果报文处理完成了,则进行统计等处理 **/ 416 | if( response_complete( c, c->buf, n) != 0){ 417 | islog_error("response_complete error"); 418 | goto error; 419 | } 420 | 421 | // if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error; 422 | // if (n == 0 && !http_body_is_final(&c->parser)) goto error; 423 | 424 | c->thread->bytes += n; 425 | } while (n == RECVBUF && sock.readable(c) > 0); 426 | 427 | return; 428 | 429 | error: 430 | c->thread->errors.read++; 431 | reconnect_socket(c->thread, c); 432 | } 433 | 434 | static struct option longopts[] = { 435 | { "threads", required_argument, NULL, 't' }, 436 | { "connections", required_argument, NULL, 'c' }, 437 | { "duration", required_argument, NULL, 'd' }, 438 | { "latency", no_argument, NULL, 'L' }, 439 | { "timeout", required_argument, NULL, 'T' }, 440 | { "test", no_argument, NULL, 'I' }, 441 | { "trace", no_argument, NULL, 'S' }, 442 | { "html", optional_argument, NULL, 'H' }, 443 | { "help", no_argument, NULL, 'h' }, 444 | { "version", no_argument, NULL, 'v' }, 445 | { NULL, 0, NULL, 0 } 446 | }; 447 | 448 | static int parse_args(config * lcfg, int argc, char **argv) { 449 | int c; 450 | 451 | memset(lcfg, 0, sizeof(config)); 452 | lcfg->threads = 1; 453 | lcfg->connections = 1; 454 | lcfg->duration = 10; 455 | lcfg->timeout = SOCKET_TIMEOUT_MS; 456 | 457 | while ((c = getopt_long(argc, argv, "t:c:d:H:T:LIrv?", longopts, NULL)) != -1) { 458 | switch (c) { 459 | case 't': 460 | if (scan_metric(optarg, &lcfg->threads)) return -1; 461 | break; 462 | case 'c': 463 | if (scan_metric(optarg, &lcfg->connections)) return -1; 464 | break; 465 | case 'd': 466 | if (scan_time(optarg, &lcfg->duration)) return -1; 467 | break; 468 | case 'L': 469 | lcfg->islatency = 1; 470 | break; 471 | case 'T': 472 | if (scan_time(optarg, &lcfg->timeout)) return -1; 473 | lcfg->timeout *= 1000; 474 | break; 475 | case 'S': 476 | lcfg->istrace = 1; 477 | break; 478 | case 'H': 479 | lcfg->ishtml = 1; 480 | if( optarg != NULL){ 481 | strcpy( lcfg->htmlfile, optarg); 482 | } 483 | break; 484 | case 'I': 485 | lcfg->istest = 1; 486 | break; 487 | case 'v': 488 | printf("wrktcp version %s [%s] ", WRKVERSION, aeGetApiName()); 489 | printf("Copyright (C) by [%s]\n", AUTHOR); 490 | break; 491 | case 'h': 492 | case '?': 493 | case ':': 494 | default: 495 | printf("command is error, input the illegal character:[%c]\n", c); 496 | return -1; 497 | } 498 | } 499 | 500 | if (optind == argc || !lcfg->threads || !lcfg->duration) return -1; 501 | 502 | strcpy( lcfg->tcpini.file, argv[optind]); 503 | /* 检查压测脚本是否符合要求 */ 504 | if(strlen( lcfg->tcpini.file) >= TCPINIFILE_MAX_LENGTH || strlen(lcfg->tcpini.file) <= 0){ 505 | fprintf(stderr, "filename's length is not allow [%ld]\n", strlen( lcfg->tcpini.file)); 506 | return -1; 507 | } 508 | if( strstr( lcfg->tcpini.file, ".ini") == NULL){ 509 | fprintf( stderr, "tcpini file must be ended with .ini,[%s]", lcfg->tcpini.file); 510 | return -1; 511 | } 512 | 513 | /* 检查连接和线程数的关系 */ 514 | if (!lcfg->connections || lcfg->connections < lcfg->threads) { 515 | fprintf(stderr, "number of connections must be >= threads\n"); 516 | return -1; 517 | } 518 | /* 检查htmlfile大小 */ 519 | if( strlen( lcfg->htmlfile) > MAX_HTML_FILELEN){ 520 | fprintf(stderr, "output html filename[%s] is too long. \n", lcfg->htmlfile); 521 | return -1; 522 | } 523 | 524 | return 0; 525 | } 526 | 527 | /* 执行和等待 */ 528 | static int running_sleep( struct config * lcfg ){ 529 | uint64_t i ; 530 | uint64_t complete = 0; 531 | uint64_t failure = 0; 532 | uint64_t bytes = 0; 533 | uint64_t error = 0; 534 | 535 | lcfg->trace.step_time = lcfg->duration / TRACE_MAX_POINT; 536 | if( lcfg->duration % TRACE_MAX_POINT > 0 ){ 537 | lcfg->trace.step_time += 1; 538 | } 539 | /*** 开始等待lcfg->duration秒 ***/ 540 | for( i =1; i <= lcfg->duration; i++){ 541 | /** 收到打断信号, 则直接退出 **/ 542 | if( stop == 1 ){ 543 | lcfg->duration = (istime_us() - lcfg->result.tm_start) / 1000000 + 1; 544 | printf("\n"); 545 | break; 546 | } 547 | /** 每秒刷新一次 **/ 548 | sleep(1); 549 | 550 | /** 开始统计结果信息 **/ 551 | complete = 0; 552 | failure = 0; 553 | bytes = 0; 554 | error = 0; 555 | for (int64_t j = 0; j < lcfg->threads; j++) { 556 | thread *t = &lcfg->p_threads[j]; 557 | complete += t->complete; 558 | failure += t->errors.status; 559 | error += t->errors.connect + t->errors.read + t->errors.write + t->errors.timeout; 560 | bytes += t->bytes; 561 | } 562 | 563 | uint64_t runtime_us = istime_us() - lcfg->result.tm_start; 564 | long double runtime_s = runtime_us / 1000000.0; 565 | long double req_success_per_s = (complete - failure) / runtime_s; 566 | long double req_fail_per_s = failure / runtime_s; 567 | long double bytes_per_s = bytes / runtime_s; 568 | 569 | printf("\r"); 570 | 571 | printf(" Time:%"PRIu64"s", i); 572 | printf(" TPS:%.2Lf/%.2Lf Latency:", req_success_per_s, req_fail_per_s); 573 | printf("%s", format_time_us(lcfg->p_threads[0].latency)); 574 | printf(" BPS:%sB", format_binary(bytes_per_s)); 575 | printf(" Error:%"PRIu64" ", error); 576 | 577 | /** 跟踪趋势点记录 **/ 578 | int n = 0; 579 | if( lcfg->duration <= 100){ 580 | lcfg->trace.step_time = 1; 581 | n = i-1; 582 | }else{ 583 | /*, 如果>100个点,则需要隔点记录 584 | 340秒 / 100个点 = 3; 585 | 1 4 7 10 13 16 19 22 25 28 31 34 37 40 586 | */ 587 | if( (i-1)%lcfg->trace.step_time == 0){ 588 | n = (i-1)/lcfg->trace.step_time; 589 | }else{ 590 | n = -1; 591 | } 592 | } 593 | if( n != -1){ 594 | /* 抓取所有线程最大的响应时间 */ 595 | int k; 596 | for( k = 0; k < lcfg->threads ; k++){ 597 | if (lcfg->trace.latency[n] < lcfg->p_threads[k].latency){ 598 | lcfg->trace.latency[n] = lcfg->p_threads[k].latency; 599 | } 600 | } 601 | /* 记录tps并更新采集点tps最大值和最小值 */ 602 | lcfg->trace.tps[n] = req_success_per_s; 603 | if( lcfg->result.tps_min > lcfg->trace.tps[n] || lcfg->result.tps_min == 0 ){ 604 | lcfg->result.tps_min = lcfg->trace.tps[n]; 605 | } 606 | if( lcfg->result.tps_max < lcfg->trace.tps[n] ){ 607 | lcfg->result.tps_max = lcfg->trace.tps[n]; 608 | } 609 | cfg.trace.use_num++; 610 | } 611 | 612 | /*** 如果达到结尾,则换行 ***/ 613 | if( i == lcfg->duration){ 614 | printf("\n"); 615 | } 616 | 617 | /*** 将标准输出打印出去 ***/ 618 | fflush(stdout); 619 | } 620 | 621 | return 0; 622 | } 623 | static int response_complete(void * data, char * buf, size_t n) { 624 | connection *c = data; 625 | thread *thread = c->thread; 626 | 627 | if(c->rsp_state != COMPLETE){ 628 | return 0; 629 | } 630 | 631 | uint64_t now = istime_us(); 632 | 633 | thread->complete++; 634 | thread->requests++; 635 | 636 | /** 匹配结果,失败的要单独统计 **/ 637 | islog_debug("rsp_head[%s], rsp_body[%s]", c->rsp_head, c->rsp_body); 638 | islog_test("RESPONSE_HEAD:[%s],RESPONSE_BODY[%s],cno[%ld]", c->rsp_head, c->rsp_body, c->cno); 639 | 640 | if(tcpini_response_issuccess(c->tcpini, c->rsp_head, c->rsp_body)){ 641 | islog_debug(" response is success !!!!"); 642 | /*EMPTY*/; 643 | }else{ 644 | islog_debug(" response is failure !!!!"); 645 | thread->errors.status++; 646 | } 647 | 648 | if (--c->pending == 0) { 649 | thread->latency = now - c->start; 650 | if (!stats_record(cfg.statistics.latency, now - c->start)) { 651 | thread->errors.timeout++; 652 | } 653 | c->delayed = cfg.isdelay; 654 | aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c); 655 | } 656 | 657 | if( c->rsp_head != NULL){ 658 | zfree(c->rsp_head); 659 | c->rsp_head = NULL; 660 | } 661 | if( c->rsp_body != NULL) { 662 | zfree(c->rsp_body); 663 | c->rsp_body = NULL; 664 | } 665 | 666 | c->rsp_state = HEAD; 667 | memset( c->buf, 0x00, sizeof(c->buf)); 668 | 669 | /** tcp非长连接,需要重新连接 **/ 670 | reconnect_socket(thread, c); 671 | 672 | return 0; 673 | } 674 | 675 | -------------------------------------------------------------------------------- /src/wrktcp.h: -------------------------------------------------------------------------------- 1 | #ifndef WRKTCP_H 2 | #define WRKTCP_H 3 | 4 | #include "config.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "stats.h" 12 | #include "ae.h" 13 | #include "tcpini.h" 14 | #include "zmalloc.h" 15 | 16 | #include "http_parser.h" 17 | 18 | #define RECVBUF 8192 19 | 20 | /** 最大支持的TPS/10, 目前的是 100W **/ 21 | #define MAX_THREAD_RATE_S 10000000 22 | /** 超时时间,设置的越大,占用的空间越大 **/ 23 | #define SOCKET_TIMEOUT_MS 5000 24 | /** 采样间隔时间 **/ 25 | #define RECORD_INTERVAL_MS 100 26 | /** 最大跟踪记录点 **/ 27 | #define TRACE_MAX_POINT 100 28 | /** 输出文件最大长度 **/ 29 | #define MAX_HTML_FILELEN 512 30 | 31 | 32 | typedef struct { 33 | pthread_t thread; 34 | long tno; 35 | struct config *lcfg; 36 | aeEventLoop *loop; 37 | uint64_t connections; 38 | uint64_t complete; 39 | uint64_t requests; 40 | uint64_t bytes; 41 | uint64_t start; 42 | uint64_t latency; 43 | tcpini *tcpini; 44 | errors errors; 45 | struct connection *cs; 46 | } thread; 47 | 48 | typedef struct connection { 49 | struct config *lcfg; 50 | thread *thread; 51 | /** http句柄 **/ 52 | http_parser http_parser; 53 | /** 连接数据 **/ 54 | long cno; 55 | int fd; 56 | char delayed; 57 | uint64_t start; /*统计时间,开始*/ 58 | tcpini *tcpini; 59 | /** 请求数据 **/ 60 | char *request; /*请求整体*/ 61 | long length; /*请求长度*/ 62 | long written; /*已发送长度*/ 63 | uint64_t pending; /*pipeline*/ 64 | /** 应答数据 **/ 65 | char buf[RECVBUF]; 66 | unsigned long readlen; 67 | enum{ 68 | HEAD, BODY, COMPLETE 69 | } rsp_state; 70 | unsigned long rsp_len; 71 | char * rsp_head; 72 | char * rsp_body; 73 | } connection; 74 | 75 | /** wrk的整体汇总数据 **/ 76 | typedef struct config { 77 | uint64_t connections; //连接数 78 | uint64_t duration; //持续时间-秒 79 | uint64_t threads; //线程数 80 | uint64_t timeout; //超时时间 81 | uint64_t pipeline; //多次发送,暂时没有用 82 | char isdelay; //是否 延迟发送 83 | char isdynamic; //是否 有动态模板 84 | char islatency; //是否 打印响应时间 85 | char istrace; //是否 打印trace明细 86 | char ishtml; //是否 输出文件 87 | char istest; //是否 单笔调试 88 | char htmlfile[MAX_HTML_FILELEN]; //输出html文件名 89 | thread * p_threads; //线程的内容 90 | /** tcp报文的结构配置信息 **/ 91 | tcpini tcpini; 92 | /*** 结果记录,汇总的结果数据 ***/ 93 | struct _result{ 94 | int64_t complete; 95 | int64_t bytes; 96 | errors errors; 97 | uint64_t tm_start; 98 | uint64_t tm_end; 99 | long double runtime_s; 100 | uint64_t runtime_us; 101 | long double req_per_s; 102 | long double req_success_per_s; 103 | long double req_fail_per_s; 104 | long double bytes_per_s; 105 | long double tps_min; 106 | long double tps_max; 107 | }result; 108 | /** 分布统计,计算平均值方差等 **/ 109 | struct _statistics{ 110 | stats *latency; 111 | stats *requests; 112 | } statistics; 113 | /** 追踪统计,记录时间变化趋势 **/ 114 | struct _trace{ 115 | int64_t step_time; 116 | int use_num; 117 | double tps[TRACE_MAX_POINT]; 118 | double latency[TRACE_MAX_POINT]; 119 | }trace; 120 | } config; 121 | 122 | #endif /* WRKTCP_H */ 123 | -------------------------------------------------------------------------------- /src/zmalloc.c: -------------------------------------------------------------------------------- 1 | /* zmalloc - total amount of allocated memory aware version of malloc() 2 | * 3 | * Copyright (c) 2009-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | 34 | /* This function provide us access to the original libc free(). This is useful 35 | * for instance to free results obtained by backtrace_symbols(). We need 36 | * to define this function before including zmalloc.h that may shadow the 37 | * free implementation if we use jemalloc or another non standard allocator. */ 38 | void zlibc_free(void *ptr) { 39 | free(ptr); 40 | } 41 | 42 | #include 43 | #include 44 | #include "config.h" 45 | #include "zmalloc.h" 46 | #include "atomicvar.h" 47 | 48 | #ifdef HAVE_MALLOC_SIZE 49 | #define PREFIX_SIZE (0) 50 | #else 51 | #if defined(__sun) || defined(__sparc) || defined(__sparc__) 52 | #define PREFIX_SIZE (sizeof(long long)) 53 | #else 54 | #define PREFIX_SIZE (sizeof(size_t)) 55 | #endif 56 | #endif 57 | 58 | /* Explicitly override malloc/free etc when using tcmalloc. */ 59 | #if defined(USE_TCMALLOC) 60 | #define malloc(size) tc_malloc(size) 61 | #define calloc(count,size) tc_calloc(count,size) 62 | #define realloc(ptr,size) tc_realloc(ptr,size) 63 | #define free(ptr) tc_free(ptr) 64 | #elif defined(USE_JEMALLOC) 65 | #define malloc(size) je_malloc(size) 66 | #define calloc(count,size) je_calloc(count,size) 67 | #define realloc(ptr,size) je_realloc(ptr,size) 68 | #define free(ptr) je_free(ptr) 69 | #define mallocx(size,flags) je_mallocx(size,flags) 70 | #define dallocx(ptr,flags) je_dallocx(ptr,flags) 71 | #endif 72 | 73 | #define update_zmalloc_stat_alloc(__n) do { \ 74 | size_t _n = (__n); \ 75 | if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 76 | atomicIncr(used_memory,__n); \ 77 | } while(0) 78 | 79 | #define update_zmalloc_stat_free(__n) do { \ 80 | size_t _n = (__n); \ 81 | if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 82 | atomicDecr(used_memory,__n); \ 83 | } while(0) 84 | 85 | static size_t used_memory = 0; 86 | pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; 87 | 88 | static void zmalloc_default_oom(size_t size) { 89 | fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", 90 | size); 91 | fflush(stderr); 92 | abort(); 93 | } 94 | 95 | static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; 96 | 97 | void *zmalloc(size_t size) { 98 | void *ptr = malloc(size+PREFIX_SIZE); 99 | 100 | if (!ptr) zmalloc_oom_handler(size); 101 | #ifdef HAVE_MALLOC_SIZE 102 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 103 | return ptr; 104 | #else 105 | *((size_t*)ptr) = size; 106 | update_zmalloc_stat_alloc(size+PREFIX_SIZE); 107 | return (char*)ptr+PREFIX_SIZE; 108 | #endif 109 | } 110 | 111 | /* Allocation and free functions that bypass the thread cache 112 | * and go straight to the allocator arena bins. 113 | * Currently implemented only for jemalloc. Used for online defragmentation. */ 114 | #ifdef HAVE_DEFRAG 115 | void *zmalloc_no_tcache(size_t size) { 116 | void *ptr = mallocx(size+PREFIX_SIZE, MALLOCX_TCACHE_NONE); 117 | if (!ptr) zmalloc_oom_handler(size); 118 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 119 | return ptr; 120 | } 121 | 122 | void zfree_no_tcache(void *ptr) { 123 | if (ptr == NULL) return; 124 | update_zmalloc_stat_free(zmalloc_size(ptr)); 125 | dallocx(ptr, MALLOCX_TCACHE_NONE); 126 | } 127 | #endif 128 | 129 | void *zcalloc(size_t size) { 130 | void *ptr = calloc(1, size+PREFIX_SIZE); 131 | 132 | if (!ptr) zmalloc_oom_handler(size); 133 | #ifdef HAVE_MALLOC_SIZE 134 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 135 | return ptr; 136 | #else 137 | *((size_t*)ptr) = size; 138 | update_zmalloc_stat_alloc(size+PREFIX_SIZE); 139 | return (char*)ptr+PREFIX_SIZE; 140 | #endif 141 | } 142 | 143 | void *zrealloc(void *ptr, size_t size) { 144 | #ifndef HAVE_MALLOC_SIZE 145 | void *realptr; 146 | #endif 147 | size_t oldsize; 148 | void *newptr; 149 | 150 | if (ptr == NULL) return zmalloc(size); 151 | #ifdef HAVE_MALLOC_SIZE 152 | oldsize = zmalloc_size(ptr); 153 | newptr = realloc(ptr,size); 154 | if (!newptr) zmalloc_oom_handler(size); 155 | 156 | update_zmalloc_stat_free(oldsize); 157 | update_zmalloc_stat_alloc(zmalloc_size(newptr)); 158 | return newptr; 159 | #else 160 | realptr = (char*)ptr-PREFIX_SIZE; 161 | oldsize = *((size_t*)realptr); 162 | newptr = realloc(realptr,size+PREFIX_SIZE); 163 | if (!newptr) zmalloc_oom_handler(size); 164 | 165 | *((size_t*)newptr) = size; 166 | update_zmalloc_stat_free(oldsize); 167 | update_zmalloc_stat_alloc(size); 168 | return (char*)newptr+PREFIX_SIZE; 169 | #endif 170 | } 171 | 172 | /* Provide zmalloc_size() for systems where this function is not provided by 173 | * malloc itself, given that in that case we store a header with this 174 | * information as the first bytes of every allocation. */ 175 | #ifndef HAVE_MALLOC_SIZE 176 | size_t zmalloc_size(void *ptr) { 177 | void *realptr = (char*)ptr-PREFIX_SIZE; 178 | size_t size = *((size_t*)realptr); 179 | /* Assume at least that all the allocations are padded at sizeof(long) by 180 | * the underlying allocator. */ 181 | if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); 182 | return size+PREFIX_SIZE; 183 | } 184 | #endif 185 | 186 | void zfree(void *ptr) { 187 | #ifndef HAVE_MALLOC_SIZE 188 | void *realptr; 189 | size_t oldsize; 190 | #endif 191 | 192 | if (ptr == NULL) return; 193 | #ifdef HAVE_MALLOC_SIZE 194 | update_zmalloc_stat_free(zmalloc_size(ptr)); 195 | free(ptr); 196 | #else 197 | realptr = (char*)ptr-PREFIX_SIZE; 198 | oldsize = *((size_t*)realptr); 199 | update_zmalloc_stat_free(oldsize+PREFIX_SIZE); 200 | free(realptr); 201 | #endif 202 | } 203 | 204 | char *zstrdup(const char *s) { 205 | size_t l = strlen(s)+1; 206 | char *p = zmalloc(l); 207 | 208 | memcpy(p,s,l); 209 | return p; 210 | } 211 | 212 | size_t zmalloc_used_memory(void) { 213 | size_t um; 214 | atomicGet(used_memory,um); 215 | return um; 216 | } 217 | 218 | void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) { 219 | zmalloc_oom_handler = oom_handler; 220 | } 221 | 222 | /* Get the RSS information in an OS-specific way. 223 | * 224 | * WARNING: the function zmalloc_get_rss() is not designed to be fast 225 | * and may not be called in the busy loops where Redis tries to release 226 | * memory expiring or swapping out objects. 227 | * 228 | * For this kind of "fast RSS reporting" usages use instead the 229 | * function RedisEstimateRSS() that is a much faster (and less precise) 230 | * version of the function. */ 231 | 232 | #if defined(HAVE_PROC_STAT) 233 | #include 234 | #include 235 | #include 236 | #include 237 | 238 | size_t zmalloc_get_rss(void) { 239 | int page = sysconf(_SC_PAGESIZE); 240 | size_t rss; 241 | char buf[4096]; 242 | char filename[256]; 243 | int fd, count; 244 | char *p, *x; 245 | 246 | snprintf(filename,256,"/proc/%d/stat",getpid()); 247 | if ((fd = open(filename,O_RDONLY)) == -1) return 0; 248 | if (read(fd,buf,4096) <= 0) { 249 | close(fd); 250 | return 0; 251 | } 252 | close(fd); 253 | 254 | p = buf; 255 | count = 23; /* RSS is the 24th field in /proc//stat */ 256 | while(p && count--) { 257 | p = strchr(p,' '); 258 | if (p) p++; 259 | } 260 | if (!p) return 0; 261 | x = strchr(p,' '); 262 | if (!x) return 0; 263 | *x = '\0'; 264 | 265 | rss = strtoll(p,NULL,10); 266 | rss *= page; 267 | return rss; 268 | } 269 | #elif defined(HAVE_TASKINFO) 270 | #include 271 | #include 272 | #include 273 | #include 274 | #include 275 | #include 276 | #include 277 | 278 | size_t zmalloc_get_rss(void) { 279 | task_t task = MACH_PORT_NULL; 280 | struct task_basic_info t_info; 281 | mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; 282 | 283 | if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) 284 | return 0; 285 | task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 286 | 287 | return t_info.resident_size; 288 | } 289 | #else 290 | size_t zmalloc_get_rss(void) { 291 | /* If we can't get the RSS in an OS-specific way for this system just 292 | * return the memory usage we estimated in zmalloc().. 293 | * 294 | * Fragmentation will appear to be always 1 (no fragmentation) 295 | * of course... */ 296 | return zmalloc_used_memory(); 297 | } 298 | #endif 299 | 300 | /* Fragmentation = RSS / allocated-bytes */ 301 | float zmalloc_get_fragmentation_ratio(size_t rss) { 302 | return (float)rss/zmalloc_used_memory(); 303 | } 304 | 305 | /* Get the sum of the specified field (converted form kb to bytes) in 306 | * /proc/self/smaps. The field must be specified with trailing ":" as it 307 | * apperas in the smaps output. 308 | * 309 | * If a pid is specified, the information is extracted for such a pid, 310 | * otherwise if pid is -1 the information is reported is about the 311 | * current process. 312 | * 313 | * Example: zmalloc_get_smap_bytes_by_field("Rss:",-1); 314 | */ 315 | #if defined(HAVE_PROC_SMAPS) 316 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { 317 | char line[1024]; 318 | size_t bytes = 0; 319 | int flen = strlen(field); 320 | FILE *fp; 321 | 322 | if (pid == -1) { 323 | fp = fopen("/proc/self/smaps","r"); 324 | } else { 325 | char filename[128]; 326 | snprintf(filename,sizeof(filename),"/proc/%ld/smaps",pid); 327 | fp = fopen(filename,"r"); 328 | } 329 | 330 | if (!fp) return 0; 331 | while(fgets(line,sizeof(line),fp) != NULL) { 332 | if (strncmp(line,field,flen) == 0) { 333 | char *p = strchr(line,'k'); 334 | if (p) { 335 | *p = '\0'; 336 | bytes += strtol(line+flen,NULL,10) * 1024; 337 | } 338 | } 339 | } 340 | fclose(fp); 341 | return bytes; 342 | } 343 | #else 344 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { 345 | ((void) field); 346 | ((void) pid); 347 | return 0; 348 | } 349 | #endif 350 | 351 | size_t zmalloc_get_private_dirty(long pid) { 352 | return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid); 353 | } 354 | 355 | /* Returns the size of physical memory (RAM) in bytes. 356 | * It looks ugly, but this is the cleanest way to achive cross platform results. 357 | * Cleaned up from: 358 | * 359 | * http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system 360 | * 361 | * Note that this function: 362 | * 1) Was released under the following CC attribution license: 363 | * http://creativecommons.org/licenses/by/3.0/deed.en_US. 364 | * 2) Was originally implemented by David Robert Nadeau. 365 | * 3) Was modified for Redis by Matt Stancliff. 366 | * 4) This note exists in order to comply with the original license. 367 | */ 368 | size_t zmalloc_get_memory_size(void) { 369 | #if defined(__unix__) || defined(__unix) || defined(unix) || \ 370 | (defined(__APPLE__) && defined(__MACH__)) 371 | #if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) 372 | int mib[2]; 373 | mib[0] = CTL_HW; 374 | #if defined(HW_MEMSIZE) 375 | mib[1] = HW_MEMSIZE; /* OSX. --------------------- */ 376 | #elif defined(HW_PHYSMEM64) 377 | mib[1] = HW_PHYSMEM64; /* NetBSD, OpenBSD. --------- */ 378 | #endif 379 | int64_t size = 0; /* 64-bit */ 380 | size_t len = sizeof(size); 381 | if (sysctl( mib, 2, &size, &len, NULL, 0) == 0) 382 | return (size_t)size; 383 | return 0L; /* Failed? */ 384 | 385 | #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) 386 | /* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */ 387 | return (size_t)sysconf(_SC_PHYS_PAGES) * (size_t)sysconf(_SC_PAGESIZE); 388 | 389 | #elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) 390 | /* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */ 391 | int mib[2]; 392 | mib[0] = CTL_HW; 393 | #if defined(HW_REALMEM) 394 | mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */ 395 | #elif defined(HW_PYSMEM) 396 | mib[1] = HW_PHYSMEM; /* Others. ------------------ */ 397 | #endif 398 | unsigned int size = 0; /* 32-bit */ 399 | size_t len = sizeof(size); 400 | if (sysctl(mib, 2, &size, &len, NULL, 0) == 0) 401 | return (size_t)size; 402 | return 0L; /* Failed? */ 403 | #else 404 | return 0L; /* Unknown method to get the data. */ 405 | #endif 406 | #else 407 | return 0L; /* Unknown OS. */ 408 | #endif 409 | } 410 | 411 | 412 | -------------------------------------------------------------------------------- /src/zmalloc.h: -------------------------------------------------------------------------------- 1 | /* zmalloc - total amount of allocated memory aware version of malloc() 2 | * 3 | * Copyright (c) 2009-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __ZMALLOC_H 32 | #define __ZMALLOC_H 33 | 34 | /* Double expansion needed for stringification of macro values. */ 35 | #define __xstr(s) __str(s) 36 | #define __str(s) #s 37 | 38 | #if defined(USE_TCMALLOC) 39 | #define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR)) 40 | #include 41 | #if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1) 42 | #define HAVE_MALLOC_SIZE 1 43 | #define zmalloc_size(p) tc_malloc_size(p) 44 | #else 45 | #error "Newer version of tcmalloc required" 46 | #endif 47 | 48 | #elif defined(USE_JEMALLOC) 49 | #define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX)) 50 | #include 51 | #if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2) 52 | #define HAVE_MALLOC_SIZE 1 53 | #define zmalloc_size(p) je_malloc_usable_size(p) 54 | #else 55 | #error "Newer version of jemalloc required" 56 | #endif 57 | 58 | #elif defined(__APPLE__) 59 | #include 60 | #define HAVE_MALLOC_SIZE 1 61 | #define zmalloc_size(p) malloc_size(p) 62 | #endif 63 | 64 | #ifndef ZMALLOC_LIB 65 | #define ZMALLOC_LIB "libc" 66 | #endif 67 | 68 | /* We can enable the Redis defrag capabilities only if we are using Jemalloc 69 | * and the version used is our special version modified for Redis having 70 | * the ability to return per-allocation fragmentation hints. */ 71 | #if defined(USE_JEMALLOC) && defined(JEMALLOC_FRAG_HINT) 72 | #define HAVE_DEFRAG 73 | #endif 74 | 75 | void *zmalloc(size_t size); 76 | void *zcalloc(size_t size); 77 | void *zrealloc(void *ptr, size_t size); 78 | void zfree(void *ptr); 79 | char *zstrdup(const char *s); 80 | size_t zmalloc_used_memory(void); 81 | void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); 82 | float zmalloc_get_fragmentation_ratio(size_t rss); 83 | size_t zmalloc_get_rss(void); 84 | size_t zmalloc_get_private_dirty(long pid); 85 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid); 86 | size_t zmalloc_get_memory_size(void); 87 | void zlibc_free(void *ptr); 88 | 89 | #ifdef HAVE_DEFRAG 90 | void zfree_no_tcache(void *ptr); 91 | void *zmalloc_no_tcache(size_t size); 92 | #endif 93 | 94 | #ifndef HAVE_MALLOC_SIZE 95 | size_t zmalloc_size(void *ptr); 96 | #endif 97 | 98 | #endif /* __ZMALLOC_H */ 99 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | testserver: testserver.c istcp.c istcp.h 2 | cc testserver.c istcp.c -o testserver 3 | clean: 4 | rm -f testserver 5 | -------------------------------------------------------------------------------- /test/istcp.c: -------------------------------------------------------------------------------- 1 | /************************************ 2 | * istcp.c is a tcp protrol library 3 | * author: icesky 4 | * date: 2020.12.21 5 | *************************************/ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "istcp.h" 18 | #include 19 | 20 | /* nginx & redis, default value is 511 */ 21 | #define ISTCP_DEFAULT_BACKLOG 511 22 | #define ISTCP_DEFAULT_TIMEOUT 60 23 | 24 | char * istcp_version(){ 25 | return ISTCP_VERSION_NO; 26 | } 27 | 28 | 29 | /*** 关闭socket ***/ 30 | void istcp_close(int sock){ 31 | close(sock); 32 | return; 33 | } 34 | 35 | /*** 申请socket并连接 ***/ 36 | int istcp_connect(char *ip, int port){ 37 | struct sockaddr_in sin; 38 | int sock; 39 | 40 | memset(&sin, 0x00, sizeof(sin)); 41 | 42 | sin.sin_family = AF_INET; 43 | sin.sin_port = htons(port); 44 | sin.sin_addr.s_addr = inet_addr(ip); 45 | 46 | if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ 47 | return ISTCP_ERROR_SOCKET; 48 | } 49 | 50 | if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0){ 51 | close(sock); 52 | return ISTCP_ERROR_CONNECT; 53 | } 54 | 55 | return sock; 56 | } 57 | 58 | /*** 按超时时间接收固定长度内容 ***/ 59 | static int istcp_timeflag = 0 ; 60 | static void istcpSetalm(int timeout) { 61 | alarm(timeout) ; 62 | return ; 63 | } 64 | static void istcpClralm(){ 65 | alarm(0) ; 66 | return ; 67 | } 68 | 69 | static void istcpCthalm(int a){ 70 | istcp_timeflag = 1 ; 71 | return ; 72 | } 73 | 74 | int istcp_recv(int sock, char *msgbuf, int len, int timeout){ 75 | int ret; 76 | int nfds; 77 | struct timeval tv; 78 | fd_set readfds; 79 | 80 | int iSize; 81 | long lStartTime, lCurrentTime; 82 | 83 | lStartTime = lCurrentTime = 0; 84 | 85 | /* 设置超时时间 */ 86 | if (timeout <= 0){ 87 | tv.tv_sec = 0; 88 | tv.tv_usec = 0; 89 | } else { 90 | tv.tv_sec = timeout; 91 | tv.tv_usec = 0; 92 | } 93 | 94 | /* 设置nfds = 1, 检查当前sock */ 95 | nfds = sock + 1; 96 | 97 | /* 初始化readfds, 并将当前sock加入 */ 98 | FD_ZERO(&readfds); 99 | FD_SET(sock, &readfds); 100 | 101 | time(&lStartTime); 102 | 103 | ret = select(nfds, &readfds, NULL, NULL, &tv); 104 | if (ret < 0){ 105 | return ISTCP_ERROR_SELECT; 106 | }else if (ret == 0){ 107 | /* 超时时间到 */ 108 | return ISTCP_ERROR_TIMEOUT; 109 | } 110 | 111 | 112 | /* 异常错误 */ 113 | if (!FD_ISSET(sock, &readfds)){ 114 | return ISTCP_ERROR_SELECT; 115 | } 116 | 117 | /* 开始接收数据包 */ 118 | iSize = len ; 119 | while(iSize > 0){ 120 | if (timeout > 0){ 121 | time(&lCurrentTime); 122 | if (lCurrentTime - lStartTime > timeout){ 123 | return ISTCP_ERROR_TIMEOUT; 124 | } 125 | } 126 | 127 | signal(SIGALRM, istcpCthalm) ; 128 | istcpSetalm(timeout) ; 129 | 130 | ret = read(sock, msgbuf, iSize); 131 | 132 | istcpClralm(); 133 | 134 | if ( istcp_timeflag == 1 ){ 135 | istcp_timeflag = 0 ; 136 | return ISTCP_ERROR_TIMEOUT; 137 | } 138 | 139 | if (ret < 0){ 140 | return ISTCP_ERROR_READ; 141 | } else if (ret == 0){ 142 | return ISTCP_ERROR_DISCONNECT_PEER; 143 | } else { 144 | iSize -= ret; 145 | msgbuf += ret; 146 | } 147 | } 148 | return len; 149 | } 150 | 151 | /*** 按超时时间等待,试图读一次接收固定长度内容,返回实际读取长度,不可靠的通讯模式,仅用于对方无法约定报文长度时使用 ***/ 152 | int istcp_recv_nowait(int sock, char *msgbuf, int len, int timeout){ 153 | int ret; 154 | int nfds; 155 | struct timeval tv; 156 | fd_set readfds; 157 | 158 | int iSize; 159 | long lStartTime, lCurrentTime; 160 | 161 | lStartTime = lCurrentTime = 0; 162 | 163 | /* 设置超时时间 */ 164 | if (timeout <= 0){ 165 | tv.tv_sec = 0; 166 | tv.tv_usec = 0; 167 | } else { 168 | tv.tv_sec = timeout; 169 | tv.tv_usec = 0; 170 | } 171 | 172 | /* 设置nfds = 1, 检查当前sock */ 173 | nfds = sock + 1; 174 | 175 | /* 初始化readfds, 并将当前sock加入 */ 176 | FD_ZERO(&readfds); 177 | FD_SET(sock, &readfds); 178 | 179 | time(&lStartTime); 180 | 181 | ret = select(nfds, &readfds, NULL, NULL, &tv); 182 | if (ret < 0){ 183 | return ISTCP_ERROR_SELECT; 184 | } 185 | else if (ret == 0){ 186 | /* 超时时间到 */ 187 | return ISTCP_ERROR_TIMEOUT; 188 | } 189 | 190 | /* 异常错误 */ 191 | if (!FD_ISSET(sock, &readfds)){ 192 | return ISTCP_ERROR_SELECT; 193 | } 194 | 195 | /* 开始接收数据包 */ 196 | iSize = len; 197 | if (timeout > 0){ 198 | time(&lCurrentTime); 199 | if (lCurrentTime - lStartTime > timeout){ 200 | return ISTCP_ERROR_TIMEOUT; 201 | } 202 | } 203 | 204 | ret = read(sock, msgbuf, iSize); 205 | if (ret < 0) { 206 | return ISTCP_ERROR_READ; 207 | } 208 | else if (ret == 0){ 209 | return ISTCP_ERROR_DISCONNECT_PEER; 210 | } 211 | 212 | return ret; 213 | } 214 | 215 | /*** 按长度发送内容 ***/ 216 | int istcp_send(int sock, char *msgbuf, int len, int timeout){ 217 | int ret; 218 | int nfds; 219 | struct timeval tv; 220 | fd_set writefds; 221 | long lStartTime, lCurrentTime; 222 | int iSize; 223 | 224 | lStartTime = lCurrentTime = 0; 225 | 226 | /* 设置超时时间 */ 227 | if (timeout <= 0){ 228 | tv.tv_sec = 0; 229 | tv.tv_usec = 0; 230 | }else{ 231 | tv.tv_sec = timeout; 232 | tv.tv_usec = 0; 233 | } 234 | 235 | time(&lStartTime); 236 | 237 | /* 设置nfds = 1, 检查当前sock */ 238 | nfds = sock + 1; 239 | 240 | /* 初始化writefds, 并将当前sock加入 */ 241 | FD_ZERO(&writefds); 242 | FD_SET(sock, &writefds); 243 | 244 | ret = select(nfds, NULL, &writefds, NULL, &tv); 245 | if (ret < 0){ 246 | return ISTCP_ERROR_SELECT; 247 | }else if (ret == 0){ 248 | /* 超时时间到 */ 249 | return ISTCP_ERROR_TIMEOUT; 250 | } 251 | 252 | /* 异常错误 */ 253 | if (!FD_ISSET(sock, &writefds)){ 254 | return ISTCP_ERROR_SELECT; 255 | } 256 | 257 | iSize = len; 258 | while(iSize > 0){ 259 | if (timeout > 0){ 260 | time(&lCurrentTime); 261 | if (lCurrentTime - lStartTime > timeout){ 262 | return ISTCP_ERROR_TIMEOUT; 263 | } 264 | } 265 | 266 | ret = write(sock, msgbuf, iSize); 267 | if (ret <= 0){ 268 | return ISTCP_ERROR_WRITE; 269 | }else{ 270 | iSize -= ret; 271 | msgbuf += ret; 272 | } 273 | } 274 | 275 | return len; 276 | 277 | } 278 | 279 | /** accept获取新连接 **/ 280 | int istcp_accept( int sock){ 281 | return istcp_accept_gethost( sock, NULL); 282 | } 283 | 284 | /** 获取新连接并取得请求信息 **/ 285 | int istcp_accept_gethost(int sock, char * *p_hostip) { 286 | int acceptSock; 287 | socklen_t len = 0; 288 | struct sockaddr_in sin; 289 | 290 | len = sizeof(struct sockaddr_in); 291 | acceptSock = accept(sock, (struct sockaddr *) &sin, &len); 292 | if (acceptSock < 0){ 293 | return ISTCP_ERROR_ACCEPT; 294 | } 295 | /* 如果不需要,则直接赋值 */ 296 | if( p_hostip != NULL){ 297 | *p_hostip = inet_ntoa(sin.sin_addr); 298 | } 299 | 300 | return acceptSock; 301 | } 302 | 303 | /** 带backlog队列大小的绑定端口和监听 **/ 304 | int istcp_listen_backlog(char *hostname, int port, int backlog){ 305 | struct hostent *h; 306 | struct sockaddr_in sin; 307 | int sock; 308 | int optLen, optVar; 309 | int ret; 310 | 311 | int ip1,ip2,ip3,ip4; 312 | 313 | memset(&sin, 0x00, sizeof(sin)); 314 | 315 | if (hostname != NULL) { 316 | /** 如果是ip地址类型,则直接赋值 **/ 317 | if( 4 == sscanf(hostname, "%d.%d.%d.%d",&ip1, &ip2, &ip3, &ip4)){ 318 | if (0<=ip1 && ip1<=255 319 | && 0<=ip2 && ip2<=255 320 | && 0<=ip3 && ip3<=255 321 | && 0<=ip4 && ip4<=255){ 322 | sin.sin_addr.s_addr = inet_addr(hostname); 323 | } 324 | } else { 325 | /** 如果不是ip地址类型,则取DNS信息 **/ 326 | h = gethostbyname(hostname); 327 | if (h == NULL){ 328 | sin.sin_addr.s_addr = inet_addr(hostname); 329 | } else { 330 | memcpy(&sin.sin_addr, h->h_addr, h->h_length); 331 | } 332 | } 333 | }else{ 334 | sin.sin_addr.s_addr = INADDR_ANY; /*本机IP*/ 335 | } 336 | 337 | sin.sin_port = htons(port); 338 | sin.sin_family = AF_INET; 339 | 340 | if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ 341 | return ISTCP_ERROR_SOCKET; 342 | } 343 | 344 | optVar = 1; 345 | optLen = sizeof(optVar); 346 | 347 | ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&optVar, optLen); 348 | if (ret < 0){ 349 | return ISTCP_ERROR_SETSOCKOPT; 350 | } 351 | 352 | if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0){ 353 | close(sock); 354 | return ISTCP_ERROR_BIND; 355 | } 356 | 357 | if (listen(sock, backlog) < 0){ 358 | close(sock); 359 | return ISTCP_ERROR_LISTEN; 360 | } 361 | 362 | return sock; 363 | } 364 | 365 | /** 标准绑定监听服务 **/ 366 | int istcp_listen(int port){ 367 | return istcp_listen_backlog( "127.0.0.1", port, ISTCP_DEFAULT_BACKLOG); 368 | } 369 | 370 | /** unix域socket **/ 371 | int istcp_listen_unix(char *pathname){ 372 | 373 | int ret; 374 | int sock; 375 | int optLen, optVar; 376 | struct sockaddr_un sun; 377 | 378 | /** 先判断长度是否超限 **/ 379 | if( strlen( pathname) >= sizeof(sun.sun_path)){ 380 | return ISTCP_ERROR_UNIXPATH_TOOLONG; 381 | } 382 | 383 | memset(&sun, 0x00, sizeof(sun)); 384 | sun.sun_family= AF_UNIX; 385 | strcpy( sun.sun_path, pathname); 386 | 387 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ 388 | return ISTCP_ERROR_SOCKET; 389 | } 390 | 391 | /** 删除原信息 **/ 392 | remove( sun.sun_path); 393 | optVar = 1; 394 | optLen = sizeof(optVar); 395 | ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&optVar, optLen); 396 | if (ret < 0){ 397 | return ISTCP_ERROR_SETSOCKOPT; 398 | } 399 | 400 | if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) < 0){ 401 | close(sock); 402 | return ISTCP_ERROR_BIND; 403 | } 404 | 405 | if (listen(sock, ISTCP_DEFAULT_BACKLOG) < 0){ 406 | close(sock); 407 | return ISTCP_ERROR_LISTEN; 408 | } 409 | 410 | return sock; 411 | } 412 | 413 | int istcp_connect_unix(char *pathname) 414 | { 415 | int sock; 416 | struct sockaddr_un sun; 417 | 418 | memset(&sun, 0x00, sizeof(sun)); 419 | 420 | sun.sun_family = AF_UNIX; 421 | if( strlen( pathname) >= sizeof(sun.sun_path)){ 422 | return ISTCP_ERROR_UNIXPATH_TOOLONG; 423 | } 424 | strcpy (sun.sun_path, pathname); 425 | 426 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ 427 | return ISTCP_ERROR_SOCKET; 428 | } 429 | 430 | if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) < 0){ 431 | close(sock); 432 | return ISTCP_ERROR_CONNECT; 433 | } 434 | 435 | return sock; 436 | 437 | } 438 | -------------------------------------------------------------------------------- /test/istcp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by icesky on 2020/12/21. 3 | // 4 | 5 | #ifndef ISTCP_H 6 | #define ISTCP_H 7 | 8 | #define ISTCP_VERSION_NO "20.12.3" 9 | char * istcp_version(); 10 | 11 | 12 | #define ISTCP_ERROR_SOCKET -5 13 | #define ISTCP_ERROR_CONNECT -10 14 | 15 | #define ISTCP_ERROR_SELECT -15 16 | #define ISTCP_ERROR_TIMEOUT -20 17 | #define ISTCP_ERROR_DISCONNECT_PEER -25 18 | #define ISTCP_ERROR_READ -30 19 | #define ISTCP_ERROR_WRITE -40 20 | 21 | #define ISTCP_ERROR_ACCEPT -50 22 | #define ISTCP_ERROR_SETSOCKOPT -55 23 | #define ISTCP_ERROR_BIND -60 24 | #define ISTCP_ERROR_LISTEN 65 25 | 26 | #define ISTCP_ERROR_UNIXPATH_TOOLONG -105 27 | 28 | int istcp_connect(char *ip, int port); 29 | int istcp_send(int sock, char *msgbuf, int len, int timeout); 30 | int istcp_recv(int sock, char *msgbuf, int len, int timeout); 31 | int istcp_recv_nowait(int sock, char *msgbuf, int len, int timeout); 32 | void istcp_close(int sock); 33 | 34 | int istcp_listen(int port); 35 | int istcp_listen_backlog(char *hostname, int port, int backlog); 36 | 37 | int istcp_accept(int sock); 38 | int istcp_accept_gethost(int sock, char * *p_hostip); 39 | 40 | int istcp_connect_unix(char *pathname); 41 | int istcp_listen_unix(char *pathname); 42 | 43 | #endif //ISTCP_H 44 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | kill -9 `ps -aef | grep testserver | awk '{if($8!~/grep/) print $2}'` 2 | testserver 8000 3 | -------------------------------------------------------------------------------- /test/testserver.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesky1stm/wrktcp/af4c5215117416dee70cfa0424e61231da349f71/test/testserver.c --------------------------------------------------------------------------------