HTTP POST 的延迟从何而来?

Where is a delay in an HTTP POST coming from?

我正在使用 cpp-netlib 在异步模式下使用 C++ 在 Ubuntu 14.04 上开发 Web 服务。该服务需要响应消息 body 中通过 HTTP POST 发送的数据。我在接收大于 1K 的输入时观察到性能不佳,我想解决这个问题。

如果数据比较小,小于1K,服务器几乎是瞬间收到数据。如果数据超过 1K,则在第一次调用异步读取回调函数之后,在将第一块实际数据呈现给回调函数之前,会有大约一秒的延迟。在初始延迟之后,后续块几乎立即到达。

如何消除这种延迟?是用curl对post测试数据的神器吗?如何轻松测试 curl 对 posting 数据的性能?

您可以在 github 上找到最小的 source code 证明问题。这是我用来 post 数据到服务器的命令:

rcook$ curl -d @AsyncDaemon.h http://localhost:8787/foo

这是其输出示例(带注释):

rcook$ ./async_daemon 
1431387368.321863: AsyncDaemon constructor
1431387368.322446: receive thread beginning

*** It's waiting for a connection here.

1431387371.536191: begin transaction 0 on thread 24050
1431387371.536237: transaction 0 constructor
1431387371.536273: received 1206 byte request for /foo from 127.0.0.1:49402
1431387371.536312: invoked asynchronous read
1431387371.536321: end transaction handler
1431387371.536335: begin asynchronous read callback on thread 24050
1431387371.536348: read 0 bytes
1431387371.536386: invoked asynchronous read
1431387371.536394: end asynchronous read callback

*** The asynchronous read callback is invoked quickly, but gets no data.
*** There is then a pause of just over one second before the asynchronous
*** read callback is invoked again.

1431387372.537203: begin asynchronous read callback on thread 24050
1431387372.537253: read 1024 bytes
1431387372.537307: invoked asynchronous read
1431387372.537317: end asynchronous read callback

*** There is no significant delay when reading the next chunk.

1431387372.537429: begin asynchronous read callback on thread 24050
1431387372.537469: read 182 bytes
1431387372.537478: finished reading the body
1431387372.537746: wrote response
1431387372.537763: transaction 0 destructor
1431387372.537772: end asynchronous read callback

*** The server is then killed with a keyboard interrupt.

^C1431387375.382186: terminating with signal 2
1431387375.382231: initiating shutdown
1431387375.382241: stopping server
1431387375.382363: server run finished
1431387375.382423: receive thread ending
1431387375.382522: AsyncDaemon destructor

如您所见,在第一次调用异步读取回调(并接收到零字节数据,顺便说一句)之后,它会请求另一块输入。此时在输入到达之前有超过一秒的暂停,在本例中是从 1431387371.536394 到 1431387372.537203。那段时间发生了什么?我怎样才能消除这种延迟?

我在网上做了一些研究,运行 几个实验(同步与异步模式 cpp-netlib(无效),curl 与 libcurl(无效)),但没有能够找到答案。

更新:TCP 转储

根据 jxh 的建议,我 运行 在样本 t运行saction:

期间进行了 tcp 转储
00:28:01.304446 IP6 localhost.52265 > localhost.8787: Flags [S], seq 3956487146, win 43690, options [mss 65476,sackOK,TS val 395479802 ecr 0,nop,wscale 7], length 0
00:28:01.304461 IP6 localhost.8787 > localhost.52265: Flags [R.], seq 0, ack 3956487147, win 0, length 0
00:28:01.305014 IP localhost.49421 > localhost.8787: Flags [S], seq 1668603425, win 43690, options [mss 65495,sackOK,TS val 395479803 ecr 0,nop,wscale 7], length 0
00:28:01.305039 IP localhost.8787 > localhost.49421: Flags [S.], seq 4010788604, ack 1668603426, win 43690, options [mss 65495,sackOK,TS val 395479803 ecr 395479803,nop,wscale 7], length 0
00:28:01.305079 IP localhost.49421 > localhost.8787: Flags [.], ack 1, win 342, options [nop,nop,TS val 395479803 ecr 395479803], length 0
00:28:01.305185 IP localhost.49421 > localhost.8787: Flags [P.], seq 1:176, ack 1, win 342, options [nop,nop,TS val 395479803 ecr 395479803], length 175
00:28:01.305210 IP localhost.8787 > localhost.49421: Flags [.], ack 176, win 350, options [nop,nop,TS val 395479803 ecr 395479803], length 0
00:28:02.306555 IP localhost.49421 > localhost.8787: Flags [P.], seq 176:1382, ack 1, win 342, options [nop,nop,TS val 395480053 ecr 395479803], length 1206
00:28:02.306620 IP localhost.8787 > localhost.49421: Flags [.], ack 1382, win 1373, options [nop,nop,TS val 395480053 ecr 395480053], length 0
00:28:02.307223 IP localhost.8787 > localhost.49421: Flags [P.], seq 1:52, ack 1382, win 1373, options [nop,nop,TS val 395480053 ecr 395480053], length 51
00:28:02.307270 IP localhost.49421 > localhost.8787: Flags [.], ack 52, win 342, options [nop,nop,TS val 395480053 ecr 395480053], length 0
00:28:02.307494 IP localhost.8787 > localhost.49421: Flags [P.], seq 52:66, ack 1382, win 1373, options [nop,nop,TS val 395480053 ecr 395480053], length 14
00:28:02.307522 IP localhost.49421 > localhost.8787: Flags [.], ack 66, win 342, options [nop,nop,TS val 395480053 ecr 395480053], length 0
00:28:02.307765 IP localhost.8787 > localhost.49421: Flags [F.], seq 66, ack 1382, win 1373, options [nop,nop,TS val 395480053 ecr 395480053], length 0
00:28:02.307867 IP localhost.49421 > localhost.8787: Flags [F.], seq 1382, ack 67, win 342, options [nop,nop,TS val 395480053 ecr 395480053], length 0
00:28:02.307917 IP localhost.8787 > localhost.49421: Flags [.], ack 1383, win 1373, options [nop,nop,TS val 395480053 ecr 395480053], length 0

我对 tcpdump 不是很有经验,但看起来有 175 字节流向服务器(HTTP headers?),然后在延迟一秒多一点后,1206 字节流向服务器服务器,后跟一个具有最小延迟的 51 字节块,然后是服务器响应。

这告诉我延迟是在客户端引入的,很可能是在 curl 中。有谁知道为什么?

问题已解决,感谢@jxh 建议的调试和诊断技术。

--trace - --trace-time 添加到 curl 命令显示 curl 在等待服务器 return 100 Continue 响应之前等待服务器 --trace - --trace-time 发送其余请求:

01:31:44.043611 == Info: Connected to localhost (127.0.0.1) port 8787 (#0)
01:31:44.043726 => Send header, 175 bytes (0xaf)
0000: 50 4f 53 54 20 2f 66 6f 6f 20 48 54 54 50 2f 31 POST /foo HTTP/1
0010: 2e 31 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 .1..User-Agent: 
0020: 63 75 72 6c 2f 37 2e 33 35 2e 30 0d 0a 48 6f 73 curl/7.35.0..Hos
0030: 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 38 37 38 t: localhost:878
0040: 37 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 7..Accept: */*..
0050: 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 3a 20 Content-Length: 
0060: 31 32 30 36 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79 1206..Content-Ty
0070: 70 65 3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f pe: application/
0080: 78 2d 77 77 77 2d 66 6f 72 6d 2d 75 72 6c 65 6e x-www-form-urlen
0090: 63 6f 64 65 64 0d 0a 45 78 70 65 63 74 3a 20 31 coded..Expect: 1
00a0: 30 30 2d 63 6f 6e 74 69 6e 75 65 0d 0a 0d 0a    00-continue....
01:31:45.045626 == Info: Done waiting for 100-continue
01:31:45.045831 => Send data, 1206 bytes (0x4b6)

cpp-netlib(至少从 0.11.0 版开始)中的 known deficiency 不支持发送 curl 期望的 100 Continue 响应。

解决方案随后成为令人信服的 curl,无需等待 100 Continue 响应。正如我 discovered here 一样,将 -H 'Expect:' 添加到 curl 命令行就可以了。这样,整个交易大约需要一毫秒。

自从我回答了我自己的问题后,我将在几周内不接受我的答案,以便让其他人有机会做出更好的贡献。