서버를 분산서버로 구현할 경우 (ex. NPC AI, 몬스터관리, 등) 다른 서버와 서로 통신을 하기 위해 한쪽은 Listener역할, 다른 한 쪽은 Connector역할을 할 수 있으니 따로 분리해서 만들어준다.
public abstract class Session
{
Socket _socket;
int _disconnected = 0;
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
object _lock = new object();
public abstract void OnConnected(EndPoint endPoint);
public abstract void OnRecv(ArraySegment<byte> buffer);
public abstract void OnSend(int numOfBytes);
public abstract void OnDisconnected(EndPoint endPoint);
public void Start(Socket socket)
{
_socket = socket;
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
_recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv();
}
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pendingList.Count == 0) // 내가 가장 먼저 들어왔다면 (대기 중인게 아무것도 없다면)
RegisterSend();
}
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
OnDisconnected(_socket.RemoteEndPoint);
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크 통신
void RegisterSend()
{
while(_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
_pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
}
_sendArgs.BufferList = _pendingList;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
_sendArgs.BufferList = null;
_pendingList.Clear(); // _대기중인 버퍼가 다 처리되었으니까
OnSend(_sendArgs.BytesTransferred);
if (_sendQueue.Count > 0)
RegisterSend();
}
catch (Exception ex)
{
Console.WriteLine($"OnSendCompleted Failed {ex}");
}
}
else
Disconnect();
}
}
void RegisterRecv()
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if (pending == false)
OnRecvCompleted(null, _recvArgs);
}
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
RegisterRecv();
}
catch (Exception ex)
{
Console.WriteLine($"(OnRecvCompleted Failed {ex}");
}
}
else
{
Disconnect();
}
}
}
#endregion
public class Listener
{
Socket _listenSocket;
Func<Session> _sessionFactory; // 세션을 어떤 방식으로 누구를 만들어줄지
public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
{
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory += sessionFactory;
_listenSocket.Bind(endPoint);
_listenSocket.Listen(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)
{
Session session = _sessionFactory.Invoke();
session.Start(args.AcceptSocket);
// 현재 줄에서 클라이언트가 접속을 끊어버리면 에러 발생
session.OnConnected(args.AcceptSocket.RemoteEndPoint);
}
else
Console.WriteLine(args.SocketError.ToString());
RegisterAccept(args);
}
}
public class Connector
{
Func<Session> _sessionFactory;
public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
{
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory = sessionFactory;
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += OnConnectCompleted;
args.RemoteEndPoint = endPoint;
args.UserToken = socket;
RegisterConnect(args);
}
void RegisterConnect(SocketAsyncEventArgs args)
{
Socket socket = args.UserToken as Socket; // 유저토큰으로 넘겨주는 이유 : Connect를 여러명이 호출할 수 있어서
if (socket == null)
return;
bool pending = socket.ConnectAsync(args);
if (pending == false)
OnConnectCompleted(null, args);
}
void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError == SocketError.Success)
{
Session session = _sessionFactory.Invoke(); // 컨텐츠단에서 요구한 방식대로 세션 만들어준다.
session.Start(args.ConnectSocket);
session.OnConnected(args.RemoteEndPoint);
}
else
{
Console.WriteLine($"OnConnectCompleted fail! : {args.SocketError}");
}
}
}
class GameSession : Session
{
// Listener.OnAcceptompleted() 에서 호출
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
byte[] sendBuff = Encoding.UTF8.GetBytes("저희 서버에 오신것을 환영합니다!!");
Send(sendBuff);
Thread.Sleep(1000);
Disconnect();
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Client] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes : {numOfBytes}");
}
}
class Program
{
static Listener _listener = new Listener();
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
// 컨텐츠단에서 세션을 만들어주는게 아니라 세션 종류만 결정해줌
_listener.Init(endPoint, () => { return new GameSession(); });
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
class GameSession : Session
{
// Listener.OnAcceptompleted() 에서 호출
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
for (int i = 0; i < 5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World{i} ");
Send(sendBuff);
}
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Server] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes : {numOfBytes}");
}
}
class Program
{
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);
Connector connector = new Connector();
connector.Connect(endPoint, () => { return new GameSession(); });
while (true)
{
try
{}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Thread.Sleep(100);
}
}
}
안녕하세요 글 잘 보고 갑니다!! 실례가 안된다면 혹시 주석을 달아주실 수 있으신가요....?