VisualBasicでもソケット通信-非同期編

|

WindowsXPやWindows7とVisualBasic2008でガサゴソやっていると、ますます周回遅れが嵩んで追い付けなくなると言うか、浦島次郎にさえ成れない? という事で、非同期ソケット通信を試してみる事に。

凡そ10年前、Windows 8/RT 以降の Windows Store App では、時間のかかるAPIには非同期バージョンの物しか用意されていない。と言われていましたが、最新のWindowsでは如何なんでしょうか?

同期バージョンのAPIが多数復活(?)してたら、喜びも一塩なんですけれどもね。


<注意点>

・マイクロソフトが言うには、

非同期ソケットは、システムスレッドプールの複数のスレッドを使用して、データの送受信接続を処理します。1つ目はネットワーク接続を処理するスレッド、2つ目はデータの送信または受信の開始を処理するスレッド、他のスレッドはデータの送信または受信を処理するスレッドなどがあります。

らしいので、非同期処理のポイントは「コールバック・デリゲート」で、元々、マルチスレッド処理の事だったのかも?

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*---- サーバー側 -------------------- お試し結果 --------------------------*/

Waiting for a connection...
Waiting for a connection...
Read 48 bytes from socket.
Data : This is a test.
Asynchronous communications<EOF>
Sent 48 bytes to client.

/*----------------------------------------------------------------------------*/

/*---- クライアント側 ---------------- お試し結果 --------------------------*/

Socket connected to ::1:22000
Sent 15 bytes to server.
Sent 33 bytes to server.
Response received : This is a test.
Asynchronous communications<EOF>

/*----------------------------------------------------------------------------*/


/*---- サーバー側 ------------------  お試しソース -------------------------*/

'Imports System.Net
'Imports System.Net.Sockets
'Imports System.Text

Public Class Form1

    ' 非同期サーバー受信用状態オブジェクト
    Public Class srvStateObj
        ' データ送受信ソケット
        Public workSocket As Net.Sockets.Socket = Nothing
        ' バッファサイズ
        Public Const BufferSize As Integer = 1024
        ' 受信バッファ
        Public buffer(BufferSize) As Byte
        ' 受信データ文字列
        Public sb As New System.Text.StringBuilder
    End Class 'srvStateObj

    ' イベント待ちハンドラ シグナル設定
    Private acceptDone As New Threading.ManualResetEvent(False)

    Private Sub srvSendCallback(ByVal ar As IAsyncResult)

        Dim cpWkDatSkt As Net.Sockets.Socket ' データ送受信ソケットのコピー
        Dim sndLen As Integer

        ' データ送受信ソケットの取得
        cpWkDatSkt = CType(ar.AsyncState, Net.Sockets.Socket)

        ' データ送信終了
        sndLen = cpWkDatSkt.EndSend(ar)
        Debug.Print("Sent {0} bytes to client.", sndLen)

        cpWkDatSkt.Shutdown(Net.Sockets.SocketShutdown.Both)
        cpWkDatSkt.Close()
    End Sub ' srvSendCallback

    Private Sub srvSend(ByVal wkDatSkt As Net.Sockets.Socket, ByVal data As String)

        Dim byteData(1024) As Byte

        ' 文字列データをバイト列データに変換
        byteData = System.Text.Encoding.ASCII.GetBytes(data)

        ' データ送信開始
        wkDatSkt.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf srvSendCallback), wkDatSkt)
    End Sub ' srvSend

    Private Sub ReadCallback(ByVal ar As IAsyncResult)

        Dim state As srvStateObj
        Dim wkDatSkt As Net.Sockets.Socket ' データ送受信ソケットのコピー

        Dim rdLen As Integer
        Dim msg As String

        ' データ送受信ソケットの取得
        state = CType(ar.AsyncState, srvStateObj)
        wkDatSkt = state.workSocket

        ' データ受信終了
        rdLen = wkDatSkt.EndReceive(ar)

        If rdLen > 0 Then
            ' ストリングビルダーに受信データを格納
            state.sb.Append(System.Text.Encoding.ASCII.GetString(state.buffer, 0, rdLen))

            ' データ受信終了をチェック
            msg = state.sb.ToString()
            If msg.IndexOf("<EOF>") > -1 Then
                ' 全データ受信終了
                Debug.Print("Read {0} bytes from socket. " + vbLf + "Data : {1}", msg.Length, msg)
                ' クライアントへデータ送信(エコーバック)
                srvSend(wkDatSkt, msg)
            Else
                ' 未受信データを受信
                wkDatSkt.BeginReceive(state.buffer, 0, srvStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
            End If
        End If
    End Sub ' ReadCallback

    Private Sub AcceptCallback(ByVal ar As IAsyncResult)

        Dim cpSrvSkt As Net.Sockets.Socket ' サーバー側ソケットのコピー
        Dim datSkt As Net.Sockets.Socket ' クライアント側とのデータ送受信ソケット

        Dim state As srvStateObj

        ' サーバーソケットの取得
        cpSrvSkt = CType(ar.AsyncState, Net.Sockets.Socket)
        ' 接続要求許可受付終了
        datSkt = cpSrvSkt.EndAccept(ar)

        ' イベント完了をセット(メイン処理を続行するため)
        acceptDone.Set()

        ' 非同期受信用状態オブジェクト初期化とデータ送受信ソケット格納
        state = New srvStateObj
        state.workSocket = datSkt
        datSkt.BeginReceive(state.buffer, 0, srvStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
    End Sub ' AcceptCallback

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim srvSkt As Net.Sockets.Socket ' サーバー側ソケット
        Dim port As Integer

        Dim ipHostInfo As Net.IPHostEntry
        Dim ipAddress As Net.IPAddress ' ローカルIPアドレス
        Dim localEP As Net.IPEndPoint

        ' ローカルエンドポイント作成
        port = 22000
        ipHostInfo = Net.Dns.GetHostEntry(Net.Dns.GetHostName())
        ipAddress = ipHostInfo.AddressList(0)
        localEP = New Net.IPEndPoint(ipAddress, port)

        ' ソケット作成
        srvSkt = New Net.Sockets.Socket(ipAddress.AddressFamily, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

        Try
            ' ソケットとローカルエンドポイントの関連付けと、接続要求のリッスン
            srvSkt.Bind(localEP)
            srvSkt.Listen(100)

            While True
                ' イベント状態をリセット
                acceptDone.Reset()

                ' 非同期の接続要求受付開始
                Debug.Print("Waiting for a connection...")
                srvSkt.BeginAccept(New AsyncCallback(AddressOf AcceptCallback), srvSkt)

                ' 接続要求受付まで待ち(ループ継続前の処理がここに在っても良いかも?)
                acceptDone.WaitOne()
            End While

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*---- クライアント側 --------------  お試しソース -------------------------*/

'Imports System.Net
'Imports System.Net.Sockets
'Imports System.Text

Public Class Form1

    ' 非同期クライアント受信用状態オブジェクト
    Public Class clntStateObj
        ' クライアント側ソケット
        Public workSocket As Net.Sockets.Socket = Nothing
        ' バッファサイズ
        Public Const BufferSize As Integer = 256
        ' 受信バッファ
        Public buffer(BufferSize) As Byte
        ' 受信データ文字列
        Public sb As New System.Text.StringBuilder
    End Class 'clntStateObj

    ' イベント待ちハンドラ シグナル設定
    Private connectDone As New Threading.ManualResetEvent(False)
    Private sendDone As New Threading.ManualResetEvent(False)
    Private receiveDone As New Threading.ManualResetEvent(False)

    ' サーバーからの応答データ設定
    Private response As String = String.Empty

    Private Sub ReceiveCallback(ByVal ar As IAsyncResult)

        Dim state As clntStateObj
        Dim wkClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー

        Dim rcvLen As Integer

        Try
            ' クライアントソケットを取得
            state = CType(ar.AsyncState, clntStateObj)
            wkClntSkt = state.workSocket

            ' データ受信終了
            rcvLen = wkClntSkt.EndReceive(ar)

            If rcvLen > 0 Then
                ' ストリングビルダーに受信データを格納
                state.sb.Append(System.Text.Encoding.ASCII.GetString(state.buffer, 0, rcvLen))

                ' 残りのデータを取得
                wkClntSkt.BeginReceive(state.buffer, 0, clntStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
            Else
                ' 全データ受信
                If state.sb.Length > 1 Then
                    response = state.sb.ToString()
                End If
                ' 全データ受信完了イベントセット(メイン処理を続行するため)
                receiveDone.Set()
            End If

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' ReceiveCallback

    Private Sub Receive(ByVal clntSkt As Net.Sockets.Socket)

        Dim state As clntStateObj

        ' 非同期受信用のオブジェクトの初期化とクライアントソケット格納
        state = New clntStateObj
        state.workSocket = clntSkt

        ' データ受信開始
        clntSkt.BeginReceive(state.buffer, 0, clntStateObj.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
    End Sub ' Receive

    Private Sub clntSendCallback(ByVal ar As IAsyncResult)

        Dim cpClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー
        Dim sndLen As Integer

        Try
            ' クライアントソケットを取得
            cpClntSkt = CType(ar.AsyncState, Net.Sockets.Socket)

            ' 送信完了
            sndLen = cpClntSkt.EndSend(ar)
            Debug.Print("Sent {0} bytes to server.", sndLen)

            ' 送信完了イベントセット(メイン処理を続行するため)
            sendDone.Set()

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' clntSendCallback

    Private Sub clntSend(ByVal clntSkt As Net.Sockets.Socket, ByVal data As String)

        Dim byteData(1024) As Byte

        ' 文字列データをバイト列データに変換
        byteData = System.Text.Encoding.ASCII.GetBytes(data)

        ' データ送信開始
        clntSkt.BeginSend(byteData, 0, byteData.Length, 0, New AsyncCallback(AddressOf clntSendCallback), clntSkt)
    End Sub ' clntSend

    Private Sub ConnectCallback(ByVal ar As IAsyncResult)

        Dim cpClntSkt As Net.Sockets.Socket ' クライアントソケットのコピー

        Try
            ' クライアントソケットを取得
            cpClntSkt = CType(ar.AsyncState, Net.Sockets.Socket)

            ' 接続完了
            cpClntSkt.EndConnect(ar)
            Debug.Print("Socket connected to {0}", cpClntSkt.RemoteEndPoint.ToString())

            ' 接続完了イベントセット(メイン処理を続行するため)
            connectDone.Set()

        Catch ex As Exception
            Debug.Print(ex.ToString())
        End Try
    End Sub ' ConnectCallback

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim clntSkt As Net.Sockets.Socket ' クライアント側ソケット
        Dim port As Integer

        Dim ipHostInfo As Net.IPHostEntry
        Dim ipAddress As Net.IPAddress ' リモートIPアドレス
        Dim remoteEP As Net.IPEndPoint

        ' リモートエンドポイント作成
        port = 22000
        ipHostInfo = Net.Dns.GetHostEntry(Net.Dns.GetHostName())
        ipAddress = ipHostInfo.AddressList(0)
        remoteEP = New Net.IPEndPoint(ipAddress, port)

        ' ソケット作成
        clntSkt = New Net.Sockets.Socket(ipAddress.AddressFamily, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

        ' イベント状態をリセット
        connectDone.Reset()
        sendDone.Reset()
        receiveDone.Reset()

        ' 応答データをクリア
        response = String.Empty

        ' サーバーへ接続要求開始
        clntSkt.BeginConnect(remoteEP, New AsyncCallback(AddressOf ConnectCallback), clntSkt)
        ' 接続完了待ち
        connectDone.WaitOne()

        ' サーバーへデータ送信開始
        clntSend(clntSkt, "This is a test.")
        ' 送信完了待ち
        sendDone.WaitOne()

        ' 送信イベント状態をリセット
        sendDone.Reset() ' 05/29 訂正
        clntSend(clntSkt, vbLf + "Asynchronous communications<EOF>")
        sendDone.WaitOne()

        ' サーバーからの応答受信開始
        Receive(clntSkt)
        ' 受信完了待ち
        receiveDone.WaitOne()

        Debug.Print("Response received : {0}", response)

        ' ソケット解放
        clntSkt.Shutdown(Net.Sockets.SocketShutdown.Both)
        clntSkt.Close()
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

このブログ記事について

このページは、微禄が2022年4月11日 10:27に書いたブログ記事です。

ひとつ前のブログ記事は「VisualBasicで非同期によるソケット通信、ちょっとその前に」です。

次のブログ記事は「VisualBasicでもDelegate処理-使い方のお浚い」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

お気に入りリンク

NOP法人 アジアチャイルドサポート 最も大切なボランティアは、自分自身が一生懸命に生きること