Bağlandıktan sonra bağlanan bir TCP yerel soket adresi ne kadar süre kullanılamıyor?


13

Linux'ta (canlı sunucularım RHEL 5.5 üzerindedir - aşağıdaki LXR bağlantıları bunun içindeki çekirdek sürümüne aittir), man 7 ipdiyor:

Bağlı bir TCP yerel yuva adresi, SO_REUSEADDR bayrağı ayarlanmadığı sürece kapatıldıktan sonra bir süre kullanılamaz.

Ben kullanmıyorum SO_REUSEADDR. "Bir süre" ne kadar sürer? Ne kadar sürdüğünü nasıl öğrenebilirim ve nasıl değiştirebilirim?

Bunu araştırıyorum ve hiçbiri bunu bir uygulama programcısının bakış açısından gerçekten açıklayamayan birkaç bilgi bulduk. Zekâ için:

  • TCP_TIMEWAIT_LEN içinde net/tcp.h"ne kadar süre-BEKLE devleti yıkmak için beklemek" olduğunu ve "yaklaşık 60 saniye" de sabittir
  • / proc / sys / net / ipv4 / tcp_fin_timeout "Bizim tarafımızdan kapatılmışsa, soketi FIN-WAIT-2 durumunda tutma zamanı" ve "Varsayılan değer 60 saniyedir"

Tökezlediğimde, çekirdeğin TCP yaşam döngüsü modeli ile programcının bağlantı noktası modeli arasındaki boşluğu doldurmakta, yani bu durumların "bir süre" ile nasıl ilişkili olduğunu anlamakta.


@Caleb: Etiketlerle ilgili olarak, bağlayıcı bir sistem çağrısıdır! man 2 bindBana inanmıyorsan dene . Kuşkusuz, muhtemelen birileri "bağla" derken, bu kadar adil olan unix insanların ilk düşündüğü şey değil.
Tom Anderson

Alternatif kullanımlarının farkındaydım bind, ancak buradaki etiket özellikle DNS sunucusuna uygulanır. Her olası sistem çağrısı için etiketimiz yok.
Caleb

Yanıtlar:


14

Soketin bir program için kullanılamaması fikrinin, hala transit olan TCP veri segmentlerinin gelmesine ve çekirdek tarafından atılmasına izin vermek olduğuna inanıyorum. Yani, bir uygulamanın close(2)bir soketi çağırması mümkündür , ancak yönlendirme gecikmeleri veya paketleri kontrol etmek veya yanlış bir şekilde TCP bağlantısının diğer tarafının bir süre veri göndermesine izin verebilirsiniz. Uygulama artık TCP veri segmentleriyle uğraşmak istemediğini belirtti, bu yüzden çekirdek sadece geldikçe onları atmalıdır.

C derlemek ve zaman aşımı süresini görmek için kullanabileceğiniz küçük bir program kesmek:

#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;
}

Bu programı 3 farklı makinede denedim ve çekirdek olmayan bir kullanıcının bir soketi yeniden açmasına izin vermeyi reddettiği 55 ila 59 saniye arasında değişken bir zaman alıyorum. Yukarıdaki kodu "opener" adlı bir yürütülebilir dosya derledi ve böyle koştu:

./opener -p 7896; ./opener -p 7896

Başka bir pencere açtım ve bunu yaptım:

telnet otherhost 7896

Bu, ilk "açıcı" örneğinin bir bağlantıyı kabul etmesine ve ardından kapatmasına neden olur. İkinci "açıcı" örneği bind(2)her saniye 7896 numaralı TCP bağlantı noktasına gitmeye çalışır . "açıcı" 55 ila 59 saniye gecikme bildiriyor.

Etrafta dolaşırken, insanların bunu yapmasını önerdiğini görüyorum:

echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout

bu aralığı azaltmak için. Benim için işe yaramadı. Erişebildiğim 4 linux makineden ikisinin 30 ve ikisinin 60'ı vardı. Ayrıca bu değeri 10'a kadar düşürdüm. "Açıcı" programında fark yok.

Bunu yapmak:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle

bazı şeyleri değiştirdi. İkinci "açıcı" yeni yuvasının alınması sadece 3 saniye sürdü.


3
Kullanılamazlık döneminin amacının ne olduğunu (kabaca) anlıyorum. Bilmek istediğim şey tam olarak o dönemin Linux'ta ne kadar sürdüğü ve nasıl değiştirilebileceğidir. Vikipedi sayfasından TCP ile ilgili bir sayı ile ilgili sorun, mutlaka genelleştirilmiş bir değer olması ve özel platformum için kesinlikle doğru olan bir şey olmamasıdır.
Tom Anderson

spekülasyonlarınız ilginçti! sadece onları kaldırmak yerine bir başlık ile işaretleyin, nedenini araştırmanın yollarını verir!
Philippe Gachoud
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.