5

Working with .net, I noticed that an attempt to connect to a port that is not listened on always takes one 1 second. To check whether this is a problem in the .net abstraction or whether it is a lower level issue, and to compare it with linux (where an unsuccessful telnet takes something like 3ms), I used a node.js script to connect to port

  • 12345, a port no process is listening on
  • 80, a port that is being listened on

Results for Windows:

Connecting to 127.0.0.1:12345
#3: error elapsed: 1000ms, Error: connect ECONNREFUSED
#2: error elapsed: 1002ms, Error: connect ECONNREFUSED
#4: error elapsed: 1003ms, Error: connect ECONNREFUSED
#1: error elapsed: 1007ms, Error: connect ECONNREFUSED
#0: error elapsed: 1015ms, Error: connect ECONNREFUSED

Connecting to 127.0.0.1:80
#0: connect elapsed: 8ms
#1: connect elapsed: 1ms
#2: connect elapsed: 3ms
#3: connect elapsed: 4ms
#4: connect elapsed: 6ms

Results for Linux:

Connecting to 127.0.0.1:12345
#4: error elapsed: 0ms, Error: connect ECONNREFUSED
#3: error elapsed: 1ms, Error: connect ECONNREFUSED
#2: error elapsed: 1ms, Error: connect ECONNREFUSED
#1: error elapsed: 1ms, Error: connect ECONNREFUSED
#0: error elapsed: 3ms, Error: connect ECONNREFUSED

Connecting to 127.0.0.1:80
#4: connect elapsed: 0ms
#3: connect elapsed: 0ms
#2: connect elapsed: 0ms
#1: connect elapsed: 1ms
#0: connect elapsed: 2ms

Node.js source:

var net = require('net');
var host = process.argv[2];
var port = Number(process.argv[3]);
console.log("Connecting to %s:%d", host, port);
for (i = 0; i < 5; i++)
{
  (function(i) {
    var date = +new Date;
    var client = net.connect({host: host, port: port});
    client.on('error', function(msg)
    {
      console.log("#%d: error elapsed: %dms, %s", i, new Date - date, msg);
    });
    client.on('connect', function()
    {
      console.log("#%d: connect elapsed: %dms", i, new Date - date);
    });
  })(i);
}

A couple of notes

  • I also tried remote IP addresses, the results are similar to those shown above for localhost
  • in the case of node.js and the way I wrote it, the (asynchronous) connection attempts run in parallel, but in case of .net, I was testing synchronously, i.e. one attempt after the other, and the result was the same: 1sec every time

Any idea why Windows is slow here? Is that some kind of deliberate throttling?

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
  • 1
    Interesting. Have you tried seeing what happens if another program starts listening on that port during that one second delay? (Also, I take it the remote addresses you were testing with did not have a firewall configured?) – Harry Johnston Oct 18 '13 at 03:07
  • @HarryJohnston Your comment lead me to the answer. Thanks. – Evgeniy Berezovsky Oct 18 '13 at 04:19

2 Answers2

5

@HarryJohnston's comment lead me to rewrite the program to see

what happens if another program starts listening on that port during that one second delay

And it seems, on Windows, a call to connect() attempts to connect to an unbound port up to 3 times in 500ms intervals. If that third attempt also fails, it gives up.

Here's the log of a test run, where I occasionally and randomly started and stopped a process that listens on port 12345:

Connecting to 192.168.77.11:12345
#1: error elapsed: 1008ms, Error: connect ECONNREFUSED
#2: error elapsed: 1001ms, Error: connect ECONNREFUSED
#3: error elapsed: 1004ms, Error: connect ECONNREFUSED
#4: connect elapsed: 1001ms
#5: connect elapsed: 1000ms
#6: connect elapsed: 500ms
#7: error elapsed: 1000ms, Error: connect ECONNREFUSED
#8: connect elapsed: 1001ms
#9: error elapsed: 1000ms, Error: connect ECONNREFUSED
#10: connect elapsed: 500ms
#11: error elapsed: 1000ms, Error: connect ECONNREFUSED
#12: error elapsed: 1000ms, Error: connect ECONNREFUSED
#13: connect elapsed: 500ms
#14: error elapsed: 1000ms, Error: connect ECONNREFUSED
#15: connect elapsed: 500ms
#16: error elapsed: 1000ms, Error: connect ECONNREFUSED
#17: connect elapsed: 500ms
#18: connect elapsed: 1000ms
#19: connect elapsed: 1000ms
#20: connect elapsed: 500ms
#21: connect elapsed: 1011ms
#22: connect elapsed: 1000ms
#23: error elapsed: 1000ms, Error: connect ECONNREFUSED

And the modified source code:

var net = require('net');
var host = process.argv[2];
var port = Number(process.argv[3]);
console.log("Connecting to %s:%d", host, port);
var connect = function(i) {
    var date = +new Date;
    var client = net.connect({host: host, port: port});
    client.on('error', function(msg)
    {
      console.log("#%d: error elapsed: %dms, %s", i, new Date - date, msg);
      connect(i + 1);
    });
    client.on('connect', function()
    {
      console.log("#%d: connect elapsed: %dms", i, new Date - date);
      connect(i + 1);
    });
  };
connect(1);
Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
  • You'll find that it does that for all error codes, but with much bigger timeouts for the other ones, totaling around a minute, something like 6+12+32 or 8+16+32. – user207421 Oct 18 '13 at 04:37
  • @EJP You mean errors of `connect()` other than *connection refused* result in those timeouts totaling almost a minute? Any examples you can share? – Evgeniy Berezovsky Oct 18 '13 at 05:23
  • A connection timeout takes a minute, but if you can set it up you will see it consists of three separate attempts. – user207421 Oct 18 '13 at 05:48
4

According to this article by Microsoft, the reason is that the RFC leaves it up to the implementation to decide what to do exactly when a connection is refused, and on Windows the default is to retry 3 times before failing.

PhilR
  • 5,375
  • 1
  • 21
  • 27
Lauri Nurmi
  • 566
  • 1
  • 3
  • 14