상세 컨텐츠

본문 제목

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

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

by 성댕쓰 2021. 12. 6. 23:05

본문

이제 Listener에서 client session 정보를 세팅하고 receive event를 register하는 부분부터 만들어 보자.

Session.h

#pragma once
#include "IocpCore.h"
#include "IocpEvent.h"
#include "NetAddress.h"

/*----------------
  Session
----------------*/

class Session : public IocpObject
{
    friend class Listener;
    friend class IocpCore;
    friend class Service;

public:
    Session();
    virtual ~Session();

public:
    void Disconnect(const WCHAR* cause);

    shared_ptr<Service> GetService() { return _service.lock(); }
    void SetService(shared_ptr<Service> service) { _service = service; }

public:
    /* 정보 관련 */
    void SetNetAddress(NetAddress address) { _netAddress = address; }
    NetAddress GetAddress() { return _netAddress; }
    SOCKET GetSocket() { return _socket; }
    bool IsConnected() { return _connected; }
    SessionRef GetSessionRef() { return static_pointer_cast<Session>(shared_from_this()); }

private:
    /* 인터페이스 구현 */
    virtual HANDLE GetHandle() override;
    virtual void Dispatch(class IocpEvent* iocpEvent, int32 numOfBytes = 0) override;

private:
    /* 전송 관련 */
    void RegisterConnect();
    void RegisterRecv();
    void RegisterSend();

    void ProcessConnect();
    void ProcessRecv(int32 numOfBytes);
    void ProcessSend(int32 numOfBytes);

    void HandleError(int32 errorCode);

protected:
    /* 컨텐츠 코드에서 오버로딩 */
    virtual void OnConnected() {}
    virtual int32 OnRecv(BYTE* buffer, int32 len) { return len; }
    virtual void OnSend(int32 len) {}
    virtual void OnDisconnected() {}

public:
    // TEMP
    char _recvBuffer[1000];

private:
    weak_ptr<Service> _service;
    SOCKET _socket = INVALID_SOCKET;
    NetAddress _netAddress = {};
    Atomic<bool> _connected = false;

private:
    USE_LOCK;

    /* 수신 관련 */

    /* 송신 관련 */
private:
    /* IocpEvent 재사용 */
    RecvEvent _recvEvent;
};

Session을 만든 Service를 알기위해 Service 멤버변수를 갖는다. 순환참조를 막기 위해 weak_ptr을 사용한다.

 

Session.cpp

#include "pch.h"
#include "Session.h"
#include "SocketUtils.h"
#include "Service.h"

/*----------------
  Session
----------------*/

Session::Session()
{
    _socket = SocketUtils::CreateSocket();
}

Session::~Session()
{
    SocketUtils::Close(_socket);
}

void Session::Disconnect(const WCHAR* cause)
{
    if (_connected.exchange(false) == false)
        return;

    // TEMP
    wcout << "Disconnect : " << cause << endl;

    OnDisconnected(); // 컨텐츠 코드에서 오버라이딩
    SocketUtils::Close(_socket);
    GetService()->ReleaseSession(GetSessionRef());
}

HANDLE Session::GetHandle()
{
    return reinterpret_cast<HANDLE>(_socket);
}

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(numOfBytes);
        break;
    }
}

void Session::RegisterConnect()
{
}

void Session::RegisterRecv()
{
    if (IsConnected() == false)
        return;

    _recvEvent.Init();
    _recvEvent.owner = shared_from_this(); // ADD_REF

    WSABUF wsaBuf;
    wsaBuf.buf = reinterpret_cast<char*>(_recvBuffer);
    wsaBuf.len = len32(_recvBuffer);

    DWORD numOfBytes = 0;
    DWORD flags = 0;
    if (SOCKET_ERROR == ::WSARecv(_socket, &wsaBuf, 1, OUT &numOfBytes, OUT &flags, &_recvEvent, nullptr))
    {
        int32 errorCode = ::WSAGetLastError();
        if (errorCode != WSA_IO_PENDING)
        {
            HandleError(errorCode);
            _recvEvent.owner = nullptr; // RELEASE_REF
        }
    }
}

void Session::RegisterSend()
{
}

void Session::ProcessConnect()
{
    _connected.store(true);

    // 세션 등록
    GetService()->AddSession(GetSessionRef());

    // 컨텐츠 코드에서 오버라이딩
    OnConnected();

    // 수신 등록
    RegisterRecv();
}

void Session::ProcessRecv(int32 numOfBytes)
{
    _recvEvent.owner = nullptr; //RELEASE_REF

    if (numOfBytes == 0)
    {
        Disconnect(L"Recv 0");
        return;
    }

    // TODO
    cout << "Recv Data Len = " << numOfBytes << endl;

    // 수신등록
    RegisterRecv();
}

void Session::ProcessSend(int32 numOfBytes)
{
}

void Session::HandleError(int32 errorCode)
{
    switch (errorCode)
    {
    case WSAECONNRESET:
    case WSAECONNABORTED:
        Disconnect(L"HandleError");
        break;
    default:
        // TODO : Log
        cout << "Handle Error : " << errorCode << endl;
        break;
    }
}

 

Service에서 Create session할 때, session에 service를 등록한다.

Service.cpp

...
SessionRef Service::CreateSession()
{
    SessionRef session = _sessionFactory();
    session->SetService(shared_from_this());

    if (_iocpCore->Register(session) == false)
        return nullptr;

    return session;
}
...

 

Listener에서 Session 이용하여 Receve event를 Reigster 하여 사용한다.

Listener.cpp

...
void Listener::ProcessAccept(AcceptEvent* acceptEvent)
{
    SessionRef session = acceptEvent->session;

    if (false == SocketUtils::SetUpdateAcceptSocket(session->GetSocket(), _socket))
    {
        RegisterAccept(acceptEvent);
        return;
    }

    SOCKADDR_IN sockAddress;
    int32 sizeOfSockAddr = sizeof(sockAddress);
    if (SOCKET_ERROR == ::getpeername(session->GetSocket(), OUT reinterpret_cast<SOCKADDR*>(&sockAddress), &sizeOfSockAddr))
    {
        RegisterAccept(acceptEvent);
        return;
    }

    session->SetNetAddress(NetAddress(sockAddress));

    session->ProcessConnect();

    RegisterAccept(acceptEvent);
}

 

Session이 제대로 소멸되는지 확인해 본다.

Client를 강제 종료 했을 때, Recv 0이 들어오고 소멸자를 부르는지 확인한다. 다른 객체에서 관리하고 있는 Session shared_ptr의 참조 카운트가 0이 되는지 확인한다.

 

순환참조에 대해 정확히 알고 넘어가자.

 

1. listener에서 accept한 session이 처음 호출하는 함수는?

- session::ProcessConnect

2. WSARecv (errorCode != WSA_IO_PENDING) 이 아닐때, session의 refcount를 줄여주는 부분은 어느 함수에 있는지?

- ProcessRecv

3. Disconnect 에서 GetService()->ReleaseSession(GetSessionRef()) 하는 이유는?

- 서비스에서 Session 들을 가지고 있기 때문에, session을 메모리 해제하려면 서비스에서 session 없애줘야 함.

 

참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)

관련글 더보기

댓글 영역