코드를 조금 정리해 보자
// ServerCore
// Listener.cs
namespace ServerCore
{
public class Listener
{
Socket _listenSocket;
public void Init(IPEndPoint endPoint)
{
_listenSocket = new Socket(endPoint.AddressFamily,
SocketType.Stream, ProtocolType.Tcp); /* TCP로 사용하겠다는 선언 */
_listenSocket.Bind(endPoint);
_listenSocket.Listen(10);
// 10 : 최대 대기수, 초과시 입장을 제한한다
}
public Socket Accept()
{
return _listenSocket.Accept();
}
}
}
// ServerCore
// Program.cs
class Program
{
static Listner _listener = new Listner();
static void Main(string[] args)
{
// DNS(Domain Name System)을 사용할 예정
// ip addr를 안쓰고 도메인네임을 쓰겠다는 말.
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777/* Port */);
try
{
while(true)
{
// 에코서버로 구현예정
Console.WriteLine("Listening...");
// 클라이언트가 입장하지 않으면 여기서 블락됨.(나중에 개선예정)
Socket clientSocket = listenSocket.Accept();
// 받는 부분
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("Welcom to MMORPG Server ");
clientSocket.Send(sendBuff);
// 소켓을 닫는다
clientSocket.Shutdown(SocketShutdown.Both); /*소켓을 닫는다를 미리 공지*/
clientSocket.Close(); /*실제로 닫는 부분*/
}
}
catch(Exception e)
{
}
}
}
코드를 조금 더 정리하기 전
현재 가장 시급한 문제는 아래이다.
class Listener
{
// ...
public Socket Accept()
{
// 여기가 accept 전 까지 블록되기에 이 부분을 해결해보자
return _listenSocket.Accept();
}
}
논블로킹으로 수정해보자
class Listener
{
Socket _listenSocket;
Action<Socket> _onAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
_listenSocket = new Socket(endPoint.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
/* TCP로 사용하겠다는 선언 */
// Accept시 델리게이트 선언
_onAcceptHandler += onAcceptHandler;
_listenSocket.Bind(endPoint);
_listenSocket.Listen(10);
// 10 : 최대 대기수, 초과시 입장을 제한한다
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
// 기존 소켓정보를 날린다
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args);
if(pending == false)
{
// 바로 완료됨
OnAcceptCompleted(null, args);
}
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError = SocketError.Success)
{
_onAcceptHandler.Invoke(args.AcceptSocket);
}
else
{
Console.WriteLine(args.SocketError.ToString());
}
// 다음 클라를 위해서 다시 Register한다
RegisterAccept(args);
}
}
// ServerCore
// Main.cs
class Program
{
static Listner _listener = new Listner();
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("Welcom to MMORPG Server ");
clientSocket.Send(sendBuff);
// 소켓을 닫는다
clientSocket.Shutdown(SocketShutdown.Both); /*소켓을 닫는다를 미리 공지*/
clientSocket.Close(); /*실제로 닫는 부분*/
}
catch(Exception e)
{
}
}
static void Main(string[] args)
{
// DNS(Domain Name System)을 사용할 예정
// ip addr를 안쓰고 도메인네임을 쓰겠다는 말.
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777/* Port */);
_listener.Init(endPoint, OnAcceptHandler);
while(true)
{
}
}
}
추가사항
class Listener
{
// ...
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
// ...
// 이벤트를 여러개 둬서 받을 수 있음
for(int i = 0; i < 10; i++)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
/*
Main에서 while이 무한으로 돌고 있는데
OnAcceptCompleted이 어느 타이밍에 호출되는가?
-> 별도의 Thread가 생성되어 OnAcceptCompleted가 호출된다.
-> 멀티쓰레드로 동작한다는 말(멀티쓰레드 세이프하게 만들어야함)
*/
RegisterAccept(args);
}
}