// соединения клиентом или из-за ошибки в сети.
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. Определение готовности сокета