Connect와 Disconnect 로직을 Session에 구현해보자.
먼저 Discoonect 이벤트를 정의한다.
IocpEvent.h
enum class EventType : uint8
{
Connect,
Disconnect,
Accept,
//PreRecv,
Recv,
Send
};
...
/*-----------------
DisconnectEvent
-----------------*/
class DisconnectEvent : public IocpEvent
{
public:
DisconnectEvent() : IocpEvent(EventType::Disconnect) {}
};
...
Connect, Disconnect를 정의한다.
Session.h
...
class Session : public IocpObject
{
...
public:
Session();
virtual ~Session();
public:
/* 외부에서 사용 */
void Send(BYTE* buffer, int32 len);
bool Connect();
void Disconnect(const WCHAR* cause);
...
private:
/* 전송 관련 */
bool RegisterConnect();
bool RegisterDisconnect();
void RegisterRecv();
void RegisterSend(SendEvent* sendEvent);
void ProcessConnect();
void ProcessDisconnect();
...
private:
/* IocpEvent 재사용 */
ConnectEvent _connectEvent;
DisconnectEvent _disconnectEvent;
RecvEvent _recvEvent;
};
Session.cpp
...
bool Session::Connect()
{
return RegisterConnect();
}
void Session::Disconnect(const WCHAR* cause)
{
if (_connected.exchange(false) == false)
return;
// TEMP
wcout << "Disconnect : " << cause << endl;
OnDisconnected(); // 컨텐츠 코드에서 오버라이딩
GetService()->ReleaseSession(GetSessionRef());
RegisterDisconnect();
}
...
void Session::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
{
switch (iocpEvent->eventType)
{
case EventType::Connect:
ProcessConnect();
break;
case EventType::Disconnect:
ProcessDisconnect();
break;
case EventType::Recv:
ProcessRecv(numOfBytes);
break;
case EventType::Send:
ProcessSend(static_cast<SendEvent*>(iocpEvent), numOfBytes);
break;
}
}
bool Session::RegisterConnect()
{
if (IsConnected())
return false;
if (GetService()->GetServiceType() != ServiceType::Client)
return false;
if (SocketUtils::SetReuseAddress(_socket, true) == false)
return false;
if (SocketUtils::BindAnyAddress(_socket, 0/*남는거*/) == false)
return false;
_connectEvent.Init();
_connectEvent.owner = shared_from_this(); // ADD_REF
DWORD numOfBytes = 0;
SOCKADDR_IN sockAddr = GetService()->GetNetAddress().GetSockAddr();
if (false == SocketUtils::ConnectEx(_socket, reinterpret_cast<SOCKADDR*>(&sockAddr), sizeof(sockAddr), nullptr, 0, &numOfBytes, &_connectEvent))
{
int32 errorCode = ::WSAGetLastError();
if (errorCode != WSA_IO_PENDING)
{
_connectEvent.owner = nullptr; // RELEASE_REF
return false;
}
}
return true;
}
bool Session::RegisterDisconnect()
{
_disconnectEvent.Init();
_disconnectEvent.owner = shared_from_this(); // ADD_REF
if (false == SocketUtils::DisconnectEx(_socket, &_disconnectEvent, TF_REUSE_SOCKET, 0))
{
int32 errorCode = ::WSAGetLastError();
if (errorCode != WSA_IO_PENDING)
{
_disconnectEvent.owner = nullptr; // RELEASE_REF
return false;
}
}
return false;
}
void Session::ProcessConnect()
{
_connectEvent.owner = nullptr; // RELEASE_REF
_connected.store(true);
// 세션 등록
GetService()->AddSession(GetSessionRef());
// 컨텐츠 코드에서 오버라이딩
OnConnected();
// 수신 등록
RegisterRecv();
}
void Session::ProcessDisconnect()
{
_disconnectEvent.owner = nullptr; // RELEASE_REF
}
...
Client에서 Server로 connect하고 서로 send, receive를 반복하는 테스트 코드를 작성해보자.
Types.h
...
using ClientServiceRef = std::shared_ptr<class ClientService>;
...
GameServer.cpp
#include "pch.h"
#include "ThreadManager.h"
#include "Service.h"
#include "Session.h"
class GameSession : public Session
{
public:
~GameSession()
{
cout << "~GameSession" << endl;
}
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();
}
DummyClient.cpp
#include "pch.h"
#include "ThreadManager.h"
#include "Service.h"
#include "Session.h"
char sendBuffer[] = "Hello World";
class ServerSession : public Session
{
public:
~ServerSession()
{
cout << "~ServerSession" << endl;
}
virtual void OnConnected() override
{
cout << "Connected To Server" << endl;
Send((BYTE*)sendBuffer, sizeof(sendBuffer));
}
virtual int32 OnRecv(BYTE* buffer, int32 len) override
{
//Echo
cout << "OnRecv Len = " << len << endl;
this_thread::sleep_for(1s);
Send((BYTE*)sendBuffer, sizeof(sendBuffer));
return len;
}
virtual void OnSend(int32 len) override
{
cout << "OnSend Len = " << len << endl;
}
virtual void OnDisconnected() override
{
cout << "Disconnected" << endl;
}
};
int main()
{
this_thread::sleep_for(1s);
ClientServiceRef service = MakeShared<ClientService>(
NetAddress(L"127.0.0.1", 7777),
MakeShared<IocpCore>(),
MakeShared<ServerSession>,
1);
ASSERT_CRASH(service->Start());
for (int32 i = 0; i < 2; i++)
{
GThreadManager->Launch([=]()
{
while (true)
{
service->GetIocpCore()->Dispatch();
}
});
}
GThreadManager->Join();
}
1. Diconnect 비동기 함수를 이용하는 방법이 소켓 닫아주는 것에 비해 장점은?
- 비동기 함수를 이용하면 소켓 재사용한다.
2. bool Session::RegisterConnect() 에서 SOCKADDR_IN sockAddr = GetService()->GetNetAddress().GetSockAddr();
sockAddr의 뜻은?
- 상대방 server address
3. SocketUtils::DisconnectEx(_socket, &_disconnectEvent, TF_REUSE_SOCKET, 0) TF_REUSE_SOCKET 의 의미는?
- 소켓 재사용. 해당 함수 성공적으로 호출하면, 해당 소켓을 AcceptEx or ConnectEx 함수에 재사용 할 수 있음.
참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)
SendBuffer (0) | 2021.12.21 |
---|---|
RecvBuffer (0) | 2021.12.20 |
네트워크 라이브러리 만들기(Session #2) (0) | 2021.12.08 |
네트워크 라이브러리 만들기(Session #1) (0) | 2021.12.06 |
네트워크 라이브러리 만들기(Server Service) (0) | 2021.12.01 |
댓글 영역