본문 바로가기
Programming/열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)

Ch 23. 내용 확인문제

by minjunkim.dev 2020. 8. 17.

모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!


01. Completion Port 오브젝트에는 하나 이상의 쓰레드가 할당되어서 입출력을 처리하게 된다. 그렇다면 Completion Port 오브젝트에 할당될 쓰레드는 어떻게 생성되며, 또 할당의 방법은 무엇인지 소스코드 레벨에서 설명해 보자.

 

- Completion Port 오브젝트에 할당될 쓰레드는 프로그래머가 생성해야 하며, 생성된 쓰레드는 Completion Port 오브젝트의 핸들을 인자로 받아서 GetQueuedCompletionStatus 함수 호출을 진행해야 한다.

 

- 위의 과정을 거치면 해당 쓰레드는 Completion Port 오브젝트에 할당이 된다.

 

- 즉, Completion Port 오브젝트의 할당은 소스코드 레벨에서 보면 GetQueuedCompletionStatus 함수의 호출을 의미한다.

 

- 단, Completion Port 오브젝트를 생성(CreateIoCompletionPort 첫번째 호출시)할 때 지정한, Completion Port 오브젝트에 할당할 최대 쓰레드의 수를 넘기면, 쓰레드가 이 함수를 호출하더라도 응답을 받는 쓰레드는 수는 지정한 최대 쓰레드의 수로 제한된다.


02. CreateCompletionPort 함수는 다른 함수들과 달리 두 가지의 기능을 제공한다. 그렇다면 이 두 가지 기능은 각각 무엇인가?

 

1) Completion Port 오브젝트의 생성

2) Completion Port 오브젝트와 소켓과의 연결


03. Completion Port 오브젝트와 소켓의 연결이 의미하는 바는 무엇인가? 그리고 연결의 과정은 어떻게 진행해야 하는가?

 

- CreateIoCompletionPort 함수호출을 통해서 Completion Port 오브젝트를 생성하고, 다시 CreateIoCompletionPort 함수를 호출해서, 인자로 전달되는 핸들의 소켓이 IO가 완료되면 그 정보를 생성한 Completion Port 오브젝트에 등록되도록 한다.


04. IOCP와 관련된 다음 문장들 중에서 옳지 않은 것을 모두 고르면?

 

c. IO가 완료되었을 때, 이와 관련된 Completion Routine이 자동으로 호출되기 때문에 IO의 완료를 대기하기 위해서 별도의 함수를 호출할 필요가 없다. X

=> IOCP에서는 CR을 등록하지 않고, 쓰레드를 alertable wait 상태로 만들지 않으므로 CR이 호출되지 않는다.

CreateIoCompletionPort 함수 호출로 Completion Port 오브젝트 생성 및 소켓과 연결하고, GetQueuedCompletionStatus 함수 호출로 완료된 IO의 정보를 확인해야 한다. GetQueuedCompletionStatus는 인자에 INFINITE가 전달되었을 때, IO가 완료되고 이에 대한 정보가 CP 오브젝트에 등록되었을 때 반환이 된다.

 

d. IOCP는 윈도우 이외의 다른 운영체제에서도 제공되는 기능이기 때문에 호환성에 있어서도 높은 점수를 줄 수 있다. X

=> IOCP는 윈도우 운영체제에서만 제공되는 기능이다.

 

# 옳은 설명

 

a. 최소한의 쓰레드로 다수의 IO를 처리할 수 있는 구조이기 때문에 쓰레드의 컨텍스트 스위칭으로 인한 성능의 저하를 막을 수 있다. O

 

b. IO가 진행중인 상태에서 서버는 IO의 완료를 기다리지 않고, 다른 일을 진행할 수 있기 때문에 CPU를 효율적으로 사용할 수 있는 구조이다. O


05. 다음 문장들 중에서 IOCP에 할당할 적정 쓰레드의 수를 결정하는 방법으로 적절하면 O, 적절치 않으면 X를 표시해보자.

 

- 일반적인 선택은 CPU의 수와 동일한 수의 쓰레드를 할당하는 것이다. O

 

- 가장 좋은 방법은 여건이 허락하는 범위 내에서 실험적인 결과를 통해서 쓰레드의 수를 결정하는 것이다. O

 

- 할당할 쓰레드의 수는 여유 있게 선택하는 것이 좋다. 예를 들어서 한 개의 쓰레드로 충분한 상황에서는 여유 있게 세 개 정도의 쓰레드를 IOCP에 할당하는 것이 좋다. X


06. 이번 Chapter에서 설명한 IOCP 모델을 바탕으로 채팅 서버를 구현해보자. 이 채팅 서버는 Chapter 20에서 소개한 채팅 클라이언트인 예제 chat_clnt_win.c와 함께 동작이 가능해야 한다. 참고로 본문에서 제시한 IOCP 예제는 하나의 사례일 뿐이니, 이 틀에 꼭 맞추려고 노력하지 않아도 된다. 이 틀에 완벽히 맞추려다 보면 오히려 구현이 어렵게 느껴질 수 있다.

 

# chat_clnt_win.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>

#define BUF_SIZE 100
#define NAME_SIZE 20

/* 쓰레드의 main 함수, 입출력 루틴 분할 */
unsigned WINAPI SendMsg(void * arg);
unsigned WINAPI RecvMsg(void * arg);
void ErrorHandling(char * msg);

char name[NAME_SIZE]="[DEFAULT]";
char msg[BUF_SIZE];

int main(int argc, char * argv[])
{
    WSADATA wsaData;
    SOCKET hSock;
    SOCKADDR_IN servAdr;
    HANDLE hSndThread, hRcvThread;
    
    if(argc!=4)// 실행파일의 경로/IP/PORT번호/채팅닉네임 을 입력으로 받아야 함
    {
        printf("Usage: %s <IP> <port> <name> \n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0) // 윈속 라이브러리 초기화
    	ErrorHandling("WSAStartup() error!");
        
    sprintf(name,"[%s]",argv[3]);
    hSock=socket(PF_INET,SOCK_STREAM,0); // TCP 소켓 생성
    
    /* 서버 주소정보 초기화 */
    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(hSock,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)
        ErrorHandling("connect() error");
    
    /* 쓰레드 생성 및 실행, 입출력 루틴 분할 */
    hSndThread=(HANDLE)_beginthreadex(NULL,0,Sendmsg,(void*)&hSock,0,NULL);
    hRcvThread=(HANDLE)_beginthreadex(NULL,0,Recvmsg,(void*)&hSock,0,NULL);
    
    /* 각 쓰레드가 종료되어 signaled 상태가 될때까지 블로킹 */
    WaitForSingleObject(hSndThread,INFINITE);
    WaitForSingleObject(hSndThread,INFINITE);
    
    closesocket(hSock); // 클라이언트 소켓 소멸
    WSACleanup(); // 윈속 라이브러리 해제
    return 0;
}

unsigned WINAPI SendMsg(void * arg) // send thread main
{
    SOCKET hSock=*((SOCKET*)arg)
    char nameMsg=[NAME_SIZE+BUF_SIZE];
    
    while(1)
    {
        fgets(msg,BUF_SIZE,stdin);
        if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n"))
        {
            closesocket(hSock); // 클라이언트 소켓 종료
            exit(EXIT_SUCCESS);
        }
        sprintf(nameMsg,"%s %s",name,msg);
        send(hSock,nameMsg,strlen(nameMsg),0); // 채팅닉네임과 채팅내용을 채팅서버로 송신
    }
}

unsigned WINAPI RecvMsg(void * arg) // read thread main
{
    SOCKET hSock=*((SOCKET*)arg)
    char nameMsg=[NAME_SIZE+BUF_SIZE];
    int strLen;
    
    while(1)
    {
        /* 서버로부터 온 데이터를 수신 */
        strLen=recv(hSock,nameMsg,NAME_SIZE+BUF_SIZE-1,0);
        if(strLen==-1)
            return -1;
        nameMsg[strLen]='\0'; // C-str 문자열을 만들기 위해 널문자 추가
        fputs(nameMsg,stdout); // 클라이언트의 표준출력에 문자열 출력
    }
}

void ErrorHandling(char * msg)
{
    fputs(msg, stdout);
    fputc('\n', stdout);
    exit(EXIT_FAILURE);
}

 

# IOCP_chat_serv_win.c


[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어