- Фантастика и фэнтези
- Ироническое фэнтези
- Стимпанк
- Детективная фантастика
- Киберпанк
- Попаданцы
- LitRPG
- Космоопера
- Технофэнтези
- Городская фантастика
- Русское фэнтези
- Юмористическая фантастика
- Космическая фантастика
- Боевое фэнтези
- Эпическая фантастика
- Зарубежная фантастика
- Книги магов
- Мистика
- Разная фантастика
- Разное фэнтези
- Любовное фэнтези
- Романтическая фантастика
- Городское фентези
- Историческая фантастика
- Историческое фэнтези
- Социально-философская фантастика
- Сказочная фантастика
- Иностранное фэнтези
- Романтическое фэнтези
- Ироническая фантастика
- Ужасы и Мистика
- Постапокалипсис
- Героическая фантастика
- Фэнтези
- Научная Фантастика
- Социально-психологическая
- Альтернативная история
- Боевая фантастика
- Разная литература
- Пословицы, поговорки
- Шахматы
- Визуальные искусства
- Фанфик
- Авто и ПДД
- Военное
- Отраслевые издания
- Гиды, путеводители
- Зарубежная образовательная литература
- Военная история
- Цитаты из афоризмов
- Современная литература
- Великолепные истории
- Начинающие авторы
- Современная зарубежная литература
- Боевые искусства
- Изобразительное искусство, фотография
- Кино
- Литература 19 века
- Недвижимость
- Музыка, музыканты
- Готические новеллы
- Истории из жизни
- Культура и искусство
- Спецслужбы
- Зарубежная прикладная литература
- Музыка, танцы
- Подростковая литература
- Прочее
- Военная техника, оружие
- Газеты и журналы
- Периодические издания
- Домоводство, Дом и семья
- Любовные романы
- Экономическая литература
- Научные и научно-популярные книги
- О животных
- Биохимия
- Культурология
- Юриспруденция
- Политика
- Психотерапия
- Языкознание
- Научпоп
- Медицина
- Психология, личное
- Радиотехника
- Филология
- Педагогика
- Образовательная литература
- Деловая литература
- Физика
- Науки: разное
- Химия
- Воспитание детей, педагогика
- Детская психология
- Зарубежная публицистика
- Биология
- Архитектура
- Зарубежная психология
- Науки о космосе
- Социология
- Математика
- География
- Беременность, ожидание детей
- Литературоведение
- Транспорт, военная техника
- Обществознание
- Зоология
- География
- Альтернативная медицина
- Иностранные языки
- Ветеринария
- Рефераты
- Астрология
- Биофизика
- Экология
- История Европы
- Учебники
- Шпаргалки
- Государство и право
- Ботаника
- Религиоведение
- История
- Техническая литература
- Прочая научная литература
- Психология
- Детективы и Триллеры
- Справочная литература
- Проза
- Новелла
- Юмор
- Проза
- Историческая проза
- Русская современная проза
- Советская классическая проза
- Повести
- Очерки
- Эссе
- Рассказы
- Зарубежная современная проза
- Сентиментальная проза
- Зарубежная классика
- Афоризмы
- Эпистолярная проза
- Феерия
- Семейный роман/Семейная сага
- Разное
- Антисоветская литература
- Магический реализм
- Русская классическая проза
- Современная проза
- О войне
- Контркультура
- Классическая проза
- Поэзия, Драматургия
- Детская литература
- Детская фантастика
- Детские остросюжетные
- Детские стихи
- Детские приключения
- Внеклассное чтение
- Учебная литература
- Зарубежные детские книги
- Бизнес
- Загадки
- Книги для подростков
- Школьные учебники
- Детский фольклор
- Книги для дошкольников
- Детские детективы
- Детская познавательная и развивающая литература
- Буквари
- Прочая детская литература
- Сказка
- Детская проза
- Детская образовательная литература
- Юмор
- Документальные книги
- Бизнес
- Внешнеэкономическая деятельность
- Тайм-менеджмент
- Кадровый менеджмент
- О бизнесе популярно
- Управление, подбор персонала
- Бизнес
- Менеджмент и кадры
- Продажи
- Экономика
- Ценные бумаги и инвестиции
- Малый бизнес
- Делопроизводство, офис
- Корпоративная культура, бизнес
- Банковское дело
- Финансы
- Зарубежная деловая литература
- Ораторское искусство / риторика
- Личные финансы
- Интернет-бизнес
- Поиск работы
- Государственное и муниципальное управление
- Бухучет и аудит
- Работа с клиентами
- Менеджмент
- Краткое содержание
- Личная эффективность
- Переговоры
- Приключения
- Старинная литература
- Религия и духовность
- Компьютеры и Интернет
- Блог
Читаем без скачивания О чём не пишут в книгах по Delphi - А. Григорьев
Шрифт:
Интервал:
Закладка:
mtError, [mbOK], 0);
end;
end;
Слушающая" нить TListenThread состоит из бесконечного ожидания подключения клиента. Каждый раз при подключении клиента библиотека сокетов создаёт новый сокет, и для работы с ним создается новая нить типа TClientThread (листинг 2.20).
Листинг 2.20. Код "слушающей" нитиprocedure TListenThread.Execute;
// Сокет, созданный для общения с подключившимся клиентом
ClientSocket: TSocket;
// Адрес подключившегося клиента
ClientAddr: TSockAddr;
ClientAddrLen: Integer;
begin
// Начинаем бесконечный цикл
repeat
ClientAddrLen := SizeOf(ClientAddr);
// Ожидаем подключения клиента
ClientSocket := accept(FServerSocket, @ClientAddr, @ClientAddrLen);
if ClientSocket = INVALID_SOCKET then
begin
// Ошибка в функции accept возникает только тогда, когда
// происходит нечто экстраординарное. Продолжать работу
// в этом случае бессмысленно.
LogMessage('Ошибка при подключении клиента: ' + GetErrorString);
Break;
end;
// Создаем новую нить для обслуживания подключившегося клиента
// и передаём ей сокет, созданный для взаимодействия с ним.
TClientThread.Create(ClientSocket, ClientAddr);
until False;
closesocket(FServerSocket);
LogMessage('Сервер завершил работу');
Synchronize(ServerForm.OnStopServer);
end;
Метод LogMessage, существующий у "слушающей" нити, эквивалентен тому, который приведен в листинге 2.7.
Код нити типа TClientThread, которая отвечает за взаимодействие с одним клиентом, приведен в листинге 2.21.
Листинг 2.21. Код нити, реализующей взаимодействие с клиентом// Сокет для взаимодействия с клиентом создается в главной нити,
// а сюда передается через параметр конструктора. Для формирования
// заголовка сюда же передается адрес подключившегося клиента
constructor TClientThread.Create(ClientSocket: TSocket; const ClientAddr:TSockAddr);
begin
FSocket := ClientSocket;
// Заголовок содержит адрес и номер порта клиента.
// Этот заголовок будет добавляться ко всем сообщениям в лог
// от данного клиента.
FHeader :=
'Сообщение от клиента ' + inet_ntoa(ClientAddr.sin_addr) + ':' +
IntToStr(ntohs(ClientAddr.sin_port)) + ': ';
inherited Create(False);
end;
procedure TClientThread.Execute; var Str: string; StrLen: Integer;
begin
LogMessage('Соединение установлено');
// Начинаем цикл, из которого выходим только при закрытии
// соединения клиентом или из-за ошибки в сети.
repeat
// Читаем длину присланной клиентом строки и помещаем ее в StrLen
case ReadFromSocket(FSocket, StrLen, SizeOf(StrLen)) of
0: begin
LogMessage('Клиент закрыл соединение');
Break;
end;
-1: begin
LogMessage('Ошибка при получении данных от клиента: ' +
GetErrorString);
Break;
end;
end;
// Протокол не допускает строк нулевой длины
if StrLen <= 0 then
begin
LogMessage('Неверная длина строки от клиента: ' +
IntToStr(StrLen));
Break;
end;
// Установка длины строки в соответствии с полученным значением
SetLength(Str, StrLen);
// Чтение строки нужной длины
case ReadFromSocket(FSocket, Str[1], StrLen) of
0: begin
LogMessage('Клиент закрыл соединение');
Break;
end;
-1: begin
LogMessage('Ошибка при получении данных от клиента: ' +
GetErrorString);
Break;
end;
end;
LogMessage('Получена строка: ' + Str);
// Преобразование строки
Str :=
AnsiUpperCase(StringReplace(Str, #0, '#0', [rfReplaceAll]),
' (Multithreaded server)';
// Отправка строки. Отправляется на один байт больше, чем
// длина строки, чтобы завершающий символ #0 тоже попал в пакет
if send(FSocket, Str[1], Length(Str) + 1, 0) < 0 then
begin
LogMessage('Ошибка при отправке данных клиенту: ' +
GetErrorString);
Break;
end;
LogMessage('Клиенту отправлен ответ: ' + Str);
until False;
closesocket(FSocket);
end;
procedure TClientThread.LogMessage(const Msg: string);
begin
FMessage := FHeader + Msg;
Synchronize(DoLogMessage);
end;
Метод LogMessage здесь несколько видоизменен по сравнению с предыдущими примерами: к каждому сообщению он добавляет адрес клиента, чтобы пользователь мог видеть, с каким именно из одновременно подключившихся клиентов связано сообщение. Что же касается кода Execute, то видно, что он практически не отличается от кода внутреннего цикла простейшего сервера (см. листинг 2.15). Это неудивительно — сообщение здесь читается и обрабатывается единым образом. Вся разница только в том, что теперь у нас одновременно могут работать несколько таких нитей, обеспечивая одновременную работу сервера с несколькими клиентами.
Этот сервер уже можно использовать как образец для подражания. Нужно только помнить, что он тратит на каждого клиента относительно много ресурсов, и поэтому не подходит там, где могут подключаться сотни и более клиентов одновременно. Кроме того, этот сервер очень уязвим по отношению к DoS-атакам, поэтому подобный сервер целесообразен там. где число клиентов относительно невелико, а вероятность DoS-атак низка.
ПримечаниеDoS-атака (Denied of Service) — способ помешать функционированию сервера, заключающийся в загрузке его бесполезной работой. В простейшем случае — это просто одновременное подключение большого числа клиентов. У нас даже простое подключение большого числа клиентов приводит к большому расходу системных ресурсов, поэтому DoS-атакой можно добиться неработоспособности не только самого сервера, но и системы в целом. Полностью защититься от DoS-атаки невозможно, но можно снизить урон, наносимый ею. Об этом мы поговорим далее.
2.1.13. Определение готовности сокета
Так как многие функции библиотеки сокетов блокируют вызвавшую их нить, если соответствующая операция не может быть выполнена немедленно, часто бывает полезно заранее знать, готов ли сокет к немедленному (без блокирования) выполнению той или иной операции. Основным средством определения этого в библиотеке сокетов служит функция select:
function select(nfds: Integer; readfds, writefds, exceptfds: PFDSet; timeout: PTimeVal): LongInt;
Первый параметр этой функции оставлен только для совместимости со старыми версиями библиотеки сокетов: в существующих версиях он игнорируется. Три следующих параметра содержат указатели на множества сокетов (эти множества описываются типом TFDSet), состояние которых должно проверяться. В данном случае понятие множества не имеет ничего общего с типом множество в Delphi. В оригинальной версии библиотеки сокетов, написанной на C, определены макросы, позволяющие очищать такие множества, добавлять и удалять сокеты и определять, входит ли тот или иной сокет в множество. В модуле WinSock эти макросы заменены одноименными процедурами и функциями (листинг 2.22).
Листинг 2.22. Функции для работы с типом TFDSet// Удаляет сокет Socket из множества FDSet.
procedure FD_CLR(Socket: TSocket; var FDSet: TFDSet);
// Определяет, входит ли сокет Socket в множество FDSet.
function FD_ISSET(Socket: TSocket; var FDSet: TFDSet): Boolean;
// Добавляет сокет Socket в множество FDSet.
procedure FD_SET(Socket: TSocket; var FDSet: TFDSet);
// Инициализирует множество FDSet.
procedure FD_ZERO(var FDSet: TFDSet);
При создании переменной типа TFDSet в той области памяти, которую она занимает, могут находиться произвольные данные, являющиеся, по сути дела, "мусором". Из-за этого мусора функции FD_CLR, FD_ISSET, и FD_SET не смогут работать корректно. Процедура FD_ZERO очищает мусор, создавая пустое множество. Вызов остальных функций FD_XXX до вызова FD_ZERO приведёт к непредсказуемым результатам.
Мы намеренно не приводим здесь описание внутренней структуры типа TFDSet. С помощью функций FD_XXX можно выполнить все необходимые операции с множеством, не зная этой структуры. Отметим, что в Windows и в Unix внутреннее устройство этого типа существенно различается, но благодаря использованию этих функций код остается переносимым.
В Windows максимальное количество сокетов, которое может содержать в себе множество TFDSet, определяется значением константы FD_SETSIZE. По умолчанию ее значение равно 64. В C/C++ отсутствует раздельная компиляция модулей в том смысле, в котором она существует в Delphi, поэтому модуль в этих языках может поменять значение константы FD_SETSIZE перед включением заголовочного файла библиотеки сокетов, и это изменение приведёт к изменению внутренней структуры типа TFDSet (точнее, типа FDSet — в C/C++ он называется так). К счастью, в Delphi модули надежно защищены от подобного влияния друг на друга, поэтому как бы мы ни переопределяли константу FD_SETSIZE в своем модуле, на модуле WinSock это никак не отразится. В Delphi приходится прибегать к другому способу изменения количества сокетов в множестве: для этого следует определить свой тип, эквивалентный по структуре TFDSet, но резервирующий иное количество памяти для хранения сокетов (структуру TFDSet можно узнать из исходного кода модуля WinSock). В функцию select можно передавать указатели на структуры нового типа, необходимо только приведение типов указателей. А вот существующие функции FD_XXX, к сожалению, не смогут работать с новой структурой, потому что компилятор требует строгого соответствия типов для параметров-переменных. Но, опять же, при необходимости очень легко создать аналоги этих функций для своей структуры.