- 블로킹 함수를 이용해 소켓을 Accept를 하면 코드는 직관적 이지만 접속을 기다리는 동안 그 후 로직이 진행되지 않아 실제 프로젝트에서는 적합하지 않음.
- 기존 블로킹 함수인 Accept 함수를 AcceptAsync 함수를 사용해 비동기 방식으로 리팩토링.
- Listener.cs를 추가하여 비동기 구현.
SocketAsyncEventArgs args = new SocketAsyncEventArgs();args.Completed += new EventHandler<SocketAsyncEventArgs>(Onacceptcompleted);RegisterAccept(args);AcceptAsync메소드는 client가 접속 되면 false 반환 client가 접속 되지 않아도 블로킹을 하지않고 true 반환 이후에 client가 접속 되면 SocketAsyncEventArgs로 소캣을 콜백bool pending = _listenSocket.AcceptAsync(args);Onacceptcompleted에서 호출 됬을때는 args가 비어있지 않음으로 기존 소켓 정보를 초기화. 초기화 하지 않으면 에러가 발생.args.AcceptSocket = null;RegisterAccept 메소드를 다시 호출.using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
class Listener
{
Socket _listenSocket;
Action<Socket> _onacceptHandler; //클라이언트 접속 되면 접속 유무 콜백
public void init(IPEndPoint endPoint, Action<Socket> onacceptHandler)
{
//문지기
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_onacceptHandler += onacceptHandler;
//문지기 교육
_listenSocket.Bind(endPoint);
//최대 대기수
_listenSocket.Listen(10);
//클라 접속 되면 연결 해준 함수로 콜백 해줌(콜백 함수 등록)
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(Onacceptcompleted);
//소켓 오픈
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
//Onacceptcompleted메소드에서 accept후 다시 들어오면 args에 기존 값이 남아있어 해당 부분을 초기화 시켜줌. 초기화 하지 않으면 에러 발생.
args.AcceptSocket = null;
//AcceptAsync client가 접속 되면 false 반환, 접속되지 않으면 true 반환(true 반환 후 client가 접속 되면 SocketAsyncEventArgs로 소캣을 콜백 해줌.
bool pending = _listenSocket.AcceptAsync(args);
if (!pending) // pending true면 아직 접속된 클라가 없다는 뜻.
Onacceptcompleted(null,args);
}
void Onacceptcompleted(object sender , SocketAsyncEventArgs args)
{
//소켓 에러유무 확인
if (args.SocketError == SocketError.Success)
{
//접속이 완료되어 호출자에게 접속 소켓 전달.
_onacceptHandler.Invoke(args.AcceptSocket);
}
else
Console.WriteLine(SocketError.SocketError);
//다음 접속을 기다릴 수 있도록 AcceptAsync를 재 실행.
RegisterAccept(args);
}
public Socket Accept()
{
return _listenSocket.Accept();
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnacceptHandler(Socket socket)
{
try
{
Console.WriteLine("Listening...");
//손님입장
Socket clientSocket = socket;
// 받는다.(블로킹 함수)
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine(recvData);
//전송 한다.(블로킹 함수)
byte[] sendBuffe = Encoding.UTF8.GetBytes("welcome to MMORPG Server !...");
clientSocket.Send(sendBuffe);
//예고
clientSocket.Shutdown(SocketShutdown.Both);
//연결끊기
clientSocket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipaddr = ipHost.AddressList[0]; //아이피가 여러개 있을수 있으며 배열로 ip를 반환함
IPEndPoint endPoint = new IPEndPoint(ipaddr, 7777);
_listener.init(endPoint, OnacceptHandler);
while (true)
{
}
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace DummyClient
{
class Program
{
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipaddr = ipHost.AddressList[0]; //아이피가 여러개 있을수 있으며 배열로 ip를 반환함
IPEndPoint endPoint = new IPEndPoint(ipaddr, 7777);
while(true)
{
try
{
//휴대폰 설정(소켓)
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
//문지기한테 입장 문의
socket.Connect(endPoint);
Console.WriteLine($"Conneted to {socket.RemoteEndPoint.ToString()}");
//보낸다.(블로킹 함수)
byte[] sendbuff = Encoding.UTF8.GetBytes("Hellow world!");
int sendByte = socket.Send(sendbuff);
//받는다.(블로킹 함수)
byte[] recvBuff = new byte[1024];
int recvBytes = socket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[From server] {recvData}");
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Thread.Sleep(1500);
}
}
}
}
dummyclient는 server에서 계속해서 접속을 받는지 테스트를 위해
wihle문으로 접속 socket 정료후 재좁속 하도록 수정.
