비동기 클라이언트 소켓 사용

보기 전환:
ScriptFree
.NET Framework 개발자 가이드
비동기 클라이언트 소켓 사용

비동기 클라이언트 소켓에서는 네트워크 작업이 완료될 때까지 대기하는 동안 응용 프로그램을 일시 중단하지 않습니다. 그 대신, 응용 프로그램이 원본 스레드에서 계속 실행되는 동안에 표준 .NET Framework 비동기 프로그래밍 모델을 사용해 하나의 스레드에서 네트워크 연결을 처리합니다. 비동기 소켓은 네트워크를 많이 사용하거나 네트워크 작업이 끝날 때까지 대기할 수 없는 응용 프로그램에 적합합니다.

Socket 클래스는 비동기 메서드에 대해 .NET Framework 명명 패턴을 따릅니다. 예를 들어, 동기 Receive 메서드는 비동기 BeginReceiveEndReceive 메서드에 해당합니다.

비동기 작업에는 작업 결과를 반환할 콜백 메서드가 필요합니다. 사용자의 응용 프로그램에서 결과를 알 필요가 없는 경우에는 콜백 메서드가 필요하지 않습니다. 이 단원의 예 코드에서는 네트워크 장치로의 연결을 시작할 메서드 및 연결을 완료할 콜백 메서드, 데이터 보내기를 시작할 메서드 및 보내기를 완료할 콜백 메서드, 데이터 받기를 시작할 메서드 및 데이터 받기를 완료할 콜백 메서드를 사용하는 방법을 보여 줍니다.

비동기 소켓에서는 시스템 스레드 풀에 있는 여러 스레드를 사용하여 네트워크 연결을 처리합니다. 한 스레드에서는 데이터 보내기 또는 받기를 시작하고, 다른 스레드에서는 네트워크 장치로의 연결을 완료하고 데이터를 보내거나 받습니다. 다음 예에서 System.Threading.ManualResetEvent 클래스의 인스턴스는 작업 실행 도중 주 스레드 및 신호의 실행을 일시 중단하는 데 사용됩니다.

다음 예에서는 네트워크 장치에 비동기 소켓을 연결하기 위해 Connect 메서드로 Socket을 초기화한 다음 BeginConnect 메서드를 호출합니다. 이때 네트워크 장치를 나타내는 원격 끝점, connect 콜백 메서드, 상태 개체인 클라이언트 Socket을 전달하는데, 이는 비동기 호출 사이에 상태 정보를 전달하는 데 사용됩니다. 이 예에서는 지정된 끝점에 지정된 Socket을 연결하는 Connect 메서드를 구현합니다. 여기서는 connectDone이라는 전역 ManualResetEvent가 있다고 가정합니다.

Visual Basic
Public Shared Sub Connect(remoteEP As EndPoint, client As Socket)
    client.BeginConnect(remoteEP, _
       AddressOf ConnectCallback, client)
    
    connectDone.WaitOne()
End Sub 'Connect
C#
public static void Connect(EndPoint remoteEP, Socket client) {
    client.BeginConnect(remoteEP, 
        new AsyncCallback(ConnectCallback), client );

   connectDone.WaitOne();
}

Connect 콜백 메서드인 ConnectCallback에서는 AsyncCallback 대리자를 구현합니다. ConnectCallback은 원격 장치를 사용할 수 있는 경우 원격 장치에 연결한 다음, ManualResetEventconnectDone을 설정하여 연결이 완료된 응용 프로그램 스레드에 신호를 보냅니다. 다음 코드에서는 ConnectCallback 메서드를 구현합니다.

Visual Basic
Private Shared Sub ConnectCallback(ar As IAsyncResult)
    Try
        ' Retrieve the socket from the state object.
        Dim client As Socket = CType(ar.AsyncState, Socket)

        ' Complete the connection.
        client.EndConnect(ar)

        Console.WriteLine("Socket connected to {0}", _
            client.RemoteEndPoint.ToString())

        ' Signal that the connection has been made.
        connectDone.Set()
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'ConnectCallback


C#
private static void ConnectCallback(IAsyncResult ar) {
    try {
        // Retrieve the socket from the state object.
        Socket client = (Socket) ar.AsyncState;

        // Complete the connection.
        client.EndConnect(ar);

        Console.WriteLine("Socket connected to {0}",
            client.RemoteEndPoint.ToString());

        // Signal that the connection has been made.
        connectDone.Set();
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

예 메서드 Send는 지정된 문자열 데이터를 ASCII 형식으로 인코딩하고, 이 데이터를 지정된 소켓이 나타내는 네트워크 장치에 비동기식으로 보냅니다. 다음 예에서는 Send 메서드를 구현합니다.

Visual Basic
Private Shared Sub Send(client As Socket, data As [String])
    ' Convert the string data to byte data using ASCII encoding.
    Dim byteData As Byte() = Encoding.ASCII.GetBytes(data)

    ' Begin sending the data to the remote device.
    client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, _
        AddressOf SendCallback, client)
End Sub 'Send

C#
private static void Send(Socket client, String data) {
    // Convert the string data to byte data using ASCII encoding.
    byte[] byteData = Encoding.ASCII.GetBytes(data);

    // Begin sending the data to the remote device.
    client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
        new AsyncCallback(SendCallback), client);
}

Send 콜백 메서드 SendCallbackAsyncCallback 대리자를 구현합니다. 이 메서드는 네트워크 장치에서 받을 준비가 되면 데이터를 보냅니다. 다음 예에서는 SendCallback 메서드를 구현하는 방법을 보여 줍니다. 여기서는 sendDone이라는 전역 ManualResetEvent가 있다고 가정합니다.

Visual Basic
Private Shared Sub SendCallback(ar As IAsyncResult)
    Try
        ' Retrieve the socket from the state object.
        Dim client As Socket = CType(ar.AsyncState, Socket)

        ' Complete sending the data to the remote device.
        Dim bytesSent As Integer = client.EndSend(ar)
        Console.WriteLine("Sent {0} bytes to server.", bytesSent)

        ' Signal that all bytes have been sent.
        sendDone.Set()
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'SendCallback

C#
private static void SendCallback(IAsyncResult ar) {
    try {
        // Retrieve the socket from the state object.
        Socket client = (Socket) ar.AsyncState;

        // Complete sending the data to the remote device.
        int bytesSent = client.EndSend(ar);
        Console.WriteLine("Sent {0} bytes to server.", bytesSent);

        // Signal that all bytes have been sent.
        sendDone.Set();
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

클라이언트 소켓에서 데이터를 읽으려면 비동기 호출 사이에 값을 전달하는 상태 개체가 필요합니다. 다음 클래스는 클라이언트 소켓에서 데이터를 받을 때 사용할 예 상태 개체입니다. 이 클래스에는 클라이언트 소켓에 대한 필드, 받은 데이터를 보관할 버퍼에 대한 필드 및 들어오는 데이터 문자열을 보유할 필드가 포함됩니다. 상태 개체에 이들 필드를 포함시키면 클라이언트 소켓에서 데이터를 읽는 호출이 여러 번 발생해도 이들 필드 값이 보존됩니다.

Visual Basic
Public Class StateObject
    ' Client socket.
    Public workSocket As Socket = Nothing 
    ' Size of receive buffer.
    Public BufferSize As Integer = 256
    ' Receive buffer.
    Public buffer(256) As Byte 
    ' Received data string.
    Public sb As New StringBuilder()
End Class 'StateObject

C#
public class StateObject {
    // Client socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 256;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();
}

Receive 메서드는 상태 개체를 설정한 다음 BeginReceive 메서드를 호출하여 클라이언트 소켓에서 비동기식으로 데이터를 읽습니다. 다음 예에서는 Receive 메서드를 구현합니다.

Visual Basic
Private Shared Sub Receive(client As Socket)
    Try
        ' Create the state object.
        Dim state As New StateObject()
        state.workSocket = client
            
        ' Begin receiving the data from the remote device.
        client.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
            AddressOf ReceiveCallback, state)
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'Receive

C#
private static void Receive(Socket client) {
    try {
        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = client;

        // Begin receiving the data from the remote device.
        client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReceiveCallback), state);
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

Receive 콜백 메서드인 ReceiveCallbackAsyncCallback 대리자를 구현합니다. 이 메서드는 네트워크 장치에서 데이터를 받아 메시지 문자열을 만듭니다. 이 메서드는 네트워크에서 한 바이트 이상의 데이터를 읽어서 데이터 버퍼에 넣은 다음, 클라이언트에서 데이터를 모두 보낼 때까지 BeginReceive 메서드를 반복 호출합니다. 클라이언트에서 모든 데이터를 읽으면, ReceiveCallbackManualResetEvent sendDone을 설정하여 데이터가 완료되었음을 응용 프로그램 스레드에 신호로 보냅니다.

다음 예 코드에서는 ReceiveCallback 메서드를 구현합니다. 이 예에서는 받은 문자열과 receiveDone이라는 이름의 ManualResetEventresponse라는 이름의 전역 문자열에 저장되는 것으로 가정합니다. 서버에서는 클라이언트 소켓을 정상적으로 종료하여 네트워크 세션을 끝내야 합니다.

Visual Basic
Private Shared Sub ReceiveCallback(ar As IAsyncResult)
    Try
        ' Retrieve the state object and the client socket 
        ' from the asynchronous state object.
        Dim state As StateObject = CType(ar.AsyncState, StateObject)
        Dim client As Socket = state.workSocket

        ' Read data from the remote device.
        Dim bytesRead As Integer = client.EndReceive(ar)

        If bytesRead > 0 Then
            ' There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, _
                bytesRead))

            '  Get the rest of the data.
            client.BeginReceive(state.buffer, 0, state.BufferSize, 0, _
                AddressOf ReceiveCallback, state)
        Else
            ' All the data has arrived; put it in response.
            If state.sb.Length > 1 Then
                response = state.sb.ToString()
            End If
            ' Signal that all bytes have been received.
            receiveDone.Set()
        End If
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
End Sub 'ReceiveCallback

C#
private static void ReceiveCallback( IAsyncResult ar ) {
    try {
        // Retrieve the state object and the client socket 
        // from the asynchronous state object.
        StateObject state = (StateObject) ar.AsyncState;
        Socket client = state.workSocket;
        // Read data from the remote device.
        int bytesRead = client.EndReceive(ar);
        if (bytesRead > 0) {
            // There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
                //  Get the rest of the data.
            client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
                new AsyncCallback(ReceiveCallback), state);
        } else {
            // All the data has arrived; put it in response.
            if (state.sb.Length > 1) {
                response = state.sb.ToString();
            }
            // Signal that all bytes have been received.
            receiveDone.Set();
        }
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

참고 항목