이벤트 핸들러를 통해 수신된 데이터를 처리해보자
우선 아래는 현재까지 완성된 코드이다.
// ServerCore
// Session.cs
class Session
{
Socket _socket;
int _disconnected = 0;
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
Queue<byte[]> _sendQueue = new Queu<byte[]>();
bool _pending = false;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
object _lock = new object();
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvComplete);
_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;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
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 RegisterRecv()
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if(pending == false)
OnRecvComplete(null, _recvArgs);
}
void OnRecvComplete(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
{
try{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
RegisterRecv();
}
catch()
{
}
}
else
{
Disconnect();
}
}
void OnSendComplete(object sender, SocketAsyncEventArgs args)
{
lock(_lock)
{
if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
{
try{
_sendArgs.BufferList = null;
_pendingList.Clear();
if(_sendQueue.Count > 0)
RegisterSend();
}
catch()
{
}
}
else
{
Disconnect();
}
}
}
}
송신/수신 완료 연결, 연결해제 처리를 위해 아래의 네 함수의 정의가 필요하다
// 상속으로 처리하기 위해 Session 클래스 자체를 abstract로 만든다
abstract class Session
{
// ...
// 상속을 통해 처리해보자
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);
// ...
// 엔진과 컨텐츠를 분리하는 작업이라 생각하면 된다
}
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
}
public override void OnSend(int numOfBytes)
{
}
public override void OnDisconnected(EndPoint endPoint)
{
}
}
// ServerCore
// Session.cs
class Session
{
Socket _socket;
int _disconnected = 0;
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
Queue<byte[]> _sendQueue = new Queu<byte[]>();
bool _pending = false;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
object _lock = new object();
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvComplete);
_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;
// abstract 함수 호출 시점
OnDisconnected(_socket.RemoteEndPoint);
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
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 RegisterRecv()
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if(pending == false)
OnRecvComplete(null, _recvArgs);
}
void OnRecvComplete(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
{
try{
// abstract 함수 호출 시점
OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
RegisterRecv();
}
catch()
{
}
}
else
{
Disconnect();
}
}
void OnSendComplete(object sender, SocketAsyncEventArgs args)
{
lock(_lock)
{
if(args.BytesTransferred > 0 && args.SocketError ==SocketError.Success)
{
try{
_sendArgs.BufferList = null;
_pendingList.Clear();
// abstract 함수 호출 시점
OnSend(_sendArgs.BytesTransferred);
if(_sendQueue.Count > 0)
RegisterSend();
}
catch()
{
}
}
else
{
Disconnect();
}
}
}
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
// Session을 Listener에서 만들자
// 여기서 다시 문제점은 GameSession을 Listener(ServerCore)에서는
// Gamesession일지 ClientSession일지 모름
// 컨텐츠단에서 session을 만들고 전달하는 방식으로 해야한다
GameSession session = new GameSession();
session.Start(args.AcceptSocket);
session.OnConnected(args.AcceptSocket.RemoteEndPoint);
}
else
{
Console.WriteLine(args.SocketError.ToString());
}
RegisterAccept(args);
}
개선해보자
public class Listener
{
Socket _listenSocket;
// Session을 컨텐츠단에서 만들어 전달받기위함
Func<Session> _sessionFactory;
public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
{
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
/* TCP로 사용하겠다는 선언 */
// session을 전달받는다
_sessionFactory += sessionFactory;
_listenSocket.Bind(endPoint);
_listenSocket.Listen(10);
// 10 : 최대 대기수, 초과시 입장을 제한한다
// for(int i = 0; i < 10; i++) // 이렇게 리스너를 여러개 두어 동시에 여러 유저의 입장을 처리가능
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(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);
}
// ...