IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Socket et multithread


Sujet :

Langage Delphi

  1. #1
    Membre régulier

    Profil pro
    Inscrit en
    Août 2003
    Messages
    207
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 207
    Points : 91
    Points
    91
    Par défaut Socket et multithread
    Bonjour à tous,

    Je fais une appli en delphi2010 qui utilise des sockets IP.
    Je dois gérer plusieurs accès simultanés et j'utilise le composant TserverSocket.

    A chaque connexiotn, l'èvenement OnGetThread
    SocketThread := TMyServerThread.Create(False, ClientSocket);

    Qui lance la procédure myServerThread.ClientExecute qui envoie à son tour une confirmation de connexion :
    ClientSocket.SendText('Welcome ' + ClientSocket.RemoteAddress);

    Si je comprends bien le processus, à chaque connection, le programme créée un Thread.

    Jusque la tout va bien et ma question est la suivante :

    Lorsque j'ai plusieurs connexions simultanées, j'ai donc plusieurs thread qui tournent avec chacun une Socket.

    Comment je fais pour envoyer un message à une Socket particulière ?


    J'ai essayé de déclarer le tableau suivant :
    SocketList: Array[0..100] of TServerClientWinSocket;


    Et à chaque création de Thread, j'incrémente nSocket et j'affecte SocketList[nSocket] avec le ClientSocket qui a été créé par le Thread.

    Ce qui donne SocketList[nSocket] := ClientSocket;


    J'espérais ainsi pouvoir envoyer un message en appelant simplement la fonction

    SocketList[nSocket].SendText( 'welcome');

    Hélas ça ne fonctionne pas.

    Est ce que quelqu'un peut m'aider sur ce sujet ?

    Voici un extrait du code que j'utilise :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    Unit1 
    ...
    var
      Form1: TForm1;
    
      SocketList: Array[0..100] of TServerClientWinSocket;
      nSocket: Integer = 0;
    
    implementation
    
    
    
    procedure TForm1.MySocketServerGetThread(Sender: TObject;
      ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
    begin
    SocketThread := TmyServerThread.Create(False, ClientSocket);
    end;
    
    
    Procedure TmyServerThread.ClientExecute;
    Var
      Data: Array[0..1023] Of ansichar;
      RecText: String;
      SocketStream: TWinSocketStream;
    Begin
    Setlength(RecText, 9999);
    
    SocketList[nSocket] := ClientSocket;
    
    While Not Terminated and ClientSocket.Connected Do
    Try
       SocketStream := TWinSocketStream.Create(ClientSocket, 6000);
       Try
          FillChar(Data, SizeOf(Data), 0);
    
          If (SocketStream.Read(Data, SizeOf(Data)) = 0)
          Then Begin
               {If no  data after xx seconds then close the connection}
               ClientSocket.SendText('Still connected ? '+#13#10);
               {Wait a little time to allow sending of text before disconnect}
               sleep(3);
               ClientSocket.Close;
               Terminate;
               end
    
          else begin
               RecText := Data;
               If (Length(RecText) > 2)
                  Then Delete(RecText, Pos(#13#10, RecText), 2); // Delete #13#10
    
               If ClientSocket.Connected
               Then Begin
                       ClientSocket.SendText('Welcome ' +  ClientSocket.RemoteAddress);
                    end;
               end;
       Finally
       SocketStream.Free;
       Inc(nSocket);
       end;
       Except
       HandleException;
       end;
    end;

    Merci d'avance,
    Wilco

  2. #2
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 577
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 577
    Points : 25 225
    Points
    25 225
    Par défaut
    Perso, j'ai massivement utilisé le TServerSocket multi-client en mode stNonBlocking
    Je n'ai pas été convaincu du mode stThreadBlocking, j'ai noté des pertes cycles sur le TimeOut, je devais envoyer un KeepAlive tous les 5 secondes à chaque client pour conserver la connexion, le protocole imposé fonctionnait ainsi.
    En mettant 5000, cela loupait un cycle un bout d'une centaine de tour, j'avais pas envie de me taper un algo pseudo temps-réel pour retirer le temps consommer par le traitement lui-même
    D'ailleurs, le 5000 était plus souvent du 5002 à 5010 que 4999 !
    Le mode stNonBlocking étant beaucoup plus simple !


    Ensuite, dans le OnRead, j'ajoutais le buffer à une TThreadList (plus des informations comme le Handle)
    Un Thread (qui n'a aucun lien avec le TServerSocket) parcourait la TThreadList pour gérer parser le buffer avec gestion des paquets partiels ou multiples ...

    ensuite, juste en parcourant TServerSocket.Socket.ActiveConnections et Connections, via le Handle, je pouvais donc retrouver l'origine du message et donc lui envoyer un ACK ou NAK puis éventuellement une réponse

    C'était à l'époque de D5, créer autant de thread que de client était aberrant, dépassé 16 threads, les performances n'étaient pas terrible par rapport au mode stNonBlocking, surtout qu'il y avait d'autres thread pour d'autres taches
    Avec FastMM et stThreadBlocking, j'ai pu monter à 1800 threads dans un projet mais je n'ai pas retenu cette méthode, j'ai préféré resté avec le Gestionnaire Mémoire de Delphi 5 et me limiter à quelques thread :
    • le main
    • 7 Listen car 7 Port donc 7 TServerSocket
    • 1 Thread de Parsage + 1 TThreadList de Buffer
    • 1 Thread de Traitement DB + 1 TThreadList de Message Reçu (tout une gestion d'état)
    • 1 Thread pour l'émission + 1 TThreadList de Message en attente d'Envoi et le "Routage" via Handle + Connection
    • 1 Thread de monitoring + 1 TThreadList d'Action
    • 1 Thread pour la file FTP + 2 TThreadList d'Opération Get\Put
    • 1 Thread pour la file AS400 + 1 TThreadList d'Opération

    Le programme échangeait avec le Robot et d'autre Programmes près de 150 000 trames par heure, à l'époque c'était du Win2K et PIII 1Ghz,
    le partie la couteuse en temps était Interbase (90% du temps de traitements)

  3. #3
    Membre régulier

    Profil pro
    Inscrit en
    Août 2003
    Messages
    207
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 207
    Points : 91
    Points
    91
    Par défaut Socket & thread
    Merci ShaiLeTroll pour tes conseils

    Je vais essayer en mode stNonBlocking et m'inspirer de ta méthode.

    Aurais tu un bout de programme qui montre un peu comment tu as fait ?


    Wilco

  4. #4
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 577
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 577
    Points : 25 225
    Points
    25 225
    Par défaut
    Un bout de programme ?
    Rien d'exploitable pour une démonstration, c'est un vieux code procédural d'il y a 10 ans, à part l'héritage TThread, je ne faisais pas encore de la POO, c'est un peu archaïque

    voici, un extrait d'un des projets (il y en a eu plusieurs avec plus ou moins de Thread), comme tu pourras le lire, c'est très spécifique au protocole que j'ai du suivre gérant un STX et un ETX le tout contenant une structure de la forme Entête+Data+Contrôle informant sur la longueur de la trame, le nombre de message dans data, la taille de chaque message, un checksum...

    Dans la fenêtre principale (une fenêtre masquée contenant un Menu via Shell_NotifyIcon)
    Pour ce projet, c'est basique, une seule socket théorique par serveur, chacune son port, je ne conserve que le type de message, lors de l'envoi, le programme emet pour toutes les sockets du port le même message (à 99.99% une socket, et quelques fois deux sockets, quand la 1ere a été déconnectée, et qu'une deuxième l'a remplace !)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //----------------------------------------------------------------------------------------------------------------
    procedure TFrmServeur.ServerSocketCHXClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    var
       StrBuffer: String;
       StrPort : String;
    begin
         if Socket.LocalPort = 20000 then begin
            StrPort := 'CHM';
         end;
         if Socket.LocalPort = 21000 then begin
            StrPort := 'CHR';
         end;
     
         try
            StrBuffer := gBufferRestant + Socket.ReceiveText();
         except
               FichierLog('RTX_TO_OAM_'+StrPort+'_ERROR', StrPort, Exception(ExceptObject).Message );
         end;
     
         if gFlagLogBrut then begin
            FichierLog('RTX_TO_OAM_'+StrPort+'_BRUT', StrPort, StrBuffer);
         end;
     
         gBufferRestant := ScanBuffers(StrBuffer, StrPort); 
     
         if gFlagLogBrut then begin
            if Trim(gBufferRestant) <> '' then begin
               FichierLog('RTX_TO_OAM_'+StrPort+'_RESTANT', StrPort, gBufferRestant);
            end;
         end;
     
    end;
    une unité dédiée au protocole (mon code aurait pu être objet, il est découpé en thème qui aurait pu donner des classes)

    la fonction de découpage qui se charge de prendre le buffer, de chercher les marqueur de début ($FD) et de fin ($FE) pour extraire une trame (binaire) complète !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    //----------------------------------------------------------------------------------------------------------------
    //                         Décompose le Buffer Recu en plusieurs Messages                                        -
    // Paramètres :                                                                                                  -
    // - BuffersReceipt : Buffer Reçu depuis la socket sous la forme d'une Chaine                                    -
    // - TypeCanal   : Chaine contenant l'identifiant du Port                                                        -
    // Resultat :                                                                                                    -
    // - Chaine contenant la valeur restante non décomposée                                                          -
    //----------------------------------------------------------------------------------------------------------------
    function ScanBuffers(const BuffersReceipt, TypeCanal : String): String;
    var
         iSOM, iEOM, iEOL : Integer;
         TempSSE : _STRUCT_SEGMENT_ENVELOPE;
    begin
         Result := BuffersReceipt;
         repeat
            // si une seule position est à zéro, c'est que le buffer n'est pas complet
     
            // Recherche du SOM (FD 253) dans BuffersReceipt
            iSOM := Pos(Chr(START_OF_MESSAGE), Result);
     
            // La ligne de code suivante n'est pas valable à cause des þ imprévus
            // iEOM = Pos(Chr(END_OF_MESSAGE), Result);
     
            // Protection pour gérer les þ imprévus
            // Lecture dans le champ LENGHT du nombre d'octets des donnees
            if iSOM > 0 then begin
                // &BufferRestant[iSOM] car la position du SOM est la première incluse dans le BufferRestant
                CopyMemory(@TempSSE, @Result[iSOM], SizeOf(TempSSE));
                // SIZE_STRUCT_SEGMENT_TAIL - 1 car la position du EOM est la dernière incluse dans le SEGMENT_TAIL
                iEOM := iSOM + SIZE_STRUCT_SEGMENT_ENVELOPE + TempSSE.wLength + SIZE_STRUCT_SEGMENT_TAIL - 1;
                // Controle dans la location iEOM si il est ègal a EOM (FD 254)
                if Result[iEOM] <> Chr(END_OF_MESSAGE) then begin
                    iEOM := -1; // le message n'est pas correct car il n'y a pas de þ comme prévu !
                end;
            end else begin
                iEOM := -1;
            end;
     
            // On peut considerer valide le message contenu de l'octet iSOM à l'octet iEOM, 
            // Ce mécanisme gère les éventuel FD ou FE compris en tant que donnees et non pas en SOM ou EOM
     
            iEOL := Length(Result);
     
            // si le message a des bornes correctes, on décompose celui-ci
            if (iSOM >= 1 ) and (iEOM >= iSOM) and (iEOL >= iEOM) then begin
                // iEOM - iSOM + 1 car SOM et EOM sont inclus dans le Message
                AddMessageToTreat(Copy(Result, iSOM, iEOM - iSOM + 1), TypeCanal);
            end;
     
            // Récupération de la fin du buffer pour obtenir les messages suivants
            if iEOL > 0 then begin
               // iEOL + 1 car EOM N'est PAS inclus dans le Message suivant
               Result := Copy(Result, iEOM + 1, iEOL - iEOM);
            end else begin
                Result := '';
                Break;
            end;
     
         // Fin de la boucle si il n'y a plus délimiteur de message
         until (iSOM = 0 ) or (iEOM = 0 )
    end;
    la fonction qui ajoute à la file d'attente !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //----------------------------------------------------------------------------------------------------------------
    //          Décompose le Buffer contenant le Message, puis ajoute la structure à la FIFO de traitement           -
    // Paramètres :                                                                                                  -
    // - MessageBuffer : Buffer contenant le Message sous la forme d'une Chaine                                      -
    // - TypeCanal   : Chaine contenant l'identifiant du Port                                                        -
    // Resultat :                                                                                                    -
    // - Flag indiquant le bon déroulement de la procédure                                                           -
    //----------------------------------------------------------------------------------------------------------------
    function AddMessageToTreat(const MessageBuffer, TypeCanal : String): boolean;
    var
       CurrentMessage : ^_STRUCT_MESSAGE;
     
    begin
         // Création d'un nouveau pointeur CurrentMessage sur un _STRUCT_MESSAGE
         New(CurrentMessage);
         // Remarque : Il sera nécessaire de faire un Dispose sur ce pointeur
     
         // Décomposition du MessageBuffer dans CurrentMessage
         Result := UnPackMessages(MessageBuffer, CurrentMessage^);
         if Result then begin
            // C'est une TThreadList donc la gestion de Section Critique est incluse dans le Add()
            TreatMessageThreadLst.Add(CurrentMessage);
         end else begin
             Dispose(CurrentMessage);
         end;
    end;
    Puis UnPackMessages génère un record issu du Buffer, je te laisse imaginer que la suite encore plus longue, ça ce n'est que la réception des Messages, ensuite, il y a le traitement, l'envoi ...


    Dans un autre projet, il pouvait y avoir plusieurs clients, au lieu de mémoriser StrPort, je mémorisais Socket.SocketHandle pour répondre uniquement à la source du message, voici la fonction me permettant de le retrouver :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //---------------------------------------------------------------------------------------------------------------------
    function IndexHandle (var ServerSocket : TServerSocket; Handle : Integer) : Integer ;
    var
        I : integer;
    begin
        // Verification de la connexion
        Result := -1 ;
        for I := ServerSocket.Socket.ActiveConnections - 1 downto 0 do begin
            if Integer(Handle) = ServerSocket.Socket.Connections[i].SocketHandle then begin
                Result := I ;
                Break ;
            end;
        end;
    end ;
    Comme tu le constate, ce code n'a rien d'intéressant, il fait parti d'un ensemble assez vaste, difficile d'en extraire une démo !

Discussions similaires

  1. Socket ftp multithreads
    Par cl56670 dans le forum Entrée/Sortie
    Réponses: 1
    Dernier message: 21/02/2011, 09h54
  2. [Req]Tuto Thread/Multithread et Tuto Sockets
    Par Booster2ooo dans le forum Delphi
    Réponses: 8
    Dernier message: 03/04/2007, 11h27
  3. Socket multithread & select
    Par crealinks dans le forum Réseau
    Réponses: 3
    Dernier message: 22/11/2006, 21h36
  4. UDP/TCP multithreading/sockets asynchrones
    Par narkotik dans le forum C++
    Réponses: 4
    Dernier message: 25/07/2006, 11h35
  5. bloquage socket multithread
    Par aderick dans le forum Développement
    Réponses: 1
    Dernier message: 02/12/2004, 10h10

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo