<Session #1>
1. 이전 시간 의문
a) 만약 RegisterAccept에서 계속 pending이 false면 OnAcceptCompleted 함수와 RegisterAccept 함수가 재귀적으로 호출되어 스택오버플로우 발생?
- ListenSocket의 Listen에서 backlog를 통해 최대 대기수를 정해줬기때문에 동시다발적으로 false만 뜰 수 없음
-> 의도적으로 많은 User들이 공격하지 않는 이상 발생할 수 없음
b) Listen Socket을 하나만 만들었다. -> 굉장히 많은 User가 게임에 들어와서 서버와 통신을 하려고 할때 어떻게?
- SocketAsyncEventArgs를 여러개 만들 수 있다. -> SocketAsyncEventArgs는 독립적으로 실행되기때문에 아래와 같이 for 문으로 여러개 만들 수 있다. (Init 함수에서)
for(int i = 0; i < 10; i++)
{
// 초기화 시에 이벤트 등록
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptComplete);
RegisterAccept(args); // 최초 한번은 args를 등록
}
c) 프로그램을 실행하면, main 함수의 While문을 계속 돌고 있어야하는데 어떻게 콜백으로 OnAcceptComplete 함수를 실행시키는가?
- OnAcceptComplete 함수의 if문에 breakpoint를 두고 debug를 하면 스레드가 주 스레드와 작업 스레드로 나뉘게 된다.
-> 비동기적으로 수행할 경우, 자동적으로 Task Pool에서 Task를 가져와 각자 실행된다는 것을 알 수 있다.
-> 만약 주 스레드와 Task 스레드가 같은 데이터를 건들게 될 경우 Race Condition이 발생할 수 있다 (OnAcceptComplete 함수 부분이 레드 존, Danger 존이 된다. )
* 멀티 스레드로 실행되기 때문에
2. Receive 부분 빼기
: Receive 부분도 Listen과 같이 비동기 방식으로 바꿔준다.
1) SeverCore프로젝트 우클릭 -> 새항목 추가 -> Session.cs 만들기: Session 클래스를 만든다.
- Init 함수의 인자로 socket을 받는다.
- 이전시간과 마찬가지로 비동기 방식에서는 RegisterRecv와 OnRecvCompleted의 두가지 함수가 필요하다
2) Receive에서도 AcceptAsync처럼 ReceiveAsync가 있다.
- 마찬가지로 SocketAsyncEventArgs를 Init 함수에 선언하고 EventHandler로 OnRecvCompleted를 콜백으로 받도록한다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace SeverCore
{
class Session
{
Socket _socket;
public void Init(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
}
// 비동기는 두단계로 나눠짐
void RegisterRecv(SocketAsyncEventArgs args)
{
_socket.ReceiveAsync(args);
}
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
}
}
}
3) Receive 할때는 메세지를 받아서 저장할 Buffer가 필요했었다.
- 아래의 recvBuff가 필요: recvArgs.SetBuffer 함수를 통해 버퍼를 설정한다.
- 후에 마찬가지로 RegisterRecv를 등록한다.
// 받는다.
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
public void Init(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
4) RegisterRecv 함수에서는 마찬가지로 ReceiveAsync 함수의 반환값을 bool으로 받는다.
- pending 값이 false이면 기다릴 필요없이 바로 받을 메세지가 왔다는 것을 뜻한다.
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false) // pending이 false이면 기다릴필요없이 바로 메세지를 받을 수 있음
OnRecvCompleted(null, args);
}
5) OnRecvCompleted 함수에서 버퍼의 값을 가져온다
a) args.BytesTransferred: 클라이언트로 가져온 Bytes의 수로 0을 넘는지 확인 -> 클라이언트가 예기치 못하게 연결을 끊는 경우 0바이트로 통신이 오기때문에 해당 값이 0이 넘어야 정상 작동
b) 그리고 SocketError가 없을 시에만 버퍼의 값을 Encoding 한다.
- 이때, args에 Buffer, Offset, BytesTransferred의 내용이 다 담겼으므로 해당 사항을 이용
c) 후 다시 RegisterRecv를 등록한다.
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// 성공 -> 버퍼의값을 받는다.
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed{e}");
}
}
else
{
// TO DO Disconnect
}
}
6) Send 하는 부분은 어렵기 때문에 그냥 간단하게 blocking 방식으로 우선 코딩한다.
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
7) Disconnect 부분도 이전과 같이 Shutdown과 Close를 사용
public void Disconnect()
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
* Session.cs의 전체 코드
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace SeverCore
{
class Session
{
Socket _socket;
public void Init(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크통신
// 비동기는 두단계로 나눠짐
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false) // pending이 false이면 기다릴필요없이 바로 메세지를 받을 수 있음
OnRecvCompleted(null, args);
}
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// 성공 -> 버퍼의값을 받는다.
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed{e}");
}
}
else
{
// TO DO Disconnect
}
}
#endregion
}
}
3. ServerCore의 Program.cs 부분 수정
1) 아래와 같이 수정한다.
2) Socket의 Accept을 하여 클라이언트와 Connect되면 Session 객체를 만든다.
* Init -> Start로 이름 변경
3) Session.Start를 하면 비동기적으로 Receive 가능
- Send와 Disconnect는 아직까지 Blocking 함수를 사용하므로 따로 작성해준다.
static void OnAcceptHandler(Socket clientSocket)
{
try
{
Session session = new Session();
session.Start(clientSocket);
// SendBuff만 남겨둠
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
session.Send(sendBuff);
Thread.Sleep(1000);
session.Disconnect();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
4. Dummy Client의 Program.cs 수정
: for 문으로 5번 정도 send하도록 한다.
for(int i = 0; i < 5; i++)
{
// 보낸다.
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
int sendBytes = socket.Send(sendBuff);
}
5. 실행
: 위의 코드로 실행하게 되면 Client와 Sever가 서로 잘 주고 받는다.
6. Disconnect 문제
: OnRecvCompleted 함수는 인자로만 이뤄져서 코드를 짜기 때문에 문제될 부분이 없으나 멀티스레드 환경에서 동시에 여러번 Disconnect 함수가 발생할 수 있다.
1) Program.cs에서 두번 Session.Disconnect를 하게 되면 error 발생
session.Disconnect();
session.Disconnect();
2) Session에서 flag로 현재 Session이 끊겼는지 여부 확인
a) 멀티스레드 환경이기 때문에 Interlocked를 이용하여 원자적으로 flag 변경
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SeverCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크통신
// 비동기는 두단계로 나눠짐
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false) // pending이 false이면 기다릴필요없이 바로 메세지를 받을 수 있음
OnRecvCompleted(null, args);
}
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// 성공 -> 버퍼의값을 받는다.
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed{e}");
}
}
else
{
// TO DO Disconnect
}
}
#endregion
}
}
b) 이렇게 하면 Disconnect를 두번하여도 문제없이 동작한다.
** 중간 이해
Listen - Sever ------------------------------------> Client
Receive - <------------------------------------
1) 서버와 클라이언트가 소켓 통신을 한다.
2) 현재까지는 비동기적으로 Listen과 Receive를 함
3) 서버에서는 메세지를 Receive 하고 메세지를 Send한 후 통신을 Disconnect하고 Client에서는 while문으로 계속 메세지를 Send하고 Receive한다. (반대로)
4) 이때 서버가 이미 disconnect했는데 또 한번 Disconnect하면 문제 발생
<Session #2>
1. 위의 코드에서 Receive Event(SocketAsynEventArgs를 하나만 선언하고 RegisterRecv에 할당)가 하나 밖에 없기 때문에 OnRecvCompleted 함수에 하나의 스레드밖에 들어올 수 없음.
-> But, Send는 다르다.
2. Receive와 기본적인 구조는 동일하게 코딩한다.
-> Receive는 클라이언트가 메세지를 보낼때 실행이되지만, Send는 메세지를 보낼 시점을 알 수 없다. (미래를 예측하여 Send할 수 없다.)
// Send
void RegisterSend(SocketAsyncEventArgs args)
{
bool pending = _socket.SendAsync(args);
if (pending == false)
OnSendCompleted(null, args);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
}
catch(Exception e)
{
Console.WriteLine($"OnSendCompleted Failed{e}");
}
}
else
{
Disconnect();
}
}
3. Send하는 시점에 SocketyAsyncEventArgs가 RegisterSend에 등록이 되어야함
1) 따라서, Start(초기화)함수가 아니라 Send 함수에 코드를 넣어줘야한다.
- Buffer 설정만 sendBuff 인자로 한다.
public void Send(byte[] sendBuff)
{
//_socket.Send(sendBuff);
SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
// Buffer 설정
sendArgs.SetBuffer(sendBuff, 0 , sendBuff.Length);
RegisterSend(sendArgs);
}
2) Receive했을 때처럼 OnRecvComplete 함수에서 버퍼값을 완전히 받은 후(완료한 후), RegisterRecv로 다시 던져줄 수 없다.
a) Send 함수가 호출된 시점에 Socket Event가 연결되기 때문에 OnSendCompleted에서 try문에 RegisterSend(args)를 하면 이미 Send한 메세지에 대한 args에 대해 등록하는 것
-> Args를 재사용할 수 없다.
b) 물론 위의 코드로 실행하면 이전처럼 실행은 됨: session.Send 함수 호출할때마다 SocketAsyncEventArgs가 생성되고 RegisterSend(sendArgs)로 등록면서 바로 보내진다. (Send 함수가 호출됐다는 것이 Send Event가 발생했다는 것을 의미 SetBuffer에서 해당 글자만큼 버퍼를 설정하면 보내면됨 * 딱히 콜백으로 OnSendCompleted 함수에서 할 것이 없다.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SeverCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
//_socket.Send(sendBuff);
SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
// Buffer 설정
sendArgs.SetBuffer(sendBuff, 0 , sendBuff.Length);
RegisterSend(sendArgs);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크통신
// 비동기는 두단계로 나눠짐
// Send
void RegisterSend(SocketAsyncEventArgs args)
{
bool pending = _socket.SendAsync(args);
if (pending == false)
OnSendCompleted(null, args);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
}
catch(Exception e)
{
Console.WriteLine($"OnSendCompleted Failed{e}");
}
}
else
{
Disconnect();
}
}
// Receive
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false) // pending이 false이면 기다릴필요없이 바로 메세지를 받을 수 있음
OnRecvCompleted(null, args);
}
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// 성공 -> 버퍼의값을 받는다.
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed{e}");
}
}
else
{
// TO DO Disconnect
Disconnect();
}
}
#endregion
}
}
3) Send를 많이 호출하는데 SendAsync로 하면 부화가 일어난다
- User1이 왼쪽으로 움직이면, 서버는 나머지 모든 유저들에게 왼쪽으로 움직였다는 메세지를 줘야한다. + 다른 유저들도 스킬과 위치 이동등 다양한 기술을 사용. 그럴때마다 SendAsync를 사용하면 부화 발생
4. 해결 방안
1) SocketAsyncEventArgs _sendArgs를 아래와 같이 클래스의 멤버 변수로 선언한 후, Srtart에서 EventHandler를 추가한다.
2) Send 함수에 SetBuffer로 Buffer만 설정하고 RegisterSend로 등록한다.
* 클래스내의 멤버변수 _sendArgs이기 때문에 RegisterSend의 인자로 넘겨주지 않아도됨
class Session
{
Socket _socket;
int _disconnected = 0;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
//_socket.Send(sendBuff);
// Buffer 설정
_sendArgs.SetBuffer(sendBuff, 0 , sendBuff.Length);
RegisterSend();
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크통신
// 비동기는 두단계로 나눠짐
// Send
void RegisterSend()
{
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
3) Send 함수에서 매번마다 Register를 하는 것이 아니라 Queue에다가 데이터를 차곡 차곡 쌓은 후에 OnSendComplete 함수가 완료된 후에 한번에 보내도록 하는 방식
- OnSendComplete가 완료되기 전까지는 Queue에다가 쌓아놓는다.
a) Session의 클래스 멤버로 Queue와 bool pending을 선언한다.
- _pending은 누가 Register를 하고 있으면 True로 하고 OnSendComplete를 완료한 후에 False로 바꾼다.
- Send에서 _pending 값이 false이면 RegisterSend를 등록한다. (_peding이 true이면 누가 아직 보내고 있다는 뜻으로 Queue에다가 쌓기만 한다.)
- 이때 여러 스레드가 Send 함수에 접근할 수 있기 때문에 lock을 걸어준다.
-> 한번에 한명씩만
class Session
{
Socket _socket;
int _disconnected = 0;
object _lock = new object();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false; // 누가 RegisterSend를 하고 있으면 True, OnSendComplete 완료 후에 False로 바꾼다: Send에서 누가 Registr를 사용하고 있으면 Queue에다가 저장만 하겠다.
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
}
b) RegisterSend 함수
- 보내려는 중이라는 것을 알려야하기 때문에 _pending = true로 변경한다.
- _sendQueue에서 Dequeue()로 byte를 꺼내온 후 _sendArgs.SetBuffer로 꺼내온 byte에 대한 정보로 버퍼를 설정한다.
- 그 이후 SendAsync로 비동기적으로 송신한다. 이때, pending이 false (Send할것이 없으면) 바로 OnSendCompleted를 하여 보내고 true면 비동기적으로 나중에 송신을 하겠다는 것을 의미
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);
}
c) OnSendCompleted 함수
- Send가 완료된 후에 콜백으로 OnSendCompleted 함수가 실행됨
-> 완료됨을 알리기 위해 _pending = false로 변경
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
_pending = false;
}
catch(Exception e)
{
Console.WriteLine($"OnSendCompleted Failed{e}");
}
}
else
{
Disconnect();
}
}
- RegisterSend와 콜백 두가지 경우로 OnSendCompleted가 호출되기 때문에 lock으로 묶어준다.
- 현재 누군가가 RegisterSend를 등록하여 _pending이 true인 상태에서 어떤 스레드가 Send를 호출하였는데 _pending이 true여서 RegisterSend를 못할 수도 있다. (Enqueue에만 쌓인다.)
-> OnSendComplete 함수에서 내가 보내고 있는 사이에 Queue가 쌓이면 다시 RegiseterSend를 해주면 더 우아하게 코딩 가능
* 위의 작업이 없으면 계속하여 Queue에 쌓인다.
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
if(_sendQueue.Count > 0)
{
RegisterSend();
}
else
_pending = false;
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed{e}");
}
}
else
{
Disconnect();
}
}
}
=> 주목적: sendArgs 재사용 & 누가 보내고 있는 작업을 완료하지않았다면 Queue에 쌓이도록
* 물론 여전히 Send를 100번 하면 SendAsync를 100번해야하는 문제가 있다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SeverCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
object _lock = new object();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false; // 누가 RegisterSend를 하고 있으면 True, OnSendComplete 완료 후에 False로 바꾼다: Send에서 누가 Registr를 사용하고 있으면 Queue에다가 저장만 하겠다.
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// Recv Buffer 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크통신
// 비동기는 두단계로 나눠짐
// Send
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);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
if(_sendQueue.Count > 0)
{
RegisterSend();
}
else
_pending = false;
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed{e}");
}
}
else
{
Disconnect();
}
}
}
// Receive
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false) // pending이 false이면 기다릴필요없이 바로 메세지를 받을 수 있음
OnRecvCompleted(null, args);
}
void OnRecvCompleted(Object sender, SocketAsyncEventArgs args)
{
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// 성공 -> 버퍼의값을 받는다.
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed{e}");
}
}
else
{
// TO DO Disconnect
Disconnect();
}
}
#endregion
}
}
'Development > 게임 서버' 카테고리의 다른 글
[Part 4 게임 서버] 네트워크 프로그래밍 5(Connector, TCP vs UDP) (0) | 2022.01.19 |
---|---|
[Part 4 게임 서버 ] 네트워크 프로그래밍 4 (Session #3, Session #4) (0) | 2022.01.18 |
[Part 4 게임 서버] 네트워크 프로그래밍 2 (Listener) (0) | 2022.01.16 |
[Part 4 게임 서버] 네트워크 프로그래밍 1(네트워크 기초 이론 ~ 소켓 프로그래밍 입문 #2 (0) | 2022.01.14 |
[Part 4 게임 서버 ] 멀티쓰레드 프로그래밍 6(ReaderWriterLock ~ Thread Local Storage) (0) | 2022.01.13 |