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