>16 Listen(listenfd, LISTENQ);
>17 for (;;) {
>18 connfd = Accept(listenfd, (SA*)NULL, NULL);
>19 ticks = time(NULL);
>20 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
>21 Write(connfd. buff, strlen(buff));
>22 Close(connfd);
>23 }
>24 }
Создание сокета TCP
>10
Создание сокета TCP выполняется так же, как и в клиентском коде.
Связывание заранее известного порта сервера с сокетом
>11-15
Заранее известный порт сервера (13 в случае сервера времени и даты) связывается с сокетом путем заполнения структуры адреса интернет-сокета и вызова функции >bind
. Мы задаем IP-адрес как >INADDR_ANY
, что позволяет серверу принимать соединение клиента на любом интерфейсе в том случае, если узел сервера имеет несколько интерфейсов. Далее мы рассмотрим, как можно ограничить прием соединений одним-единственным интерфейсом.
Преобразование сокета в прослушиваемый сокет
>16
С помощью вызова функции >listen
сокет преобразуется в прослушиваемый, то есть такой, на котором ядро принимает входящие соединения от клиентов. Эти три этапа, >socket
, >bind
и >listen
, обычны для любого сервера TCP при создании того, что мы называем прослушиваемым дескриптором (listening descriptor) (в нашем примере это переменная >listenfd
).
Константа >LISTENQ
взята из нашего заголовочного файла >unp.h
. Она задает максимальное количество клиентских соединений, которые ядро ставит в очередь на прослушиваемом сокете. Более подробно мы расскажем о таких очередях в разделе 4.5.
Прием клиентского соединения, отправка ответа
>17-21
Обычно процесс сервера блокируется при вызове функции >accept
, ожидая принятия подключения клиента. Для установки TCP-соединения используется трехэтапное рукопожатие (three-way handshake). Когда рукопожатие состоялось, функция accept возвращает значение, и это значение является новым дескриптором (>connfd
), который называется присоединенным дескриптором (connected descriptor). Этот новый дескриптор используется для связи с новым клиентом. Новый дескриптор возвращается функцией >accept
для каждого клиента, соединяющегося с нашим сервером.
ПРИМЕЧАНИЕ
Стиль, используемый в книге для обозначения бесконечного цикла, выглядит так:
>for (;;) {
> ...
>}
Библиотечная функция >time
возвращает количество секунд с начала эпохи Unix: 00:00:00 1 января 1970 года UTC (Universal Time Coordinated — универсальное синхронизированное время, среднее время по Гринвичу). Следующая библиотечная функция, >ctime
, преобразует целочисленное значение секунд в строку следующего формата, удобного для человеческого восприятия:
>Fri Jan 12 14:27:52 1996
Возврат каретки и пустая строка добавляются к строке функцией >snprintf
, а результат передается клиенту функцией >write
.
ПРИМЕЧАНИЕ
Если вы еще не выработали у себя привычку пользоваться функцией snprintf вместо устаревшей sprintf, сейчас самое время заняться этим. Функция sprintf не в состоянии обеспечить проверку переполнения буфера получателя. Функция snprintf, наоборот, требует, чтобы в качестве второго аргумента указывался размер буфера получателя, переполнение которого таким образом предотвращается.
Функция snprintf была добавлена в стандарт ANSI С относительно нравно, в версии ISO C99. Практически все поставщики программного обеспечения уже сейчас включают эту функцию в стандартную библиотеку языка С. Существуют и свободно распространяемые реализации. В нашей книге мы используем функцию snprintf и рекомендуем вам пользоваться ею в своих программах для повышения их надежности.
Удивительно много сетевых атак было реализовано хакерами с использованием незащищенности sprintf от переполнения буфера. Есть еще несколько функций, с которыми нужно быть аккуратными: gets, strcat и strcpy. Вместо них лучше использовать fgets, strncat и strncpy. Еще лучше работают более современные функции strlcat и strlcpy, возвращающие в качестве результата правильно завершенную строку. Полезные советы, касающиеся написания надежных сетевых программ, можно найти в главе 23 книги [32].
Завершение соединения
>22
Сервер закрывает соединение с клиентом, вызывая функцию >close
. Это инициирует обычную последовательность прерывания соединения TCP: пакет FIN посылается в обоих направлениях, и каждый пакет FIN распознается на другом конце соединения. Более подробно трехэтапное рукопожатие и четыре пакета TCP, используемые для прерывания соединения, будут описаны в разделе 2.6.