Utiliser Delphi pour établir des serveurs de communication et d'échange de données - Analyse de la technologie Transceiver (Partie 2) Auteur : 火鸟 [email protected] 2. Explication détaillée du service Transceiver 1. Résumé de l'analyse du service Transceiver Le service Transceiver est le composant principal du système Transceiver. Il est chargé de lire les définitions et les paramètres de port et de canal définis par la console Transceiver à partir de la bibliothèque de configuration du système, créant et contrôlant dynamiquement les ports de communication et leurs relations pendant l'exécution. , et contrôle des données, planification de la réception, de l'envoi et de la mise en mémoire tampon, gestion des journaux et des files d'attente, etc. Transceiver Shell est l'implémentation de tous les types de ports pris en charge pour l'envoi et la réception de données. 2. Résumé de la conception du service Transceiver Le service Transceiver est développé à partir de l'application de service dans Delphi. L'application de service peut s'exécuter en mode système au lieu du mode utilisateur. Le système d'exploitation Service Control Manager (SCM) est responsable de la gestion des opérations du programme. et appartient au programme d'arrière-plan. Transceiver Kernel est une série de méthodes de la classe Transceiver qui établit et contrôle Transceiver Shell, et Transceiver Shell est un ensemble d'objets responsables de la communication. Remarque : En raison de considérations de performances et de charge, Transceiver Kernel n'implémente logiquement que la division fonctionnelle dans le diagramme d'architecture, et les modules constitutifs ne sont pas implémentés de manière entièrement basée sur les objets. 3. Résumé de l'implémentation du service Transceiver i. Créez une application de service. Sélectionnez NOUVEAU | Autre... dans le menu principal Delphi, Fichier... et sélectionnez NOUVEAU | Application de service dans la boîte de dialogue contextuelle Nouveaux éléments. Vous pouvez voir que le programme généré. Le framework est le suivant : PRogram Project1 ; utilise SvcMgr, Unit1 dans 'Unit1.pas' {Service1 : TService} ;{$R *.RES}begin Application.Initialize Application.CreateForm(TService1, Service1 ); Application.Run;end.unit Unit1;interfaceutilise Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;type TService1 = class(TService) private { Déclarations privées } public function GetServiceController : TServiceController ; end;var Service1 : TService1;implémentation{$R *.DFM}procédure ServiceController(CtrlCode: DWord); stdcall;begin Service1.Controller(CtrlCode);end;function TService1.GetServiceController: TServiceController;begin Résultat := ServiceController;end;end. Vous pouvez voir qu'en plus du SvcMgr utilisé pour la gestion des services référencé dans l'unité d'utilisation, TService1 hérite de TServiced au lieu de TForm, d'une fonction GetServiceController surchargée et du processus ServiceController appelé en mode stdcall, il est créé avec Delphi Il n'y a pas grand-chose de spécial dans un programme de service, Delphi Les fans voudront peut-être applaudir à nouveau, c'est le charme puissant de Delphi RAD. De plus, étant donné que l'application de service ne peut pas être déboguée directement au moment de l'exécution et ne dispose pas d'interface utilisateur, la sortie sans interface des informations de débogage doit être envisagée pendant le développement pour faciliter le débogage et le dépannage. ii. Pour créer une classe de ports qui répond à des besoins spécifiques et utiliser le noyau du transceiver avec un mécanisme d'exécution et de traitement unifié, les ports du Transceiver Shell doivent avoir des règles de traitement unifiées. Certains ports du Shell sont des classes de composants existantes dans Delphi. environnement de développement (tel que TCP, FTP, etc.), et certains ne le sont pas (comme MSMQ, File, etc.). À ce stade, vous devez créer une classe qui peut répondre à vos besoins. Par exemple : type//Comme il n'y a pas d'interface utilisateur, elle est héritée de TComponent au lieu de TControl TFilePort=class(TComponent) private FilePath:string;//Obtenir ou enregistrer l'emplacement du dossier du fichier Prefix:string;//File prefix suffix:string; //File suffix end; Après avoir établi la classe TFilePort, Transceiver Kernel peut utiliser une méthode de traitement de classe unifiée pour référencer et gérer des objets afin d'accéder à des fichiers spécifiques à partir du dossier spécifié par FilePath. S'ils sont utilisés comme source, les fichiers qui remplissent les conditions seront obtenus à partir d'un dossier spécifique. S'ils sont utilisés comme cible, les données obtenues à partir de la source correspondante seront écrites dans le fichier spécifié (en fait, les paramètres réels de chaque objet Port proviennent du fichier spécifié. définition de la table Port dans la bibliothèque de configuration du système). Autre exemple : tapez TCOMPort=class(TComponent) private ComFace:string;//interface COM pour obtenir ou soumettre des donnéesend; TCOMPort sera utilisé pour obtenir des données ou soumettre des données à l'interface du composant COM spécifiée. Dans Delphi, la classe OleVariant est l'un des moyens d'implémenter les appels de composants COM. La nécessité d'utiliser la classe TCOMPort est que Transceiver instanciera l'interface COM définie par TCOMPort dans un objet OleVariant uniquement lorsque l'accès aux données nécessaire est requis et que l'objet sera libéré après utilisation, cela peut réduire la pression de charge sur l'émetteur-récepteur et le serveur COM. Les mêmes considérations s'appliquent à d'autres composants similaires. L'exemple de classe fourni par l'auteur ici n'est qu'un modèle, et des méthodes et événements appropriés doivent être ajoutés si nécessaire. Les classes implémentées par l'auteur au cours du développement incluent : TCOMPort, TMSMQPort, TDBPort, TFilePort, etc.iii. Prise en charge de plusieurs canaux - un tableau d'objets déclarant des ports. Transceiver considère un processus de communication comme un processus de flux de données de la source à la cible. Un tel processus est un canal dans Transceiver, et ce canal est composé d'au moins deux. ports (un pour la source et un pour la cible), donc si vous souhaitez définir un nombre indéfini de canaux pouvant être librement combinés avec la source et la cible, vous devez les déclarer séparément pour la source et la cible. Des tableaux d'objets de différentes classes de ports (et établissez les relations correspondantes pour eux, comme vous le verrez plus tard). Par exemple : private { Déclarations privées }TCPSource : tableau de TServerSocket ;//Tableau d'objets pour la source TCP TCPTarget : tableau de TClientSocket ; ///Tableau d'objets pour la cible TCP MailSource : tableau de TIdPOP3 //Pour le tableau d'objets de source de courrier MailTarget : tableau de TIdSMTP ; //Tableau d'objets pour Mail Target fileSource : tableau de TFilePort ; //Tableau d'objets pour le fichier source fileTarget : tableau de TFilePort ; //Tableau d'objets pour le fichier cible comSource : tableau de TCOMPort ; //Tableau d'objets pour la source COM comTarget : tableau de TCOMPort ; Les règles de fonctionnement des ports pour la Source et la Cible du même type sont complètement différentes, elles sont considérées comme des objets complètement différents sans relation directe dans le concept Transceiver. Par conséquent, pour le même type de port, les tableaux d'objets sont également créés séparément en fonction de la source et de la cible. iv. Instanciez le tableau d'objets au moment de l'exécution. Le nombre d'éléments dans chaque tableau d'objets est géré par Port Builder au moment de l'exécution. Si l'utilisateur définit certains ports d'un certain type via la console Transceiver, le Port Builder les instanciera en fonction de leur nombre. et les paramètres respectifs. Le tableau d’objets. Sinon, le tableau d'objets ne sera pas instancié. Dans l'objet Port de type Source, la propriété Nom est définie sur la forme « Recevoir » + ID de port. Dans les déclencheurs de réception de données ultérieurs, cela aidera le répartiteur de données à localiser l'objet et à planifier uniformément différents types d'objets Port. L'attribut Tag est utilisé pour fournir au contrôleur de canal les informations d'identification cible du canal dans lequel il se trouve. Ce qui suit est la partie d'instanciation du tableau d'objets comSource dans Port Builder start //Créer COM/ Port de réception itmp:=high(comSource)+1;// Obtenez le nombre maximum actuel de comSource, itmp est la variable entière SetLength(comSource ,itmp +1); // Ajouter un membre du tableau comSource comSource [itmp]:=TCOMPort.Create(self); // Instancier le membre comSource[itmp].Name:= 'Receive'+inttostr(isource); //Définissez l'attribut Name sur 'Receive'+Port ID, et isource est le PortID actuel du type entier comSource [itmp].Tag:= itarget; //Définissez-le sur la cible; ID du canal où il se trouve NullTest :=rece.Fields['Address'].value;//Obtenir la valeur de la configuration du système COMFace, NullTest est une variable Variant si (NullTest <>null) et (trim(NullTest)<>'') puis commencecomSource [itmp].ComFace:=NullTest; //Attribuez des valeurs valides à ComFaceNullTest:=rece.Fields['interval'].value; //Obtenez l'intervalle de temps de déclenchement pour les objets COM pour obtenir des données dans la configuration du système SetTimer(application.handle,isource,NullTest*60000 ,nil); //Établit une horloge de déclenchement pour le port actuel pour collecter régulièrement des données, isource est le port IDendelsecomSource [itmp].Tag:=-1;//Échec de l'initialisation, marqué comme non valide. Fin du port ; comSource est le port de classe source utilisé pour appeler l'interface définie dans ComFace et obtenir des données après un certain intervalle de temps, correspondant à comTarget. L'implémentation est similaire, sauf que la soumission des données à ComFace de comTarget est un processus en temps réel, il n'est donc pas nécessaire d'utiliser l'intervalle de déclenchement et les deux instructions pour établir l'horloge peuvent être omises. La création et l'initialisation d'autres types d'objets Port sont similaires. Par exemple, un autre fragment d'implémentation de MailTarget : begin //Create SMTP/Send Port itmp:=high(MailTarget)+1; SetLength(MailTarget,itmp+1); itmp].Name:='send'+ inttostr(itarget); MailTarget[itmp].Tag:=3;//Définir comme identification du type de port cible NullTest:=rece.Fields['Address'].value; //Adresse du serveur de messagerie if (NullTest <>null) et (trim(NullTest) <>'') puis MailTarget[itmp].Host :=NullTest else bValid:=false; NullTest:=rece.Fields['Port'].value; //Port du serveur de messagerie si NullTest <>null then (if NullTest<>0 then MailTarget[itmp].Port :=NullTest)else bValid:=false; =rece.Fields['user'].value;//Nom d'utilisateur de connexion si NullTest <>null thenMailTarget[itmp].UserId :=NullTest else bValid:=false;=rece.Fields['password'].value;//Mot de passe de connexion………………………end;Peut-être aurez-vous quelque chose comme J'ai quelques doutes. Un grand nombre de composants de communication Transceiver Shell sont créés par Port Builder au moment de l'exécution. Les performances du service Transceiver seront-elles élevées ? En fait, la mission du Port Builder est terminée une fois que l'événement ServiceCreate se produit. Le nombre de ports Shell n'affectera que la vitesse d'initialisation du service Transceiver. La vitesse de communication du port Shell et les performances globales du service Transceiver. ne sera pas affecté. Bien sûr, les ressources du système Cela pourrait prendre un peu plus. v. Allocation dynamique et traitement des événements Parmi plusieurs ports de communication pris en charge par Transceiver Shell, utilisez TServerSocket (vous préférerez peut-être utiliser le composant de communication d'Indy, mais cela ne viole pas l'idée de conception de Transceiver Service, c'est juste une modification au niveau Au niveau du shell (ou juste ajouté), le TCPSource implémenté est plus distinctif, car TServerSocket, en tant que port source, est différent des objets tels que COM ou POP3 qui doivent être déclenchés régulièrement. Il se trouve dans Transceiver. Le service est toujours en état d'écoute après son démarrage. Il s'agit d'un composant qui génère des événements correspondants lorsqu'un ClientSocket se connecte et envoie des données. Ce qui suit est le fragment d'instanciation de TCPSource : start //Create TCP/Receive Port itmp:=high(TCPSource)+1;SetLength(TCPSource,itmp+1); [ itmp].OnClientRead:=TCPServersClientRead;//Attribuer le processus de traitement de l'événement OnClientRead à TCPServersClientRead TCPSource [itmp].OnClientError:=TCPServerClientError;//Attribuer le processus de traitement de l'événement OnClientError à TCPServerClientErrorTCPSource [itmp].Name:= 'Receive'+inttostr(isource); //Définissez la propriété Name sur 'Receive'+Port ID; TCPSource [itmp ].Tag:=itarget; //Définit l'IDTCPSource cible de son canal [itmp].Socket.Data:=@ TCPSource [itmp].Tag;//Attachez l'ID cible de cet objet Port à l'objet Socket en tant que données de pointeur…………………………end; Revenez et regardez le traitement de notre comSource. Il établit une horloge de déclenchement, mais comment gérer les événements lorsque l'horloge se déclenche ? De la même manière, il s'agit également de l'allocation dynamique du traitement événementiel. La définition du traitement de l'horloge de comSource peut être ajoutée au traitement de l'événement ServiceCreate : application.OnMessage:=Timer; pour implémenter la surcharge du traitement des messages Lorsqu'un message de l'Application est généré, le Timer sera déclenché dans l'événement Timer. filtrer le traitement Avec le message WM_TIMER déclenché par l'horloge, vous pouvez appeler la méthode d'acquisition de données d'un port source spécifique en fonction de l'ID du port et du type : Procedure TCarrier.Timer(var Msg: TMsg; var Handled: Boolean);var stmp:string; Obj:TComponent;begin if Msg.message =WM_TIMER then//Traitement du message d'horloge begin//Trouver l'objet qui définit ce message en fonction de l'ID de port qui a déclenché le messageObj:=FindComponent(' Recevoir'+inttostr (Msg.WParam)); si obj=nil alors quitter;//Quitter le traitement s'il n'est pas trouvé stmp:=obj.ClassName;//Réfléchir pour obtenir les informations de type de cet objet Port si stmp='TIdPOP3' alors GetPOP3(TIdPOP3(Obj)); si stmp='TIdFTP' alors GetFTP(TIdFTP(obj)); si stmp='TFilePort' alors GetFile(TFilePort(Obj)); puis GetCOM(TCOMPort(Obj));//Appelez le processus d'acquisition de données de COMSource…………………… end;end; vi. Obtenir des données Voici la procédure de traitement d'acquisition de données de COMSource TCarrier.GetCOM(COMObj: TCOMPort);var stmp:string COMInterface:OleVariant;begin try//Créer un objet composant COM basé sur la valeur de ComFace COMInterface:=CreateOleObject (COMObj.ComFace); stmp:=COMInterface.GetData; //Appelle la méthode d'interface convenue pour obtenir les données pendant que stmp<>#0 do // #0 est l'indicateur de fin d'extraction de données convenu. begin DataArrive(stmp, COMObj.Tag); // transmis au répartiteur de données pour un traitement unifié, COMObj.Tag est l'ID de port cible du canal où se trouve l'objet stmp:=COMInterface.GetData; end ; COMInterface:= Non attribué sauf COMInterface := Non attribué; Terminez l'opération d'extraction de données et libérez l'objet composant jusqu'au prochain appel de déclencheur. Voici le traitement d'acquisition de données de TCPSource : procédure TCarrier.TCPServersClientRead(Sender: TObject; Socket:TCustomWinSocket);beginDataArrive(socket.ReceiveText,integer(TServerWinSocket( sender).data ^));//Laissé au data Dispatcher pour un traitement unifié, Le deuxième paramètre est la valeur du pointeur d'ID de port cible attachée à l'expéditeur de l'objet Socket, fin ; différents types d'objets de port source reçoivent des données de différentes manières, mais en fin de compte, les données reçues sont transmises au répartiteur de données. Au niveau de l'implémentation, chaque fois qu'un objet de réception de données est ajouté et que sa réception de données est implémentée, un nouveau port source est implémenté pour Transceiver Shell. Remarque : L'auteur implémente ici uniquement la réception de données texte. Les utilisateurs peuvent avoir besoin de recevoir des objets mémoire, des flux de données ou des données binaires, et d'apporter simplement de légères modifications au code de réception. vii. Planification des données La planification des données du service émetteur-récepteur est complétée par l'unité logique du répartiteur de données. La tâche principale du répartiteur de données est de gérer et de contrôler uniformément les données reçues des différents ports sources et de travailler en collaboration avec le contrôleur de canal. Selon le canal, définissez la distribution des données sur différents ports cibles, surveillez si les résultats d'envoi réussissent et décidez si les données doivent être soumises au gestionnaire de files d'attente et à l'enregistreur de journaux pour la mise en mémoire tampon et le traitement des journaux en fonction des résultats d'envoi et des paramètres. de la bibliothèque de configuration du système. Ensuite, examinez la méthode DataArrive du port source soumettant des données : procédure TCarrier.DataArrive(sData:String;PortID:Integer);var dTime:Datetime; bSendSeccess:Boolean;begin if sData='' then exit;/ / Si les données sont vides, sautez iLogID:=-1; dTime:= now; //Recevoir l'heure if sData[length(sdata)]=#0 then; sdata:=copy(sdata,1,length(sdata)-1);//Format de chaîne pour la compatibilité avec le langage C bSendSeccess:=DataSend(sdata,PortID);//Appelez le répartiteur de données pour envoyer la méthode de répartition, PortID est l'ID du port cibleif (TSCfg.LogOnlyError=false) ou (bSendSeccess=false) theniLogID:=writeLog(dTime, now,sData, PortID, bSendSeccess);//Enregistrer les journaux selon les règles de traitement des journaux et envoyer les résultats dans les informations de configuration du système si (TSCfg.Queueing=True) et (bSendSeccess=false) puis PutQueue(dTime, now,sData, PortID, bSendSeccess, iLogID) ; / /Déterminer la fin du traitement de la file d'attente en fonction de la définition de la configuration de la file d'attente dans les informations de configuration du système du package. Ce qui précède est des données ; La méthode DataArrive de Dispatcher, dans laquelle le traitement de la file d'attente est déterminé en fonction des informations de configuration du système et de l'état d'envoi, peut également être ajustée au traitement de file d'attente obligatoire. Voici la méthode DataSend de Data Dispatcher, qui est utilisée pour distribuer et traiter les données en fonction du type de port cible : Function TCarrier.DataSend(sData:String;PortID:Integer):boolean;var Obj:TComponent;begin DataSend:= false;Obj:=FindComponent ('Send'+inttostr(PortID)); //Recherche l'objet en fonction de l'ID du port si (obj=nil) ou (obj.Tag =-1) puis quittez ;//L'objet n'existe pas ou a été marqué comme non valide en raison d'un échec d'initialisation du cas du port obj.Tag de 1:DataSend:=PutTCP(TClientSocket(obj),sdata); (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; Il convient de noter que si un tableau d'objets n'est pas utilisé, mais qu'il n'y a qu'une seule instance de chacun type de port Si tel est le cas, une meilleure façon de gérer la distribution des données serait d'utiliser une fonction de rappel, mais dans le cas actuel, cela conduirait à ne pas savoir quel membre du tableau d'objets doit gérer les données. De plus, la méthode de traitement actuelle ne sépare pas complètement le Transceiver Kernel et le Transceiver Shell, et nous devrions rechercher une méthode de traitement plus abstraite et indépendante. viii. Envoi de données Voici la fonction d'envoi de TCP TCarrier.PutTCP(TCPOBJ:TClientSocket;sdata:string):Boolean;var itime:integer;begin PutTCP:=false; try TCPOBJ.Open itime; gettickcount ; //Heure de démarrage, répéter application.ProcessMessages ; jusqu'à (TCPOBJ.Active=true) ou (gettickcount-itime>5000); //Sortez de la boucle si la connexion réussit ou si le délai d'attente de 5 secondes se produit si TCPOBJ.Active commence alors TCPOBJ.Socket.SendText(sdata); la valeur de retour est uniquement lorsque les données sont envoyées avec succès Trueend;TCPOBJ.Close; ExceptTCPOBJ.Close end;Voici la fonction d'envoi de COM. TCarrier.PutCOM(COMOBJ:TCOMPort;sdata:string):Boolean;var Com:OleVariant;begin PutCOM:=false; try Com:=CreateOleObject(COMOBJ.ComFace);//Créer une interface prédéfinie PutCOM:=Com.PutData ( sdata);//Appelle la méthode prédéfinie Com:= Unassigned; exceptCom:= Unassigned end; end; Les autres types d'envoi de port sont similaires et ne seront pas répétés ici. Jusqu'à présent, le traitement de base de la source et de la cible est terminé. Une fonction de communication de base a été établie. Après une correspondance libre de différents types de source et de cible, des fonctions de communication complètement différentes peuvent être réalisées. En créant plusieurs canaux, vous pouvez implémenter de manière centralisée le traitement des communications pour plusieurs fonctions différentes. ix. Traitement de la file d'attente : une fois les données envoyées dans la méthode DataArrive ci-dessus, Data Dispatcher appellera la méthode writeLog pour la journalisation des données et la méthode PutQueue pour le traitement de la file d'attente. Les fonctions des deux sont similaires. paramètres système. Le stockage n’est pas le sujet de cet article. Le traitement Retry de la file d'attente est similaire au principe de répartition du traitement par type de port dans l'événement Timer. Il s'appuie sur le déclencheur du Queue Timer pour lire les données mises en mémoire tampon de la base de données et appeler à nouveau DataSend en fonction de l'ID du port cible. réessayez d'envoyer les données, si la transmission réussit, la transaction de cette transmission de données est terminée, sinon elle entrera à nouveau dans la file d'attente et attendra le prochain moment de déclenchement pour réessayer jusqu'à ce que la transmission soit réussie ou que le nombre maximum de tentatives soit atteint. est atteint. trois, Résumé de l'expérience de développement Puisque l'objectif de cet article est d'expliquer les idées de base et les concepts de conception de Transceiver, il simplifie et affaiblit le traitement multithread, le pooling d'objets et la prise en charge des transactions que Transceiver devrait considérer comme un service d'arrière-plan, et le plus complexe et des groupes sources et cibles puissants. l'intégration interne, la capacité d'envoyer et de recevoir des objets mémoire, des flux de données, des données binaires, la lecture des informations de configuration du système et la mise en œuvre de ses classes d'encapsulation, la sécurité du système et des données, etc. J'espère que les lecteurs pourront fournir quelques informations et comprendre les idées de conception de Transceiver. Inspirez des étincelles d'inspiration dans le travail de développement réel et créez des logiciels plus remarquables et plus puissants. Auteur : Firebird [email protected] Utiliser Delphi pour établir des serveurs de communication et d'échange de données—Analyse technique de l'émetteur-récepteur (Partie 1) Utiliser Delphi pour établir des serveurs de communication et d'échange de données—Analyse technique de l'émetteur-récepteur (Partie 2) Implémenter des classes de collection via C#Présentation de. NET Collections et anciennes choses techniques associées : raccourcis de programme/suppressions de programmes/EXE auto-suppression de vieilles choses DIY : notes d'expérience sur l'algorithme de programmation de l'enfance