251030

lililllilillll·2025년 10월 30일

개발 일지

목록 보기
340/350

✅ 한 것들


  • 윤성우의 열혈 TCP/IP 소켓 프로그래밍


📖 윤성우의 열혈 TCP/IP 소켓 프로그래밍


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 구조체 변수 주소값
#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);
}


profile
너 정말 **핵심**을 찔렀어

0개의 댓글