Problème de dépassement de pile

Fermé
cedrys Messages postés 1 Date d'inscription lundi 3 août 2009 Statut Membre Dernière intervention 3 août 2009 - 3 août 2009 à 23:55
 Utilisateur anonyme - 6 août 2009 à 00:08
Bonjour,

Je cherche désespérément une solution à mon problème depuis des mois et je serai TRES reconnaissant à celui qui pourra me dépanner.

J'ai créé un serveur web lite en vb6 et je me trouve confronté à une erreur de dépassement de pile 'erreur automatation : l'objet s'est déconnecté de ses clients' au bout de quelques secondes lors de la lecture de gros fichiers via un contrôle utilisateur qui me sert de file reader.

Vu le type de l'appli (tout passe par des événements), il m'est impossible de passer par une boucle.

Seul l'appel d'un timer reglé à 1ms à la place de l'appel de la fonction règle le problème mais ralenti considérable la lecture.

Voici la simplification du code qui créé l'erreur dans mon serveur :
'Form1.frm
Private i As Integer

Private Sub Command1_Click()
    lireblockfichier
End Sub

Private Sub lireblockfichier()
    i = i + 1
    lecteurfichier.liredonnee
End Sub

Private Sub lecteurfichier_donneelu()
    If i < 10000 Then lireblockfichier
End Sub


'lecteurfichier.ctl
Public Event donneelu()

Public Sub liredonnee()
    RaiseEvent donneelu
End Sub

Comment faire pour les subs ne s'imbriquent pas et ne finissent pas par causer un débordement? Comment les décharger en même temps que l'appel de la prochaine fonction sans passer par un timer à 1ms?

7 réponses

cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
4 août 2009 à 00:14
Salut
Sur quoi agit le Timer dont tu parles ?
Remplace t-il l'action du Command1_Click ?

Un Timer, même réglé à 1 mSec, ne tournera jamais à 1 mSec (10 à 15 au grand maximum), mais le problème n'est pas là.
La fonction est enclenchée :
- par le Timer
- par la fin de la séquence de lecture elle-même qui donc, se mort la queue.
Cela fait beaucoup et le dépassement est logique.

Puisque ton Timer tabasse la commande lireblockfichier, il ne faut surtout pas qu'elle se relance elle même. Supprime le test If i < 10000 Then lireblockfichier

Ce que je ferai :
[*] Un Timer, pourquoi pas
[*] Dès que tu commences lireblockfichier, tu le rends Enabled = False : il ne se déclenchera plus
[*] Dès que tu reçois la confirmation de lecture, évènement donneelu, tu le redémarres en repassant Enabled à True
Le Timer relancera la lecture et il faudra qu'elle soit terminée pour qu'un autre soit demandée.

Libre à toi de détecter quand le travail est terminé et qu'il faut stopper le Timer définitivement.

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)
0
Utilisateur anonyme
4 août 2009 à 02:10
Déjà merci pour ta réponse très rapide

Pour le moment justement, je n'utilise pas encore de timer. Le timer est la seule solution que j'ai trouvé pour éviter le massacre (le dépassement de pile) au cas ou dans l'avenir je ne trouve rien de mieux.

J'ai écrit un code (sale) qui schématise exactement mon serveur sur une seule form. Je me suis planté forcément car comme tu le dis : le système se mord la queue, tout s'imbrique et à un moment ça déborde, mais ou...

Voila ce code :

Private Const MAX_READ As Long = 65536
Private bytData() As Byte
Private FileReadSize As Long
Private strRequest As String
Private strFilePath As String
Private lngFileLen As Long
Private intFilePointer As Integer
Private strBlockData As String

Private Sub Form_Load()

    usrSocket(0).LocalPort = 80
    usrSocket(0).Listen

End Sub

Private Sub usrSocket_ConnectionRequest(Index As Integer, ByVal requestID As Long)

    Load usrSocket(1)
    usrSocket(1).CloseSck
    usrSocket(1).Accept requestID

End Sub

Private Sub usrSocket_SendComplete(Index As Integer)

    If FileReadSize < lngFileLen Then
        
        FileRead
        
    Else
        
        Unload usrSocket(1)
            
    End If

End Sub

Private Sub usrSocket_DataArrival(Index As Integer, ByVal bytesTotal As Long)
                     
    strFilePath = "d:\bigfile.avi"
    lngFileLen = FileLen(strFilePath)
    FileReadSize = 0
    strRequest = "HTTP/1.1 200 OK" & vbCrLf & "Server: ServLite" & vbCrLf & "Last-Modified: Tue, 21 Mar 2006 14:24:28 GMT" & vbCrLf & "Connection: Close" & vbCrLf & "Content-Length: " & lngFileLen & vbCrLf & vbCrLf
    intFilePointer = FreeFile
    Open strFilePath For Binary As #intFilePointer
    FileRead

End Sub

Private Sub FileRead()

    If MAX_READ > lngFileLen Then
    
        strBlockData = Space(lngFileLen)
    
    ElseIf FileReadSize + MAX_READ > lngFileLen Then
    
        strBlockData = Space(lngFileLen - FileReadSize)
       
    Else
    
        strBlockData = Space(MAX_READ)
        
    End If

    Get #intFilePointer, FileReadSize + 1, strBlockData
    
    If FileReadSize = 0 Then
    
        bytData = StrConv(strRequest + strBlockData, vbFromUnicode)
    
    Else
    
        bytData = StrConv(strBlockData, vbFromUnicode)
        
    End If
    
    FileReadSize = FileReadSize + Len(strBlockData)
    usrSocket(1).SendData bytData()
    
End Sub
0
Utilisateur anonyme
4 août 2009 à 02:21
PS: Dans l'exemple ci-dessus, en remplaçant l'appel direct de FileRead() dans usrSocket_SendComplete() par un timer 1ms qui appel FileRead() ça passe car les subs se "déchargent" en allant, même en laissant tourner une heure un transfert de 10go (mais par contre c'est très long et très sale ).
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
4 août 2009 à 10:39
Ah mais c'est du WinSock !
Pas la peine de provoquer la lecture : Un WinSock déclenche lui même l'évènement DataArrival lorsque des données arrivent dans le buffer.
Mais tu t'y prends mal : Quand DataArrival se déclenche, cela signifie qu'il y a des données à lire, mais pas forcément toutes les données, elles peuvent arriver par paquets successifs.
Donc, créer le fichier dans DataArrival est une mauvaise idée.
Il faut buffuriser les données arrivant et les ajouter les unes aux autres.
En parlant, je regarde DataArrival : Tu n'as pas utilisé de GetData : Comment lis-tu les données ?

Bon, admettons que le seul déclenchement de DataArrival désigne la demande d'envoi du fichier.
C'est donc l'envoi séquenciel du fichier qui te pose problème (parce que j'usqu'à présent, tu ne parles que de lecture).
Dans FileRead, quand tu fais ton Get#, pourquoi ne pas lire directement un tableau de bytes ?
ReDim bytData(LaLongueurChosisie)
Get #intFilePointer, FileReadSize + 1, bytData  ' ou bytData(0), à vérifier


Pour le reste, c'est bon : Quand le Winsock dit "SendComplete", tu lances la lecture des bytes suivants et tu les envoies, c'est correct. Les envois se succederont, temporisés par les évènements réels.

Concernant l'envoi de strRequest au démarrage de l'échange, je ne suis pas persuadé que les échanges HTML fonctionnent comme ça, mais c'est un autre sujet.

Bref, dans ce programme, ça devrait fonctionner.
Tu envoies des paquets de 65ko pour ne pas saturer le buffer (sauf le premier envoi puisque tu y rajoutes l'entête, mais bon, ce n'est pas lourd)
Ensuite, que ce soit long : il faut se mettre en tête que le débit du flux sortant d'une connexion ADSL 2+ (dite "20Mega") n'est que de 100ko/sec maxi, alors 10Go, ça va passer (à la louche) en 100.000 secondes ~28 heures

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Utilisateur anonyme
4 août 2009 à 15:37
Merci d'avoir pris le temps d'étudier le problème ;)

Le code que j'ai écrit plus haut n'est là que pour illustrer mes propos. Il est sale (requête directement créée dans data_arrival, utilisation de GET et non d'API de lecture...) mais schématise bien le problème :

1) Un appel de datarrival une seule fois provoqué par la requête du client.
2) Traitement de la requête et début du processus de lecture dans le fichier bloc par bloc.
3) ReadFileBlock et socket_sendcomplete s'appelant récursivement, forcément comme ça s'imbrique, au bout d'un moment c'est la cata.

En fait le problème ressemble à ça :

Private Sub sub1()
    sub2
End Sub

Private Sub sub2()
    sub1
End Sub


PS: Ce n'est pas exactement du winsock mais du csocketmaster, un clone parfait de winsocket (qui comprend un module + une classe + un usercontrol). Ca ne change donc rien au problème.

PS2: Le code réel, je n'en ai pas parlé jusqu'à maintenant car ça aurait complexifié encore plus la chose : il s'agit de Http explorer disponible ici :

http://www.vbfrance.com/codes/HTTP-EXPLORER-SERVEUR-WEB-DEDIE-PARTAGE-MEDIAS_38848.aspx
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
4 août 2009 à 16:32
Dans ton dernier exemple (Sub1 appelle Sub2 qui rappelle Sub1, etc)
Il faut absolument bannir cette arborescence, il y a fort risque de dépassement.

Il faut utiliser les Sub liées aux évènements :
Dans ton cas, tu fais un SendData dans FileRead.
Une fois que le socket aura expédié les données, il déclenchera le SendComplete.
Dans ce SendComplete, tu peux sans problème relancer FileRead pour de nouvelles données, puisque le Socket dit qu'il a terminé son job, donc pas de risque d'empilement.

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)
0
Utilisateur anonyme
6 août 2009 à 00:08
Pour le dernier exemple sans condition de sortie c'est sur ça crash (sauf avec un timer à la place d'une des subs).

Théoriquement oui, send_complete appel de nouveau FileRead et se décharge mais en pratique dans mon exemple j'ai une belle erreur de dépassement...

Le code ci-dessus en projet : http://kroman.ath.cx/Autres/Applications/serveur%20lite.rar
0
Rejoignez-nous