IOCP의 기본적은 구성은두개의 쓰레드로 만들어 진다. 왼편에 있는 루프는 보통 main() 함수에서 있는 루프고 오른쪽에 있는 루프는 main함수가 루프를 들어가기 전에 생성해야 하는 쓰레드로 보통 Worker쓰레드라고 한다. 권장하는 Worker쓰레드의 수는 하나의 CPU당 두개로(Send/Recv) 되어 있다. 또한 2번에서 3번으로의 데이터 전송은 전역변수를 이용하거나 Worker쓰레드를 생성할 때, 포인터를 전송하여 두 루프가 변수를 공유하는 방법이다. 이에 대해서는 달리 자세히 설명하지는 않겠다. 1. wait for client to connect ( accept )
이 과정에 이전에 이미IOCP 핸들과 소켓 초기화, 클라이언트의 접속을 대기하는 소켓, 접속한 클라이언트들의 정보를 가지는 배열( 물론, 자료구조는 자신의 선택이다. )을 생성해 두어야 한다. 여기는 클라이언트의 접속을 대기하는 소켓에 Accept()를 호출하여 클라이언트가 접속하기를 기다리는 과정이다.
unsigned int WINAPI CallAccepterThread(LPVOID p)
{
cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p;
pOverlappedEvent->ThreadAccepter();
return 0;
}
// 서버 시작 -- 준비
bool cIOCompletionPort::StartServer(void)
{
m_hIOCP = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
NULL,
NULL,
0);
if ( NULL == m_hIOCP )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateIoCompletionPort() 실패:%d:",GetLastError() );
return false;
}
bool bRet = CreateWorkerThread();
if ( false == bRet )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateWorkerThread() 실패:%d:",GetLastError() );
return false;
}
bRet = CreateAccepterThread();
if ( false == bRet )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateAccepterThread() 실패:%d:",GetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" StartServer() - CreateIoCompletionPort / Worker /Accepter 성공" );
///
return true;
}
bool cIOCompletionPort::CreateAccepterThread(void)
{
unsigned int uiThreadId = 0;
/// 클라이언트 접속 요청을 받은 쓰레드 생성
m_hAccepterThread = (HANDLE)_beginthreadex(NULL, 0,
&CallAccepterThread,
this,
CREATE_SUSPENDED,
&uiThreadId);
if ( m_hAccepterThread == NULL )
{
m_pMainDlg->OutputMsg("CreateAccepterThread() [에러] 실패:%d:",GetLastError() );
return false;
}
ResumeThread( m_hAccepterThread );
m_pMainDlg->OutputMsg(" CreateAccepterThread() 성공" );
///
return true;
}
// 사용자 접속 받는 쓰레드
void cIOCompletionPort::ThreadAccepter(void)
{
SOCKADDR_IN stClientAddr;
int nAddrLen = sizeof(SOCKADDR_IN);
while ( m_bAccepterRun )
{
// 접속 받을 구조체 인덱스 얻기
stClientInfo * pClientInfo = GetEmptyClientInfo();
if ( NULL == pClientInfo )
{
m_pMainDlg->OutputMsg("** AccepterThread [에러] NULL == pClientInfo :%s:","Client FULL");
return ;
}
m_pMainDlg->OutputMsg("** AccepterThread -- accept : pClientInfo->m_socketClient");
// 클라이언트 접속 요청까지 대기
//
// accept()
//
pClientInfo->m_socketClient = accept ( m_socketListen,
(SOCKADDR *)&stClientAddr, &nAddrLen );
if ( INVALID_SOCKET == pClientInfo->m_socketClient ) {
continue;
}
m_pMainDlg->OutputMsg("** AccepterThread -- BindIOCompletionPort & 소켓 pClientInfo");
// I/O Completion Port객체와 소켓을 연결 시킨다.
//
// BindIOCompletionPort()
//
bool bRet = BindIOCompletionPort( pClientInfo );
if ( false == bRet ) {
return;
}
// Recv Overlapped I/O 작업을 요청한다
m_pMainDlg->OutputMsg("** AccepterThread -- BindRecv & pClientInfo ");
//
// BindRecv()
//
bRet = BindRecv(pClientInfo);
if ( false == bRet ) {
return;
}
m_pMainDlg->OutputMsg("** Accepter Thread -- [클라이언트 접속] ip(%s) SOCKET(%d)",
inet_ntoa( stClientAddr.sin_addr) ,
pClientInfo->m_socketClient);
m_nClientCnt ++;
}
}
2. open commnunication channel for client
1번 과정에서 클라이언트가 접속을 하면 여기서는 올바른 소켓인지 판단하고, 접속한 소켓을 클라이언트 정보를 저장하는 배열에 저장하고, IOCP에 해당 소켓을 등록하고, 소켓에 읽기 작업을 신청한다. ( 읽기를 먼저 신청하는 이유는 대해서는 설명하지 않겠다. ) 이 때 중요한 것은 현재 WSARecv(), ReadFile()함수를 사용하여 읽기 작업을 신청했지만, IOCP는 작업의 완료 시에 읽기 작업을 끝냈는지 쓰기 작업을 끝냈는지 가르쳐 주지 않는다는 것이다. 따라서,OVERLAPPED구조체를 상속( C관점에서는 새로운 구조체를 선언할 때, OVERLAPPED구조체를 제일 첨에 위치하는 멤버로 선언하여 사용할 수 있다.)하여 읽기 작업을 하는 건지 쓰기 작업을 하는 건지에 대한 흔적을 남겨야 한다.
bool cIOCompletionPort::BindIOCompletionPort(stClientInfo * pClientInfo)
{
HANDLE hIOCP;
/// socket 과 pClientInfo를 CompletionPort객체와 연결 시킨다.
hIOCP = CreateIoCompletionPort( (HANDLE)pClientInfo->m_socketClient,
m_hIOCP,
reinterpret_cast( pClientInfo ),
0);
if ( NULL == hIOCP || m_hIOCP != hIOCP )
{
m_pMainDlg->OutputMsg("CreateIoCompletionPort() - [에러] 실패:%d:",GetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" BindIOCompletionPort() - 성공" );
///
return true;
}
bool cIOCompletionPort::BindRecv(stClientInfo * pClientInfo)
{
DWORD dwFlag = 0;
DWORD dwRecvNumBytes = 0;
// Overlapped I/O Setting
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.len = MAX_SOCKBUF;
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.buf =
pClientInfo->m_stRecvOverlappedEx.m_szBuf;
pClientInfo->m_stRecvOverlappedEx.m_eOperation = OP_RECV;
//// 입력 버퍼 클리어 ???????????????
//ZeroMemory(pClientInfo->m_stRecvOverlappedEx.m_szBuf, 1024);
int nRet = WSARecv( pClientInfo->m_socketClient,
&(pClientInfo->m_stRecvOverlappedEx.m_wsaBuf),
1,
&dwRecvNumBytes,
&dwFlag,
(LPWSAOVERLAPPED)&(pClientInfo->m_stRecvOverlappedEx),
NULL);
/// socket_error 이면 client socket이 끊어 진걸로 처리한다.
if ( nRet == SOCKET_ERROR && ( ERROR_IO_PENDING != WSAGetLastError() ) )
{
m_pMainDlg->OutputMsg("[에러] BindRecv - WSARecv() 실패 WSAGetLastError:%d:",WSAGetLastError() );
return false;
}
m_pMainDlg->OutputMsg("\n [] BindRecv - WSARecv() nRet : %d", nRet);
return true;
}
3. Read Request from client
이 과정에서 GetQueuedCompletionStatus() 함수를 사용하여 현재 등록된 소켓들 중에 읽기나 쓰기 작업이 완료된 것이 있는지 확인한다. 이 때,IOCP가 알려주는 정보는 2번 과정에서 등록 할 때, 소켓과 같이 입력했던 KEY와 I/O작업을 신청할 때, 인자로 넘겨주었던 OVERLLAPPED 구조체( 확장을 시켰다면 확장 구조체형으로 캐스팅 해주면 된다.)의 주소이다.
void cIOCompletionPort::ThreadWorker(void)
{
// CompletionKey를 받을 포인터 변수
stClientInfo * pClientInfo = NULL;
// 함수 호출 성공여부
BOOL bSuccess = TRUE;
// Overlapped I/O작업에서 전송된 데이타 크기
DWORD dwIoSize = 0;
// I/O 작업을 위해 요청한 Overlapped 구조체를 받을 포인터
LPOVERLAPPED lpOverlapped = NULL;
while ( m_bWorkerRun )
{
/**
이 함수로 인해 쓰래들들은 WaitingThread Queue에 대기상태로 들어간다
완료된 Overlapped I/O 작업이 발생하면 IOCP Queue에서 완료된 작업을 가져와 뒤처리
그리고 PostQueuedCompletionStatus()함수에 의해 사용자 메시지가 도착되면 쓰레드 종료
**/
bSuccess = GetQueuedCompletionStatus( m_hIOCP,
&dwIoSize, // 실제 전송된 바이트
(LPDWORD)&pClientInfo, // Completionkey
&lpOverlapped, // Overlappped I/O 객체
INFINITE); // 대기할 시간(무한대기)
// 클라이언트가 접속 끊었을 때
//
// FALSE == bSuccess
//
if ( FALSE == bSuccess && 0 == dwIoSize )
{
m_pMainDlg->OutputMsg("WorkerThread [클라이언트] SOCKET(%d) 접속 끊김", pClientInfo->m_socketClient);
CloseSocket(pClientInfo);
continue;
}
// 사용자 스레드 종료 메시지 처리
//
// TRUE == bSuccess
//
if ( TRUE == bSuccess && 0 == dwIoSize && NULL == lpOverlapped )
{
//
// WorkerThread 종료
//
m_bWorkerRun = false ;
continue;
}
if ( NULL == lpOverlapped ) {
continue;
}
stOverlappedEx * pOverlappedEx =(stOverlappedEx *)lpOverlapped;
// Overlapped I/O Recv 작업 결과 뒤 처리
//
// OP_RECV
//
if ( OP_RECV == pOverlappedEx->m_eOperation )
{
pOverlappedEx->m_szBuf[dwIoSize] = NULL;
m_pMainDlg->OutputMsg("----->>>>> WorkerThread [수신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
// 클라이언트에 메시지를 에코한다.
//BindRecv( pClientInfo );
//SendMsg(pClientInfo, pOverlappedEx->m_szBuf, dwIoSize );
//pOverlappedEx->m_eOperation = OP_SEND;
//BindRecv( pClientInfo );
SendMsg( pClientInfo, pOverlappedEx->m_szBuf, dwIoSize);
}
// Overlapped I/O Send 작업 결과 뒤 처리
//
// OP_SEND
//
else if ( OP_SEND == pOverlappedEx->m_eOperation )
{
m_pMainDlg->OutputMsg("<<<<<-------WorkerThread [송신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
//// 입력 버퍼 클리어 ???????????????
ZeroMemory(pOverlappedEx->m_szBuf, 1024);
BindRecv( pClientInfo );
}
else
{
m_pMainDlg->OutputMsg("WorkerThread [클라이언트] SOCKET(%d) 예외 상황 ",pClientInfo->m_socketClient);
}
lpOverlapped = NULL;
}
}
4. Execute request locally
이제3번 과정에서 받은 key와 OVERLAPPED구조체를 가지고 I/O작업을 신청했을 때, 만약 작업이 끝나면 했었을 작업을 해주면 된다.
5. return result to client
이 과정은 사실4번 과정과 같이 포함되는 부분이다. 즉, 채팅을 예로 들면, 어떤 사용자가 메시지를 입력했을 때, 4번과정의 If( pOverlapped->mode == 읽기 ) {} 블록안이 실행될 것이고, 어떤 메시지인지 확인한 후, 접속한 모든 사용자에게 메시지를 보내게 된다.
#include "StdAfx.h"
#include "cIOCompletionPort.h"
//// 흠
#include "IOCompletionPortDlg.h"
/// 쓰레드 만들기
/// WSARecv , WSASend의 Overlapped I/O 작업을 위한
unsigned int WINAPI CallWorkerThread(LPVOID p)
{
cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p;
pOverlappedEvent->ThreadWorker();
return 0;
}
unsigned int WINAPI CallAccepterThread(LPVOID p)
{
cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p;
pOverlappedEvent->ThreadAccepter();
return 0;
}
///
cIOCompletionPort::cIOCompletionPort(void)
{
/// 모든 멤버 변수들의 초기화
m_pMainDlg = NULL;
m_bWorkerRun = true;
m_bAccepterRun = true;
m_nClientCnt = 0;
m_hAccepterThread = NULL;
m_hIOCP = NULL;
m_socketListen = INVALID_SOCKET;
ZeroMemory(m_szBuf, 1024);
for ( int i=0; i < MAX_WORKERTHREAD; i++ ) {
m_hWorkerThread [i] = NULL;
}
m_pClientInfo = new stClientInfo[MAX_CLIENT];
}
cIOCompletionPort::~cIOCompletionPort(void)
{
// 윈속 사용 해재
WSACleanup();
// 다 사용한 객체 삭제
if ( m_pClientInfo )
{
delete[] m_pClientInfo;
m_pClientInfo = NULL;
}
}
bool cIOCompletionPort::InitSocket(void)
{
WSADATA wsaData;
// 윈속 버젼 2.2
int nRet = WSAStartup( MAKEWORD(2,2) , &wsaData);
if ( 0 != nRet ) {
m_pMainDlg->OutputMsg("[에러]WSAStartup() 실패:%d:",WSAGetLastError() );
return false;
}
m_socketListen = WSASocket(AF_INET, SOCK_STREAM,
IPPROTO_TCP, NULL, NULL, WSA_FLAG_OVERLAPPED );
if ( INVALID_SOCKET == m_socketListen ) {
m_pMainDlg->OutputMsg("[에러]WSASocket() 실패:%d:",WSAGetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" InitSocket() 성공" );
///
return true;
}
bool cIOCompletionPort::BindandListen(int nPort)
{
SOCKADDR_IN stServerAddr;
stServerAddr.sin_family = AF_INET;
stServerAddr.sin_port = htons(nPort);
stServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int nRet = bind( m_socketListen, (SOCKADDR *)&stServerAddr, sizeof(SOCKADDR_IN) );
if ( 0 != nRet ) {
m_pMainDlg->OutputMsg("[에러] bind() 실패:%d:",WSAGetLastError() );
return false;
}
nRet = listen( m_socketListen, 5 ) ;
if ( 0 != nRet ) {
m_pMainDlg->OutputMsg("[에러] listen() 실패:%d:",WSAGetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" BindandListen() 성공" );
///
return true;
}
bool cIOCompletionPort::CreateWorkerThread(void)
{
unsigned int uiThreadId = 0;
/// Waiting Thread Queue에 대기 상태로 넣을 쓰레드들 생성
/// 권장하는 개수 : cpu *2 +1
for ( int i =0; i < MAX_WORKERTHREAD; i++ )
{
m_hWorkerThread[i] = (HANDLE)_beginthreadex(NULL, 0,
&CallWorkerThread,
this,
CREATE_SUSPENDED,
&uiThreadId);
if ( m_hWorkerThread[i] == NULL )
{
m_pMainDlg->OutputMsg("[에러] CreateWorkerThread() 실패:%d:",GetLastError() );
return false;
}
ResumeThread( m_hWorkerThread[i] );
}
m_pMainDlg->OutputMsg(" CreateWorkerThread() 성공" );
///
return true;
}
bool cIOCompletionPort::CreateAccepterThread(void)
{
unsigned int uiThreadId = 0;
/// 클라이언트 접속 요청을 받은 쓰레드 생성
m_hAccepterThread = (HANDLE)_beginthreadex(NULL, 0,
&CallAccepterThread,
this,
CREATE_SUSPENDED,
&uiThreadId);
if ( m_hAccepterThread == NULL )
{
m_pMainDlg->OutputMsg("CreateAccepterThread() [에러] 실패:%d:",GetLastError() );
return false;
}
ResumeThread( m_hAccepterThread );
m_pMainDlg->OutputMsg(" CreateAccepterThread() 성공" );
///
return true;
}
////
bool cIOCompletionPort::BindIOCompletionPort(stClientInfo * pClientInfo)
{
HANDLE hIOCP;
/// socket 과 pClientInfo를 CompletionPort객체와 연결 시킨다.
hIOCP = CreateIoCompletionPort( (HANDLE)pClientInfo->m_socketClient,
m_hIOCP,
reinterpret_cast( pClientInfo ),
0);
if ( NULL == hIOCP || m_hIOCP != hIOCP )
{
m_pMainDlg->OutputMsg("CreateIoCompletionPort() - [에러] 실패:%d:",GetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" BindIOCompletionPort() - 성공" );
///
return true;
}
bool cIOCompletionPort::StartServer(void)
{
m_hIOCP = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
NULL,
NULL,
0);
if ( NULL == m_hIOCP )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateIoCompletionPort() 실패:%d:",GetLastError() );
return false;
}
bool bRet = CreateWorkerThread();
if ( false == bRet )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateWorkerThread() 실패:%d:",GetLastError() );
return false;
}
bRet = CreateAccepterThread();
if ( false == bRet )
{
m_pMainDlg->OutputMsg("StartServer [에러] CreateAccepterThread() 실패:%d:",GetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" StartServer() - CreateIoCompletionPort / Worker /Accepter 성공" );
///
return true;
}
bool cIOCompletionPort::BindRecv(stClientInfo * pClientInfo)
{
DWORD dwFlag = 0;
DWORD dwRecvNumBytes = 0;
// Overlapped I/O Setting
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.len = MAX_SOCKBUF;
pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.buf =
pClientInfo->m_stRecvOverlappedEx.m_szBuf;
pClientInfo->m_stRecvOverlappedEx.m_eOperation = OP_RECV;
//// 입력 버퍼 클리어 ???????????????
//ZeroMemory(pClientInfo->m_stRecvOverlappedEx.m_szBuf, 1024);
int nRet = WSARecv( pClientInfo->m_socketClient,
&(pClientInfo->m_stRecvOverlappedEx.m_wsaBuf),
1,
&dwRecvNumBytes,
&dwFlag,
(LPWSAOVERLAPPED)&(pClientInfo->m_stRecvOverlappedEx),
NULL);
/// socket_error 이면 client socket이 끊어 진걸로 처리한다.
if ( nRet == SOCKET_ERROR && ( ERROR_IO_PENDING != WSAGetLastError() ) )
{
m_pMainDlg->OutputMsg("[에러] BindRecv - WSARecv() 실패 WSAGetLastError:%d:",WSAGetLastError() );
return false;
}
m_pMainDlg->OutputMsg("\n [] BindRecv - WSARecv() nRet : %d", nRet);
return true;
}
bool cIOCompletionPort::SendMsg(stClientInfo * pClientInfo, char * pMsg, int nLen)
{
DWORD dwRecvNumBytes = 0;
//전송될 메시지를 복사
CopyMemory( pClientInfo->m_stSendOverlappedEx.m_szBuf, pMsg, nLen );
// Overlapped I/O Setting 정보
pClientInfo->m_stSendOverlappedEx.m_wsaBuf.len = nLen;
pClientInfo->m_stSendOverlappedEx.m_wsaBuf.buf =
pClientInfo->m_stSendOverlappedEx.m_szBuf;
pClientInfo->m_stSendOverlappedEx.m_eOperation = OP_SEND;
int nRet = WSASend( pClientInfo->m_socketClient,
&(pClientInfo->m_stSendOverlappedEx.m_wsaBuf),
1,
&dwRecvNumBytes,
0,
(LPWSAOVERLAPPED)&(pClientInfo->m_stSendOverlappedEx),
NULL);
if ( nRet == SOCKET_ERROR ) {
m_pMainDlg->OutputMsg("[에러] SendMsg - WSASend() nRet:%s:","SOCKET_ERROR" );
}
/// socket_error 이면 client socket이 끊어 진걸로 처리한다.
if ( nRet == SOCKET_ERROR && ( WSAGetLastError() != ERROR_IO_PENDING ) )
{
m_pMainDlg->OutputMsg("[에러] SendMsg - WSASend() 실패 WSAGetLastError:%d:",WSAGetLastError() );
return false;
}
m_pMainDlg->OutputMsg(" [] SendMsg - WSASend() ");
return true;
}
// 할당
stClientInfo * cIOCompletionPort::GetEmptyClientInfo(void)
{
for(int i = 0; i < MAX_CLIENT; i++ )
{
if(INVALID_SOCKET == m_pClientInfo[i].m_socketClient)
{
return &m_pClientInfo[i];
}
}
return NULL;
}
// 사용자 접속 받는 쓰레드
void cIOCompletionPort::ThreadAccepter(void)
{
SOCKADDR_IN stClientAddr;
int nAddrLen = sizeof(SOCKADDR_IN);
while ( m_bAccepterRun)
{
// 접속 받을 구조체 인덱스 얻기
stClientInfo * pClientInfo = GetEmptyClientInfo();
if ( NULL == pClientInfo )
{
m_pMainDlg->OutputMsg("** AccepterThread [에러] NULL == pClientInfo :%s:","Client FULL");
return ;
}
m_pMainDlg->OutputMsg("** AccepterThread -- accept : pClientInfo->m_socketClient");
// 클라이언트 접속 요청까지 대기
//
// accept()
//
pClientInfo->m_socketClient = accept ( m_socketListen,
(SOCKADDR *)&stClientAddr, &nAddrLen );
if ( INVALID_SOCKET == pClientInfo->m_socketClient ) {
continue;
}
m_pMainDlg->OutputMsg("** AccepterThread -- BindIOCompletionPort & 소켓 pClientInfo");
// I/O Completion Port객체와 소켓을 연결 시킨다.
//
// BindIOCompletionPort()
//
bool bRet = BindIOCompletionPort( pClientInfo );
if ( false == bRet ) {
return;
}
// Recv Overlapped I/O 작업을 요청한다
m_pMainDlg->OutputMsg("** AccepterThread -- BindRecv & pClientInfo ");
//
// BindRecv()
//
bRet = BindRecv(pClientInfo);
if ( false == bRet ) {
return;
}
m_pMainDlg->OutputMsg("** Accepter Thread -- [클라이언트 접속] ip(%s) SOCKET(%d)",
inet_ntoa( stClientAddr.sin_addr) ,
pClientInfo->m_socketClient);
m_nClientCnt ++;
}
}
void cIOCompletionPort::ThreadWorker(void)
{
// CompletionKey를 받을 포인터 변수
stClientInfo * pClientInfo = NULL;
// 함수 호출 성공여부
BOOL bSuccess = TRUE;
// Overlapped I/O작업에서 전송된 데이타 크기
DWORD dwIoSize = 0;
// I/O 작업을 위해 요청한 Overlapped 구조체를 받을 포인터
LPOVERLAPPED lpOverlapped = NULL;
while ( m_bWorkerRun )
{
/**
이 함수로 인해 쓰래들들은 WaitingThread Queue에 대기상태로 들어간다
완료된 Overlapped I/O 작업이 발생하면 IOCP Queue에서 완료된 작업을 가져와 뒤처리
그리고 PostQueuedCompletionStatus()함수에 의해 사용자 메시지가 도착되면 쓰레드 종료
**/
bSuccess = ( m_hIOCP,
&dwIoSize, // 실제 전송된 바이트
(LPDWORD)&pClientInfo, // Completionkey
&lpOverlapped, // Overlappped I/O 객체
INFINITE); // 대기할 시간(무한대기)
// 클라이언트가 접속 끊었을 때
//
// FALSE == bSuccess
//
if ( FALSE == bSuccess && 0 == dwIoSize )
{
m_pMainDlg->OutputMsg("WorkerThread [클라이언트] SOCKET(%d) 접속 끊김", pClientInfo->m_socketClient);
CloseSocket(pClientInfo);
continue;
}
// 사용자 스레드 종료 메시지 처리
//
// TRUE == bSuccess
//
if ( TRUE == bSuccess && 0 == dwIoSize && NULL == lpOverlapped )
{
//
// WorkerThread 종료
//
m_bWorkerRun = false ;
continue;
}
if ( NULL == lpOverlapped ) {
continue;
}
stOverlappedEx * pOverlappedEx =(stOverlappedEx *)lpOverlapped;
// Overlapped I/O Recv 작업 결과 뒤 처리
//
// OP_RECV
//
if ( OP_RECV == pOverlappedEx->m_eOperation )
{
pOverlappedEx->m_szBuf[dwIoSize] = NULL;
m_pMainDlg->OutputMsg("----->>>>> WorkerThread [수신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
// 클라이언트에 메시지를 에코한다.
//BindRecv( pClientInfo );
//SendMsg(pClientInfo, pOverlappedEx->m_szBuf, dwIoSize );
//pOverlappedEx->m_eOperation = OP_SEND;
//BindRecv( pClientInfo );
SendMsg( pClientInfo, pOverlappedEx->m_szBuf, dwIoSize);
}
// Overlapped I/O Send 작업 결과 뒤 처리
//
// OP_SEND
//
else if ( OP_SEND == pOverlappedEx->m_eOperation )
{
m_pMainDlg->OutputMsg("<<<<<-------WorkerThread [송신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
//// 입력 버퍼 클리어 ???????????????
ZeroMemory(pOverlappedEx->m_szBuf, 1024);
BindRecv( pClientInfo );
}
else
{
m_pMainDlg->OutputMsg("WorkerThread [클라이언트] SOCKET(%d) 예외 상황 ",pClientInfo->m_socketClient);
}
lpOverlapped = NULL;
}
}
void cIOCompletionPort::SetMainDlg(CIOCompletionPortDlg * pMainDlg)
{
/// .h에서 .cpp로 옮겨 놓았는데? 문제 없나?
m_pMainDlg = pMainDlg;
}
void cIOCompletionPort::DestroyThread(void)
{
// 1. 종료 버튼 클릭시
/
for(int i=0; i< MAX_WORKERTHREAD; i++)
{
// WaitingThreadQueue에서 대기중인 쓰레드에 사용자 종료 메시지 보내기
PostQueuedCompletionStatus( m_hIOCP,0,0,NULL);
}
// Worker Thread 종료
for(int i=0; i< MAX_WORKERTHREAD; i++)
{
CloseHandle( m_hWorkerThread[i] );
WaitForSingleObject ( m_hWorkerThread[i], INFINITE );
}
m_bAccepterRun = false;
// Accepter Thread 종료
closesocket( m_socketListen );
// Thread 종료
WaitForSingleObject( m_hAccepterThread, INFINITE );
}
void cIOCompletionPort::CloseSocket(stClientInfo * pClientInfo, bool bIsForce)
{
struct linger stLinger = {0,0};
if ( true ) {
// timeout=0으로 설정되어 강제 종료. 주의 : 데이타 손실 가능성
// right now !!!
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;
}