Chapter 23 - IOCP(Input Output Completion Port)
Chapter 23 - IOCP(Input Output Completion Port)
23-1 : Overlapped IO를 기반으로 IOCP 이해하기
리눅스의 epoll
과 윈도우의 IOCP
사이의 절대적 비교우위는 존재하지 않음
대신 하드웨어의 성능 / 할당된 대역폭이 충분한 상황에서 응답시간 / 동시접속자 수에 문제가 발생할 경우 아래 사항들을 먼저 고려
- 비효율적인 IO의 구성 또는 비효율적인 CPU의 활용
- 데이터베이스의 설계내용과 쿼리(Query)의 구성
윈도우에서는 ioctlsocket()
함수를 통해 논블로킹 모드로 소켓의 속성을 변경할 수 있음
#include <winsock2.h>
int WSAAPI ioctlsocket(SOCEKT s, long cmd, u_long *argp);
// 성공시 0, 실패시 SOCKET_ERROR 반환
cmd
: 소켓의 IO방식을 지정cmd
를 입출력 모드를 뜻하는FIONBIO
로,argp
를 0이 아닌 값으로 두면 논블로킹 모드로 변경됨- 위와같이 변경시 추가로 클라이언트의 연결요청이 존재하지 않는 상태에서
accept()
함수 호출시 곧바로INVALID_SOCKET
을 반환 - 이어서
WSAGetLastError()
함수 호출시WSAEWOULDBLCOK
을 반환 accept()
함수의 호출로 새로 생성되는 소켓 또한 논블로킹 속성을 지님
- 위와같이 변경시 추가로 클라이언트의 연결요청이 존재하지 않는 상태에서
Overlapped IO
기반의 서버구현에는 반드시 논블로킹 소켓이 필요
Overlapped IO
를 사용한 에코 서버 예시
// CmplRouEchoServ_win.cpp
#include <iostream>
#include <winsock2.h>
using namespace std;
#define BUF_SIZE 1024
void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(char *message);
typedef struct
{
SOCEKT hClntSock;
char buf[BUF_SIZE];
WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hLisnSock, hRecvSock;
SOCKADDR_IN lisnAdr, recvAdr;
LPWSAOVERLAPPED lpOvLp;
DWORD recvBytes;
LPPER_IO_DATA hbInfo;
int mode = 1, recvAdrSz, flagInfo = 0;
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
ioctlsocket(hLisnSock, FIONBIO, &mode);
memset(&lisnAdr, 0, sizeof(lisnAdr));
lisnAdr.sin_family = AF_INET;
lisnAdr.sin_addr.s_addr = htonl(INADDR_ANY);
lisnAdr.sin_port = htons(atoi(argv[1]));
if (::bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR)
ErrorHandling("bind() error");
if (::listen(hLisnSock, 5) == SOCKET_ERROR)
ErrorHandling("listen() error");
recvAdrSz = sizeof(recvAdr);
while (1)
{
SleepEx(100, TRUE); // for alertable wait state
hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz);
if (hRecvSock == INVALID_SOCKET)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
continue;
else
ErrorHandling("accept() error");
}
cout << "Client connected....." << endl;
lpOvLp = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
memset(lpOvlp, 0, sizeof(WSAOVERLAPPED));
hbInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
hbInfo->hClntSock = (DWORD)hRecvSock;
(hbINfo->wsaBuf).buf = hbInfo->buf;
(hbInfo->wsaBuf).len = BUF_SIZE;
lpOvLp->hEvent = (HANDLE)hbInfo;
WSARecv(hRecvSock, &(hbInfo->wsaBuf), 1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);
}
closesocket(hRecvSock);
closesocket(hLisnSock);
WSACleanup();
return 0;
}
void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
SOCEKT hSock = hbInfo->hClntSock;
LPWSABUF bufInfo = &(hbInfo->wsaBuf);
DWORD sentBytes;
if (szRecvBytes == 0)
{
closesocket(hSock);
free(lpOverlapped->hEvent);
free(lpOverlapped);
cout << "Client disconnected.....\n";
}
else // echo
{
bufInfo->len = szRecvBytes;
WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
}
}
void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
SOCEKT hSock = hbInfo->hClntSock;
LPWSABUF bufInfo = &(hbInfo->wsaBuf);
DWORD recvBytes;
int flagInfo = 0;
WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
- 클라이언트가 연결되면
WSARecv()
함수를 호출하면서 논블로킹 모드로 데이터 수신, 수신 완료시ReadCompRoutine()
함수가 호출됨 ReadCompRoutine()
함수 호출시WSASend()
함수를 호출하면서 논블로킹 모드로 데이터 송신, 송신 완료시WriteCompRoutine()
함수 호출- 호출된
WriteCompRoutine()
함수는 다시WSARecv()
함수를 호출하면서 논블로킹 모드로 데이터 수신을 기다림
소켓은 처음 생성시 블로킹 모드이기 때문에 ioctlsocket()
함수를 통해 논블로킹 모드로 전환
Completion Routine
기반 Overlapped IO
에서는 Event
오브젝트가 불필요하기 때문에 hEvent
에 다른 정보를 채울 수 있음
따라서 입출력 완료시 자동으로 호출되는 Completion Routine
내부로 클라이언트 정보(소켓 / 버퍼)를 전달하기 위해 WSAOVERLAPPED
구조체의 멤버 hEvent
를 사용
약간의 문제점을 해결한 에코 클라이언트 예시
// StableEchoClnt_win.cpp
#include <iostream>
#include <winsock2.h>
using namespace std;
#define BUF_SIZE 1024
void ErrorHandling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAdr;
char message[BUF_SIZE];
int strLen, readLen;
if(argc != 3)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error");
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);
servAdr.sin_port = htons(atoi(argv[2]));
if (::connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("connect() error");
else
cout << "Connected.................\n";
while (1)
{
cout << "Input message(Q to quit) : ";
cin.getline(message, BUF_SIZE);
if (!strcmp(message, "q") || !strcmp(message, "Q"))
break;
strLen = strlen(message);
send(hSocket, message, strLen, 0);
readLen = 0;
while (1)
{
readLen += recv(hSocket, &message[readLen], BUF_SIZE - 1, 0);
if (readLen >= strLen)
break;
}
message[strLen] = 0;
cout << "Message from server : " << message << endl;
}
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
Overlapped IO
모델 에코 서버의 경우 논블로킹 모드의 accept()
함수와 alertable wait
상태 진입을 위한 SleepEx()
함수의 반복 호출이 성능에 영향을 미칠 수 있음
이를 해결하기 위해 accept()
함수 호출은 메인 쓰레드가 처리하도록 하고, 별도로 쓰레드를 하나 생성하여 클라이언트와의 입출력을 담당하게 할 수 있음
이것이 IOCP에서 제안하는 서버의 구현 모델임
23-2 : IOCP의 단계적 구현
IOCP에서는 완료된 IO의 정보가 Completion Port
오브젝트라는 커널 오브젝트에 등록됨
단, 이를 위해서는 소켓(반드시 Overlapped
속성이 부여되어있어야함)과 CP 오브젝트와의 연결 요청이 필요
CP 오브젝트의 생성에는 CreateIoCompletionPort()
함수를 사용
#include <windows.h>
HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurretThreads);
// 성공시 CP 오브젝트의 핸들, 실패시 NULL 반환
FileHandle
: CP 오브젝트 생성시INVALID_HANDLE_VALUE
ExistingCompletionPort
: CP 오브젝트 생성시 NULLCompletionKey
: CP 오브젝트 생성시 0NumberOfConcurrentThreads
: CP 오브젝트에 할당되어 완료된 IO를 처리할 쓰레드 수, 0 전달시 시스템의 CPU가 동시 실행 가능한 최대 쓰레드 수로 설정
CP 오브젝트를 소켓에 연결시킬 때도 CreateIoCompletionPort()
함수를 사용하며, 이 때는 매개변수의 사용이 달라짐
FileHandle
: CP 오브젝트에 연결할 소켓의 핸들ExistingCompletionPort
: 소켓과 연결할 CP 오브젝트의 핸들CompletionKey
: 완료된 IO 관련 정보의 전달을 위한 매개변수NumberOfConcurrentThreads
:ExistingCompletionPort
가 NULL이 아니면 무시됨
CP에 등록되는 완료된 IO의 확인에는 GetQueuedCompletionStatus()
함수를 사용
#include <windows.h>
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytes, PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);
// 성공시 TRUE, 실패시 FALSE 반환
CompletionPort
: 완료된 IO 정보가 등록되어있는 CP 오브젝트 핸들lpNumberOfBytes
: 입출력 과정에서 송수신된 데이터 크기정보를 저장할 변수의 주소값lpCompletionKey
:CreateIoCompletionPort()
함수의CompetionKey
값의 저장을 위한 변수의 주소값lpOverlapped
:WSASend()
/WSARecv()
함수 호출시OVERLAPPED
구조체 변수의 주소값이 저장될 변수의 주소값dwMilliseconds
: 타임아웃, 지정한 시간 완료시 즉시FALSE
반환 / INFINITE로 지정시 완료된 IO가 CP 오브젝트에 등록될 때까지 블로킹 상태 유지
IOCP 기반 에코 서버 구현
// IOCPEchoServ_win.cpp
// 클라이언트는 이전의 StableEchoClnt_win.cpp를 사용
#include <iostream>
#include <process.h>
#include <windows.h>
#include <winsock2.h>
using namespace std;
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
typedef struct // socket info
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
typedef struct // buffer info
{
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[BUF_SIZE];
int rwMode; // Read or Write
} 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, flags = 0;
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
GetSystemInfo(&sysInfo);
for (int 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]));
if (::bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("bind() error");
if (::listen(hServSock, 5) == SOCKET_ERROR)
ErrorHandling("listen() error");
while (1)
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
int addrLen = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
handleInfo->hClntSock = hClntSock;
memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
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;
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)
{
GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
sock = handleInfo->hClntSock;
if (ioInfo->rwMode == READ)
{
cout << "message received!\n";
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
{
cout << "message sent!\n";
free(ioInfo);
}
}
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
IOCP가 성능이 좀더 나은 이유
- 논블로킹 방식으로 IO가 진행되기 때문에 IO 작업으로 인한 시간지연이 발생하지 않음
- IO가 완료된 핸들을 찾기 위한 반복문을 구성할 필요가 없음
- IO의 진행대상인 소켓 핸들을 배열에 저장하여 관리할 필요가 없음
- IO의 처리를 위한 쓰레드의 수를 조절할 수 있음
내용 확인문제
-
Completion Port
오브젝트에는 하나 이상의 쓰레드가 할당되어서 입출력을 처리하게된다. 그렇다면Completion Port
오브젝트에 할당될 쓰레드는 어떻게 생성되며, 또 할당의 방법은 무엇인지 소스코드 레벨에서 설명해보자.Completion Port
오브젝트에 할당할 쓰레드는 프로그래머가 직접 생성해주어야하며, 해당 쓰레드내에서GetQueuedCompletionPort()
함수를 호출하는 것이 해당 오브젝트에 할당함을 의미함 -
CreateIoCompletionPort
함수는 다른 함수들과 달리 두 가지의 기능을 제공한다. 그렇다면 이 두 가지 기능은 각각 무엇인가?Completion Port
오브젝트의 생성, 오브젝트와 소켓과의 연결 -
Completion Port
오브젝트와 소켓의 연결이 의미하는 바는 무엇인가? 그리고 연결의 과정은 어떻게 진행해야 하는가?IO 작업의 완료 여부를 관찰할 소켓이
Completion Port
오브젝트에 등록되었음을 뜻함
CreateIoCompletionPort()
함수를 사용하여 연결 - IOCP와 관련된 다음 문장들 중에서 옳지 않은 것을 모두 고르면?
- 최소한의 스레드로 다수의 IO를 처리할 수 있는 구조이기 때문에 쓰레드의 컨텍스트 스위칭으로 인한 성능의 저하를 막을 수 있다. (O)
- IO가 진행중인 상태에서 서버는 IO의 완료를 기다리지 않고, 다른 일을 진행할 수 있기 때문에 CPU를 효율적으로 사용할 수 있는 구조이다. (O)
- IO가 완료되었을 때, 이와 관련된
Completion Routine
이 자동으로 호출되기 때문에 IO의 완료를 대기하기 위해서 별도의 함수를 호출할 필요가 없다. (X) - IOCP는 윈도우 이외의 다른 운영체제에서도 제공되는 기능이기 때문에 호환성에 있어서도 높은 점수를 줄 수 있다.(X)
- 다음 문장들 중에서 IOCP에 할당할 적정 쓰레드의 수를 결정하는 방법으로 적절하면 O, 적절치 않으면 X를 표시해보자.
- 일반적인 선택은 CPU의 수와 동일한 수의 쓰레드를 할당하는 것이다. (O)
- 가장 좋은 방법은 여건이 허락하는 범위 내에서 실험적인 결과를 통해 쓰레드의 수를 결정하는 것이다. (O)
- 할당할 쓰레드의 수는 여유있게 선택하는 것이 좋다. 예를 들어서 한 개의 쓰레드로 충분한 상황에서는 여유있게 세 개 정도의 쓰레드를 IOCP에 할당하는 것이 좋다. (X)
-
이번 Chapter에서 설명한 IOCP 모델을 바탕으로 채팅 서버를 구현해보자. 이 채팅서버는 Chapter 20에서 소개한 채팅 클라이언트인 예제 chat_clnt_win.cpp와 함께 동작이 가능해야한다. 참고로 본문에서 제시한 IOCP 예제는 하나의 사례일 뿐이니, 이 틀에 꼭 맞추려고 노력하지 않아도 된다. 이 틀에 완벽히 맞추려다보면 오히려 구현이 어렵게 느껴질 수 있다.
// IOCPchatserv_win.cpp // 클라이언트는 이전의 chat_clnt_win.cpp를 사용 #include <iostream> #include <process.h> #include <windows.h> #include <winsock2.h> using namespace std; #define BUF_SIZE 100 #define MAX_CLNT 256 typedef struct // socket info { SOCKET hClntSock; SOCKADDR_IN clntAdr; } PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct // buffer info { OVERLAPPED overlapped; WSABUF wsaBuf; char buffer[BUF_SIZE]; } PER_IO_DATA, *LPPER_IO_DATA; DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO); void ErrorHandling(char *message); int clntCnt = 0; SOCKET clntSocks[MAX_CLNT]; 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, flags = 0; if(argc != 2) { cout << "Usage : " << argv[0] << " <port>\n"; exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); GetSystemInfo(&sysInfo); for (int 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])); if (::bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (::listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); while (1) { SOCKET hClntSock; SOCKADDR_IN clntAdr; int addrLen = sizeof(clntAdr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen); handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA)); handleInfo->hClntSock = hClntSock; memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen); clntSocks[clntCnt++] = hClntSock; 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; 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) { GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE); sock = handleInfo->hClntSock; cout << "message received!\n"; if (bytestrans == 0) // EOF 전송시 { for (int i = 0; i < clntCnt; i++) { if (sock == clntSocks[i]) { while (i++ < clntCnt - 1) clntSocks[i] = clntSocks[i + 1]; } } clntCnt--; closesocket(sock); free(handleInfo); free(ioInfo); continue; } for (int i = 0; i < clntCnt; i++) send(clntSocks[i], ioInfo->buffer, bytesTrans, 0); memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL); } return 0; } void ErrorHandling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }