Usando Delphi para estabelecer servidores de comunicação e troca de dados - Análise da tecnologia Transceptor (Parte 2) Autor: 火鸟 [email protected] 2. Explicação detalhada do Serviço Transceptor 1. Resumo da análise do serviço do transceptor O serviço do transceptor é o componente principal do sistema do transceptor e é responsável pela leitura das definições e parâmetros de porta e canal definidos pelo console do transceptor na biblioteca de configuração do sistema, criando e controlando dinamicamente as portas de comunicação e seus relacionamentos durante o tempo de execução. e controle de dados, agendamento de recebimento, envio e buffer, gerenciamento de logs e filas, etc. Transceptor Shell é a implementação de todos os tipos de portas suportadas para envio e recebimento de dados. 2. Resumo do design do serviço do transceptor O serviço do transceptor é desenvolvido a partir do aplicativo de serviço no Delphi. O aplicativo de serviço pode ser executado no estado do sistema em vez do estado do usuário. O Service Control Manager (SCM) do sistema operacional é responsável pela operação e gerenciamento do programa. O Serviço não possui interface de usuário e pertence ao programa em segundo plano. Transceiver Kernel é uma série de métodos da classe Transceiver que estabelece e controla o Transceiver Shell, e o Transceiver Shell é uma coleção de objetos responsáveis pela comunicação. Nota: Devido a considerações de desempenho e carga, o Kernel do Transceptor implementa logicamente apenas a divisão funcional no diagrama de arquitetura, e os módulos constituintes não são implementados de maneira completamente baseada em objetos. 3. Resumo da implementação do serviço do transceptor i. Crie um aplicativo de serviço Selecione NOVO|Outro... no menu principal do Delphi... e selecione NOVO|Aplicativo de serviço na caixa de diálogo pop-up Novos itens. estrutura é a seguinte: PRogram Project1; usa SvcMgr, Unit1 em 'Unit1.pas' {Service1: TService};{$R *.RES}begin Application.Initialize(TService1, Service1); Application.Run;end.unit Unit1;faz interface com Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, SvcMgr, Diálogos;tipo TService1 = class(TService) private { Declarações privadas } função pública GetServiceController: TServiceController override; end;var Service1: TService1;implementação{$R *.DFM}procedimento ServiceController(CtrlCode: DWord);begin Service1.Controller(CtrlCode);end;função TService1.GetServiceController: TServiceController;begin Resultado:= ServiceController;end;end. Você pode ver que além do SvcMgr usado para gerenciamento de serviços referenciado na unidade de uso, TService1 herda de TServiced em vez de TForm, uma função GetServiceController sobrecarregada e o processo ServiceController chamado no modo stdcall, ele é criado com Delphi Não há muito especial em um programa de serviço, os fãs do Delphi podem querer torcer novamente, esse é o charme poderoso do Delphi RAD. Além disso, como o aplicativo de serviço não pode ser depurado diretamente em tempo de execução e não possui uma interface de usuário, a saída sem interface de informações de depuração deve ser considerada durante o desenvolvimento para facilitar a depuração e a solução de problemas. ii. Para criar uma classe Port que atenda a necessidades específicas e usar o Kernel do Transceptor com um mecanismo unificado de execução e processamento, as Portas no Shell do Transceptor devem ter regras de processamento unificadas. Algumas Portas no Shell são classes de componentes existentes no Delphi. ambiente de desenvolvimento (como TCP, FTP, etc.), e alguns não (como MSMQ, Arquivo, etc.). Neste momento, você precisa criar uma classe que possa atender às suas necessidades. Por exemplo: type//Como não há interface de usuário, ela é herdada de TComponent em vez de TControl TFilePort=class(TComponent) private FilePath:string;//Obtém ou salva o local da pasta do arquivo Prefix:string;//Arquivo prefix suffix:string; //File suffix end; Após estabelecer a classe TFilePort, o Transceiver Kernel pode usar um método de processamento de classe unificado para referenciar e gerenciar objetos para atingir o objetivo de acessar arquivos específicos da pasta especificada por FilePath. Se usado como Fonte, os arquivos que atendem às condições serão obtidos de uma pasta específica. Se usado como Destino, os dados obtidos da Fonte correspondente serão gravados no arquivo especificado (na verdade, os parâmetros reais de cada objeto Port vêm do arquivo Port. definição da tabela Port na biblioteca de configuração do sistema). Outro exemplo: type TCOMPort=class(TComponent) private ComFace:string; //Interface COM para obter ou enviar dados final TCOMPort será usado para obter dados ou enviar dados para a interface do componente COM especificada Realizar o processamento subsequente. No Delphi, a classe OleVariant é uma das maneiras de implementar chamadas de componentes COM. A necessidade de usar a classe TCOMPort é que o Transceptor instanciará a interface COM definida por TCOMPort em um objeto OleVariant somente quando o acesso aos dados necessário for necessário e ao objeto. será liberado após o uso, isso pode reduzir a pressão de carga no transceptor e no servidor COM. As mesmas considerações se aplicam a outros componentes semelhantes. O exemplo de classe fornecido pelo autor aqui é apenas um modelo, e métodos e eventos apropriados devem ser adicionados quando necessário. As classes implementadas pelo autor durante o desenvolvimento incluem: TCOMPort, TMSMQPort, TDBPort, TFilePort, etc.iii. Suporte para múltiplos canais - uma matriz de objetos declarando Portas considera um processo de comunicação como um processo de fluxo de dados da origem ao destino. Tal processo é um Canal no Transceptor, e este Canal é composto por pelo menos dois. portas (uma para Origem e outra para Destino), portanto, se você deseja definir um número indefinido de múltiplos Canais com Origem e Destino combinados livremente, você deve declará-los para Origem e Destino respectivamente. Arrays de objetos de diversas classes Port (e estabeleça relacionamentos correspondentes para eles, como você verá mais adiante). Por exemplo: private {Declarações privadas }TCPSource:array de TServerSocket;//Array de objetos para origem TCP TCPTarget:array de TClientSocket;//Array de objetos para TCP Target MailSource:array de TIdPOP3; //Para array de objetos de origem de correio MailTarget:array; de TIdSMTP; //Array de objetos para Mail Target fileSource:array de TFilePort; //Array de objetos para origem de arquivo fileTarget:array de TFilePort; //Array de objetos para destino de arquivo comSource:array de objetos de origem COM comTarget:array de objeto TCOMPort; As regras de operação portuária para Fonte e Destino do mesmo tipo são completamente diferentes, são consideradas objetos completamente diferentes e não diretamente relacionados no conceito de Transceptor. Portanto, para o mesmo tipo de Porta, arrays de objetos também são criados separadamente de acordo com Fonte e Destino. iv. Instanciar o array de objetos em tempo de execução O número de elementos em cada array de objetos é gerenciado pelo Port Builder em tempo de execução. Se o usuário definir alguns tipos de portas através do Transceiver Console, o Port Builder irá instanciá-los de acordo com seu número e número. respectivos parâmetros. Caso contrário, o array de objetos não será instanciado. No objeto Porta do tipo Fonte, a propriedade Nome é definida como 'Receber' + ID da Porta. Nos acionadores de recepção de dados subsequentes, isso ajudará o Data Dispatcher a localizar o objeto e agendar uniformemente diferentes tipos de objetos Porta. O atributo Tag é usado para fornecer ao Controlador de Canal as informações de ID de destino do Canal no qual ele está localizado. A seguir está a parte de instanciação da matriz de objetos comSource no Port Builder Begin //Create COM/ Receive Port itmp:=high(comSource)+1;// Obtém o número máximo atual de comSource, itmp é a variável inteira SetLength(comSource ,itmp +1); // Adiciona um membro da matriz comSource comSource [itmp]:=TCOMPort.Create(self); 'Receive'+inttostr(isource); //Defina o atributo Name como 'Receive'+Port ID, e isource é o PortID atual do tipo inteiro comSource [itmp].Tag:= itarget; o Canal onde está localizado NullTest :=rece.Fields['Address'].value;//Obtém o valor da configuração do sistema COMFace, NullTest é uma variável Variant if (NullTest <>null) e (trim(NullTest)<>'') então begincomSource [itmp].ComFace:=NullTest; //Atribuir valores válidos ao ComFaceNullTest:=rece.Fields['interval'].value //Obter o intervalo de tempo de disparo dos objetos COM para obter dados na configuração do sistema SetTimer(application.handle,isource,NullTest*60000; ,nil); //Estabelece um relógio de disparo para a porta atual para coletar dados regularmente, isource é Port IDendelsecomSource [itmp].Tag:=-1;//Falha na inicialização, marcada como inválida Port end comSource é a classe Source Port usada para chamar a interface definida no ComFace e obter dados após um determinado intervalo de tempo, correspondente a comTarget A implementação é semelhante, exceto que o envio de dados ao ComFace do comTarget é um processo em tempo real, portanto não há necessidade de usar o intervalo de disparo e as duas instruções para estabelecer o relógio podem ser omitidas. A criação e inicialização de outros tipos de objetos Port são semelhantes. Por exemplo, outro fragmento de implementação do MailTarget: begin //Create SMTP/Send Port itmp:=high(MailTarget)+1; itmp].Name:='enviar'+ inttostr(itarget); MailTarget[itmp].Tag:=3;//Definir como identificação do tipo de porta de destino NullTest:=rece.Fields['Address'].value; //Endereço do servidor de correio if (NullTest <>null) and (trim(NullTest) <>'') então MailTarget[itmp].Host :=NullTest else bValid:=false; NullTest:=rece.Fields['Port'].value; //Porta do servidor de correio if NullTest <>null then(if NullTest<>0 then MailTarget[itmp].Port :=NullTest)else bValid:=false; =rece.Fields['user'].value;//Nome de usuário de login if NullTest <>null thenMailTarget[itmp].UserId :=NullTest else bValid:=NullTest:=rece.Fields['password'].value;//Senha de login………………………end;Talvez você tenha isso eu estou confuso. Um grande número de componentes de comunicação do Transceiver Shell são criados pelo Port Builder em tempo de execução. O desempenho do Transceiver Service será alto? Na verdade, a missão do Port Builder é concluída uma vez quando ocorre o evento ServiceCreate. O número de portas Shell afetará apenas a velocidade de inicialização do serviço transceptor. A velocidade de comunicação da porta Shell e o desempenho geral do serviço transceptor. não será afetado. Claro, os recursos do sistema Pode demorar um pouco mais. v. Alocação dinâmica e processamento de eventos Entre as diversas portas de comunicação suportadas pelo Transceiver Shell, use TServerSocket (você pode estar mais inclinado a usar o componente de comunicação do Indy, mas isso não viola a ideia de design do Transceiver Service, é apenas uma modificação no nível do Shell ou apenas adicionado), o TCPSource implementado é mais diferenciado, pois TServerSocket, como Porta de Origem, é diferente de objetos como COM ou POP3 que precisam ser acionados regularmente. O Serviço está sempre em estado de escuta após ser iniciado. É um componente que gera eventos correspondentes quando um ClientSocket se conecta e envia dados. A seguir está o fragmento de instanciação de TCPSource: begin //Create TCP/Receive Port itmp:=high(TCPSource)+1;SetLength(TCPSource,itmp+1); [ itmp].OnClientRead:=TCPServersClientRead;//Atribuir o processo de processamento do evento OnClientRead a TCPServersClientRead TCPSource [itmp].OnClientError:=TCPServerClientError;//Atribuir o processo de processamento do evento OnClientError para TCPServerClientErrorTCPSource [itmp].Name:= 'Receive'+inttostr(isource); //Defina a propriedade Name como 'Receive'+Port ID; TCPSource [itmp ].Tag:=itarget; //Definir o IDTCPSource alvo do seu Canal [itmp].Socket.Data:=@ TCPSource [itmp].Tag;//Anexar o ID de destino deste objeto Port ao objeto Socket como dados de ponteiro…………………end; relógio, mas como lidar com os eventos quando o relógio dispara? Da mesma forma, é também a alocação dinâmica do processamento de eventos. A definição de processamento do relógio do comSource pode ser adicionada ao processamento do evento ServiceCreate: application.OnMessage:=Timer; para implementar a sobrecarga do processamento de mensagens. Quando uma mensagem do Aplicativo for gerada, o Timer será acionado. filtrar e processá-lo. Com a mensagem WM_TIMER acionada pelo relógio, você pode chamar o método de aquisição de dados de uma porta de origem específica de acordo com o ID da porta e digitar: Procedure TCarrier.Timer(var Msg: TMsg; var Handled: Boolean);var stmp:string; Obj:TComponent;begin if Msg.message =WM_TIMER then//Início do processamento da mensagem do relógio//Encontre o objeto que define esta mensagem de acordo com o ID da porta que acionou a mensagem Obj:=FindComponent( 'Receive'+inttostr (Msg.WParam)); if obj=nil then exit;//Sai do processamento se não for encontrado stmp:=obj.ClassName;//Reflect para obter as informações de tipo deste objeto Port se stmp='TIdPOP3' então GetPOP3(TIdPOP3(Obj)); se stmp='TIdFTP' então GetFTP(TIdFTP(obj)); se stmp='TFilePort' então GetFile(TFilePort(Obj)); then GetCOM(TCOMPort(Obj));//Chama o processo de aquisição de dados do COMSource…………………… end;end; vi. Obter dados A seguir está o procedimento de processamento de aquisição de dados de COMSource TCarrier.GetCOM(COMObj: TCOMPort);var stmp:string;begin try//Crie um objeto de componente COM com base no valor de COMInterface:OleVariant;begin try//Crie um objeto de componente COM com base no valor de COMInterface:OleVariant; ComFace COMInterface:=CreateOleObject (COMObj.ComFace); stmp:=COMInterface.GetData //Chame o método de interface acordado para obter dados enquanto stmp<>#0; do // #0 é o sinalizador de final de extração de dados acordado start DataArrive(stmp, COMObj.Tag); // entregue ao Dispatcher de dados para processamento unificado, COMObj.Tag é o Target Port ID do Canal onde o objeto está localizado stmp:=COMInterface.GetData; end ; COMInterface:= Não atribuído, exceto COMInterface:= Não atribuído; Conclua a operação de extração de dados e libere o objeto componente até a próxima chamada do gatilho A seguir está o processamento de aquisição de dados do TCPSource: procedimento TCarrier.TCPServersClientRead(Sender: TObject; Socket:TCustomWinSocket);beginDataArrive(socket.ReceiveText,integer(TServerWinSocket(). sender).data ^));//Deixado para o data Dispatcher para processamento unificado, O segundo parâmetro é o valor do ponteiro do ID da porta de destino anexado ao remetente do objeto Socket, final; diferentes tipos de objetos da porta de origem recebem dados de maneiras diferentes, mas, em última análise, os dados recebidos são entregues ao processamento unificado de dados. Do nível de implementação, toda vez que um objeto receptor de dados é adicionado e sua recepção de dados é implementada, uma nova porta de origem é implementada para o Transceiver Shell. Nota: O autor aqui implementa apenas o recebimento de dados de texto. Os usuários podem precisar receber objetos de memória, fluxos de dados ou dados binários e apenas fazer pequenas alterações no código de recebimento. vii. Agendamento de Dados O agendamento de dados do Serviço Transceptor é completado pela unidade lógica do Despachante de Dados. A principal tarefa do Despachante de Dados é gerenciar e controlar uniformemente os dados recebidos de diferentes Portas de Origem e trabalhar em conjunto com o Controlador de Canal. . De acordo com a distribuição de dados de definição de canal para diferentes portas de destino, monitore se os resultados do envio foram bem-sucedidos e decida se os dados precisam ser enviados ao gerenciador de filas e ao gravador de log para armazenamento em buffer e processamento de log com base nos resultados de envio e nas configurações. da biblioteca de configuração do sistema. A seguir, observe o método DataArrive da porta de origem que envia dados: procedimento TCarrier.DataArrive(sData:String;PortID:Integer);var dTime:Datetime:integer;bSendSeccess:Boolean;begin if sData='' then exit;/ / Se os dados estiverem vazios, salte iLogID:=-1; dTime:= now //Receba o tempo if sData[length(sdata)]=#0 then; sdata:=copy(sdata,1,length(sdata)-1);//Formato de string para compatibilidade com linguagem C bSendSeccess:=DataSend(sdata,PortID);//Chama o Data Dispatcher para enviar o método de despacho, PortID é o ID da porta de destino se (TSCfg.LogOnlyError=false) ou (bSendSeccess=false) theniLogID:=writeLog(dTime, now,sData, PortID, bSendSeccess);//Grava logs de acordo com as regras de processamento de log e envia resultados nas informações de configuração do sistema if (TSCfg.Queueing=True) e (bSendSeccess=false) then PutQueue(dTime, now,sData, PortID, bSendSeccess, iLogID) ; // /Determine o final do processamento da fila com base na definição de configuração da fila nas informações de configuração do sistema do pacote acima são Dados; O método DataArrive do Dispatcher, no qual o processamento da fila é determinado de acordo com as informações de configuração do sistema e status de envio, também pode ser ajustado para processamento de fila obrigatório. A seguir está o método DataSend do Data Dispatcher, que é usado para distribuir e processar dados de acordo com o tipo de porta de destino: Function TCarrier.DataSend(sData:String;PortID:Integer):boolean;var Obj:TComponent;begin DataSend:=false ;Obj:=FindComponent ('Send'+inttostr(PortID)); //Encontre o objeto com base no ID da porta if (obj=nil) ou (obj.Tag =-1) then exit;//O objeto não existe ou foi marcado como inválido devido a falha de inicialização. Port case obj.Tag of 1:DataSend:=PutTCP(TClientSocket(obj),sdata); (obj),sdados); 5:DataSend:=PutFTP(TIdFTP(obj),sdados); 7:DataSend:=PutHTTP(TIdHTTP(obj),sdata); PutDB(TDBPort(obj),sdata); 15:DataSend:=PutCOM(TCOMPort (obj),sdata); …………… …………… end;end; tipo de porta Nesse caso, a melhor maneira de lidar com a distribuição de dados seria usar uma função de retorno de chamada, mas, neste caso, isso levaria a não saber qual membro da matriz de objetos deveria lidar com os dados. Além disso, o método de processamento atual não separa completamente o Transceptor Kernel e o Transceptor Shell, e devemos buscar um método de processamento mais abstrato e independente. viii. Envio de dados A seguir está a função de envio do TCP TCarrier.PutTCP(TCPOBJ:TClientSocket;sdata:string):Boolean;var itime:integer;begin PutTCP:=false; try TCPOBJ.Open; gettickcount; //Hora de início repete application.ProcessMessages; até (TCPOBJ.Active=true) ou (gettickcount-itime>5000); //Saia do loop se a conexão for bem-sucedida ou o tempo limite de 5 segundos ocorrer se TCPOBJ.Active então iniciar TCPOBJ.Socket.SendText(sdata); o valor de retorno é quando os dados são enviados com sucesso;TCPOBJ.Close; ExceptTCPOBJ.Close end;A seguir está a função de envio de COM; TCarrier.PutCOM(COMOBJ:TCOMPort;sdata:string):Boolean;var Com:OleVariant;begin PutCOM:=false; try Com:=CreateOleObject(COMOBJ.ComFace);//Cria uma interface predefinida PutCOM:=Com.PutData ( sdata);//Chama o método predefinido Com:= Unassigned; exceptCom:= Unassigned end; end; Outros tipos de envio de porta são semelhantes e não serão repetidos aqui. Até agora, o processamento básico de Origem e Destino foi concluído. Uma função de comunicação básica foi estabelecida Após a correspondência livre de diferentes tipos de Fonte e Destino, funções de comunicação completamente diferentes podem ser realizadas. Ao criar vários canais, você pode implementar centralmente o processamento de comunicação para diversas funções diferentes. ix. Processamento de fila: Após os dados serem enviados no método DataArrive acima, o Data Dispatcher chamará o writeLog para registro de dados e o método PutQueue para processamento de fila. parâmetros do sistema. O armazenamento não é o foco deste artigo. O processamento de nova tentativa da fila é semelhante ao princípio de distribuição do processamento por tipo de porta no evento Timer. Ele depende do gatilho do Temporizador de fila para ler os dados armazenados em buffer do banco de dados e chamar DataSend novamente de acordo com o ID da porta de destino. tente enviar novamente os dados, se a transmissão for bem-sucedida, a transação desta transmissão de dados será concluída, caso contrário, ele entrará novamente na fila e aguardará o próximo tempo de disparo para tentar novamente até que a transmissão seja bem-sucedida ou o número máximo definido de novas tentativas. é alcançado. três, Resumo da experiência de desenvolvimento Como o foco deste artigo é explicar as ideias centrais e conceitos de design do Transceptor, ele simplifica e enfraquece o processamento multi-threading, o pool de objetos e o suporte a transações que o Transceptor deve considerar como um serviço de segundo plano, e o mais complexo e poderosos grupos de origem e alvo e Cha. integração de nnel, a capacidade de enviar e receber objetos de memória, fluxos de dados, dados binários, a leitura de informações de configuração do sistema e a implementação de suas classes de encapsulamento, a segurança do sistema e dos dados, etc. entenda as idéias de design do Transceptor Inspire faíscas de inspiração no trabalho de desenvolvimento real e crie software mais excelente e poderoso. Autor: Firebird [email protected] Use Delphi para estabelecer servidores de comunicação e troca de dados – Análise técnica de transceptor (Parte 1) Use Delphi para estabelecer servidores de comunicação e troca de dados – Análise técnica de transceptor (Parte 2) Implemente classes de coleção através de C# Visão geral de . Coleções NET e coisas técnicas antigas relacionadas : atalhos de programas / exclusões de programas / exclusão automática de EXE Coisas antigas DIY: notas de experiência de algoritmo de programação infantil