Session 클래스에 send 기능을 정의해보자.
Session.h
...
public:
/* 외부에서 사용 */
void Send(BYTE* buffer, int32 len);
...
private:
/* 전송 관련 */
void RegisterConnect();
void RegisterRecv();
void RegisterSend(SendEvent* sendEvent);
...
void ProcessSend(SendEvent* sendEvent, int32 numOfBytes);
void HandleError(int32 errorCode);
...
public:
// TEMP
BYTE _recvBuffer[1000];
...
Session.cpp
...
void Session::Send(BYTE* buffer, int32 len)
{
// 생각할 문제
// 1) 버퍼 관리?
// 2) sendEvent 관리? 단일? 여러개? WSASend 중첩?
// TEMP
SendEvent* sendEvent = xnew<SendEvent>();
sendEvent->owner = shared_from_this(); // ADD_REF
sendEvent->buffer.resize(len);
::memcpy(sendEvent->buffer.data(), buffer, len);
WRITE_LOCK; //WSASend는 thread-safe 하지 않다
RegisterSend(sendEvent);
}
...
void Session::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
{
switch (iocpEvent->eventType)
{
case EventType::Connect:
ProcessConnect();
break;
case EventType::Recv:
ProcessRecv(numOfBytes);
break;
case EventType::Send:
ProcessSend(static_cast<SendEvent*>(iocpEvent), numOfBytes);
break;
}
}
...
void Session::RegisterSend(SendEvent* sendEvent)
{
if (IsConnected() == false)
return;
WSABUF wsaBuf;
wsaBuf.buf = (char*)sendEvent->buffer.data();
wsaBuf.len = (ULONG)sendEvent->buffer.size();
DWORD numOfBytes = 0;
if (SOCKET_ERROR == ::WSASend(_socket, &wsaBuf, 1, OUT & numOfBytes, 0, sendEvent, nullptr))
{
int32 errorCode = ::WSAGetLastError();
if (errorCode != WSA_IO_PENDING)
{
HandleError(errorCode);
sendEvent->owner = nullptr; //RELEASE_REF
xdelete(sendEvent);
}
}
}
...
void Session::ProcessRecv(int32 numOfBytes)
{
_recvEvent.owner = nullptr; //RELEASE_REF
if (numOfBytes == 0)
{
Disconnect(L"Recv 0");
return;
}
// 컨텐츠 코드에서 오버라이딩
OnRecv(_recvBuffer, numOfBytes);
// 수신등록
RegisterRecv();
}
void Session::ProcessSend(SendEvent* sendEvent, int32 numOfBytes)
{
sendEvent->owner = nullptr; // RELEASE_REF
xdelete(sendEvent);
if (numOfBytes == 0)
{
Disconnect(L"Send 0");
return;
}
// 컨텐츠 코드에서 오버라이딩
OnSend(numOfBytes);
}
...
Sendbuffer를 저장하는 임시 공간을 만들자. 추후 수정예정
IocpEvent.h
...
/*-----------------
SendEvent
-----------------*/
class SendEvent : public IocpEvent
{
public:
SendEvent() : IocpEvent(EventType::Send) {}
// TEMP
vector<BYTE> buffer;
private:
};
작업한 기능을 테스트한다.
GameServer.cpp
#include "pch.h"
#include "ThreadManager.h"
#include "SocketUtils.h"
#include "Listener.h"
#include "Service.h"
#include "Session.h"
class GameSession : public Session
{
public:
virtual int32 OnRecv(BYTE* buffer, int32 len) override
{
//Echo
cout << "OnRecv Len = " << len << endl;
Send(buffer, len);
return len;
}
virtual void OnSend(int32 len) override
{
cout << "OnSend Len = " << len << endl;
}
};
int main()
{
ServerServiceRef service = MakeShared<ServerService>(
NetAddress(L"127.0.0.1", 7777),
MakeShared<IocpCore>(),
MakeShared<GameSession>,
100);
ASSERT_CRASH(service->Start());
for (int32 i = 0; i < 5; i++)
{
GThreadManager->Launch([=]()
{
while (true)
{
service->GetIocpCore()->Dispatch();
}
});
}
GThreadManager->Join();
}
라이브러리 제작 틈틈이 메모리 릭 체크도 해주자.
1. Send가 Receive 와 다른 점은 무엇
- Receive는 connect되고 일단 걸어놓고 보지만, Send는 실제 보낼 데이터가 있을 때만 걸어준다.
2. Send의 buffer를 session 멤버나 SendEvent에 멤버로 쓰는 방법?
- 내부 버퍼에 두는 방법은 여러 번 Send를 호출할 때, overwrite 되지 않게 영역을 잘 잡는다.
> 단점 : 복사비용 e.g. 모든 세션에 동일한 데이터 보낼 때, 모든 세션에 복사해야 함.
3. WSASend는 사용할 때 주의할 점?
- Thread Safe 하지 않다. Send할 땐 상관없더라도, queue에서 빼와 받을 때, 호출 순서와 다를 수 있다.
- WSASend 호출하면 내부적으로, 페이징 문제를 해결하기 위한 코스트가 들어간다.
> 작은 데이터를 여러 번 보내기 보다, 한 번에 보내는게 유리함.
참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)
RecvBuffer (0) | 2021.12.20 |
---|---|
네트워크 라이브러리 만들기(Session #3) (0) | 2021.12.13 |
네트워크 라이브러리 만들기(Session #1) (0) | 2021.12.06 |
네트워크 라이브러리 만들기(Server Service) (0) | 2021.12.01 |
네트워크 라이브러리 만들기(Iocp Core) (0) | 2021.11.29 |
댓글 영역