Почему мой элемент управления winsock не работает правильно в vb6 в контексте Form_QueryUnload? - PullRequest
1 голос
/ 24 апреля 2009

Моя цель - заставить эту программу отправлять команду выхода из системы, когда пользователь выходит из системы или выключает свой компьютер.

Программа подключена к серверному приложению через TCP-сокет с использованием объекта Winsock. Вызов singleSock.SendData "quit" & vbCrLf - это просто способ выхода из системы. Я собираюсь начать сбор данных с помощью Wireshark, но я хотел бы знать, пытаюсь ли я что-то сделать в корне неправильно.

Как ни странно, если я установил Cancel на True и позволил таймеру, который я использую, выполнить команду logout, а затем вызвать другую выгрузку, это работает, однако при тестировании этой конфигурации (другой код) это не позволяет пользователю регистрироваться в первый раз. Они должны инициировать выход из системы, он ничего не делает, затем они снова выходят из системы, и в этот момент моя программа исчезла. Также, как ни странно, в Vista выход из системы происходит после краткого отображения экрана с сообщением о том, что моя программа препятствовала выходу из системы. Большая часть моего развертывания - на XP, которая имеет две проблемы с выходом из системы.

 Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

   If UnloadMode = vbFormControlMenu Then
      Me.WindowState = vbMinimized
      Cancel = True
   Else

      If SHUTDOWN_FLAG = True Then
        Cancel = False
      Else
        Cancel = True
        SHUTDOWN_FLAG = True
      End If
      tmrSocket.Enabled = False
      SHUTDOWN_FLAG = True
      Sleep (1000)
      singleSock.SendData "quit" & vbCrLf

      Call pUnSubClass
      'If singleSock.state <> sckConnected Then
      '   singleSock.Close
      '   tmrSocket.Enabled = False
      '   LogThis "tmrSocket turned off"
      'End If
      DoEvents
   End If

End Sub

Ответы [ 3 ]

1 голос
/ 27 апреля 2009

Не проще было бы просто без команды QUIT. В коде вашего сервера просто предположите, что закрытие сокета делает то же самое, что и получение выхода.

Кроме того, одна вещь, которую вы хотите остерегаться, - это внезапное завершение работы клиентского программного обеспечения. Например, машина, которая теряет питание или подключение к сети, или машина, которая переходит в спящий режим или режим гибернации.

В этих случаях вам следует периодически проверять соединение для всех клиентов с сервера и закрывать все соединения, которые не отвечают какой-либо команде ping.

1 голос
/ 25 апреля 2009

Вы не ожидаете, что элемент управления Winsock действительно отправит сообщение "quit". Метод SendData является асинхронным: он может вернуться до того, как данные действительно будут отправлены по сети. Данные буферизуются локально на вашем компьютере и через некоторое время отправляются сетевым драйвером.

В вашем случае вы пытаетесь отправить сообщение "выход", а затем почти сразу после этого закрываете сокет. Поскольку SendData является асинхронным, вызов может вернуться до того, как на сервер будет отправлено сообщение «quit», и, следовательно, код может закрыть сокет, прежде чем он сможет отправить сообщение.

Это работает, когда вы сначала отменяете выгрузку формы и позволяете таймеру отправить сообщение «выход», потому что вы даете сокету достаточно времени, чтобы отправить сообщение на сервер, прежде чем сокет закроется. Тем не менее, я бы не рассчитывал на это всегда работает; это совпадение, что дополнительные шаги дали сокету достаточно времени для отправки сообщения, и не всегда так получается.

Вы можете устранить проблему, подождав, пока сокет вызовет событие SendCompleted после отправки сообщения «quit» и до закрытия сокета. Ниже приведен основной пример. Обратите внимание, что код QueryUnload намного проще.

Private m_bSendCompleted As Boolean
Private m_bSocketError As Boolean

Private Sub singleSock_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
   'Set error flag so we know if a SendData call failed because of an error'
   'A more robust event handler could also store the error information so that'
   'it can be properly logged elsewhere'
   m_bSocketError = True
End Sub

Private Sub singleSock_SendCompleted()
   'Set send completed flag so we know when all our data has been sent to the server'
   m_bSendCompleted = True
End Sub

'Helper routine. Use this to send data to the server'
'when you need to make sure that the client sends all the data.'
'It will wait until all the data is sent, or until an error'
'occurs (timeout, connection reset, etc.).'
Private Sub SendMessageAndWait(ByVal sMessage As String)

   m_bSendCompleted = False
   singleSock.SendData sMessage

   singleSock.SendData sMessage

   Do Until m_bSendCompleted or m_bSocketError
      DoEvents
   Loop

   If m_bSocketError Then
      Err.Raise vbObjectError+1024,,"Socket error. Message may not have been sent."
   End If

End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

   'This is (almost) all the code needed to properly send the quit message'
   'and ensure that it is sent before the socket is closed. The only thing'
   'missing is some error-handling (because SendMessageAndWait could raise an error).'

   If UnloadMode = vbFormControlMenu Then
      Me.WindowState = vbMinimized
      Cancel = True
   Else
      SendMessageAndWait "quit" & vbCrLf
      singleSock.Close
   End If

End Sub

Вы можете сделать код чище, добавив логику для отправки сообщения и дождавшись его отправки в отдельный класс. Это хранит приватные переменные и обработчики событий в одном месте, вместо того, чтобы они засоряли ваш основной код. Это также облегчает повторное использование кода, когда у вас есть несколько сокетов. Я назвал класс SynchronousMessageSender из-за отсутствия лучшего имени. Этот пример также имеет более полную обработку ошибок:

SynchronousMessageSender.cls

Private WithEvents m_Socket As Winsock
Private m_bAttached As Boolean

Private m_bSendCompleted As Boolean
Private m_bSocketError As Boolean

Private Type SocketError
    Number As Integer
    Description As String
    Source As String
    HelpFile As String
    HelpContext As Long
End Type

Private m_LastSocketError As SocketError

'Call this method first to attach the SynchronousMessageSender to a socket'
Public Sub AttachSocket(ByVal socket As Winsock)

    If m_bAttached Then
        Err.Raise 5,,"A socket is already associated with this SynchronousMessageSender instance."
    End If

    If socket Is Nothing Then
        Err.Raise 5,,"Argument error. 'socket' cannot be Nothing."
    End If

    Set m_Socket = socket

End Sub

Private Sub socket_SendCompleted()
    m_bSendCompleted = True
End Sub

Private Sub socket_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)

    m_bSocketError = True

    'Store error information for later use'
    'Another option would be to create an Error event for this class'
    'and re-raise it here.'

    With m_lastSocketError
        .Number = Number
        .Description = Description
        .Source = Source
        .HelpFile = HelpFile
        .HelpContext = HelpContext
    End With

End Sub

'Sends the text in sMessage and does not return'
'until the data is sent or a socket error occurs.'
'If a socket error occurs, this routine will re-raise'
'the error back to the caller.'

Public Sub SendMessage(ByVal sMessage As String)

    If Not m_bAttached Then
        Err.Raise 5,,"No socket is associated with this SynchronousMessageSender. Call Attach method first."
    End If

    m_bSendCompleted = False
    m_bSocketError = False

    m_socket.SendData sMessage & vbCrLf

    'Wait until the message is sent or an error occurs'
    Do Until m_bSendCompleted Or m_bSocketError
        DoEvents
    Loop

    If m_bSocketError Then
        RaiseLastSocketError
    End If

End Sub

Private Sub RaiseLastSocketError()

    Err.Raise m_lastSocketError.Number, _
              m_lastSocketError.Source, _
              m_lastSocketError.Description, _
              m_lastSocketError.HelpFile, _
              m_lastSocketError.HelpContext

End Sub

Пример использования

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

    Dim sender As New SynchronousMessageSender

   'Ignore errors since the application is closing...'
   On Error Resume Next

   If UnloadMode = vbFormControlMenu Then
      Me.WindowState = vbMinimized
      Cancel = True
   Else

      Set sender = New SynchronousMessageSender
      sender.AttachSocket singleSock
      sender.SendMessage "quit"
      singleSock.Close

   End If

End Sub

Используя отдельный класс, теперь весь необходимый код можно поместить в Form_QueryUnload, что поддерживает порядок.

0 голосов
/ 15 мая 2009

Почему подпрограмма называется Private Sub singleSock_SendCompleted (), когда фактическое событие называется _SendComplete ()?

...