6

I have the following simple http server using Node.js:

var http = require('http');

var server = http.createServer(function(req, res) {
    var counter = 0;

    for(var i = 1; i <= 30; i++) {
        http.get({ host: "www.google.com" }, function(r) {
            counter++;
            res.write("Response " + counter + ": " + r.statusCode + "\n");
            if(counter == 30) res.end();                                                                                                                                   
        });
    }
});

server.listen(8000);

When I curl into my local host on port 8000, I do get the expected result of:

Response 1: 200
Response 2: 200
Response 3: 200
...
Response 30: 200

But when I try to curl in from another terminal while the first process is running, I see the console hang and wait for the first process to finish entirely before it starts receiving the same output.

My understanding was that since this is async code using callbacks that node could handle multiple requests in sync by processing them on the next tick of the event loop. And in fact I even watched a video of Ryan Dahl doing something similar with a hello world example. What's in my code that's making the server block?

4

3 回答 3

8

您的问题与阻止呼叫无关;这与您一次只能打开一定数量的连接到单个主机的事实有关。一旦达到最大打开连接数,其他异步调用http.get必须等到打开连接数再次下降,这发生在其他请求完成并触发它们的回调时。由于您创建新请求的速度比它们消耗的速度快,因此您会得到看似阻塞的结果。

这是我为测试而创建的程序的修改版本。(请注意,有一种更简单的方法可以解决您的问题,如 mtomis 所示——下面将对此进行更多介绍。)我添加了一些console.log日志记录,因此更容易判断事情的处理顺序;我也拒绝除 之外的所有请求/,因此favicon.ico请求被忽略。最后,我向许多不同的网站提出请求。

var http = require('http');

// http://mostpopularwebsites.net/1-50/
var sites = [
  "www.google.com", "www.facebook.com", "www.youtube.com",
  "www.yahoo.com", "www.blogspot.com", "www.baidu.com", "www.live.com",
  "www.wikipedia.org", "www.twitter.com", "www.qq.com", "www.msn.com",
  "www.yahoo.co.jp", "www.sina.com.cn", "www.google.co.in", "www.taobao.com",
  "www.amazon.com", "www.linkedin.com", "www.google.com.hk",
  "www.wordpress.com", "www.google.de", "www.bing.com", "www.google.co.uk",
  "www.yandex.ru", "www.ebay.com", "www.google.co.jp", "www.microsoft.com",
  "www.google.fr", "www.163.com", "www.google.com.br",
  "www.googleusercontent.com", "www.flickr.com"
];

var server = http.createServer(function(req, res) {
  console.log("Got a connection.");
  if(req.url != "/") {
    console.log("But returning because the path was not '/'");
    res.end();
    return;
  }

  var counter = 0;

  for(var i = 1; i <= 30; i++) {
    http.get({ host: sites[i] }, function(index, host, r) {
      counter++;
      console.log("Response " + counter + " from # " + index + " (" + host + ")");
      res.write("Response " + counter + " from # " + index + " (" + host + ")\n");
      if(counter == 30) res.end();
    }.bind(this, i, sites[i]));
  }
  console.log("Done with for loop.");
});

server.listen(8000);

我运行了这个程序并很快在两个不同的浏览器中访问了该页面(我还刷新了我的 DNS 缓存,因为测试运行得太快而无法获得良好的输出)。这是输出:

Got a connection.
Done with for loop.
Response 1 from # 8 (www.twitter.com)
Response 2 from # 1 (www.facebook.com)
Response 3 from # 12 (www.sina.com.cn)
Response 4 from # 4 (www.blogspot.com)
Response 5 from # 13 (www.google.co.in)
Response 6 from # 19 (www.google.de)
Response 7 from # 26 (www.google.fr)
Response 8 from # 28 (www.google.com.br)
Response 9 from # 17 (www.google.com.hk)
Response 10 from # 6 (www.live.com)
Response 11 from # 20 (www.bing.com)
Response 12 from # 29 (www.googleusercontent.com)
Got a connection.
Done with for loop.
Response 13 from # 10 (www.msn.com)
Response 14 from # 2 (www.youtube.com)
Response 15 from # 18 (www.wordpress.com)
Response 16 from # 16 (www.linkedin.com)
Response 17 from # 7 (www.wikipedia.org)
Response 18 from # 3 (www.yahoo.com)
Response 19 from # 15 (www.amazon.com)
Response 1 from # 6 (www.live.com)
Response 2 from # 1 (www.facebook.com)
Response 3 from # 8 (www.twitter.com)
Response 4 from # 4 (www.blogspot.com)
Response 20 from # 11 (www.yahoo.co.jp)
Response 21 from # 9 (www.qq.com)
Response 5 from # 2 (www.youtube.com)
Response 6 from # 13 (www.google.co.in)
Response 7 from # 10 (www.msn.com)
Response 8 from # 24 (www.google.co.jp)
Response 9 from # 17 (www.google.com.hk)
Response 10 from # 18 (www.wordpress.com)
Response 11 from # 16 (www.linkedin.com)
Response 12 from # 3 (www.yahoo.com)
Response 13 from # 12 (www.sina.com.cn)
Response 14 from # 11 (www.yahoo.co.jp)
Response 15 from # 7 (www.wikipedia.org)
Response 16 from # 15 (www.amazon.com)
Response 17 from # 9 (www.qq.com)
Response 22 from # 5 (www.baidu.com)
Response 23 from # 27 (www.163.com)
Response 24 from # 14 (www.taobao.com)
Response 18 from # 5 (www.baidu.com)
Response 19 from # 14 (www.taobao.com)
Response 25 from # 24 (www.google.co.jp)
Response 26 from # 30 (www.flickr.com)
Response 20 from # 29 (www.googleusercontent.com)
Response 21 from # 22 (www.yandex.ru)
Response 27 from # 23 (www.ebay.com)
Response 22 from # 19 (www.google.de)
Response 23 from # 21 (www.google.co.uk)
Response 24 from # 28 (www.google.com.br)
Response 25 from # 25 (www.microsoft.com)
Response 26 from # 20 (www.bing.com)
Response 27 from # 30 (www.flickr.com)
Response 28 from # 22 (www.yandex.ru)
Response 28 from # 27 (www.163.com)
Response 29 from # 25 (www.microsoft.com)
Response 29 from # 26 (www.google.fr)
Response 30 from # 21 (www.google.co.uk)
Response 30 from # 23 (www.ebay.com)
Got a connection.
But returning because the path was not '/'

正如您所看到的,除了我使用 hit 所花费的时间之外Alt+Tab Enter,回调是完全混合的——最好的异步、非阻塞 I/O。

[编辑]

正如 mtomis 所提到的,每个主机可以打开的最大连接数可以通过全局http.globalAgent.maxSockets. 只需将其设置为您希望每个主机能够处理的并发连接数,您观察到的问题就会消失。

于 2011-08-17T05:32:14.033 回答
5

Node.js 对每个主机的客户端连接有限制(默认情况下,每个主机有 5 个连接),如下所述:http ://nodejs.org/docs/v0.5.4/api/http.html#agent.maxSockets

您的第二个 curl 进程挂起直到第一个完成的原因是因为第一个进程将 30 个请求排队,其中 5 个可以同时处理,因此第二个进程的下 30 个请求在第一个请求完成之前无法处理完全的。在您的示例中,如果您设置http.globalAgent.maxSockets = 60;,则呼叫将同时处理。

于 2011-08-17T08:26:03.690 回答
0

好吧,您实际上并没有将我认为的请求生成为可以回调的东西。您只有一个事件处理程序,并且它连续运行一个循环。

你能找到 Ryan Dahl 的演讲地点吗?

于 2011-08-17T03:41:48.417 回答