А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
// чем нужное количество минус то, что уже прочитано
// на предыдущих итерациях. Очередную порцию данных
// помещаем в буфер со смещением Total.
Res := recv(S, (PChar(@Buffer) + Total)^, Cnt - Total, 0);
if Res = 0 then
begin
// Соединение закрыто удаленной стороной
Result := 0;
Exit;
end;
if Res < 0 then
begin
// Произошла ошибка при чтении
Result := -1;
Exit;
end;
Inc(Total, Res);
until Total >= Cnt;
Result:= 1;
end;
Эта функция будет использоваться в дальнейшем в нескольких наших примерах.
2.1.12. Примеры передачи данных с помощью TCP
Теперь у нас достаточно знаний, чтобы написать TCP-клиент и TCP-сервер. Как и в случае с UDP, сначала нужно договориться о том, какими данными и в каком формате будут обмениваться наши программы. С протоколом, описанным здесь, нам предстоит работать на протяжении всей главы. По мере изучения новых возможностей библиотеки сокетов мы будем реализовывать новые варианты серверов и клиентов, но почти все они будут поддерживать один и тот же протокол, поэтому любой клиент сможет работать с любым сервером.
Наши сервер и клиент будут обмениваться строковыми сообщениями: клиент пошлет строку, сервер отправит ответ. Мы уже не можем, как в случае UDP, просто отправить строку, потому что при использовании TCP несколько строк могут быть отправлены одним пакетом, или наоборот, одна строка разбита на несколько пакетов. Соответственно, наш протокол должен позволять определить, где заканчивается одна строка и начинается другая.
Ранее мы уже упоминали три основных способа определения границ логического пакета в TCP: все пакеты могут иметь одинаковую длину, пакет может предваряться фиксированным заголовком, содержащим длину, между пакетами может вставляться заранее оговоренная последовательность байт. Первый способ самый легкий в реализации, но он накладывает существенные ограничения на передаваемые данные. В частности, нам он не подходит, потому что мы будем передавать строки произвольной длины. Второй и третий способы приемлемы для передачи строк, и чтобы проиллюстрировать как можно больше различных вариантов в наших примерах, мы будем использовать их оба. При передаче данных от клиента серверу, мы будем перед строгой передавать четырёхбайтное значение — длину строки, а при передаче данных от сервера клиенту длину строки мы передавать не будем, но к каждой строке будет добавляться символ #0
, указывающий на завершение строки. Таким образом, получается, что строки, передаваемые клиентом, могут содержать символ #0
в середине, а передаваемые сервером — нет.
Все серверы, которые мы напишем, будут возвращать клиенту присланную строку, но слегка преобразованную. Во-первых, все символы #0
будут в ней заменены на подстроку " #0
", во-вторых, все буквы превращены в заглавные, а в-третьих, добавлено имя сервера, который ответил.
Практическое знакомство с TCP мы начнем с написания простейшего сервера. На компакт-диске этот сервер находится в папке SimplestServer
. Сразу отметим, что это чисто учебный пример, и брать его за основу при создании реальных серверов ни в коем случае нельзя. Чуть позже мы напишем другой сервер, который уже может служить образцом для подражания.
Наш простейший сервер будет использовать только одну нить. Как мы помним, сервер должен вызывать две функции, которые блокируют работу нити: accept и recv. Очевидно, что задействовать их обе сразу в одной нити не получится, именно поэтому наш сервер сможет работать только с одним клиентом одновременно. И чтобы не блокировать пользовательский интерфейс, наш сервер будет консольным приложением. В командной строке ему передается номер порта, к которому привязывается слушающий сокет.
Первое, что должен сделать сервер, — это создать сокет. привязать его к требуемому адресу и перевести в режим прослушивания. Этот код мало чем отличается от приведенного ранее примера создания сокета для UDP (см. листинг 2.8). Вся разница только в том, что вместо сокета типа SOCK_DGRAM
создается сокет типа SOCK_STREAM
, а в конце еще вызывается функция listen
(листинг 2.14).
var
// Порт, который будет "слушать" сервер
Port: Word;
// "Слушающей" сокет
MainSocket: TSocket;
// Сокет, создающийся для обслуживания клиента
ClientSocket: TSocket;
// Адрес "слушающего" сокета
MainSockAddr: TSockAddr;
// Адрес подключившегося клиента
ClientSockAddr: TSockAddr;
// Размер адреса подключившегося клиента
ClientSockAddrLen: Integer;
//Без этой переменной не удастся инициализировать библиотеку сокетов
WSAData: TWSAData;
StrLen: Integer;
Str: string;
begin
try
if ParamCount = 0 then
// Если в командной строке порт не задан, назначаем его
Port := 12345;
else
// В противном случае анализируем командную строку и назначаем порт
try
Port := StrToInt(ParamStr(1));
if Port = 0 then
raise ESocketException.Create(
'Номер порта должен находиться в диапазоне 1-65535');
except
on EConvertError do
raise ESocketException.Create(
'Параметр "' + ParamStr(1) + '" не является целым числом');
on ERangeError do
raise ESocketException.Create(
'Номер порта должен находиться в диапазоне 1-65535');
end;
// инициализация библиотеки сокетов
if WSAStartup($101, WSAData) <> 0 then
raise ESocketException.Create(
'Ошибка при инициализации библиотеки WinSock');
// Создание сокета, который затем будет "слушать" порт
MainSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if MainSocket = INVALID_SOCKET then
raise ESocketException.Create(
'Невозможно создать слушающий сокет: ' + GetErrorString');
// Формирование адреса для "слушающего" сокета
FillChar(MainSockAddr.sin_zero, SizeOf (MainSockAddr.sin_zero, 0);
MainSockAddr.sin_family := AF_INET;
// Выбор IP-адреса доверяем системе
MainSockAddr.sin_addr.S_addr := INADDR_ANY;
// Порт назначаем, не забывая перевести его номер в сетевой формат
MainSockAddr.sin_port := htons(Port);
// Привязка сокета к адресу
if bind(MainSocket, MainSockAddr, SizeOf(MainSockAddr)) = SOCKET_ERROR then
raise ESocketException.Create(
'Невозможно привязать слушающий сокет к адресу: ' +
GetErrorString);
// Установка сокета в режим прослушивания
if listen(MainSocket, SOMAXCONN) = SOCKET_ERROR then
raise ESocketException.Create(
'Невозможно установить сокет в режим прослушивания: ' +
GetErrorString);
WriteLn(OemString('Сервер успешно начал прослушивание порта '), Port);
...
// Основная часть сервера приведена в листинге 2.15
Интервал:
Закладка: