namespace ServerCore
{
internal class Session
{
Socket _socket;
int _disconnected = 0;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false;
object _lock = new object();
public void Start(Socket socket)
{
_socket = socket;
// 연결된 클라이언트에서 메시지가 올 경우 호출될 함수 등록
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv(recvArgs);
}
// Send의 경우는 언제 메시지를 보낼지 모르니
// 원하는 타이밍에 호출되는 함수
public void Send(byte[] sendBuff)
{
lock (_lock) // 동시에 Send 호출 금지!
{
_sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
}
// 여러 클라이언트 쓰레드가 동시에 disconnect하거나 두번연속 끊으면 문제이기 때문에 락
public void Disconnect()
{
// 리턴 1이면 누군가 이미 Disconnected했다는 말
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
// 내부에서만 사용
#region 네트워크 통신
// 누군가 RegisterSend하고 있으면 Send에서 RegisterSend() 호출하는게 아니라 큐에다만 쌓아놓는다.
void RegisterSend()
{
_pending = true;
byte[] buff = _sendQueue.Dequeue();
_sendArgs.SetBuffer(buff, 0, buff.Length);
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
// Send 함수에서 뿐만 아니라 콜백으로 호출될 수 있기 때문에 lock 처리
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// Send -> RegisterSend에서 pending이 false라 OnSendCompleted실행 안되고 Send에서 lock을 빠져나왔는데(_pending = true인 상태)
// 다른 놈이 Send를 호출해서 큐에다 메시지 넣은 후 바로 OnSendCompleted가 호출되었다면
// Count는 현재 1인 상태일 것이다. _pending이 true이기 때문에 RegisterSend()는 호출하지 못했기 때문에
// 여기서 해준다.
if (_sendQueue.Count > 0)
RegisterSend();
else
_pending = false;
}
catch (Exception ex)
{
Console.WriteLine($"OnSendCompleted Failed {ex}");
}
}
else
Disconnect();
}
}
void RegisterRecv(SocketAsyncEventArgs args)
{
// 클라이언트로부터 메시지 받으면 pending = false
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnRecvCompleted(null, args);
}
// 클라이언트로부터 메시지가 오면 뺀 후 출력
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success) // 상대방이 연결을 끊는 등 0이 올 수 있다.
{
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch (Exception ex)
{
Console.WriteLine($"(OnRecvCompleted Failed {ex}");
}
}
else
{
Disconnect();
}
}
}
#endregion
}