노출되는 이미지가 불편하시겠지만 양해를 구합니다. 노출, 클릭등에 관한 자료로 활용 중입니다.

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
블로그 이미지

StartGuide

I want to share the basic to programming of each category and how to solve the error. This basic instruction can be extended further. And I have been worked in southeast Asia more than 3 years. And I want to have the chance to work another country.

,