7.5. Основные функции для работы с сокетами

Хотя FreeBSD предлагает различные функции для работы с сокетами, нам всего лишь потребуется 4 для ''открытия'', а в некоторых случаях только 2 функции.

7.5.1. Различие между клиентом и сервером

Обычно одним из концов соединения на основе сокетов является сервер, а другим клиент.

7.5.1.1. Общие элементы

7.5.1.1.1. socket

Функция, используемая как клиентом, так и сервером - socket(2). Она объявлена, как:

int socket(int domain, int type, int protocol);

Возвращаемое значение того же типа, что и у функции open, т.е. целого. FreeBSD выделяет это значение из той же таблицы, откуда выделяются значения для дескрипторов файлов. Эта возможность позволяет относиться к сокетам, также, как и к файлам.

Аргумент domain указывает на семейство протоколов, которое мы хотим использовать. Некоторые из существующих семейств зависимы от компании-разработчика, другие общеизвестные. Они объявлены в sys/socket.h.

Используйте PF_INET для UDP, TCP и других интернет протоколов (IPv4).

Существует 5 значений, определенных для аргумента type, объявленных в файле sys/socket.h. Все они начитаются с ''SOCK_''. Наиболее общий - SOCK_STREAM, который говорит системе, что мы хотим использовать надёжный потоковый сервис доставки, каковым является TCP при использовании с PF_INET.

SOCK_DGRAM указывает на использование сервиса доставки дейтаграмм без установления соединения (в нашем случае это UDP).

Если вы хотите работать с низкоуровневыми протоколами, такими, как IP или с сетевыми интерфейсами (например, Ethernet), вам нужно указать SOCK_RAW.

И наконец, аргумент protocol зависит от предыдущих двух аргументов, и не всегда является значимым. В этом случае используйте значение 0.

Неприсоединённый сокет: Нигде, при вызове функции socket, мы не указали к какой системе мы хотим присоединиться. Наш только что созданный сокет остаётся неприсоединённым.

Это аналогично ситуации, когда мы подключили модем к телефонной линии и не дали ему ни команду дозвона, ни команду ответа на входящие звонки.

7.5.1.1.2. sockaddr

Различные функции для работы с сокетами предполагают использование адреса (указатель, в терминологии языка С) маленького участка памяти. Различные объявления из файла sys/socket.h в качестве этого указателя используют адрес struct sockaddr. Описание данной структуры можно найти в этом же файле:

/*
 * Structure used by kernel to store most
 * addresses.
 */
struct sockaddr {
    u_char      sa_len;     /* total length */
    sa_family_t sa_family;  /* address family */
    char        sa_data[14];    /* actually longer; address value */
};
#define SOCK_MAXADDRLEN 255     /* longest possible addresses */

Обратите внимание на неясность с которой объявлено поле sa_data. Данное поле представляет всего лишь массив из 14 байт, с комментарием, говорящим, что на самом деле количество байт может превышать 14.

Это неясность довольно хорошо продумана. Сокеты это очень мощный интерфейс. Хотя многие возможно думают, что это не больше, чем Internet интерфейс--большинство приложений тоже используют их--сокеты могут использоваться для любого вида взаимодействий между процессами, из которых Internet (или, если быть более точным, IP) только одна разновидность.

В файле sys/socket.h присутствует список различных типов протоколов, которые сокетами используются, как адресные семейства (address families). Он находится перед определением структуры sockaddr:

/*
 * Address families.
 */
#define AF_UNSPEC   0       /* unspecified */
#define AF_LOCAL    1       /* local to host (pipes, portals) */
#define AF_UNIX     AF_LOCAL    /* backward compatibility */
#define AF_INET     2       /* internetwork: UDP, TCP, etc. */
#define AF_IMPLINK  3       /* arpanet imp addresses */
#define AF_PUP      4       /* pup protocols: e.g. BSP */
#define AF_CHAOS    5       /* mit CHAOS protocols */
#define AF_NS       6       /* XEROX NS protocols */
#define AF_ISO      7       /* ISO protocols */
#define AF_OSI      AF_ISO
#define AF_ECMA     8       /* European computer manufacturers */
#define AF_DATAKIT  9       /* datakit protocols */
#define AF_CCITT    10      /* CCITT protocols, X.25 etc */
#define AF_SNA      11      /* IBM SNA */
#define AF_DECnet   12      /* DECnet */
#define AF_DLI      13      /* DEC Direct data link interface */
#define AF_LAT      14      /* LAT */
#define AF_HYLINK   15      /* NSC Hyperchannel */
#define AF_APPLETALK    16      /* Apple Talk */
#define AF_ROUTE    17      /* Internal Routing Protocol */
#define AF_LINK     18      /* Link layer interface */
#define pseudo_AF_XTP   19      /* eXpress Transfer Protocol (no AF) */
#define AF_COIP     20      /* connection-oriented IP, aka ST II */
#define AF_CNT      21      /* Computer Network Technology */
#define pseudo_AF_RTIP  22      /* Help Identify RTIP packets */
#define AF_IPX      23      /* Novell Internet Protocol */
#define AF_SIP      24      /* Simple Internet Protocol */
#define pseudo_AF_PIP   25      /* Help Identify PIP packets */
#define AF_ISDN     26      /* Integrated Services Digital Network*/
#define AF_E164     AF_ISDN     /* CCITT E.164 recommendation */
#define pseudo_AF_KEY   27      /* Internal key-management function */
#define AF_INET6    28      /* IPv6 */
#define AF_NATM     29      /* native ATM access */
#define AF_ATM      30      /* ATM */
#define pseudo_AF_HDRCMPLT 31       /* Used by BPF to not rewrite headers
                     * in interface output routine
                     */
#define AF_NETGRAPH 32      /* Netgraph sockets */

#define AF_MAX      33

Для IP используется AF_INET. Это макроопределение означает число 2.

Это адресное семейство, используемое в поле sa_family структуры sockaddr, которая определяет, как будут использоваться байты из sa_data.

Когда адресным семейством является AF_INET мы можем использовать struct sockaddr_in, определённую в netinet/in.h вместо sockaddr:

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
    u_char  sin_len;
    u_char  sin_family;
    u_short sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};

Графически её организацию можно представить так:

Три значимых поля: sin_family - 1 байт структуры, sin_port - 16-битное значение, расположенное во 2 и 3 байтах и sin_addr - 32-битное целое представление IP адреса, занимающее байты с 4 по 7.

Давайте попытаемся их заполнить. Предположим, что мы пишем клиент для протокола daytime, который просто будет показывать текущую дату и время, выданное сервером на 13 порт. Мы хотим использовать TCP/IP, и поэтому нам нужно указать AF_INET в поле адресного семейства. AF_INET определён, как число 2. Давайте будем использовать IP адрес 192.43.244.18 сервера времени федерального правительства Соединённых Штатов (time.nist.gov).

Поле sin_addr объявлено в виде значения, имеющего тип struct in_addr, который определён в файле netinet/in.h:

/*
 * Internet address (a structure for historical reasons)
 */
struct in_addr {
    in_addr_t s_addr;
};

in_addr_t - это 32-битное целое.

192.43.244.18 - это всего лишь удобная форма выражения 32-битного целого, путём перечисления всех его байт, начиная с самого значимого.

До этого момента мы не слишком много внимания уделяли sockaddr. Наш компьютер не хранит короткие целые в виде одного 16-битного объекта, а в виде 2-байтовой последовательности. Аналогично, 32-битные целые хранятся в виде 4-байтовой последовательности.

Предположим, что мы написали что-то типа этого:

   sa.sin_family      = AF_INET;
    sa.sin_port        = 13;
    sa.sin_addr.s_addr = (((((192 << 8) | 43) << 8) | 244) << 8) | 18;

Каким будет результат?

Да, конечно это зависит от представления целых. На Pentium® или на другом, основанном на x86, компьютере результат будет следующим:

На другой системе это может выглядеть так:

На PDP результат может выглядеть по-другому. Но, два выше приведённых способа встречаются наиболее часто.

Обычно, желая написать переносимый код, программисты не обращают внимания на эти различия и не обрабатывают их (кроме случаев, когда программа пишется на ассемблере). Увы, вы не можете легко опустить эту обработку при программировании сокетов.

Почему?

Потому что, при соединении с другим компьютером, вы обычно не знаете, хранит ли он данные, начиная с самого значимого байта (most significant byte, MSB) или с наименее значимого байта (least significant byte, LSB).

Возможно вы спросите: ''Неужели сокеты не сделают это за меня?''

Не сделают.

Хотя этот ответ может удивить вас, запомните, что общий интерфейс сокетов понимает только поля sa_len и sa_family структуры sockaddr. И здесь вам не надо беспокоиться о порядке байт (конечно, на FreeBSD sa_family занимает только 1 байт в любом случае, но многие другие UNIX® системы не имеют поля sa_len и используют 2 байта под sa_family и предполагают, что данные, независимо от порядка следования являются родными для компьютера).

Но остальное - просто sa_data[14] до конца действия интерфейса сокетов. В зависимости от адресного семейства, сокеты просто пересылают эти данные к месту назначения.

Когда мы вводим номер порта мы хотим, чтобы другой компьютер знал какой сервис мы запрашиваем. И, когда мы являемся сервером, мы получаем номер порта, чтобы знать какой наш сервис запросил другой компьютер. В любом случае сокеты пересылают номер порта в виде данных и они его не интерпретируют.

Аналогично, когда мы вводим IP адрес, мы хотим, чтобы любой хост, который будет пересылать наши данные знал, куда их направлять. Сокеты, опять же, только пересылают его, как данные.

По этой причине, мы (программисты, а не сокеты) должны различать порядок байт (byte order), используемый нашим компьютером и условный порядок байт, используемый для отсылки данных другому компьютеру.

Мы будем называть порядок байт, используемый нашим компьютером - host byte order (порядок байт хоста) или просто host order.

Существует договор пересылки данных посредством IP начиная с MSB. Это порядок байт договоримся называть network byte order (сетевой порядок байт), или просто network order.

Сейчас, если бы мы скомпилировали выше приведённый код на Intel компьютере, то наш порядок байт хоста выдал бы:

Но сетевой порядок байт требует представления данных, начиная с MSB:

К сожалению, наш порядок байт хоста точная противоположность сетевому порядку байт.

Есть несколько способов обработки таких моментов. Способ, представленный ниже переворачивает значения в нашем коде:

   sa.sin_family      = AF_INET;
    sa.sin_port        = 13 << 8;
    sa.sin_addr.s_addr = (((((18 << 8) | 244) << 8) | 43) << 8) | 192;

Эта хитрость заставит компилятора поменять порядок на сетевой порядок байт. В некоторых случаях этот способ используется (например, при программировании на ассемблере). Однако, в большинстве случаях это может вызвать проблемы.

Предположим, что вы написали программу на C, работающую с сокетами. Вы знаете, что она будет запускаться на Pentium, и поэтому, принудительно перевели все константы в сетевой порядок байт. Она работает хорошо.

Затем, в один прекрасный день, ваш старенький Pentium становится непригодным. Вы меняете его на систему, у которой порядок байт хоста совпадает с сетевым порядком байт. Вам нужно будет перекомпилировать всё ваше программное обеспечение. После этого все программы будут продолжать работать нормально, за исключением написанной вами.

Вы наверняка забыли, что изменили все ваши константы на противоположные порядку байт хоста. Вы проводите много времени, вырывая волосы на голове, взываете ко всем богам, имена которых вы только слышали (некоторых придумали сами), бьёте по монитору дубинкой и проводите другие обычные церемонии, стараясь понять, почему что-то, что работало столь хорошо внезапно совсем перестаёт работать.

В конце концов вы замечаете ошибку, произносите несколько ругательных слов и начинаете переписывать код.

К счастью, вы не первый, кто сталкивается с данной проблемой. Кто-то уже создал функции htons(3) и htonl(3) для перевода коротких (short) и длинных (long) чисел, соответственно, из порядка байт хоста в сетевой порядок байт. Функции ntohs(3) и ntohl(3) делают обратный перевод.

На MSB системах эти функции ничего не выполняют. На LSB системах происходит преобразование значений к нужному порядку.

Независимо от того на какой системе была скомпилирована ваша программа, данные будут располагаться в нужном порядке, если вы будете использовать эти функции.

7.5.1.2. Функции на стороне клиента

Обычно, клиент инициирует соединение с сервером. Клиент знает, какой сервер нужно использовать: он знает его IP адрес, порт, на котором располагается сервер. Это похоже на ситуацию, когда вы звоните по телефону на определённый номер (адрес), затем после того, как кто-нибудь ответил, спрашиваете человека (порт).

7.5.1.2.1. connect

После создания клиентом сокета его нужно присоединить к определённому порту на удалённой системе. Для этого используется connect(2):

int connect(int s, const struct sockaddr *name, socklen_t namelen);

Аргумент s - сокет, т.е. значение, полученное посредством вызова socket. name - указатель на структуру sockaddr, про которую мы столько говорили. И наконец, namelen информирует систему о размере структуры sockaddr в байтах.

Если connect завершилась удачно - возвращаемое значение 0. Иначе возвращается -1, и код ошибки сохраняется в errno.

Существует множество причин из-за которых connect может завершиться неудачно. К примеру, при попытке создания Интернет соединения, IP адрес может не существовать, или хост с данным адресом может быть выключен, или он может быть слишком занят, а может просто не иметь сервера, прослушивающего указанный порт. Также может быть аутрайт отклонение любого запроса из-за специфичного кода.

7.5.1.2.2. Наш первый клиент

Теперь у нас достаточно знаний для написания простейшего клиента, который будет получать текущее время с 192.43.244.18 и выводить его на stdout.

/*
 * daytime.c
 *
 * Programmed by G. Adam Stanislav
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
  register int s;
  register int bytes;
  struct sockaddr_in sa;
  char buffer[BUFSIZ+1];

  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  bzero(&sa, sizeof sa);

  sa.sin_family = AF_INET;
  sa.sin_port = htons(13);
  sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
  if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
    perror("connect");
    close(s);
    return 2;
  }

  while ((bytes = read(s, buffer, BUFSIZ)) > 0)
    write(1, buffer, bytes);

  close(s);
  return 0;
}

Наберите текст программы в редакторе, сохраните его под именем daytime.c, скомпилируйте и запустите:

% cc -O3 -o daytime daytime.c
% ./daytime

52079 01-06-19 02:29:25 50 0 1 543.9 UTC(NIST) * 
%

В этом примере полученная дата: 19 Июня, 2001, a время 02:29:25 UTC. Естественно ваши результаты будут отличаться.

7.5.1.3. Функции на стороне сервера

Типичный сервер не инициирует соединение. Вместо этого он ожидает клиента, который обратится к нему и будет запрашивать сервисы. Он не знает ни когда клиент обратится, ни как много много будет клиентов. Он может просто сидеть и терпеливо ждать. Спустя некоторое время он может обнаружить, что ему нужно обработать несколько запросов, посланных в одно время.

Интерфейс сокетов предлагает три основных функции для обеспечения данных действий.

7.5.1.3.1. bind

Порты можно представить в виде абонентов офисной мини-АТС: после того, как вы дозвонились, вы набираете дополнительный номер, чтобы соединиться с нужным человеком или департаментом.

Существует 65535 IP портов, но сервер, как правило обслуживает запросы, пришедшие только на один порт. Это как сказать телефонному оператору, что мы сейчас на работе и можем ответить на звонок по определённому номеру. Для того, чтобы сообщить сокетам, какой порт мы хотим обслуживать, используйте bind(2).

int bind(int s, const struct sockaddr *addr, socklen_t addrlen);

Рядом с указанием порта в addr, сервер может вставить свой IP адрес. Тем не менее, он просто может использовать константу INADDR_ANY, чтобы указать, что он будет обрабатывать все запросы на указанный порт, независимо от его IP адреса. Это и несколько подобных макроопределений объявлены в файле netinet/in.h

#define    INADDR_ANY      (u_int32_t)0x00000000

Предположим, что мы писали бы сервер для daytime протокола, работающий через TCP/IP. Используемый порт - 13. Наша структура sockadd_in выглядела бы так:

7.5.1.3.2. listen

Продолжаем проводить аналогию с телефоном. После того, как вы сообщили центральному телефонному оператору по какому номеру вы будете доступны, вы идёте в свой офис и убеждаетесь, что ваш телефон подключён и звонок не выключен. Дополнительно вы проверяете, что ваша система ожидания звонка активирована, и теперь вы сможете услышать звонок телефона даже когда с кем-то разговариваете.

Сервер проверяет всё это с помощью функции listen(2).

int listen(int s, int backlog);

Переменная backlog сообщает сокетам какое количество входящих запросов принимать, пока вы заняты обработкой последнего запроса. Другими словами, он определяет максимальный размер очереди рассматриваемых соединений.

7.5.1.3.3. accept

После того, как вы слышите телефонный звонок - вы берёте трубку. Вы только что создали соединение с вашим клиентом. Оно будет продолжаться до тех пор, пока вы или ваш клиент не положит трубку.

Сервер принимает соединения путём использования функции accept(2).

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

Заметьте, что в этот раз addrlen это указатель. Это необходимо потому, что в этот раз используемый сокет заполняет addr, структуру sockaddr_in.

Возвращаемое значение целого типа. На самом деле, accept возвращает новый сокет. Вы будете использовать его для соединения с клиентом.

Что происходит со старым сокетом? Он продолжает ожидать новых запросов (вспомнили переменную backlog, которую мы передавали listen?) до тех пор, пока мы не закроем его.

Теперь новый сокет предназначен только для коммуникаций. Он полностью присоединён. Мы не можем заново передать его функции listen, пытаясь принять дополнительные соединения.

7.5.1.3.4. Наш первый сервер

Наш первый сервер отчасти будет сложнее клиента: не только потому, что мы будем использовать больше функций для работы с сокетами, но ещё он должен быть даемоном.

Это лучше всего достигается созданием процесса-ребёнка после связывания порта. Главный процесс затем выходит и возвращает управление shell (или любой другой программе, которая его запустила).

Ребёнок вызывает listen, потом начинает бесконечный цикл, во время которого он принимает соединение, обслуживает его и в конечном счёте закрывает сокет.

/*
 * daytimed - a port 13 server
 *
 * Programmed by G. Adam Stanislav
 * June 19, 2001
 */
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define BACKLOG 4

int main() {
    register int s, c;
    int b;
    struct sockaddr_in sa;
    time_t t;
    struct tm *tm;
    FILE *client;

    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        return 1;
    }

    bzero(&sa, sizeof sa);

    sa.sin_family = AF_INET;
    sa.sin_port   = htons(13);

    if (INADDR_ANY)
        sa.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
        perror("bind");
        return 2;
    }

    switch (fork()) {
        case -1:
            perror("fork");
            return 3;
            break;
        default:
            close(s);
            return 0;
            break;
        case 0:
            break;
    }

    listen(s, BACKLOG);

    for (;;) {
        b = sizeof sa;

        if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
            perror("daytimed accept");
            return 4;
        }

        if ((client = fdopen(c, "w")) == NULL) {
            perror("daytimed fdopen");
            return 5;
        }

        if ((t = time(NULL)) < 0) {
            perror("daytimed time");

            return 6;
        }

        tm = gmtime(&t);
        fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
            tm->tm_year + 1900,
            tm->tm_mon + 1,
            tm->tm_mday,
            tm->tm_hour,
            tm->tm_min,
            tm->tm_sec);

        fclose(client);
    }
}

В начале программы создаётся сокет. Затем мы заполняем структуру sockaddr_in, объявленную, как sa. Обратите внимание на условное использование INADDR_ANY:

    if (INADDR_ANY)
        sa.sin_addr.s_addr = htonl(INADDR_ANY);

Её значение равно 0. Только что, с помощью bzero мы обнулили всю структуру, и будет излишним заново присваивать 0. Но, если портируем наш код на другую систему, где INADDR_ANY возможно не равняется нулю, то мы должны присвоить его sa.sin_addr.s_addr. Большинство современных компиляторов языка С достаточно умны, чтобы заметить, что INADDR_ANY - константа, и если она равна нулю, то они полностью уберут условное выражение.

После удачного завершения bind мы готовы стать даемоном: мы используем fork для создания процесса-ребёнка. И в родительском, и в порождённом процессах переменная s будет сокетом. Родительскому процессу он не будет нужен, поэтому он закрывает его, а затем возвращает 0 для информирования собственного родительского процесса о своём удачном завершении.

Тем временем порождённый процесс продолжает работать в фоновом режиме. Он вызывает listen и устанавливает количество рассматриваемых соединений 4. Не нужно устанавливать большое значение, так как daytime не тот протокол, к которому часто обращается большое количество клиентов, и он сможет обработать каждый запрос мгновенно.

И наконец, даемон начинает бесконечный цикл, во время которого выполняются следующие шаги:

  1. Вызывается accept. Сервер ждёт пока не появится клиент. На этом шаге создаётся новый сокет c, который используется для соединения с этим конкретным клиентом.

  2. Он использует функцию fdopen для преобразования низкоуровнего файлового дескриптора к указателю FILE. Это позволит в дальнейшем использовать функцию fprintf.

  3. Он проверяет время и записывает результат в формате ISO 8601 в ''файл'' client. Затем он закрывает его, используя fclose. Последнее действие, также, закроет сокет.

Мы можем обобщить эти шаги и использовать их, как модель для многих других серверов:

Эта блок-схема хорошо подходит для последовательных серверов, т.е. серверов, которые могут обслуживать одного клиента в одно и тоже время (пример: наш daytime сервер). Это возможно, только тогда, когда нет реального ''разговора'' между клиентом и сервером: после того, как сервер определил соединение с клиентом, он пересылает некоторые данные и закрывает соединение. Полная операция может занять наносекунды.

Преимущество данной блок-схемы в том, что почти всегда только один процесс является активным, за исключением небольшого промежутка между вызовом процессом-родителем fork и его выходом, т.е. наш сервер не требует большого количества памяти и других ресурсов системы.

Обратите внимание на пункт initialize daemon в нашей блок-схеме. Нам не нужно инициализировать нашего даемона, но это хорошее место в процессе выполнения программы для установки любых обработчиков сигналов, открытия нужных файлов и т.д.

Почти все пункты из этой блок-схемы применимы ко многим различным серверам. Исключение - serve. Это вроде некой ''черной коробки'', т.е. что-то, что вы придумали специально для вашего сервера и просто ''подключаете к остальному''.

Не все протоколы такие простые. Многие получают запрос от клиента, отвечают на него, затем получают другой запрос от этого же клиента. Из-за этого, они не знают как долго им придётся обслуживать клиента. Такие серверы обычно запускают новый процесс для каждого клиента. Пока новый процесс обслуживает клиента, даемон может продолжать ждать новых запросов на соединение.

Сохраните вышеприведённый код сервера в daytimed.c (имена даемонов принято заканчивать буквой d). После того, как вы скомпилировали сервер, попытайтесь его запустить:

% ./daytimed
bind: Permission denied
%

Что здесь случилось? Как вы помните, протокол daytime использует 13 порт. Но все порты ниже 1024 зарезервированы для запуска суперпользователем, иначе любой может запустить даемон, обслуживающий обще-используемый порт, образовав брешь в безопасности.

Попытайтесь снова, в этот раз, как суперпользователь:

# ./daytimed
#

Что... Ничего не происходит? Давайте попробуем ещё раз:

# ./daytimed

bind: Address already in use
#

Каждый порт может быть связан только с одной программой в одно и тоже время. Наша первая попытка на самом деле была удачная: она запустила дитя-даемона и незаметно вернула управление. Даемон продолжает работать до тех пор пока либо вы его не убьёте, либо неудачно завершится один из системных вызовов, либо пока вы не перезагрузите систему.

Хорошо, мы знаем, что программа запущена в фоновом режиме. Но работает ли она? Как нам узнать, что это правильный daytime сервер? Просто:

% telnet localhost 13

Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2001-06-19T21:04:42Z
Connection closed by foreign host.
%

В начале telnet попытался использовать новый протокол IPv6, и как видите это у него не получилось. Затем он попробовал IPv4, и попытка завершилась удачно. Даемон работает.

Если у вас есть доступ к другой UNIX системе через telnet, вы можете протестировать доступ к серверу удалённо. Мой компьютер не имеет статического IP адреса, и вот, что я сделал:

% who

whizkid          ttyp0   Jun 19 16:59   (216.127.220.143)
xxx              ttyp1   Jun 19 16:06   (xx.xx.xx.xx)
% telnet 216.127.220.143 13

Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:11Z
Connection closed by foreign host.
%

Работает. Проверим, будет ли она работать, если мы введём имя домена?

% telnet r47.bfm.org 13

Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:40Z
Connection closed by foreign host.
%

telnet выводит сообщение Connection closed by foreign host после того, как наш даемон закрывает сокет. Это показывает нам, что fclose(client); работает как надо.

Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.