전체 글 315

경우의 수, 순열(Permutation)

순열이란 어떤 집합의 원소들을 특정한 순서로 배열하는 것. // 순열로 접근하는 문제순서를 재배치하여~~순서의 경우에는 max값을 #include using namespace std;/* next_permutation(from, to): 오름차순으로 정렬된 순열 뽑기 반드시 오름차순으로 정렬되어있어야 한다. from은 시작할 변수를 말한다. vector: vector.begin() / array: 0 end는 마지막 변수를 포함하지 않는다. -> end는 마지막보다 1 크게 vector: vector.end() / array: array.size() 공식 nPr -> n개중에 r개를 뽑는 경우의 수 n! / (n-r)!*/int main(){ ..

재귀함수(Recursion Function)

재귀함수는 매개변수를 변경해가며 자기 자신을 호출하는 함수를 말한다.보통은 매개변수를 줄이는 쪽이다. { 종료조건 로직 매개변수 변경 후, 재귀호출} 종료조건을 먼저 작성하자.함수간 사이클이 존재해서는 안된다.대부분의 경우, 반복문이 재귀함수보다 빠르다. #include using namespace std;int factorialFor(int n){ /* f(1) = 1 f(2) = 1 * 2 = 2 f(3) = 1 * 2 * 3 = 6 f(4) = 1 * 2 * 3 * 4 = 24 */ int res = 1; for(int i=1; i f(1) * 2 f(3) = 1 * 2 * 3 = 6 -> f(2) ..

더미클라이언트 테스트, 패킷 지연처리

에러라고 하기엔 뭐하지만, 프로젝트 작업을 일단 마치고 테스트에 들어갔다.구성한 서버의 흐름은 다음과 같다. - 클라이언트의 패킷 수신- 데이터 처리 후 바로 송신 더미클라이언트의 테스트 조건은 다음과 같다.- 입력된 숫자만큼 더미 세션 생성- 3초에 한 번씩 서버에 패킷 전송- 서버의 패킷 수신 후, 3초 뒤 다시 재전송 반복.- 송수신 시간차를 ms로 지연시간 표기. 플레이어가 100명이 넘어가는 순간부터 캐릭터들의 이동 동기화가 느려지기 시작했다.1,000명의 플레이어가 패킷을 보내니 공격과 피격, 채팅 그리고 이동 동기화까지 엉망이 되어버렸다.지연속도 테스트 환경서버에 연결된 클라이언트는 각각 3초마다 Ping 메세지를 보낸다. -> 송신 시간 측정데이터를 수신한 서버는 곧바로 클라이언트에 답변한..

Study/에러 정리 2024.09.11

STL std::map에서 키 값 체크

std::map을 사용하던 도중 아래 에러를 맞이했다.std::_Tree_unchecked_iterator 원인-> std::map에서 존재하지 않는 키에 접근하면, 새로운 엔트리가 자동으로 추가된다.map _players;lock_guard lock(_lock);if (_players[targetPlayerId] != nullptr) // null체크를 함과 동시에 데이터 삽입{ _players[targetPlayerId]->GetOwnerSession()->Send(sendBuffer);} STL std::map을 사용할 때에는 키가 있는지 확인하는 find 메소드를 먼저 사용하자.auto it = _players.find(targetPlayerId);if (it != _players.end() &&..

Study/에러 정리 2024.09.10

공유포인터 생명주기 확인하기

클라이언트 세션에 플레이어의 정보를 담고있고,플레이어의 정보에 해당 세션의 정보를 담도록 설계했다.공유포인터끼리 서로를 참조하여 생명주기를 끊기지 않도록 설계했다. 플레이어가 접속 종료 후, 세션의 소멸자가 실행되지 않아 메모리가 줄어들지 않았다.그래서 공유포인터의 생명주기를 확인하게 되었다.서로 참조된 데이터가 없어 공유포인터가 소멸될 때.session = 0xcccccccccccccccc {_player=??? _accountId=??? }서로 참조된 데이터가 있어 소멸되지 않을 때.session = shared_ptr {_player=shared_ptr {_playerId=6 _accountId=13 _playerName="a" ...} [1 strong ref] [make_shared] _acco..

Study/에러 정리 2024.09.10

람다식 캡쳐와 const, 공유포인터 업데이트 문제

작업중인 스레드 내부에서 다른 스레드에게 비동기 작업을 요청하는 과정에서 문제가 발생했다. 정확히는 공유포인터를 매개변수로 람다식 내부에서 사용할 때 문제가 발생했다.처음에는 참조캡쳐(&)로 메모리를 아껴야겠다는 생각을 했다. 주소값을 건네 받고 스레드가 작업을 시작하면 미리 선언된 공유포인터의 메모리는 해제되고 공유포인터의 주소값은 null값을 가르키게 되었다.// Room.hvoid UpdateRoomItem(shared_ptr& roomItem);// Room.cppvoid Room::UpdateRoomItem(shared_ptr& roomItem){ 업데이트 로직} shared_ptr roomItem = ItemController::GetRoomItemByRoomItemId(recvItem.roo..

Study/에러 정리 2024.09.08

멀티스레드 환경 서버의 패킷 지연처리

아래는 메인함수의 스레드풀을 작동시키는 부분이다.할당된 워커 스레드들은 반복문을 돌며 입출력 큐에 등록된 작업들을 비동기로 진행한다.각 스레드마다 락이 잠겨있어 경합을 억제한다.// Worker Threadsmutex m;vector workers;for (int32 i = 0; i guard(m); service->GetIocpCore()->Dispatch(); } }));}cout this_thread::sleep_for 함수를 사용한 문제문제의 함수{ ... // 1차로 클라이언트에 전송 GRoom.Broadcast(MakeSendBuffer(pkt, packetId)); // 2차 로직 ... this_thread::sleep_for(3s); // 3초..

Study/에러 정리 2024.09.07

클라이언트 변조와 서버 의존성

멀티 대전게임에서 다음 작업에 앞서 고민할 일이 생겼다.로직은 다음과 같다. 1. 서버에서 힐팩 생성을 클라이언트에 전송한다.2. 클라이언트에서 힐팩을 생성한다.3. 유저가 힐팩을 먹고 서버에 전송한다.4. 5초 후 힐팩을 재생성한다. 힐팩 재생성 과정에서는 두 가지 선택지가 있다.1. 서버에서 5초후 생성하라고 즉시 클라이언트에 전송한다.2. 서버에서 즉시 생성하라고 5초 후 클라이언트에 전송한다. 1번은 클라이언트 의존성이 더 높아 이후 클라이언트 변조, 해킹에 대한 우려가 있다.2번은 서버의 리소스가 더 증가하지만 비교적 안전한 게임을 만들 수 있다. 2년간 웹서버 개발자로 일하면서 클라이언트는 절대 신뢰하지 말라는 CTO님의 가르침이 있었다.클라이언트는 서버에서 받은 결과만 뿌려주는 역할이라고 하..

Study/끄적끄적 2024.09.06

Protobuf send 전 size 체크

게임룸에 입장한 유저들에게 브로드캐스팅한다.만약 players의 size가 0이면 전송을 하지 않을줄 알았다.// Send all players in room to new playermap* players = GRoom.GetPlayersInRoom();for (const auto& pair : *players){ const PlayerRef _player = pair.second; Protocol::Player* repeatedPlayer = pkt.add_players(); repeatedPlayer->set_id(_player->GetPlayerId()); repeatedPlayer->set_accountid(_player->GetAccountId()); repeatedP..

Study/끄적끄적 2024.09.05

첫 번째 데드락 발생

1. lock_guard가 끝나지 않은 상태에서 함수를 호출한다.2. 호출된 함수 내부에서 동일한 뮤텍스로 락을 실행한다.3. 영원히 대기 뮤텍스를 잠그고 스코프가 끝나지 않은 상태에서 RegisterSend 함수를 호출한다.void Session::ProcessSend(int32 numOfBytes){ ... lock_guard lock(_lock); if (_sendQueue.empty()) _sendRegistered.store(false); else RegisterSend();} _lock 뮤텍스가 잠겨있는 상태에서 동일한 뮤텍스 _lock을 잠그려고 한다면 데드락 상태에 빠질 수 있다.이미 _lock이 잠겨있는 상태에서 _lock을 사용하여 잠그려고하니 영원히 대기상태에 빠진다.void Se..

Study/에러 정리 2024.09.04