Одинаковые 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 шт):
Я правильно понимаю, что где то в недрах дескриптора процесса есть таблица трансляции "клиентских файловых дескрипторов" в "настоящие, системные файловые дескрипторы"
Всё примерно так. Но «системными файловыми дескрипторами», называть их не принято, обычно используется термин открытое файловое описание/описатель открытого файла (open file description).
Таблица файловых дескрипторов одна на процесс (в терминах POSIX). В типовой реализации (в частности в linux) таблица — это буквально просто массив указателей на структуры связанные с открытым файлом (в широком смычле слова), а fd — это смещение в этом массиве. Такое представление прямо подразумевается в POSIX, например, тем требованием, что dup должен возвращать минимальный незанятый файловый дескриптор.
Несколько разных файловых дескрипторов в одном и том же или рызных процессах могут ссылаться на один и тот же открытый файл. В частности это происходит при дублировании оных с помощью dup(), создании нового процесса с помощью fork() или передаче дескриптора другому процессу через сокет AF_UNIX.