상세 컨텐츠

본문 제목

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

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

by 성댕쓰 2021. 12. 8. 21:34

본문

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)

관련글 더보기

댓글 영역