在Node.js中处理多个并行的HTTP请求

21
我知道Node是非阻塞的,但我刚刚意识到http.listen(8000)的默认行为意味着所有HTTP请求都是逐个处理的。我知道我不应该感到惊讶(这就是端口的工作方式),但这确实让我严重地思考如何编写我的代码,以便能够处理多个并行的HTTP请求。
那么,编写服务器的最佳方法是什么,以便它不会占用80端口,而长时间运行的响应不会导致长请求队列?
为了说明问题,请尝试运行下面的代码,并在两个浏览器标签中同时加载它。
var http = require('http');
http.createServer(function (req, res) {
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.write("<p>" + new Date().toString() + ": starting response");
    setTimeout(function () {
        res.write("<p>" + new Date().toString() + ": completing response and closing connection</p>");
        res.end();
    }, 4000);
}).listen(8080);

1
http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/ - Paul
4个回答

17
你误解了 Node 的工作方式。上述代码可以接受数百或数千个客户端的 TCP 连接,读取 HTTP 请求,然后等待您设定的 4000 毫秒超时,随后发送响应。每个客户端会在约 4000 毫秒加上一小段时间内获得响应。在这段 setTimeout 时间(以及任何 I/O 操作期间),Node 可以继续处理。这包括接受其他 TCP 连接。我测试了你的代码,浏览器每个都在 4 秒内收到响应。如果你是这样认为它工作的话,第二个浏览器不需要 8 秒。
我使用键盘在 4 个标签页中尽可能快地运行了“curl -s localhost:8080”,时间戳中的秒数分别为:
1. 54 至 58 2. 54 至 58 3. 55 至 59 4. 56 至 00
这里没有问题,尽管我能理解你为什么会认为有问题。如果 Node 的工作方式与您的帖子所示相同,那么 Node 就会完全失效。
以下是另一种验证方法:
for i in 1 2 3 4 5 6 7 8 9 10; do curl -s localhost:8080 &;done                                                                                                                                                                       

6
没错,是 Chrome 导致了这个问题。至少对我来说,在第一个请求返回之前,第二个请求不会被发送。 - Andrew
即使是 Chrome,通常也应该允许每个源最多6个并发连接。 - Peter Lyons
1
我知道,这就是我感到惊讶并认为它是Node的原因。 - Andrew
你知道为什么Chrome不能同时发送两个请求吗? - edi9999
坦白地说,我不相信楼主声称Chrome正在按照我上面的评论序列化请求。浏览器和服务器之间的完整交互是复杂的。但是如果没有重复问题的代码,我无法进一步评论。 - Peter Lyons
@PeterLyons:请原谅我的愚蠢问题。服务器如何处理多个用户登录其前端应用程序?我的意思是它如何为每个用户实例化?您有任何文章/示例可以让我查看和理解吗? - harshes53

4

您的代码可以接受多个连接,因为工作是在setTimeout调用的回调函数中完成的。

但是,如果您进行了一个耗费大量资源的任务,那么确实会出现node.js无法接受其他多个连接的情况! setTimeout意外地释放了进程,因此node.js可以接受其他任务,并且您的代码在其他“线程”中执行。

我不知道正确的实现方式是什么。但这似乎是它的工作方式。


2

浏览器会阻止其他相同的请求。如果您从不同的浏览器调用它,那么它将并行工作。


0
我使用了以下代码来测试请求处理。
app.get('/', function(req, res) {
  console.log('time', MOMENT());  
  setTimeout( function() {
    console.log(data, '          ', MOMENT());
    res.send(data);
    data = 'changing';
  }, 50000);
  var data = 'change first';
  console.log(data);
});

由于此请求的处理时间不长,除了50秒的setTimeout和所有超时像通常一样一起处理。

响应3个请求一起 -

time moment("2017-05-22T16:47:28.893")
change first
time moment("2017-05-22T16:47:30.981")
change first
time moment("2017-05-22T16:47:33.463")
change first
change first            moment("2017-05-22T16:48:18.923")
change first            moment("2017-05-22T16:48:20.988")
change first            moment("2017-05-22T16:48:23.466")

在此之后,我进入了第二阶段......即,如果我的请求需要花费很长时间来处理同步文件或其他需要时间的事情。

app.get('/second', function(req, res) {
    console.log(data);
    if(req.headers.data === '9') {
        res.status(200);
        res.send('response from api');
    } else {
        console.log(MOMENT());
        for(i = 0; i<9999999999; i++){}
        console.log('Second MOMENT', MOMENT());
        res.status(400);
        res.send('wrong data');
    }

    var data = 'second test';
});

由于我的第一个请求仍在处理中,因此我的第二个请求没有被Node接受。 因此,我收到了以下2个请求的响应-
undefined
moment("2017-05-22T17:43:59.159")
Second MOMENT moment("2017-05-22T17:44:40.609")
undefined
moment("2017-05-22T17:44:40.614")
Second MOMENT moment("2017-05-22T17:45:24.643") 

因此,对于所有异步函数,在Node中都有一个虚拟线程,并且在完成先前请求的异步工作(如fs、mysql或调用API)之前,Node会接受其他请求。但是,它仍然保持单线程,并且在所有先前的请求完成之前不会处理其他请求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接