앞의 TCP 소켓예제는 문제가 좀 있다. 왜냐하면 소켓 통신에 사용되는 모든 메서드가 기본적으로 동
기 호출이라는 점 때문이다. 즉, I/O가 완료될 때까지 Send/Receive 메서드를 호출한 스레드는 블로
킹 되므로 서버 측에서 Accept를 빠르게 처리할 수 없다는 문제가 발생한다.
이런 문제를 해결하기 위해 서버 소켓이 Accept로 반환받은 클라이언트의 처리를 별도의 스레드에
맡겨서 처리하는 방법이 있다.
private static void serverFunc(object obj)
{
using (Socket srvSocket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 11200);
srvSocket.Bind(endPoint);
srvSocket.Listen(10);
while (true)
{
Socket clntSocket = srvSocket.Accept();
ThreadPool.QueueUserWorkItem((WaitCallback)clientSocketProcess, clntSocket);
}
}
}
private static void clientSocketProcess(object state)
{
Socket clntSocket = state as Socket;
byte[] recvBytes = new byte[1024];
int nrecv = clntSocket.Receive(recvBytes);
string txt = Encoding.UTF8.GetString(recvBytes, 0, nrecv);
byte[] sendBytes = Encoding.UTF8.GetBytes("Hello: " + txt);
clntSocket.Send(sendBytes);
clntSocket.Close();
}
이렇게 클라이언트 하나당 스레드를 대응시켜 처리하는 것은 구현하기 쉽다는 장점 덕분에 종종
사용된다. 하지만 단점으로 서버의 성능을 충분히 발휘할 수 없는 고조적 결함이 있다.
일례로 32비트 서버에서는 사용자 프로그램이 사용할 수 있는 메모리 용량이 2GB였고, 하나의
스레드가 필요로 하는 스택의 메모리 크기가 1MB였다는 점을 감안하면 많아야 2,000개로 제한된다
는 것이다. 물론, 64비트 서버로 넘어오면서 2GB 제약이 없어졌지만 스레드가 많아진다는 것은 여전
히 문제가 된다. 왜냐하면 그로 인해 “스레드 문맥 전환” 부하가 발생한다는 점이다. 가령 스레드를
60,000개 생성했다고 가정하면 CPU는 그 6만 개의 스레드를 동시에 실행하느라 오히려 부하가 가중
되고 따라서 서버의 성능 저하로 이어진다.
그와 같은 문제를 비동기 통신을 사용함으로써 해결할 수 있다. 소켓 통신 역시 운영체제에게는
I/O에 속하기 때문에 Socket 타입에는 Send, Receive 메서드에 대해 각각 BeginSend/EndSend,