Одинаковые socket descriptor после каждого вызова fork (С, сокеты)

Господа,

казалось бы, что необычного может быть в сокете... Однако же! (Ubuntu 18, x64, gcc 9.3)

Тут, разбирая учебный пример из известной книжки Стивенса "UNIX. Разработка сетевых приложений" я завис над вот каким примером: смотрим в листинг 4.3. Это - пример сервера, который на каждый accept() создает свой процесс, который обслуживает пользователя, вызовом fork().

При этом accept() возвращает файловый дескриптор - просто целое число.

И при параллельном вызове сервера с нескольких клиентов эти фйаловые дескрипторы.... одинаковые!

То есть, я запускаю сервер. Эмулирую клиента командой telnet localhost 2000.

Сервер мне говорит, что клиент приконнектился с такого то порта и connfd у него такой то.

Делает fork, передавая ему connfd - там по этому connfd процедура приёма файла (в примере - просто печать на экран) разберется. И снова ждет соединений.

Потом я запускаю второй telnet localhost 2000. Он коннектится к серверу с другого порта, а accept возвращает тот же самый connfd.

Я правильно понимаю, что где то в недрах дескриптора процесса есть таблица трансляции "клиентских файловых дескрипторов" в "настоящие, системные файловые дескрипторы", и поэтому то, что двум параллельно работающим процессам отдали один и тот же дескриптор - совсем не значит, что они будут работать с одним и тем же сокетом/файлом?

Пример сервера:

    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <errno.h>

    void doit(int connfd, in_port_t port){
        printf ("Hi from file receiving proc!\n");

        char ch;
        while ( read(connfd, &ch, 1) > 0 )
            printf ("%c", ch);
        printf("Done\n");
    }


    int main(){

        int listenfd = socket(AF_INET,SOCK_STREAM,0); //серверный сокет
        struct sockaddr_in server_addr ;

        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(2000);

        if (bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr))<0){  // параметры сокета
            printf ("Error in bind\n"); return 0; } 

        if (listen (listenfd, 5)<0) { // 5 - глубина очереди
            printf ("Error in listen\n"); return 0; }

        printf ("Server started!\n" );

        for (;;){
            struct sockaddr_in client_addr;
            socklen_t addrlen = sizeof(client_addr);
            int  connfd=accept(listenfd, (struct sockaddr*)&client_addr, &addrlen);

            printf ("Connection with connfd=%d from port=%d accepted!\n", connfd, client_addr.sin_port);

            pid_t pid;
            if ((pid=fork())==0){   // fork разваивает процесс
                close(listenfd);

                doit (connfd, client_addr.sin_port);
                
                close (connfd);
                _exit(0);
            }
            close(connfd);
        }
    }

Ответы (1 шт):

Автор решения: Fat-Zer

Я правильно понимаю, что где то в недрах дескриптора процесса есть таблица трансляции "клиентских файловых дескрипторов" в "настоящие, системные файловые дескрипторы"

Всё примерно так. Но «системными файловыми дескрипторами», называть их не принято, обычно используется термин открытое файловое описание/описатель открытого файла (open file description).

Таблица файловых дескрипторов одна на процесс (в терминах POSIX). В типовой реализации (в частности в linux) таблица — это буквально просто массив указателей на структуры связанные с открытым файлом (в широком смычле слова), а fd — это смещение в этом массиве. Такое представление прямо подразумевается в POSIX, например, тем требованием, что dup должен возвращать минимальный незанятый файловый дескриптор.

Несколько разных файловых дескрипторов в одном и том же или рызных процессах могут ссылаться на один и тот же открытый файл. В частности это происходит при дублировании оных с помощью dup(), создании нового процесса с помощью fork() или передаче дескриптора другому процессу через сокет AF_UNIX.

→ Ссылка