А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Сразу отметим, что, хотя спецификация допускает частичное копирование функцией send
данных в буфер сокета, на практике такое поведение наблюдать пока не удалось: все эксперименты показали, что функция send
всегда либо копирует данные целиком, расширяя при необходимости буфер, либо дает ошибку WSAEWOULDBLOCK
. Далее этот вопрос будет обсуждаться подробнее. Тем не менее при написании программ следует учитывать возможность частичного копирования, т.к. оно может появиться в тех условиях или в тех реализациях библиотеки сокетов, которые в наших экспериментах не были проверены.
2.1.16. Сервер на неблокирующих сокетах
В этом разделе мы создадим сервер, основанный на неблокирующих сокетах. Это будет наш первый сервер, не использующий функцию ReadFromSocket
(см. листинг 2.13). Этот сервер (пример NonBlockingServer
на компакт-диске) состоит из одной нити, которая никогда не будет блокироваться сокетными операциями, т.к. все сокеты используют неблокирующий режим. На форме находится таймер, по сигналам которого сервер выполняет попытки чтения данных с сокетов всех подключившихся клиентов. Если данных нет, функция recv немедленно завершается с ошибкой WSAEWOULDBLOCK
, и сервер переходит к попытке чтения из следующего сокета.
Запуск сервера (листинг 2.30) мало чем отличается от запуска многонитевого сервера (см. листинг 2.19). Практически вся разница заключается в том, что вместо запуска "слушающей" нити сокет переводится в неблокирующий режим и включается таймер.
// Реакция на кнопку "Запустить" - запуск сервера
procedure TServerForm.BtnStartServerClick(Sender: TObject);
var
// Адрес, к которому привязывается слушающий сокет
ServerAddr: TSockAddr;
NonBlockingArg: u_long;
begin
// Формируем адрес для привязки.
FillChar(ServerAddr.sin_zero, SizeOf(ServerAddr.sin_zero), 0);
ServerAddr.sin_family := AF_INET;
ServerAddr.sin_addr.S_addr := INADDR_ANY;
try
ServerAddr.sin_port := htons(StrToInt(EditPortNumber.Text));
if ServerAddr.sin_port = 0 then
begin
MessageDlg('Номер порта должен находиться в диапазоне 1-65535',
mtError, [mbOK], 0);
Exit;
end;
// Создание сокета
FServerSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if FServerSocket = INVALID_SOCKET then
begin
MessageDlg('Ошибка при создании сокета: '#13#10 + GetErrorString,
mtError, [mbOK], 0);
Exit;
end;
// Привязка сокета к адресу
if bind(FServerSocket, ServerAddr, SizeOf(ServerAddr)) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при привязке сокета к адреcу: '#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод сокета в режим прослушивания
if listen(FServerSocket, SOMAXCONN) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при переводе сокета в режим прослушивания:'#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод сокета в неблокирующий режим
NonBlockingArg := 1;
if ioctlsocket(FServerSocket, FIONBIO, NonBlockingArg) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при переводе сокета в неблокирующий режим:'#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод элементов управления в состояние "Сервер работает"
LabelPortNumber.Enabled := False;
EditРоrtNumber.Enabled := False;
BtnStartServer.Enabled := False;
TimerRead.Interval := TimerInterval;
LabelServerState.Caption := 'Сервер работает';
except
on EConvertError do
// Это исключение может возникнуть только в одном месте -
// при вызове StrToInt(EditPortNumber.Text)
MessageDlg('"' + EditPortNumber.Text +
'" не является целым числом', mtError, [mbOK], 0);
on ERangeError do
// Это исключение может возникнуть только в одном месте -
// при присваивании значения номеру порта
MessageDlg('Номер порта должен находиться в диапазоне 1-65535',
mtError, [mbOK], 0);
end;
end;
Так как протокол TCP допускает разбиение посылки на произвольное число пакетов, возможна ситуация, когда на момент срабатывания таймера в буфере сокета будет только часть того, что отправил клиент. Так как мы договорились не блокировать нить, то ждать, пока придет остальное, мы не будем. Вместо этого будем запоминать то, что пришло, а при следующем срабатывании таймера, если пришло еще что-то. добавлять это к предыдущим данным, и так до тех пор, пока не придет все, что мы ожидаем получить от клиента. Так как посылка может разорваться в любом месте, наш код должен быть к этому готов.
Взаимодействие сервера с клиентом состоит из трех этапов. На первом этапе сервер получает от клиента четырёхбайтное значение — длину строки. На втором этапе сервер получает от клиента саму строку, размер которой уже известен из величины, полученной на первом этапе. На третьем этапе сервер отправляет ответ клиенту, состоящий из строки, завершающейся нулем. Чтобы при очередном "тике" таймера сервер мог продолжить общение с клиентом, прерванное в произвольном месте, необходимо запоминать, на каком этапе было прервано взаимодействие в предыдущий раз, сколько байтов на данном этапе уже прочитано или отправлено и сколько еще осталось прочитать или отправить. Для хранения этих данных мы будем использовать типы TTransportPhase
и TConnection
(листинг 2.31).
TTransportPhase
и TConnection
type
// Этап взаимодействия с клиентом:
// tpReceiveLength - сервер ожидает от клиента длину строки
// tpReceiveString - сервер ожидает от клиента строку
// tpSendString - сервер посылает клиенту строку
TTransportPhase = (tpReceiveLength, tpReceiveString, tpSendString);
// Информация о соединении с клиентом:
// СlientSocket - сокет, созданный для взаимодействия с клиентом
// ClientAddr - строковое представление адреса клиента
// MsgSize - длина строки, получаемая от клиента
// Msg - строка, получаемая от клиента или отправляемая ему,
// Phase - этап взаимодействия с данным клиентом
// Offset - количество байтов, уже полученных от клиента
// или отправленных ему на данном этапе
// BytesLeft - сколько байтов осталось получить от клиента
// или отправить ему на данном этапе
PConnection = ^TConnection;
TConnection = record
ClientSocket: TSocket;
ClientAddr: string;
MsgSize: Integer;
Msg: string;
Phase: TTransportPhase;
Offset: Integer;
BytesLeft: Integer;
end;
Для каждого подключившегося клиента создается отдельный экземпляр записи TConnection
, в котором хранится информация как о самом подключении, так и о том, на каком этапе находится взаимодействие с данным клиентом.
Интервал:
Закладка: