1

私の目標は、ユーザーがログオフするか PC をシャットダウンするときに、このプログラムにログアウト コマンドを送信させることです。

プログラムは、Winsock オブジェクトを使用して tcp ソケット経由でサーバー アプリケーションに接続されます。通話singleSock.SendData "quit" & vbCrLfは単にログアウトする方法です。Wireshark でデータのキャプチャを開始しようとしていますが、何か根本的な間違いをしようとしているのか知りたいです。

奇妙なことに、Cancel を True に設定し、実行中のタイマーがログアウト コマンドを実行できるようにしてから、別のアンロードを呼び出すと動作しますが、この構成 (別のコード) をテストすると、ユーザーが最初にログアウトできなくなります。時間。彼らはログアウトを開始する必要がありますが、それは何もしません。その後、彼らは再びログアウトし、その時点で私のプログラムはなくなりました。また、奇妙なことに、Vista では、プログラムがログアウトを妨げているという画面が短時間表示された後、ログアウトが行われます。私の展開のほとんどはXPで行われており、2回のログアウトの問題があります。

 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、"quit" メッセージが実際にサーバーに送信される前に呼び出しが返される可能性があり、そのため、メッセージを送信する前にコードがソケットを閉じる可能性があります。

最初にフォームのアンロードをキャンセルし、タイマーに「終了」メッセージを送信させると機能します。これは、ソケットが閉じられる前にメッセージをサーバーに送信するのに十分な時間をソケットに与えているためです。ただし、これが常に機能するとは考えていません。余分な手順によってソケットがメッセージを送信するのに十分な時間を与えたのは偶然であり、常にそのように機能するとは限りません。

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

メッセージを送信し、別のクラスで送信されるのを待つロジックを配置することで、コードをよりクリーンにすることができます。これにより、プライベート変数とイベント ハンドラーがメイン コードにばらばらになるのではなく、1 か所に保持されます。また、複数のソケットがある場合にコードを再利用しやすくなります。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 に答える