Server/Socket Server

[I/O Models] SELECT 모델

2023. 3. 13. 14:16

Non-blocking 소켓을 사용하되, 준비되었을 때를 미리 파악할 수 있도록 하자!

Multiplexing I/O Model

select 모델은 Multiplexing I/O 모델로 분류된다.

Multiplexing 모델은 먼저 소켓 리스트들을 등록하고, 등록된 소켓 리스트들 중에 send나 recv와 같은 I/O 작업이 수행 가능한 준비 상태의 소켓들을 알려주는 API를 제공한다.

I/O가 가능한 소켓에 대해서만 I/O 함수를 호출하기 때문에 스레드가 blocking되지 않고 실행 결과를 바로 받을 수 있다.

따라서, 적은 수의 스레드로도 다중 접속을 처리할 수 있다.

 

Multiplexing I/O Model에는 select, poll, epoll이 있다.

이 중 windows와 linux 두 운영체제 모두에서 사용가능한 것이 select 모델이다.

Select I/O Model

Select I/O 모델은 select 함수가 핵심이 되는 모델이다.

select 함수를 통해 소켓 함수 호출이 성공할 시점을 미리 알 수 있다.

select 함수가 소켓이 send, recv를 할 수 있는지 체크해준다.

 

select 모델은 blocking socket, non-blocking socket 각각이 가진 문제점을 해결해준다.

기존 socket 프로그래밍의 경우 다음과 같은 문제 상황이 나타날 수 있다.

1. 수신 버퍼에 데이터가 없는데, read를 하는 경우

2. 송신 버퍼가 꽉 찼는데, write 하는 경우

이 경우 select 함수를 사용하면 문제가 해결된다.

blocking socket의 경우에는 조건이 만족되지 않아서 블로킹되는 상황을 예방할 수 있고,

non-blocking socket의 경우에는 조건이 만족되지 않아서 불필요하게 반복 체크해야 하는 상황을 방지할 수 있다.

 

select 함수는 동기방식의 함수다.

따라서, 결과물이 나올 때까지 대기 후, 결과물이 하나라도 나오면 리턴한다.

 

select 함수는 다음과 같다.

int WSAAPI select(
  [in]      int           nfds,
  [in, out] fd_set        *readfds,
  [in, out] fd_set        *writefds,
  [in, out] fd_set        *exceptfds,
  [in]      const timeval *timeout
);

 

select 함수에 fd_set들을 인자로 전달해야 한다.

fd_set를 통해 select함수와 같은 다양한 windows 소켓 함수에 소켓의 set을 전달할 수 있다.

typedef struct fd_set{
	u_int fd_count;
	SOCKET fd_array[FD_SETSIZE];
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;

 

fd_set과 관련되어 주로 사용하는 매크로들은 FD_ZERO, FD_SET, FD_CLR, FD_ISSET이 있다.

 

  • FD_ZERO : set을 비운다.
    ex) FD_ZERO(set);
  • FD_SET : 관찰대상 set에 소켓 s를 넣는다.
    ex) FD_SET(s, &set);
  • FD_CLR : 소켓 s를 제거한다.
    ex) FD_CLR(s, &set);
  • FD_ISSET : 소켓 s가 set에 들어있으면 0이 아닌 값을 리턴함

select 모델의 전체적인 매커니즘을 살펴보면 다음과 같다.

 

  1. 읽기[] 쓰기[] 예외(OOB)[] 관찰 대상 등록
    : OOB(Out of band)는 send의 마지막 인자 MSG_OOB로 보내는 특별한 데이터로 받는 쪽에서도 recv OOB 세팅을 해야 읽을 수 있다.
  2. select(readSet, writeSet, exceptSet);
    : 관찰 시작
  3. 적어도 하나의 소켓이 준비되면 리턴
    : 낙오자는 알아서 제거됨
  4. 남은 소켓 체크해서 진행
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

void HandleError(const char* cause)
{
	int32 errCode = ::WSAGetLastError();
	cout << cause << " ErrorCode : " << errCode << endl;
}

const int32 BUFSIZE = 1000;

// 클라이언트가 서버에 접속하면
// 세션이라는 구조체로 클라이언트의 정보를 관리한다.
struct Session
{
	SOCKET socket;
	char recvBuffer[BUFSIZE] = {};
	int32 recvBytes = 0;
	int32 sendBytes = 0;
};

int main()
{
	WSAData wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 0;

	SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (listenSocket == INVALID_SOCKET)
		return 0;

	// 소켓을 논블로킹 방식으로 설정
	u_long on = 1;
	if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
		return 0;

	SOCKADDR_IN serverAddr;
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
	serverAddr.sin_port = ::htons(7777);

	if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
		return 0;

	if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
		return 0;

	cout << "Accept" << endl;

	vector<Session> sessions;
	sessions.reserve(100);

	fd_set reads;
	fd_set writes;

	while (true)
	{
		// 소켓 셋 초기화
		FD_ZERO(&reads);
		FD_ZERO(&writes);

		// ListenSocket 등록
		FD_SET(listenSocket, &reads);

		// 소켓 등록
		for (Session& s : sessions)
		{
			if (s.recvBytes <= s.sendBytes)
				FD_SET(s.socket, &reads);
			else
				FD_SET(s.socket, &writes);
		}

		// select
		// [옵션] 마지막 timeout 인자 설정 가능 = 얼마만큼 기다릴 것인가
		int32 retVal =::select(0, &reads, &writes, nullptr, nullptr);
		if (retVal == SOCKET_ERROR)
			break;

		// Listener 소켓 체크
		if (FD_ISSET(listenSocket, &reads))
		{
			SOCKADDR_IN clientAddr;
			int32 addrLen = sizeof(clientAddr);
			SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
			if (clientSocket != INVALID_SOCKET)
			{
				cout << "Client Connected" << endl;
				sessions.push_back(Session{ clientSocket });
			}
		}

		// 나머지 소켓 체크
		for (Session& s : sessions)
		{
			// Read
			if (FD_ISSET(s.socket, &reads))
			{
				int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
				if (recvLen <= 0)
				{
					// TODO : sessions 제거
					continue;
				}
				s.recvBytes = recvLen;
			}

			// Write
			if (FD_ISSET(s.socket, &writes))
			{
				// 블로킹 모드 -> 모든 데이터 다 보냄
				// 논블로킹 모드 -> 일부만 보낼 수가 있음 ( 상대방 수신 버퍼 상황에 따라 )
				int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
				if (sendLen == SOCKET_ERROR)
				{
					// TODO : sessions 제거
					continue;
				}

				s.sendBytes += sendLen;
				if (s.recvBytes == s.sendBytes)
				{
					s.recvBytes = 0;
					s.sendBytes = 0;
				}
			}
		}
	}


	// WinSock 종료
	::WSACleanup();
}

 

select 모델은 다음과 같은 단점이 있다.

 

  1. FD_SETSIZE가 64로 작다
    : fd_set에 최대 64개의 소켓을 설정할 수 있으며, 따라서 큰 set이 필요하다면 set을 여러개 만들어줘야 한다.
  2. 매번 소켓 set을 초기화 해야 한다.
    : 따라서 단순하지만 최대 성능을 보인다고는 할 수 없다.
Tuesberry
Tuesberry
초보 개발자의 공부 기록
Tuesberry
공부 노트
Tuesberry
전체
오늘
어제
  • 분류 전체보기 (29)
    • Server (5)
      • Socket Server (1)
      • ASP.NET Core Web Server (2)
      • Multithreading (2)
    • Unreal (0)
    • C++ (17)
      • Effective C++ (13)
      • Effective Modern C++ (4)
      • Etc (0)
    • CS (0)
      • OS (0)
      • Database (0)
    • Project (3)
      • React Native & html (3)
    • Information (2)
    • Retrospect (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • multiplexing model
  • 퍼포스 디스크 용량 차이
  • I/O model
  • react native
  • 퍼포스
  • Smart Pointers
  • Modern C++
  • 디자인적 사고 프로젝트
  • ASP.NET Core
  • Firebase Web Hosting
  • Perforce
  • Interlocked Singly Linked Lists
  • Effective Modern C++
  • 퍼포스 파일 크기
  • Serverless Game
  • 서버캠퍼스
  • Perforce Storage
  • Object pool
  • 웹뷰
  • effective C++
  • 게임 서버
  • 리액트 네이티브
  • 파이어베이스
  • Modern Effective C++
  • Invisible Proxy Class
  • React Native Webview
  • memory pool
  • Interlocked Singly Linked List
  • SList
  • AWS Community Day

최근 댓글

최근 글

hELLO · Designed By 정상우.
Tuesberry
[I/O Models] SELECT 모델
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.