Примечание
Разумеется, ни один сервер не может работать с неограниченным числом клиентов. Здесь и далее под словом "неограниченный" подразумевается то, что количество клиентов сервера ограничивается только ресурсами системы, а не самой реализацией сервера.
Инициализация сокета и установка его в режим прослушивания в новом сервере ничем не отличается от простейшего, изменения начинаются только с цикла. Теперь цикл только один (вложенные циклы в нем есть, но они выполняют чисто техническую роль). Начинается цикл с того, что с помощью функции select
определяется готовность к чтению слушающего сокета. Если слушающий сокет готов к чтению, то в данном случае это означает, что есть клиенты, которые уже подключились к серверу, но еще не были обработаны функцией accept
. Если такие клиенты есть, то сервер принимает подключение, причем только одно за одну итерацию цикла. Для каждого подключившегося клиента сервер создает экземпляр записи TConnection
, которая описана в листинге 2.25.
Листинг 2.25. Описание типа TConnection
// запись TConnection хранит информацию о подключившемся клиенте.
// поле ClientAddr содержит строковое представление адреса клиента.
// Поле ClientSocket содержит сокет, созданный функцией accept
// для взаимодействия с данным клиентом.
// Поле Deleted - служебное. Если оно равно False, значит,
// соединение с данным клиентом по каким-то причинам потеряно,
// и сервер должен освободить ресурсы, выделенные для этого клиента.
PConnection = ^Connection;
TConnection = record
ClientAddr: string;
ClientSocket: TSocket;
Deleted: Boolean;
end;
Поле ClientAddr
хранит строковое представление адреса клиента в виде "X.X.X.X:Port" — это поле используется только при выводе сообщений, связанных с данным клиентом. Поле ClientSocket
содержит сокет, созданный для связи с данным клиентом. Поле Deleted
необходимо для того, чтобы упростить удаление записей для тех клиентов, соединение с которыми уже потеряно. Список соединений хранится в глобальной переменной FConnections
типа TList
. Потеря соединения обнаруживается при попытке чтения или отправки данных через сокет. Если в одном цикле делать и попытки чтения, и удаление ненужных записей, этот цикл усложняется, и в нем легко сделать ошибку в индексах. Чтобы избежать этого, в "читающем" цикле те записи, для которых потеряно соединение, просто помечаются как удаленные с помощью свойства Deleted
. Затем другой цикл удаляет все записи, помеченные для удаления.
После проверки новых подключений начинается проверка получения сообщений от тех клиентов, которые уже подключены. Для этого перебираются сокеты из списка подключений и для каждого вызывается select
. Чтобы повысить производительность, сокеты проверяются не по одному, а группами. Как уже было сказано, множество типа TFDSet
может содержать не более FD_SETSIZE
сокетов, а в нашем списке их может оказаться больше. Приходится разбивать сокеты на группы размером по FD_SETSIZE
и для каждой группы вызывать select
отдельно.
Для тех сокетов, которые готовы к чтению, вызывается процедура ProcessSocketMessage
. Ее код практически полностью совпадает с кодом одной итерации внутреннего цикла примера SimplestServer (см. листинг 2.15), т.е. процедура сначала читает размер строки, затем — саму строку, после этого формирует ответ и отправляет его клиенту. Реализуя эту функцию таким образом, мы пошли на некоторый риск блокировки, потому что функция select информирует только о том, что во входном буфере сокета есть хоть что-то, но вовсе не гарантирует, что там лежит уже все сообщение целиком. Наша же функция реализована таким образом, что она завершается либо после прочтения сообщения целиком, либо после обнаружения ошибки. Тем не менее в простых случаях можно пойти на такой риск, потому что, во-первых, короткие сообщения редко разбиваются на части, а во-вторых, если даже такое произойдет, оставшаяся часть сообщения, скорее всего, догонит первую достаточно быстро, и блокировка долгой не будет, так что риск при нормальной работе сети и клиента не очень велик.
Примечание
Эта ситуация отличается от использования select
для UDP-сокетов. С ними такой проблемы не возникает, т.к. дейтаграмма никогда не приходит по частям, и если функция select
показала готовность сокета. значит, уже получено все сообщение целиком.
Завершается основной цикл сервера удалением всех ресурсов, связанных с закрытыми соединениями. После небольшой паузы, сделанной для того, чтобы сервер не нагружал процессор непрерывно, управление передается на начало цикла (листинг 2.26).
Читать дальше
Конец ознакомительного отрывка
Купить книгу