Select Model

Yongs12 ㅣ 2024. 6. 7. 15:25

 

소켓 프로그래밍에서 동시에 여러 클라이언트의 요청을 처리할 필요가 있을 때 select() 함수를 활용한 I/O 멀티플렉싱 기법이다.

 

select()

여러 소켓의 상태를 동시에 감시하여 읽기, 쓰기 또는 예외 조건이 발생한 소켓을 찾아낸다.

 

FD_ZERO(), FD_SET() 함수를 사용하여 감시할 소켓들을 집합에 추가한다.

 

첫번째 인자는 Unix/Linux와의 호환을 위해 있을 뿐 window os에서는 사용하지 않는다.

 

 

Select Model 흐름

1. FD_SET을 사용해 감시할 소켓들을  FD_ZERO()로 초기화 후 등록

2. select() 함수로 이벤트 발생 대기

3. select() 함수의 반환값을 확인하여 이벤트 발생 확인

4. FD_ISSET()을 이용해 이벤트가 발생한 소켓 검사

5. 이벤트에 맞게 FD_ACCEPT, FD_READ, FD_CLOSE 처리

6. 초기화 설정 반복

 

예제

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData{};
    if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cerr << "Winsock 초기화 실패" << std::endl;
        return 1;
    }

    // 서버 소켓 생성
    SOCKET listenSocket{ ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) };
    if (listenSocket == INVALID_SOCKET)
    {
        std::cerr << "소켓 생성 실패: " << ::WSAGetLastError() << std::endl;
        ::WSACleanup();
        return 1;
    }

    sockaddr_in serverAddr{};
    ZeroMemory(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = ::htons(7777);

   ;
    // 소켓 바인딩
    if (::bind(listenSocket,  reinterpret_cast<sockaddr*>( &serverAddr ), sizeof(serverAddr)) == SOCKET_ERROR)
    {
        std::cerr << "바인드 실패: " << ::WSAGetLastError() << std::endl;
        ::closesocket(listenSocket);
        ::WSACleanup();
        return 1;
    }

    // 클라이언트 연결 대기
    if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
    {
        std::cerr << "리스닝 실패: " << ::WSAGetLastError() << std::endl;
        ::closesocket(listenSocket);
        ::WSACleanup();
        return 1;
    }

    // select()를 위한 파일 디스크립터 집합
    FD_SET fdSet{};
    FD_ZERO(&fdSet);
    FD_SET(listenSocket, &fdSet);
    SOCKET fdMax{ listenSocket };

    while (true)
    {
        FD_SET readSet{ fdSet }; // 매 루프마다 읽기 집합 초기화

        // select() 호출 (타임아웃 없이 블로킹)
        const int ret{ ::select(0, &readSet, nullptr, nullptr, nullptr) };
        if (ret == SOCKET_ERROR)
        {
            std::cerr << "select() 실패: " << ::WSAGetLastError() << std::endl;
            break;
        }

        // 이벤트가 발생한 소켓 처리
        for (SOCKET i = 0; i <= fdMax; ++i)
        {
            if (FD_ISSET(i, &readSet))
            {
                if (i == listenSocket)
                {
                    // 새로운 클라이언트 연결 수락
                    sockaddr_in clientAddr;
                    int clientAddrSize = sizeof(clientAddr);
                    SOCKET clientSocket{ ::accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize) };
                    if (clientSocket == INVALID_SOCKET)
                    {
                        std::cerr << "accept() 실패: " << ::WSAGetLastError() << std::endl;
                    }
                    else
                    {
                        FD_SET(clientSocket, &fdSet);
                        if (clientSocket > fdMax)
                        {
                            fdMax = clientSocket;
                        }
                        std::cout << "새로운 클라이언트 연결 (SOCKET: " << clientSocket << ")" << std::endl;
                    }
                }
                else
                {
                    // 클라이언트로부터 데이터 수신
                    char buffer[1024]{};
                    const int bytesReceived{ ::recv(i, buffer, sizeof(buffer), 0) };
                    if (bytesReceived <= 0)
                    {
                        if (bytesReceived == 0)
                        {
                            std::cout << "클라이언트 연결 종료 (SOCKET: " << i << ")" << std::endl;
                        }
                        else
                        {
                            std::cerr << "recv() 실패: " << ::WSAGetLastError() << std::endl;
                        }
                        ::closesocket(i);
                        FD_CLR(i, &fdSet);
                    }
                    else
                    {
                        // 받은 데이터를 클라이언트에게 에코
                        std::cout << "받은 데이터: " << buffer << std::endl;
                        int bytesSent = send(i, buffer, bytesReceived, 0);
                        if (bytesSent == SOCKET_ERROR)
                        {
                            std::cerr << "send() 실패: " << ::WSAGetLastError() << std::endl;
                            ::closesocket(i);
                            FD_CLR(i, &fdSet);
                        }
                    }
                }
            }
        }
    }

    // 소켓 및 Winsock 정리
    ::closesocket(listenSocket);
    ::WSACleanup();
    return 0;
}

'Socket 관련' 카테고리의 다른 글

OVERLAPPED Event Model  (0) 2024.06.09
WSAEventSelect Model  (0) 2024.06.08
Windows 소켓 통신  (2) 2024.06.06
winsock2 라이브러리 추가하기  (0) 2024.06.05
TCP, UDP  (0) 2024.06.02