비동기 서버 소켓 사용

보기 전환:
ScriptFree
.NET Framework 개발자 가이드
비동기 서버 소켓 사용

비동기 서버 소켓에서는 .NET Framework 비동기 프로그래밍 모델을 사용하여 네트워크 서비스 요청을 처리합니다. Socket 클래스는 표준 .NET Framework 비동기 명명 패턴을 따릅니다. 예를 들어, 동기 Accept 메서드는 비동기 BeginAcceptEndAccept 메서드에 해당합니다.

비동기 서버 소켓에서는 네트워크로부터 연결 요청을 받아들이기 시작할 메서드, 연결 요청을 처리하고 네트워크에서 데이터 받기를 시작할 콜백 메서드, 데이터 받기를 끝낼 콜백 메서드가 필요합니다. 이 모든 메서드는 이 단원에서 자세히 설명합니다.

네트워크로부터 연결 요청을 받도록 설정하는 다음 예에서는 StartListening 메서드로 Socket을 초기화한 다음 BeginAccept 메서드를 사용하여 새 연결을 받아들이기 시작합니다. 소켓에서 새 연결 요청을 받으면 Accept 콜백 메서드가 호출됩니다. 이 메서드는 연결을 처리할 Socket 인스턴스를 가져오고 요청을 처리할 스레드에 해당 Socket을 넘겨줘야 합니다. Accept 콜백 메서드는 AsyncCallback 대리자를 구현합니다. 이 메서드는 void를 반환하고 IAsyncResult 형식의 단일 매개 변수를 사용합니다. 다음 예는 Accept 콜백 메서드의 셸입니다.

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

BeginAccept 메서드는 두 개의 매개 변수를 사용하는데, 하나는 Accept 콜백 메서드를 가리키는 AsyncCallback 대리자이고 다른 하나는 콜백 메서드에 상태 정보를 전달하는 데 사용되는 개체입니다. 다음 예에서는 수신 대기 중인 Socketstate 매개 변수를 통해 콜백 메서드에 전달됩니다. 이 예에서는 AsyncCallback 대리자를 만들고 네트워크로부터 연결을 받아들이기 시작합니다.

Visual Basic
listener.BeginAccept( _
    New AsyncCallback(SocketListener.acceptCallback),_
    listener)
C#
listener.BeginAccept(
    new AsyncCallback(SocketListener.acceptCallback), 
    listener);

비동기 소켓은 시스템 스레드 풀의 스레드를 사용하여 들어오는 연결을 처리합니다. 한 스레드는 연결을 받아들이고 다른 스레드는 들어오는 각 연결을 처리하고 또 다른 스레드는 연결에서 데이터를 받습니다. 스레드 풀에서 할당된 스레드에 따라서 이들 스레드는 같은 스레드일 수도 있습니다. 다음 예에서 System.Threading.ManualResetEvent 클래스는 작업 실행 도중 주 스레드 및 신호의 실행을 일시 중단합니다.

다음 예에서는 로컬 컴퓨터에서 비동기 TCP/IP 소켓을 만들고 연결을 받아들이기 시작하는 비동기 메서드를 보여 줍니다. 여기서는 allDone이라는 전역 ManualResetEvent가 있고, 메서드는 SocketListener라는 클래스의 멤버이고 acceptCallback이라는 콜백 메서드가 정의된 것으로 가정합니다.

Visual Basic
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

C#
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...");
}

Accept 콜백 메서드(이전 예의 acceptCallback)는 주 응용 프로그램 스레드에 처리를 계속하도록 신호를 보내고, 클라이언트와의 연결을 설정하고, 클라이언트로부터 온 데이터를 비동기식으로 읽기 시작해야 합니다. 다음 예는 acceptCallback 메서드 구현의 첫 번째 부분입니다. 해당 메서드 중 이 섹션은 주 응용 프로그램 스레드에 처리를 계속하도록 신호를 보내고 클라이언트와의 연결을 설정합니다. 여기서는 allDone이라는 전역 ManualResetEvent가 있다고 가정합니다.

Visual Basic
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
C#
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 필드를 포함합니다. 상태 개체에 이들 필드를 포함시키면 클라이언트 소켓에서 데이터를 읽는 호출이 여러 번 발생해도 이들 필드 값이 보존됩니다.

Visual Basic
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
C#
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 클래스가 정의되어 있으며 readCallback 메서드가 SocketListener라는 클래스에 정의되어 있다고 가정합니다.

Visual Basic
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

C#
    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);
    }

비동기 소켓 서버를 위해 구현해야 하는 마지막 메서드는 클라이언트에서 보낸 데이터를 반환하는 Read 콜백 메서드입니다. Accept 콜백 메서드와 마찬가지로, Read 콜백 메서드는 AsyncCallback 대리자입니다. 이 메서드는 클라이언트 소켓에서 하나 이상의 바이트를 읽어서 데이터 버퍼에 넣은 다음, 클라이언트에서 보낸 데이터가 완료될 때까지 BeginReceive 메서드를 다시 호출합니다. 클라이언트로부터 모든 메시지를 읽으면 콘솔에 문자열이 표시되고 클라이언트와의 연결을 처리하는 서버 소켓이 닫힙니다.

다음 예에서는 readCallback 메서드를 구현합니다. 여기서는 StateObject 클래스가 정의된 것으로 가정합니다.

Visual Basic
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

C#
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();
    }
}

참고 항목