노출되는 이미지가 불편하시겠지만 양해를 구합니다. 노출, 클릭등에 관한 자료로 활용 중입니다.
IO Completion Port 작성 하기
1. MFC 프로젝트 - 다이얼로그 방식
프로젝트명 : IOCompletionPort
생성 :
자동 ( IOCompletionPortDlg.h / IOCompletionPortDlg.cpp / class CIOCompletionPort() {} /
로 생김(자동으로 C가 붙음) )
2. 다이얼로그에 ListBox Control 추가
---- 흠
3. 버튼을 옮겨서 : 서버 시작 버튼을 만들고.....클릭시 함수처리부분에서 호출 및 사용될 class 정의
4. class 정의 : cIOCompletionPort
5. 클래스 마법사에서 ListBox 멤버 변수 작성 - IOCompletionPortDlg.cpp
6. ListBox에 문자 출력하는 함수 작성 - IOCompletionPortDlg.cpp
IOCompletionPortDlg.h
// 흠 흠 흠
#include "afxwin.h"
#include "resource.h"
#include "cIOCompletionPort.h"
#define LISTEN_PORT 8000
///..........
public:
void OutputMsg(char * szOutputString,...);
CListBox m_ctOutput;
boolean m_bServerStarted;
cIOCompletionPort m_IOCompletionPort;
afx_msg void OnBnClickedStartserver();
afx_msg void OnBnClickedCancel();
IOCompletionPortDlg.cpp
/// 사용자 추가
void CIOCompletionPortDlg::OutputMsg(char * szOutputString,...)
{
char szOutStr[1024];
va_list argptr;
va_start(argptr, szOutputString) ;
vsprintf( szOutStr, szOutputString, argptr );
va_end(argptr);
// CListBox에 추가
// 속성 - > 구성속성->일반 -> 멀티 바이트 문자 조합 MBCS
//m_ctOutput.SetCurSel( m_ctOutput.AddString( szOutStr ) );
m_ctOutput.InsertString(0, szOutStr) ;
}
void CIOCompletionPortDlg::OnBnClickedStartserver()
{
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
if ( ! m_bServerStarted ) {
OutputMsg("0.----서버 ");
m_IOCompletionPort.SetMainDlg(this);
bool bRet = m_IOCompletionPort.InitSocket();
OutputMsg("1.----포트:%d", LISTEN_PORT );
m_IOCompletionPort.BindandListen(LISTEN_PORT);
OutputMsg("2.----가동 " );
m_IOCompletionPort.StartServer();
m_bServerStarted=true;
} else {
OutputMsg("****************서버 가동중 : 누르지 마세요 ******************");
}
}
void CIOCompletionPortDlg::OnBnClickedCancel()
{
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
m_IOCompletionPort.DestroyThread();
CDialogEx::OnCancel();
}
cIOCompletionPort.h
// 1. 패킷 사이즈
#define MAX_SOCKBUF 1024
// 2. 클라이언트 수
#define MAX_CLIENT 1024
// 3. 쓰레스 수
#define MAX_WORKERTHREAD 4
enum enumOperation {
OP_RECV,
OP_SEND
};
/// WSAOVERLAPPED 구조체를 확장시켜 필요한 정보 추가
struct stOverlappedEx {
WSAOVERLAPPED m_wsaOverlapped; // Overlapped I/O 구조체
SOCKET m_socketClient; // client socket
WSABUF m_wsaBuf; // Overlapped I/O 작업 버퍼
char m_szBuf[MAX_SOCKBUF]; // 데이타 버퍼
enumOperation m_eOperation; // 작업 동작 종류
};
/// client 정보를 담기 위한 구조체
struct stClientInfo {
SOCKET m_socketClient; // 클라이언트와 연결되는 소켓
stOverlappedEx m_stRecvOverlappedEx; // Recv Overlapped I/O 작업을 위한 변수
stOverlappedEx m_stSendOverlappedEx; // Send Overlapped I/O 작업을 위한 변수
/// 생성자에서 멤버 변수들을 초기화
stClientInfo()
{
m_socketClient = INVALID_SOCKET;
ZeroMemory( &m_stRecvOverlappedEx, sizeof(m_stRecvOverlappedEx) );
ZeroMemory( &m_stSendOverlappedEx, sizeof(m_stSendOverlappedEx) );
}
};
class CIOCompletionPortDlg;
class cIOCompletionPort
{
public:
cIOCompletionPort(void);
~cIOCompletionPort(void);
bool InitSocket(void);
bool BindandListen(int nPort);
bool StartServer(void);
bool CreateWorkerThread(void);
bool CreateAccepterThread(void);
stClientInfo * GetEmptyClientInfo(void);
bool BindIOCompletionPort(stClientInfo * pClientInfo);
bool BindRecv(stClientInfo * pClientInfo);
bool SendMsg(stClientInfo * pClientInfo, char * pMsg, int nLen);
void WorkerThread(void);
void AccepterThread(void);
void SetMainDlg(CIOCompletionPortDlg * pMainDlg);
void DestroyThread(void);
void CloseSocket(stClientInfo * pClientInfo, bool bIsForce=false);
private:
// 1. 클라이언트 정보 저장 구조체
stClientInfo * m_pClientInfo;
// 2. 클라이언트 접속을 받기위한 리슨 소켓
SOCKET m_socketListen;
// 3. 접속 되어 있는 클라이언트 수
int m_nClientCnt;
// 4. 메인 윈도우 포인터
CIOCompletionPortDlg * m_pMainDlg;
// 5. 작업 스레드 핸들
HANDLE m_hWorkerThread[MAX_WORKERTHREAD];
// 6. 접속 스레드 핸들
HANDLE m_hAccepterThread;
// 7. CompletionPort 객체 핸들
HANDLE m_hIOCP;
// 8. 작업 스레드 동작 플래그
bool m_bWorkerRun;
// 9. 접속 스레드 동작 플래그
bool m_bAccepterRun;
// 10. 소켓 버퍼
char m_szBuf[1024];
};
cIOCompletionPort.cpp
#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->WorkerThread(); return 0; } unsigned int WINAPI CallAccepterThread(LPVOID p) { cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p; pOverlappedEvent->AccepterThread(); 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("[에러] CreateIoCompletionPort() 실패:%d:",GetLastError() ); return false; } bool bRet = CreateWorkerThread(); if ( false == bRet ) { m_pMainDlg->OutputMsg("[에러] CreateWorkerThread() 실패:%d:",GetLastError() ); return false; } bRet = CreateAccepterThread(); if ( false == bRet ) { m_pMainDlg->OutputMsg("[에러] CreateAccepterThread() 실패:%d:",GetLastError() ); return false; } m_pMainDlg->OutputMsg(" StartServer() 성공" ); /// 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("[알림] BindRecv WSARecv() 성공"); 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::AccepterThread(void) { SOCKADDR_IN stClientAddr; int nAddrLen = sizeof(SOCKADDR_IN); while ( m_bAccepterRun) { // 접속 받을 구조체 인덱스 얻기 stClientInfo * pClientInfo = GetEmptyClientInfo(); if ( NULL == pClientInfo ) { m_pMainDlg->OutputMsg("[에러] NULL == pClientInfo :%s:","Client FULL"); return ; } // 클라이언트 접속 요청까지 대기 pClientInfo->m_socketClient = accept ( m_socketListen, (SOCKADDR *)&stClientAddr, &nAddrLen ); if ( INVALID_SOCKET == pClientInfo->m_socketClient ) { continue; } // I/O Completion Port객체와 소켓을 연결 시킨다. bool bRet = BindIOCompletionPort( pClientInfo ); if ( false == bRet ) { return; } // Recv Overlapped I/O 작업을 요청한다 bRet = BindRecv(pClientInfo); if ( false == bRet ) { return; } m_pMainDlg->OutputMsg("[클라이언트 접속] ip(%s) SOCKET(%d)", inet_ntoa( stClientAddr.sin_addr) , pClientInfo->m_socketClient); m_nClientCnt ++; } } void cIOCompletionPort::WorkerThread(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("[클라이언트] 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("[수신] ( %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("[송신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf); //// 입력 버퍼 클리어 ??????????????? ZeroMemory(pOverlappedEx->m_szBuf, 1024); BindRecv( pClientInfo ); } else { m_pMainDlg->OutputMsg("[클라이언트] SOCKET(%d) 예외 상황 ",pClientInfo->m_socketClient); } lpOverlapped = NULL; } } void cIOCompletionPort::SetMainDlg(CIOCompletionPortDlg * pMainDlg) { /// .h에서 .cpp로 옮겨 놓았는데? 문제 없나? m_pMainDlg = pMainDlg; } void cIOCompletionPort::DestroyThread(void) { for(int i=0; i< MAX_WORKERTHREAD; i++) { // WaitingThreadQueue에서 대기중인 쓰레드에 사용자 종료 메시지 보내기 PostQueuedCompletionStatus( m_hIOCP,0,0,NULL); } 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; }
'Application, App > VC++' 카테고리의 다른 글
OnTimer - SetTimer (0) | 2016.12.15 |
---|---|
Packet Class // 바이트 버퍼링 전송 수신 send receive (0) | 2016.12.15 |
Stack with std::list (0) | 2016.12.15 |
Effective C++ 목차 (0) | 2016.12.15 |