IOCP : I/O 완료를 큐에 넣고, 워커 스레드가 그 큐에서 꺼내 처리하는 모델
https://diy-multitab.tistory.com/56
CreateIoCompletionPort()
GetQueueCompletionStatus()
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
// 클라이언트와 연결된 소켓 정보를 담기 위해 정의된 구조체
// 이 구조체 기반으로 변수가 언제 할당, 전달, 활용되는지 관찰
typedef struct
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// IO 버퍼와 Overlapped IO에 반드시 필요한 OVERLAPPED 구조체 담은 구조체 정의
typedef struct
{
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[BUF_SIZE];
int rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;
DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
HANDLE hComPort;
SYSTEM_INFO sysInfo;
LPPER_IO_DATA ioInfo;
LPPER_HANDLE_DATA handleInfo;
SOCKET hServSock;
SOCKADDR_IN servAdr;
int recvBytes, i, flags = 0;
if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
ErrorHandling("WSAStartup() error");
// CP 오브젝트 생성. 마지막 전달 인자 0이므로 코어 수(CPU 수)만큼 CP 오브젝트에 쓰레드 할당 가능.
hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
GetSystemInfo(&sysInfo); // 현재 실행중인 시스템 정보 얻기
// CPU 수만큼 쓰레드 생성. 쓰레드 생성할 때 방금 만든 CP 오브젝트 핸들 전달.
for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)
_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);
hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR *)&servAdr, sizeof(servAdr));
listen(hServSock, 5);
while(1)
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
int addrLen = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR *)&clntAdr, &addrLen);
// PER_HANDLE_DATA 동적 할당 후 정보 담기
handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
handleInfo->hClntSock = hClntSock; // 클라와 연결된 소켓 정보 저장
memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen); // 클라 주소 정보 저장
// 클라 연결 소켓과 CP 오브젝트를 연결
// 이제 해당 소켓 기반 Overlapped IO 완료 시,
// 연결된 CP 오브젝트에 완료 정보가 삽입되고,
// 이로 인해 GetQueued... 함수가 반환된다.
// 반환될 때, 3번째 인자 값을 얻을 수 있다.
CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len = BUF_SIZE;
ioInfo->wsaBuf.buf = ioInfo->buffer;
ioInfo->rwMode = READ; // 입력인지 출력인지 명시
// 6번째 인자로 OVERLAPPED 주소값 전달. 이 값은 이후 GetQueued... 함수 반환 시 얻을 수 있다.
WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
}
return 0;
}
// 쓰레드에 의해 실행되는 함수
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
HANDLE hComPort = (HANDLE)pComPort;
SOCKET sock;
DWORD bytesTrans;
LPPER_HANDLE_DATA handleInfo;
LPPER_IO_DATA ioInfo;
DWORD flags = 0;
while(1)
{
// IO 완료되면 다시 깨어나서 나머지 일 하고 다시 기다린다. 대기용 워커 쓰레드니까...
// 반환됐을 때 세번째, 네번째 인자를 통해 두 가지 정보를 얻게 된다.
GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED *)&ioInfo, INFINITE);
sock = handleInfo->hClntSock;
if(ioInfo->rwMode==READ)
{
puts("message received!");
if(bytesTrans==0) // EOF
{
closesocket(sock);
free(handleInfo);
free(ioInfo);
continue;
}
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len = bytesTrans;
ioInfo->rwMode = WRITE;
WSASend(sock, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);
ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len = BUF_SIZE;
ioInfo->wsaBuf.buf = ioInfo->buffer;
ioInfo->rwMode = READ;
WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL);
}
else
{
puts("message sent!");
free(ioInfo);
}
}
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}