전체 글 31

뱀서라이크 던전 돌파 게임 만들기 (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이 따로 생긴..

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

게임 서버, C++로 처음부터 만들어보기 — TCP는 메시지 단위로 오지 않는다지금까지 만든 echo 서버엔 사실 치명적인 함정이 숨어있었다. TCP는 "바이트 스트림"이지 "메시지 스트림"이 아니다.직접 확인해봤다클라이언트에서 3000자짜리 문자열을 WriteLine 한 번으로 보냈는데, 서버 로그는 이랬다:Received 1024 bytesReceived 1024 bytesReceived 954 bytes한 번에 보낸 게 세 조각으로 쪼개져서 도착했다. 1024씩 잘린 건 내 수신 버퍼가 char buffer_[1024]였기 때문이다.반대로 여러 번 보낸 게 한 덩어리로 합쳐져 오기도 한다. 보낸 쪽이 "한 번에 썼다"는 사실은 받는 쪽에 전혀 전달되지 않는다.지금은 echo라 상관없지만, {"typ..

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

게임 서버, C++로 처음부터 만들어보기 — 한 명 처리하다 막혀있던 문제지난 편까지는 한 명씩 순서대로 접속 받고 echo 해주는 서버였다. 근데 이거, 사실 치명적인 문제가 있다 — 한 명이 접속해서 아무것도 안 보내고 가만히 있으면, 다른 사람은 접속 자체가 안 되는 것처럼 멈춰버린다.직접 재현해봤다PowerShell 창 두 개 열어서:창 1: 접속만 하고 아무것도 안 보냄창 2: 접속하고 메시지 보냄 → 응답 대기결과: 창 2가 응답을 영원히 못 받고 멈춰있었다. 서버가 창 1을 처리하는 루프에 갇혀서, 창 2를 위해 다시 "새 접속 받아라"를 호출할 기회 자체가 없었던 거다.비동기로 전환accept(), read_some()처럼 "끝날 때까지 기다리는" 함수들을, async_accept, asy..