1

我的目标是让这个程序在用户注销或关闭他们的电脑时发送一个注销命令。

该程序使用 Winsock 对象通过 tcp 套接字连接到服务器应用程序。呼叫singleSock.SendData "quit" & vbCrLf只是一种退出的方式。我将开始使用 Wireshark 捕获数据,但我想知道我是否试图做一些根本错误的事情。

奇怪的是,如果我将 Cancel 设置为 True,并允许我运行的计时器执行注销命令,然后调用另一个卸载,它可以工作,但是在测试此配置(不同的代码)时,这会阻止用户注销第一个时间。他们必须启动注销,它什么也没做,然后他们再次注销,我的程序在那时就消失了。同样奇怪的是,在 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
4

2 回答 2

1

您无需等待 Winsock 控件实际发送“退出”消息。该SendData方法是异步的:它可以在数据实际通过网络发送之前返回。数据在您的机器上本地缓冲,稍后由网络驱动程序发送。

在您的情况下,您尝试发送“退出”消息,然后几乎立即关闭套接字。因为SendData是异步的,调用可能会在“退出”消息实际发送到服务器之前返回,因此代码可能会在有机会发送消息之前关闭套接字。

当您首先取消表单的卸载并让计时器发送“退出”消息时,它会起作用,因为您为套接字提供了足够的额外时间,以便在套接字关闭之前将消息发送到服务器。但是,我不会指望这总是有效的。巧合的是,额外的步骤给了套接字足够的时间来发送消息,并且不能保证总是这样。

SendCompleted您可以通过在发送“退出”消息后和关闭套接字之前等待套接字引发事件来解决此问题。下面是一个基本的例子。请注意,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,这让事情变得更整洁。

于 2009-04-25T03:35:11.567 回答
1

如果没有 QUIT 命令就不会更容易了。在您的服务器代码中,只需假设关闭套接字与接收退出执行相同的操作。

另外,您要注意的一件事是客户端软件的突然关闭。例如,断电或网络连接中断的机器或进入睡眠或休眠模式的机器。

在这些情况下,您应该定期检查来自服务器的所有客户端的连接,并关闭任何不响应某种 ping 命令的连接。

于 2009-04-27T15:15:51.880 回答