Credo che l'idea che il socket non sia disponibile per un programma sia di consentire a tutti i segmenti di dati TCP ancora in transito di arrivare e di essere scartati dal kernel. Cioè, è possibile che un'applicazione chiami close(2)
su un socket, ma i ritardi di instradamento o gli inconvenienti per controllare i pacchetti o ciò che è possibile consentire all'altro lato di una connessione TCP di inviare dati per un po '. L'applicazione ha indicato che non vuole più occuparsi dei segmenti di dati TCP, quindi il kernel dovrebbe semplicemente scartarli appena entrano.
Ho hackerato un piccolo programma in C che puoi compilare e usare per vedere quanto tempo è il timeout:
#include <stdio.h> /* fprintf() */
#include <string.h> /* strerror() */
#include <errno.h> /* errno */
#include <stdlib.h> /* strtol() */
#include <signal.h> /* signal() */
#include <sys/time.h> /* struct timeval */
#include <unistd.h> /* read(), write(), close(), gettimeofday() */
#include <sys/types.h> /* socket() */
#include <sys/socket.h> /* socket-related stuff */
#include <netinet/in.h>
#include <arpa/inet.h> /* inet_ntoa() */
float elapsed_time(struct timeval before, struct timeval after);
int
main(int ac, char **av)
{
int opt;
int listen_fd = -1;
unsigned short port = 0;
struct sockaddr_in serv_addr;
struct timeval before_bind;
struct timeval after_bind;
while (-1 != (opt = getopt(ac, av, "p:"))) {
switch (opt) {
case 'p':
port = (unsigned short)atoi(optarg);
break;
}
}
if (0 == port) {
fprintf(stderr, "Need a port to listen on\n");
return 2;
}
if (0 > (listen_fd = socket(AF_INET, SOCK_STREAM, 0))) {
fprintf(stderr, "Opening socket: %s\n", strerror(errno));
return 1;
}
memset(&serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
gettimeofday(&before_bind, NULL);
while (0 > bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
fprintf(stderr, "binding socket to port %d: %s\n",
ntohs(serv_addr.sin_port),
strerror(errno));
sleep(1);
}
gettimeofday(&after_bind, NULL);
printf("bind took %.5f seconds\n", elapsed_time(before_bind, after_bind));
printf("# Listening on port %d\n", ntohs(serv_addr.sin_port));
if (0 > listen(listen_fd, 100)) {
fprintf(stderr, "listen() on fd %d: %s\n",
listen_fd,
strerror(errno));
return 1;
}
{
struct sockaddr_in cli_addr;
struct timeval before;
int newfd;
socklen_t clilen;
clilen = sizeof(cli_addr);
if (0 > (newfd = accept(listen_fd, (struct sockaddr *)&cli_addr, &clilen))) {
fprintf(stderr, "accept() on fd %d: %s\n", listen_fd, strerror(errno));
exit(2);
}
gettimeofday(&before, NULL);
printf("At %ld.%06ld\tconnected to: %s\n",
before.tv_sec, before.tv_usec,
inet_ntoa(cli_addr.sin_addr)
);
fflush(stdout);
while (close(newfd) == EINTR) ;
}
if (0 > close(listen_fd))
fprintf(stderr, "Closing socket: %s\n", strerror(errno));
return 0;
}
float
elapsed_time(struct timeval before, struct timeval after)
{
float r = 0.0;
if (before.tv_usec > after.tv_usec) {
after.tv_usec += 1000000;
--after.tv_sec;
}
r = (float)(after.tv_sec - before.tv_sec)
+ (1.0E-6)*(float)(after.tv_usec - before.tv_usec);
return r;
}
Ho provato questo programma su 3 macchine diverse e ho un tempo variabile, tra 55 e 59 secondi, quando il kernel rifiuta di consentire a un utente non root di riaprire un socket. Ho compilato il codice sopra in un eseguibile chiamato "apri", e l'ho eseguito in questo modo:
./opener -p 7896; ./opener -p 7896
Ho aperto un'altra finestra e ho fatto questo:
telnet otherhost 7896
Ciò fa sì che la prima istanza di "apri" accetti una connessione, quindi la chiuda. La seconda istanza di "apri" tenta di bind(2)
accedere alla porta TCP 7896 ogni secondo. "apri" riporta da 55 a 59 secondi di ritardo.
Cercando su Google, trovo che le persone raccomandino di fare questo:
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
per ridurre tale intervallo. Non ha funzionato per me. Delle 4 macchine Linux a cui ho avuto accesso, due avevano 30 e due avevano 60. Ho anche impostato quel valore a partire da 10. Nessuna differenza rispetto al programma "apri".
Facendo questo:
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
ha cambiato le cose. Il secondo "apri" ha impiegato solo circa 3 secondi per ottenere il suo nuovo socket.
man 2 bind
se non mi credi. Certo, probabilmente non è la prima cosa a cui pensano le persone unix quando qualcuno dice "legare", in modo abbastanza equo.