非同期でTCP 

9月 7th, 2008
前回は軽くDnsのメソッドを非同期化してみたので、今回はソケットのブロッキングが発生するメソッドの非同期版を実装してみました。前回は.NET風味な非同期の実装をしたのですが、今回は自分勝手にちょっとやってみました。ちなみに、前回IAsyncResultが理解できないとかなんとか言ってましたが、どうやら.NETにはBeginInvoke,EndInvokeとかいう大変便利そうなメソッドがあるらしく、これを使うと任意のメソッドが非同期で呼び出せるらしいです。これを使って他のBegin…,End…の非同期メソッドが実装されていると予測すると、IAsyncResultが出てくるのは当然だというわけです。

非同期なので取得中ウインドウを動かせます。当然のことですが。
コードはこちら。前回同様、最新のリポジトリ+展開したNet.zipをablib/src/Classesに放り込むと実行することが出来ます。
#require <Classes/ActiveBasic/Windows/UI/Form.ab> #require <Classes/ActiveBasic/Windows/UI/Application.ab> #require <Classes/ActiveBasic/Windows/UI/Button.ab> #require <Classes/ActiveBasic/Windows/UI/EditBox.ab> #require <Classes/ActiveBasic/Windows/UI/TaskMsg.ab> #require <Classes/ActiveBasic/Windows/UI/ListBox.ab> #require <api_ws2tcpip.sbp> #require <Classes/System/Net/misc.ab> #require <Classes/System/Net/Dns.ab> #require <Classes/System/Net/EndPoint.ab> #require <Classes/System/Net/IPAddress.ab> #require <Classes/System/Net/IPHostEntry.ab> #require <Classes/System/Net/Sockets/misc.ab> #require <Classes/System/Net/Sockets/Socket.ab> Imports ActiveBasic.Windows.UI Imports System Imports System.Collections.Generic Imports System.Net Imports System.Net.Sockets Imports System.IO #resource "UI_Sample.rc" Class MyApplication Inherits Form Public Sub MyApplication() AddCreate(AddressOf(OnCreate)) End Sub Protected Override Sub GetCreateStruct(ByRef cs As CREATESTRUCT) Super.GetCreateStruct(cs) With cs .style = WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_DLGFRAME Or WS_MINIMIZEBOX .cx = 400 .cy = 400 End With End Sub Private Sub OnCreate(sender As Object, e As CreateArgs) Dim wpHFontControl = GetStockObject(DEFAULT_GUI_FONT) As WPARAM getButton = New Button With getButton .Create(This) .Text = "取得" .Move(280, 5, 80, 30) .AddClick(AddressOf(Get_Click)) .SendMessage(WM_SETFONT, wpHFontControl, 0) End With textField = New EditBox With textField .Create(This, 0, WS_EX_CLIENTEDGE) .Move(10, 10, 175, 20) .SendMessage(WM_SETFONT, wpHFontControl, 0) End With requestField = New EditBox With requestField .Create(This, ES_MULTILINE Or ES_WANTRETURN Or ES_AUTOVSCROLL Or WS_VSCROLL, WS_EX_CLIENTEDGE) .Move(10, 40, 375, 100) .SendMessage(WM_SETFONT, wpHFontControl, 0) End With textView = New EditBox With textView .Create(This, ES_READONLY Or ES_MULTILINE Or ES_WANTRETURN Or ES_AUTOHSCROLL Or ES_AUTOVSCROLL Or WS_HSCROLL Or WS_VSCROLL, WS_EX_CLIENTEDGE) .Move(10, 150, 375, 190) .SendMessage(WM_SETFONT, wpHFontControl, 0) End With End Sub Sub Get_Click(sender As Object, e As Args) InvalidForms = True text = New Text.StringBuilder textView.Text = text.ToString() Dns.BeginGetHostEntry(textField.Text, "http", AddressOf(GetHostCallback), Nothing) End Sub Sub GetHostCallback(ar As IAsyncResult) Dim host = Dns.EndGetHostEntry(ar).Host As IPEndPoint Dim socket = New Socket(host.AddressFamily, SocketType.Stream, ProtocolType.Tcp) socket.ErrorDelegate = AddressOf(SocketDidError) socket.BeginConnect(host, AddressOf(SocketDidConnect)) End Sub Sub SocketDidConnect(s As Socket) Dim request = requestField.Text + Ex"¥n¥n" As String s.BeginSend(request, request.Length, AddressOf(SocketDidSend)) End Sub Sub SocketDidSend(s As Socket, n As Long) s.BeginReceive(AddressOf(SocketDidReceive)) End Sub Sub SocketDidReceive(s As Socket, buffer As *Byte, len As Long) If len Then Dim receive = New String(buffer As PCTSTR, len) text.Append(receive) s.BeginReceive(AddressOf(SocketDidReceive)) Else s.Close() textView.Text = text.ToString() InvalidForms = False End If End Sub Sub SocketDidError(s As Socket, ex As Exception) MessageBox(0, ex.ToString(), "エラー", MB_OK) s.Close() InvalidForms = False End Sub Sub InvalidForms(flag As Boolean) textField.Enabled = Not flag requestField.Enabled = Not flag getButton.Enabled = Not flag If flag Then getButton.Text = "取得中..." Else getButton.Text = "取得" End If End Sub getButton As Button textField As EditBox requestField As EditBox textView As EditBox text As Text.StringBuilder End Class Control.Initialize(GetModuleHandle(0)) Dim f = New MyApplication f.CreateForm() Winsock.Initialize() Application.Run(f) Winsock.Finalize()
「取得」イベントとかの部分。
Sub Get_Click(sender As Object, e As Args) InvalidForms = True text = New Text.StringBuilder textView.Text = text.ToString() Dns.BeginGetHostEntry(textField.Text, "http", AddressOf(GetHostCallback), Nothing) End Sub Sub GetHostCallback(ar As IAsyncResult) Dim host = Dns.EndGetHostEntry(ar).Host As IPEndPoint Dim socket = New Socket(host.AddressFamily, SocketType.Stream, ProtocolType.Tcp) socket.ErrorDelegate = AddressOf(SocketDidError) socket.BeginConnect(host, AddressOf(SocketDidConnect)) End Sub Sub SocketDidConnect(s As Socket) Dim request = requestField.Text + Ex"¥n¥n" As String s.BeginSend(request, request.Length, AddressOf(SocketDidSend)) End Sub Sub SocketDidSend(s As Socket, n As Long) s.BeginReceive(AddressOf(SocketDidReceive)) End Sub Sub SocketDidReceive(s As Socket, buffer As *Byte, len As Long) If len Then Dim receive = New String(buffer As PCTSTR, len) text.Append(receive) s.BeginReceive(AddressOf(SocketDidReceive)) Else s.Close() textView.Text = text.ToString() InvalidForms = False End If End Sub Sub SocketDidError(s As Socket, ex As Exception) MessageBox(0, ex.ToString(), "エラー", MB_OK) s.Close() InvalidForms = False End Sub Sub InvalidForms(flag As Boolean) textField.Enabled = Not flag requestField.Enabled = Not flag getButton.Enabled = Not flag If flag Then getButton.Text = "取得中..." Else getButton.Text = "取得" End If End Sub
DNSの名前解決は前回のコードと同様です。そのあとのソケットの部分を今回作りました。今回の実装は、デリゲートの引数に戻り値を入れる実装にしてみました。途中でエラーが発生した場合は、あらかじめソケットに設定しておいたエラー用のデリゲートに飛ぶようにしてあります。
さて、ソケットの基本的な部分がほとんど実装できてきたので、次は何を実装しようか迷います。TCP,UDPソケットのクラス(.NETのTCPClientとか)って必要あるのか、ちょっと疑問だったりします。とりあえず、NetworkStreamの実装には入ろうかどうかってところでしょうか。















