728x90

TypeCast.h

#include <iostream>
using namespace std;

#pragma region TypeList

template<typename... T>
struct TypeList;

template<typename T, typename U>
struct TypeList<T, U>
{
	using Head = T;
	using Tail = U;
};

template<typename T, typename... U>
struct TypeList<T, U...>
{
	using Head = T;
	using Tail = TypeList<U...>;
};

#pragma endregion

#pragma region Length

template<typename... T>
struct Length;

template<>
struct Length<TypeList<>>
{
	enum { value = 0};
};

template<typename T, typename... U>
struct Length<TypeList<T, U...>>
{
	enum { value = 1 + Length< TypeList<U...>>::value };
};

#pragma endregion

#pragma region TypeAt

/*
TypeAt< TypeList<Tail...>, index - 1 >::Result 가 타입을 의미 하는지
값을 의미 하는지 컴파일러는 알지 못 함.

때문에 이런 모호함 때문에 템플릿 인자에 의존적인 이름은 기본적으로 '타입'
이 아닌 '값'으로 컴파일러가 처리하게 되는데,
'타입'을 명시 하려면 typename 붙여 줘야 한다.
*/

template<typename TL, int index>
struct TypeAt;

template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0>
{
	using Result = Head;
};

template<typename Head, typename... Tail, int index>
struct TypeAt<TypeList<Head, Tail...>, index>
{
	using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};

#pragma endregion



#pragma region IndexOf

template <typename TL, typename T>
struct IndexOf;

template <typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T>
{
	enum { value = 0 };
};

template <typename T>
struct IndexOf<TypeList<>, T>
{
	enum { value = -1 };
};

template <typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T>
{
private:
	enum { temp = IndexOf<TypeList<Tail...>, T>::value }; 

public:
	enum { value = (temp == -1) ? -1 : temp + 1 };
};

#pragma endregion




#pragma region Conversion

template<typename From, typename To>
class Conversion
{
private:
	using Small = __int8;
	using Big = __int16;

	///  ... 매개변수 우선순위가 낮음.
	static Small Test(const To&) { return 0; }
	static Big Test(...) { return 0; }
	static From MakeFrom() { return 0; }
public:
	enum { exists = ( sizeof(Test(MakeFrom())) == sizeof(Small) ) };
};

#pragma endregion


#pragma region TypeCast


	template<typename TL>
	class TypeConversionBefore
	{
	public:
		enum
		{
			length = Length<TL>::value
		};

		// 런타임에 정해지는 변수와 컴파일에 정해지는 값이 혼용되어 컴파일 불가능.
		TypeConversionBefore()
		{
			for (int i = 0; i < length; i++;)
			{
				for (int j = 0; j < length; j++;)
				{
					using FromType = typename TypeAt<TL, i>::Result;
					using ToType  = typename TypeAt<TL, j>::Result;

					if (Conversion<const FromType*, const ToType*>::exists)
						s_convert[i][j] = true;
					else
						s_convert[i][j] = false;
				}
			}
		}
	};


// v 값 별로 별도의 클래스
template<int v>
struct Int2Type
{
	enum { value = v};
};

template<typename TL>
class TypeConversion
{
public:
	enum
	{
		length = Length<TL>::value
	};

	TypeConversion()
	{
		MakeTable(Int2Type<0>(), Int2Type<0>());
	}

	template<int i, int j>
	static void MakeTable(Int2Type<i>, Int2Type<j>)
	{
		using FromType = typename TypeAt<TL, i>::Result;
		using ToType = typename TypeAt<TL, j>::Result;

		if (Conversion<const FromType, const ToType>::exists)
		{
			s_convert[i][j] = true;
		}
		else
		{
			s_convert[i][j] = false;
		}

		MakeTable(Int2Type<i>(), Int2Type<j + 1>());
	}

	template<int i>
	static void MakeTable(Int2Type<i>, Int2Type<length>)
	{
		MakeTable(Int2Type<i + 1>(), Int2Type<0>());
	}

	template<int j>
	static void MakeTable(Int2Type<length>, Int2Type<j>)
	{
	}
		
	static bool CanConvert(int from, int to)
	{
		static TypeConversion conversion;

		return s_convert[from][to];
	}

public:
	static bool s_convert[length][length];
};

// s_convert는 멤버 변수 같지만 전역 변수 이기 때문에 클래스 외부에 전역 변수 선언 하듯 선언 해야 한다.
// 다만 해당 클래스 네임스페이스를 이용해서만 접근 가능 하도록 변수를 선언한다.
template<typename TL>
bool TypeConversion<TL>::s_convert[length][length];

#pragma endregion

 

TypeCast.cpp

#include "TypeCast.h"



class Player
{

};

class Knight : public Player
{

};

class Mage : public Player
{

};

class Archer : public Player 
{

};


int main()
{
    // TypeList
    {
        TypeList<Knight>::Head;
        TypeList<Knight>::Tail;

        TypeList<Knight, Mage>::Head;
        TypeList<Knight, Mage>::Tail;

        TypeList<Knight, Mage, Archer>::Head;
        TypeList<Knight, Mage, Archer>::Tail::Head;
        TypeList<Knight, Mage, Archer>::Tail::Tail;
    }

    // Length
    {
        Length<TypeList<Knight>>().value;
        Length<TypeList<Knight, Mage>>().value;
        Length<TypeList<Knight, Mage, Archer>>().value;
    }

    // TypeAt
    {
        using TL = TypeList<Knight, Mage, Archer>;

        TypeAt<TL, 0>::Result;
        TypeAt<TL, 1>::Result;
        TypeAt<TL, 2>::Result;
    }

    // IndexOf
    {
        using TL = TypeList<Knight, Mage, Archer>;

        IndexOf<TL, Knight>().value;
        IndexOf<TL, Mage>().value;
        IndexOf<TL, Archer>().value;
    }

    // Conversion
    {
        Conversion<Knight, Player>().exists;
        Conversion<Player, Knight>().exists;
        Conversion<Mage, Knight>().exists;
    }

    // TypeCast
    {
        using TL = TypeList<Player, Mage, Archer>;


        bool can1 = TypeConversion<TL>().CanConvert(1, 1);
        bool can2 = TypeConversion<TL>().CanConvert(0, 2);
        bool can3 = TypeConversion<TL>().CanConvert(2, 0);

    }
}

 

728x90
728x90

1.나는 그가 열심히 살도록 격려하기 위해 약간의 돈을 기부한다.

더보기

I donate some money in aid to encourage him to live hard.   

*  in aid to + R, in aid of + N : ~를 돕기 위해

* aid + (목) + in sth : ~를 돕다

 

2. 너와 대화하는 것은 항상 나를 기쁘게 할 뿐만 아니라 열심히 공부하도록 격려해 준다.

더보기

Talking with you always doesn't only make me pleased but also encourages me to study hard. 

 

3. 우리 회사는 창립기념일을 기념하여 직원들에게 기념할 만한 휴일을 제공했습니다.

더보기

Our company marked the foundation day by offering a memorable holiday to its employees.

 

4. 기밀 자료 창고는 사람들이 접근할 수 없다.

더보기

The confidential materials storage is inaccessible to people.

* (in)accessible to N 

 

5. 당신은 그 건물에 접근할 수 있습니다

더보기

You can have access to the building.

* access가 명사로 쓰일 땐 불가산 명사

 

6. 방해해서 죄송합니다. (끼어 들어서 죄송합니다.)

더보기

I am sorry to interrupt you.

 

7. 나는 새 직장을 구할 준비 때문에 매우 바쁘다.

더보기

I am so imminent because of preparing to get a new job.

 

8. 그 지역의 수자원이 고갈되어 생산물이 떨어졌다.

더보기

The area's water resources have been depleted, causing a drop in produce.

*deplete 고갈시키다

 

9. 연료 자원이 거의 고갈되었다.

더보기

The resource of fuel is almost exhausted.

*exhaust 고갈시키다

 

10. 그들은 멸종 위기에 처한 종들을 보호하기 위해 싸우고 있다.

더보기

They are fighting to protect threatened species.

*endangered = threatened

 

11. 공룡은 멸종되었다.

더보기

Dinosaurs had become extinct.

 

12. 새 직장을 구했는데도 월급은 이전과 같았다.

더보기

Even though I got a new job, My salary was the same as my previous one.

*the same (+N) as ~ : ~와 같은

 

13. 휴가를 내도 될 것 같은데, 부서장이 아니라 팀장의 승인을 받아야 해요.

더보기

I think you can take a leave, but you need to be approved by your team leader rather than by the department manager.

 

14. 나는 이전에 내가 한 일에 대한 태만함을 사과한다.

더보기

I apologize for the negligence in what I did before.

728x90

'English > Writing' 카테고리의 다른 글

With Corona  (0) 2021.11.22
How would you respond?  (0) 2021.10.26
Do you like your job?  (0) 2021.10.25
What do you think about being rich?  (0) 2021.10.22
Dieting  (0) 2021.10.19
728x90

To whom it may concern (관계 있을지 모를 누군가에게 / 익명의 단체에게)

Dear sir or madam (선생님 또 사모님에게 / 익명의 개인에게)

 

하지만 현재는 대부분 스팸메일에서 사용된다고 한다.

 

더 자세한 이메일에서의 첫인사 및 서두인사 법은 아래 링크를 참고

https://business-english.tistory.com/2

 

영어 이메일 첫인사 서두인사 호칭 Dear sir or madam, To whom it may concern, Hi, Hello, Dear

비즈니스영어 이메일 1 편 영어 이메일 첫인사,서두인사,호칭 선택 사다리 타기 영어 이메일을 작성할 때 처음 시작 인사를 To whom it may concern ? Dear sir or madam ? Hello ? Hi ? 어떤 것을 써야하는지 헷.

business-english.tistory.com

 

728x90

'English' 카테고리의 다른 글

관계 대명사 that을 쓰는 경우  (0) 2021.10.17
Filler  (0) 2021.08.25
728x90

멀티 프로세스 -> 멀티 스레드

select -> epoll(리눅스), iocp(window)

 

멀티 프로세스 서버의 단점

- 프로세스의 빈번한 생성은 성능은 저하

- 멀티 프로세스의 흐름을 고려해서 구현해야 하기 때문에 구현이 쉽지 않다.

- 프로세스간 통신이 필요한 상황에서는 서버의 구현이 더 복잡해진다.

 

멀티 프로세스 서버의 대안

- 하나의 프로세스가 다수의 클라이언트에게 서비스 할 수 있도록 한다

- 이를 위해서는 하나의 프로세스가 여러 개의 소켓을 핸들링 할 수 있는 방법이 존재 해야 한다.

- 바로 이것이 IO 멀티플렉싱이다.

 

 

멀티 플렉싱의 의해

- 기존 멀티 프로세스에서는 클라이언트 갯수 만큼 프로세스를 1대1로 대응 시켰지만, 멀티 플렉싱은 하나의 프로세스에서 다수의 클라이언트 접속을 처리 한다(하나의 리소스를 통해서)

 

select 모델 flow

-> 멀티플렉싱

-> 다수 클라이언트 fd를 하나의 프로세스가 관리 해야 한다

-> 게임을 예로 들면 서버-클라이언트 사이에서 처리할 데이터의 크기가 굉장히 작기 때문에 하나의 프로세스에서도 충분히 처리가 가능하다 때문에 하나의 프로세스가 모든 클라이언트 요청을 처리하는 것이 '프로세스를 여러개 만드는 것보다' 효율적일 수 있다.

-> 다수의 클라이언트 소켓 fd를 저장할 fd_set 자료구조가 존재

-> fd_set에 클라이언트 소켓 fd를 등록

-> select() 함수를 통해서 fd_set에 변경된 데이터가 있으면  값을 반환한다(그전 까진 블로킹 상태)

-> 변경된 fd 정보 하나에는 나머지는 다 리셋된다.

-> 해당되는 fd를 찾아서)(fd_set을 반복문을 통해서 찾아야 한다.) 이용 read/write

-> 다시 fd_set에 fd를 등록하고 다시 select() 함수 호출 후 대기(블로킹)

 

fd_set 관련 함수

FD_ZERO

FD_SET

FD_CLR

FD_ISSET

 

 

 

728x90
728x90

프로세스 별로 별도의 메모리 공간을 가지기 때문에 기본적으로 프로세스 간의 데이터 공유는 불가능 하다.

 

solution

OS가 별도의 메모리 공간(프로세스들이 접근 가능한)을 만들어 준다.

 

step

1. 부모프로세스가 os에게 자식프로세스와 통신할 것이라고 한다

2. os는 부모프로세스와 자식프로세스가 공유 가능한 메모리 공간(파이프)를 생성하고 파이프에 읽고 쓰기가 가능한 파일스크립터를 부모프로세스에게 제공한다.

3. 자식프로세스가 생성된다.

4. 자식프로세스는 부모프로세스의 모든 걸 복사하기 때문에 자식프로세스 역시 읽고 쓰기가 가능한 파일스크립터를 얻는다.

5.이로써 읽고 쓰기가 가능한 파일스크립터를 이용한 파이프 사용을 통해 부모-자식간에 통신이 가능하다.

 

 

pipe()

파이프 생성

송수신 가능한 디스크립터 반환

 

주의점

파이프가 하나 일 때 부모 프로세스는 본인이 파이프에 wirte하고 본인이 직접 read가 가능하기 때문에 문제가 된다.

이를 해결하기 위해 애초에 부모->자식, 자식->부모용 파이프를 두개 생성하여 사용하는 것이 일반적이다.

728x90
728x90

https://www.youtube.com/watch?v=2BCYPanHIRo 

 

'by + 수치' 표현이 접할 떄 마다 '~까지'라고 해석하게 되는 경우가 많아서..

[차이의 정도]를 나타낼 때의 by의 쓰임을 찾아봤다.

 

We won the game by two points.

우리는 2점 차이로 게임을 이겼다.

 

I am older than my sister by 6years.

나는 내 여동생 보다 6살 많다.

 

Housing prices rose by 10 percent.

집값이 10 퍼센트 올랐다. (이런 경우 10퍼센트까지 올랐다... 라고 해석하는 경우가 자주 있었음)

 

728x90

'English > ETC' 카테고리의 다른 글

will, 'll 발음  (0) 2021.10.01
live,reside,dwell  (0) 2021.09.30
Would, Could  (0) 2021.09.20
Mr / Miss Mrs Ms  (0) 2021.09.17
envy vs jealous  (0) 2021.09.17
728x90

둘 이상의 프로세스는 운영체제의 의해 실행 된다.

이때, CPU 스케줄링 -> context switching 발생 -> 이전 데이터 스왑영역에 이동(하드디스크에 보관 됨)

 

이런 작업의 부담으로 경량화 된 프로세스 등장 (스레드)

 

다중 접속 서버의 구현 방법들

1) 멀티 프로세스 기반 서버 : 다수의 프로세스를 생성하는 방식

2) 멀티 플렉싱 기반 서버 : 입출력 대상을 묶어서 관리하는 방식으로 서비스 제공

3) 멀티 쓰레딩 기반 서버 : 클라이언트 수만큼 쓰레드를 생성하는 방식으로 서비스 제공

 

프로세스란

- 간단하게는 실행 중인 프로그램을 뜻한다.

- 실행중인 프로그램에 관련된 메모리, 리소스 등을 총칭하는 의미이다.

- 멀티프로세스 운영체제는 둘 이상의 프로세스를 동시에 생성 가능하다.

 

프로세스 ID

운영체제는 생성되는 모든 프로세스에 id를 할당한다.

 

fork()

함수가 호출되면 호출한 프로세스가 복사되어 fork함수 호출 이후를 각각의 프로세스가 독립적으로 실행하게 된다.

부모 프로세스 에서는 자식 프로세스의 ID(0이 아닌 값). 자식  프로세스에서는 0값을 반환.

 

좀비 프로세스

프로세스 간에는 부모-자식 계층 관계를 갖는다. 

자식 프로세스가 종료되면 실제로 메모리 상에서 사라지지 않고 OS에게 값을 반환하게 된다. 부모 프로세스가 자식 프로세스가 반환한 값을 확인 할 때 비로서 자식 프로세서는 사라진다. 그 값이 int main 함수의 return 값이다. 

 

자식 프로세스가 종료되었지만 아직 소멸되지 않은 상태(부모 프로세스가 자식 프로세스가 종료하면서 반환한 값을 확인하지 않은 상태)

 

자식 프로세스가 좀비 상태에서 부모 프로세스가 소멸 되면 자식 프로세스도 자동으로 같이 소멸 된다.

 

wait()

자식 프로세스의 반환값을 확인한다. 

자식 프로세스가 종료되지 않았으면 블로킹 상태에 놓인다.

 

waitpid()

특정 프로세스 id를 지정하여 반환값을 얻는다.

블로킹 상태가 되지 않는다.

 

시그널 핸들링

좀비 프로세스가 생성되는 것을 os가 부모 프로세스에게 알려 줄 수 있다.

부모 프로세스 -> os (요구) => 등록

os -> 부모 프로세스 (알림) => 콜백함수 호출

 

시그널 종류

SIGALRM : alarm 함수호출을 통해서 등록된 시간이 된 상황

SIGINT : crtl + c 가 입력된 상황

SIGCHILD : 자식 프로세스가 종료된 상황

 

signal(), 

시그널 등록 함수

타입과, 함수포인터 전달

 

sigaction() 

운영체제와 상관 없이 동일한 인터페이스 제공

실제로 signal() 대신 sigaction() 함수 사용

 

프로세스 기반 다중접속 서버 모델

1단계 - 에코서버(부모프로세스)는 accpet 함수 호출을 통해서 연락 요청을 수락한다.

2단계 - 이때 얻게 되는 소켓의 파일 디스크립터를 자식 프로스에게 생성해서 넘겨준다.(실제로 fork 함수로 프로세스가 생성되면 부모프로세스의 모든 정보가 자식프로세스에도 복사 된다.)

3단계 - 자식 프로세스는 전달 받은 파일 스크립터(클라이언트와 연결에 필요한)를 바탕으로 서비스를 제공한다.

4단계 - 부모 프로세스는 클라이언트 소켓을 자식 프로세스는 리스닝 소켓을 제거한다(필요 없으므로) => 하나의 커널 오브젝트를 두개의 디스크립터가 가리키게 되면 모든 디스크립터가 제거되야 커널 오브젝트가 소멸 되므로 미리 관리 해주는 것이 좋다.

 

 

728x90
728x90

http://www.kocw.net/home/search/kemView.do?kemId=1226304 

 

운영체제

<교재 및 출처><br/><br/>- A. Silberschatz et al., Operating System Concepts, 9th Edition, John Wiley & Sons, Inc. 2013.<br/><br/>- A. Silberschatz et al., Operating System Principles, Wiley Asia Student Edition<br/><br/>- 반효경, 운영체제와

www.kocw.net

 

 

무료 운영체제 강의 중에서 제일 만족스러운 강의였다. 다른 무료 강의들은 내용이 너무 함축적이라 설명이 부족한 느낌이 들거나... 무료 강의다 보니 전체적인 강의 질이 떨어진다고 느꼈는데..

 

이 수업은 공룡책의 전반적인 내용을 너무 함축하지 않게 설명하고 반효경 교수님 수업 전달력 굉장히 깔끔했다.

무턱대고 공룡책을 봤을 땐 새로운 개념 + 부자연스러운 번역 등으로 내용이 제대로 들어오지 않았는데, 이 강의는 공룡책 내용을 훨씬 쉽게 이해 시킨다.

 

운영체제를 직접 개발하는 수준이 아니라면 전공자, 프로그래머에게 충분히 좋은 운영체제 강의인 것 같다. 

 

혹시나 공룡책을 볼 계획이라면 전에 이 강의를 한번 훑고 보는 걸 추천!!

 

나는.. 일단 공룡책 읽기를 멈추고 해당 수업을 몇 차례 반복하고 이후에 실전에서 좀 더 운영체제 지식이 필요한 시기가 오면 공룡책을 직접 참고할 것 같다.

 

강의 목차 (완강 : 0회)

1.운영체제 개요

2.운영체제 개요

 

3.컴퓨터 시스템의 구조

4.컴퓨터 시스템의 구조

5.컴퓨터 시스템의 구조

6.컴퓨터 시스템의 구조

 

7.프로세스 관리

8.프로세스 관리

9.프로세스 관리

10.프로세스 관리

11.프로세스 관리

 

12.CPU 스케줄링

13.CPU 스케줄링

14.CPU 스케줄링

15.CPU 스케줄링

 

16.병행제어1

17.병행제어1

18.병행제어1

 

19.병행제어2

20.병행제어2

21.병행제어2

 

22.데드락

 

23.메모리 관리

24.메모리 관리

25.메모리 관리

 

26.가상 메모리

27.가상 메모리

 

28.파일 시스템

29.파일 시스템

30.파일 시스템

 

31.입출력 시스템

32.입출력 시스템

 

728x90
728x90

getsockopt()

: socket option을 얻기 위한 함수

 

setsockopt()

: socket option을 세팅 하기 위한 함수

 

모든 옵션이 get, set이 되는 건 아니다.

 

입출력 버퍼 사이즈 또한 세팅, 반환이 가능 (하지만 실제로 세팅해도 적극적 참고만 이루어질 뿐 정확하게 반영되진 않는다.)

 

time-wait

- 주소할당 에러의 원인

- tcp 소켓 종료 시 4-way shaking의 첫 번째 메시지를 전달하는 호스트의 소켓은 time-wait 상태를 거친다. 그 동안에는 소켓이 소멸되지 않아서(프로그램이 종료 됐지만, 여전히 운영체제에서는 사라지지 않음) 할당 받은 port를 다른 소켓이 할당 할 수 없다.

- 먼저 메시지를 보낸 호스트의 마지막 ack 메시지가 상대방 호스트에게 제대로 전달되지 않은 경우를 대비해서 time-wait를 가지게 된다. ack 메시지가 제대로 전달 되지 않았으면 상대 호스트는 다시 fin 메시지를 보낼 것이기 때문이다.

- 이러한 time-wait는 상대 호스트의 fin 메시지를 받으면 timer가 재가동 되기 때문에 경우에 따라서는 굉장히 길어질 수 도 있다. 

 

reuseaddr (소켓 옵션)

- time-wait를 무시하고 port를 할당이 가능하도록 코드를 수정해야 한다.

 

nagle 알고리즘

-인터넷의 과도한 트래픽을 막기 위해서 디자인 된 알고리즘

-일반적으로 목적이 분명한 게 아니라면 nagle알고리즘을 적용하는 것이 좋다.

-데이터에는 헤더가 붙기 때문에 모아서 보내는 게 효율적이다.

-앞서 전송한 데이터에 대한 ack 메시지를 받아야만 다음 데이터를 전송하는 알고리즘이다.

-nagle 알고리즘을 적용하면 마치 tcp가 udp와 같은 퍼포먼스를 발휘 할 수도...?

-socket option 이름은 TCP_NODELAY (기본값이 on 상태이다)

 

728x90
728x90

샘플코드

#include <iostream>
#include <chrono>

using namespace std;
using namespace chrono;

const int MAGNITUDE = 100000;
const int PAGE_SIZE = 0x1000;
const int CACHE_LINE = 0x10;

int main()
{
    {
        char* data = (char*)malloc(PAGE_SIZE * MAGNITUDE);
        
        int count = 0;
        auto begin = chrono::high_resolution_clock::now();

        for (size_t i = 0; i < PAGE_SIZE * MAGNITUDE; i += PAGE_SIZE)
        {
            data[i] = 'a';
            count++;
        }

        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << "duration(per a page) :: " << duration.count() << endl;
    }

    cout << endl;

    {
        char* data = (char*)malloc(CACHE_LINE *MAGNITUDE);

        int count = 0;
        auto begin = chrono::high_resolution_clock::now();

        for (size_t i = 0; i < CACHE_LINE * MAGNITUDE; i += CACHE_LINE)
        {
            data[i] = 'a';
            count++;
        }

        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << "duration(per a cache line) :: " << duration.count() << endl;
    }

    cout << endl;

    {
        char* data = (char*)malloc(MAGNITUDE);

        int count = 0;
        auto begin = chrono::high_resolution_clock::now();

        for (size_t i = 0; i < MAGNITUDE; i++)
        {
            data[i] = 'a';
            count++;
        }

        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << "duration (per a byte) :: " << duration.count() << endl;
    }

    cout << endl;

    {
        char* data = (char*)malloc(MAGNITUDE * 3);

        int count = 0;
        auto begin = chrono::high_resolution_clock::now();

        for (size_t i = 0; i < MAGNITUDE  * 3; i +=3)
        {
            data[i] = 'a';
            count++;
        }

        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << "duration (per 3bytes) :: " << duration.count() << endl;
    }

    cout << endl;

}

 

 

결과

 

 

실제로 임의의 3bytes 간격으로 수행 했을 때는 별차이가 없는 수행속도를 보여준다. 하지만 캐시라인, 페이지 사이즈 경계를 벗어나는 경우 확연한 속도 차이를 보여준다.

728x90
728x90

도메인 이름

DNS에 의해 IP로 변환어 접속이 가능하다.

 

DNS서버

도메인 이름을 IP로 변환해주는 서버. 

DNS는 일종의 분산 데이터베이스 시스템이다.

계층적으로 구성되어 있다.

 

gethostbyname함수

도메인 이름으로 IP 주소(hostent 구조체) 얻어 오기

 

gethostbyaddr함수

IP주소로 도메인 정보 (hostent 구조체) 얻어 오기

 

hostent 구조체 

h_name : 공식 도메인 이름

h_aliases : 별칭의 도메인 이름들.

h_addrtype : 반환된 IP의 정보가 IPv4인 경우, AF_INET이 반환

h_length : 반환된 IP 정보의 크기, IPv4의 경우 4, IPv6의 경우

h_addr_list : IP 주소 정보들(char** 타입인 이유는 ipv4는 4바이트 ipv6는 16바이트 이기 때문에 일관되게 1byte 배열로 취급하기 위해서 char* 배열을 쓴다. 현대 였으면 VOID**를 했겠지만 구조체 디자인될 시점에는 void** 가 없었기 때문에) 

 

 

 

 

 

 

728x90
728x90

close 및 closesocket 함수

소켓의 완전 소멸을 의미

소켓이 소멸되므로 더 이상의 입출력은 불가능

상대방의 상태에 상관없이 일방적인 종료의 형태를 띤다.

상대 호스트의 데이터 송수신이 아직 완료되지 않은 상황이라면 문제가 발생한다.

이를 위한 대안이 half-close 기법이 존재  

 

half close : 어플리케이션 계층에서 동작

4way close : 버퍼 수준에서 동작

 

shutdown(socket, SHUT_WR) : 해당 소켓의 write 버퍼 스트림을 닫음. EOF를 보낸다.

728x90
728x90

UDP 소켓과 TCP소켓의 데이터 송수신 비교

flow control이 없음

ip와 크게 다르지 않다.

데이터 분실 및 손실 위험이 있다.

전송이 빠르다.

 

코드레벨에서의 TCP와 UDP의 차이점

tcp는 연결과 해제의 개념이 있음

udp는 연결 개념이 없다. 때문에 보낼 때 마다 목적지 정보를 담아서 보내야 함.

udp는 서버 소켓(listen socket)과 클라이언트 소켓의 구분이 없다.

 

주요 함수

sendto(+주소포함)

recvfrom(+주소정보확인)

 

데이터 경계가 있기 때문에 recvfrom 과 sendto 함수 호출이 1대1이다.

 

unconnected udp 소켓의 sendto 함수 호출 과정

1단계 : UDP 소켓에 목적지 IP와 port번호 등록

2단계 : 데이터 전송

3단계 : UDP 소켓에 등록된 목적지 정보 삭제

매번 반복적인 1,3단계가 발생하기 때문에 이를 줄이기 위해 connected UDP 소켓 등장

 

connected udp 소켓

connected UDP 소켓은 TCP 처럼 실제 연결을 의미 하지 않는다. (connect 함수 호출로 주소등록 가능(실제 접속은 아님))

단순히 목적지 정보를 등록해서 이후에는 read, write를 통해 데이터 송신을 가능하게 한다.

 

 

728x90
728x90

TCP와 UDP에 대한 이해

 

TCP/IP 프로토콜 스택

- 인터넷 기반의 데이터 송수신을 목적으로 설계된 프로토콜 스택

- 큰 문제를 작게 나눠서 계층화 한 결과

- 7계층을 세분화가 되며, 4계층으로도 표현함

 

4계층 '애플리케이션 -> TCP/UDP (전송)-> IP(라우터, 인터넷) -> LINK(네트워크 엑세스)

7계층 '애플리케이션 -> 표현 -> 세션 -> 전송 -> 네트워크 -> 데이터 링크 -> 물리 계층

 

4계층

Link 계층 : 물리적인 연결 (라우터 자체는 물리계층)

Ip 계층 : 라우팅(경로설정)은 ip 계층 --> 이 단계만 되어도 물리적, 논리적 연결이 가능함

전송 계층 : 데이터를 어떻게 보낼 것 인가.

어플리케이션 계층

 

IP만으로도 데이터를 주고 받는 것은 가능하다.

 

프로그래머에 의해서 완성되는 application 계층

- 응용프로그램의 프로토콜을 구성하는 계층

- 소켓을 기반으로 완성하는 프로토콜을 의미함

- 소켓을 생성하면 link,ip,tcp/udp 계층에 대한 내용은 감춰진다.

- 응용 프로그래머는 application 계층의 완성에만 집중하면 된다.

 

tcp기반 서버, 클라이언트의 구현

 

tcp 서버의 기본적인 함수 호출 순서

socket(소켓생성) -> bind (소켓에 주소할당)-> listen(연결요청 대기상태) -> accept(연결허용) -> read/write -> close

 

서버측 >>

listen 함수를 통해서 소켓을 listen socket으로 지정하는 셈. 

listen socket이 하는 일은 지정된 socket을 이용해서 연결요청을 대기 큐에 전달하는 역할을 한다.

listen socket은 실질적인 연결(서비스)를 제공하진 않음. 단순히 대기 큐에 등록해주는 역할

추가적인 소켓이 생성되면서 연결이 되는 상황은 accept 함수가 호출 됐을 경우다. accept 함수에서 listen socket을 인자라 받는 이유는 listen socekt 과 연결요청 대기큐가 쌍을 이루기 때문입니다. (listen socekt이 두개면 대기큐도 두개가 된다) 

 

!! 소켓 프토번호에 관해서

소켓은 기본적으로 중복된 포트번호를 갖을 수 없다. 왜냐하면 외부로 부터 접속 할 때 포트번호는 어떤 소켓에 접속 될지 결정하는 요소가 이기 때문이다.

그러면 Listen scoekt 포트가 80이라면 클라이언트는 서버 ip + listen 포트로 접속 요청을 하게 된다. 클라이언트는 물리적으로 다르기 때문에 다른 클라이언트도 같은 80포트로 접속하는 소켓을 갖는 건 문제가 되지 않는다.

그렇다면 accept를 통해 실질적으로 클라이언트와 통신하게 되는 소켓은 어떤 포트를 갖을까?

정답은 accept로 인해 생성된 소켓은 인자로 넣어준 listen socket의 포트번호가 맵핑된다. 프로그래머가 임의로 같은 포트를 지정할 순 없지만 이 경우엔 운영체제에 의해 맵핑 되기 때문에 가능하다.

그렇다면 클라이언트가 다수 연결이 되면 다수의 동일한 포트(listen socket 포트와 같은)의 소켓이 존재하게 되는데 이때 클라이언트 소켓과 서버측 소켓은 어떻게 맵핑 되는걸까? 이떄는 단순히 포트 번호로만 맵핑하는 것이 아니라 ip 주소까지 확인하여 적절한 socket으로 read/write를 하게 된다.

 

클라이언트측>>

socket -> connect -> read/write -> close

 

Iterative 기반의 서버, 클라이언트 구현

데이터의 경계가 존재하지 않기 때문에 read 와 write 함수의 사용시 유의.

윈도우와 리눅스의 차이점 : read/write 대신 send/recv, WSAStarup 과 같은 윈속 라이브러리 로드 필요.

 

데이터를 어떻게 주고 받고 해석할지를 프로그래밍 하는 것이 어플리케이션 프로토콜이다.

 

TCP 소켓에 존재하는 입출력 버퍼

wrtie read 되는 상황이 실제로 쓰고 읽는 상황이 아니라 write 되면 출력버퍼에 데이터를 전달하고 이를 tcp 프로토콜에 근거해서 물리적인 전달이 이루어진다. 이와 같은 버퍼 때문에 슬라이딩 윈도우 프로토콜(패킷 흐름제어)이 적용 가능하다.

 

TCP의 내부 동작 원리

- 상대 소켓과의 연결 : 3way handshaking

소켓 a : hey socket B bro, 할말 있으니까 연결 좀 하자

소켓 b : okay bro. I am ready.

소켓 a : okay thanks

 

- 데이터 통신 : ack = seq + 전송된 bytes + 1 => 이 값을 통해 데이터를 완전히 받았는지 아닌지 확인 가능.

 

- 연결 정료 : 4way handshaking (일반적인 종료로 인한 데이터의 손실을 막기 위함)

소켓 a : 연결 끊으려는데..

소켓 b : 어 잠시만

소켓 b : okay 끊으세요.

소켓 a : okay bye~

 

728x90
728x90

 

무턱대고 네트워크 프로그래밍에 관심을 갖게 되면서 네트워크 기반 지식들 쉽게 배울 만한 수단이 뭐가 있을까.. 찾는 중에 발견하게 된 책이 '후니의 시스코 네트워킹' 이란 책입니다. 

 

이 책은 주로 네트워크 장비, 그 중에서도 스위치, 라우터를 여러가지 상항(네트워크 물리적인 구성)을 예시로 들면서 설명하고 물리 계층에서 네트워크 계층 까지 적용되는 프로토콜을 설명합니다. 이해를 돕기 위한 이미지가 많고 '자문 자답' 형식으로 말하듯 설명합니다.

 

챕터 중간에 실제로 네트워크 configuration을 콘솔에서 지정하고 확인하는 내용이 있는데, 저는 '네트워크 장치 관리를 실제로 어떻게 관리' 하는지 보단 '네트워크의 물리적인 단계에서의 대략적인 흐름'을 알고 싶었기 때문에 실습예제는 세세하게 읽진 않습니다. 게다가 스토리텔링 하듯 설명하기 때문에 책을 읽고 나면 이야기 책을 본듯한 느낌이 납니다. (중간에 가려울 법한 부분은 별도로 보충 설명을 하는 등의 노력을 많이 한 점이 보입니다.) 때문에 1,2권을 합치면 600page가 넘는 분량이지만 생각보다 빨리 읽을 수 있었네요.

이 책은 전반적으로 네트워크 장치(스위치, 라우터)를 기준으로 네트워크 설명을 하기 때문에 게임 서버 프로그래밍과는 직접적인 관련이 있진 않습니다. (물론 네트워크 계층 프로토콜, IP 체계에 대한 설명은 매우 유용하다고 생각합니다.) 그래도 가독성이 좋은 책이기 때문에 네트워크 계층 아래 프로토콜들의 원리가 궁금하시다면 가볍게 읽고 네트워크에 대한 인사이트를 확장 시켜보는 것도 좋을 것 같습니다 :D

 

 

 

728x90
728x90

IP 와 PORT

IP(Internet Address)

- 인터넷상에서 컴퓨터를 구분기 위한 목적

- 4바이트 주소체계 IPv4, 16바이트 주소체계 IPv6.

- 네트워크 주소와 호스트 주소로 나뉜다. 네트워크 주소를 이용해서 네트워크를 찾고, 호수트 주소를 이용해서 호스트를 구분한다.

 

클래스 별 네트워크 주소와 호스트 주소의 경계

 

클래스 a의 첫번쨰 비트는 항상 0

클래스 b의 첫 두 비트는 항상 10

클래스 c의 첫 세 비트는 항상 110

 

Port번호

- IP는 컴퓨터를 구분하는 용도로 사용되며, PORT번호는 소켓을 구분하는 용도(프로세스를 구분하는 용도가 아님)로 사용된다.

- 하나의 프로그램 내에서는 둘 이상의 소켓이 존재할 수 있으므로 둘 이상의 Port가 하나의 포르그램에 의해 할당될 수 있다.

- PORT번호는 16비트로 표현되며 0~65535이하 

- 0~1023은 Well-known Port 라 해서 이미 용도가 결정되어 있다.

 

[데이터 수신 -> 랜카드 cpu에 인터럽트 -> cpu 운영체제를 통해서 port에 맞는 소켓 확인 -> 소켓 버퍼에 데이터 넣어줌]

 

주소정보의 표현

 

주소 정보를 보관하는 구조체

sockaddr : 다양한 주소 구조체를 담을 수 있는 구조체 

sockaddr_in : ipv4용 

 

sockaddr_in

sin_familay : 주소 체계

sin_port : 16비트 포트. 네트워크 바이트 순서

sin_addr : 23비트 ip주소 정보 저장. 네트워크 바이트 순서 저장.

sin_zero : 0로 채워야 함 (그래서 구조체 세팅 시 최초에 memset을 통해서 0으로 밀고 시작 하는게 편함)

 

네트워크 바이트 순서와 인터넷 주소 변환

 

네트워크 통신하는 두 호스트가 데이터를 해석하는 순서가 다르면 정확하게 송수신 할 수 없다.

때문에 네트워크 할 때는 바이트 오더를 일치 시켜야 한다.

 

네트워크 상에서 바이트 오더를 네트워크 바이트 오더라고 한다. 네트워크 바이트 오더는 빅엔디안을 따른다.

 

호스트 바이트 순서 : CPU별 데이터 저장방식을 의미함. (대개 리틀 엔디안)

네트워크 바이트 순서 : 빅엔디안.

 

통신을 위해선 네트워크 바이트 순서를 기준으로 맞춘다. 

 

주의! 소켓이 생성 된 이후의 read, write 시에는 알아서 바이트 오더가 변환 돼서 신경 쓸 필요 없지만, 소켓을 생성할 때는 명시적으로 네트워크 바이트 오더에 맞게 데이터를 입력 해줘야 한다.

 

바이트 순서 변환 함수

=> cpu가 어떤 네트워크 바이트 오더와 동일 하면 동일한 값을 그대로 반환하고, 그게 아니라면 네트워크 바이트 오더에 맞게 변환한다.

htons : host byte order 를 network byte order로 바꾸고 shor를 반환 해라. port가 16비트 이므로 s 반환 버전을 쓴다.

ntohs : port가 16비트 이므로 s 반환 버전을 쓴다.

htonl 

ntohl 

 

인터넷 주소의 초기화와 할당

서버는 자신의 ip와 port를 할당

클라이언트는 접속할 대상(서버)의 ip와 port를 할당. (그러면 클라는 본인의 주소를 등록 안 하나? connect할 때 자동으로 된다)

 

함수

inet_addr(문자열) : 문자열 ip를 입력하면 네트워크 바이트 순서의in_addr_t로 반환한다

inet_aton(문자열, 저장 대상) : 문자열 ip를 inet_addr와 같이 변환하여 저장까지 하는 기능. 

inet_ntoa(대상) : 구조체 멤버의 ip 정보 변수를 문자열 형태 ip로 반환.

 

inddr_any

서버의 본인의 ip는 바꿀 수 없다. 그래서 고정적으로 등록할 필요가 있는데 서버 컴퓨터가 여러개의 ip 주소를 갖을 수도 있기 때문에 inndr_any를 통해서 해당 ip를 모두 등록할 수 있다.

 

127.0.0.1

= 자기 자신 주소. 루프백 주소.

 

주소할당

bind 함수 (서버 사이드)

 

윈도우 기반으로 구현하기

 

WSAStringToAddress  : 표면적으로 inet_addr과 비슷해 보이나 inet_addr는 ipv4로 기능이 제한되는 반면, 이 함수는 IPv6 기반에서도 사용 가능. 좀 더 기능이 확장 됐다고 생각하면 된다. 쓰다 보면 윈도우에 의존적인 코드가 된다..

 

WSAAddressToString 도 있음.

 

 

 

728x90
728x90

소켓은 다양한 방식으로 통신 하도록 설계되어 있다.

물론 데이터를 주고 받는 소켓 간의 타입은 동일해야 하고 약속된 규칙(프로토콜)로 통신한다.

 

프로토콜이란

- 개념적으로 약속이란 의미

- 컴퓨터 상호간의 데이터 송수신에 필요한 통신규약.

- 소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다.

 

소켓 함수 매개변수

Protocol Family(프로토콜 체계) : PF_INET(ipv4) or somethings

type : 데이터 전송 타입 : 연결지향형(데이터 보장, 느림), 비연결지향형(데이터 보장 안 함, 빠름) or somethings

protocol

 

연결지향형 소켓(sock_stream) (tcp)

- 중간에 데이터 소멸되지 않음(흐름제어, 혼잡제어)

- 전송 순서 보장

- 데이터 경계 없음(write 한 만큼 read할 필요가 없다)

- 소켓 대 소켓 연결은 반드시 1대1

 

비연결혈지향형 소켓(sock_dgram) (udp)

- 전송순서 상관없이 빠른 속도의 전송을 지향

- 데이터 손실 및 파손의 우려

- 데이터의 경계가 존재(write한 만큼 read 해야 한다.)

- 한번에 전송할 수 있는 데이터의 크기가 제한된다.

 

IPv4 프로토콜에서 연결지향형 프로토콜은 tcp 밖에 없으므로 0으로 세팅해도 된다.

IPv4 프로토콜에서 비연결지향형 프로토콜은 udp 밖에 없으므로 0으로 세팅해도 된다.

 

 

 

 

 

 

728x90
728x90

#define은 치환 전처리기

typedef는 새로운 자료형을 저으이

728x90
728x90

병렬 프로그래밍 하다 보면 => 다수 스레드를 관리하게 됨 => fork(쓰레드 다수 생성) - join(쓰레드 소멸 이후 워크 플로우 메인스레드에서만 관리) 방식을 사용하면 관리에 용이.

 

mutex가 정말 필요하다면 그냥 메인스레드에서만 접근하고 연산이 많이 필요한 경우에만 다수 스레드를 사용

 

쓰레드를 생성하고 소멸하는 과정이 비싸다. 때문에 쓰레드 풀을 만드는 방식을 써왔다. 현재 모던 c++ 표준에서는 이마저도 지원 해준다.

 

Amdahl's law

프로그파일링을 통해서 자원을 많이 잡아 먹는 부분을 우선적으로 병렬 프로그래밍을 통해서 최적화를 해야 한다.

 

 

 

 

728x90
728x90

os 를 만들 때는 parallel programing 이 중요하지만

app 위주 개발에서는 고난이도 parallel programming이 중요하지 않아지고 있다.

 

mutex, semaphore => 공유 자원들을 컨트롤 하기 위한 기법들.

하지만 os를 이미 어느 정도 지원하기 때문에 그럴 일이 별로 필요하지 않음.

 

app 공유자원을 관리 한다는 건 heap이나 global 메모리 정도를 관리하는 얘기. 하지만 이마저도 굳이 멀티스레드를 쓸 필요가 없다.

 

임베디드에서도 좋은 os가 들어 가고 있다.

 

web도 내부적으로는 멀티스레드지만 db가 자체적으로 공유자원에 대한 문제를 해결해준다.

 

silocon < engineer. 고급 엔지니어를 이용한 high optimization 보다 clould 기반 기술을 이용하여 virtual pc 등으로 수요를 감당 하는게 더 저렴함.

 

따라서 병렬프로그래밍은 OS를 개발하는 사람이 아니면 점점 수요가 떨어지고 있다.

 

C++이 high performance를 필요로 하기 때문에 멀티코어, 멀티스레드 프로그래밍을 해야 하는 건 사실.

하지만 여전히 edge device(개인이 사용하는)에는 필요하긴 하다.

728x90
728x90

소켓 생성을 위한 준비

- 윈속은 ws2_32.lib 라이브러리 필요

- 프로그램 실행시 lib가 자동 로드 되진 않음.

- WSAStartup를 통해서 lib를 로드함.

- WSACleanup을 통해서 윈속 라이브러리를 해제

 

윈도우도 descriptor 혹은 handle이라고 칭하는 정수값을 반환한다. 하지만 윈도우는 SOCKET이라는 별도의 이름을 쓴다. (실제로는 int형 정수)

 

윈도우는 closesocket을 쓰고 리눅스는 file용 close()를 쓴다. (윈도우는 소켓과 파일을 구분한다.)

 

소켓 생성까지는 윈도우 리눅스 거의 비슷하다. 하지만 데이터를 주고 받을 때는 다르다.

리눅스는 소켓을 file로 취급하기 때문에 read(), write()를 사용하지만 윈도우는 send(),recv()라는 별도의 소켓 관련 함수를 사용한다.

 

 

 

728x90
728x90

 

소켓 연결이 되면 데이터를 주고 받는다.

리눅스에서는 기본적으로 파일 입출력 함수를 그대로 사용한다.(물론 별도의 함수도 있겠지만)

때문에 파일기반 io를 공부할 필요가 있음.

 

운영체제별로 라이브러리를 제공한다 (예 윈도우는 winapi를 제공한다)

 

OS(os 지원 라이브러리) <=> 컴파일러(C표준을 이해하고 os 라이브러리를 사용하는 역할) <=> C

 

여기서 배울 함수들은 운영체제가 지원해주는 함수들 기준이다.(c 표준 함수가 아니라) 

 

시스템, 네트워크 프로그래밍에서는 주로 운영체제가 지원해주는 함수를 사용.

따라서 리눅스냐 윈도우냐에 따라서 차이가 있다.

 

저수준 파일입출력

- ANSI의 표준함수가 아닌 운영체제가 재공하는 함수 기반 파일 입출력

- 표준이 아니기 때문에 운영체제에대한 호환성이 없다.

- 리눅스는 소켓도 파일로 간주하기 때문에 저 수준 파일 입출력 함수를 기반으로 소켓 기반의 데이터 송신이 가능하다.

 

파일 디스크립터

- 운영체제가 만든 파일을 구분하기 위한 일종의 숫자

- 저수준 파일 입출력 함수는 입출력을 목적으로 파일 디스크립터를 요구한다. (키보드, 모니터 등도 파일로 간주한다.)

 

파일 열기와 닫기

open(), close()

write(), read()

 

리눅스에서는 소켓, 파일, 외부장치등이 일종의 파일로 간주되고 처리 된다.

728x90
728x90

멀티 스레드에서 인접 메모리를 연산하는 경우가 있다고 가정 해보자. cache line이 64byte로 설계되어 있어 64byte보다 작은 데이터를 연산하더라도 인접 메모리까지 포함해서 코어는 64byte를 가져와서 계산하고 이때 현재 계산 대상은 아니지만 다른 코어에서 인접 부분 메모리를 수정하고 있게 된다. 이걸 false sharing 이라고 부르고 이를 해결 하기 위해서 코어간에 sync process를 진행하게 된다.  따라서 오히려 싱글 스레드 연산보다 더 느린 결과 나올 수 있다

 

이런 sync process 과정을 없애기 위한 방법으로 패딩을 넣을 수 있다. 현재 컴퓨터 구조에서는 메모리 자원이 cpu 자원보다 훨씬 저렴하기에 메모리 자원을 낭비하고 CPU 자원을 효율적으로 활용하는 경우가 많다.

 

샘플코드

#include <iostream>
#include <thread>
#include <chrono>
#include <iomanip>

using namespace std;
using namespace chrono;

long long num1 = 0;
long long num2 = 0;
long long num3 = 0;
alignas(64) long long num4 = 0;
alignas(64) long long num5 = 0;

void func1()
{
    for (size_t i = 0; i < 10000000; i++) num1 += 1;
}

void func2()
{
    for (size_t i = 0; i < 10000000; i++) num2 += 1;
}

void func3()
{
    for (size_t i = 0; i < 10000000; i++) num3 += 1;
}

void func4()
{
    for (size_t i = 0; i < 10000000; i++) num4 += 1;
}

void func5()
{
    for (size_t i = 0; i < 10000000; i++) num5 += 1;
}

int main()
{
    const int RANGE = 20000;

    cout.setf(ios::right);
    {
        auto begin = chrono::high_resolution_clock::now();

        func3();
        
        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << setw(50) << std::left << "single thread (elapsed time) :: " << setw(10) << duration.count() << endl;
    }

    {
        auto begin = chrono::high_resolution_clock::now();

        thread t1 = thread(func1);
        thread t2 = thread(func2);

        t1.join();
        t2.join();
       
        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << setw(50) << "mutli thread (elapsed time) :: " << setw(10) << duration.count() << endl;
    }

    {
        auto begin = chrono::high_resolution_clock::now();

        thread t1 = thread(func4);
        thread t2 = thread(func5);

        t1.join();
        t2.join();

        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double> duration = end - begin;

        cout << setw(50) << "mutli thread + padding (elapsed time) :: " << setw(10) << duration.count() << endl;
    }
}

 

결과

 

728x90
728x90

문자셋 집합(문자셋)

데이터는 2진수 형태 숫자일 뿐 이것을 어떻게 해석할지는 다른 얘기.

이런 숫자 데이터를 인간의 글자 체계로 해석하기 위해 문자셋이 등장한다. 숫자와 문자를 매핑한 테이블. 

처음에 ASCII 문자셋이 나왔으나 이걸로는 한국, 중국등 다른 나라의 모든 나라에 사용하기는 부족해서 각 나라마다 본인들 만의 문자셋을 쓰기 시작함. (한국은 KS X 1001) 이런 문제를 통합하기 위해 나온 것이 unicode 문자셋이다. unicode 문자셋은 많은 언어를 지원하기 때문에 2byte로 문자를 표현한다. 사용빈도가 낮은 문자들은 2byte 범위를 벗어 나기도 한다.(BMP)

 

그럼 유니코드 문자셋을 쓰기 위해서는 2byte를 사용하면 되는가?

그건 다른 문제다. 유니코드를 쓴다 하더라도 1byte 범위 내에서 있는 영어, 라틴계 문자들은 1byte로 충분히 표현 가능하다.  따라서 문자를 해석하기 위해 사용하는 테이블이 문자집합이고 이를 참조해서 어떻게 데이터를 관리(압축)(인코딩, 디코딩) 하는 것은 다르다.

 

인코딩 방식

UTF-8

unicode 문자 집합을 사용하는 인코딩 방식.

영문은 1바이트

한글은 3바이트로 처리.

 

UTF-16

unicode 문자 집합을 사용하는 인코딩 방식. BMP 전 까지는 2바이트, 그 다음은 4바이트.

영문은 2byte

한글은 2byte

 

CP949

KS-X-1003 문자집합, KS-X-1001 문자집합, 두개의 문자집합을 참조 하는 문자 집합. MS에서 도입.

 

MBCS Vs WBCS

MBCS(Multi Byte Character Set)

- char

- 개별 문자를 다수의 바이트로 표현한 문자 

 

WBCS(Wide Byte Character Set)

- wchar

- 유니코드 기반의 character set (windows 기준 = UTF-16)

- 운영체제에 따라 다르게 동작 하지만 windows에서는 whcar를 사용하게 되면 자동으로 utf16으로 처리하려고 함.

 

 

실습코드

	//아스키 코드 참조
	{
		char c1 = '가'; // 정상적으로 값을 인식하지 못합니다.
		char c2 = 'a'; // 정상적으로 아스키 코드 값이 들어감.

		cout << c1 << " " << c2 << endl;
	}

	// CP949 (한글 2byte, 로바 1byte)
	{
		char sendData1[1000] = "가"; // multibyte + 2byte
		char sendData2[1000] = "a"; // multibyte + 1byte
	}

	// UTF8 (한글3byte + 로바 1byte)
	{
		char sendData1[1000] = u8"가"; // multibyte + 3byte
		char sendData2[1000] = u8"a"; // multibyte + 1byte

	}

	// UTF16 (한글/로바 2byte)
	{
		wchar_t sendData1[1000] = L"가";  //widebyte + 2byte
		wchar_t sendData2[1000] = L"a"; // widebyte + 2byte
	}

	// CP949 or UTF16 (설정에 따라)
	{
		TCHAR sendData1[1000] = _T("가"); // 속성에서 multibyte로 하면 cp949가 적용, 유니코드 적용하면 utf16이 적용
		TCHAR sendData2[1000] = _T("a"); // 속성에서 multibyte로 하면 cp949가 적용, 유니코드 적용하면 utf16이 적용
	}

 

실습을 하다 보면 몇가지 사실을 알 수 있다.

- ' ' 기호는 아스키코드를 참조하도록 한다. 때문에 '가' 방식으로 한글을 표현할 수 없다.

- "" 는 cp949를, u8""은 utf8을, L""은 utf16로 인코딩한다.

- L""를 사용하면 utf16을 사용하면 wbcs을 사용하기 때문에 wchar_t을 사용해야 한다.

- visual studio의 포로젝트 세팅에서 유니코드 문자셋은 utf16을 의미한다. multibyte set은 cp949를 의미한다.

- multibyte set이라고 하면 여러가지 인코딩을 예로 들수 있지만 wbcs라고 하면 utf16 자체를 의미 하는 것 같다.

 

단순히 문자셋, 인코딩에 대한 개념을 이해하는 것은 어렵지 않으나, 의미를 굉장히 혼동하여 사용하는 것 같다.. 때문에 개념을 정확하게 이해하지 못하면 혼용되어 사용될 때 이해하기 난해한 경우가 있으니,  기본 개념을 정확히 이해하고 개발환경에서는 또 어떻게 혼용하는지 까지 이해할 필요가 있다.

 

 

 

728x90
728x90

네트워크 프로그래밍 = 소켓 프로그래밍

 

소켓이란 

- 네트워크 연결 도구 (소프트웨어 개념)

- 소켓을 통해 프로그래머는 송수신에 대한 물리적, 소프트웨어적 세세한 내용을 신경 쓰지 않게 된다.

- TCP 소켓은 전화기에 비유될 수 있다.

- 소켓은 socket 함수의 호출을 통해서 생성된다.

- 소켓은 수신 전용, 송신 전용으로 나뉘어 진다.

 

소켓(전화를 받는/서버 측) 설정 순서 (tcp 기준)

1. socekt 함수 호출 - 소켓 생성 (서버 소켓 or 리스닝 소켓이라고 한다)

2. bind 함수 호출 - 주소 할당(ip, port)

3. listen 함수 호출 - 연결 요청이 가능하도록 

4. accpet 함수 호출 - 연결 요청이 왔을 때 요청을 수락하는 기능(미리 호출 되고(블로킹) 전화 올때 반환이 된다. 이때 접속된 클라이언트 연결하는 소켓이 생성된다. )

5. 이후 통신이 가능 상태로 연결된 상태 => 양방향 통신이 가능한 상태가 된다.

 

socket 함수를 호출하면 운영체제가 socket을 만들어 주고 프로그래머가 컨트롤 할수 있도록 숫자값을 반환한다. 이때 이 숫자를 리눅스에서는 descriptor, 윈도우에선 handle로 칭한다. 중요한건 단순히 숫자에 불가 하다는 점이다. 이렇게 획득한 클라이언트 소켓(accept 함수 호출로 얻은)을 통해 클라이언트와 데이터를 주고 받을 수 있다.

728x90
728x90

이 글은 [인프런-게임서버 만들기 by rookiss]를 내용을 토대로 개인적으로 학습한 내용을 종합하여 정리 했습니다.

 

목표

운영체제에 직접 메모리를 할당/해제 요청 함으로써 잘못된 메모리 접근을 막는다.

 

구현

void* StompAllocator::Alloc(int32 size)
{
	// 페이지 사이즈 단위로 반올림.
	const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE; 
	const int64 reqSize = pageCount * size;

	return ::VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);;
}

void StompAllocator::Release(void* ptr)
{
	::VirtualFree(ptr, 0, MEM_RELEASE);
}

애초에 작은 공간을 요청해도 페이지 단위로 할당하기 때문에 페이지 사이즈 단위로 요청하는 코드이다.

 

 

적용

template<typename Type, typename... Args>
Type* xnew(Args&&... args)
{
	Type* memory = static_cast<Type*>( StompAllocator::Alloc(sizeof(Type)) );

	new(memory)Type(::forward<Args>(args)...);

	return memory;
}

template<typename Type>
void xdelete(Type* obj)
{
	obj->~Type();
	StompAllocator::Alloc(obj);
}

이전 단계에서 만든 xnew / xdelete의 메모리 할당 해제 부분을 stomp allocator로 대체 했다.

 

개선점

지금까지의 stomp allocator로 할당된 메모리에 접근하는 잘못된 메모리를 접근을 방지할 수 있지만 메모리가 해제 되지 않은 상태에서 잘못된 메모리 참조는 여전히 가능하다. 메모리 할당이 페이지 단위로 이루어 지기 때문에 4byte 객체를 생성해도 64byte가 할당되고 사용하지 않는 메모리 참조가 가능하게 되는 오버플로우 문제가 발생한다. 이를 해결하기 위한 방법을 알아보자.

 

해결 idea

사용할 메모리를 우측 정렬 한다.

 

기존 메모리 구조 => [o][o][o][x][x][x][x][x][x][x]

메모리를 우측 정렬 =>  [x][x][x][x][x][x][x][o][o][o]

 

구현

void* StompAllocator::Alloc(int32 size)
{
	// 페이지 사이즈 단위로 반올림.
	const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE; 
	const int64 dataOffset = pageCount * PAGE_SIZE - size;
	const int64 reqSize = pageCount * size;

	void* base = ::VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);;

	return static_cast<void*> ( static_cast<char*>(base) + dataOffset );
}

void StompAllocator::Release(void* ptr)
{
	// 메모리는 프로세스에 메모리가 64kb로 할당 되고
	// 메모리 요청은 페이지 단위 4kb 할당 되기 떄문에
	// 4kb로 나머지 연산한 값이 offset 값임을 알 수 있다.
	const int64 address = reinterpret_cast<int64>(ptr);
	const int64 baseAddress = address - (address % PAGE_SIZE);

	::VirtualFree(reinterpret_cast<void*>(baseAddress), 0, MEM_RELEASE);
}

offset을 구해서 char* 타입 포인터 값과 포인터 연산을 한 모습이다. 이렇게 되면 반대로 언더플로우 문제가 생길 수 도 있으나 오버플로우 처럼 일반적인 경우는 아니기 때문에 신경 쓰지 않기로 한다.

 

결론

해제된 메모리의 잘못된 접근을 막는다.

할당 메모리의 오버플로우 문제를 막는다.(언더플로우는 못 막음)

 

728x90
728x90

[인프런 rookiss 님의 C++ 서버 강의]를 듣고 [윤성우 tcp/ip 소켓] 책을 보게 됐습니다.

tcp/ip 가 무엇이고 실제로 windows에서 소켓 프로그래밍을 어떻게 작성하는 대략적으로 알고 있는 상태에서 이 책을 읽었습니다.

 

프로그래밍 입문 당시 프로그래밍 기초 문법을 살펴보고 좀 더 심도 있는 공부를 원하던 때 윤성우님의 c/c++ 책은 제게 심화 학습을 위한 중간 다리와도 같은 느낌이 였습니다. 자칫 초심자에게 어려울 수 있는 내용은 굉장히 친절하게 전달하려고 노력한다고 느꼈기 때문입니다.

 

단순히 프로그래밍 기본 문법을 전달하는 한국서적을 읽고 좀더 레벨(?)을 올리기 위해 새로운 책들은 찾아 보면 대부분의 고급주제를 다루는 책들이 영문버전이 많고 해석본 마저 부자연스러운 번역들이 많이 이런 부분들은 더 많은 경험치를 쌓고자 하는 초심자인 저를 가로 맞는 큰 벽과 같았습니다.

 

처음 c/c++을 배울 때 이런 상황에서 윤성우님의 책은 제게 [초심자-중급자]의 중간 다리 역할을 하는 책이라 생각합니다. 윤성우님의 책은 조금 더 심화된 내용을 독자들로 하여금 이해하기 쉽게 설명하고 더 나아가 힘조절(?)을 하면서 앞으로 공부 방향성에서 자연스럽게 유도합니다.

 

이런 좋은 기억을 되새겨 서버프로그래밍을 시작한 지 얼마 안된 뉴비인 저에게 윤성우님의 tcp/ip 소켓 프로그래밍 책은 후회 없는 선택이였습니다.

 

이 책의 장단점과 다루는 주제에 대해 간략히 요약하겠습니다.

 

장점

- 문장 하나하나에 굉장히 심혈을 기울였다고 생각합니다. 기술서적의 경우 한가지 개념을 설명하기 위해서 여러가지 개념의 수반되는 됩니다. 그리고 이때 모든 개념을 다루다 보면 서적의 난이도는 굉장히 올라가게 됩니다. 이 책은 이 밸런스를 잘 조절 했다고 생각합니다. 이해를 쉽게 하기 위해 중요한 기반지식을 생략하지도, 최대한 자세하게 설명하기 위해 가독성을 포기하지도 않았습니다.

 

- 모든 챕터의 진행이 [개념->샘플 소스코드] 구성입니다. 모든 개념을 설명한 뒤에 적절한  타이밍(?)에 샘플코드가 나옵니다. 

 

- 챕터 중간중간 소켓프로그래밍 지식을 전달하며 수반되는 필요성에 대한 언급도 자주 합니다. 책 마지막에는 향후 학습 책도 추천합니다. 주니어 프로그래머에게는 단순히 주제에 대한 지식 전달 뿐만 아니라 앞으로 공부 방향성에 대한 조언은 굉장히 유익한 부분이라고 생각합니다.

 

- 리눅스, 윈도우 두 OS를 대상으로 설명하고 샘플코드를 첨부합니다. 때문에 설령 Window Socket 프로그래밍이 관심 있는 분이라고 할지라도 이 책을 통해 공부한다면 리눅스에서의 소켓프로그래밍의 공통점과 차이점을 이해하는 것은 되려 윈도우에서의 소켓프로그래밍을 제대로 이해하는데 도움이 될 거라고 생각합니다.

 

- 윈도우, 리눅스에서 소켓 프로그래밍에 사용되는 함수들 매개변수, 반환형 단위로 상세하게 설명합니다. 어떤 분야든 처음 공부할 때는 거시적은 설계보다는 세세한 사용법에 신경 쓰게 됩니다. 지극히 자연스러운 부분이고 세세한 사용법을 알아가는 과정도 거시적인 안목을 기르는 자연스러운 기본기가 되는 부분이라 생각합니다. 이점에서 이책은 함수 단위로 상세하게 설명하면서 초심자들의 가려운 부분을 시원하게 긁어주고 있지 않나 생각합니다.

 

- 동영상 강의도 책을 구입한 독자들 대상으로 12개월 무료로 제공됩니다. 아직 동영상 강의를 보지 않았지만 개인적으로 다양한 형태의 input이 중요하다고 생각하기 때문에 이점은 무조건 장점이라고 생각하는 부분입니다.

 

단점

- 제가 책을 읽은 2022 기준 샘플코드가 복붙으로 간단하게 실행되진 않습니다. 전 간단한 소켓 프로그래밍을 이미 작성 해본 경험이 있고 해당 책은 눈으로만 읽었기 때문에 이런 부분이 문제가 되진 않습니다. 하지만 샘플소스코드를 꼭 실행해보고 싶은 사람들에게는 조금은 불친절한 책이 될 수도 있을 것 같습니다. 하지만.. 대부분의 프로그래밍 책들이.. 책이 쓰인 시점과 책을 읽는 시점의 환경이 많아 달라진 탓에 제대로 돌아가지 않긴 합니다.. 

 

- 다루는 주제 범위가 굉장히 넓거나 굉장히 심도 있진 않습니다. 저 같은 경우에는 그래도 두세번은 더 읽은 만한 가치가 있다고 생각해서 책으로 소장하는 것에 대한 후회는 없지만, 어느정도 소켓프로그래밍 수준이 높은 분들은 서너번 읽게 될 책은 아닐 것 같습니다. 이건 독자들의 수준에 따라 다를 것 같습니다.

 

다루는 주제들

- 소켓,포로토콜에 대한 기본적인 개념

- 리눅스와 윈도우 소켓 모델 소개 

- 멀티프로세스, 멀티플렉싱, 멀티쓰레드

- tcp vs udp

- tcp의 half close

- 윈도우, 리눅스에서 소켓 프로그래밍에 사용되는 함수들

 

 

 

728x90
728x90

https://www.slideshare.net/namhyeonuk90/iocp

 

Iocp 기본 구조 이해

IOCP IO Completion Port NHN NEXT 남현욱

www.slideshare.net

 

- CP 생성시 완료된 IO를 처리할 쓰레드 수를 지정(대략 프로세서 개수) => release thread list에 에 한번에 올라 갈 수있는 스레드의 갯수

- send recv를 받기 처리하기 위한 쓰레드풀은 보통 프레서스 갯수 두배 만큼(waiting thread queue)

- 쓰레드 풀이 실행 되면서 GetQueue.. 함수가 호출 되면 waithing thread queue에 쓰레드 풀 쓰레드가 대기하게 된다 이때 waiting thread queue는 쓰레드 풀 갯수만 큼 대기(프로세서 갯수 두배)

- 하지만 io가 처리가 가능한 쓰레드 갯수는 cp 생성시 지정 한 숫자(프로세서 갯수).

 

- IO 완료 처리 쓰레드 갯수 => 프로세서 갯수 => release thread list 최대 갯수

- send recv처리를 위한 쓰레드 풀 갯수 => 프로세서 갯수 * 2 => waiting thread queue 최디 갯수

 

728x90
728x90

이 글은 [인프런-게임서버 만들기 by rookiss]를 내용을 토대로 개인적으로 학습한 내용을 종합하여 정리 했습니다.

 

이번 단계는 앞서 만든 메모리 할당기 stomp allocator를 컨테이너에도 적용되게 하는 실습이다.

 

컨테이너는 기본적으로 할당자를 템플릿 변수로 받는다. 이를 채워 줄 StlAllocator를 정의 해보자.

 

template<typename T>
class StlAllocator
{
public:
	using value_type = T;

	StlAllocator() {}

	template<typename other>
	StlAllocator(const StlAllocator<other>&) {}

	T* allocate(size_t count)
	{
		const int32 size = count * sizeof(T);

		return static_cast<T*>(StompAllocator::Alloc(size));
	}

	void deallocate(T* ptr, size_t count)
	{
		StompAllocator::Release(ptr);
	}
};

할당자로 쓰기 위해 정의 해야하는 부분은 vector 라이브러 버전에 따라 달라 질 수 있으니 참고하자. 중요한 건 allocate, deallocate 함수의 구현부다.

 

커스텀 할당기를 편하게 사용하기 위해 template using 문을 작성 해보자.

template<typename T>
using Vector = vector < T, StlAllocator<T>>;

 

결과

int main()
{	
	Vector<Knight> v(100);
}

 

 

이로서 일단 메모리를 xnew를 사용하는 버전, 컨테이너를 생성하는 버전 모두 stomp allocator를 통해서 메모리를 할당 받을 수 있다!

728x90
728x90

이 글은 [인프런-게임서버 만들기 by rookiss]를 내용을 토대로 개인적으로 학습한 내용을 종합하여 정리 했습니다.

 

 

필요한 이유

메모리 이슈를 디버깅 시점에 수월하게 잡기 위함.

 

메모리 이슈 사례 몇 가지

- dangling pointer  =>스마트 포인터로 어느 정도 해결 가능

 

- user after free  =>스마트 포인터로 어느 정도 해결 가능

 

- 컨테이너 순회 중에 컨테이너 요소를 삽입/삭제하거나 컨테이너를 clear하는 등의 동작을하고 반복문을 빠져 나오지 않는 경우 => 컴파일러가 주기도 하지만 모든 상황에 그럴거란 보장은 없음

 

- casting 문제 상속관계 객체를 잘못 캐스팅 한 경우(일종의 overflow) => dynamic 캐스팅을 하면 좀더 안전하지만 속도 문제로 dynamic 캐스팅을 안 쓰기도 함

 

 

페이지 확인 하는 함수

	SYSTEM_INFO info;

	::GetSystemInfo(&info);

	// 페이지 사이즈. (4kb, 0x1000)
	// 페이지 보다 더 작은 사이즈의 메모리를 운영체제에게 요구해도
	// 운영체제는 페이지 사이즈를 예약합니다.
	info.dwPageSize; 

	// 프로세스 주소 공간에서 특정 영역을 예약할 때 사용하는
	// 단위의 크기, 대부분 65536 값을 가지고 있다.
	info.dwAllocationGranularity;

 

운영체에게 직접 메모리 할당/해제를 요청 하는 법

	int* test = (int*)::VirtualAlloc(NULL, 4, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	*test = 100;
	::VirtualFree(test, 0, MEM_RELEASE);

 

 

이렇게 했을 때 malloc, free를 이용해서 메모리 할당/해제를 했을 때 무슨 차이가 있을까? VirutualFree()가 호출되고 다시 '*test=200'과 같은 코드가 있으면 크래쉬가 난다. 이전에 new, delete를 사용할 때 발생하는 dangling pointer 이슈 생기지 않는다. 이 말은 new / delete 키워드를 쓰면 운영체제에게 직접 메모리 할당 / 해제 요청을 하는 게 아니라 컴파일러 자체적으로 최적화가 이루어 지고 있을 수 추측할 수 있다. 이런 최적화가 있기 때문에 앞으로 다룰 메모리풀링이 어쩌면 의미가 없을 수도 있다. 하지만 반대로 이런 최적화는 디버깅 단계, 개발 단계에서는 오히려 버그를 찾기 힘들게 한다. 따라서 stomp allocator의 핵심은 직접 운영체제에게 메모리 할당/해제 요청을 함으로써 잘못된 메모리 참조로 인한 이슈를 찾기 위함이다. 3,4단계 포스팅을 통해 stomp allocator의 필요성과 구현을 위한 사전지식을 학습 했으므로 다음 단계에서 직접 구현 해보도록 하자.

728x90

+ Recent posts