본문 바로가기
개인공부/IOCP 서버 제작 실습

IOCP 서버 제작 실습 4

by 하고싶은건많은놈 2023. 3. 22.

Send의 구조를 1-Send로 변경

  • N-Send : 이전 비동기 Send에 대한 완료를 확인하지 않고 곧바로 다음 Send를 호출
  • 1-Send : 이전 비동기 Send에 대한 완료가 확인되었을 때만 다음 Send를 호출
    따라서 Send할 데이터를 별도의 버퍼에 보관하는 과정이 필요함

 

  • IOCPServer.h
    • stClientInfo 객체를 할당하여 관리하도록 mClientInfos를 vector<stClientInfo*>로 변경
    • StartServer() 함수 호출시 SendThread를 생성하도록 변경
//Send 쓰레드
std::thread					mSendThread;
//Send 쓰레드 동작 플래그
bool						mIsSenderRun = false;
    

void SendThread()
{
    while(mIsSenderRun)
    {
        for (auto client : mClientInfos)
        {
            if (client->IsConnectd()  == false)
            {
                continue;
            }

            client->SendIO();
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(8));
    }
}

 

 

  • ClientInfo.h
    • SendIO() 함수에서 실제로 WSASend() 함수를 통해 Send
      • mSendPos가 0이하일경우 버퍼에 보낼 데이터가 없는 것이므로 리턴
      • mIsSending 플래그가 켜져있는 경우 아직 이전 Send 작업 완료가 확인되지 않은 경우이므로 리턴
        Worker 쓰레드에서 SendCompleted()가 호출되어야 Send 작업 완료가 확인됨
      • 이전 Send 작업 완료가 확인되었고, 정상적으로 보낼 데이터가 존재하는 경우(mSendPos > 0)
        버퍼를 다른 쓰레드들과 공유하므로 lock_guard를 걸어주고, mIsSending 을 true로 바꾸어 Send 작업이 이루어지고있음을 표기
        이후 mSendBuf에 존재하는 데이터를 모두 mSendingBuf에 저장하고 WSASend() 함수를 호출하여 해당 데이터를 Send함
      • WSASend() 함수 호출 후에는 mSendPos를 0으로 초기화하여 Send 작업이 이루어졌음을 표기
    • SendMsg() 함수가 호출된경우 Recv한 데이터가 있는 것이므로 mSendBuf에 해당 데이터를 저장
      이 때도 마찬가지로 다른 쓰레드들과 공유하는 버퍼에 접근하는 것이므로 lock_guard를 걸어줌
      • 현재 mSendBuf에 존재하는 데이터 + 받은 데이터의 크기가 mSendBuf의 크기 이상인 경우 mSendPos를 0으로 변경하여 저장하는 링버퍼 형식이지만, 이 경우 한꺼번에 데이터가 많이 들어올 경우 데이터가 덮어씌워지는 문제가 생길 수 있음
bool SendIO()
{
    if (mSendPos <= 0 || mIsSending)
    {
        return true;
    }

    std::lock_guard<std::mutex> guard(mSendLock);

    mIsSending = true;

    CopyMemory(mSendingBuf, &mSendBuf[0], mSendPos);

    mSendOverlappedEx.m_wsaBuf.len = mSendPos;
    mSendOverlappedEx.m_wsaBuf.buf = &mSendingBuf[0];
    mSendOverlappedEx.m_eOperation = IOOperation::SEND;

    DWORD dwRecvNumBytes = 0;
    if (WSASend(mSock, &(mSendOverlappedEx.m_wsaBuf), 1,
        &dwRecvNumBytes, 0, (LPWSAOVERLAPPED) &(mSendOverlappedEx), NULL) == SOCKET_ERROR
        && (WSAGetLastError() != ERROR_IO_PENDING))
    {
        std::cout << "WSASend() Error : " << WSAGetLastError() << std::endl;
        return false;
    }

    mSendPos = 0;
    return true;
}

void SendCompleted(const UINT32 dataSize_)
{
    mIsSending = false;
    std::cout << "Send Bytes : " << dataSize_ << std::endl;
}

bool SendMsg(const UINT32 dataSize_, char *pMsg_)
{
    std::lock_guard<std::mutex> guard(mSendLock);

    if ((mSendPos + dataSize_) > MAX_SOCK_SENDBUF)
    {
        mSendPos = 0;
    }

    auto pSendBuf = &mSendBuf[mSendPos];

    CopyMemory(pSendBuf, pMsg_, dataSize_);
    mSendPos += dataSize_;

    return true;
}

 

 

  • EchoServer.h
    • mPacketDataQueue에 쌓여있는 데이터가 있는 경우 SendMsg() 함수를 호출하여 최종적으로 각 클라이언트의 SendMsg() 함수를 호출
void ProcessPacket()
{
    while (mIsRunProcessThread)
    {
        auto packetData = DequePacketData();
        if (packetData.DataSize != 0)
        {
            SendMsg(packetData.SessionIndex, packetData.DataSize, packetData.pPacketData);
        }
        else
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
    }
}

 

 



 

 

1-Send를 일반 버퍼가 아닌 queue에서 작동하도록 변경

이전에 만들어두었던 ProcessPacket() 쓰레드에서 Send가 이루어지므로 이전에 별도로 만들었던 Send 쓰레드는 제거

 

  • ClientInfo.h
    • SendMsg() 함수에서 mSendDataqueue에 Send할 데이터를 저장
      • mSendDataqueue는 공유 자원이므로 당연히 lock_guard를 걸어주어야함
      • queue의 크기가 1이라면 SendIO() 함수로 진입해 WSASend() 함수를 호출하여 실제로 Send
      • queue의 크기가 1이 아니라면 아직 이전 Send 작업 완료가 확인되지 않았으므로 아무 작업도 하지 않은 채 반환
    • SendCompleted() 함수가 호출된 경우 GetqueuedCompletionPort() 함수로 Send 작업 완료가 확인된 것이므로 mSendDataqueue의 원소를 지움
      • 역시 lock_guard를 걸어주어야함
      • mSendDataqueue에 들어있는 버퍼의 메모리와 해당 원소의 메모리 모두 해제가 필요
      • 메모리 해제 후 큐에서 원소를 제거
      • 제거 후에도 큐가 비어있지 않은 경우 추가로 Send를 해야하므로 SendIO() 함수를 호출하여 WSASend() 작업을 진행
std::mutex					mSendLock;
std::queue<stOverlappedEx*>	mSendDataqueue;

bool SendMsg(const UINT32 dataSize_, char *pMsg_)
{
    auto sendOverlappedEx = new stOverlappedEx;
    ZeroMemory(sendOverlappedEx, sizeof(stOverlappedEx));
    sendOverlappedEx->m_wsaBuf.len = dataSize_;
    sendOverlappedEx->m_wsaBuf.buf = new char[dataSize_];
    CopyMemory(sendOverlappedEx->m_wsaBuf.buf, pMsg_, dataSize_);
    sendOverlappedEx->m_eOperation = IOOperation::SEND;

    std::lock_guard<std::mutex> guard(mSendLock);

    mSendDataqueue.push(sendOverlappedEx);

    if (mSendDataqueue.size() == 1)
    {
        SendIO();
    }

    return true;
}

bool SendIO()
{
    auto sendOverlappedEx = mSendDataqueue.front();

    DWORD dwRecvNumBytes = 0;
    if (WSASend(mSock, &(sendOverlappedEx->m_wsaBuf), 1,
        &dwRecvNumBytes, 0, (LPWSAOVERLAPPED)sendOverlappedEx, NULL) == SOCKET_ERROR
        && (WSAGetLastError() != ERROR_IO_PENDING))
    {
        std::cout << "WSASend() Error : " << WSAGetLastError() << std::endl;
        return false;
    }

    return true;
}

void SendCompleted(const UINT32 dataSize_)
{
    std::cout << "Send Bytes : " << dataSize_ << std::endl;

    std::lock_guard<std::mutex> guard(mSendLock);

    delete[] mSendDataqueue.front()->m_wsaBuf.buf;
    delete mSendDataqueue.front();

    mSendDataqueue.pop();

    if (mSendDataqueue.empty() == false)
    {
        SendIO();
    }
}

'개인공부 > IOCP 서버 제작 실습' 카테고리의 다른 글

IOCP 서버 제작 실습 6  (0) 2023.03.23
IOCP 서버 제작 실습 5  (0) 2023.03.23
IOCP 서버 제작 실습 3  (0) 2023.03.22
IOCP 서버 제작 실습 2  (0) 2023.03.21
IOCP 서버 제작 실습 1  (0) 2023.03.21

댓글