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?
The SO_REUSEADDR socket option allows a socket to forcibly bind to a port in use by another socket. The second socket calls setsockopt with the optname parameter set to SO_REUSEADDR and the optval parameter set to a boolean value of TRUE before calling bind on the same port as the original socket.
When a socket has both an IP address and a port number it is said to be 'bound to a port', or 'bound to an address'. A bound socket can receive data because it has a complete address. The process of allocating a port number to a socket is called 'binding'.
You only need to bind on the client if the server expects you to be coming from a specific port or port range.
bind() associates the socket with its local address [that's why server side binds, so that clients can use that address to connect to server.] connect() is used to connect to a remote [server] address, that's why is client side, connect [read as: connect to server] is used.
When you create a socket with socket()
, it has only a type and a protocol family. The ideal is to bind()
it to a local address:port too.
The error you mentioned 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).
What happens then is when you try to connect()
to the same host and same port, it uses the default socket's name/address/port/etc, but this combination is already in use by your zombie socket. To avoid this, you can change the local address:port used to establish the connection by calling bind()
after the socket creation, providing 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
so your socket won't block upon close
/closesocket
, it will just ignore queued data and (hopefully) close the fd. As a last resort you can adjust the TIME_WAIT
delay by changing the value of this registry key (highly discouraged!):
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With