2단계에서는 아래의 내용을 진행한다.
- `OverlappedEx` 구조체에서 버퍼 관리 최적화
- 불필요한 메모리 낭비 제거
- 코드 구조 개선
코드 분리
이전 코드에서 `IOCompletionPort.h`에 `stOverlappedEx`와 `stClientInfo` 구조체가 있었다.
하나의 파일에 모든 코드가 들어가 있으면 구조적으로 좋지 않기 때문에 분리하는 작업을 진행한다.
Server 코드는 그대로 두고, enum class와 구조체를 정의한 코드만 분리하여 별도의 파일로 만들었다.
`Define.h`
#pragma once
#include <winsock2.h>
#include <Ws2tcpip.h>
const UINT32 MAX_SOCKBUF = 256;
const UINT32 MAX_WORKERTHREAD = 4;
enum class IOOperation
{
RECV,
SEND
};
struct stOverlappedEx
{
WSAOVERLAPPED m_wsaOverlapped;
SOCKET m_socketClient;
WSABUF m_wsaBuf;
char m_szBuf[ MAX_SOCKBUF ];
IOOperation m_eOperation;
};
struct stClientInfo
{
SOCKET m_socketClient;
stOverlappedEx m_stRecvOverlappedEx;
stOverlappedEx m_stSendOverlappedEx;
stClientInfo()
{
ZeroMemory( &m_stRecvOverlappedEx , sizeof( stOverlappedEx ) );
ZeroMemory( &m_stSendOverlappedEx , sizeof( stOverlappedEx ) );
m_socketClient = INVALID_SOCKET;
}
};
이렇게 분리된 코드를 기존 Server에서 사용해야 하기 때문에 `include` 해준다.
#pragma once
#pragma comment(lib, "ws2_32")
#include "Define.h"
#include <thread>
#include <vector>
class IOCompletionPort
{
...
`OverlappedEx` 구조체에서 버퍼 관리 최적화
기존 `stOverlappedEx` 구조체에서는 `char m_szBuf[MAX_SOCKBUF]`로 Receive와 Send를 하나로 사용하고 있다. 버퍼 데이터를 저장하는 위치를 옮기고 Receive와 Send 버퍼를 분리했다.
#pragma once
#include <winsock2.h>
#include <Ws2tcpip.h>
const UINT32 MAX_SOCKBUF = 256;
const UINT32 MAX_WORKERTHREAD = 4;
enum class IOOperation
{
RECV,
SEND
};
struct stOverlappedEx
{
WSAOVERLAPPED m_wsaOverlapped;
SOCKET m_socketClient;
WSABUF m_wsaBuf;
IOOperation m_eOperation;
};
struct stClientInfo
{
SOCKET m_socketClient;
stOverlappedEx m_stRecvOverlappedEx;
stOverlappedEx m_stSendOverlappedEx;
char mRecvBuf[MAX_SOCKBUF]; //데이터 버퍼
char mSendBuf[MAX_SOCKBUF]; //데이터 버퍼
stClientInfo()
{
ZeroMemory(&m_stRecvOverlappedEx, sizeof(stOverlappedEx));
ZeroMemory(&m_stSendOverlappedEx, sizeof(stOverlappedEx));
m_socketClient = INVALID_SOCKET;
}
};
구조체가 변경되면서 기존 코드에서 에러가 발생한다. 적절하게 수정하자.
메모리 낭비? 어디서?
기존 구조체에서 어떤 부분이 메모리를 낭비하는 걸까?
struct stOverlappedEx
{
WSAOVERLAPPED m_wsaOverlapped; // 32바이트 (Windows x64 기준)
SOCKET m_socketClient; // 8바이트
WSABUF m_wsaBuf; // 16바이트 (포인터 + int)
char m_szBuf[MAX_SOCKBUF];// 256바이트
IOOperation m_eOperation; // 4바이트 (enum class, 기본 int형)
};
크기 (대략) = 32 + 8 + 16 + 256 + 4 = 316바이트 (패딩 포함하면 약 320바이트 이상 될 수 있음)
struct stClientInfo
{
SOCKET m_socketClient; // 8바이트
stOverlappedEx m_stRecvOverlappedEx; // 320바이트
stOverlappedEx m_stSendOverlappedEx; // 320바이트
};
총 크기 ≈ 8 + 320 + 320 = 648바이트 per client
모든 클라이언트 당 송수신 버퍼가 256바이트씩 중복으로 존재한다.
`m_szBuf`는 `WSABUF.buf`로 연결되기만 하고, 결국 `stClientInfo` 전체에서 `RecvBuf`와 `SendBuf`가 존재하게 되어 실제 사용되는 버퍼와 구조체 내 버퍼가 이중화될 수 있다.
비교 테스트
int main()
{
cout << "========== 구조체 메모리 비교 ==========\n";
cout << "Overlapped 객체 수: " << OVERLAPPED_COUNT << "\n\n";
cout << "[With Buffer 구조체]\n";
cout << "OverlappedWithBuffer 크기 : " << sizeof(OverlappedWithBuffer) << " bytes\n";
cout << "총 메모리 사용량 : "
<< (sizeof(OverlappedWithBuffer) * OVERLAPPED_COUNT) / 1024 << " KB ("
<< (sizeof(OverlappedWithBuffer) * OVERLAPPED_COUNT) << " bytes)\n\n";
cout << "[No Buffer 구조체]\n";
cout << "OverlappedNoBuffer 크기 : " << sizeof(OverlappedNoBuffer) << " bytes\n";
cout << "총 메모리 사용량 : "
<< (sizeof(OverlappedNoBuffer) * OVERLAPPED_COUNT) / 1024 << " KB ("
<< (sizeof(OverlappedNoBuffer) * OVERLAPPED_COUNT) << " bytes)\n\n";
size_t savedPerClient = sizeof(OverlappedWithBuffer) - sizeof(OverlappedNoBuffer);
cout << "========== 절감 효과 ==========\n";
cout << "객체 1개당 절약 메모리 : " << savedPerClient << " bytes\n";
cout << "전체 절약된 메모리 : " << savedPerClient * OVERLAPPED_COUNT / 1024 << " KB\n";
return 0;
}
'C++' 카테고리의 다른 글
[단계별로 IOCP 실습] 5단계 효율적인 Send 구현 (1-Send 구현하기) (3) | 2025.08.06 |
---|---|
[단계별로 IOCP 실습] 4단계 네트워크와 로직 처리 스레드 분리 (4) | 2025.08.05 |
[단계별로 IOCP 실습] 3단계 애플리케이션과 네트워크 코드 분리 (2) | 2025.08.04 |
[단계별로 IOCP 실습] 1단계 Echo Server 만들기 (4) | 2025.07.31 |
공룡 점프 게임 만들기 (3) | 2025.07.30 |