Share via


非同期サーバー ソケットの使用

非同期サーバー ソケットは、.NET Framework の非同期プログラミング モデルを使用して、ネットワーク サービス要求を処理します。Socket クラスは、非同期メソッドに対する .NET Framework の標準的な名前付け規則に従っています。たとえば、同期メソッドである Accept は、非同期の BeginAccept メソッドと EndAccept メソッドに対応しています。

非同期サーバー ソケットには、ネットワークからの接続要求の受け入れを開始するメソッド、接続要求を処理し、ネットワークからのデータの受信を開始するコールバック メソッド、およびデータの受信を終了するコールバック メソッドが必要です。ここでは、これらすべてのメソッドについて説明します。

ネットワークからの接続要求の受け入れを開始するために、StartListening メソッドを使用して Socket を初期化してから、BeginAccept メソッドを使用して新しい接続の受け入れを開始する例を次に示します。受け入れコールバック メソッドは、ソケット上で新しい接続要求が受信されたときに呼び出されます。このメソッドは、接続を処理する Socket インスタンスを取得し、その Socket を要求を処理するスレッドに渡します。受け入れコールバック メソッドは AsyncCallback デリゲートを実装しています。このメソッドは、戻り値の型が void で、IAsyncResult 型のパラメータを 1 つ受け取ります。受け入れコールバック メソッドのシェルのコード例を次に示します。

Sub acceptCallback(ar As IAsyncResult)
    ' Add the callback code here.
End Sub 'acceptCallback
void acceptCallback( IAsyncResult ar) {
    // Add the callback code here.
}

BeginAccept メソッドには、受け入れコールバック メソッドを指す AsyncCallback デリゲートと、コールバック メソッドに状態情報を渡すために使用されるオブジェクトを 2 つのパラメータで渡します。リッスン中の Socketstate パラメータを使用してコールバック メソッドに渡すコード例を次に示します。この例は、AsyncCallback デリゲートを作成し、ネットワークからの接続の受け入れを開始します。

listener.BeginAccept( _
    New AsyncCallback(SocketListener.acceptCallback),_
    listener)
listener.BeginAccept(
    new AsyncCallback(SocketListener.acceptCallback), 
    listener);

非同期ソケットは、システムのスレッド プールから複数のスレッドを使用して、着信した接続を処理します。あるスレッドで接続の受け入れを行い、着信した各接続を処理するために別のスレッドを使用し、接続からのデータを受信するためにまた別のスレッドを使用します。これらのスレッドは、スレッド プールから割り当てられるスレッドに応じて、同じスレッドになる場合もあります。System.Threading.ManualResetEvent クラスがメイン スレッドの実行を中断し、実行を再開できるようになった時点でその旨を通知する例を次に示します。

ローカル コンピュータ上で非同期の TCP/IP ソケットを作成し、接続の受け入れを開始する非同期メソッドを次の例に示します。この例は、allDone というグローバルな ManualResetEvent が存在すること、例中のメソッドが SocketListener というクラスのメンバであること、および acceptCallback という名前のコールバック メソッドが定義されていることを前提としています。

Public Sub StartListening()
    Dim ipHostInfo As IPHostEntry = Dns.Resolve(Dns.GetHostName())
    Dim localEP = New IPEndPoint(ipHostInfo.AddressList(0), 11000)
    
    Console.WriteLine("Local address and port : {0}", localEP.ToString())
    
    Dim listener As New Socket(localEP.Address.AddressFamily, _
       SocketType.Stream, ProtocolType.Tcp)
    
    Try
        listener.Bind(localEP)
        listener.Listen(10)
        
        While True
            allDone.Reset()
            
            Console.WriteLine("Waiting for a connection...")
            listener.BeginAccept(New _
                AsyncCallback(SocketListener.acceptCallback), _
                listener)
            
            allDone.WaitOne()
        End While
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
    Console.WriteLine("Closing the listener...")
End Sub 'StartListening
public void StartListening() {
    IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000);

    Console.WriteLine("Local address and port : {0}",localEP.ToString());

    Socket listener = new Socket( localEP.Address.AddressFamily,
        SocketType.Stream, ProtocolType.Tcp );

    try {
        listener.Bind(localEP);
        listener.Listen(10);

        while (true) {
            allDone.Reset();

            Console.WriteLine("Waiting for a connection...");
            listener.BeginAccept(
                new AsyncCallback(SocketListener.acceptCallback), 
                listener );

            allDone.WaitOne();
        }
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }

    Console.WriteLine( "Closing the listener...");
}

受け入れコールバック メソッド (上の例では acceptCallback) は、メイン アプリケーション スレッドに処理を続行するように通知し、クライアントとの接続を確立し、クライアントからのデータの非同期読み取りを開始します。acceptCallback メソッドの実装の最初の部分を次の例に示します。このメソッドのこの部分では、メイン アプリケーション スレッドに処理を続行するように通知し、クライアントへの接続を確立しています。また、allDone というグローバルな ManualResetEvent が存在することを前提としています。

Public Sub acceptCallback(ar As IAsyncResult)
    allDone.Set()
    
    Dim listener As Socket = CType(ar.AsyncState, Socket)
    Dim handler As Socket = listener.EndAccept(ar)

    ' Additional code to read data goes here.
End Sub 'acceptCallback
public void acceptCallback(IAsyncResult ar) {
    allDone.Set();

    Socket listener = (Socket) ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Additional code to read data goes here.  
}

クライアント ソケットからデータを読み取るには、複数の非同期呼び出し間で値を受け渡しする状態オブジェクトが必要です。リモート クライアントから文字列を受信するための状態オブジェクトを実装するコード例を次に示します。このオブジェクトには、クライアント ソケット用のフィールド、データの受信用のデータ バッファ、およびクライアントから送信されたデータ文字列を作成するための StringBuilder があります。これらのフィールドを状態オブジェクトに用意することにより、クライアント ソケットからデータを読み取るための複数の呼び出しの間で、これらの値を保持できます。

Public Class StateObject
    Public workSocket As Socket = Nothing
    Public BufferSize As Integer = 1024
    Public buffer(BufferSize) As Byte
    Public sb As New StringBuilder()
End Class 'StateObject
public class StateObject {
    public Socket workSocket = null;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
}

acceptCallback メソッドの中で、クライアント ソケットからのデータの受信を開始するこの部分では、最初に StateObject クラスのインスタンスを初期化してから、BeginReceive メソッドを呼び出して、クライアント ソケットからのデータの非同期読み取りを開始します。

完全な acceptCallback メソッドの例を次に示します。この例は、allDone, という名前のグローバルな ManualResetEvent が存在すること、StateObject クラスが定義されていること、および SocketListener というクラス内で readCallback メソッドが定義されていることを前提としています。

Public Shared Sub acceptCallback(ar As IAsyncResult)
    ' Get the socket that handles the client request.
    Dim listener As Socket = CType(ar.AsyncState, Socket)
    Dim handler As Socket = listener.EndAccept(ar)
    
    ' Signal the main thread to continue.
    allDone.Set()
    
    ' Create the state object.
    Dim state As New StateObject()
    state.workSocket = handler
    handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
        AddressOf AsynchronousSocketListener.readCallback, state)
End Sub 'acceptCallback
    public static void acceptCallback(IAsyncResult ar) {
        // Get the socket that handles the client request.
        Socket listener = (Socket) ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        // Signal the main thread to continue.
        allDone.Set();

        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(AsynchronousSocketListener.readCallback), state);
    }

非同期ソケット サーバーに実装する必要がある最後のメソッドは、クライアントから送信されたデータを返す、読み取りコールバック メソッドです。受け入れコールバック メソッドと同様に、読み取りコールバック メソッドも AsyncCallback デリゲートです。このメソッドは、クライアント ソケットから 1 つ以上のバイトをデータ バッファに読み取り、次に、クライアントからのデータ送信が完了するまでの間 BeginReceive メソッドを繰り返し呼び出します。クライアントからメッセージ全体を読み取ったら、その文字列をコンソールに表示し、クライアントへの接続を処理していたサーバー ソケットを閉じます。

readCallback メソッドを実装するコード例を次に示します。この例は、StateObject クラスが定義されていることを前提としています。

Public Shared Sub readCallback(ar As IAsyncResult)
    Dim state As StateObject = CType(ar.AsyncState, StateObject)
    Dim handler As Socket = state.workSocket
    
    ' Read data from the client socket. 
    Dim read As Integer = handler.EndReceive(ar)
    
    ' Data was read from the client socket.
    If read > 0 Then
        state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, read))
        handler.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
            AddressOf readCallback, state)
    Else
        If state.sb.Length > 1 Then
            ' All the data has been read from the client;
            ' display it on the console.
            Dim content As String = state.sb.ToString()
            Console.WriteLine("Read {0} bytes from socket." + _
                ControlChars.Cr + " Data : {1}", content.Length, content)
        End If
    End If
End Sub 'readCallback
public static void readCallback(IAsyncResult ar) {
    StateObject state = (StateObject) ar.AsyncState;
    Socket handler = state.WorkSocket;

    // Read data from the client socket.
    int read = handler.EndReceive(ar);

    // Data was read from the client socket.
    if (read > 0) {
        state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,read));
        handler.BeginReceive(state.buffer,0,StateObject.BufferSize, 0,
            new AsyncCallback(readCallback), state);
    } else {
        if (state.sb.Length > 1) {
            // All the data has been read from the client;
            // display it on the console.
            string content = state.sb.ToString();
            Console.WriteLine("Read {0} bytes from socket.\n Data : {1}",
               content.Length, content);
        }
        handler.Close();
    }
}

参照

概念

同期サーバー ソケットの使用
非同期サーバー ソケットの例
ソケットを使用したリッスン

その他の技術情報

マネージ スレッド処理