Problème de réception TCP

Problème de réception TCP - C#/.NET managed - Programmation

Marsh Posté le 02-04-2007 à 16:12:11    

Bonjour à tous   :) ,
 
Je développe actuellement un programme qui joue entre autre le rôle de serveur TCP et j'ai un gros problème lors de la réception des données envoyées par le client:
 
Dans ma boucle de réception, à un certain moment, la fonction 'Receive' de mon socket de réception, normalement bloquante, retourne directement en me renvoyant un nombre de bytes inférieur à celui demandé.  
Ce nombre est toujours soit 404 soit 0. Dans le cas où je reçois 0 byte, ma connexion est irrécupérable et la fonction Receive me retournera toujours 0.
Par contre, lorsque je reçois 404 bytes, je reçois parfois les suivants au Receive suivant...
 
J'ai l'impression que ce phénomène est du à un débordement du buffer de réception lorsque windows prend la main trop longtemps. Est-ce possible?
Pourquoi la fonction Receive retourne simplement sans balancer d'exception?
 
Le programme client étant programmé par quelqu'un d'autre, je n'y ai pas directement accès. Mais je suis quasi sûr qu'il continue à envoyer ses données.
 
J'espère que mes explications sont assez claires  :pt1cable:  
 
Un tout grand merci d'avance  ;) ,
 
BigBulle.

Reply

Marsh Posté le 02-04-2007 à 16:12:11   

Reply

Marsh Posté le 02-04-2007 à 21:08:06    

Hello
 

Dans ma boucle de réception, à un certain moment, la fonction 'Receive' de mon socket de réception, normalement bloquante, retourne directement en me renvoyant un nombre de bytes inférieur à celui demandé.  

Il faut que tu comptes le nbr de bytes recu à chaque receive et que tu boucles tant que tu n as pas recu le nombre d octets attendus.
 

Ce nombre est toujours soit 404 soit 0. Dans le cas où je reçois 0 byte, ma connexion est irrécupérable et la fonction Receive me retournera toujours 0.
Par contre, lorsque je reçois 404 bytes, je reçois parfois les suivants au Receive suivant...  
 

Aucune idée d'un tel bug (0 = echec), mais 404 ne serait pas le nbr de bytes que tu demandes de lire (parametres passé au receive) ??
 

J'ai l'impression que ce phénomène est du à un débordement du buffer de réception lorsque windows prend la main trop longtemps. Est-ce possible?
Pourquoi la fonction Receive retourne simplement sans balancer d'exception?

Chaque fois que j ai eu de tels poblemes et que j ai mis ca sur le compte des trames ou des longueurs de buffer, je me suis trompé et l'erreur venait de moi. Donc, peu de chance ...
 
Sinon, il y a aussi les network stream qui sont simples à utiliser . Tu peux essayer avec aussi.
 
En esperant t avoir un peu aider ...
 

Reply

Marsh Posté le 02-04-2007 à 22:23:00    

Merci beaucoup pour ta réponse  :D !
 
Le nombre de bytes que je désire lire est 528.
 

Citation :

Il faut que tu comptes le nbr de bytes recu à chaque receive et que tu boucles tant que tu n as pas recu le nombre d octets attendus.


C'est quand même bizarre pour une fonction bloquante... Normalement il doit attendre d'avoir tout les bytes demandés non...
Lorsque la fonction Receive() retourne 0 byte, là j'ai beau boucler et attendre, elle me retournera toujours 0 byte...
 

Citation :

Sinon, il y a aussi les network stream qui sont simples à utiliser . Tu peux essayer avec aussi.


C'est ce que j'avais utilisé au début. Mais la classe masquait le problème décrit ci-dessus.
 

Citation :

Chaque fois que j ai eu de tels poblemes et que j ai mis ca sur le compte des trames ou des longueurs de buffer, je me suis trompé et l'erreur venait de moi. Donc, peu de chance ...


Quelle genre d'erreur pourrait causer ce phénomène?
 
Encore merci pour ta réponse.
 
A+ ;)
 
BigBulle

Reply

Marsh Posté le 03-04-2007 à 17:24:02    

Je suis en train de finir une class hyper simplifié mais pratique pour la communication réseau, si tu es intéressé je pourrais te la refiler.
 
Au passage ça me permettra de me faire allumer par quelques gurus histoire que je puisse l'améliorer davantage... :sleep:

Reply

Marsh Posté le 04-04-2007 à 11:24:10    

Citation :

Je suis en train de finir une class hyper simplifié mais pratique pour la communication réseau, si tu es intéressé je pourrais te la refiler.


Cool tiens moi au courant :).
 
J'ai encore cherché sur le Net et j'ai pu lire que la fonction Receive() n'attend pas spécialement d'avoir tous les bytes demandés pour retourner...
Mais je n'ai pas encore compris qu'est ce qui fait que la fonction Receive() retourne...
Est-ce lors de la réception d'un segment de donnée TCP?  
Je ne pense pas...
 
Quant à la réception de 0 byte ceci est apparement simplement du au fait que le client s'est déconnecté.
 
A+ ;)

Reply

Marsh Posté le 04-04-2007 à 12:45:27    

Voilà tu trouveras ci-dessous en exclusivité ma ClassComm.
Je ne sais pas si tu es en C# ou en VB.net, en tout cas la class elle est en VB.net et tu sais comme moi que ça n'y change rien...
 
Fonctionnement :
 
} Connexion
- on définit grâce au constructeur si c'est un serveur qui écoute ou bien un client qui va se connecter à un serveur
- si instanciée en serveur, un évenement prévient lorsqu'une connexion arrive, et donne le socket client
- si instanciée en client (avec les infos de connexion en paramètre), un évenement prévient si c'est connecté ou non
 
} Parlote
# les messages sont des classcomm.commMessage (structure perso pour simplifier la comm réseau)
- pour envoyer un message, on utilise Send qui prend en paramètre un Ordre (string) et un Message (string)
- pour recevoir des messages, il suffit de tester de temps en temps MsgCount : si y'en a 0 c'est que y'a pas de commmessage à consommer, sinon c'est que y'en a le nombre indiqué en attente d'être lu, grâce à ReadNextMsg qui renvoie un commMessage
 
} L'event ProblemDetected
- renvoit ErrorType.connection_failed si un client n'a pas su se connecter à un serveur donné
- renvoit ErrorType.seems_disconnected si on est vraisemblablement déconnecté (erreur pendant une transmission par exemple)
- renvoit ErrorType.receive_an_empty_message si un buffer de 0 byte est reçu, ce qui n'est pas normal du tout (j'ai failli merger cette erreur avec la précédente en fait... car c'est tout comme !)
 
} Fermeture
- pour fermer un serveur ou une connexion ouvert, utilisez la méthode Dispose
 
Pourquoi avoir utilisé une structure perso (commMessage) pour l'envoi et la réception ?
L'utilisateur de cette classe simplifiée n'a pas à s'inquiéter de ce qu'il envoit et comment il l'envoit. Il envoit juste.
A la réception on reçoit donc un commMessage, on regarde l'ordre (par exemple avec un select case) et on interprete donc le message correctement, l'ordre nous renseignant sur ce qu'on doit faire du message (exemple bidon : est-ce que c'est l'âge du gars, son nom ou bien son prénom ?)
 
Pourquoi ne pas avoir mis en event un message arrivant ?
Parceque c'est le problème qui me reste à résoudre : tous les events (y'en a pas beaucoup quand même) sont renvoyé dans un thread à part à cause des callbacks des sockets, donc on peut pas agir sur une interface à partir des events de la class.
Alors j'ai jugé plus souple de mettre les messages arrivant dans une file d'attente FIFO (first in first out), qui sera par exemple checké par l'utilisateur à l'aide d'un Timer 100ms, pour de la comm réseau, c'est pas un souci.
 
C'est quoi idCode dans le commMessage ?
La classe est consciencieuse : quand elle envoit un message, avant de pouvoir en envoyer un nouveau, elle attend d'avoir la confirmation que le message précédent a bien été reçu. Chaque message a un idCode unique, et ce code sert d'accusé de réception. Quand la class reçoit un message, elle renvoie l'idCode à l'expéditeur pour dire OK, sinon le dit expéditeur renverra sans cesse le message au bout d'un laps de temps (timeout définit au constructeur) jusqu'à avoir la confirmation que c'est bien reçu.
Pas d'inquiétude, les messages n'arrivent pas en double, le mécanisme est bien géré, tout est vérifié.
Pas d'inquiétude non plus, les nouveaux messages demandés à être envoyé entre un envoi précédent et son propre accusé de réception sont mis en file d'attente pour être envoyé après, donc rien n'est perdu.
Au final on a une class bien aboutie qui gère bien ses comm réseau à la place de l'user.
Bref, ne pas prendre garde à l'idcode, c'est pour info seulement, au cas où on en aurait besoin pour autre chose... sinon pas touche !!
 
EXEMPLE CONCENTRÉ DANS UNE FORM

Citation :


Public Class Form1
 
  Private WithEvents Srv As New ClassComm(25000) 'ici on a déjà un serveur en écoute sur le port 25000
  Private WithEvents Clt1 As ClassComm
  Private WithEvents Clt2 As ClassComm
 
'en cliquant sur le Button1, on créé le client classComm CLT1 qui va tenter de se connecter au serveur (déjà ouvert juste au dessus)
  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Clt1 = New ClassComm("localhost", 25000)
  End Sub
 
'ici le serveur reçoit la demande de connexion et l'accepte en créant le client ClassComm CLT2
  Private Sub Srv_ConnectionRequestAccepted(ByRef sck As System.Net.Sockets.Socket) Handles Srv.ConnectionRequestAccepted
    Clt2 = New ClassComm(sck)
  End Sub
 
'ceci est l'évenement du client ClassComm CLT1 qui confirme que la connexion a bien eu lieu
'a ce stade on a donc une communication prête et ouverte entre CLT1 et CLT2, avec en plus un serveur qui écoute toujours
'normalement c'est fait pour du multiclient donc on devrait pas avoir juste CLT1 et CLT2 mais une collection de client
'et là à ce stade de cette petite appli, vu que ses 2 clients sont pris, on devrait fermer le serveur avec Srv.dispose, mais bref....

  Private Sub Clt1_Connected() Handles Clt1.Connected
    MsgBox("connecté" )
  End Sub
 
'en cliquand sur le Button2, on fait envoyer un commMessage (ordre + message) par CLT1
  Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    Clt1.Send(InputBox("ordre" ), InputBox("mon message" ))
  End Sub
 
'ici j'ai mis un bouton qui permet de dire si la file d'attente du CLT2 contient des messages
  Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
    MsgBox(Clt2.MsgCount)
  End Sub
 
'et ici un bouton qui lit les messages en attente dans CLT2
  Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
    Dim cm As CommMessage = Clt2.ReadNextMsg
    MsgBox(cm.Order & vbCrLf & cm.Message)
 
'ici il faudrait (exemple) :
'SELECT CASE cm.Order
'CASE "age" : msgbox ("le type a " & cm.Message & " ans" )
'CASE "nom" : msgbox ("le type s'appelle " & cm.Message)
'CASE "profession" : msgbox ("le type travaille dans " & cm.Message)
'END SELECT

 
  End Sub
 
 
'Les 2 subs précédentes devraient être dans un Timer avec un test sur MsgCount
 
'ici un exemple de l'event ProblemDetected chez CLT1
  Private Sub Clt1_ProblemDetected(ByVal source As ClassComm.ErrorType) Handles Clt1.ProblemDetected
    Select Case source
      Case ErrorType.connection_failed : MsgBox("échec connexion" )
      Case ErrorType.seems_disconnected : MsgBox("semble déconnecté" )
      Case ErrorType.receive_an_empty_message : MsgBox("msg recu vide" )
    End Select
    Clt1.Dispose()
    Clt1 = Nothing
  End Sub
 
End Class


 
 
Voici donc ma classComm :
 
 


Imports System.Net.Sockets
Imports System.Net
Imports System.Text
 
Public Class ClassComm
  Implements IDisposable
 
#Region "IDisposable Support"
  Private disposedValue As Boolean = False
 
  ' IDisposable
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    If Not Me.disposedValue Then
      If disposing Then
        If Not TimerConnectionTimeOut Is Nothing Then
          TimerConnectionTimeOut.Enabled = False
          TimerConnectionTimeOut.Close()
          TimerConnectionTimeOut.Dispose()
          TimerConnectionTimeOut = Nothing
        End If
        If Not TimerSendTimeOut Is Nothing Then
          TimerSendTimeOut.Enabled = False
          TimerSendTimeOut.Close()
          TimerSendTimeOut.Dispose()
          TimerSendTimeOut = Nothing
        End If
        If Not TimerSend Is Nothing Then
          TimerSend.Enabled = False
          TimerSend.Close()
          TimerSend.Dispose()
          TimerSend = Nothing
        End If
        _bytes = Nothing
        _qrcv.Clear()
        _qrcv = Nothing
        _qsnd.Clear()
        _qsnd = Nothing
        On Error Resume Next
        _sck.Shutdown(SocketShutdown.Both)
        _sck.Close()
        On Error GoTo 0
        _sck = Nothing
      End If
      'free shared unmanaged resources
    End If
    Me.disposedValue = True
  End Sub
 
  Public Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(Me)
  End Sub
#End Region
 
#Region "Enumeration"
  Public Enum ErrorType
    seems_disconnected
    connection_failed
    failed_to_listen
  End Enum
#End Region
 
#Region "Structure"
  Public Structure CommMessage
    Public Order As String
    Public Message As String
    Private _idCode As Long
    Public Property idCode() As Long
      Get
        If _idCode = 0 Then _idCode = Now.Ticks
        Return _idCode
      End Get
      Set(ByVal value As Long)
        If _idCode = 0 Then _idCode = value
      End Set
    End Property
    Public ReadOnly Property MyBytes() As Byte()
      Get
        If _idCode = 0 Then _idCode = Now.Ticks
        Dim ms As New IO.MemoryStream
        Dim bw As New IO.BinaryWriter(ms, Encoding.Unicode)
        With bw
          .Write(Order)
          .Write(Message)
          .Write(_idCode)
          .Close()
        End With
        ms.Close()
        Return ms.ToArray
        bw = Nothing
        ms.Dispose()
        ms = Nothing
      End Get
    End Property
    Public Sub SetBytes(ByRef ByteCommMessage As Byte())
      Dim ms As New IO.MemoryStream(ByteCommMessage, False)
      Dim br As New IO.BinaryReader(ms, Encoding.Unicode)
      With br
        Order = .ReadString
        Message = .ReadString
        _idCode = .ReadUInt64
        .Close()
      End With
      br = Nothing
      ms.Close()
      ms.Dispose()
      ms = Nothing
    End Sub
    Public Function ConfirmSequence() As Byte()
      Dim ms As New IO.MemoryStream
      Dim bw As New IO.BinaryWriter(ms, Encoding.Unicode)
      With bw
        .Write("" )
        .Write("" )
        .Write(_idCode)
        .Close()
      End With
      ms.Close()
      Return ms.ToArray
      bw = Nothing
      ms.Dispose()
      ms = Nothing
    End Function
  End Structure
#End Region
 
#Region "Private objects"
  Private _bytes() As Byte
  Private _qrcv As New Queue(Of CommMessage)
  Private _qsnd As New Queue(Of CommMessage)
  Private _last_send As CommMessage
  Private _last_rcv As CommMessage
  Private _sck As Socket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP)
  Private WithEvents TimerConnectionTimeOut As Timers.Timer
  Private WithEvents TimerSendTimeOut As Timers.Timer
  Private WithEvents TimerSend As New Timers.Timer(100)
  Private _sending As Boolean = False
#End Region
 
#Region "Events"
  Public Event ConnectionRequestAccepted(ByRef sck As Socket)
  Public Event Connected()
  Public Event ProblemDetected(ByVal source As ErrorType)
#End Region
 
#Region "Constructor New"
 
  Public Sub New(ByVal Port_To_Listen_If_Server As Integer)
    _last_rcv.idCode = -1
    Try
      With _sck
        .Bind(New IPEndPoint(IPAddress.Any, Port_To_Listen_If_Server))
        .Listen(10)
        .BeginAccept(AddressOf CallBackAccept, _sck)
      End With
    Catch
      RaiseEvent ProblemDetected(ErrorType.failed_to_listen)
    End Try
  End Sub
  Public Sub New(ByVal Host_To_Connect As String, ByVal Port_To_Connect As Integer, Optional ByVal Timeout_ms As Integer = 5000)
    _last_rcv.idCode = -1
    If Timeout_ms >= 0 Then
      TimerConnectionTimeOut = New Timers.Timer
      TimerConnectionTimeOut.Interval = Timeout_ms
      TimerConnectionTimeOut.Enabled = True
      TimerSendTimeOut = New Timers.Timer
      TimerSendTimeOut.Interval = Timeout_ms
      TimerSendTimeOut.Enabled = False
    End If
    Try
      _sck.BeginConnect(Host_To_Connect, Port_To_Connect, AddressOf CallBackConnect, _sck)
    Catch
      RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
    End Try
  End Sub
  Public Sub New(ByRef Sck As Socket, Optional ByVal Timeout_ms As Integer = 5000)
    _last_rcv.idCode = -1
    If Sck.Connected Then
      If Timeout_ms >= 0 Then
        TimerSendTimeOut = New Timers.Timer
        TimerSendTimeOut.Interval = Timeout_ms
        TimerSendTimeOut.Enabled = False
      End If
      Try
        _sck = Sck
        TimerSend.Enabled = True
        _bytes = New Byte(_sck.ReceiveBufferSize) {}
        _sck.BeginReceive(_bytes, 0, _sck.ReceiveBufferSize, SocketFlags.None, AddressOf CallBackReceive, _sck)
      Catch
        RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
      End Try
    Else
      RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
    End If
  End Sub
 
#End Region
 
#Region "Timers events"
  Private Sub TimerConnectionTimeOut_Elapsed(ByVal sender As Object, ByVal e As system.Timers.ElapsedEventArgs) Handles TimerConnectionTimeOut.Elapsed
    TimerConnectionTimeOut.Enabled = False
    RaiseEvent ProblemDetected(ErrorType.connection_failed)
  End Sub
 
  Private Sub TimerSendTimeOut_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles TimerSendTimeOut.Elapsed
    If _sending Then Sendcm(_last_send.MyBytes) Else TimerSendTimeOut.Enabled = False
  End Sub
 
  Private Sub TimerSend_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles TimerSend.Elapsed
    If Not _sending Then
      If _qsnd.Count > 0 Then
        Sending(True)
        _last_send = _qsnd.Dequeue
        Sendcm(_last_send.MyBytes)
      End If
    End If
  End Sub
 
#End Region
 
#Region "Public properties"
  Public ReadOnly Property ReadNextMsg() As CommMessage
    Get
      If _qrcv.Count > 0 Then Return _qrcv.Dequeue Else Return Nothing
    End Get
  End Property
 
  Public ReadOnly Property MsgCount() As Integer
    Get
      Return _qrcv.Count
    End Get
  End Property
#End Region
 
#Region "Public functions"
 
  Public Overloads Sub Send(ByVal Order As String, ByVal Message As String)
    Dim cm As New CommMessage
    cm.Order = Order
    cm.Message = Message
    _qsnd.Enqueue(cm)
  End Sub
  Public Overloads Sub Send(ByRef varCommMessage As CommMessage)
    _qsnd.Enqueue(varCommMessage)
  End Sub
  Public Overloads Sub Send(ByRef BytesCommMessage As Byte())
    Dim cm As New CommMessage
    cm.SetBytes(BytesCommMessage)
    _qsnd.Enqueue(cm)
  End Sub
 
#End Region
 
#Region "Private functions"
  Private Sub Sendcm(ByRef _b() As Byte)
    Try
      _sck.BeginSend(_b, 0, _b.Length, SocketFlags.None, AddressOf CallBackSend, _sck)
    Catch
      RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
    End Try
  End Sub
  Private Sub Sending(ByVal Flag As Boolean)
    _sending = Flag
    TimerSendTimeOut.Enabled = Flag
  End Sub
#End Region
 
#Region "Socket delegate CallBack"
 
  'en mode serveur, renvoit un socket connecté à un client
  'puis repasse en attente
  Private Sub CallBackAccept(ByVal async As IAsyncResult)
    Try
      RaiseEvent ConnectionRequestAccepted(_sck.EndAccept(async))
      _sck.BeginAccept(AddressOf CallBackAccept, _sck)
    Catch
      RaiseEvent ProblemDetected(ErrorType.failed_to_listen)
    End Try
  End Sub
 
  'en mode client, établit la connexion
  Private Sub CallBackConnect(ByVal async As IAsyncResult)
    If Not TimerConnectionTimeOut Is Nothing Then TimerConnectionTimeOut.Enabled = False
    Try
      _sck.EndConnect(async)
      RaiseEvent Connected()
      TimerSend.Enabled = True
      _bytes = New Byte(_sck.ReceiveBufferSize) {}
      _sck.BeginReceive(_bytes, 0, _sck.ReceiveBufferSize, SocketFlags.None, AddressOf CallBackReceive, _sck)
    Catch
      RaiseEvent ProblemDetected(ErrorType.connection_failed)
    End Try
  End Sub
 
  'réception de données, puis se remet en attente
  Private Sub CallBackReceive(ByVal async As IAsyncResult)
    Dim size As Integer
    Try
      size = _sck.EndReceive(async)
      If size > 0 Then
        Dim cm As New CommMessage
        cm.SetBytes(_bytes)
        If cm.Order = "" AndAlso cm.Message = "" Then
          If cm.idCode = _last_send.idCode Then Sending(False)
        Else
          If _last_rcv.idCode <> cm.idCode Then _qrcv.Enqueue(cm)
          _last_rcv = cm
          Sendcm(cm.ConfirmSequence)
        End If
      Else
        RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
      End If
      _bytes = New Byte(_sck.ReceiveBufferSize) {}
      _sck.BeginReceive(_bytes, 0, _sck.ReceiveBufferSize, SocketFlags.None, AddressOf CallBackReceive, _sck)
    Catch
      RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
    End Try
  End Sub
 
  'envoi des données terminées
  Private Sub CallBackSend(ByVal async As IAsyncResult)
    Try
      _sck.EndSend(async)
    Catch
      RaiseEvent ProblemDetected(ErrorType.seems_disconnected)
    End Try
  End Sub
 
#End Region
 
End Class


 
 
 
 
A vos critiques.
Merci de ne pas s'approprier odieusement mon travail !   :kaola:  
 
 :hello:  
Celiphane
 
14h58 : je viens de mettre à jour le code suite à une erreur que j'ai trouvé dans l'implémentation du dispose
15h54 : Nouvelle mise à jour : détection d'erreur ajoutée sur certains points et changement de certains code erreur ;)
 
NB : puisque je m'apprête à utiliser la class dans des applis pro, je vais ouvrir un topic séparé pour avoir des retours


Message édité par Celiphane le 04-04-2007 à 15:59:39
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed