들어가며

 

소켓의 I/O 를 다루는 방법중 하나인 Select model 입니다.

select 함수를 중심으로 I/O가 이루어지기 때문에 Select model 이라는 이름이 붙여졌습니다.

 

이 모델을 정말정말 초 단순화 하면 이렇습니다.

어떻게 보면 "if문으로 "소켓에 변화가 있나요? → 변화한 소켓에 작업" 의 추가일 뿐입니다.

SOCKET listenSock;
vector<SOCKET> client;

while (true)
{
	if (listenSock에 누군가 접근했나요 ? == true)
	{
		client.push_back(접근한 클라이언트 소켓)
	}

	for (SOCKET& s : client)
	{
		if (소켓 s에 변화가 있나요 ? == true)
		{
			소켓  s를 이용한 작업
		}
	}
}

 

기존 단순한 블로킹 모드의 소켓의 문제점은 함수가 성공할때까지 스레드가 무한정으로 대기한다는것이 문제였습니다.

Select모델을 사용할 경우 if문의 추가로 recv, send가 성공할때까지 대기하지 않아도 되었습니다.

 

기존 단순한 논블로킹 모드 소켓의 문제점은 불필요한 recv, send등 요청을 계속 하는게 문제였습니다.

Select모델을 사용할 경우 if문의 추가로 recv, send가 필요한 소켓이 나타날때 작업을 할수 있게 되었습니다.

 

그럼 코드로 넘어가겠습니다.


fd_set 설정

 

먼저 연결된 클라이언트의 소켓을 관리하기위해 clients라는 벡터를 생성했습니다.

그리고 fd_set을 정의했는데, fd_set이란 간단히 select 함수등 다양한 소켓 함수에 사용되는 소켓의 배열입니다.

 

fd_set은 사용되는 함수가 네 가지가 있습니다. 이를 이용해 fd_set을 이용할 것 입니다.

1. FD_ZERO(set) : fd_set인 set의 모든 비트 값을 0 으로 만듭니다.
2. FD_SET(s, &set) : 소켓 s를 fd_set인 set에 추가합니다.
3. FD_CLR(s, &set) : 소켓 s를 fd_set인 set에서 제거합니다.
4. FD_ISSET(s, set) : 소켓 s가 fd_set인 set에 들어있으면 0이 아닌 값을 리턴합니다.

vector<SOCEKT> clients;

// recv가 가능한지 확인할 용도의 fd_set
fd_set receivers;
// send가 가능한지 확인할 용도의 fd_set
fd_set senders;

 

fd_set 초기화 작업

 

while (true)
{
	// fd_set 0으로 초기화
	FD_ZERO(&receivers);
	FD_ZERO(&senders);
	
	// 클라이언트의 접속을 얻기 위해 listening중인 소켓 등록
	FD_SET(listenSock, receivers);
	
	// 연결된 클라이언트들을 각 fd_set에 저장
	for (auto& c : clients)
	{
	    FD_SET(c.socket, &receivers);
	    FD_SET(c.socket, &senders);
	}

 

Select함수 호출

 

Select함수는 fd_set에 있는 소켓의 변화를 체크할 수 있습니다.

변화는 총 세 가지로 체크할 수 있습니다.

1. 읽기 가능한 소켓 : 체크하고 싶은 fd_set을 두 번째 매개변수에 지정

2. 쓰기 가능한 소켓 : 체크하고 싶은 fd_set을 세 번째 매개변수에 지정

3. 예외 상태 소켓 : 체크하고 싶은 fd_set을 네 번째 매개변수에 지정

 

또 하나 중요한 것은 체크한 fd_set에서 변화가 없는 소켓은 fd_set에서 모두 제거 된다는게 특징입니다.

아까 만들었던 fd_set은 읽기와 쓰기용을 만들었으니 각 fd_set을 해당 매개변수에 넣습니다.

 

반환 값으로는 남은 소켓의 총 개수 입니다.

그래서 변화가 있는 소켓이 0개라면 다시 처음으로 돌아갑니다.

	int retCnt = select(0, &receivers, &senders, nullptr, nullptr);
	if (retCnt == 0)
	{
		continue;
	}

 

접근 클라이언트 소켓 저장

 

누군가 접근했다는것은 소켓에 데이터가 생겼단 뜻이므로 listening중이였던 소켓을 receivers fd_set에서 검색합니다.

정상적으로 소켓을 받아오면 클라이언트 소켓의 배열에 저장합니다.

	if (FD_ISSET(listenSocket, &receivers))
	{
		SOCKADDR_IN clientAddr;
		int32 addrLen = sizeof(clientAddr);

		SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
		if (clientSocket != INVALID_SOCKET)
		{
			clients.push_back(clientSocket);
		}
	}

 

모든 연결된 소켓 처리

 

마지막 부분입니다. 연결된 클라이언트의 모든 소켓을 FD_ISSET으로 확인하여 각 목적에 맞게 처리합니다.

	for (SOCEKT& s : clients)
	{
		/////////////////////////////////////////////////////////////
		// RECV
		/////////////////////////////////////////////////////////////
		// 클라이언트 소켓이 receviers FD_SET에 들어있는지 확인
		if (FD_ISSET(s, &reads) == true)
		{
        	// 들어있다면 recv처리
			recv(s, buffer, size, 0);
		}

		/////////////////////////////////////////////////////////////
		// SEND
		/////////////////////////////////////////////////////////////
		// 클라이언트 소켓이 senders FD_SET에 들어있는지 확인
		if (FD_ISSET(s, &senders))
		{
			// 들어있다면 send처리
			send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
		}
	}
}

 


정리

 

장점

  • 위의 코드처럼 간단하게 구현이 가능합니다.
  • 하나의 스레드를 이용하여 여러 소켓을 처리할수 있습니다.

단점

  • fd_set의 사이즈가 최대 64로 하나의 fd_set에 64개의 소켓만을 넣을수 있습니다.
스레드?

 

프로세스(프로그램) 내에서 실행되는 독립적인 코드의 흐름

이 흐름이 추상적일수 있는데 예를 들어 하단의 코드를 보자.

int main()
{
	cout << "Hello" << endl;
	Test();

	return 0;
}

string Test()
{
	cout << "Test" << endl;
}

이 코드를 실행시킨 프로그램은 main → Test → main 의 흐름으로 프로그램이 실행된다. 이런게 하나의 (코드)흐름이다.

그래서 프로그램이 실행되면 코드가 실행되기는 해야하니까 무조건 하나의 흐름은 가지고 있을 수 밖에 없는데 이게 main thread라 한다.

이 main thread는 디버깅창에서도 확인할 수 있다.

 

 

● 스레드는 각 스택 영역을 가지며 힙, 데이터 영역을 공유 한다.

하나의 스레드는 CPU의 하나의 코어가 움직인다.

 

스레드 생성

 

thread를 include해주어야 사용할 수 있다.

#include <thread>

//스레드 개체 t 생성
std::thread t;

 

스레드 실행

 

스레드 실행은 함수를 연결해 주면 그때 부터 실행된다.

// 일반 함수
void Func() { cout<<"Hi"<<endl; };

// 스레드 t1 생성
std::thread t1;

// 스레드 t1 함수 연결하여 실행
t1 = std::thread(Func);


// 스레드 t2 생성과 동시에 함수 연결하여 실행 1
std::thread t2 = std::thread(Func)
// 스레드 t2 생성과 동시에 함수 연결하여 실행 2
std::thread t2(Func);

 

스레드 클래스의 멤버 함수

 

int main()
{
	std::thread t1;

	// 1.cpu의 코어 개수를 반환
	int core = t1.hardware_concurrency();

	// 2.스레드의 고유 ID를 반환
	// 자료형이 auto인 이유는 운영체제마다 id관리하는 자료형이 달라 윈도우에선 int로 받을 수 없다.
	auto id = t1.get_id(); // == std::thread::id id = t1.get_id()

	// 3.t1의 스레드가 끝날때 까지 기다림
	t1.join()
	
	// 4.부모 스레드와 자식 스레드의 연결을 끊어 버림, 여기선 main과 t1의 연결을 끊는다.
	t1.detach()
}

여기서 join 이라는 함수가 중요한데, main thread가 우리가 만든 다른 스레드보다 먼저 종료하면 에러가 생성된다.

그래서 join 을 통해 우리가 만든 스레드가 종료될 때까지 기다리게하는 것이다.


개념 정리

 

스레드 : 프로세스 내에서 실행되는 독립적인 코드의 흐름

C++14에서 생겨난 구분자

 

보통 자리수가 많아지면 보기가 힘들어져서 우리는 , 로 구분한다.

ex) 10,000 원 // 10,000,000원 ....

 

이는 변수에 값을 넣을때도 마찬가지다.

//한눈에 보기 어려움
int a = 100000000;

그래서 작은따옴표로 구분을 할 수 있게 해주었다.

//보다 쉬움
int a = 100'000'000;

//위치는 어느 곳에 넣던지 상관없다.
int b = 1'000'0'000'0;
int c = 1'000000'00;

//모두 같은 값
a = b = c

//소수도 가능하다.
double d = 10'00.324;

 

숫자형 상수를 좀 더 가독성 있게 사용 할 수 있겠다.

 

 

https://mypark.tistory.com/entry/C14-%EC%88%AB%EC%9E%90-%EA%B5%AC%EB%B6%84%EC%9E%90Digit-separators

 

C++14 : 숫자 구분자(Digit separators)

C++14에서 작은따옴표 문자(')는 정수 리터럴과 부동 소수점 리터럴 모두 숫자 리터럴에서 숫자 구분 기호로 임의로 사용할 수 있다. 이것은 코드를 보는 사람들이 큰 숫자를 더 쉽게 알아차릴 수

mypark.tistory.com

'C++ > 유용한 것들' 카테고리의 다른 글

서식문자  (0) 2022.06.06
문자열 다루기  (0) 2022.06.05
Cout에서 소수점 자리 고정하기  (0) 2022.06.04

서식문자

%hhu			//unsigend char
%hhi,%hhd		//sigend char

%hu				//unsigend short
%hi,%hd			//sigend short

%d				//signed int
%ld				//long Int
%lld			//long long Int
%u				//unsigned int

%f				//float
%lf				//double

%c				//character
%s				//character array (string)

%o				//octal (8진수)
%x				//hexadecimal (16진수 알파벳은 소문자)
%X				//hexadecimal (16진수 알파벳은 대문자)

%				//% (\%로 출력이 되지 않고 %%로 출력이 된다)
\u250C		 //Unicode 250C

Printf

printf("%2d", value); //출력하는 자리수를 지정
printf("%02d", value); //자리수를 설정하고 자리수의 빈칸을 0으로 출력

printf("%.11lf", value); //소수점 이하 11자리까지 출력된다.

Scanf

//사용자 설정 형식을 입력 받는 경우
//Scanf는 지정한 형식에 따라 입력을 받는 함수이기에
//이 형식또한 우리가 설정할수있다, 오직 서식문자만이 아닌 밑처럼 형식을 변형해서 받을 수 있다.
scanf("%d:%d", &h, &m);
/*input*/			/*input*/			/*input*/			/*input*/
//12:243			//12 :243			//12:  243			//12,243

/*output*/			/*output*/			/*output*/			/*output*/
//a = 12, b = 243	//a = 12, b = error//a = 12, b = 243	//a = 12, b = error
//////////////////////////////////////////////////////////////////////
//숫자 한자리만 입력받는 경우
scanf("%1d", &a);
/*input*/
//12344432

/*output*/
//a = 1
//////////////////////////////////////////////////////////////////////
//공백문자를 패스하고 입력받는 경우
scanf(" %d %d", &a, &b);
/*input*/
//       23   123

/*output*/
//a = 23, b = 123

 


 

 

More Input and Output | Introduction to C

More on the standard input and output library

intro2c.sdds.ca

 

'C++ > 유용한 것들' 카테고리의 다른 글

숫자 구분자  (0) 2022.07.27
문자열 다루기  (0) 2022.06.05
Cout에서 소수점 자리 고정하기  (0) 2022.06.04

개 등신같이 항상 문자열 다룰 때마다 까먹어서 하나씩 추가하면서 정리해야겠다


STRING

  • string to Int
int a = stoi(str);
int b = stoi("3");

 

  • 특정 문자 기준으로 자르기 (string tokenizer)
//////////////////////////////////////////////////////////////////////
//1. 이 방법은 구분기준이 문장이 될수 없고 문자만 가능!
#include <sstream>

string result = "";
stringstream ss("Hi Hello!! My Name");
string token = "";
const char delimiter = '!';

while (getline(ss, token, delimiter))
{
	cout << token << endl;
}

//////////////////////////////////////////////////////////////////////
//2.구분기준이 문장이 가능
string line = "Hi Hello ※※ My ※※ Name";
string delimiter = "※※";
int pos = 0;

//★ pos = line.find(delimiter)는 연산 순서 때문에 무조건 괄호로 감싸줘야한다! ★
while ( (pos = line.find(delimiter)) != string::npos)
{
	cout<<line.substr(0, pos)<<endl;
	line.erase(0, pos + delimiter.length());
}

//substr 처음 인덱스부터 끝 인덱스를 지정해주면 그만큼 문자열에서 가져온다.
//erase 지정 인덱스부터 지정 길이만큼 문자 삭제

 

  • 문자가 숫자인지 아닌지 판별
#include <cctype>

//isdigit(char)의 반환 값이 0이면 문자이고 아니면 숫자임
//문자 하나하나씩 판별해가는방식
string str = "1233BVBBA";

for (int i = 0; i < str.length(); ++i)
{
	if (isdigit(str[i]) == 0) cout << "문자임";
	else cout << "숫자임";
}

 

  • 문자열에서 문자열 검색하기 [ string::find( ) ]
더보기
int main() 
{
	string s, y;
	getline(cin, s); getline(cin, y); 
	
	while (pos != string::npos)
	{
		pos = s.find(y, pos + y.size());
	}
}

내가 가장 많이 쓰는 오버로딩 형식이다.

size_type find ( const string & str, size_type pos = 0 ) const;

 

호출된 문자열 객체의 pos번째 인덱스부터

str찾기 시작하며 찾은 첫 인덱스를 반환한다.

  • 문자열의 일부구간을 반환하기 [ string::substr( ) ]
더보기
#include <string>
using namespace std;

int main()
{
	string str = "ABCDEF";
    str.substr(3); 		//"DEF"
    str.substr(2, 2); 	//"CD"
    str.substr(2, 99); 	//"CDEF"
    str.substr(99, 2); 	// Error std::out_of_range
	return 0;
}

string substr (size_t pos = 0, size_t len = npos) const;

문자열의 pos번째 문자부터  len 길이 만큼의 문자열을 리턴한다.

len이 npos라면 알아서 문자열의 끝까지 적용시켜주고

반면에 pos는 유효한 인덱스가 아니면 에러를 일으키기에 주의할 것


https://plein-de-verite.tistory.com/339

 

[C++] 문자열 파싱하기 (토큰화하기)

Tokenizing a string in C++ string을 Tokenizing한다는 것은 delimiter를 기준으로 string을 split하는 것과 동일한 의미를 갖는다. string을 tokenize하는 여러 가지 방법을 알아보자! stringstream 클래스와 ge..

plein-de-verite.tistory.com

'C++ > 유용한 것들' 카테고리의 다른 글

숫자 구분자  (0) 2022.07.27
서식문자  (0) 2022.06.06
Cout에서 소수점 자리 고정하기  (0) 2022.06.04
//자리수를 6자리 넘어가는것은 6자리 까지 맞춰준다는 뜻
cout.precision(6);

cout << 12344325534; //12344325534 출력 - 소수점이 들어가지 않으면 효과가 없다.

cout << 12344325534.0f //1.23443e+9 출력 - 소수점이 들어가는 순간 지수로 표현하여 자리수를 맞춤
cout << 1234.0f //1234.0 출력 - 자리수 미만이라면 그대로 출력

//지정했던 precision을 소숫점에서 사용한다는 뜻 (바로 이전에 사용했던 precision으로 설정됨
cout << fixed;

cout << 3334.221f; //3334.220947 출력 - 소숫점이 6자리로 고정 됨

 

'C++ > 유용한 것들' 카테고리의 다른 글

숫자 구분자  (0) 2022.07.27
서식문자  (0) 2022.06.06
문자열 다루기  (0) 2022.06.05

문제 : 

 


문제 해결

멤버 이니셜라이저는 선언된 순서대로 초기화 되기 때문에 기본 클래스 선언 순서와 맞춰주어야한다.

 


느낀점

오.....

'C++ > 오류' 카테고리의 다른 글

구문 오류: ';'이(가) '<class-head>'앞에 없습니다.  (0) 2020.09.05

복사 생성자를 공부하던 중

이러한 코드에서 복사 생성자가 호출되는가? 에 대한 자문을 해보았을 때

MyClass a = 2; // MyClass(int) 으로 오버로딩된 생성자가 있으니 type casting가능
MyClass a = MyClass(2); //임시객체 생성으로 오버로딩 생성자 호출
MyClass a(MyClass(2)); //복사생성자 호출하여 복사

이러한 과정으로 예측을 하여, 나는 무조건 호출되겠지 라고 생각을 했었다.

2가 임시객체가 되는 이유 : https://ddidding.tistory.com/98

 

근데 실행을 해보면 

복사 생성자가 실행이 되지 않는다.

어셈블리로 확인을 해보아도 오버로딩 생성자 하나만 호출될 뿐이였다.

호출되는 생성자는 복사 생성자가 아닌 오버로딩 생성자

즉, 이러한 과정이 된다는건데

MyClass a = 2 -----> MyClass a(2)

 

아무리 찾아도 저렇게 변환된다는 문법을 찾을 수 가 없었다 ㅅㅂ..😢

 

그러다가 이게 정말 생략되었다는 느낌이 강하게 들어서 컴파일러의 최적화 문제인가 싶어서 검색중에

Copy elision이라는 컴파일 옵션을 알게되었다.

 


Copy elision


Copy elision : 객체의 불필요한 복사를 제거할 수 있는 컴파일러 최적화 기술

std 12/31

이러한 Copy elision는 다음과 같은 상황에서 허용된다.

std 12/31

이 중에 지금 31.3을 해석 해보면 딱 지금과 같은 상황이다.

참조에 바인딩되지 않은 임시 클래스 객체가
동일한 유형의 클래스 객체로 복사/이동될 때(cv-unqualification 일때),
임시 객체를 대상에 직접 생성하여 복사/이동 작업을 생략할 수 있습니다.

MyClass a = 2; 에서

참조에 바인딩 되지 않은 임시 클래스 객체

= 2가 type casting으로 MyClass(2)가 되면서 임시객체가 됨

동일한 유형의 클래스 객체로 복사될 때(const, volatile가 아닐때)

= a와 임시객체는 같은 MyClass 타입으로, 동일한 유형의 클래스 객체로 복사 된다고 볼 수 있다.

 

코드로 보자면 이러하다.

copy elision이 된 경우는 복사가 생략되어 임시 객체가 생성이 되지 않는다.

 

그런데 "임시 객체를 대상에 직접 구성" 이라는건 무슨 뜻일까?

이게 나에겐 두가지 의미로 다가와서 굉장히 헷갈렸었다.

더보기

 

1. 임시 객체를 생성하고 그걸 대상에 직접 구성한다.

MyClass a = MyClass(2)
//copy elision
MyClass a(임시객체)

2. 임시 객체를 생성 하지 않고 대상에 직접 생성자를 호출하여 최적화

MyClass a = MyClass(2)
//copy elision
MyClass a(2)

굉장히 여러군데 찾아보면서 생각했다.

근데 애시당초 1번은

지금 복사 생성자가 호출이 안되서 찾아보고있는데 MyClass a(임시객체)에서는 복사 생성자 호출이 돼서

사실 생각할 것도 없었다. 나는 버드..?

게다가 답이 2번이라는 사실은 어셈블리에 이미 답이 있었다..ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅅㅂ...

 

약간 임시객체에 꽂혀서 "임시 객체를 대상에 직접 구성" 을 보니까

"임시객체가 생성된다고ㅅㅂ?!!?!" 라고 바로 생각도 안하고 머리속에서 정의내려버리니까

답을 찾는데 굉장히 시간이 더 걸렸다.

 

다음부터는 좀 더 자세히 뜻을 파악하고 근본 원인에 대해 신경은 쓰고 있어야겠다.

한글을 이해 못해서 일어난 문제라 씁슬하다...

 

복사/이동 작업을 생략한거지

임시객체의 생성을 생략한건 아니지않나?

라는 생각도 들었었는데

복사가 생략되면서 임시객체의 생성도 생략되었다.

 

자 여기서 복사 대입 연산자는 copy elision이 적용되는가? 한다면

참조에 바인딩되지 않은 임시 클래스 객체

라 명시되어 있기 때문에 더 생각할 필요는 없겠다.

 


 

결론

copy elision라는 최적화 기능으로 인해 복사 생성자가 생략되는 경우가 있었다.

그 방법은 임시객체가 대상으로 바로 생성되게하여 복사를 생략하는 방법이였다.

 

그리고 copy elision에 대해 함수부분으로 RVO, NRVO같은 개념이 있는데 이건 일단 나중에 다뤄야겠다.

 

 

https://shaharmike.com/cpp/rvo/

 

Return Value Optimization | Shahar Mike's Web Spot

Return Value Optimization (1096 words) Fri, Aug 18, 2017 Return Value Optimization (RVO), Named RVO (NRVO) and Copy-Elision are in C++ since C++98. In this post I will explain what these concepts mean and how they help improve runtime performance. I will u

shaharmike.com

 

https://stackoverflow.com/questions/6383639/conditions-for-copy-elision

 

Conditions for copy elision?

I wanted to verify the if the following optimizations work as expected: RVO Named RVO Copy elision when passing an argument by value So I wrote this little program: #include <algorithm> #i...

stackoverflow.com

https://kldp.org/node/158078

 

c++ string 클래스의 원리에 있어 궁금한 것이 있습니다. | KLDP

글쓴이: 익명 사용자 / 작성시간: 토, 2017/08/12 - 10:05오후 연습으로 string 클래스를 만들어보고 있는데요(아주 기본적인 것들로만요..하하하) 일반적으로 string 클래스를 이용할 때 string str1 = "Happy c

kldp.org

https://kldp.org/node/157183

 

c++ 생성자에 반환형 없나요? | KLDP

글쓴이: 익명 사용자 / 작성시간: 수, 2017/03/15 - 6:14오후 제가 생각했을때 컴파일 타임에 컴파일러가 추가시킬거 같은데 그래야 Class c = Class(); 이런 생성자 반환값 대입 문장이 유효하지 않나요?

kldp.org

https://en.wikipedia.org/wiki/Copy_elision

 

Copy elision - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Compiler optimization in the C++ programming language eliminating unnecessary copying of objects In C++ computer programming, copy elision refers to a compiler optimization technique t

en.wikipedia.org

https://stackoverflow.com/questions/1758142/why-copy-constructor-is-not-called-in-this-case#comment1649230_1758617

 

Why copy constructor is not called in this case?

Here is the little code snippet: class A { public: A(int value) : value_(value) { cout <<"Regular constructor" <<endl; } A(const A& other) : value_(other.

stackoverflow.com

https://ko.cppreference.com/w/cpp/language/copy_elision

 

copy elision - cppreference.com

복사 및 이동 생성자를 무복사 값에 의한 전달(zero-copy pass-by-value)로 최적화 하는 것을 말합니다. 아래와 같은 상황일 때 컴파일러는 객체의 복사 및 이동 생성자를 생략합니다. 심지어 복사/이동

ko.cppreference.com

https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization/12953145#12953145

 

What are copy elision and return value optimization?

What is copy elision? What is (named) return value optimization? What do they imply? In what situations can they occur? What are limitations? If you were referenced to this question, you're proba...

stackoverflow.com

+ Recent posts