-
Notifications
You must be signed in to change notification settings - Fork 204
/
ngx_writev_chain.c
312 lines (248 loc) · 9.63 KB
/
ngx_writev_chain.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// annotated by chrono since 2016
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
// 发送limit长度(字节数)的数据
// limit有限制,但基本上可以说是无限大了
// 如果事件not ready,即暂不可写,那么立即返回,无动作
// 要求缓冲区必须在内存里,否则报错
// 最后返回消费缓冲区之后的链表指针
// 发送出错、遇到again、发送完毕,这三种情况函数结束
// 返回的是最后发送到的链表节点指针
ngx_chain_t *
ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
{
ssize_t n, sent;
off_t send, prev_send;
ngx_chain_t *cl;
ngx_event_t *wev;
ngx_iovec_t vec;
struct iovec iovs[NGX_IOVS_PREALLOCATE];
// 从连接获取写事件
wev = c->write;
// 如果事件not ready,即暂不可写,那么立即返回,无动作
if (!wev->ready) {
return in;
}
#if (NGX_HAVE_KQUEUE)
if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
(void) ngx_connection_error(c, wev->kq_errno,
"kevent() reported about an closed connection");
wev->error = 1;
return NGX_CHAIN_ERROR;
}
#endif
/* the maximum limit size is the maximum size_t value - the page size */
// 由脚本生成,ngx_auto_config.h:#define NGX_MAX_SIZE_T_VALUE 9223372036854775807LL
// limit有限制,但基本上可以说是无限大了
if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
}
// 发送的字节数,初始化为0
// 注意不是sent
send = 0;
// 设置iovs,指向函数内部的数组iovs,长度是NGX_IOVS_PREALLOCATE,通常是64
vec.iovs = iovs;
vec.nalloc = NGX_IOVS_PREALLOCATE;
// 成功发送了limit字节,或者缓冲区链表已经结束
// 内核发送缓冲区满,不能再发送
// 那么发送结束
for ( ;; ) {
// 暂存之前发送的字节数
prev_send = send;
/* create the iovec and coalesce the neighbouring bufs */
// 缓冲区链表转换为iovec结构体
// 输出参数vec,存储iovec,输入参数in是nginx的缓冲区链表
// limit,发送数据的限制长度
// 要求缓冲区必须在内存里,否则报错
// 最后返回消费缓冲区之后的链表指针
cl = ngx_output_chain_to_iovec(&vec, in, limit - send, c->log);
// 要求缓冲区必须在内存里,否则报错
if (cl == NGX_CHAIN_ERROR) {
return NGX_CHAIN_ERROR;
}
// 要求缓冲区必须在内存里,否则报错
if (cl && cl->buf->in_file) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"file buf in writev "
"t:%d r:%d f:%d %p %p-%p %p %O-%O",
cl->buf->temporary,
cl->buf->recycled,
cl->buf->in_file,
cl->buf->start,
cl->buf->pos,
cl->buf->last,
cl->buf->file,
cl->buf->file_pos,
cl->buf->file_last);
ngx_debug_point();
return NGX_CHAIN_ERROR;
}
// vec.size里存储的是在iovs里的总字节数
// 增加发送的字节数
send += vec.size;
// 封装系统调用writev,发送多个内存块
// again,暂时不可写,需要等待事件可写再重试,返回again
// 被中断,需要立即重试,可能就成功了
// 其他的就是错误
n = ngx_writev(c, &vec);
// 不可恢复的错误
if (n == NGX_ERROR) {
return NGX_CHAIN_ERROR;
}
// 如果是again,发送失败,已发送字节不增加
sent = (n == NGX_AGAIN) ? 0 : n;
// 连接里的已发送字节数增加
c->sent += sent;
// 根据已经实际发送的字节数更新链表
// 已经发送的缓冲区会清空
// 最后返回处理之后的链表指针
// 如果没有发送(again)就直接返回
in = ngx_chain_update_sent(in, sent);
// 两者相减,判断是否完全发送了数据
// 不完全,只发送了部分,也就是说内核写缓冲区满,写不可用
if (send - prev_send != sent) {
// 暂时不可写,需要等待下次写事件发生才能写
wev->ready = 0;
return in;
}
// 成功发送了limit字节,或者缓冲区链表已经结束
// 那么发送结束
if (send >= limit || in == NULL) {
return in;
}
// limit字节很多,这次没有发送完
// 需要再从循环开头取数据发送
}
}
// 缓冲区链表转换为iovec结构体
// 输出参数vec,存储iovec,输入参数in是nginx的缓冲区链表
// limit,发送数据的限制长度
// 要求缓冲区必须在内存里,否则报错
// 最后返回消费缓冲区之后的链表指针
ngx_chain_t *
ngx_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, size_t limit,
ngx_log_t *log)
{
size_t total, size;
u_char *prev;
ngx_uint_t n;
struct iovec *iov;
// 指向vec里的某个元素
iov = NULL;
// 缓冲区的字节指针,由于优化
prev = NULL;
// 发送的总字节数
total = 0;
// vec里的数组序号
n = 0;
// 填满vec数组,或者字节数达到limit的限制
// 每处理完一个缓冲区指针就后移
for ( /* void */ ; in && total < limit; in = in->next) {
// 忽略flush、sync、eof等控制用特殊缓冲区
if (ngx_buf_special(in->buf)) {
continue;
}
// 不考虑磁盘文件
if (in->buf->in_file) {
break;
}
// 要求缓冲区必须在内存里,否则报错
if (!ngx_buf_in_memory(in->buf)) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"bad buf in output chain "
"t:%d r:%d f:%d %p %p-%p %p %O-%O",
in->buf->temporary,
in->buf->recycled,
in->buf->in_file,
in->buf->start,
in->buf->pos,
in->buf->last,
in->buf->file,
in->buf->file_pos,
in->buf->file_last);
ngx_debug_point();
return NGX_CHAIN_ERROR;
}
// 获得缓冲区内数据长度
size = in->buf->last - in->buf->pos;
// 如果当前缓冲区的大小超过了最后的限制,那么只发一部分
if (size > limit - total) {
size = limit - total;
}
// 这里是一种特殊情况,也可能很常见
// 两个buf,它们实际上指向了一块连续的内存
// 即buf1的last是buf2的pos
// 所以nginx进行优化,不需要赋值,直接加上长度
// 节约一个iov数组元素
if (prev == in->buf->pos) {
iov->iov_len += size;
} else {
// 不是连续的内存,就要使用一个iov结构体
// vec里的数组已经填满了
if (n == vec->nalloc) {
break;
}
// iov指针指向vec里的第n个数组,然后n加1
iov = &vec->iovs[n++];
// iov结构里的数据指针和数据长度
iov->iov_base = (void *) in->buf->pos;
iov->iov_len = size;
}
// prev指针移动
prev = in->buf->pos + size;
// 总字节数增加,当大于等于limit时就结束循环
total += size;
}
// 如果不连续的内存很多,那么n就是vec->nalloc
// 如果limit比较小,那么n就小于vec->nalloc
vec->count = n;
// size是总字节数,受limit和n的限制
// 不一定正好是limit
vec->size = total;
// 最后返回消费缓冲区之后的链表指针
return in;
}
// 封装系统调用writev,发送多个内存块
// again,暂时不可写,需要等待事件可写再重试,返回again
// 被中断,需要立即重试,可能就成功了
// 其他的就是错误
ssize_t
ngx_writev(ngx_connection_t *c, ngx_iovec_t *vec)
{
ssize_t n;
ngx_err_t err;
// 这个goto标签可以改用for+continue来实现
// 即发生EINTR错误就重试发送数据
eintr:
// 系统调用writev,发送多个内存块
n = writev(c->fd, vec->iovs, vec->count);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"writev: %z of %uz", n, vec->size);
// n < 0 出错,检查errno
if (n == -1) {
err = ngx_errno;
switch (err) {
// again,暂时不可写,需要等待事件可写再重试,返回again
case NGX_EAGAIN:
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
"writev() not ready");
return NGX_AGAIN;
// 被中断,需要立即重试,可能就成功了
case NGX_EINTR:
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
"writev() was interrupted");
goto eintr;
// 其他的就是错误
default:
c->write->error = 1;
ngx_connection_error(c, err, "writev() failed");
return NGX_ERROR;
}
}
return n;
}