Receive코드 알아보러 가기
네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Receive
Listener코드 알아보러 가기 네트워크프로그래밍 - C# 간단한 Non - blocking 사용한 Server 소켓프로그래밍 구현 blocking방식 이용한 Server코드 네트워크 프로그래밍 - C# 간단한 Server 소켓프로그래밍 구현
code-piggy.tistory.com
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 Faield {e}");
}
}
else
{
Disconnect();
}
}
코드의 틀은 Receive와 비슷하다.하지만 Send하는 시점은 정해져 있지 않다.
Receive같은 경우에는 Start할때 한 번만 예약 해놓고 Client측에서 메시지를 전송하면은 Receive가 완료되면서 OnRecvCompleted가 자동으로 호출된다.
public void Start(Socket socket)
{
RegisterRecv(recvArgs);
}
Send같은 경우에는 보내줄 버퍼와 메시지를 같이 설정해주어야하는데 미래에 어떤 메시지를 보낼지 알고 예약을 할 수가 없으니 불가능하다.
그러므로 Receive와 다르게 접근해야 한다.
원래 있었던 Send부분을 수정하자
원래 있었던 Send코드
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
01 Send안에서 RegisterSend을 실행하도록 수정
public void Send(byte[] sendBuff)
{
SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
sendArgs.SetBuffer(sendBuff,0,sendBuff.Length);
RegisterSend(sendArgs);
}
Receive와 비교를 해보자면 OnRecvCompleted이 호출이 되면 RegisterRecv(args)를 통해 다시 등록을 해주었다..
하지만 Send같은 경우 OnSendCompleted이 호출되고 다시 등록하는게 불필요한데 그 이유는 Send에서 sendBuff(보내고 싶은 정보)를 보내게 되면 똑같은 정보를 다시 보내게 되는 것이기 때문이다. 그러므로 재사용이 불가능하다.
문제1 이벤트가 생길때마다 다시 만들면서 보내고있으니 재사용 불가 상태
SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
sendArgs.SetBuffer(sendBuff,0,sendBuff.Length);
멀티쓰레드 환경에서 동시다발적으로 send를 한다고 가정하면 SendAysnc함수가 아파지는것은 아닌데 재사용을 할 수 없다는 문제가 발생한다.
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
}
}
}
일반적으로 OnSendCompleted가 됐다고 해서 receive 같은 경우 따로 등록을 해줬는데 Send 같은 경우 성공한 경우에는 사실 설정할 이벤트가 마땅히 없다.
문제2 RegisterSend를 매번 다시하고 있다.
RegisterSend(sendArgs);
만약에 send를 1000명의 유저가 있다고 하면 어떤 A라는 유저가 움직였다고하면 주변의 모든 유저에게 loop를 돌면서 모든 유저에게 A라는 유저가 움직였다는 정보를 보내준다. 더 큰 문제는 A유저만 움직이는 것이 아니라 모든 유저가 움직이므로 Send가 호출되는것이 되게 많아질 것이다.
그러면 SendAsync가 매번 호출될텐데 그러면 네트워크 과부하가 걸릴것이다. 운영체제가 커널에서 처리하기 때문에 막 쓰는것이 큰 문제가 된다.
이를 해결하기위해 매번 보내는 것이아니라 뭉쳐서 보내는 방법은 없을까?
해결책1 이벤트 핸들러 재사용
Session 클래스 내에서 이벤트 핸들러를 등록하면 여러 번 Send메서드를 호출할 때마다 매번 이벤트를 등록할 필요가 없어진다. 그러므로 재사용할 수 있게 만들 수 있다.
class Session
{
Socket _socket;
int _disconnected = 0;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
}
public void Start(Socket socket)
{
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterSend();
}
void RegisterSend()
{
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
_sendArgs변수가 이미 Session클래스의 멤버로 선어되었기 때문에 RegisterSend에서 별도의 매개변수가 필요하지 않게 되었다.
해결책2 queue에 차곡차곡 모아서 어느 정도 모았으면 그때 보내기
01 Queue만들기
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false;
pending이 false인 경우
Queue에 아무것도 등록이 되지 않은 상태
pending이 true인 경우
Queue에 등록이 되어있는 상태
02 Quene에 등록하기
싱글쓰레드 버젼
public void Send(byte[] sendBuff)
{
_sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
멀티쓰레드 버젼
lock을 이용해 한번에 한번씩만 들어오게 하기
object _lock = new object();
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
}
03 Queue에 순차적으로 담고 다 찼으면 OnSendCompleted 호출하기
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);
}
1) 다음에 Send되는애가 Register안돼고 바로 queue에 담겨지기 위해서 _pending = true로 설정한다.
_pending = true;
2) _sendQueue에서 큐에서 첫 번째로 들어온 데이터를 가져온다.
byte[] buff = _sendQueue.Dequeue();
3) _sendArgs에 전송할 데이터의 정보 설정
_sendArgs.SetBuffer(buff, 0, buff.Length);
4) 작업의 완료 여부를 pending에 저장
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
Send.Async메서드가 호출되면 비동기작업이 시작된다.
pending이 true인 경우
작업이 완료되지 않아서 현재 대기 중인 상태
pending이 false인 경우
작업이 즉시 완료된 상태
04 전송완료결과 처리
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 Faield {e}");
}
}
else
{
Disconnect();
}
}
}
Queue에 보내야할 데이터가 남아 있는 경우
if(_sendQueue.Count > 0)
{
RegisterSend(); // 다음 전송을 위해서 RegisterSend을 호출한다
}
Queue에 보내야할 데이터가 남아 있지 않은 경우
else
_pending = false; // 작업이 완료되었음을 나타낸다.
개선된 Send코드 보러가기
네트워크 프로그래밍 -Send코드 개선하기
Send 코드 참조 네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Send Receive코드 알아보러 가기 네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Receive Li
code-piggy.tistory.com
참고 : 본 내용은 MMORPG PART4 강의를 수강하여 작성하였습니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
'cs공부 > 네트워크프로그래밍' 카테고리의 다른 글
네트워크프로그래밍 - Session부분과 Listener부분 분리하기(엔진과 컨텐츠 분리하기) (0) | 2023.07.14 |
---|---|
네트워크 프로그래밍 -Send코드 개선하기 (0) | 2023.07.13 |
네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Receive (0) | 2023.07.03 |
네트워크 프로그래밍 - non-blocking code에 대해 더 알아보기 (0) | 2023.07.03 |
네트워크프로그래밍 - blocking(send등), non-blocking(BeiginSend,SocketAsyncEventArgs등) (0) | 2023.06.21 |
댓글