В листинге 2.63 приведен код нити, взаимодействующей с клиентом (код методов LogMessage
и DoLogMessage
опущен, т. к. он идентичен приведенному в листингах 2.20 и 2.7 соответственно).
unit ClientThread;
{
Нить, обслуживающая одного клиента.
Выполняет цикл, выход из которого возможен по внешнему сигналу или при возникновении ошибки на сокете. Умеет отправлять клиенту сообщения по внешнему сигналу.
}
interface
uses
Windows, Classes, WinSock, Winsock2_Events, ShutdownConst, SysUtils, SyncObjs;
type
TClientThread = class(TThread)
private
// Сообщение, которое нужно добавить в лог,
// хранится в отдельном поле, т. к. метод, вызывающийся через
// Synchronize, не может иметь параметров.
FMessage: string;
// Префикс для всех сообщений лога, связанных с данным клиентом
FHeader: string;
// Сокет для взаимодействия с клиентом
FSocket: TSocket;
// События нити
// FEvents[0] используется для остановки нити
// FEvents[1] используется для отправки сообщения
// FEvents[2] связывается с событиями FD_READ, FD_WRITE и FD_CLOSE
FEvents; array[0..2] of TWSAEvent;
// Критическая секция для доступа к буферу исходящих
FSendBufSection: TCriticalSection;
// Буфер исходящих
FSendBuf: string;
// Вспомогательный метод для вызова через Synchronize
procedure DoLogMessage;
// Функция, проверяющая, завершила ли нить работу
function GetFinished: Boolean;
protected
procedure Execute; override;
// Вывод сообщения в лог главной формы
procedure LogMessage(сonst Msg: string);
// Отправка клиенту данных из буфера исходящих
function DoSendBuf: Boolean;
public
constructor Create(ClientSocket: TSocket; const ClientAddr: TSockAddr);
destructor Destroy; override;
// Добавление строки в буфер исходящих
procedure SendString(const S: string);
// Остановка нити извне
procedure StopThread;
property Finished: Boolean read GetFinished;
end;
ESocketError = class(Exception);
implementation
uses
MainServerUnit;
{ TClientThread }
// Сокет для взаимодействия с клиентом создается в главной нити,
// а сюда передается через параметр конструктора. Для формирования
// заголовка сюда же передается адрес подключившегося клиента
constructor TClientThread.Create(ClientSocket: TSocket; const ClientAddr: TSockAddr);
begin
FSocket:= ClientSocket;
// заголовок содержит адрес и номер порта клиента.
// Этот заголовок будет добавляться ко всем сообщениям в лог
// от данного клиента.
FHeader:=
'Сообщение от клиента ' + inet_ntoa(ClientAddr.sin_addr) +
': ' + IntToStr(ntohs(ClientAddr.sin_port)) + ': ';
// Создаем события и привязываем первое из них к сокету
FEvents[0]:= WSACreateEvent;
if FEvents[0] = WSA_INVALID_EVENT then
raise ESocketError.Create(