C#

비동기 Socket 서버

비동기 Socket 서버

이전 아티클에서는 기본 개념 소개를 목적으로 Socket 클래스를 사용하여 동기 서버 (그것도 하나의 클라이언트만 받아들이는) 를 만드는 방법을 소개했는데, 여기서는 여러 클라이언트들을 비동기적으로 처리하는 보다 실무에 가까운 비동기 Socket 서버에 대해 소개한다.

TcpListener 클래스에서 TCP 연결을 받아들이기 위하여 사용하였던 AcceptTcpClientAsync() 같은 *Async() 메서드는 C# await 패턴을 지원하는 메서드들이었다. 그러나, Socket 클래스의 *Async() 메서드는 이러한 await 패턴을 지원하는 메서드들이 아니며, 사용법도 좀 복잡하다.

보통 Socket 클래스에서는 APM (Asynchronous Programming Model) 방식으로 비동기 처리를 하는데, 예를 들어 비동기 Accept를 위해 BeginAccept()와 EndAccept() 메서드를 사용한다. 이렇게 고전적인 APM 방식은 그 사용법이 복잡하고 코드가 간결하지 않기 때문에, 이를 TAP (Task-based Asynchronous Pattern) 방식으로 변환하여 await 패턴으로 사용하도록 변경하면 코드가 간결해지고 가독성도 높일 수 있다. 이를 위해 아래 예제는 Task.Factory.FromAsync() 메서드를 사용하여 APM을 await 패턴으로 변환하여 비동기 소켓 서버를 구현하였다.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace sockasyncsrv
{
    class Program
    {
        static void Main(string[] args)
        {
            RunAsyncSocketServer().Wait();
        }

        static async Task RunAsyncSocketServer()
        { 
            int MAX_SIZE = 1024;  // 가정

            // (1) 소켓 객체 생성 (TCP 소켓)
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // (2) 포트에 바인드
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, 7000);
            sock.Bind(ep);

            // (3) 포트 Listening 시작
            sock.Listen(100);

            while (true)
            {
                // (4) 비동기 소켓 Accept
                Socket clientSock = await Task.Factory.FromAsync(sock.BeginAccept, sock.EndAccept, null);
                
                // (5) 비동기 소켓 수신
                var buff = new byte[MAX_SIZE];

                int nCount = await Task.Factory.FromAsync<int>(
                           clientSock.BeginReceive(buff, 0, buff.Length, SocketFlags.None, null, clientSock),
                           clientSock.EndReceive);

                if (nCount > 0)
                {
                    string msg = Encoding.ASCII.GetString(buff, 0, nCount);
                    Console.WriteLine(msg);

                    // (6) 비동기 소켓 송신
                    await Task.Factory.FromAsync(
                            clientSock.BeginSend(buff, 0, buff.Length, SocketFlags.None, null, clientSock),
                            clientSock.EndSend);
                }

                // (7) 소켓 닫기
                clientSock.Close();
            }
        }
    }
}

위 예제를 각 스텝별로 살펴보면,

  1. 먼저 서버 Socket 객체를 생성한다.
  2. Socket 객체를 포트에 바인드한다.
  3. 포트 Listening을 시작한다.
  4. 비동기로 클라이언트 연결을 Accept 하기 위해 BeginAccept/EndAccept를 사용하는데, 여기서는 APM 방식을 TAP 방식으로 변환하기 위해 Task.Factory.FromAsync() 메서드를 사용하였다. Task.Factory.FromAsync() 메서드는 첫번째 파라미터에 Begin* 델리게이트를, 두번째 파라미터에 End* 델리게이트를 적는다.
  5. 비동기로 소켓에서 데이타를 수신하기 위해 Socket 객체의 BeginReceive/EndReceive를 사용하는데, 다시 C# await 패턴을 위해 Task.Factory.FromAsync<T>() 메서드를 사용하였다. BeginReceive() 가 int 를 리턴하므로 FromAsync<int>() 를 사용하였으며, BeginReceive() 메서드가 여러 파라미터들을 가지므로 이들 입력파라미터들을 메서드 원형에 맞춰 채워넣었다.
  6. 비동기로 소켓으로 데이타를 전송하기 위해서 Socket 객체의 BeginSend/EndSend 를 사용한다.
  7. 클라이언트와 통신이 끝나면 사용된 클라이언트 소켓을 닫는다.

본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.

Previous Next Print