기존에는 데이터 버퍼가 Overlapped 구조체 확장형에 존재(m_szBuf)
- 비동기 I/O 요청시마다 Overlapped 구조체가 생성됨
- 그러나 버퍼 크기보다 데이터가 작을 수 있고, 버퍼를 사용하지 않는 I/O 요청이 있을 수도 있음
- 따라서 현재 구조로는 불필요한 메모리 낭비가 발생할 수 있음
struct stOverlappedEx
{
WSAOVERLAPPED m_wsaOverlapped; //OVERLAPPED 구조체
SOCKET m_socketClient; //클라이언트 소켓
WSABUF m_wsaBuf; //Overlapped I/O 작업 버퍼
char m_szBuf[MAX_SOCKBUF]; //데이터 버퍼
IOOperation m_eOperation; //작업 동작 종류
};
따라서 데이터 버퍼를 클라이언트 객체로 옮기는 작업을 진행
또한 recv 버퍼와 send 버퍼를 나누어 에코 도중 추가로 데이터를 주고받을 때도 정상적으로 작동하도록 구성
struct stClientInfo
{
SOCKET m_socketClient; //Client 소켓
stOverlappedEx m_stRecvOverlappedEx; //Recv Overlapped I/O 작업 위한 변수
stOverlappedEx m_stSendOverlappedEx; //Send OVerlapped I/O 작업 위한 변수
char mRecvBuf[MAX_SOCKBUF]; //데이터 버퍼(recv)
char mSendBuf[MAX_SOCKBUF]; //데이터 버퍼(send)
stClientInfo()
{
ZeroMemory(&m_stRecvOverlappedEx, sizeof(stOverlappedEx));
ZeroMemory(&m_stSendOverlappedEx, sizeof(stOverlappedEx));
m_socketClient = INVALID_SOCKET;
}
};
또한 해당 구조체들과 enum을 Define.h 파일로 따로 분리하도록 구조 수정
기존 코드에서 애플리케이션과 네트워크 코드를 분리
네트워크를 구성하는 코드는 IOCPServer.h에, 애플리케이션을 구성하는 코드는 EchoServer.h에 위치하도록 구성
애플리케이션은 서버의 클라이언트 연결 / 해제 / 데이터 수신시 출력되도록 구성
- main.cpp
#include "EchoServer.h"
#include <string>
#include <iostream>
const UINT16 SERVER_PORT = 11021;
const UINT16 MAX_CLIENT = 100;
int main()
{
EchoServer server;
//소켓 초기화
server.InitSocket();
//소켓 서버 등록
server.BindandListen(SERVER_PORT);
server.StartServer(MAX_CLIENT);
std::cout << "아무 키나 누를 때까지 대기\n";
while (true)
{
std::string inputCmd;
std::getline(std::cin, inputCmd);
if (inputCmd == "quit")
{
break;
}
}
server.DestroyThread();
return 0;
}
- Define.h
#pragma once
#include <WinSock2.h>
#include <WS2tcpip.h>
#define MAX_SOCKBUF 1024
#define MAX_WORKERTHREAD 4
enum class IOOperation
{
RECV,
SEND
};
//WSAOVERLAPPED 구조체 확장
struct stOverlappedEx
{
WSAOVERLAPPED m_wsaOverlapped; //OVERLAPPED 구조체
SOCKET m_socketClient; //클라이언트 소켓
WSABUF m_wsaBuf; //Overlapped I/O 작업 버퍼
IOOperation m_eOperation; //작업 동작 종류
};
//클라이언트 정보 구조체
struct stClientInfo
{
INT32 mIndex = 0;
SOCKET m_socketClient; //Client 소켓
stOverlappedEx m_stRecvOverlappedEx; //Recv Overlapped I/O 작업 위한 변수
stOverlappedEx m_stSendOverlappedEx; //Send OVerlapped I/O 작업 위한 변수
char mRecvBuf[MAX_SOCKBUF]; //데이터 버퍼(recv)
char mSendBuf[MAX_SOCKBUF]; //데이터 버퍼(send)
stClientInfo()
{
ZeroMemory(&m_stRecvOverlappedEx, sizeof(stOverlappedEx));
ZeroMemory(&m_stSendOverlappedEx, sizeof(stOverlappedEx));
m_socketClient = INVALID_SOCKET;
}
};
- EchoServer.h
#pragma once
#include "IOCPServer.h""
class EchoServer : public IOCPServer
{
virtual void OnConnect(const UINT32 clientIndex_) override
{
std::cout << "[OnConnect] 클라이언트 : Index " << clientIndex_ << std::endl;
}
virtual void OnClose(const UINT32 clientIndex_) override
{
std::cout << "[OnClose] 클라이언트 : Index " << clientIndex_ << std::endl;
}
virtual void ONReceive(const UINT32 clientIndex_, const UINT32 size, char* pData_) override
{
std::cout << "[OnReceive] 클라이언트 : Index " << clientIndex_ << ", dataSize " << size_ << std::endl;
}
};
- IOCPServer.h
#pragma once
#pragma comment(lib, "ws2_32")
#include <thread>
#include <vector>
#include <iostream>
#include "Define.h"
class IOCPServer
{
private:
// 클라이언트 정보 저장 구조체
std::vector<stClientInfo> mClientInfos;
//리슨 소켓
SOCKET mListenSocket = INVALID_SOCKET;
//접속중인 클라이언트 수
int mClientCnt = 0;
//IO Worker 쓰레드
std::vector<std::thread> mIOWorkerThreads;
//Accpet 쓰레드
std::thread mAccepterThread;
//CompletionPort 객체 핸들
HANDLE mIOCPHandle = INVALID_HANDLE_VALUE;
//작업 쓰레드 동작 플래그
bool mIsWorkerRun = true;
//접속 쓰레드 동작 플래그
bool mIsAccepterRun = true;
void CreateClient(const UINT32 maxClientCount)
{
for (UINT32 i = 0; i < maxClientCount; ++i)
{
mClientInfos.emplace_back();
mClientInfos[i].mIndex = i;
}
}
//worker 쓰레드 생성
bool CreateWorkerThread()
{
for (int i = 0; i < MAX_WORKERTHREAD; i++)
{
mIOWorkerThreads.emplace_back([this]() {WorkerThread(); });
}
std::cout << "WorkerThread start...\n";
return true;
}
//accept요청 처리 쓰레드 생성
bool CreateAccepterThread()
{
mAccepterThread = std::thread([this]() {AccepterThread(); });
std::cout << "AccepterThread start...\n";
return true;
}
//사용하지 않는 클라이언트 정보 구조체 반환
stClientInfo* GetEmptyClientInfo()
{
for (auto& client : mClientInfos)
{
if (INVALID_SOCKET == client.m_socketClient)
{
return &client;
}
}
}
//CompletionPort 객체와 소켓 연결
bool BindIOCompletionPort(stClientInfo* pClientInfo)
{
auto hIOCP = CreateIoCompletionPort((HANDLE)pClientInfo->m_socketClient, mIOCPHandle, (ULONG_PTR)(pClientInfo), 0);
if (hIOCP == NULL || hIOCP != mIOCPHandle)
{
std::cout << "CreateIoCompletionPort() Error : " << GetLastError() << std::endl;
return false;
}
return true;
}
//WSARecv overlapped I/O
bool BindRecv(stClientInfo* pClientInfo)
{
DWORD dwFlag = 0;
DWORD dwRecvNumBytes = 0;
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.len = MAX_SOCKBUF;
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.buf = pClientInfo->mRecvBuf;
pClientInfo->m_stRecvOverlappedEx.m_eOperation = IOOperation::RECV;
if (WSARecv(pClientInfo->m_socketClient, &(pClientInfo->m_stRecvOverlappedEx.m_wsaBuf), 1,
&dwRecvNumBytes, &dwFlag, (LPWSAOVERLAPPED) & (pClientInfo->m_stRecvOverlappedEx), NULL) == SOCKET_ERROR
&& (WSAGetLastError() != ERROR_IO_PENDING))
{
std::cout << "WSARecv() Error : " << WSAGetLastError() << std::endl;
return false;
}
return true;
}
//WSASend overlapped I/O
bool SendMsg(stClientInfo* pClientInfo, char* pMsg, int nLen)
{
DWORD dwRecvNumBytes = 0;
CopyMemory(pClientInfo->mSendBuf, pMsg, nLen);
pClientInfo->m_stSendOverlappedEx.m_wsaBuf.len = nLen;
pClientInfo->m_stSendOverlappedEx.m_wsaBuf.buf = pClientInfo->mSendBuf;
pClientInfo->m_stSendOverlappedEx.m_eOperation = IOOperation::SEND;
if (WSASend(pClientInfo->m_socketClient, &(pClientInfo->m_stSendOverlappedEx.m_wsaBuf), 1,
&dwRecvNumBytes, 0, (LPWSAOVERLAPPED) & (pClientInfo->m_stSendOverlappedEx), NULL) == SOCKET_ERROR
&& (WSAGetLastError() != ERROR_IO_PENDING))
{
std::cout << "WSASend() Error : " << WSAGetLastError() << std::endl;
return false;
}
return true;
}
//worker Thread
void WorkerThread()
{
stClientInfo* pClientInfo = NULL;
BOOL bSuccess = TRUE;
DWORD dwIoSize = 0;
LPOVERLAPPED lpOverlapped = NULL;
while (mIsWorkerRun)
{
bSuccess = GetQueuedCompletionStatus(mIOCPHandle, &dwIoSize, (PULONG_PTR)&pClientInfo, &lpOverlapped, INFINITE);
//쓰레드 종료 메시지 처리
if (bSuccess == TRUE && dwIoSize == 0 && lpOverlapped == NULL)
{
mIsWorkerRun = false;
continue;
}
if (lpOverlapped == NULL)
{
continue;
}
// 클라이언트 접속 해제
if (bSuccess == FALSE || (dwIoSize == 0 && bSuccess == TRUE))
{
std::cout << "socket " << (int)pClientInfo->m_socketClient << " disconnected\n";
CloseSocket(pClientInfo);
continue;
}
stOverlappedEx* pOverlappedEx = (stOverlappedEx*)lpOverlapped;
//Overlapped I/O Recv
if (pOverlappedEx->m_eOperation == IOOperation::RECV)
{
onReceive(pClientInfo->mIndex, dwIoSize, pClientInfo->mRecvBuf);
//echo
SendMsg(pClientInfo, pClientInfo->mRecvBuf, dwIoSize);
BindRecv(pClientInfo);
}
//Overlapped I/O Send
else if (pOverlappedEx->m_eOperation == IOOperation::SEND)
{
std::cout << "Send Bytes : " << dwIoSize << ", msg : " << pClientInfo->mSendBuf << std::endl;
}
else
{
std::cout << "socket " << (int)pClientInfo->m_socketClient << " exception";
}
}
}
//사용자 접속 쓰레드
void AccepterThread()
{
SOCKADDR_IN stClientAddr;
int nAddrLen = sizeof(SOCKADDR_IN);
while (mIsAccepterRun)
{
stClientInfo* pClientInfo = GetEmptyClientInfo();
if (pClientInfo == NULL)
{
std::cout << "Client Full Error\n";
return;
}
pClientInfo->m_socketClient = accept(mListenSocket, (SOCKADDR*)&stClientAddr, &nAddrLen);
if (pClientInfo->m_socketClient == INVALID_SOCKET)
{
continue;
}
//I/O Completion Port 객체와 소켓 연결
if (BindIOCompletionPort(pClientInfo) == false)
{
return;
}
//Recv Overlapped I/O 작업 요청
if (BindRecv(pClientInfo) == false)
{
return;
}
onConnect(pClientInfo->mIndex);
++mClientCnt;
}
}
//소켓 연결 종료
void CloseSocket(stClientInfo* pClientInfo, bool bIsForce = false)
{
// SO_DONTLINGER로 설정
struct linger stLinger = { 0, };
// SO_LINGER, timeout = 0으로 설정하여 강제 종료, 데이터 손실이 있을 수 있음
if (bIsForce == true)
{
stLinger.l_onoff = 1;
}
shutdown(pClientInfo->m_socketClient, SD_BOTH);
setsockopt(pClientInfo->m_socketClient, SOL_SOCKET, SO_LINGER, (char*)&stLinger, sizeof(stLinger));
closesocket(pClientInfo->m_socketClient);
pClientInfo->m_socketClient = INVALID_SOCKET;
onClose(pClientInfo->mIndex);
}
public:
IOCPServer(void) {}
~IOCPServer(void)
{
WSACleanup();
}
virtual void onConnect(const UINT32 clientINdex_) {}
virtual void onClose(const UINT32 clientINdex_) {}
virtual void onReceive(const UINT32 clientINdex_, const UINT32 size_, char *pData_) {}
//소켓 초기화
bool InitSocket()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "WSAStartup() Error : " << WSAGetLastError() << std::endl;
return false;
}
SOCKET mListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, NULL, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == mListenSocket)
{
std::cout << "WSASocket() Error : " << WSAGetLastError() << std::endl;
return false;
}
std::cout << "socket init success\n";
return true;
}
bool BindandListen(int nBindPort)
{
SOCKADDR_IN stServerAddr;
stServerAddr.sin_family = AF_INET;
stServerAddr.sin_port = htons(nBindPort);
stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(mListenSocket, (SOCKADDR*)&stServerAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
std::cout << "bind() Error : " << WSAGetLastError() << std::endl;
return false;
}
if (listen(mListenSocket, 5) == SOCKET_ERROR)
{
std::cout << "listen() Error : " << WSAGetLastError() << std::endl;
return false;
}
std::cout << "server registration success\n";
return true;
}
//접속 요청 수락, 메시지 처리
bool StartServer(const UINT32 maxClientCount)
{
CreateClient(maxClientCount);
mIOCPHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, MAX_WORKERTHREAD);
if (mIOCPHandle == NULL)
{
std::cout << "CreateIoCompletionPort() Error : " << GetLastError() << std::endl;
return false;
}
if (CreateWorkerThread() == false)
{
return false;
}
if (CreateAccepterThread() == false)
{
return false;
}
std::cout << "Server start\n";
return true;
}
//생성되어있던 쓰레드 파괴
void DestroyThread()
{
mIsWorkerRun = false;
CloseHandle(mIOCPHandle);
for (auto& th : mIOWorkerThreads)
{
if (th.joinable())
{
th.join();
}
}
mIsAccepterRun = false;
closesocket(mListenSocket);
if (mAccepterThread.joinable())
{
mAccepterThread.join();
}
}
};
'개인공부 > IOCP 서버 제작 실습' 카테고리의 다른 글
IOCP 서버 제작 실습 6 (0) | 2023.03.23 |
---|---|
IOCP 서버 제작 실습 5 (0) | 2023.03.23 |
IOCP 서버 제작 실습 4 (0) | 2023.03.22 |
IOCP 서버 제작 실습 3 (0) | 2023.03.22 |
IOCP 서버 제작 실습 1 (0) | 2023.03.21 |
댓글