Uważam, że idea, że gniazdo jest niedostępne dla programu, polega na umożliwieniu dotarcia segmentów danych TCP wciąż będących w tranzycie i odrzucenia ich przez jądro. Oznacza to, że aplikacja może wywoływać close(2)
na gnieździe, ale opóźnienia routingu lub nieszczęścia w celu kontrolowania pakietów lub co możesz pozwolić drugiej stronie połączenia TCP na wysyłanie danych przez pewien czas. Aplikacja wskazała, że nie chce już zajmować się segmentami danych TCP, dlatego jądro powinno je po prostu odrzucić, gdy tylko się pojawią.
Zhakowałem mały program w C, który możesz skompilować i użyć, aby sprawdzić, jak długi jest limit czasu:
#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;
}
Wypróbowałem ten program na 3 różnych komputerach i otrzymuję zmienny czas, od 55 do 59 sekund, kiedy jądro odmawia zezwolenia użytkownikowi innemu niż root na ponowne otwarcie gniazda. Skompilowałem powyższy kod do pliku wykonywalnego o nazwie „otwieracz” i uruchomiłem go w następujący sposób:
./opener -p 7896; ./opener -p 7896
Otworzyłem kolejne okno i zrobiłem to:
telnet otherhost 7896
To powoduje, że pierwsza instancja „otwieracza” akceptuje połączenie, a następnie je zamyka. Druga instancja „otwieracza” bind(2)
co sekundę próbuje połączyć się z portem TCP 7896. „otwieracz” zgłasza od 55 do 59 sekund opóźnienia.
Googlując się po okolicy, stwierdzam, że ludzie zalecają to:
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
aby skrócić ten interwał. Nie działało to dla mnie. Z 4 maszyn linuksowych, do których miałem dostęp, dwie miały 30, a dwie 60. Ustawiłem też tę wartość na 10. Nie ma różnicy w programie „otwieracza”.
Robiąc to:
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
zmienił rzeczy. Drugi „otwieracz” potrzebował tylko około 3 sekund, aby uzyskać nowe gniazdo.
man 2 bind
jeśli mi nie wierzysz. Trzeba przyznać, że prawdopodobnie nie jest to pierwsza rzecz, o której ludzie uniksowi myślą, kiedy ktoś mówi „wiąż”, co jest dość sprawiedliwe.