프로젝트 세팅 -> 콜리전

Object Channels

콜리전을 가지는 물체들 사이의 충돌을 신경써야할 때 사용

ㄴ 기본 반응 (Ignore, block, overlap)

block : 충돌한다.

overlap : 뚫고 지나가지만 이벤트를 발생시킬 수 있음

Ignore  : 무시

 

캐릭터의 Capsule 컴포넌트에 콜리전 프리셋(충돌 규칙) 적용

블루 프린터 디테일 창에서 콜리전 프리셋을 설정해도 되고

Weapon->SetCollisionProfileName(TEXT("NoCollision")); 처럼 생성자에서 설정해줄 수도 있다.

 

Trace Channels

주위에 무엇이 있는지 논리적인 판단을 확인하거나 탐색하기 위해 사용

bool UWorld::SweepSingleByChannel 함수

ㄴ 트레이스 채널을 활용하여 첫 번째로 부딪힌 물체에 대한 충돌 정보를 얻을 수 있는 함수

 

Socket

메시 스켈레톤의 관절에 소켓을 추가하면 캐릭터의 손에 무기를 쥐어줄 수도 있다.

ㄴ Actor를 캐릭터 메시의 소켓에 attatch하는 방식으로

 

GameInstance

매니저 역할을 할 클래스는 GameInstance

프로젝트 세팅 -> 맵 & 모드 -> 게임 인스턴스 클래스 설정

 

전투 상황에서 체력 계산은 공격을 당해서 체력이 깎이는 쪽에서 처리를 해주는 편이 낫다.

 

액터 컴포넌트 (stat component)

ㄴ 캐릭터의 스탯 컴포넌트 (최대 체력, 레벨, 공격력 정보)

ㄴ OnAttacked 함수, SetStat 함수를 여기에 구현

 

싱글톤처럼 어디서든 GameInstance를 받아올 수 있는 방법

auto MyGameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));

 

UI

UI 프리팹마다 1대1로 대응되는 스크립트를 만들어 관리하는 것이 좋다. (Widget 클래스 상속)

 

HP UI를 3D로 캐릭터에 붙이는 코드 (MyCharacter.cpp)

HpBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBAR")); 
HpBar->SetupAttachment(GetMesh());
HpBar->SetRelativeLocation(FVector(0.f, 0.f, 200.f));
HpBar->SetWidgetSpace(EWidgetSpace::Screen); // World는 3D 세상의 UI, Screen은 화면에 항상 보이는 2D UI



static ConstructorHelpers::FClassFinder<UUserWidget> UW(TEXT("WidgetBlueprint'/Game/UI/WBP_HpBar.WBP_HpBar_C'"));
if (UW.Succeeded())
{
HpBar->SetWidgetClass(UW.Class); 
HpBar->SetDrawSize(FVector2D(200.f, 50.f));
}

 

UI를 만드는 여러가지 방식

1. UHUD 클래스를 상속받는 방법

ㄴ 언리얼 4 이전 버전에서 사용하던 방법

2. UserWiget 클래스를 상속받는 방법 (UMG)

ㄴ 비주얼 툴을 이용하여 UI를 만든다.

'언리얼 5 > 정리' 카테고리의 다른 글

라이팅 설정  (0) 2022.10.05
컨테이너  (0) 2022.09.15
AI Controller & Behavior Tree  (0) 2022.09.14
애니메이션  (0) 2022.09.13
Unreal의 기초  (0) 2022.09.12

 

애니메이션 기초

Skeletal Mesh의 Animation 속성을 활용한다.

ㄴ 애니메이션 블루프린트 클래스와 연결한다.

애니메이션 인스턴스를 활용할 때는 애니메이션 블루프린트라는 별도의 창에서 만들어야 한다.

캐릭터에 애니메이션 관련 코드를 넣는게 아니라 애니메이션 인스턴스에서 캐릭터의 상태를 읽어서 동작을 지정하는 방식을 쓰자

스테이트 머신

애니메이션 상태 관리는 웬만하면 블루프린트를 사용한다.

 

애니메이션 몽타주

애니메이션을 편집해서 또 다른 애니메이션을 만드는 기법

ㄴ 여러가지 애니메이션을 연결하여 하나의 긴 애니메이션을 만들 수 있다.

ㄴ 여러 개의 애니메이션을 섹션으로 구분하여 호출하고 싶은 섹션의 애니메이션을 재생할 수 있다.

스켈레탈 메시 -> 에셋 생성 -> 애님 몽타주

 

델리게이트

어떤 행동이 끝났을때 어떤 기능을 호출하고 싶을 때 사용함

 

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMontageEndedMCDelegate, UAnimMontage*, Montage, bool, bInterrupted); // Unreal의 델리게이트 선언 (Montage가 끝날 때 등록된 함수를 호출하는 델리게이트)

...

FOnMontageEndedMCDelegate OnMontageEnded; 

 

void AMyCharacter::BeginPlay()
{
Super::BeginPlay();

AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::OnAttackMontageEnded);

}

ㄴ 애니메이션 몽타주가 끝날때 호출될 함수(OnAttackMonrageEnded)를 추가한다.

 

애니메이션 노티파이

애니메이션 클립 중간에 노티파이를 설정하여 애니메이션 중 원하는 타이밍에 함수를 호출할 수 있도록 한다.

ㄴ AnimatorInstance 클래스에 AnimNotify_[노티파이 이름]() 의 함수를 정의하면 된다.

ㄴ Montage_JumpToSection 함수를 활용하면 노티파이 이름

 

블렌드 스페이스

가중치에 따라 애니메이션을 섞는다.

ㄴ 캐릭터의 걷기 애니메이션 (상하좌우)를 섞어 블렌드 스페이스를 만들면 플레이어의 움직임을 자연스럽게 만들 수 있다.

'언리얼 5 > 정리' 카테고리의 다른 글

라이팅 설정  (0) 2022.10.05
컨테이너  (0) 2022.09.15
AI Controller & Behavior Tree  (0) 2022.09.14
충돌과 UI  (0) 2022.09.14
Unreal의 기초  (0) 2022.09.12

Unreal 엔진의 기초

Unreal은 월드 안의 요소(게임 객체)들의 유형이 정해져 있다.

Unity는 컴포넌트를 조립하여 요소의 유형을 결정한다.

 

Unreal은 C++ 게임 객체를 만들 때 부모 클래스의 종류를 정해주며 시작한다. (만들 때 부모 클래스를 지정한다.) 

Unity는 아무것도 없는 객체에 컴포넌트를 붙이는 방식이라면 Unreal은 종류가 정해진 객체에 컴포넌트를 붙이는 방식

이런 관점에서는 Unity가 Unreal보다 자유도가 더 높다.

 

Object (Unreal 게임 객체의 최상위 클래스)

ㄴ Actor

    ㄴ Pawn

        ㄴ Character

 

Actor 클래스 (AActor를 상속받음)

ㄴ BeginPlay() : 스폰될 때 한 번 실행되는 함수

ㄴ Tick() : 매 프레임 호출되는 함수

 

Unreal 특징

ㄴ C++ 클래스를 삭제하려면 엔진을 끄고 파일 탐색기에서 삭제해야 한다.

ㄴ 컴포넌트들을 스마트포인터 멤버로 관리한다.

ㄴ 컴포넌트를 추가할 때 컴포넌트들 중 하나는 루트 컴포넌트여야 한다.

ㄴ 빌드는 Visual Studio에서 해도 되고 엔진의 컴파일 버튼을 눌러도 된다.

ㄴ 유니티와 비슷하게 멤버를 private으로 관리하고 리플렉션 기능을 활용하여 엔진에서 수정할 수 있다.

ex) UPROPERTY(VisibleAnywhere) UPROPERTY(EditAnywhere, Category=BattleStat)

 

로그와 디버깅

로그

UE_LOG(카테고리, 로그 수준, 형식, 인자)

UE_LOG(LogTemp, Warning, TEXT("hello %d"), 3);

카테고리별로 로그를 볼 수 있다.

 

디버깅

엔진 끄고 솔루션 구성 Development editor나 debuggame editor로 설정 후 디버깅 시작하면 엔진이 켜짐

엔진의 플레이 버튼 누르면 중단점에 걸림

 

솔루션 구성의 Debug와 Development는 debug와 release의 관계와 비슷하다.

editor와 editor가 안붙은 것의 차이 

ㄴ editor 붙은 건 dll을 만들고 안 붙은건 exe를 만든다.

 

게임플레이 프레임워크

GameModeBase 클래스

ㄴ 게임의 규칙을 정의하는 클래스

월드 세팅

 

ctrl + n 단축키로 map을 생성할 수 있다.

ㄴ 세팅 -> 프로젝트 세팅 -> 맵&모드 -> 기본 맵 세팅

 

Pawn 클래스 (입력을 받을 수 있는 객체)

축과 액션에 대한 정보 세팅

ㄴ 세팅 -> 프로젝트 세팅 -> 엔진-입력 -> 축 매핑 이름 맞춰주기

 

Character 클래스

카메라가 캐릭터를 3인칭 시점으로 보이게 하기

ㄴ SpringArm은 CapsuleComponent에 붙인다.

ㄴ Camera는 SpringArm에 붙인다.

 

BluePrint

C++만을 사용하여 게임을 만들 수도 있지만 C++와 BluePrint를 적절히 섞어 사용하여 게임을 만들 수도 있다.

컴포넌트를 붙이거나 규칙을 정하는 과정을 시각화하여 표현한 것이다.

블루프린트 클래스가 C++ 클래스에 비해 느리긴 해도 유용하게 사용될 수 있다. 외형을 둘러볼 수도 있고 속성을 에디터에서 변경하여 적용해 볼 수도 있다.

기존에 만들어져 있는 C++ 클래스를 상속받는 블루프린트 클래스를 만들 수도 있다. 

 

'언리얼 5 > 정리' 카테고리의 다른 글

라이팅 설정  (0) 2022.10.05
컨테이너  (0) 2022.09.15
AI Controller & Behavior Tree  (0) 2022.09.14
충돌과 UI  (0) 2022.09.14
애니메이션  (0) 2022.09.13

온라인 게임 프로그래밍에서 소켓은 파일 핸들 방식과는 다르다.

 

1. 클라이언트 개수만큼 소켓이 있어야 한다.

2. 파일 처리를 하는동안 스레드가 대기하는 일이 없어야 한다.

 

이러한 이유 때문에 소켓은 보통 비동기 입출력 상태로 다룬다. 소켓을 비동기 입출력으로 다루는 방식에는 크게 두 가지 방식이 있다.

1. 논블로킹 소켓

2. Overlapped I/O 방식

발전된 버전

3. epoll

4. I/O Completion Port (IOCP)

 

3.1 블로킹 소켓

스레드에서 네트워크 수신을 하는 함수를 호출하면, 수신할 수 있는 데이터가 생길 때까지 스레드는 waitable 상태 (블로킹) 가 된다.

 

3.3 블로킹과 소켓 버퍼

소켓 각각은 송신 버퍼와 수신 버퍼를 하나씩 가지고 있다.

 

TCP 송신

소켓.send(data) 함수는 블로킹 없이 바로 리턴된다. (송신 버퍼가 가득 찬 경우가 아니라면)

 

TCP 수신

수신 버퍼가 가득 차면 사용자는 소켓에서 데이터를 수신하는 함수를 호출하여 수신된 데이터를 꺼낼 수 있다.

수신 버퍼가 완전히 비어 있으면 블로킹이 일어난다.

 

3.5 수신 버퍼가 가득 차면 발생하는 현상

TCP 수신 함수인 recv()는 1바이트라도 수신할 수 있으면 즉시 리턴한다.

수신 함수가 수신 버퍼에서 데이터를 꺼내는 속도가 운영체제가 수신 버퍼의 데이터를 채우는 속도보다 느리면 수신 버퍼가 꽉 차게 된다.

ㄴ 보내는 쪽의 송신 함수 send()가 블로킹된다. TCP 연결이 끊어지는 것은 아니다.

ㄴ 실제 송신 속도는 데이터를 느리게 처리하는 쪽에 맞추어진다.

ㄴ TCP는 초당 보내는 데이터양이 수신자가 초당 수신할 수 있는 데이터양보다 많을 때, 송신자 측 운영체제가 알아서 초당 송신량을 줄인다. (흐름 제어)

 

UDP의 경우

받는 쪽에서의 수신 함수는 수신 버퍼에 데이터그램이 하나라도 있으면 즉시 리턴한다.

수신 버퍼가 가득 차도 송신 함수에서 블로킹이 발생하지 않는다. (데이터그램은 버려진다.)

ㄴ 수신버퍼의 처리 속도가 느리면 데이터그램 유실이 발생한다.

ㄴ UDP에는 흐름 제어 기능이 없기 때문에 UDP를 속도 제한 없이 마구 송신하면 주변의 네트워킹이 경쟁이 밀려 혼잡 현상이 발생할 수 있다.

 

3.6 논블록 소켓

네트워킹을 해야하는 대상이 여럿인 경우에는 네트워킹 개수만큼 스레드를 만들고 각 스레드는 각각의 네트워킹 대상과 데이터를 주고받도록 하면 된다.

ㄴ 네트워크 대상이 많지 않을 때는 문제가 없다.

ㄴ 스레드 개수가 수백 개, 수천 개인 경우에는 컨텍스트 스위치 문제 때문에 자원 낭비로 이어진다.

 

논블록 소켓

ㄴ 송신 함수, 수신 함수에서 블로킹이 발생하지 않는다. (즉시 리턴한다, 리턴 값은 성공, 혹은 would block 둘 중에 하나)

ㄴ 대부분 운영체제에서 소켓 함수가 블로킹되지 않게 하는 API를 추가로 제공한다.

ㄴ 한 스레드에서 여러 소켓을 한꺼번에 다룰 수 있다.

ㄴ 플랫폼마다 다른 오류 처리 코드를 반환하기 때문에 잘 처리해야 한다.

 

논블록 소켓으로 연결을 시도하는 경우

ㄴ would block을 리턴한 경우 would block이 끝났는지 확인하기 위한 방법을 사용해야 한다.

ㄴ 클라이언트 입장에서는 게임 루프마다 길이가 0바이트인 데이터를 송신함수로 보내 성공 값을 받으면 연결이 성공한 것으로 간주하면 된다.

ㄴ 서버 입장에서는 스핀 락 방식으로 구현할 수 없다. poll 함수를 활용해야 한다. (CPU를 최대한 아껴야 하기 떄문에)

 

Poll 함수 (select 함수)

ㄴ 소켓 리스트와 블록 시간을 입력값으로 받는 함수

ㄴ 리스트 내 소켓중 하나라도 I/O 처리를 할 수 있게 되면 바로 리턴, 아니라면 지정된 타임아웃 시간 이후 리턴

 

논블록 accept

ㄴ 블로킹 모드인 경우 리스닝 소켓에 대해 accept()를 호출하면 accept()는 블로킹이 걸린다. 그리고 TCP 연결이 들어오면 리턴을 하는데, 이때 accept()의 리턴 값은 새 TCP 연결에 대한 소켓 핸들이다.

ㄴ 논블록 소켓을 사용한 I/O 함수는 연결이 안되었으면 블로킹 대신 would block 오류 코드를 주기 때문에 반복해서 재시도 호출을 해야 한다. Poll 함수를 사용하여 I/O 가능 이벤트가 감지될 때 accept()를 호출하도록 하여 CPU 사용량 폭주 문제를 해결해야 한다.

 

3.7 Overlapped I/O 혹은 비동기 I/O

논블록 소켓의 장점

ㄴ 스레드 블로킹이 없으므로 중도 취소 같은 통제가 가능하다.

ㄴ 스레드 개수가 1개여도 여러 소켓을 다룰 수 있다. (호출 스택 메모리 비용, 컨텍스트 스위치 비용이 작다)

 

논블록 소켓의 단점

ㄴ 소켓 I/O 함수가 리턴한 코드가 would block인 경우 재시도 호출 낭비가 발생한다.

ㄴ 소켓 I/O 함수를 호출할 때 입력하는 데이터 블록에 대한 복사 연산이 발생한다.

ㄴ connect() 함수는 재시도 호출을 하지 않지만, send() 함수나 receive() 함수는 재시도 호출해야 하는 API가 일관되지 않는다는 문제가 있다.

 

재시도 호출 낭비의 예시

UDP의 send()는 송신 버퍼가 1바이트라도 비어 있으면 I/O 가능인데 UDP는 데이터의 일부만 보낼 수 없으므로 넣을 수 있는 데이터 크기만큼의 공간이 비어있지 않다면 would block이 발생한다. I/O 가능인데 would block이므로 CPU로 낭비로 이어진다.

 

소켓 함수 내부의 데이터 복사 부하의 예시

ㄴ CPU 안에 있는 캐시 메모리에 내용이 복사되어 있으면 데이터 액세스는 매우 빠르지만, 캐시에 없는 데이터를 액세스할 때는 RAM을 액세스하는데 이것은 느리다.

 

Overlapped I/O는 이 문제들을 해결할 수 있다.

ㄴ Overlapped I/O 함수는 즉시 리턴되지만, 운영체제로 해당 I/O 실행이 별도로 동시간대에 진행되는 상태이기 때문에 I/O 함수가 비동기로 하는 일이 완료될 때까지 소켓 API에 인자로 넘긴 데이터 블록을 제거하거나 내용을 변경해서는 안된다.

ㄴ I/O 함수의 인자로 보내는 Overlapped status 구조체 또한 중간에 내용을 건드려서는 안된다.

 

Overlapped I/O 전용 송신 함수를 호출하면 운영체제는 송신할 데이터가 저장되어 있는 메모리 블록 자체를 송신 버퍼로 사용한다. 수신할 때는 수신 데이터 블록 자체를 수신 버퍼로 사용한다.

 

논블록 방식 대비 Overlapped I/O의 장단점

장점

소켓 I/O 함수 호출 후 would block 값인 경우 재시도 호출 낭비가 없다.

소켓 I/O 함수를 호출할 때 입력하는 데이터 블록에 대한 복사 연산을 없앨 수 있다.

send, receive, connect, accept 함수를 한 번 호출하면 이에 대한 완료 신호는 딱 한 번만 오기 때문에 프로그밍 결과물이 깔끔하다.

I/O completion port와 조합하면 최고 성능의 서버를 개발할 수 있다.

윈도우 플랫폼에서만 사용할 수 있다.

 

 

단점

완료되기 전까지 Overlapped status 객체나 데이터 블록을 중간에 훼손하지 말아야 한다.

accept, connect 함수 계열의 초기화가 복잡하다.

 

논블록 소켓은 리액터 패턴, Overlapped I/O는 프로액터 패턴이다.

 

3.8 epoll (논블록 방식)

epoll은 안드로이드와 리눅스에서만 사용 가능하다.

IOS, MacOs, FreeBSD에서는 epoll과 매우 유사한 kqueue를 사용하면 된다.

 

epoll에 논블록으로 설정된 소켓을 

 

select를 쓰면 모든 소켓에서 루프를 돌지만 epoll을 사용하면 I/O 가능인 상태의 소켓에서만 루프를 돌면 된다. 

소켓의 송신 버퍼가 빈 공간이 없는 순간을 유지하는 순간은 짧기 때문에 대부분의 소켓은 I/O 가능 상태이다. (비효율적이다) 

이 문제를 해결하기 위해 레벨 트리거 대신 에지 트리거를 써야 한다.

 

레벨 트리거 : I/O 가능이면 epoll에서 항상 꺼내어진다.

에지 트리거 : I/O 가능이 아니었다가 가능으로 변하는 순간에만 epoll에서 꺼내어진다.

 

에지 트리거를 쓸 때 주의해야 할 점

1. I/O 호출을 한 번만 하지 말고 would block이 발생할 때까지 반복해야 한다.

2. 소켓은 논블록으로 미리 설정되어 있어야 한다.

 

3.9 IOCP

epoll은 논블록 소켓을 대량으로 갖고 있을 때 효율적으로 처리해 주는 API이다.

IOCP는 Overlapped I/O를 다루는 운영체제에서 사용하는 API이다.

 

IOCP는 소켓의 Overlapped I/O가 완료되면 이를 감지해서 사용자에게 알려 주는 역할을 한다. 

ㄴ epoll과 비슷하지만 epoll은 I/O 가능인 것을 알려 주지만, IOCP는 I/O 완료인 것을 알려 준다.

 

epoll과 비교했을 때 사용법에 복잡한 기능이 몇 가지 있는데, 그중 하나가 Accept 처리이다.

1. IOCP에 listen socket L을 추가했다면, L에서 TCP 연결을 받을 경우 이에 대한 완료 신호가 IOCP에 추가됩니다.

2. 단 사전에 이미 AcceptEx로 Overlapped I/O를 건 상태여야 한다.

3. IOCP로 L에 대한 이벤트를 얻어 왔지만, 앞서 Overlapped accept처럼 SO_UPDATE_ACCEPT_CONTEXT와 관련된 처리를 해 주어야 새 TCP 소켓 핸들을 얻어 올 수 있다.

 

IOCP는 epoll에서 할 수 없는 성능상 유리한 기능이 있다. (스레드 풀링을 활용하여 여러 가지 일을 스레드 몇 개가 골고루 분담해서 처리할 수 있다)

'읽은 책 > 게임 서버 프로그래밍 교과서' 카테고리의 다른 글

2. 컴퓨터 네트워크  (2) 2022.09.09
1. 멀티 스레딩  (0) 2022.09.08

 

2.3 컴퓨터 네트워크 데이터

OSI 모델의 2계층에서는 데이터 단위를 프레임이라 하고, 3계층에서는 패킷이라고 한다.

프로그래머는 이것들 대신 스트림과 메세지라는 것을 주로 다룬다.

 

2.3.1 스트림 형식

스트림 : 두 단말기를 연결한 후 그 연결을 끊기 전까지 한쪽에서 다른 한쪽으로 연결된 데이터 흐름

 

컴퓨터 네트워크에서 스트림의 개념은 파일의 스트림과 다르다.

ㄴ 데이터가 여러 부분으로 

ㄴ 스트림 송신 횟수와 수신 횟수가 불일치할 수 있다.

 

데이터가 여러 부분으로 나뉘어질 수 있다면 사용자가 어떻게 받을지 정의를 해주어야 한다.

ㄴ 데이터의 크기를 헤더로 붙이는 방식

ㄴ 구분자를 이용하는 방식

 

2.3.2 메세지 형식

보낸 데이터의 수와 받는 데이터의 수가 같다. (각 데이터가 정확히 구별된다)

 

 

운영체제에 내장된 네트워크 모듈인 네트워크 스택에서 프로그램이 보내고 받는 스트림과 메세지를 관리해주기 때문에

IP패킷의 크기가 제한적이더라도 스트림이나 메세지에서는 크기 제한이 없다.

 

프로그램이 매우 긴 스트림을 송신할 때 운영체제는 이를 IP 패킷의 크기 제한에 맞추어 여러 조각을 낸다 (단편화)

각 조각은 IP 패킷 하나하나가 되어 받는 쪽에 송신한다.

받는 쪽에서의 복원 과정도 운영체제 안에서 진행된다.

 

2.4 컴퓨터 네트워크 식별자

IPv4는 4바이트

IPv6는 16바이트

포트는 2바이트

 

2.5 컴퓨터 네트워크의 품질과 특성

2.5.1 네트워크의 품질을 저해하는 것들

 

1. 스위치나 라우터가 자신이 처리할 수 있는 한계를 넘는 데이터를 수신할 경우

ㄴ 자기가 처리할 수 있는 것 이상을 그냥 버리기

ㄴ 아직 처리하지 못한 것들을 메모리에 누적하기

 

2. 데이터가 오가는 선로에 문제가 있는 경우

ㄴ 회선 신호가 약하거나 잡음이 섞이면 패킷 유실이 발생할 수 있다.

 

2.5.2 전송 속도와 전송 지연 시간

전송 속도 : 두 기기 간에 초당 전송될 수 있는 최대 데이터 양, 보통 bps로 표현 (= 두 단말기 사이에 있는 네트워크 기기 중 최소 스루풋에 의해 결정)

레이턴시 : 두 기기 간에 최소량의 데이터를 전송할 때 걸리는 시간 (= 두 단말기 사이에 있는 네트워크 기기의 레이턴시 총합)

 

전송 속도에 영향을 주는 것

ㄴ 선로의 종류와 품질

ㄴ 두 기기의 소프트웨어와 하드웨어 종류

 

레이턴시에 영향을 주는 것

ㄴ 선로의 종류와 품질

ㄴ 송신자-수신자 사이의 라우터 처리 속도

 

송신자와 수신자 사이에는 스위치, 라우터, 방화벽 같은 네트워크 기기들이 있고 각각의 기기들은 수신한 데이터를 처리 가능한 데이터로 변환하고 그것에 대한 연산 처리를 하기 때문에 시간을 차지한다.

 

2.5.3 네트워크 품질 기준 세 가지

전송 속도, 패킷 유실률, 레이턴시

 

2.5.4 무선 네트워크의 품질

다른 기기들이 동시에 신호를 보내면 신호가 변조되기 때문에 다른 전파가 감지되는지 확인 후 데이터를 보내기 때문에 (CSMA) 레이턴시가 길게 나온다.

 

2.6 컴퓨터 네트워크에서 데이터 보내기와 받기

2.6.1 UDP 네트워킹 (User Datagram Protocol)

받는 쪽의 프로그램과 보내는 쪽의 프로그램이 각자 데이터 통신에 사용할 포트를 할당하고 있어야 한다.

UDP를 이용하여 데이터그램을 보낼 수 있는데, 데이터그램은 64KB 이하의 이진 테이터로, 스트림이 아닌 메세지 성질을 가진다.

ㄴ 데이터그램 내용이 훼손되지는 않지만 패킷 유실이 발생할 수 있다. (데이터그램을 못 받거나 같은 데이터그램을 두 번 이상 받을 가능성이 있다)

ㄴ 받는 쪽에서 데이터그램 유실이나 순서 뒤바뀜 혹은 중복 수신 현상이 발생해도 괜찮은 경우에만 UDP를 사용하는 것이 좋다. (예를 들면 동영상이나 음성 데이터, 네트워크 게임에서는 캐릭터의 이동 정보)

 

UDP로 데이터를 주고받으려면 소켓을 생성하고 포트를 바인드 한뒤 송신, 수신 작업을 진행하면 된다.

 

UDP의 특징

수신용 소켓과 송신용 소켓을 따로 만들지 않아도 된다.

다대다 통신이 가능하다. (하나의 소켓이 여러개의 소켓으로 송수신 할 수 있다)

 

2.6.2 TCP 네트워킹

데이터를 주고받기 전에 연결을 해줘야 한다.

TCP는 메세지 형태가 아니라 스트림 형태로 데이터들을 전달한다.

 

UDP는 IP 패킷 유실이 발생하면 UDP 데이터그램도 드롭되지만 TCP는 똑같은 상황에서도 흐름 제어 기능이 있기 때문에 데이터가 상대방에게 정확하게 전송된다.

 

TCP에서 보낼 스트림 데이터는 세그먼트라는(IP 패킷안에 넣을 수 있는 크기) 단위로 쪼개져 수신자에게 전송된다.

수신자가 IP 패킷을 받으면 ack를 회신하는데 보낸 쪽에서는 일정 시간 안에 ack이 회신되지 않으면 응답이 올때까지 다시 세그먼트를 보낸다.

 

TCP 소켓 사용법

보내는 쪽

1. TCP 소켓 생성

2. 포트 바인딩

3. 연결 (받는 쪽 ip:port) (블로킹)

4. 송신 (이미 연결 되어있으므로 ip:port가 아닌 데이터만 사용)

5. 필요한 만큼 송신했다면 연결을 끊는다. (close)

 

받는 쪽

1. TCP 소켓 생성

2. listen (포트)

3. accept() (연결 될때 까지 블로킹, 새로운 소켓 생성)

4. 반복문내에서 recv()로 데이터를 받는다. 연결 상태를 유지하다가 0바이트 크기의 데이터를 받으면 반복문을 탈출하고 연결을 끊는다. (close)

ㄴ UDP에서는 데이터그램을 메세지 형식으로 주고받는다. 0바이트 메세지도 허용한다. TCP는 크기 0의 데이터를 받으면 연결이 종료되었음을 의미한다.

 

TCP 소켓도 UDP 소켓과 마찬가지로 소켓 하나로 송수신을 모두 할 수 있다.

 

2.7 패킷 유실 시 UDP와 TCP에서 현상

UDP는 1만 바이트의 데이터그램을 UDP로 전송하면 운영체제는 이를 1300바이트 정도의 크기 8개로 나누어 IP 패킷 8개로 전송되는데 이들 중 하나라도 유실되면 수신자는 8개 모두 못 받는다. (1만바이트짜리 데이터그램이 유실된다)

 

TCP에서는 유실되는 패킷의 재전송 시간만큼 지연 시간이 발생한다.

 

UDP의 레이턴시 = 네트워크 기기의 레이턴시

TCP의 레이턴시 = 네트워크 기기의 레이턴시 + 패킷 유실률 * 재전송 대기 시간

 

네트워크 게임 개발에서 거의 모든 메세지 종류에는 TCP를 사용하고 특정 상황에서만 UDP를 사용하지만 실제 통신량의 대부분은 UDP가 차지한다.

 

2.8 주로 사용하는 메세지 형식

1. 텍스트 형식

ㄴ HTTP나 JSON 같은 표준화된 형식을 씀

 

2. 바이너리 형식

ㄴ 각 바이트 혹은 비트 필드는 특정한 의미를 가진다.

ㄴ 텍스트 형식에 비해 구문 분석기가 필요 없어서 처리 성능이 더 낫고, 통신량도 적다

 

메세지 안에 내용을 담는 것뿐만 아니라 그 메세지 내용이 어떤 것을 담고 있는지의 정보를 같이 포함시키는 형식

 

ㄴ 메타 데이터를 포함시키면 데이터 통신량이 많아지고, 해커에게도 더 쉽게 노출된다는 단점이 있지만 하위 호환성 측면에서 유리하다.

ㄴ 받는쪽과 보내는쪽의 버전이 달라도 호환성 문제를 해결할 수 있다.

 

2.9 네트워크 주소 변환

네트워크 주소 변환 (NAT : network address translation)

ㄴ 다른 단말기로 전송되던 패킷의 송신자 주소나 수신자 주소가 다른 것으로 변환되는 과정 

ㄴ 인터넷 공유기는 NAT 라우터이다.

 

공유기 안에 있는 기기가 공유기 밖에 있는 기기에 패킷을 보내려고 하면 패킷을 통과하여 수신지에 전달

공유기는 내부 주소와 외부 주소의 매핑 정보를 생성해서 가지고 있는다.

 

2.11 더 읽을 거리

TCP 흐름 제어 (ARQ)

ㄴ slicing window (윈도우 크기만큼의 패킷을 응답받지 않고 보내기)

ㄴ 송신측의 처리 속도가 수신측의 처리 속도보다 빨라서 수신측의 저장용량을 초과한 데이터가 손실되는 것을 방지하기 위함

 

TCP 혼잡 제어

ㄴ AIMD (additive increase, multiplicative decrease), slow start, fast retransmit, fast recovery

 

네트워크 게임의 성능 최적화를 위해 UDP 위에 흐름 제어를 별도로 구현하는 경우도 있다.

 

송신자와 수신자 간 오가는 메세지는 제 3자가 도청하거나 변조할 수 있기 때문에 메세지를 암호화해야 한다.

'읽은 책 > 게임 서버 프로그래밍 교과서' 카테고리의 다른 글

3. 소켓 프로그래밍  (0) 2022.09.10
1. 멀티 스레딩  (0) 2022.09.08

1.4 스레드 정체

CPU 개수와 Runnable 스레드 개수가 같거나 Runnable 스레드 개수가 더 적으면 컨텍스트 스위치가 발생할 이유가 없다. 하지만 Runnable 스레드 개수가 더 많으면 컨텍스트 스위치가 어느 CPU 안에서는 반드시 발생한다. (비둘기집 원리)

 

멀티스레딩을 사용하여 소수 구하기

1 ~ 150000 사이의 소수들을 찾는 프로그램

멀티 스레드를 활용하지 않았을 때
4개의 스레드를 활용할 때

 

1.8 잠금 순서의 규칙

 

여러 뮤텍스를 사용할 때 교착 상태를 예방하려면 각 뮤텍스의 잠금 순서를 먼저 그래프로 그려 두고, 잠금을 할 때 잠금 순서 그래프를 보면서 거꾸로 잠근 것이 없는지 체크해야 한다.

 

재귀 뮤텍스

ㄴ 한 스레드가 같은 뮤텍스를 여러 번 반복해서 잠궈도 된다. 물론 잠금 횟수만큼 풀어야 한다.(재귀 잠금이 가능하다.)

ㄴ 재귀 잠금은 잠금 순서 그래프에 상관 없이 교착 상태를 일으키지 않는다. (첫 잠금에서만 순서를 지키면 된다)

 

1.9 병렬성과 시리얼 병목

여러 CPU가 각 스레드의 연산을 싫애하여 동시 처리량을 올리는 것을 병렬성이라고 한다. 병렬로 실행되게 프로그램을 만들었는데 정작 한 CPU만 연산을 수행하는 현상을 시리얼 병목이라고 한다.

 

 

시리얼 병목이 있을 때 CPU 개수가 많을수록 총 처리 효율성이 떨어지는 현상을 암달의 법칙(Amdahl's Law)라고 한다.

 

Visual Studio의 Concurrency Visualizer도구

ㄴ 멀티 스레드 프로그램이 병렬적으로 여러가지 일들을 동시에 잘 수행하는지 분석하여 시각화해서 보여주는 도구

 

 

https://marketplace.visualstudio.com/itemsitemName=Diagnostics.DiagnosticsConcurrencyVisualizer2019#overview

 

4개의 스레드를 사용한 프로그램을 분석한 경우

1.10 싱글 스레드 게임 서버

 

1.11 멀티 스레드 게임 서버

 

1.12 스레드 풀링

처리할 이벤트를 큐에 쌓아두고 비어 있는 스레드에 이벤트를 처리하도록 배분하는 방식이다.

스레드 개수를 클라이언트 개수만큼 두지 않고 스레드 풀링을 사용한다.

 

CPU 코어가 8개 있는 8개의 스레드를 실행시키는 상황에서 스레드가 CPU 연산만 하는 것이 아니라 데이터베이스나 파일 등에 액세스하는 스레드여서 CPU 사용율이 25%이고 디바이스 타임이 75%인 상황이라면 스레드 풀의스레드의 갯수를 4배 늘려 CPU를 놀게 하지 않게 해야 한다.

 

CPU 연산이 주 역할인 서버의 스레드 풀의 스레드 개수는 서버의 CPU 코어 개수와 동일하게 잡아도 충분하다.

 

1.13 이벤트

잠자는 스레드를 깨우는 도구로써 사용된다.

event는 내부적으로 0과 1의 상태 값을 가진다. (이벤트가 없으면 0 있으면 1)

 

윈도우 API에 이벤트 관련 함수가 있다.

https://docs.microsoft.com/ko-kr/windows/win32/sync/using-event-objects

 

이벤트 개체 사용(동기화) - Win32 apps

애플리케이션은 여러 상황에서 이벤트 개체를 사용하여 대기 스레드에 이벤트 발생을 알릴 수 있습니다.

docs.microsoft.com

 

자동 이벤트

ㄴ 이벤트 상태 값을 0으로 갖고 있다가 신호를 받으면(SetEvent) 상태 값이 1로 바뀌는데 이때 이벤트를 기다리던(잠들어 있던) 스레드 (Wait)가 있다면 그 스레드를 깨우고 자동으로 0의 상태 값으로 바뀐다.

ㄴ 두개 이상의 스레드를 이벤트를 기다리고 있었다면 여러 스레드중 하나만 깨어난다.

 

수동 이벤트

ㄴ 이벤트가 신호를 받으면 대기하고 있던 모든 스레드가 깨어난다.

ㄴ SetEvent()를 한 스레드에서 호출하면 대기하던 스레드에서 SetEvent(0)을 호출하여 수동으로 이벤트 상태 값을 되돌려줘야 한다. 하지만 이 방법을 사용하면 대기하던 스레드중 일부만 깨어날 가능성이 있다.

ㄴ PulseEvent()를 사용하여 이벤트에 신호를 주면 모든 스레드를 깨우면서 상태값을 자동으로 0으로 되돌려 준다.

 

1.14 세마포어

뮤텍스는 1개의 스레드만 자원을 액세스할 수 있게 하지만 세마포어는 원하는 개수의 스레드가 자원을 액세스할 수 있게 한다.

 

Wait() ~ Release() (mutex의 lock unlock같은 느낌)을 사용한다.

 

윈도우 API에 세마포어 관련 함수가 있다.

https://docs.microsoft.com/ko-kr/windows/win32/sync/using-semaphore-objects

 

세마포 개체 사용 - Win32 apps

다음 예제에서는 세마포 개체를 사용하여 특정 작업을 수행할 수 있는 스레드 수를 제한합니다.

docs.microsoft.com

 

스레드 간에 공유되는 큐가 있고 한 스레드는 큐에 항목을 넣고 다른 한 스레드는 큐에서 항목을 꺼내 쓰는 디자인에 활용하기 좋다.

 

초기값이 0인 세마포어를 만든 뒤, 항목을 넣는 스레드에서는 세마포어.Release()를 하면서 큐에 푸시하고 꺼내는 스레드에서는 Wait()를 하여 세마포어 값이 1 이상인 경우에만 큐에서 하나씩 요소를 꺼낸다.

 

1.15 원자 조작

원자 조작은 뮤텍스나 임계 영역 잠금 없이도 여러 스레드가 안전하게 변수에 접근할 수 있게 해준다.

32비트나 64비트 변수 타입에 여러 스레드가 접근할 때 한 스레드씩만 처리됨을 보장한다.

mutex보다 속도가 더 빠르다.

 

https://lemonyun.tistory.com/103

 

C++ memory order, atomic객체

atomic #include std::atomic counter(0); 여러 쓰레드에서 atomic 객체 자원을 수정할 때 원자적 연산(CPU가 명령어 한개로 처리하는 명령)을 사용하면 mutex를 사용하지 않아도 정상적인 결과가 나온다. memory..

lemonyun.tistory.com

 

'읽은 책 > 게임 서버 프로그래밍 교과서' 카테고리의 다른 글

3. 소켓 프로그래밍  (0) 2022.09.10
2. 컴퓨터 네트워크  (2) 2022.09.09

 

compl

~ 연산자를 대체함

 

explicit

생성자에 붙여 암시적 형변환을 금지하고 명시적 형변환만 가능하게 만드는 용도로 사용한다.

예를 들어 Point p = 10; 의 사용을 금지하고 (int -> Point의 형변환 금지) Point p = Point(10); 만을 사용할 수 있게 한다.

 

extern

다른 파일에 선언된 함수나 변수를 참조하기 위한 용도로 사용 (사용할 전역 변수가 외부 cpp 파일에 있다는 것을 알림)

 

현재 스코프에 사용하고자 하는 함수가 없는 경우 같은 파일 내에서 현재 스코프까지 없는 함수를 참조할 수 있게 해준다.

 

friend

수평적인 클래스 간의 멤버 변수를 공유해야 할 경우 주로 사용된다.

 

private, protected로 선언된 멤버를 외부 클래스에서 접근할 수 있게 해주는 키워드

 

접근 당할 클래스에서 friend 함수, friend 클래스를 선언해주면 외부 함수와 클래스에서 접근 제한자의 영향을 받지 않고 멤버에 접근할 수 있다.

 

mutable

const 함수에서는 원래 멤버 변수의 값을 바꿀 수 없는데 mutable로 선언된 변수의 값은 바꿀 수 있다.

 

volatile

변수의 컴파일러 최적화를 제한하는 용도로 사용 (잘 모르겠음..)

 

typeid, type_info

런타임에 자료형, 개체, 표현식의 형식을 확인할 수 있다.

 

#include <typeinfo>
template < typename T >
T max( T arg1, T arg2 ) {
   cout << typeid( T ).name() << "s compared." << endl;
   return ( arg1 > arg2 ? arg1 : arg2 );
}

typeid() 함수는 추상 클래스 type_info를 구현한 객체를 반환한다.

 

noexcept (C++11)

예외를 발생시키지 않을 함수에 noexcept를 붙인다.

어떤 함수가 절대로 예외를 발생시키지 않는다는 것을 명시하면 컴파일러가 최적화를 수행할 수 있다.

 

noexcept가 붙은 함수에서 예외를 발생시키면 예외가 처리되지 않고 프로그램이 종료된다.

 

C+11부터 소멸자들은 기본적으로 noexcept이기 때문에 소멸자에서 예외를 던지면 안된다.

 

static_cast (C++11)

논리적으로 변환 가능한 타입을 변환한다.

실수 <-> 정수, 열거형 <-> 정수형, 실수 <-> 실수

상속 관계에 있는 포인터 끼리의 변환 (업캐스팅)

 

dynamic_cast (C++11)

안전한 다운 캐스팅을 위해 사용된다.

런타임 시간에 실제로 해당 타입이 다운 캐스팅 가능한지 검사하기 때문에 런타임 비용이 조금 높은 캐스트 연산자이다.

 

reinterpret_cast (C++11)

임의의 포인터 타입끼리 변환을 허용하는 캐스트 연산자

정수 계열 형식이 포인터 형식으로 변환될 수도 있다. (그 반대도 가능)

 

int 변수를 첫 번째 멤버로 갖는 구조체가 있다고 할때 구조체 변수 포인터를 int형 포인터로 형변환하면 int형 변수를 가리키는 포인터가 된다.

 

const_cast (C++11)

const 포인터와 const 레퍼런스의 상수성을 제거하는데 쓰인다.

 

alignof, alignas (C++11)

최신 컴퓨터 하드웨어의 CPU는 데이터 주소가 데이터 크기의 배수일때 효율적으로 메모리를 읽는다.

 

alignas

ㄴ 타입의 정렬 조건을 지정한다.

// alginas(4)와 alignas(alginof(float))은 동일하다.

struct alignas(32) sse_t{ // sizeof(sse_t) = 32, alignof(sse_t) = 32
    float sse_data[4];
}

alignas(64) char cacheline[64]; // alignof(cacheline) = 64

 

alignof(타입)

ㄴ 타입의 맞춤 단위를 반환한다.

 

static_assert (C++11)

컴파일 타임에 assertion을 처리할 수 있다.

템플릿 인자도 constant-expression 범주 안에 들어갈 수 있기 때문에 템플릿을 디버깅하는데 용이하다.

 

더 많은 키워드 확인:

https://docs.microsoft.com/ko-kr/cpp/cpp/keywords-cpp?view=msvc-170 

 

키워드(C++)

C++ 표준 언어 키워드, Microsoft 관련 키워드 및 컨텍스트별 키워드를 나열합니다.

docs.microsoft.com

 

'C++' 카테고리의 다른 글

C++ 동기와 비동기  (0) 2022.08.26
C++ Callable  (0) 2022.08.26
C++ RAII와 스마트포인터  (0) 2022.08.26
C++ 가상 함수, virtual 소멸자, 가상 함수 테이블, 추상 클래스  (0) 2022.08.25
C++ 템플릿, 함수 객체(Functor)  (0) 2022.08.25

C++에서는 시간이 오래 걸리는 작업(파일 읽기)을 쓰레드를 만들어 처리할 수 있다.

프로그램의 실행이 한 갈래가 아니라 여러 갈래로 갈라져서 동시에 진행되는 것을 비동기적 실행이라고 부른다.

 

C++11 표준 라이브러리는 간단한 비동기적 실행을 할 수 있게 해주는 도구를 제공한다.

 

std::promise와 std::future

#include <future>
#include <iostream>
#include <string>
#include <thread>
using std::string;

void worker(std::promise<string>* p) {
  // 약속을 이행하는 모습. 해당 결과는 future 에 들어간다.
  p->set_value("some data");
}
int main() {
  std::promise<string> p;

  // 미래에 string 데이터를 돌려 주겠다는 약속.
  std::future<string> data = p.get_future();

  std::thread t(worker, &p);

  // 미래에 약속된 데이터를 받을 때 까지 기다린다.
  data.wait();

  // wait 이 리턴했다는 뜻이 future 에 데이터가 준비되었다는 의미.
  // 참고로 wait 없이 그냥 get 해도 wait 한 것과 같다.
  std::cout << "받은 데이터 : " << data.get() << std::endl;

  t.join();
}

 

 

future에서 get을 호출하면 설정된 객체가 이동하기 때문에 두 번 호출해서는 안된다.

 

future에 예외도 전달할 수 있다.

 

shared_future

여러 개의 다른 쓰레드에서 future를 get할 필요가 있을 때 사용

future와 달리 복사가 가능하고, 복사본들이 모두 같은 객체를 공유하게 된다.

 

packaged_task

C++에서는 promise-future 패턴을 비동기적 함수(Callable)의 리턴값에 간단히 적용할 수 있는 package_task라는 것을 지원한다.

 

std::async

쓰레드를 명시적으로 생성하지 않아도 됨

std::async에 함수를 전달하면 알아서 쓰레드를 만들어 전달된 함수를 비동기적으로 실행하고, 결과값을 future에 전달함.

 

참조: 

https://modoocode.com/284

'C++' 카테고리의 다른 글

C++ 키워드 정리  (0) 2022.08.27
C++ Callable  (0) 2022.08.26
C++ RAII와 스마트포인터  (0) 2022.08.26
C++ 가상 함수, virtual 소멸자, 가상 함수 테이블, 추상 클래스  (0) 2022.08.25
C++ 템플릿, 함수 객체(Functor)  (0) 2022.08.25

+ Recent posts