├── .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 | [](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
--------------------------------------------------------------------------------