[Server] Socket - Listener 분리
Listener
1. _listenSocket.AcceptAsync(args); 이함수를 실행하면 비동기적으로 계속해서 연결요청이 있는지 확인
2. 연결 요청이 왔을시 내부에서 누군가가(닷넷)args.Completed.Invoke(sender, args) 실행
3. 등록했던 이벤트 OnAcceptCompleted 가실행됨
바로 접속하면 직접 OnAcceptCompleted 를 실행 (pending이 false일 경우)
=> Callback 함수를 실행할 수 없기 때문에
나중에 접속하면 args.Completed.Invoke()를 통해 실행
=> 클라이언트의 입장을 기다리다가 입장 신청을 하게되면 OnAcceptComplete를 콜백으로 실행
Socket.AcceptAsync 메서드 (System.Net.Sockets)
들어오는 연결을 허용합니다.
learn.microsoft.com
C#의 ThreadPool에는 2가지 thread
1. 일반적인 worker thread
2. I/O작업이 완료되었을때의 작업을 담당하는 (I/O) completion port thread
소켓통신은 CPU가 아니라 I/O단에서 이루어지는 작업
SocketAsyncEventArgs의 Complete eventHandler는 completion port thread가 담당하여 invoke
(Pending == true일때)
이와 같이 사용하는 이유
기다리지 않고 바로 처리
이전처럼 Socket clinetSocket = _listener.Accept(); 와 같이 사용한다면
실제 클라이언트 쪽에서 Connect라는 함수를 입장요청을 하기 전까지 return하지 않음
메인쓰레드를 만들었지만, 일을하지 않고 가만히 아무일을 하지 않는 쓰레드가 됨
유저가 많아졌을 경우 그 유저들을 처리하기 위해서 Receive와 Send를 왔다갔다 해야하는데,
무한 대기를 하면 안되기 때문
따라서 비동기(async) 함수를 사용해야함
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Listener
{
private Socket _listenSocket;
private Action<Socket> _onAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_onAcceptHandler += onAcceptHandler;
// Bind
_listenSocket.Bind(endPoint);
// Listen
// backlog : 최대 대기 수
_listenSocket.Listen(10);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
// 이벤트를 재사용 하기 때문에 null값 입력
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args);
if (!pending)
{
OnAcceptCompleted(null,args);
}
}
void OnAcceptCompleted(object sender,SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
// Socket clientSocket = _listener.Accept(); 와 같은 역할
_onAcceptHandler.Invoke(args.AcceptSocket);
}
else
{
Console.WriteLine(args.SocketError.ToString());
}
RegisterAccept(args);
}
}
}
Main
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
class Program
{
private static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket)
{
try
{
// 수신
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[From Client] {recvData}");
// 발신
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to YoungJune Server!");
clientSocket.Send(sendBuff);
// 퇴장
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
static void Main(string[] args)
{
//DNS (Domain Name System)
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
_listener.Init(endPoint, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
}
}
}
}