# Part 03 윈도우즈 기반 프로그래밍

## Chapter 19 Windows에서의 쓰레드 사용

### 19-1 커널 오브젝트 (Kernel Objects)

운영체제가 만드는 리소스 : 프로세스, 쓰레드, 파일, 세마포어, 뮤텍스 등 커널 오브젝트

* 리소스 관리 목적으로 정보 기록하기 위해 생성한 데이터 블록
* 소유자는 프로세스가 아니라 커널(운영체제)
* 즉, 생성, 관리, 소멸까지 모두 운영체제의 몫

### 19-2 윈도우 기반의 쓰레드 생성

프로그램이 시작될 때 main 함수를 호출하는 주체는 쓰레드 프로세스는 자원/주소 공간, 쓰레드는 실행 흐름 윈도우 쓰레드 소멸 시점은 쓰레드가 맨 처음 호출된 main 함수가 반환하는 시점

`CreateThread()`

* 쓰레드 생성
* 운영체제가 관리를 위해 커널 오브젝트도 생성
* 커널 오브젝트 구분자인 핸들도 반환
* 만들어진 쓰레드는 C/C++ 표준 함수에 대해 안정적으로 동작하지 않음
  * `_beginthreadex()` 쓰면 해결됨

```c
int main(int argc, char* argv[])
{
	HANDLE hThread;
	unsigned threadID;
	int param = 5;

	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);
	puts("end of main");
	return 0;
}
unsigned WINAPI ThreadFunc(void* arg)
{
	int i;
	int cnt = *((int*)arg);
	for (i = 0; i < cnt; i++)
	{
		Sleep(1000); puts("running thread");
	}
	return 0;
}
```

* `_beginthreadex()`의 반환 값은 원래 unsinged인데, 역시 정수형인 HANDLE로 저장
* `_beginthreadex()`가 요구하는 호출규약 지키기 위해 WINAPI 삽입

쓰레드를 구분하는 값에는 ID도 있고 핸들도 있다

* 핸들 : 프로세스 달라지면 중복 가능
* ID : 프로세스 영역 넘어서도 중복 안됨

메인 쓰레드 소멸하면 생성된 쓰레드들 다 사라져서, 5개가 아니라 3개만 호출되고 끝난다.

### 19-3 커널 오브젝트의 두 가지 상태

signaled 상태 : 종료됨 non-signaled 상태 : 안 종료됨

프로세스나 쓰레드 종료되면 해당 커널 오브젝트는 signaled 상태로 변경됨

`WaitForSingleObject()`

* 커널 오브젝트에 대해 signaled 상태인지 확인
* signaled 상태가 되면 반환
* auto-reset 모드 커널 오브젝트 : 반환되면 다시 non-signaled
* manual-reset 모드 커널 오브젝트 : 반환되도 다시 안 돌아감

`WaitForMultipleObjects()`

* 여러 커널 오브젝트 대상으로 상태 확인

```c
	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	
	if((wr=WaitForSingleObject(hThread, INFINITE))==WAIT_FAILED)
	{
		puts("thread wait error");
		return -1;
	}

	printf("wait result: %s \n", (wr == WAIT_OBJECT_0) ? "signaled" : "time-out");
```

쓰레드 기다려주면 5개 다 출력된다

## Chapter 20 Windows에서의 쓰레드 동기화

### 20-1 동기화 기법의 분류와 `CRITICAL_SECTION` 동기화

윈도우 운영체제 연산 방식 : dual-mode operation

* 유저 모드
  * 응용 프로그램 실행되는 기본 모드
  * 물리 영역 접근 불가
  * 접근 메모리 영역 제한
* 커널 모드 : 운영체제 실행될 때의 모드
  * 메모리, 하드웨어 접근 제한 없음

유저모드 동기화 : 커널 모드 전환 안 해서 속도 빠르다 커널모드 동기화 : 기능 더 많다

* Dead-lock 막기 위해 타임아웃 지정 가능.
* 서로 다른 프로세스 두 쓰레드 간 동기화도 가능
  * 커널 오브젝트 기반이기 때문에 가능

#### `CRITICAL_SECTION` 기반의 동기화

`CRITICAL_SECTION` 오브젝트

* `CRITICAL_SECTION` 기반 동기화할 때 생성되어 활용됨
* 커널 오브젝트와는 다르다
* 임계영역 진입에 필요한 일종의 열쇠

```c
CRITICAL_SECTION cs;

int main(int argc, char *argv[])
{
    HANDLE tHandles[NUM_THREAD];
    int i;

    InitializeCriticalSection(&cs);
    for (i = 0; i < NUM_THREAD; i++)
    {
        if(i%2)
            tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
        else
            tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
    }

    WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
    DeleteCriticalSection(&cs);
    printf("result: %lld \n", num);
    return 0;
}

unsigned WINAPI threadInc(void* arg)
{
    int i;
    EnterCriticalSection(&cs);
    for (i = 0; i < 500000; i++)
        num += 1;
    LeaveCriticalSection(&cs);
    return 0;
}
```

### 20-2 커널모드 동기화 기법

#### Mutex 오브젝트 기반 동기화

관련 함수

* `CreateMutex()`
* `CloseHandle()`
* `ReleaseMutex()`
* `WaitForSingleObject()` : Mutex는 이거 반환될 때 자동으로 non-signaled 상태 되는 auto-reset 모드 커널 오브젝트임

#### Semaphore 오브젝트 기반 동기화

관련 함수

* `CreateSemaphore()`
* `CloseHandle()`
* `ReleaseSemaphore()`
* `WaitForSingleObject()`

#### Event 오브젝트 기반 동기화

Event 동기화 오브젝트는 auto-reset 모드와 manual-reset 모드 택일 가능 관련 함수

* `CreateEvent()` : 두 번째 인자 TRUE면 manual-reset
* `ResetEvent()` : non-signaled 상태로 변경
* `SetEvent()` : signaled 상태로 변경

```c
// 둘 이상의 쓰레드가 동시에 대기상태 빠져나오는 예제
int main(int argc, char *argv[])
{
    HANDLE hThread1, hThread2;
    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // manual-reset 모드로 non-signal 상태인 Event 생성
    hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
    hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);

    fputs("Input string: ", stdout);
    fgets(str, STR_LEN, stdin);
    SetEvent(hEvent);
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);
    ResetEvent(hEvent); // 여기서 hEvent 해금하면 쓰레드1, 쓰레드2 둘 다 실행됨
    CloseHandle(hEvent);
    return 0;
}

unsigned WINAPI NumberOfA(void* arg)
{
    int i, cnt = 0;
    WaitForSingleObject(hEvent, INFINITE);
    for (i = 0; str[i] != 0; i++)
    {
        if(str[i]=='A')
            cnt++;
    }
    printf("Num of A: %d \n", cnt);
    return 0;
}
```

## Chapter 21 Asynchronous Notification IO 모델

### 21-1 비동기 Notification IO 모델의 이해

윈도우에서 데이터 입출력할 때 send, recv로 동기화된 입출력 진행했다. send는 출력 버퍼에 데이터 전송돼야 반환, recv는 원하는 만큼 데이터 읽은 후에 반환.

비동기 입출력 : 함수 반환 시점과 데이터 송수신 완료시점 일치 하지 않음

Notification IO : IO 관련 상황 발생했는가?

* select는 IO 필요하거나 가능한 상황일 때만 반환
* 비동기로 만들면 IO 관찰 후 다른 일 하다가 상태 변화 확인 가능

### 21-2 비동기 Notification IO 모델의 이해와 구현

Nofitication : IO 상태 변화 알림

IO 상태 변화

* 소켓에 대한 IO의 상태변화
* 소켓에 대한 IO 관련 이벤트의 발생

`WSAEventSelect()`

* 임의의 소켓 대상 이벤트 발생 관찰
* 인자로 전달된 소켓에서 INetworkEvents에 전달된 이벤트 중 하나 발생하면,
* hEventObject에 전달된 핸들의 커널 오브젝트를 signaled 상태로 바꾼다
* 즉, Event 오브젝트와 소켓을 연결한다
* 이벤트 발생유무 상관 없이 바로 반환. 비동기 Notification임.

`WSACreateEvent()` : manual-reset 모드면서 non-signaled 상태인 Event 오브젝트 생성 `WSACloseEvent()` : Event 오브젝트 종료 `WSAWaitForMultipleEvents()` : 이벤트 발생 유무 확인 `WSAEnumNetworkEvents()` : 해당 오브젝트가 signaled된 원인 확인. 확인한 오브젝트는 non-signaled로 되돌려준다.

```c
    hSockArr[numOfClntSock] = hServSock;
    hEventArr[numOfClntSock] = newEvent;
    numOfClntSock++;

    while(1)
    {
        posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
        startIdx = posInfo - WSA_WAIT_EVENT_0;

        for (i = startIdx; i < numOfClntSock; i++)
        {
            // hEventArr[i]이 시그널 상태인지 non block으로 확인. signal이면 WSA_WAIT_EVENT_0 반환.
            int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
            if((sigEventIdx==WSA_WAIT_FAILED || sigEventIdx==WSA_WAIT_TIMEOUT)) 
            {
                continue;
            }
            else
            {
                // 어떤 네트워크 이벤트(FD_ACCEPT/FD_READ/FD_CLOSE)가 발생했는지 확인
                sigEventIdx = i;
                WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);
                if(netEvents.lNetworkEvents & FD_ACCEPT) // 연결 요청 시
                {
                    if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
                    {
                        puts("Accept Error");
                        break;
                    }
                    clntAdrLen = sizeof(clntAdr);
                    hClntSock = accept(hSockArr[sigEventIdx], (SOCKADDR *)&clntAdr, &clntAdrLen);
                    newEvent = WSACreateEvent();
                    // 새 소켓에 이벤트 연결
                    WSAEventSelect(hClntSock, newEvent, FD_READ | FD_CLOSE);

                    hEventArr[numOfClntSock] = newEvent;
                    hSockArr[numOfClntSock] = hClntSock;
                    numOfClntSock++;
                    puts("connected new client...");
                }

                if(netEvents.lNetworkEvents & FD_READ) // 데이터 수신 시
                {
                    if(netEvents.iErrorCode[FD_READ_BIT]!=0)
                    {
                        puts("Read Error");
                        break;
                    }
                    strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
                    send(hSockArr[sigEventIdx], msg, strLen, 0);
                }

                if(netEvents.lNetworkEvents & FD_CLOSE) // 종료 요청 시
                {
                    if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0)
                    {
                        puts("Close Error");
                        break;
                    }
                    WSACloseEvent(hEventArr[sigEventIdx]);
                    closesocket(hSockArr[sigEventIdx]);

                    numOfClntSock--;
                    CompressSockets(hSockArr, sigEventIdx, numOfClntSock);
                    CompressEvents(hEventArr, sigEventIdx, numOfClntSock);
                }
            }
        }
    }
    WSACleanup();
    return 0;
}
```

Event를 활용한 서버

## Chapter 22 Overlapped IO 모델

### 22-1 Overlapped IO 모델의 이해

Overlapped IO : 비동기로 여러 IO 동시 진행

* `WSASocket()` : 마지막 인자에 `WSA_FLAG_OVERLAPPED`를 전달하면 비동기 API 사용 가능한 소켓 된다. (Non-blocking은 아님.)
* `WSASend()` : Overlapped IO에 사용되는 데이터 출력 함수
  * `__WSABUF` : 두 번째 인자 구조체. 전송 데이터 버퍼와 크기 정보 저장
  * `__WSAOVERLAPPED` : 여섯 번째 인자 구조체
    * Overlapped IO 진행하려면 NULL 아니라 유효한 구조체 넣어야 한다.
    * WSASend()로 동시에 둘 이상 영역에 데이터 전송 시 구조체 변수를 별도로 구성해야 한다
* `WSARecv()` : 데이터 수신

`WSASend()`의 인자 lpNumberOfBytesSent에는 전송 데이터 크기 저장됨

* Q. 호출되자마자 반환하는데, 어떻게 전송 데이터 크기 반환?
* A. 출력 버퍼가 비어있고, 전송 데이터 크기 작다면, 함수 호출과 동시에 전송 완료 가능
  * 이땐 WSASend()가 0을 반환하고, 데이터 크기 정보도 저장
* 반환 후에도 전송 이뤄진다면, WSASend()는 `SOCKET_ERROR` 반환.
  * `WSAGetLastError()` 호출해서 `WSA_IO_PENDING` 확인 가능.
  * 이땐 `WSAGetOverllapedResult()`로 전송 데이터 크기 확인 (이 함수는 수신 결과 확인도 가능)

### 22-2 Overlapped IO에서의 입출력 완료의 확인

Overlapped IO 입출력 완료 및 결과 확인 방법

* `WSASend()`, `WSARecv()` 6번째 인자 활용. Event 오브젝트 기반
* `WSASend()`, `WSARecv()` 7번째 인자 활용. Completion Routine 기반

#### Event 오브젝트 사용하기

```c
    if(connect(hSocket, (SOCKADDR*)&sendAdr, sizeof(sendAdr))==SOCKET_ERROR)
        ErrorHandling("connect() error!");

    evObj = WSACreateEvent();
    memset(&overlapped, 0, sizeof(overlapped));
    overlapped.hEvent = evObj; // Event 오브젝트 등록
    dataBuf.len = strlen(msg) + 1;
    dataBuf.buf = msg;

    // 바로 반환되는데, 에러 뜨면 아직 남아있는 건지 확인하고 Event로 기다리기
    if(WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR)
    {
        if(WSAGetLastError() == WSA_IO_PENDING)
        {
            puts("Background data send");
            WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
            WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL);
        }
        else
        {
            ErrorHandling("WSASend() error");
        }
    }
```

Overlapped Send 예제

```c
    // 역시 바로 반환되는데, ERROR 뜨면 PENDING인지 확인. PENDING이면 이벤트 기다리기.
    if(WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, NULL) == SOCKET_ERROR)
    {
        if(WSAGetLastError()==WSA_IO_PENDING)
        {
            puts("Background data receive");
            WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
            WSAGetOverlappedResult(hRecvSock, &overlapped, &recvBytes, FALSE, NULL);
        }
        else
        {
            ErrorHandling("WSARecv() error");
        }
    }
```

Overlapped Recv 예제

#### Completion Routine 사용하기

Completion Routine 등록

* WSASend(), WSARecv() 마지막 인자 통해 함수 등록
* Pending된 IO가 완료되면, 이 함수를 호출해라
* 중요한 작업 중 CR 호출되면 안되니까, IO 요청 쓰레드가 alertable wait일 때만 CR 호출

alertable wait 상태 만드는 함수

* `WaitForSingleObjectEx()`
* `WaitForMultipleObjectsEx()`
* `WSAWaitForMultipleEvents()`
* SleepEx()\`
* 전부 마지막 인자에 TRUE 전달하면 alertable wait 상태 된다
* ex 붙은 건 원래 함수랑 똑같은데 마지막 인자 추가

```c
    memset(&overlapped, 0, sizeof(overlapped));
    dataBuf.len = BUF_SIZE;
    dataBuf.buf = buf;
    evObj = WSACreateEvent(); // Dummy event object

    // 마지막 인자에 콜백 함수 등록
    if(WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, CompRoutine)==SOCKET_ERROR)
    {
        if(WSAGetLastError()==WSA_IO_PENDING)
            puts("Background data receive");
    }

    // 등록한 콜백 함수 실행되도록 alert wait 상태로 넘어가기
    idx = WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE);
    if(idx==WAIT_IO_COMPLETION) // IO가 정상 완료 했다면
        puts("Overlapped I/O Completed");
    else
        ErrorHandling("WSARecv() error");

    WSACloseEvent(evObj);
    closesocket(hRecvSock);
    closesocket(hLisnSock);
    WSACleanup();
    return 0;
}

// CALLBACK 의미 : 콜백 함수는 운영체제가 호출하므로, 호출 규약을 알려주는 것
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    if(dwError!=0)
    {
        ErrorHandling("CompRoutine error");
    }
    else
    {
        recvBytes = szRecvBytes;
        printf("Received message: %s \n", buf);
    }
}
```

* CR을 이용한다 하더라도, WSAOVERLAPPED 구조체 주소값은 반드시 전달해줘야 한다.
  * hEvent 초기화 위해 Event 오브젝트 생성할 필요는 없다,.
* 책과는 다르게, recvBytes랑 flags는 DWORD로 선언해야 컴파일 해줬다. 더 깐깐해진거인듯.

## Chapter 23 IOCP (Input Output Completion Port)

### 23-1 Overlapped IO를 기반으로 IOCP 이해하기

`ioctlsocket(hLisnSock, FIONBIO, &mode)`

* 넌-블로킹 모드로 소켓 속성 변경
* 소켓 입출력모드(FIONBIO)를 mode에 저장된 값으로 변경
* mode가 0이면 블로킹, 0 아니면 넌-블로킹

넌-블로킹 모드 소켓

* 넌-블로킹 모드로 입출력
* 클라 요청 없을 때 accept() 호출되면 `INVALID_SOCKET` 바로 반환
  * 이어서 `WSAGetLastError()` 호출하면 `WSAEWOULDBLOCK` 반환
* accept()로 새로 생성되는 소켓 역시 넌-블로킹

#### Overlapped IO만으로 에코 서버 구현

```c
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(char *message);

typedef struct
{
    SOCKET hClntSock;
    char buf[BUF_SIZE];
    WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hLisnSock, hRecvSock;
    SOCKADDR_IN lisnAdr, recvAdr;
    LPWSAOVERLAPPED lpOvLp;
    DWORD recvBytes;
    LPPER_IO_DATA hbInfo;
    int mode = 1, recvAdrSz, flagInfo = 0;

    if(argc!=2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");

    // 마지막 인자로 비동기 API 가능한 소켓 생성
    hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    // Non-blocking 모드로 바꾼다
    ioctlsocket(hLisnSock, FIONBIO, &mode);

    memset(&lisnAdr, 0, sizeof(lisnAdr));
    lisnAdr.sin_family = AF_INET;
    lisnAdr.sin_addr.s_addr = htonl(INADDR_ANY);
    lisnAdr.sin_port = htons(atoi(argv[1]));

    if(bind(hLisnSock, (SOCKADDR*) &lisnAdr, sizeof(lisnAdr))==SOCKET_ERROR)
        ErrorHandling("bind() error");
    if(listen(hLisnSock, 5)==SOCKET_ERROR)
        ErrorHandling("listen() error");

    recvAdrSz = sizeof(recvAdr);
    while(1)
    {
        SleepEx(100, TRUE); // for alertable wait state
        hRecvSock = accept(hLisnSock, (SOCKADDR *)&recvAdr, &recvAdrSz);
        // 호출 대상 소켓이 Non-blocking 모드이므로, accept() 정말 받은건지 아닌지 확인 필요.
        if (hRecvSock == INVALID_SOCKET)
        {
            if (WSAGetLastError() == WSAEWOULDBLOCK)
                continue;
            else
                ErrorHandling("aceept() error");
        }
        puts("Client connected...");

        // accept()된 클라에게 WSAOVERLAPPED 구조체 할당할 준비
        lpOvLp = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
        memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));

        hbInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        hbInfo->hClntSock = (DWORD)hRecvSock; // 소켓 핸들 정보 저장
        (hbInfo->wsaBuf).buf = hbInfo->buf;
        (hbInfo->wsaBuf).len = BUF_SIZE;

        lpOvLp->hEvent = (HANDLE)hbInfo; // CR 기반은 event 필요 없으므로, event 오브젝트에 다른 정보 넣어도 된다
        WSARecv(hRecvSock, &(hbInfo->wsaBuf), 1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);
    }
    closesocket(hRecvSock);
    closesocket(hLisnSock);
    WSACleanup();
    return 0;
}

// 이 함수가 호출됐다는 건, 데이터 입력이 완료됐다는 뜻
// WSARecv() 6번째 인자로 넘긴걸, 7번째 인자 함수에서 3번째 인자로 쓴다는게 고정
void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
    SOCKET hSock = hbInfo->hClntSock; // 아까 event 대신 넣어놨던 소켓 정보 활용
    LPWSABUF bufInfo = &(hbInfo->wsaBuf);
    DWORD sentBytes;

    if(szRecvBytes==0)
    {
        closesocket(hSock);
        free(lpOverlapped->hEvent);
        free(lpOverlapped);
        puts("Client disconnectd....");
    }
    else // echo!
    {
        bufInfo->len = szRecvBytes;
        // 다 받았으면, 다음 alertable wait 때 WriteCompRoutine 실행
        WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
    }
}

void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
    SOCKET hSock = hbInfo->hClntSock;
    LPWSABUF bufInfo = &(hbInfo->wsaBuf);
    DWORD recvBytes;
    int flagInfo = 0;
    // 다 보냈으면, 다음 alertable wait 때 ReadCompRoutine 실행
    WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}

void ErrorHandling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

```

CR 기반 에코 서버

```c
    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(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
        ErrorHandling("connect() error");
    else
        puts("Connected.....");
    
    while(1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        strLen = strlen(message);
        send(hSocket, message, strLen, 0);
        readLen = 0;
        while(1)
        {
            readLen += recv(hSocket, &message[readLen], BUF_SIZE - 1, 0);
            if(readLen >= strLen)
                break;
        }
        message[strLen] = 0;
        printf("Message from server: %s", message);
    }
```

CR 기반 에코 서버와 문제 없이 돌아가는 클라

위의 Overlapped IO 모델의 에코 서버가 지니는 단점

* 넌-블로킹 모드 accept()가 계속 호출됨
* alertable wait 상태를 위한 SleepEx()가 계속 호출됨

해결 방법

* accept()는 main 쓰레드에서 처리
* 클라와의 입출력 담당하는 별도 쓰레드 생성
* 이것이 바로 IOCP.
  * IO를 담당하는 쓰레드 별도 생성하고, 그 쓰레드가 모든 클라 대상으로 IO 진행.
  * `IO만 담당`은 아니고, IO의 전후 과정을 전부 담당하는 쓰레드를 하나 이상 생성

중요한 것

* 쓰레드 생성은 중요한 게 아니다
* 입출력이 넌-블로킹인가?
* 넌-블로킹으로 진행된 입출력 완료 어케 확인하는가?

### 23-2 IOCP의 단계적 구현

IOCP : I/O 완료를 큐에 넣고, 워커 스레드가 그 큐에서 꺼내 처리하는 모델

<https://diy-multitab.tistory.com/56>

* 적당한 수의 워커 쓰레드를 생성한다. (보통 (코어수 \* 2) + 1)
* 소켓 생성
* 클라이언트 접속 시도시 accept 함수 호출
* 연결 완료시 Complete Port 할당
* WSARecv함수를 호출해 입출력 디바이스에서 입출력이 완료되면 completion queue에 등록하고 워커 쓰레드에 할당한다.

`CreateIoCompletionPort()`

* IOCP 서버 구현에 필요한 2가지에 사용된다
* Completion Part 오브젝트 생성
* Completion Part 오브젝트와 소켓 연결
  * IOCP에선 완료된 IO 정보가 Completion Part 오브젝트에 등록된다.
  * 반드시 Overlapped 속성 부여된 소켓 사용

`GetQueueCompletionStatus()`

* CP에 등록되는 완료된 IO 확인
* IOCP의 완료된 IO 처리를 담당하는 쓰레드가 호출하는 함수
* 3번째 인자로 얻는 것 : 소켓과 CP 오브젝트 연결하려고 CreateIoCompletionPort() 호출될 때 전달되는 3번째 인자 값
* 4번째 인자로 얻는 것 : WSASend(), WSARecv() 호출 시 전달되는 WSAOVERLAPPED 구조체 변수 주소값

```c
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>

#define BUF_SIZE 100
#define READ 3
#define WRITE 5

// 클라이언트와 연결된 소켓 정보를 담기 위해 정의된 구조체
// 이 구조체 기반으로 변수가 언제 할당, 전달, 활용되는지 관찰
typedef struct
{
    SOCKET hClntSock;
    SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

// IO 버퍼와 Overlapped IO에 반드시 필요한 OVERLAPPED 구조체 담은 구조체 정의
typedef struct
{
    OVERLAPPED overlapped;
    WSABUF wsaBuf;
    char buffer[BUF_SIZE];
    int rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;

DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    HANDLE hComPort;
    SYSTEM_INFO sysInfo;
    LPPER_IO_DATA ioInfo;
    LPPER_HANDLE_DATA handleInfo;

    SOCKET hServSock;
    SOCKADDR_IN servAdr;
    int recvBytes, i, flags = 0;
    if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
        ErrorHandling("WSAStartup() error");

    // CP 오브젝트 생성. 마지막 전달 인자 0이므로 코어 수(CPU 수)만큼 CP 오브젝트에 쓰레드 할당 가능.
    hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    GetSystemInfo(&sysInfo); // 현재 실행중인 시스템 정보 얻기
    // CPU 수만큼 쓰레드 생성. 쓰레드 생성할 때 방금 만든 CP 오브젝트 핸들 전달.
    for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)
        _beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);

    hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAdr.sin_port = htons(atoi(argv[1]));

    bind(hServSock, (SOCKADDR *)&servAdr, sizeof(servAdr));
    listen(hServSock, 5);

    while(1)
    {
        SOCKET hClntSock;
        SOCKADDR_IN clntAdr;
        int addrLen = sizeof(clntAdr);

        hClntSock = accept(hServSock, (SOCKADDR *)&clntAdr, &addrLen);
        // PER_HANDLE_DATA 동적 할당 후 정보 담기
        handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
        handleInfo->hClntSock = hClntSock; // 클라와 연결된 소켓 정보 저장
        memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen); // 클라 주소 정보 저장

        // 클라 연결 소켓과 CP 오브젝트를 연결
        // 이제 해당 소켓 기반 Overlapped IO 완료 시,
        // 연결된 CP 오브젝트에 완료 정보가 삽입되고,
        // 이로 인해 GetQueued... 함수가 반환된다.
        // 반환될 때, 3번째 인자 값을 얻을 수 있다.
        CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);

        ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
        ioInfo->wsaBuf.len = BUF_SIZE;
        ioInfo->wsaBuf.buf = ioInfo->buffer;
        ioInfo->rwMode = READ; // 입력인지 출력인지 명시
        // 6번째 인자로 OVERLAPPED 주소값 전달. 이 값은 이후 GetQueued... 함수 반환 시 얻을 수 있다.
        WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
    }
    return 0;
}

// 쓰레드에 의해 실행되는 함수
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
    HANDLE hComPort = (HANDLE)pComPort;
    SOCKET sock;
    DWORD bytesTrans;
    LPPER_HANDLE_DATA handleInfo;
    LPPER_IO_DATA ioInfo;
    DWORD flags = 0;

    while(1)
    {
        // IO 완료되면 다시 깨어나서 나머지 일 하고 다시 기다린다. 대기용 워커 쓰레드니까...
        // 반환됐을 때 세번째, 네번째 인자를 통해 두 가지 정보를 얻게 된다.
        GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED *)&ioInfo, INFINITE);
        sock = handleInfo->hClntSock;

        if(ioInfo->rwMode==READ)
        {
            puts("message received!");
            if(bytesTrans==0) // EOF
            {
                closesocket(sock);
                free(handleInfo);
                free(ioInfo);
                continue;
            }

            memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
            ioInfo->wsaBuf.len = bytesTrans;
            ioInfo->rwMode = WRITE;
            WSASend(sock, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);

            ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
            memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
            ioInfo->wsaBuf.len = BUF_SIZE;
            ioInfo->wsaBuf.buf = ioInfo->buffer;
            ioInfo->rwMode = READ;
            WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL);
        }
        else
        {
            puts("message sent!");
            free(ioInfo);
        }
    }
    return 0;
}

void ErrorHandling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lazyartisan.gitbook.io/note/main-page/books/tcp-ip/part-03.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
