안녕하세요. 선생님 MMROPG 초당 패킷에 관하여 궁금한 점이 있습니다. 지금 현재 Unity 클라이언트를 사용하여, 플레이어 움직임을 동기화 작업을 하고 있습니다. 위메x드의 신규 게임인 나이트크로우의 플레이어 움직임을 모방하여 구현하고 있습니다. (나이트크로우는 카메라가 바라보는 방향으로 플레이어의 방향이 결정됩니다.) 위, 위 오른쪽, 오른쪽, 아래 오른쪽, 아래, 아래 왼쪽, 왼쪽, 위 왼쪽, 이렇게 8방향으로 움직입니다. 1초에 패킷이 얼마나 날아가는지 로그를 찍어 보았더니 일반 MMORPG 게임을 하는 것처럼 움직이면 초당 평균 6개 정도 날아갑니다. 하지만 인위적으로 키보드와 카메라를 엄청 움직이면 초당 최대 40번 까지 패킷이 날아갑니다. 제가 실무 경험은 없어도 MMORPG가 움직임만으로 초당 40번의 패킷을 쏘는건 문제가 있어 보입니다. 그래서 나이트크로우에서는 어떻게 처리를 했나 확인하려고 들어가서 인위적으로 마구마구 움직여보니깐 제 유니티 화면이랑 똑같이 마구마구 움직입니다. (다른 플레이어가 봤을 때 어떻게 움직이는지는 잘 모르겠습니다.) 이러한 상황이고 패킷을 줄이는 해결방안을 모색중입니다. Dead Reckoning 방법을 써서 어느정도 패킷을 줄였는데도 짧은 거리를 마구잡이로 방향을 바꿔버리면서 이동하면 패킷이 그대로 날라가 버리게 되어서 문제입니다... <질문 요약> MMORPG 1초당 패킷 적정 수 현업에서는 플레이어가 인위적으로 (앞으로 갔다 뗐다 빠르게 반복) 움직이는 걸 어떻게 처리하는지 궁금합니다.
벌써 루키스님 강의를 보기 시작한지 2년이 넘었습니다. 항상 좋은 강의 감사드립니다 특히 요즘 다이렉트 강의보면서 혼자 공부할 때는 많이 힘들었는데 엔진 구조 개발 부분에서 많이 얻어가고 있습니다. 이 강의를 공개하신 시점도 벌써 2년이 지나게 되었는데 혹시 언리얼과 게임서버 연동 하실 때 이 서버를 그대로 사용 하실 것 인지 아니면 다른 방식으로 업그레이드 후 서버 개발 후 연동하실지가 궁금합니다.
첨부파일을 다운받아 그대로 빌드를 실행했는데, 다음의 부분에서 에러가 생깁니다. #if 3017001 < PROTOBUF_MIN_PROTOC_VERSION #error This file was generated by an older version of protoc which is #error incompatible with your Protocol Buffer headers. Please #error regenerate this file with a newer version of protoc. #endif 이상한 점이 protoc 3.17.1 버전으로 생성한 Enum.pb.h인데 PROTOBUF_MIN_PROTOC_VERSION의 값이 3021000으로 뜹니다. 혹시 Visual studio 2022를 사용해서 ide때문에 에러가 생기는 것일까요?
while 루프 내부에서 expected = false 를 굳이 왜 해야하는 질문이 들었는데요. 의사코드를 보면 else 구문에서 expected = locked에 때문에 한다고 말씀해주셨는데 결국 if문에서 _locked=true일 때 조건을 만족하는 경우가 생겨서 그런건가요? false로 조건을 통과해야되는 데 말이죠. (잠겼는데 통과하는 경우가 발생) 그렇다면 의사코드에서 else 구문에서 expected = _locked을 왜하는지 의문이 드는데, "그냥 칩회사에서 그렇게 구현했다." 라고 받아들이면 되는건가요?
이제 개발을 할때 WSASend함수를 통해 Scatter-Gether 방식으로 전달을 한다고 하면 내부적으로는 해당 데이터들이 하나의 Sendbuffe에 담겨서 가는지가 궁금합니다. WSASend에 요청한 데이터가 너무 커서 2번이상의 걸처 나눠서 전송이 된다면, 모든 데이터가 전송이 완료되어야지만 IOCP GQCS에서 완료되었다는 사실을 알 수 있는지 궁금합니다. 정리) 1. Scatter-Gether를 통해서 여러 데이터를 보내면 내부적으로 1개의 Send Buffer에 담아서 보내지는지 2. WSASend 호출 -> 데이터가 너무 커서 2개로 짤림 이때 1개의 데이터를 전송 완료 후 GQCS OR 2개의 데이터를 전부 전송 완료 후 GQCS에 등록 이 되는지 궁금합니다.
해당함수를 사용하면 기존에 생성했던 Socket을 다시 만들필요 없이 재사용할 수 있다라고 설명하셨는데, 해당 코드를 보면 Disconnect가 될 시 Service에서 Session을제거하기 때문에 해당 Session은 메모리에서 해제가 되는것으로 알고 있습니다. 그럼 다시 CreateSocket함수를 통해서 socket이 생성되면 사실 의미가 없는게 아닌가 궁금합니다. 그냥 SocketPool을 만들어서 제가될떄 넣고, 생성될때 꺼내쓰는게 효율적일 것 같은데 혹시 어떻게 생각하시는지 궁금합니다.
안녕하세요 오브젝트 풀을 사용하는 이유가 객체의 동적할당/해제의 회수를 줄이기 위함이라 생각하는데요 return std::shared_ptr<T> ptr {pop(), push}; 을 사용하게되면 shared_ptr 내부에서 객체를 관리하기 위한 공간이 동적으로 할당되게된다고 생각되는데요. (refcount 같은것들) 그렇게되면 동적할당된 객체는 재사용이되지만 동적할당 비용을 줄이기 위함은 딱히 의미가 없어진다고 생각되는데요 그럼에도 쓰는게 의미가있는가요? 오히려 make_shared를 쓰는게 더 나은건 아닌지 궁금합니다.
https://stackoverflow.com/questions/4470645/acceptex-without-wsaioctl 다른 질문에서 보내주신 링크로 들어가서 확인 했습니다. 추가적으로 제가 이해한 부분이 맞는지 확인해주시면 감사하겠습니다. 기본적으로 AcceptEX, ConnectEX 함수등은 아키텍처가 외부에 있기에 많은 비용이 발생하여 맵핑하여 사용한다고 생각이 듭니다 이것을 제외하고 추가적인 부분이 있을까요?
정말 기초적인건데 갑자기 이해가 안돼서 질문 남깁니다. AccountManager.h에서는 pch.h가 include되어 있는지 모르고 AccountManager.cpp에만 include되어 있는데 어떻게 AccountManager에서는 map에 std::를 안붙이고 쓰고, int32 타입을 쓸 수 있는걸까요?
데드락 관련해서 버그가 떠서 질문글을 남깁니다. 윈도우 어플리케이션을 생성해서 윈도우 메인에 서버에게 C_LOGIN을 보내는 패킷을 생성했습니다. 밑은 해당 코드입니다. bool flag = true; while (true) { if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { if (WM_QUIT == msg.message) break; if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { CEngine::GetInst()->progress(); CEditorObjMgr::GetInst()->progress(); ImGuiMgr::GetInst()->progress(); // 렌더 종료 CDevice::GetInst()->Present(); if (service->GetCurrentSessionCount() > 0 && KEY_RELEASE(KEY::A) && flag == true) { Protocol::C_LOGIN pkt; auto sendBuffer = ServerPacketHandler::MakeSendBuffer(pkt); sendBuffer = nullptr; flag = false; } } } return (int) msg.wParam; } A를 클릭해서 서버와 연결한 후에 X를 눌러서 윈도우 창을 닫았는데, 이후 이렇게 코어글로벌이 종료될 때, GSendBufferManager를 delete하는 부분에서 타고타고 들어가서 DeadLockProfiler가 DeadLock이 존재했다고, 알려줬습니다. Server에 OnConnected가 되었을 때, 패킷을 보내면 이런 문제가 일어나지 않았는데, 원하는 때, 원하는 키를 입력해서 패킷을 보내려고 하니까 문제가 일어나네요 ㅜㅜ 문제를 알 수 있을까요? 프로토 버퍼를 사용해서 주고 받고 있습니다!
안녕하세요 루키스님. 제가 비쥬얼 스튜디오에서 윈도우 어플리케이션을 만들어서 DX11포폴을 만들고 서버를 연동하려고 시도중입니다. 이 상황에서 wWinMain에 ClientServiceRef service = MakeShared<ClientService>( NetAddress(L"127.0.0.1", 7777), MakeShared<IocpCore>(), MakeShared<ServerSession>, // TODO : SessionManager 등 1); ASSERT_CRASH(service->Start()); GThreadManager->SetFlags(1); for (int32 i = 0; i < 2; i++) { GThreadManager->Launch([=]() { while (true) { service->GetIocpCore()->Dispatch(10); } }); } //밑은 DX11을 작동시켜주기 위한 함수들입니다. while (true) { if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { if (WM_QUIT == msg.message) break; if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { CEngine::GetInst()->progress(); CEditorObjMgr::GetInst()->progress(); ImGuiMgr::GetInst()->progress(); // 렌더 종료 CDevice::GetInst()->Present(); } } 이렇게 쓰레드를 생성해서 작동을 시켜주었습니다. 그런데 생성된 게임 윈도우에서 X키를 눌러서 프로그램을 강제로 종료시켰는데, 이 메인 쓰레드 자체는 정상적으로 WM_DESTROY를 호출 받아서 종료되었는데, 멀티 쓰레드들이 멈추지 않고 계속 돌아 프로그램이 종료되지 않고, 좀비처럼 살아남는 현상이 계속되고 있습니다. 쓰레드를 강제로 종료시키니 메모리 릭이 남고, 어떻게 문제를 해결해야할지 해결책을 찾지 못해서 이렇게 질문 남깁니다. 참고) 루키스님의 패킷 직렬화#3 코드를 참조해서 만들고 있습니다! 감사합니다.
1) 제가 제대로 비동기 방식을 이해한게 맞는지 궁금합니다. 비동기 방식이 어떤 함수를 호출한다면 함수를 지금 바로 처리하지 않고 예약같은 개념으로 요청한 함수를 나중에 처리하고 그동안 요청한 함수외에 다른 부분을 실행하는걸로 이해를 했는데 맞는건지 궁금합니다. 2) 만약에 제가 이해한 방식대로 비동기가 진행이 된다면 수업에서 진행한 ::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE); 코드에서 블로킹 방식처럼 진행하는데 이 부분은 비동기와는 맞지 않는 부분아닌가요??
Job Queue #2의 영상을 보고 질문드립니다. 이 영상을 보고 저번에 메모리 관련해서도 보고 느낀 것인데, 템플릿 흑마법을 따로 공부할 수 있는 책이나 가이드가 있을까요? 루키스님의 영상을 볼 때는 템플릿 흑마법이 이해가 가긴 하는데, 이걸 스스로 짤 생각을 하니 도저히 엄두가 안나서 공부가 필요할 것 같아 이렇게 질문 글을 남깁니다.
서버랑 언리얼 연동 후 단순한 데이터 교환은 되는데, 패킷을 받아 스폰 기능을 사용하려고 하면 IsInGameThread() 라는 에러가 뜹니다. 대충 느낌을 보아하니 뭔가 외부 쓰레드? 그런 걸 차단하는 느낌이 드는데... Dispatch 해주는 워커쓰레드한테 FRunable 해줘도 해결 되지 않아서 질문 드립니다.. 혹시 다른 사람은 어떤가 싶어, 코드를 보니 서버는 IOCP를 사용하되, 언리얼에서는 그냥 recv만 사용하더군요.