- IOCP 실습 2 Echo 서버 코드 분리 + 구조체 변수 이동2023년 08월 28일
- 묭묭.cpp
- 작성자
- 2023.08.28.:52
지난 시간에 실습한 IOCP Echo 서버의 2단계 실습이다.
#출처 https://www.youtube.com/watch?v=RMRsvll7hrM
먼저 2단계에서 진행할 작업을 정리하고 진행하도록 하겠다.
1. 2단계에서 작업할 내용
- 선언부와 IOCP Class의 코드를 다른 파일로 분리한다.
- stOverlappedEx 구조체의 버퍼 정보를 stClientInfo 구조체로 옮긴다.
1번 작업은 모든 코드가 한 파일에 존재하게 되면 코드의 양이 많아짐에 따라 복잡해지고 원하는 함수를 찾을 때 어려움이 발생한다. 따라서 실습 2단계에서는 구조체 정의, enum class 선언 등의 코드와 Server 코드를 분리하는 작업을 진행한다.
2번 작업은 stOverlappedEx 구조체에 IO 버퍼 데이터가 포함되어 있고 너무 큰 버퍼의 용량을 지정하고 있으므로 불필요한 메모리의 낭비가 발생한다.
ex) 비동기 IO 요청할 때마다 해당 구조체를 사용 (버퍼 데이터를 사용하지 않는 경우도 발생)
따라서 버퍼 데이터를 Client 객체로 넘겨주고, SEND 버퍼와 RECV 버퍼를 분리하는 작업을 진행한다.
2. 작업 내용
1. 코드의 분리
위 사진처럼 헤더파일 하나에 담겨있던 코드를
코드 분리 작업을 진행하였다.
- Define.h : 구조체의 선언 등의 데이터 구조를 선언하는 부분만을 담당할 헤더파일을 생성
- IOCompletionPort.h : Server 프로그램의 전반적인 코드
- pch.h : 미리 컴파일된 헤더
1번 작업은 단순히 선언부와 프로그램 코드를 분리하는 작업으로 큰 어려움을 겪진 않았다.
추가로 pch.h (미리 컴파일된 헤더)를 추가하여 라이브러리 정의 코드의 중복을 없애고 공통적으로 사용될 변수나 라이브러리를 이곳에 선언할 수 있도록 만들었다.
2. stOverlappedEx의 버퍼 데이터를 stClientInfo로 이동
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 m_recvBuf[MAX_SOCKBUF]; // 데이터 버퍼 char m_sendBuf[MAX_SOCKBUF]; // 데이터 버퍼 stClientInfo() { // 메모리 영역을 0X00으로 채우는 매크로 : memset과 동일 ZeroMemory(&m_stRecvOverlappedEx, sizeof(stOverlappedEx)); ZeroMemory(&m_stSendOverlappedEx, sizeof(stOverlappedEx)); m_socketClient = INVALID_SOCKET; } };
- stOverlappedEx 구조체에 있던 m_szBuf를 삭제
- stClientInfo 구조체에 RECV 버퍼와 SEND 버퍼 데이터 추가
단순히 버퍼 데이터를 저장하는 위치를 변경하였다. 이로써 비동기 IO 작업(SEND, RECV)에서 항상 호출되던 stOverlappedEx 구조체가 아닌 stClientInfo 구조체로 버퍼 데이터를 옮기는 작업을 진행하였다.
그런데 여기서?
비동기 IO 작업은 SEND와 RECV 작업만 진행하는데 어디서 메모리 낭비가 발생하는지 의문을 갖게 되었다.
ACCEPT 작업은 Blocking 작업이고 OVERLAPPED 구조체를 전달하지 않는데도 말이다.
혹시 몰라서 ACCEPT 함수 뒷편에 Blocking 확인 코드를 넣고 테스트 해보았는데 해당 부분에서 메모리 낭비가 발생하지는 않다는 것은 확신할 수 있게 되었다.
다음 확인 작업으로 어디서 OVERLAPPED 구조체가 사용되는지 코드 레벨에서 분석해보았다.
int nRet = WSASend(pClientInfo->m_socketClient, &(pClientInfo->m_stSendOverlappedEx.m_wsaBuf), 1, &dwRecvNumBytes, 0, (LPWSAOVERLAPPED) & (pClientInfo->m_stSendOverlappedEx), NULL);
int nRet = WSARecv(pClientInfo->m_socketClient, &(pClientInfo->m_stRecvOverlappedEx.m_wsaBuf), 1, &dwRecvNumBytes, &dwFlag, (LPWSAOVERLAPPED) & (pClientInfo->m_stRecvOverlappedEx), NULL);
다음과 같이 IOCP 오브젝트에 SEND와 RECV 작업을 예약할 때 stOverlappedEx 구조체의 포인터 변수가 전달되는 것을 알 수 있었다.
그리고 해당 작업에서 IOCP 핸들에 작업을 요청하고 이 작업이 완료되면 IO Completion Queue에 완료정보를 밀어넣게 되는데
IO Completion Queue의 구조를 살펴보면 pOverlapped 구조체의 포인터 변수가 들어가있다는 것을 알 수 있다.
pOverlapped를 참조하여 작업을 할 때 사용하지도 않는 데이터 버퍼가 들어간다면 더 큰 메모리를 차지하게 되므로 메모리 낭비가 발생한다는 추측을 하였다.
WSAOVERLAPPED 구조체를 확장하였을 때 데이터를 포함하는 경우 어떤 이유에서 메모리 낭비가 발생하는지 더 확실한 답을 구하기 위해 검색과 ChatGPT를 활용하여 정보를 구해보았다.
- 구조체 크기의 증가 : OVERLAPPED 구조체는 비동기 I/O 작업을 추적하는데 필요한 데이터와 포인터를 포함한다. 따라서 불필요하게 큰 데이터(버퍼 데이터)를 포함하면 구조체가 크게 증가함으로 메모리를 불필요하게 낭비하게 된다.
- 메모리 오버헤드 발생 : 버퍼 데이터를 추가함으로 완료 관리 데이터와 실제 데이터가 혼합되어 메모리 관리에 대한 오버헤드가 발생할 수 있다. * 오버헤드란? 필요하지 않은 추가 메모리
- 응용 프로그램 복잡성 : 데이터를 추가함으로써 추가적인 로직과 작업이 필요할 수 있으므로 코드가 더 어려워질 수 있다.
위와 같은 이유로 OVERLAPPED 구조체와 실제 데이터를 분리하는 것이 일반적으로 더 효율적인 방법이다.
OVERLAPPED 구조체로 비동기 I/O를 추적하고 작업이 완료되었을 때 데이터를 버퍼에서 가져오는 방식으로 설계하면 메모리 낭비를 최소화하고 코드를 더 쉽게 관리할 수 있다.
즉, OVERLAPPED 구조체와 추가적인 데이터는 분리하자!
여기까지... OVERLAPPED 구조체와 버퍼 데이터를 분리하는 작업을 수행하였고
후처리로 일전에 stOverlappedEx 구조체에서 버퍼를 사용하는 부분을 stClientEx 구조체에서 각 목적에 맞는 (SEND와 RECV) 버퍼를 사용하도록 코드를 수정하면 된다.
일단 구조체 코드를 변경하게 되면 일전에 사용하던 코드에서 오류 메시지를 출력하게 된다.
해당 부분을 찾아서 적절하게 수정하면 완료이다.
코드는 워낙 간단하기 때문에 따로 첨부하지는 않겠다.
중간에 어디서 메모리 낭비가 나는지? 생각하면서 OVERLAPPED 구조체에 대한 이해를 쌓는데 큰 도움이 된 것 같다.
이번 실습에서 이해한 내용을 간단히 정리해보면 OVERLAPPED 구조체는 비동기 I/O 작업을 추적하는데 필요한 구조체이기 때문에 불필요한 데이터는 분리하는 것이 좋다! 라는 것이다.
나중에 보면 이런 하수같은 생각도 했었구나라고 떠올릴 수 있도록 실력을 착실히 쌓아나갈 수 있었으면 좋겠다.
IOCP 2단계 실습 깃허브 링크 첨부하고 2단계 실습 포스팅은 마무리하도록 하겠다.
https://github.com/MyungHyun-Ahn/Server-Programming-Study/tree/main/IOCP_STUDY/IOCP_02
반응형'IOCP 게임서버' 카테고리의 다른 글
IOCP 실습 6 Echo 서버 - 1-Send 방식 (Queue 방식 구현) (0) 2023.08.31 IOCP 실습 5 Echo 서버 - 1-Send 방식과 N-Send 방식 (SendBuffer 구현) (0) 2023.08.30 IOCP 실습 4 Echo 서버 - 네트워크 / 로직 처리 각각의 쓰레드로 분리 (2) 2023.08.30 IOCP 실습 3 Echo 서버 - 역할에 따라 코드 분리 + 연결, 끊어짐, 데이터 받음을 애플리케이션에 전달 (0) 2023.08.29 IOCP 실습 1 Echo Server 만들기 (0) 2023.08.28 다음글이전글이전 글이 없습니다.댓글