상세 컨텐츠

본문 제목

네트워크 라이브러리 만들기(Session #3)

똑똑한 개발/C++ 게임개발

by 성댕쓰 2021. 12. 13. 22:40

본문

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)

관련글 더보기

댓글 영역