Чтение онлайн

на главную - закладки

Жанры

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Так как клиент по новому протоколу перед отправкой сообщения не обязан ждать, пока сервер ответит на предыдущее, возможны ситуации, когда ответ на следующее сообщение сервер должен готовить уже тогда, когда предыдущее еще не отправлено. Кроме того, сервер может отправить сообщение по собственной инициативе, и этот момент тоже может наступить тогда, когда предыдущее сообщение еще не отправлено. Таким образом, мы вынуждены формировать очередь сообщений в том или ином виде. Так как протокол TCP, с одной стороны, может объединять несколько пакетов в один, а с другой, не обязан отправлять отдельную строку за один раз, проще всего не делать очередь из отдельных строк, а заранее объединять их в одном буфере и затем пытаться отправить все содержимое буфера. Таким буфером в нашем случае является

поле
FSendBuf
, метод
SendString
добавляет строку в этот буфер, a
DoSendBuf
отправляет данные из этого буфера. Если все данные отправить за один раз не удалось, отправленные данные удаляются из буфера, а оставшиеся будут отправлены при следующем вызове
SendBuf
. Все операции с буфером FSendBuf выполняются внутри критической секции, т.к. функция
SendString
может вызываться из других нитей. К каждой строке добавляется символ
#0
, который, согласно протоколу, является для клиента разделителем строк в потоке.

Сигналом к отправке данных является событие

FEvents[1]
. Метод
SendString
, помещая данные в буфер, взводит это событие. Если все содержимое буфера за один раз отправить не удастся, то через некоторое время возникнет событие
FD_WRITE
, означающее готовность сокета к приему новых данных. Это событие привязано у нас к
FEvents[2]
, поэтому при наступлении
FEvents[2]
тоже возможна отправка данных.

Для приема данных здесь также используется буфер. Прямой необходимости в этом нет — можно было, как и раньше, помещать данные непосредственно в переменную, хранящую длину строки, а затем и в саму строку. Сделано это в учебных целях, чтобы показать, как можно работать с подобным буфером. Буфер имеет фиксированный размер. Сначала мы читаем из сокета в этот буфер столько, сколько сможем, а потом начинаем разбирать полученное точно так же, как и раньше, копируя данные то в целочисленную, то в строковую переменную. Когда строковая переменная полностью заполняется, строка считается принятой, для пользователя выводится ответ на нее, а в буфер для отправки добавляется ответная строка. Достоинством такого способа является то, что, с одной стороны, за время обработки одного события сервер может прочитать несколько запросов от клиента (если буфер достаточно велик), но, с другой стороны, это не приводит к зацикливанию, если сообщения поступают непрерывно. Другими словами, разработчик здесь сам определяет, какой максимальный объем данных можно получить от сокета за один раз. Иногда это бывает полезно.

Теперь рассмотрим нить, обслуживающую слушающий сокет. Код этой нити приведен в листинге 2.64.

Листинг 2.64. Код нити, обслуживающей слушающий сокет

unit ListenThread;

{

 Нить, следящая за подключением клиента к слушающему сокету.

 При обнаружении подключения она создает новую нить для работы с подключившимся клиентом, а сама продолжает обслуживать "слушающий" сокет.

}

interface

uses

 SysUtils, Classes, WinSock, WinSock2_Events;

type

 TListenThread = class(TThread)

 private

// Сообщение, которое нужно добавить в лог.

// Хранится в отдельном поле, т.к. метод, вызывающийся

// через Synchronize, не может иметь параметров.

FMessage: string;

// Сокет, находящийся в режиме прослушивания

FServerSocket: TSocket;

//
События нити

// FEvents[0] используется для остановки нити

// FEvents[1] связывается с событием FD_ACCEPT

FEvents: array[0..1] of TWSAEvent;

// Список нитей клиентов

FClientThreads: TList;

// Если True, сервер посылает клиенту сообщения

// по собственной инициативе

FServerMsg: Boolean;

// Вспомогательный метод для вызова через Synchronize

procedure DoLogMessage;

 protected

procedure Execute; override;

// Вывод сообщения в лог главной формы

procedure LogMessage(const Msg: string);

 public

constructor Create(ServerSocket: TSocket; ServerMsg: Boolean);

destructor Destroy; override;

// Вызывается извне для остановки сервера

procedure StopServer;

 end;

implementation

uses

 MainServerUnit, ClientThread;

{ TListenThread }

// "Слушающий" сокет создается в главной нити,

// а сюда передается через параметр конструктора

constructor TListenThread.Create(ServerSocket: TSocket; ServerMsg: Boolean);

begin

 FServerSocket := ServerSocket;

 FServerMsg := ServerMsg;

 // Создаем события

 FEvents[0] := WSACreateEvent;

 if FEvents[0] = WSA_INVALID_EVENT then

raise ESocketError.Create(

'Ошибка при создании события для сервера:' + GetErrorString);

 FEvents[1] := WSACreateEvent;

 if FEvents[1] = WSA_INVALID_EVENT then

raise ESocketError.Create(

'Ошибка при создании события для сервера: ' + GetErrorString);

 if WSAEventSelect(FServerSocket, FEvents[1], FD_ACCEPT) = SOCKET_ERROR then

raise ESocketError.Create(

'Ошибка при привязывании серверного сокета к событию: ' + GetErrorString);

 FClientThreads := TList.Create;

 inherited Create(False);

end;

destructor TListenThread.Destroy;

begin

 // Убираем за собой

 FClientThreads.Free;

 WSACloseEvent(FEvents[0]);

 WSACloseEvent(FEvents[1]);

 inherited;

end;

procedure TListenThread.Execute;

var

 // Сокет, созданный для общения с подключившимся клиентом

Поделиться:
Популярные книги

Камень Книга седьмая

Минин Станислав
7. Камень
Фантастика:
фэнтези
боевая фантастика
6.22
рейтинг книги
Камень Книга седьмая

Я сделаю это сама

Кальк Салма
1. Магический XVIII век
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Я сделаю это сама

Кровь на эполетах

Дроздов Анатолий Федорович
3. Штуцер и тесак
Фантастика:
альтернативная история
7.60
рейтинг книги
Кровь на эполетах

Сыночек в награду. Подари мне любовь

Лесневская Вероника
1. Суровые отцы
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Сыночек в награду. Подари мне любовь

Кодекс Крови. Книга ХII

Борзых М.
12. РОС: Кодекс Крови
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Кодекс Крови. Книга ХII

Найди меня Шерхан

Тоцка Тала
3. Ямпольские-Демидовы
Любовные романы:
современные любовные романы
короткие любовные романы
7.70
рейтинг книги
Найди меня Шерхан

Идеальный мир для Лекаря 4

Сапфир Олег
4. Лекарь
Фантастика:
фэнтези
юмористическая фантастика
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 4

Релокант. По следам Ушедшего

Ascold Flow
3. Релокант в другой мир
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Релокант. По следам Ушедшего

Мир-о-творец

Ланцов Михаил Алексеевич
8. Помещик
Фантастика:
альтернативная история
5.00
рейтинг книги
Мир-о-творец

Протокол "Наследник"

Лисина Александра
1. Гибрид
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Протокол Наследник

Кодекс Охотника. Книга VII

Винокуров Юрий
7. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
4.75
рейтинг книги
Кодекс Охотника. Книга VII

Попаданка в семье драконов

Свадьбина Любовь
Попаданка в академии драконов
Любовные романы:
любовно-фантастические романы
7.37
рейтинг книги
Попаданка в семье драконов

Новые горизонты

Лисина Александра
5. Гибрид
Фантастика:
попаданцы
технофэнтези
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Новые горизонты

Скрываясь в тени

Мазуров Дмитрий
2. Теневой путь
Фантастика:
боевая фантастика
7.84
рейтинг книги
Скрываясь в тени