정보공간_1

[7기 대구 유용수] IO Completion Port 4장. IOCP구조체와 함수, 실습 본문

IT 놀이터/Elite Member Tech & Talk

[7기 대구 유용수] IO Completion Port 4장. IOCP구조체와 함수, 실습

알 수 없는 사용자 2015. 5. 25. 18:52

Overlapped 구조체

 이제 드디어 본격적인 IOCP모델에 대해서 배워 보겠습니다. 먼저 우리는 IOCP모델에서 사용할 구조체들과 함수들을 정확히 숙지하고 넘어가야합니다. 앞장에서 IOCP는 중첩 입출력모델의 확장이라고 표현했습니다. 때문에 IOCP에서는 기본적으로 중첩입출력모델의 구조체와 함수들을 사용합니다.

1. Overlapped 구조체

2. WSABUF

해당 구조체들은 내부적으로 운영체제와 유기적인 관계를 맺고 있는 구조체입니다. 해당 구조체들을 이용해서 IOCP의 장점을 모두 살릴 수 있습니다.

3. WSASocket()

Overlapped IO나 IOCP에서는 소켓을 생성할때 해당 함수를 이용해야 하고, 소켓 속성을 꼭 WSA_FLAG_OVERLAPPED로 맞춰 주어야 합니다. 인자에 대한 정보들은 2장에서 모두 설명 하였기 때문에 넘어가겠습니다.

4. CreatIoCompletionPort() - Create

해당 함수는 완료포트를 생성하기 위한 함수 입니다. 생성할 때는 서버의 중심 핸들로 잡아 주어야 합니다(hComPort)클라이언트와의 연결때도 같은 함수를 사용하기에 사용하실때 유의해서 사용하여야 합니다. 아래쪽은 클라이언트와의 연결시 사용 방법입니다.

5. CreateIoCompletionPort() - Connect

핸들의 인자를 클라이언트의 핸들을 넘겨 주어야 하고, 현재 코드에서는 pClientInfo라는 클래스에 접속된 클라이언트의 정보를 관리 하기에 DWORD로 형변환을 하여 3번째 인자에 키를 넘겨주었습니다.

6. GetQueuedCompletionStatus()

바로 해당 함수가 완료통보를 받을 함수 입니다. 이때 아까 연결시에 키값으로 던진 ClientInfo클래스가 그대로 넘어 오게 됩니다.

7. WSASend()

8. WSARecv()

이 까지가 IOCP개발에 알아야 될 구조체들과 함수들입니다. 이제 실습으로 넘어가겠습니다.

실습

실습.zip

 해당 파일을 다운받으시면 client폴더와 Server폴더가 있습니다. client는 접속과 데이터 전달을 위한 최소한의 코드만 작성되어 있습니다.

 서버 폴더를 임포트하면 해당 그림처럼 소스가 있을 것 입니다. 딱 보기만 봐도 어떤 파일인지 아시겠지만, 간략히 설명 하고 넘어가겠습니다.

ClientInfo = 클라이언트에 대한 정보를 담고 있는 클래스입니다. 해당 클래스에 위에 설명한 구조체들과 유저레벨에 필요한 변수들이 할당 되어 있습니다.

Server = IOCP서버를 구동하고 데이터를 주고받는 역할을 하는 클래스입니다. 해당 클래스에 위에 설명한 함수들이 모두 구현되어 있습니다.

PacketProcess = 해당 클래스는 특정 패킷을 받았을때, 패킷 종류에따른 작업을 하기위에 만들어 놓은 클래스입니다. 해당클래스는 작업은 Server에서 호출하고, 클래스의 함수들은 모두 함수포인터배열로 되어있습니다.(패킷에 따른 case문으로 코드의 복잡화 해소를 위한 클래스) 해당 클래스에서 Server의 송/수신 함수를 호출 하여야 하기에 Server가 상속받은 ServerWork라는 인터페이스를 정의하고 있습니다.

헤더파일을 열어보시면 각각의 클래스에 주석을 자세히 달아 놓았으니 한번 쭉 일어보시기 바랍니다.

1. Server.cpp

 생성자와 소멸자 - 에서는 당연히 해당 클래스가 가지고있는 변수와 인스턴스들에 대한 초기화와, 메모리 해제가 있습니다.

 다음 줄에는 작업 스레드와 승인 스레드를 호출 하기 위한 WINAPI함수들을 구현했습니다.

 Init() 함수에서는 윈속의 초기화와 호스트의 cpu 갯수를 알아 오기위한 시스템정보를 담고 있습니다. 적절한 쓰레드 갯수 생성을 위해 cpu갯수를 담아옵니다.

 CloseSocket() - 현재 클라이언트의 접속을 종료하는 일을 하고 있습니다.

 CreateSocket() - 드디어 WSASocket함수를 호출하여 소켓을 생성하고 있습니다.

 BindSocket() - 주소정보를 설정하고, 소켓을 해당 포트에 바인드 하는 일을 합니다.

 ListenSocket() - 소켓에대한 접속대기를 요청합니다.

 CreateIOCP() - 이제 완료 큐를 사용하기 위해 CompletionPort를 서버의 Handle로 생성하고 있습니다.

StartServer() - 이제 모든 준비가 되었기에 입출력과 클라이언트의 접속승인을위한 스레드를 생성하는 함수를 호출합니다.

 CreateAcceptThread() - 이제 클라이언트의 접속승인하는 스레드를 생성합니다. 아까 위에서 만들어 놓은 WINAPI함수중 CallAcceptThread를 생성합니다. 해당 함수는 Server::AcceptThread를 호출합니다.

 AcceptThred() - 클라이언트 배열의 마지막 주소를 가져와서 접속승인후에 해당 클래스에 클라이언트 정보를 담습니다. 그리고 바로 CreateIoCompletionPort함수를 이용해서 해당 클라이언트 클래스의 핸들을 완료포트에 연결하고, 작업을 수행 할 키값으로 클라이언트정보 클래스를 DWORD로 캐스팅하여 넘겨줍니다. 후에 Recv함수를 호출 해 놓습니다.

 CreateWorkerThread() - AceeptThread와 같이 Server::WorkerThread를 호출하는 앞서 작성해 놓은 WINAPI CallWorkerThread를 호출합니다.

 WorkerThread() - 해당 함수가 가장 중요한 함수 입니다. 큐에 작업이 담기게 되면 해당 함수내의 GetQueuedCompletionStatus함수가 활성화 되고, 그렇지 않으면 대기하게 됩니다. 이때 2번째 인자(dwIoSize)에 들어온 데이터의 길이가 담기게 되고, 3번째 인자인 클라이언트 정보 클래스에 작업한 클라이언트가 들어오게 됩니다. 그리고 클라이언트 정보 클래스 내부에 stOverlappedEX구조체의 첫번째 변수가 Overlapped구조체 이기 때문에, 첫번째 주소를 가지고 있어 398번 라인의
stOverlappedEX* pOverlappedEx = (stOverlappedEX*)lpOverlapped;
이 대입이 성립이 됩니다.

여기서 이제 작업 상황이 송신이냐 수신이냐에 따라서 다른 작업을 하게되고, 수신을 경우에 정수로 된 opCode에 따라서 PacketProcess의 작업 함수를 호출하게 됩니다.

2. PacketProcess

해당 클래스는 opCode에 따른 작업을 하는 클래스입니다.

생성자() - 함수포인터배열에 따라서 각각의 함수를 초기화 대입합니다.

CallPacketProcess() - Server클래스에서 해당 함수를 호출해서 해당 클래스의 opCode에 맞는 함수를 자동으로 호출하게 됩니다.

Zero(),One() - 해당 함수는 테스트용으로 만들어 놓은 opCode가 0일때와 1일때의 호출 작업을 작성 해 놓은 코드입니다.

3. 실행

 먼저 서버를 실행 하게 되면

 해당 화면처럼 출력됩니다. 이때 Thread수는 호스트컴퓨터 cpu갯수에 따라서 달라집니다.

 다음으로 클라이언트를 실행하게 되면

 해당 그림과 같은 화면이 뜨게되고, 서버에서 설정해놓은 opcode는 0과 1입니다. 해당 클라이언트 소스는 아무 문자열을 입력했을때, flag값을 이용하며 보낼때 마다 0과 1로 바껴서 opCode가 전송되게 됩니다. 

보시는 바와 같이 클라이언트고 계속 opCode를 바꿔가며 보내고, 서버도 바꿔가면서 수신하고 있습니다. 

 여기 까지가 IOCP실습 이었습니다. 해당 예제코드는 아주 기본적인 코드이기 때문에 실제로 사용하실때는 접속이나 종료에 정교한 예외처리가 필요합니다. 이까지 봐주신 모든 분들께 감사드리고, 모두 개발 열심히 합니다!!