views:

514

answers:

2

I'm binding a client TCP socket to a specific local port. To handle the situation where the socket remains in TIME_WAIT state for some time, I use setsockopt() with SO_REUSEADDR on a socket.

It works on Linux, but does not work on Windows, I get WSAEADDRINUSE on connect() call when the previous connection is still in TIME_WAIT.

MSDN is not exactly clear what should happen with client sockets:

[...] For server applications that need to bind multiple sockets to the same port number, consider using setsockopt (SO_REUSEADDR). Client applications usually need not call bind at all—connect chooses an unused port automatically. [...]

How do I avoid this?

+2  A: 

When you create a socket, it just have a type and a protocol family. The ideal is to bind() it to a local address/port too.

The error normally happens when the last connection to the same host/port didn't have a graceful shutdown (FIN/ACK FIN/ACK). In these cases, the socket stays in TIME_WAIT state for a certain period of time (OS dependent, but adjustable, at least on *nix).

What happens then is when you try to connect() to the same host and same port, it uses the default socket name/address/port/etc, but this combination is already in use by your zoombie socket. However, you can change the local address/port used to establish the connection. Just call bind() after the socket creation, and provide the sockaddr struct filled with your local address and a random port.

int main() {
    int ret, fd;
    struct sockaddr_in sa_dst;
    struct sockaddr_in sa_loc;
    char buffer[1024] = "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n";

    fd = socket(AF_INET, SOCK_STREAM, 0);

    // Local
    memset(&sa_loc, 0, sizeof(struct sockaddr_in));
    sa_loc.sin_family = AF_INET;
    sa_loc.sin_port = htons(LOCAL_RANDOM_PORT);
    sa_loc.sin_addr.s_addr = inet_addr(LOCAL_IP_ADDRESS);

    ret = bind(fd, (struct sockaddr *)&sa_loc, sizeof(struct sockaddr));
    assert(ret != -1);

    // Remote
    memset(&sa_dst, 0, sizeof(struct sockaddr_in));
    sa_dst.sin_family = AF_INET;
    sa_dst.sin_port = htons(80);
    sa_dst.sin_addr.s_addr = inet_addr("64.233.163.104"); // google :)

    ret = connect(fd, (struct sockaddr *)&sa_dst, sizeof(struct sockaddr));
    assert(ret != -1);

    send(fd, buffer, strlen(buffer), 0);
    recv(fd, buffer, sizeof(buffer), 0);
    printf("%s\r\n", buffer);
}

Update: As using a specific local port is a requirement, consider setting SO_LINGER with l_onoff=1 and l_linger=0, then your socket won't block upon close/closesocket, it will just ignore queued data, and (hopefully) close the fd. Or ultimately, adjusting the TIME_WAIT delay changing the value of this registry key:

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay
jweyrich
Yes, I know the mechanics behind it, but unfortunately, that's not an option for me. I *do* need to bind to the same local port every single time (see my comment to the question).
Alex B
Is adjusting the TIME_WAIT to a lower value acceptable?
jweyrich
IMHO it should never be the first thing considered...
Len Holgate
@Len don't be shy, we welcome suggestions :-)
jweyrich
Btw, I've updated the last lines to mention SO_LINGER. It came to mind after @Len's comment.
jweyrich
It's just that adjusting TIME_WAIT on a machine wide basis should be one of the last things to think about... There's often a better way; such as moving the active close to the other side of the connection or using linger to send an RST...
Len Holgate
+1  A: 

You don't specify which Windows platform you're running on, that may affect things as may the security principal that you're running under (i.e. are you admin?)...

This may help: http://blogs.msdn.com/wndp/archive/2005/08/03/Anthony-Jones.aspx

Len Holgate
Windows XP, not admin.
Alex B