전체 글 33

뱀서라이크 던전 돌파 게임 만들기 (14)

게임 서버, C++로 처음부터 만들어보기 — 똑같이 만들면 TCP를 쓰면 되니까지난 편에서 UDP 서버의 연결 계층을 만들었다. 헤더에 ack와 ack_bits라는 필드를 넣어두고 값은 0으로 비워뒀다.이번엔 그걸 채운다. TCP가 공짜로 해주던 걸 손으로 만드는 작업이다.그런데 TCP와 똑같이 만들면 안 된다. 똑같이 만들 거면 TCP를 쓰면 되니까.안 보내도 되는 것들TCP는 모든 바이트를 책임진다. 유실되면 재전송하고, 순서가 틀리면 맞출 때까지 기다린다.게임에서는 그게 손해다.초당 30번 플레이어 위치를 보낸다고 하자. 0.5초 전 위치 패킷이 유실됐다. TCP는 그걸 기어코 다시 보내고, 도착할 때까지 그 뒤 패킷을 전부 막는다. 최신 위치가 이미 도착해서 버퍼에 쌓여 있는데도 꺼내 쓸 수가 없..

뱀서라이크 던전 돌파 게임 만들기 (13)

게임 서버, C++로 처음부터 만들어보기 — TCP가 해주던 걸 손으로지금까지 만든 건 로비 서버다. 로그인, 방, 채팅, 매칭, 경매, 친구. TCP로 잘 돌아간다.전투는 거기서 못 한다. TCP가 너무 잘해줘서 못 한다.보장이 독이 되는 순간TCP는 도착과 순서를 보장한다. 패킷이 유실되면 재전송하고, 그동안 그 뒤에 온 데이터를 붙잡아둔다. head-of-line blocking이라고 부른다.초당 30번 위치를 보내는 상황을 생각해보자.t=0.00 위치 A → 유실t=0.03 위치 B → 도착t=0.06 위치 C → 도착TCP는 A를 재전송할 때까지 B와 C를 애플리케이션에 안 준다. 0.03초 전 위치를 기다리느라 지금 위치를 못 받는 거다.그런데 게임에서 A는 버려도 되는 데이터다. ..

뱀서라이크 던전 돌파 게임 만들기 (12)

게임 서버, C++로 처음부터 만들어보기 — 서버는 창을 닫으면 안 된다12편 만에 서버를 밖으로 내보낸다. 라즈베리파이에 올려서 24시간 돌린다.그런데 배포를 준비하다 보니, 지금까지 미뤄둔 것들이 한꺼번에 청구되기 시작했다.문제개발 중엔배포하면io.run()이 반환 안 함창을 닫으면 그만docker stop이 10초 기다렸다 강제 종료로그가 버퍼에 갇힘불편한 정도크래시하면 직전 로그가 통째로 사라짐포트가 코드에 박힘재컴파일하면 됨이미지를 다시 만들어야 함죽으면 끝다시 띄우면 됨새벽에 죽으면 아침까지 멈춰 있음전부 "나중에"라고 넘겼던 것들이다. 나중이 왔다.왜 도커인가원래 계획은 systemd 유닛 파일이었다. Restart=always를 걸고 apt로 의존성을 깔면 된다.도커로 바꾼 이유는 의존성이..

뱀서라이크 던전 돌파 게임 만들기 (11)

게임 서버, C++로 처음부터 만들어보기 — 지금 접속 안 한 사람지금까지 이 서버가 "사람들"을 담아둔 곳은 셋이었다.std::vector members; // 같은 방 사람들std::vector match_queue_; // 매칭 대기 중std::unordered_map> sessions_; // 접속 중인 연결전부 "지금 이 순간"의 관계다. 방에서 나가면 남이고, 매칭되면 큐에서 빠지고, 연결이 끊기면 사라진다.친구는 다르다. 세 가지가 전부 새롭다.접속하지 않은 사람과도 유지된다상대가 수락해야 성립한다 — 한쪽만 신청한 중간 상태가 존재한다양방향이다 — 내 목록에 쟤가 있으면 쟤 목록에도 내가 있어야 한다세 번째가 특히 함정이다.각자 목록을 들고 있으면제일 먼저 떠오르는 방식은 이거다..

뱀서라이크 던전 돌파 게임 만들기 (10)

게임 서버, C++로 처음부터 만들어보기 — 아무도 부르지 않는 코드Step 1부터 9까지, 이 서버의 모든 코드는 같은 모양이었다.메시지가 온다 → 처리한다 → 응답한다연결이 끊긴다 → 정리한다전부 반응이다. 누가 뭔가를 하지 않으면 서버는 아무것도 하지 않는다.경매는 다르다. 마감 시각이 있고, 그때가 되면 아무도 아무것도 안 보내도 스스로 낙찰돼야 한다.서버에 시간이라는 개념을 들일 차례다.1초마다 깨어나는 것void start_tick_timer() { tick_timer_.expires_after(std::chrono::seconds(1)); tick_timer_.async_wait([this](boost::system::error_code ec) { if (ec) ret..

뱀서라이크 던전 돌파 게임 만들기 (9)

게임 서버, C++로 처음부터 만들어보기 — 줄 곳이 없다보스를 잡으면 보상을 줘야 한다. 그런데 줄 곳이 없었다.지금 서버가 플레이어에 대해 아는 건 이게 전부다.class Session {private: uint64_t player_id_; std::string username_; bool logged_in_ = false;};전부 Session 안에 있다. 연결이 끊기면 Session이 죽고 같이 사라진다. 재화가 그러면 안 된다.Step 5에서 이렇게 적어뒀었다.영속성 없음 — 서버 재시작하면 전부 사라진다. player_id_도 "계정 번호"가 아니라 사실상 "접속 순번"에 가깝다.그때는 "나중에 보완"이라고 미뤘는데, 보상이 생기면서 청구서가 돌아왔다.연결보다 오래 사는 것Pl..

뱀서라이크 던전 돌파 게임 만들기 (8)

게임 서버, C++로 처음부터 만들어보기 — 남을 방에 집어넣기매칭을 붙일 차례다. 두 명이 대기열에 모이면 방을 만들어서 같이 넣어주고, 보스를 띄운다.기능만 보면 단순하다. 대기열은 vector 하나면 되고, 두 명이 차면 방을 만들면 끝이다.그런데 코드를 쓰다 막혔다. Server가 alice와 bob을 방에 넣으려면 두 사람의 방 소속을 바꿔야 하는데, 그 값이 Session 안에 있다.class Session {private: uint32_t room_id_ = 0; // private. Server가 이걸 어떻게 건드리나};지금까지는 왜 문제가 없었나room_id_가 Session 안에 있는 건 이유가 있었다. Step 6에서 방을 만들 때, 소켓이나 유저네임과 마찬가지로 "그 연결만..

뱀서라이크 던전 돌파 게임 만들기 (7)

게임 서버, C++로 처음부터 만들어보기 — 번호는 아는데 보낼 수가 없다방에 여러 명이 모였다. 이제 채팅을 붙일 차례다.서버는 방에 누가 있는지 알고 있다. members: [1, 2]. 그런데 1번에게 메시지를 보내려고 하니 할 수 있는 게 없었다. 번호는 번호일 뿐이고, 그 번호가 어느 연결인지 이어주는 게 아무것도 없다.Step 6에서 방 멤버를 player_id만 저장하기로 한 결정이 여기서 청구서로 돌아왔다.번호와 연결을 잇는 표그래서 Server에 표를 하나 더 뒀다.std::unordered_map> sessions_;이게 있으면 브로드캐스트는 그냥 반복문이다.void broadcast_to_room(uint32_t room_id, const nlohmann::json& message) ..

뱀서라이크 던전 돌파 게임 만들기 (6)

게임 서버, C++로 처음부터 만들어보기 — 상태를 어디에 둘 것인가지금까지 모든 상태는 Session 안에 있었다. 소켓, 유저네임, 로그인 여부. 전부 "그 연결만의 것"이라 문제가 없었다.방은 다르다. 방 목록은 모두가 공유하는 상태다. alice가 만든 방을 bob이 볼 수 있어야 한다. alice의 Session에 방 목록을 넣으면 bob은 그걸 볼 방법이 없다.드디어 중앙 관리자그래서 모든 Session 바깥에서 공유 상태를 들고 있을 Server 클래스를 만들었다.여태 안 만들었던 이유는 필요가 없었기 때문이다. 필요해지기 전에 구조부터 잡지 않는다는 원칙을 지키다 보니 딱 지금이 그 시점이 됐다.기능보다 리팩터링 먼저방 기능을 바로 붙이지 않고, 먼저 main()에 있던 accept 루프를..

뱀서라이크 던전 돌파 게임 만들기 (5)

게임 서버, C++로 처음부터 만들어보기 — 서버는 아직 "누구"인지 모른다지난 편에서 메시지를 구조화해서 주고받을 수 있게 됐다. 그런데 서버는 여전히 중요한 걸 모르고 있었다 — 누가 보낸 메시지인지.채팅이든 방 생성이든 경매든, 전부 "누가"가 전제되는 기능이다. 그래서 이번엔 로그인을 붙였다.Session에 상태가 생겼다지금까지 Session은 순수하게 네트워크만 다뤘다. 소켓, 버퍼, 송신 큐. 여기에 게임 상태가 들어간다.uint64_t player_id_;std::string username_;bool logged_in_ = false;왜 하필 Session인가Session은 연결마다 하나씩 만들어지는 객체다. A가 접속하면 A의 Session, B가 접속하면 B의 Session이 따로 생긴..