Использование Delphi для создания серверов связи и обмена данными - Анализ технологии трансиверов (Часть 2) Автор: 火鸟 [email protected] 2. Подробное объяснение службы трансиверов 1. Краткое описание анализа службы трансивера Служба трансивера является основным компонентом системы трансивера. Ядро трансивера отвечает за чтение определений портов и каналов, а также параметров, установленных консолью трансивера, из библиотеки конфигурации системы, динамическое создание и управление портами связи и их взаимосвязями во время выполнения. и управление данными. Планирование приема, отправки и буферизации, управление журналами и очередями и т. д. Transceiver Shell — это реализация всех типов портов, поддерживаемых для отправки и получения данных. 2. Краткое описание конструкции службы трансивера Служба трансивера разрабатывается на основе приложения службы в Delphi. Приложение службы может работать в состоянии системы, а не в состоянии пользователя. Диспетчер управления службами операционной системы (SCM) отвечает за работу и управление программой. Сервис не имеет пользовательского интерфейса и принадлежит системной фоновой программе. Transceiver Kernel — это серия методов класса Transceiver, которые устанавливают и управляют Transceiver Shell, а Transceiver Shell — это набор объектов, отвечающих за связь. Примечание. Из соображений производительности и нагрузки ядро трансивера только логически реализует функциональное разделение на диаграмме архитектуры, а составляющие модули не реализованы полностью на объектной основе. 3. Краткое описание реализации службы приемопередатчика i. Создайте приложение-службу. В главном меню Delphi выберите «Файл...» и выберите «Приложение-служба» во всплывающем диалоговом окне «Новые элементы». Вы можете увидеть созданную программу. структура следующая: PROgram Project1; использует SvcMgr, Unit1 в 'Unit1.pas' {Service1: TService};{$R *.RES}begin Application.Initialize;CreateForm(TService1, Service1); Application.Run;end.unit Unit1;interfaceuses Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;type TService1 = class(TService) Private {Частные объявления} public function GetServiceController: TServiceController {Общественные объявления}; end;var Service1: TService1;реализация{$R *.DFM}процедуры ServiceController(CtrlCode: DWord);begin Service1.Controller(CtrlCode);end;функция TService1.GetServiceController: TServiceController;begin Result:= ServiceController;end;end. Вы можете видеть, что в дополнение к SvcMgr, используемому для управления службами, указанному в модуле Uses, TService1 наследует от TServiced вместо TForm, перегруженную функцию GetServiceController и процесс ServiceController, вызываемый в режиме stdcall, он создается с помощью. Delphi В сервисной программе нет ничего особенного, поклонники Delphi, возможно, захотят еще раз поаплодировать: в этом мощное очарование Delphi RAD. Кроме того, поскольку приложение-службу нельзя отладить непосредственно во время выполнения и оно не имеет пользовательского интерфейса, во время разработки следует учитывать бесинтерфейсный вывод отладочной информации, чтобы облегчить отладку и устранение неполадок. ii Чтобы создать класс порта, отвечающий конкретным потребностям, и использовать ядро трансивера с унифицированным механизмом работы и обработки, порты в оболочке трансивера должны иметь унифицированные правила обработки. Некоторые порты в оболочке являются существующими классами компонентов в Delphi. среда разработки (например, TCP, FTP и т. д.), а некоторые нет (например, MSMQ, File и т. д.). На данный момент вам необходимо создать класс, который может удовлетворить ваши потребности. Например: type//Поскольку пользовательский интерфейс отсутствует, он наследуется от TComponent вместо TControl TFilePort=class(TComponent) Private FilePath:string;//Получить или сохранить местоположение папки с файлом Prefix:string;//File prefix suffix:string; //File suffix end; После установления класса TFilePort ядро Transceiver может использовать унифицированный метод обработки классов для ссылки на объекты и управления ими для достижения цели доступа к определенным файлам из папки, указанной FilePath. При использовании в качестве источника файлы, соответствующие условиям, будут получены из определенной папки. При использовании в качестве цели данные, полученные из соответствующего источника, будут записаны в указанный файл (фактически фактические параметры каждого объекта порта берутся из файла). определение таблицы портов в библиотеке конфигурации системы). Другой пример: type TCOMPort=class(TComponent) Private ComFace:string; //COM-интерфейс для получения или отправки данных end; TCOMPort будет использоваться для получения данных или отправки данных в указанный интерфейс COM-компонента. Выполните последующую обработку. В Delphi класс OleVariant является одним из способов реализации вызовов COM-компонентов. Необходимость использования класса TCOMPort заключается в том, что Transceiver будет создавать экземпляр COM-интерфейса, определенного TCOMPort, в объекте OleVariant только тогда, когда требуется необходимый доступ к данным и объекту. будет выпущен после использования. Это может снизить нагрузку на трансивер и COM-сервер. Те же соображения применимы и к другим аналогичным компонентам. Пример класса, приведенный здесь автором, является всего лишь моделью, и при необходимости следует добавлять соответствующие методы и события. Классы, реализованные автором при разработке, включают: TCOMPort, TMSMQPort, TDBPort, TFilePort и т. д.iii. Поддержка нескольких каналов — массив объектов, объявляющих порты. Трансивер рассматривает процесс связи как процесс потока данных от источника к цели. Такой процесс является каналом в трансивере, и этот канал состоит как минимум из двух. порты (один для источника и один для цели), поэтому, если вы хотите определить неопределенное количество нескольких каналов со свободным объединением источника и цели, вы должны объявить их для источника и цели соответственно. Массивы объектов различных классов Порта (и установить для них соответствующие отношения, как вы увидите позже). Например: Private { Частные объявления }TCPSource:array of TServerSocket;//массив объектов для источника TCP TCPTarget:array of TClientSocket;//массив объектов для целевого TCP MailSource:массив TIdPOP3 //для массива объектов источника почты MailTarget:array; из TIdSMTP //массив объектов для почтового целевого файла fileSource:массив TFilePort; // Массив объектов для исходного файла fileTarget: массив TFilePort; // Массив объектов для целевого файла comSource: массив TCOMPort; // Массив объектов для исходного файла COM comTarget: массив целевых объектов COM Примечание. Правила работы портов для однотипных Source и Target совершенно разные, они рассматриваются как совершенно разные и не связанные напрямую объекты в концепции Transceiver. Поэтому для одного и того же типа Порта массивы объектов также создаются отдельно по Source и Target. iv. Создание экземпляра массива объектов во время выполнения. Количество элементов в каждом массиве объектов определяется построителем портов во время выполнения. Если пользователь определяет некоторые типы портов через консоль трансивера, построитель портов создаст их экземпляры в соответствии с их количеством и. соответствующие параметры. Массив объектов. В противном случае экземпляр массива объектов не будет создан. В объекте «Порт типа источника» для свойства «Имя» задана форма «Получение» + идентификатор порта. В последующих триггерах приема данных это поможет диспетчеру данных найти объект и единообразно запланировать различные типы объектов порта. Атрибут Tag используется для предоставления контроллеру канала информации о целевом идентификаторе канала, в котором он расположен. Ниже приведена часть экземпляра массива объектов comSource в Port Builder. start //Создать COM/ Порт приема itmp:=high(comSource)+1;// Получить текущее максимальное количество comSource, itmp — это целочисленная переменная SetLength(comSource ,itmp +1); // Добавляем элемент массива comSource comSource [itmp]:=TCOMPort.Create(self); // Создаем экземпляр элемента comSource[itmp].Name:= 'Receive'+inttostr(isource); //Установим для атрибута Name значение 'Receive'+Port ID, а isource — текущий PortID целочисленного типа comSource [itmp].Tag:= itarget; //Установим целевой идентификатор; Канал, в котором он расположен. NullTest :=rece.Fields['Address'].value;//Получить значение конфигурации системы COMFace, NullTest — это переменная Variant if (NullTest <>null) и (trim(NullTest)<>''), затем BegincomSource [itmp].ComFace:=NullTest; //Назначаем действительные значения ComFaceNullTest:=rece.Fields['interval'].value //Получаем временной интервал триггера для COM-объектов для получения данных в конфигурации системы SetTimer(application.handle,isource,NullTest*60000; ,nil); //Устанавливаем триггер для текущего порта для регулярного сбора данных, isource — это Port IDendelsecomSource [itmp].Tag:=-1;//Инициализация не удалась, помечена как недопустимая. Port end; comSource — порт исходного класса, используемый для вызова интерфейса, определенного в ComFace, и получения данных через определенный интервал времени, соответствующий comTarget. Реализация такая: аналогично, за исключением того, что отправка данных в ComFace comTarget — это процесс в реальном времени, поэтому нет необходимости использовать интервал запуска, а два оператора для установки часов можно опустить. Создание и инициализация других типов объектов Port аналогичны. Например, еще один фрагмент реализации MailTarget: start //Создаем SMTP/Порт отправки itmp:=high(MailTarget)+1; SetLength(MailTarget,itmp+1); MailTarget[itmp]:=TIdSMTP.Create(self); itmp].Name:='send'+ inttostr(itarget); MailTarget[itmp].Tag:=3;//Установить в качестве идентификации типа целевого порта NullTest:=rece.Fields['Address'].value; //Адрес почтового сервера if (NullTest <>null) и (trim(NullTest) <>''), затем MailTarget[itmp].Host :=NullTest else bValid:=false; NullTest:=rece.Fields['Port'].value; //Порт почтового сервера if NullTest <>null then(if NullTest<>0 then MailTarget[itmp].Port :=NullTest)else bValid:=NullTest: =rece.Fields['user'].value;//Имя пользователя для входа, если NullTest <>null thenMailTarget[itmp].UserId :=NullTest else bValid:=false; NullTest:=rece.Fields['password'].value;//Пароль для входа………………………end;Может быть, у вас будет это I Я в замешательстве. Большое количество коммуникационных компонентов Transceiver Shell создается Port Builder во время выполнения. Будет ли производительность Transceiver Service высокой? Фактически, миссия Port Builder завершается один раз, когда происходит событие ServiceCreate. Количество портов оболочки будет влиять только на скорость инициализации службы трансивера. Скорость связи порта оболочки и общая производительность службы трансивера. Это не повлияет, конечно, на системные ресурсы. Это может занять немного больше. v. Динамическое распределение и обработка событий. Среди нескольких коммуникационных портов, поддерживаемых Transceiver Shell, используйте TServerSocket (возможно, вам больше понравится использовать коммуникационный компонент Indy, но это не нарушает идею дизайна Transceiver Service, это всего лишь модификация). на уровне оболочки или только что добавленный), реализованный TCPSource является более отличительным, поскольку TServerSocket, как исходный порт, отличается от таких объектов, как COM или POP3, которые необходимо запускать регулярно. Он находится в Transceiver. Служба всегда находится в состоянии прослушивания после запуска. Это компонент, который генерирует соответствующие события, когда ClientSocket подключается и отправляет данные. Ниже приведен фрагмент экземпляра TCPSource: start //Создание TCP/порта приема itmp:=high(TCPSource)+1;SetLength(TCPSource,itmp+1); TCPSource [itmp]:=TServerSocket.Create(self); [ itmp].OnClientRead:=TCPServersClientRead;//Назначаем процесс обработки события OnClientRead TCPServersClientRead TCPSource [itmp].OnClientError:=TCPServerClientError;//Назначаем процесс обработки события OnClientError TCPServerClientErrorTCPSource [itmp].Name:= 'Receive'+inttostr(isource); //Установим для свойства Name значение 'Receive'+Port ID; TCPSource [itmp ].Tag:=itarget; //Устанавливаем целевой IDTCPSource своего канала [itmp].Socket.Data:=@ TCPSource; [itmp].Tag;//Прикрепите целевой идентификатор этого объекта Port к объекту Socket в качестве данных указателя……end Вернитесь и посмотрите на обработку нашего comSource. Во время создания экземпляра мы устанавливаем триггер. часы, но как обрабатывать события, когда часы срабатывают? Точно так же это еще и динамическое распределение обработки событий. Определение обработки часов comSource можно добавить в обработку событий ServiceCreate: application.OnMessage:=Timer; чтобы реализовать перегрузку обработки сообщений. Когда генерируется сообщение из приложения, в событии Timer срабатывает таймер. отфильтровать и обработать его. С помощью сообщения WM_TIMER, вызванного часами, вы можете вызвать метод сбора данных определенного исходного порта в соответствии с идентификатором порта и ввести: Процедура TCarrier.Timer(var Msg: TMsg; var Handled: Boolean);var stmp:string; Obj:TComponent;begin if Msg.message =WM_TIMER then//Начинается обработка сообщения часов//Найти объект, определяющий это сообщение, в соответствии с идентификатором порта, который вызвал сообщение Obj:=FindComponent( 'Receive'+inttostr (Msg.WParam)); если obj=nil, то выходим;//Выходим из обработки, если не найден stmp:=obj.ClassName;//Отражаем, чтобы получить информацию о типе этого объекта порта, если stmp='TIdPOP3' then GetPOP3(TIdPOP3(Obj)); если stmp='TIdFTP' then GetFTP(TIdFTP(obj)); если stmp='TFilePort' then GetFile(TFilePort(Obj)); then GetCOM(TCOMPort(Obj));//Вызов процесса получения данных COMSource………… end;end; vi. Получить данные Ниже приведена процедура обработки получения данных COMSource TCarrier.GetCOM(COMObj: TCOMPort);var stmp:string;begin try//Создать объект COM-компонента на основе значения. ComFace COMInterface:=CreateOleObject (COMObj.ComFace); stmp:=COMInterface.GetData //Вызов согласованного метода интерфейса для получения данных, пока stmp<>#0; do // #0 — согласованный флаг завершения извлечения данных. start DataArrive(stmp, COMObj.Tag // передается диспетчеру данных для унифицированной обработки, COMObj.Tag — идентификатор целевого порта канала, в котором находится объект); stmp:=COMInterface.GetData; end ; COMInterface:= Не назначено; кроме COMInterface: = Не назначено; // Завершите операцию извлечения данных и освободите объект компонента до следующего вызова триггера. Ниже представлена обработка получения данных TCPSource: sender).data ^));//Оставлено диспетчеру данных для унифицированной обработки, Второй параметр — это значение указателя идентификатора целевого порта, прикрепленное к отправителю объекта Socket, end; разные типы объектов исходного порта получают данные по-разному, но в конечном итоге полученные данные передаются диспетчеру данных. На уровне реализации каждый раз, когда добавляется объект приема данных и реализуется прием данных, для Transceiver Shell реализуется новый исходный порт. Примечание. Здесь автор реализует только получение текстовых данных. Пользователям может потребоваться получать объекты памяти, потоки данных или двоичные данные и просто внести небольшие изменения в код приема. vii. Планирование данных. Планирование данных службы приемопередатчика завершается логическим блоком диспетчера данных. Основная задача диспетчера данных состоит в единообразном управлении и контроле данных, полученных от различных исходных портов, и работе совместно с контроллером каналов. В соответствии с распределением данных по различным целевым портам отслеживайте, являются ли результаты отправки успешными, и решайте, нужно ли отправлять данные в диспетчер очереди и регистратор журнала для буферизации и обработки журнала на основе результатов отправки и настроек. библиотеки конфигурации системы. Затем посмотрите на метод DataArrive исходного порта, передающий данные: / Если данные пусты, выходим iLogID:=-1; dTime:= now; //Получаем время if sData[length(sdata)]=#0 then; sdata:=copy(sdata,1,length(sdata)-1);//Формат строки для совместимости с языком C bSendSeccess:=DataSend(sdata,PortID);//Вызов диспетчера данных для отправки метода отправки, PortID — это идентификатор целевого порта, если (TSCfg.LogOnlyError=false) или (bSendSeccess=false) theniLogID:=writeLog(dTime, now,sData, PortID, bSendSeccess);//Запись логов по правилам обработки логов и отправка результатов в информацию о конфигурации системы if (TSCfg.Queueing=True) и (bSendSeccess=false) then PutQueue(dTime, now,sData, PortID, bSendSeccess, iLogID) ; // Определить окончание обработки очереди на основе определения конфигурации очереди в информации о конфигурации системы пакета, указанной выше; Метод DataArrive диспетчера, в котором обработка очереди определяется в соответствии с информацией о конфигурации системы и статусом отправки, также может быть адаптирован для обязательной обработки очереди. Ниже приведен метод DataSend диспетчера данных, который используется для распределения и обработки данных в соответствии с типом целевого порта: Function TCarrier.DataSend(sData:String;PortID:Integer):boolean;var Obj:TComponent;begin DataSend:=false ;Obj:=FindComponent('Send'+inttostr(PortID)); //Находим объект по идентификатору порта if (obj=nil) или (obj.Tag =-1) затем выходим;//Объект не существует или помечен как недействительный из-за ошибки инициализации. Случай порта obj.Tag of 1:DataSend:=PutTCP(TClientSocket(obj),sdata); 3:DataSend:=PutSMTP(TIdSMTP); (obj), sdata); 5:DataSend:=PutFTP(TIdFTP(obj),sdata); 7:DataSend:=PutHTTP(TIdHTTP(obj),sdata); 9:DataSend:=PutFile(TFilePort(obj),sdata); 11:DataSend:=PutMSMQ(TMSMQPort (obj),sdata); PutDB(TDBPort(obj),sdata); 15:DataSend:=PutCOM(TCOMPort (obj),sdata); …………… …………… end;end; тип порта. Если да, то лучшим способом управления распределением данных было бы использование функции обратного вызова, но в данном случае это привело бы к незнанию того, какой член массива объектов должен обрабатывать данные. Кроме того, текущий метод обработки не полностью разделяет ядро трансивера и оболочку трансивера, и нам следует искать более абстрактный и независимый метод обработки. viii. Отправка данных Ниже приведена функция отправки TCP TCarrier.PutTCP(TCPOBJ:TClientSocket;sdata:string):Boolean;var itime:integer;begin PutTCP:=false; try TCPOBJ.Open itime:=; gettickcount; // Время начала повторения application.ProcessMessages до (TCPOBJ.Active=true) или (gettickcount-itime>5000); //Выходим из цикла, если соединение установлено успешно или происходит 5-секундный таймаут, если TCPOBJ.Active then start TCPOBJ.Socket.SendText(sdata); PutTCP:=true;//The Возвращаемое значение — это когда данные отправлены успешно. Trueend;TCPOBJ.Close; ExceptTCPOBJ.Close end; Ниже приведена функция отправки COM. TCarrier.PutCOM(COMOBJ:TCOMPort;sdata:string):Boolean;var Com:OleVariant;begin PutCOM:=false; try Com:=CreateOleObject(COMOBJ.ComFace);//Создаем предопределенный интерфейс PutCOM:=Com.PutData ( sdata);//Вызов предопределенного метода Com:= Unassigned; ExceptCom:= Unassigned end; end Остальные типы отправки Port аналогичны и здесь не будут повторяться; На данный момент базовая обработка источника и цели завершена. Установлена базовая функция связи. После свободного сопоставления различных типов источника и цели могут быть реализованы совершенно разные функции связи. Создав несколько каналов, вы можете централизованно реализовать обработку связи для множества различных функций. ix. Обработка очереди: после отправки данных с помощью метода DataArrive, описанного выше, диспетчер данных вызывает метод writeLog для регистрации данных и метод PutQueue для обработки очереди. Оба они обрабатывают информацию в соответствии с. системные параметры. Хранилище не является целью этой статьи. Повторная обработка очереди аналогична принципу распределения обработки по типу порта в событии Timer. Он основан на срабатывании таймера очереди для чтения буферизованных данных из базы данных и повторного вызова DataSend в соответствии с идентификатором целевого порта. повторите попытку отправки данных, если передача успешна, транзакция этой передачи данных завершена, в противном случае она повторно войдет в очередь и будет ждать следующего времени запуска для повторной попытки, пока передача не будет успешной или не будет установлено максимальное количество повторов. достигается. три, Краткое изложение опыта разработки Поскольку цель этой статьи — объяснить основные идеи и концепции дизайна Transceiver, она упрощает и ослабляет многопоточную обработку, объединение объектов и поддержку транзакций, которые Transceiver следует рассматривать как фоновую службу, а более сложные и мощное управление исходными и целевыми группами и Ча. nnel, возможность отправлять и получать объекты памяти, потоки данных, двоичные данные, чтение информации о конфигурации системы и реализацию ее классов инкапсуляции, безопасность системы и данных и т. д. Я надеюсь, что читатели смогут предоставить некоторую информацию и понять дизайнерские идеи Transceiver. Вдохновлять искры вдохновения в реальных разработках и создавать более выдающееся и мощное программное обеспечение. Автор: Firebird [email protected] Использование Delphi для создания серверов связи и обмена данными — Технический анализ трансивера (Часть 1) Использование Delphi для создания серверов связи и обмена данными — Технический анализ трансивера (Часть 2) Реализация классов сбора данных с помощью C# Обзор . NET Collections и связанные с ними старые технические вещи: ярлыки программ/удаления программ/самоудаление EXE старые вещи своими руками: заметки об опыте алгоритма программирования в детстве