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
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/*============================================================================*/