월 66,000원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
한가지 에러와 한가지 질문이 있습니다
한가지 에러는 WRITE_LOCK 부분인데요void MemoryPool::Push(MemoryHeader* ptr){ WRITE_LOCK; //Pool에 메모리 반납 queue.push(ptr); allocCount.fetch_sub(1);}부분에서 WRITE_LOCK에서 에러가 나고 있는 상황입니다.Push와 Pop 둘 다 동일합니다.심각도 코드 설명 프로젝트 파일 줄 비표시 오류(Suppression) 상태오류(활성) E0300 바인딩된 함수에 대한 포인터는 함수를 호출하는 데에만 사용할 수 있습니다.이런 에러가 나고 있는 상황이고 한가지 질문은 Memory부분에서if (allocSize > MAX_ALLOC_SIZE) { header = reinterpret_cast<MemoryHeader*>(malloc(allocSize)); }부분에서 왜 static_cast를 쓰지 않고 reinterpret_cast를 사용하는 건가요? static_cast를 사용했을 때 발생할 수 있는 문제점이 있나요?
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
10014 에러 관련 질문
위 스크린샷과 같이, 10014 에러 코드가 뜨면서 딱 한 번 데이터를 주고받은 다음에 진행이 되지 않았습니다.그리고 디버깅 및 코드 비교를 통해 RecvBuffer.cpp 부분에서 아래와 같은 실수가 있었기에 위와 같은 오류가 발생하게 된 것임을 알게 되었습니다.그런데 강의에서 저는 이 버퍼의 크기를 크게 잡아주는 것이 일종의 성능향상과 편의를 위함으로 이해했었습니다. 그리고 처음에는 10014 오류가 주소와 관련한 오류라는 것을 보고, NetAddress, Session, Listener를 집중하여 분석하느라 원인을 찾는 데에 오래 걸렸었습니다. 위 상황에서 저 버퍼의 크기를 resize 해주는 과정이 잘못된 것이 왜 10014 오류를 발생시키게 되었는지 의문점이 들었습니다. 혹시 10014 오류가 발생했던 정확한 원인이 무엇이었을지 알려주실 수 있을까요? (코드 : https://drive.google.com/file/d/1Yjn7lKGXzTKfNuBmRBw66t7AkZgbmGIO/view?usp=sharing )
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
push 에 관한여 해제..
push 에서 push 스레드가 3개 있다고 했을때 3개 모두다 T* oldData = nullptr; 이 구문 까지 실행 됐다는 가정 하에 질문을 해보면..if (oldTail.ptr->data.compare_exchange_strong(oldData, newData.get())){ oldTail.ptr->next = dummy; oldTail =tail.exchange(dummy); [........] FreeExternalCount(oldTail); 현재 스레드에서 [........] 부분 까지 실행 되다가(FreeExternalCount(oldTail); 이건 실행 되지 않고..)다른 push 스레드들 에서 이 구문을 들어오지 못한 push 스레드는들은 oldTail.ptr->ReleaseRef(); 이 계속 반복적으로 계속 호출 되면서 internalCount 값이 -2 이상을 훨씬 더 넘는 상황이 발생 할 수도 있을것 같은데현재 스레드는 ... 에서 계속 처리가 되지 않고 스케줄러에 의해 지연되고 있다 가정한다면 internalCount 카운트의 계산이 이상해지고 while 문은 언젠가 끝나긴 하겠지만 internalCount 가 -로 많이 쌓이게 될 경우에는 메모리가 삭제 되지 않고 누수가 될것 같은데요..? 맞나요?아직 pop 함수는 보진 않았습니다만.. 카운팅 방식이 맞나해서 질문 남깁니다
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
특정 영상이 재생되지 않습니다
나머지 영상은 괜찮은데 Thread Local Storage 영상이 어제부터 계속 재생이 안되는데 인프런 서버 문제일까요? 혹시 해결 방법이 있을까요?
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
클라에서 이상하게 패킷을 대량으로 보낼때 질문입니다.
안녕하세요 Rookiss 선생님!"일정 시간 동안" 클라가 서버로 WSABuf 와 같이 한번에 여러 패킷 ID와 데이터를 보내는 경우가 아닌,하나의 일정한 덩어리 "패킷 ID+데이터" 단위로 여러 개 보낼 경우서버가 recv 과정에서 이상하게 클라로부터 패킷을 여러개 받을 때어떤 패킷 아이디로 이상하게 왔는지 분석하는 효율적인 방법이 있을까요?클라에서 WSABuf와 같이 뭉쳐서 보내면 서버 recv 단에서 패킷 크기가 많을 경우 따로 처리하면 되겠지만.클라가 일정 크기로 하나의 패킷 ID+ 데이터 단위로 보내면 계속 패킷 크기를 카운트 해야 한다는 문제점이 있을 거 같습니다.또한 어떤 패킷 아이디가 이상한가? 분석하려면 서버측에서 클라가 보낼 수 있는 패킷 아이디 C_ 개수만큼 배열을 설정하고패킷 아이디 마다 카운트를 늘리고 다시 0으로 초기화하는 작업은 비효율적이거 같아서 질문드립니다.
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
동기화 문제
질문.1 push 쪽에서 node->next = head; 이 로직이 실행 된 이후 head.compareexchange_weak 이 실행 되기전pop 이 다른스레드에 의해서 먼저 실행 된다면 _head 값이 달라질것이고 이와 반대 되는 상황도 있을것 같은데그럴때에도 정상 작동 하는건가요? 질문2.반대로 pop 쪽에서 먼저 실행되고 질문1 처럼 실행 됐을때도 정상 작동 하는 걸까요?
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
Push에서 exchange 사용 이유
140번줄에 있는 _tail.exchange(dummy); 부분에서exchange를 사용하는 이유가 궁금합니다.해당 부분에서 필요한 기능은 store만 해도 될 것 같다는 생각이 듭니다. 그래서 139번줄에 있는 store로 바꿔서 테스트 해봐도 일단은 문제가 없었는데요.혹시 제가 놓치는 다른 이유가 있는 건가요?
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
Event와 CV 차이
프로듀서에서 빠르게 데이터를 밀어주는 경우Event는 이벤트와 락 부분이 묶여 있지 않기 때문에 큐에 데이터가 있음에도컨슈머가 실행되지 못하고 프로듀서가 계속 실행되는 경우가 생기지만,CV는 락과 조건체크 부분이 묶여서 그런 일을 방지해준다.즉 데이터가 있으면 무조건 팝 할수 있게 된다.이렇게 이해하면 맞을까요 ?
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
결국 atomic도 lock도
그러면 결국 유저가 직접 구현해서 경쟁상태를 해소할 방법은 없는건가요 ?어셈블리 명령어가 쪼개져서 생기는 문제니깐 원자적 연산이나 lock 같은것을 응용 프로그래머가직접 구현 할수는 없는것인가여 ...?다른책에 CAS 함수가 하나 있길래 적용해볼려고 했는데 제가 잘못 이해한건지..아니면 이것도 결국 비교->대입 하는 부분을 원자적으로 묶을 방법이 없어서 불가능한 의사코드인가요#include <iostream> #include <thread> using namespace std; class SpinLock { public: SpinLock() : mylock(false), expected(false), desired(true) {} void lock() { while (CompareAndSet(expected, desired) == true) { static long time = 0; cout << " I am Spining now.." << time++ << endl; expected = false; } } void unlock() { mylock = false; } bool CompareAndSet(bool expected, bool desired) { bool original = mylock; if (original == expected) mylock = desired; return original; } private: bool mylock; bool expected; bool desired; }; long sum; SpinLock spin; void Add() { for (int i = 0; i < 1'0000'0000; i++) { spin.lock(); sum++; spin.unlock(); } } void Sub() { for (int i = 0; i < 1'0000'0000; i++) { spin.lock(); sum--; spin.unlock(); } } int main() { thread t1(Add); thread t2(Sub); t1.join(); t2.join(); cout << sum; return 0; }
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
CAS 구현
스핀락 강의를 보다가 CAS 의사코드를 써주셨는데직접 구현할려면 어떻게 해야되나요 ?이렇게 한번 넣어봤는데 안되서요 ㅠㅠclass SpinLock { public: void lock() { while (CAS(expected, desried) == false) {} } void unlock() { _locked = false; } bool CAS(bool expected, bool desired) { if (_locked == expected) { expected = _locked; _locked = desired; return true; } else { expected = _locked; return false; } } private: atomic<bool> _locked = false; atomic<bool> expected = false; atomic<bool> desried = true; };
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
SendBufferRef에 대하여 질문 있습니다.
안녕하세요 루키스님이번 SendBuffer 강의를 들으면서 궁금한 것이 생겨 질문 드립니다.SendBuffer에 자기 자신에 대한 스마트 포인터를 갖도록 해주어 Reference Counting을 해주었는데, 이번 강의의 코드를 복습하면서 분석하다 보니 '왜 SendBuffer에 RefCounting이 필요한가?' 에 대한 의문이 생겼습니다. 일단 제가 이해한 Send의 흐름은 다음과 같습니다.GameSession에서 SendBuffer를 생성하여 원하는 데이터를 복사하여 Send를 호출하면 SendQueue에 일단 Push하여 데이터를 쌓아 놓습니다.그 때 어떠한 쓰레드에서도 그 세션에 대한 RegisterSend를 수행하고 있지 않다면 해당 쓰레드가 RegisterSend를 수행하는데 이때까지 쌓인 데이터를 SendEvent의 버퍼에 담아 WSASend를 수행합니다.그런 다음 IocpCore에서 완료 통지를 받으면 Worker 쓰레드가 Dispatch를 수행하여 ProcessSend를 수행하면서 이 때 SendQueue에 데이터가 더 있다면 그 쓰레드가 RegisterSend를 수행합니다.위의 흐름이 제가 이해한 내용인데 결국 한번에 하나의 RegisterSend가 진행되도록 하는 것이 핵심이며 한 번에 하나만 실행하기 때문에 SendEvent도 재 사용 가능한 것이라고 생각합니다. 그렇다면 이 세션에 대한 SendEvent는 Send되고 있는 동안에는 멀티쓰레드 환경에서 안전하다고 생각 되는데 그렇다면 SendEvent에 담긴 SendBuffer도 안전하여 사라질 걱정을 하지 않아도 되는 것 아닐까 싶습니다.즉, WSASend를 한 번에 하나의 쓰레드만 수행하기 때문에 SendEvent도 재 사용이 가능하고 그 속에 담긴 SendBuffer도 멀티쓰레드 환경으로부터 안전하기 때문에 SendBuffer의 RefCounting이 왜 필요한 것인지 궁금합니다.
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
LThreadIid의 범위 질문
const unit32 desired =((LThreadID<<16))&WRITE_THREAD_MASK);이부분에서 LThreadID가 2^16-1 이하라는 가정으로 하는건가요?? 이 값보다 LThreadID가 커버리면 중복되는 경우가 생길것같아서 질문드립니다.
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
3번강의 스택은 2번강의 스택에 비해 장점이 뭔가요??
완전 스마트 포인터로 대체된것도 아니고 결국 raw 포인터가 남아있는데 2번강의에 비해 어떤점이 나아진건지 모르겠습니다.
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
상속 관계 복사
안녕하세요 선생님~! 강의 정말 잘 보고 있습니다. // 상속 관계 복사 template<typename U> TSharedPtr(const TSharedPtr<U>& rhs) { Set(static_cast<T*>(rhs._ptr)); } 이 부분을 저는 같은 상속관계에 있는 애들끼리 서로 포인터를 공유할 수 있도록 하기 위해 존재한다고 이해했습니다. class StrongMissile : public Missile{}; using StrongMissileRef = TSharedPtr<StrongMissile>; MissileRef missile(new Missile()); StrongMissileRef strongMissile(new StrongMissile()); MissileRef m1 = strongMissile;MissileRef m2 = strongMissile; 위와 같이 테스트를 해봤는데 pirvate 이라 rhs._ptr 에 접근할 수 없다고 오류가 발생하더라구요. template<typename U> TSharedPtr(const TSharedPtr<U>& rhs) { Set(static_cast<T*>(rhs.Get())); } public: T* Get() const { return _ptr; } 그래서 위처럼 Get 함수를 만들어서 포인터를 넘겼는데 저렇게 T* 으로 포인터를 주면 위험해서 안되는건가요 ?? 아니면 다른 방법이 있는건가요 ?? 그게 아니라면 제가 잘못 이해하고 있는건가요 ??
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
질문이 두가지 있는데용
만약에 동시 참조권 얻은 사람이 둘 이라고 하면 externalCount =3 internalCount = 1 이 되잖아요. 소유권을 획득한 사람은 이미 나간것으로 치고 남은 소유권 못얻은 사람 중 마지막 한 사람이 (internalCount ==1) 불 끄고 나가는거죠?? 그리고 두번째로 계속 lock을 쓰는거랑 성능차이가 별로 안난다. 다들 궁금해하니까 알려준다 이러시는걸 봐서는 별로 알 필요가 없는 내용인가..? 싶어져서요. 회사 면접볼 때나 일할 때 중요할까요? 아니면 알아두면 좋은 정도인가요? 아니면 몰라도 되는 정도 인가요?
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
디버깅시 에러가 발생합니다.
따라서 만든 코드도 동일하고, 수업자료로 올려주신 코드로 디버깅을 해봐도 동일한 위치에서 계속 에러가 발생합니다. GameServer.cpp 의 // 나머지 소켓 체크for (Session& s : sessions) 이 부분에서 계속 에러가 발생하면서 디버깅이 멈추는데 어떤 이유에서일까요? vector관련된 에러인듯한데 자세한 원인을 못찾겠습니다.
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
_ maxSessionCount 관련하여 질문드립니다.
안녕하세요 루키스님 강의 정말 잘 듣고있습니다. 처음에 제가 강의를 들으면서 이해한 바로는 _maxSessionCount가 서버에 허용 가능한 최대 세션의 갯수를 의미하는 값으로 생각을 했습니다. 그래서 아래와 같이 ServerService 객체를 생성하면서 마지막 인자값으로 10을 넣으면 최대 10개의 클라이언트가 접속가능하다고 생각했습니다. ServerServiceRef service = make_shared<ServerService>( NetAddress(L"127.0.0.1", 7777), make_shared<IocpCore>(), []() -> SessionRef { return make_shared<GameSession>(); }, // TODO : SessionManager 등 10); 하지만 Listener에서 maxSessionCount만큼 AcceptEvent를 생성하여 RegisterAccept를 해주고 const int32 acceptCount = _service->GetMaxSessionCount(); for (int32 i = 0; i < acceptCount; i++) { AcceptEvent* acceptEvent = new AcceptEvent(); acceptEvent->owner = shared_from_this(); _acceptEvents.push_back(acceptEvent); RegisterAccept(acceptEvent); } 그 뒤에 클라이언트가 접속하면 Listener에서 Dispatch하여 ProcessAccept를 실행하면 아래와 같이 미리 생성했던 Session에 정보를 업데이트하고 난 후에 다시 RegisterAccept를 해줍니다. void Listener::ProcessAccept(AcceptEvent* acceptEvent) { /* 생략 */ session->SetNetAddress(NetAddress(sockAddress)); session->ProcessConnect(); // session->OnRecv(); cout << "Client Connected " << session->_socket << endl; // TODO RegisterAccept(acceptEvent); } 이렇게 된다면 AcceptEvent가 재사용되어 또 Session을 만들어 다른 클라이언트의 접속을 받을 수 있게 되는데 그렇다면 무한정으로 Session을 받을 수 있다는 의미가 아닌지 궁금합니다. 실제로 ServerService의 _maxSessionCount보다 DummyClient의 _maxSessionCount를 더 크게 하여 실행했을 때, DummyClient 갯수만큼 세션을 생성하는 것을 확인했습니다. 즉 _maxSessionCount가 의미하는 것이 서버에서 최대로 허용하는 세션의 갯수가 아니라 서버가 한번에 Accept 처리할 수 있는 최대 세션의 갯수인 것인지 궁금합니다.
- 해결됨[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
PushGlobal 관련 질문입니다.
PushGlobal의 매개변수가SendBufferChunkRef가 아닌 SendBufferChunk*인 이유가 있나요??
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
Lock-Free Stack #3 문의 있습니다..
안녕하세요. 좋은 강의 감사합니다..Lock-Free Stack #3에서 shared_ptr<T> TryPop(){CountedNodePtr oldHead = _head; <= === atomic_load를 붙여줘야 하는거 아닌가요 ? CountedNodePtr oldHead = atomic_load(&_head);안 붙여줘도 된다면 왜 안 붙여줘도 되는지 설명 부탁드리겠습니다.
- 미해결[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
std::async 와 싱글톤 class의 함수 사용 안전한가요?
std::async를 호출할 때 쓰레드 세이프하지 않은 싱글톤 클래스 내부의 함수를 사용해도 안전한가요? 호출하는 곳은 메인쓰레드에서만 돌아갑니다. 저는 메인쓰레드에서 각각 10초씩 걸리는 작업을 동기방식으로 5개 돌려서 50초 걸리는 작업을 가지고 성능향상이 있을것이라고 보고 이 5개를 각각 async로 나누어서 돌려보았지만 똑같이 50초가 걸렸습니다. 같은 메인쓰레드에서 돌았기 때문일거라고 추측 하고 있습니다. 추측에 대한 의견과 이런 상황에서 성능 향상을 하려면 어떻게 해야하는지 조언을 듣고 싶습니다