[말랑 퀴즈] 26/08/19 해답

게시:     수정

카테고리:

태그:

제 말랑말랑 퀴즈 생성기는 이곳에서 확인하실 수 있습니다.

말랑말랑 퀴즈 — 해답지 ✅

날짜: 2026-08-19 문제 수: 5문제 총점: 29.5/50 (59점) 🟡 복습이 필요해요

📊 채점 결과

문항 유형 난이도 결과 점수
Q1 객관식 🟡 보통 ⭕ 정답 10 / 10
Q2 서술형 🟡 보통 🔺 부분 정답 3.5 / 10
Q3 OX 🔴 어려움 🔺 부분 정답 9 / 10
Q4 빈칸 채우기 🟢 쉬움 ⬜ 미응답 0 / 10
Q5 서술형 🔴 어려움 🔺 부분 정답 7 / 10
합계       29.5 / 50

총평

TCP/UDP의 스트림 vs 데이터그램 차이(Q1), 스레드 모델의 선택 기준(Q3), 변수 정의 비용과 루프 안팎의 호출 횟수(Q5)처럼 원리를 따지는 문제에서는 정확한 감각을 보여줬습니다. 특히 Q5의 생성자/소멸자/대입 횟수는 표를 그대로 옮긴 듯 정확했습니다.

반면 “왜 그렇게 해야 하는가”의 근거를 한 가지로 단정하는 습관이 점수를 깎았습니다. Q2에서 DB 필드 처리 방식을 곧바로 SQL 인젝션 문제로 연결했는데, 이 절의 요지는 보안이 아니라 타입 불일치와 NULL 검증입니다. Q3도 결론은 맞았지만 두 조건 중 하나만 근거로 들었습니다.

Q4는 프라우드넷 기본 모듈 이름을 묻는 가장 쉬운 문제였는데 미응답이었습니다. NetServer / NetClient / P2P는 이후 챕터 전체의 전제가 되는 이름이니 먼저 외워 두는 편이 좋겠습니다.


Q1. 🟡 보통 — ⭕ 10/10

문제: 아래는 어떤 프로토콜로 데이터를 주고받는 두 프로그램의 의사 코드이다.

// 송신자
main() {
    s = socket(???);
    s.bind(any_port);
    s.connect("55.66.77.88:5959");
    s.send("hello");
    s.close();
}

// 수신자
main() {
    s = socket(???);
    s.listen(5959);
    s2 = s.accept();
    while(true) {
        r = s2.recv();
        if(r.length == 0) break;
        print(r);
    }
    s2.close();
}

송신자가 send("a"), send("bb"), send("ccc"), send("dddd")를 연속으로 호출했을 때, 위 코드의 프로토콜에 대한 설명으로 가장 올바른 것은?

  • A. 수신자는 반드시 "a", "bb", "ccc", "dddd" 네 개의 메시지를 경계가 유지된 채로 그대로 받는다.
  • B. 수신자는 "abb", "cccdddd"처럼 데이터가 뭉치거나 쪼개진 형태로 받을 수 있으며, recv()가 0바이트를 반환하면 연결 종료를 의미한다.
  • C. 이 프로토콜은 1:N 통신을 지원하므로 하나의 소켓으로 여러 수신자에게 동시에 브로드캐스트할 수 있다.
  • D. 데이터 유실이 발생해도 재전송하지 않으므로, 애플리케이션이 직접 데이터 검사와 재전송을 구현해야 한다.

📝 내 답: B

정답: B

해설:

코드에 connect(), listen(), accept(), send(), recv()가 등장하는 것으로 보아 TCP임을 알 수 있다. (UDP라면 sendTo() / recvfrom()을 쓰고, connect·listen·accept 과정이 없다.)

  • B (정답): TCP는 스트림 방식이라 데이터 경계가 유지되지 않는다. 보낸 데이터가 뭉치거나("abb") 쪼개져서("cccdddd") 도착할 수 있다. 또 0바이트 수신은 데이터가 없다는 뜻이 아니라 연결 종료를 의미하므로, 예시 코드도 r.length == 0일 때 루프를 빠져나간다.
  • A (오답): 메시지 경계가 유지되는 것은 UDP(데이터그램) 의 특성이다.
  • C (오답): TCP는 1:1 통신만 가능하다. 1:N이 가능한 쪽은 UDP다.
  • D (오답): TCP는 세그먼트마다 ack(확인 응답) 를 받으며, ack를 받지 못하면 받을 때까지 재전송해 신뢰성을 보장한다. 애플리케이션이 직접 검사·재전송을 해야 하는 쪽은 UDP다.

🔍 피드백: 정확합니다. 오답 보기들이 모두 UDP의 특성(경계 유지, 1:N, 재전송 없음)을 TCP에 갖다 붙인 것이었는데 잘 걸러냈습니다. 함수 이름(connect/listen/accept)만 보고 프로토콜을 판별하는 감각도 좋습니다.

출처: server/game_server/2/2025-12-04-game_server_2_6.md


Q2. 🟡 보통 — 🔺 3.5/10

문제: 게임 서버에서 DB 질의 구문을 실행할 때, (가)는 GetField의 결과를 바로 int/string에 대입하고, (나)는 DbVariable로 받아 isNull() 검사 후 Convert로 변환한다. (나) 방식이 권장되는 이유와, INSERT 시 값을 하드 코딩하는 대신 써야 할 방식을 서술하시오.

// (가)
DbRecordSet rs = cmd.Execute("select (a,b,c) from table1 where a>=?");
foreach(DbRecord r in rs) {
    int a = r.GetField("a");
    int b = r.GetField("b");
    string c = r.GetField("c");
}

// (나)
DbRecordSet rs = cmd.Execute("select (a,b,c) from table1 where a>=?");
foreach(DbRecord r in rs) {
    DbVariable vA = r.GetField("a");
    if(vA.isNull() == false) int a = vA.Convert(int);
    // b, c도 동일한 방식으로 처리
}

📝 내 답:

SQL 인젝션 공격을 받을 수 있기 때문이다.

(나) 방식과 비슷하게 변수를 받아서 검증하고 명령 구문을 실행하도록 한다.

정답:

(1) (나) 방식이 권장되는 이유

  • 프로그래밍 언어의 타입과 데이터베이스의 타입은 완전히 일치하지 않는다. (가)처럼 r.GetField("a")를 곧바로 intstring 변수에 대입하면 타입이 암묵적으로 맞춰지길 기대하는 셈이라 안전하지 않다.
  • 특히 DB 필드는 NULL일 수 있는데, (가)에는 NULL을 걸러낼 방법이 없다. (나)는 DbVariable로 받아 isNull()검증한 뒤 Convert(타입)로 명시적으로 변환한다.
  • 즉, 귀찮더라도 검증(isNull) → 변환(Convert) 단계를 거치는 것이 원칙이다.

(2) INSERT 시 권장 방식

값을 질의 문자열에 직접 이어 붙이는 하드 코딩 대신, 파라미터(? 바인딩)에 값을 채워 넣는 방식을 사용해야 한다.

// 방법 2 - 파라미터
cmd.Parameters[0] = 123;
cmd.Parameters[1] = 456;
cmd.Parameters[2] = 789;
cmd.Execute("insert into table1 (a,b,c) values (?,?,?)");

// 방법 3 - 파라미터 + 저장 프로시저
cmd.Parameters[0] = 123;
cmd.Parameters[1] = 456;
cmd.Parameters[2] = 789;
cmd.Execute("InsertRecord");

파라미터 방식(방법 2)이나 파라미터 + 저장 프로시저 방식(방법 3)이 권장된다.

참고: 메모리에서 레코드를 먼저 구성해 DB로 넘기는 SetField 방식(DbVariable v = new DbVariable(DbType.integer, 123); record.SetField("a", v);)도 존재하지만, 이는 일반적인 방법은 아니다.

🔍 피드백: 채점은 두 소문항 5점씩입니다.

  • (1) 0 / 5SQL 인젝션은 이 문제의 답이 아닙니다. (가)와 (나)는 둘 다 ? 파라미터로 조회하고 있어 인젝션 위험은 동일합니다. 두 코드의 차이는 읽어온 값을 어떻게 받느냐뿐이고, 그 답은 ① 언어 타입과 DB 타입이 일치하지 않는다는 점, ② 필드가 NULL일 수 있다는 점입니다. “검증 → 변환”이라는 키워드로 기억해 두세요.
  • (2) 3.5 / 5 — “변수를 받아서 실행한다”는 방향은 맞습니다. 다만 파라미터 바인딩(values (?,?,?) + cmd.Parameters[i]) 이라는 이름을 정확히 쓰지 못했고, 한 단계 더 권장되는 저장 프로시저 방식은 언급하지 않았습니다.

두 소문항이 “값을 그대로 믿지 말라”는 하나의 원칙으로 이어진다는 점을 잡으면 함께 외우기 쉽습니다.

출처: server/game_server/7/2026-03-06-game_server_7_10.md


Q3. 🔴 어려움 — 🔺 9/10

문제: 다음 명제가 참(O)인지 거짓(X)인지 판단하라.

게임 서버 내부 데이터 전체가 뮤텍스 1개로만 보호되고, 서버 로직이 DB·파일 액세스 같은 디바이스 대기 시간 없이 CPU 계산만 수행한다면, 워커 스레드를 CPU 코어 개수만큼 늘리는 멀티스레드 방식이 처리량 향상에 가장 효과적이다.

📝 내 답: X — “내부 데이터 전체가 잠기기 때문에 의미가 없어진다”

정답: X

해설:

명제가 제시한 두 조건은 오히려 멀티스레드 방식을 권장하지 않는 대표적인 상황이다.

  1. 뮤텍스 1개로 전체 보호: 스레드를 아무리 많이 만들어도 그 뮤텍스를 잡은 스레드 한 개만 실행될 수 있다. 병렬성 이득은 없고 Context Switching 비용만 늘어난다.
  2. 디바이스 타임이 없고 CPU 계산만 수행: I/O 대기로 놀고 있는 시간이 없으므로, 대기 시간을 다른 스레드로 메우는 멀티스레드의 이점이 사라진다.

이런 경우에는 워커 스레드를 1개로 두고 CPU 개수만큼 서버 프로세스를 띄우는 멀티프로세스 방식이 적합하다. 프라우드넷에서는 스레드가 없는 스레드 풀을 만들어 NetServer에 넘기고, 메인 루프에서 직접 돌리는 식으로 구현한다.

CThreadPool* p = CThreadPool::Create(..., 0);   // 스레드 0개짜리 풀
param.m_externalNetWorkerThreadPool = p;
param.m_externalUserWorkerThreadPool = p;
netServer->Start(param);
while(true) { p->Process(10); }                  // 최대 10ms 대기하며 수동 처리

정리하면, I/O 대기가 많고 데이터 보호가 세분화되어 있으면 멀티스레드, CPU 계산 위주이거나 전역 뮤텍스 하나를 쓰면 멀티프로세스가 선택 기준이다.

🔍 피드백: 판단(7점) 만점, 근거(3점) 중 2점입니다. 뮤텍스 1개 → 한 번에 한 스레드만 실행이라는 핵심을 정확히 짚었습니다. 다만 명제가 조건을 두 개 준 이유가 있습니다. 나머지 하나인 “디바이스 타임이 없다(CPU 계산만 한다)” 도 그 자체로 멀티스레드를 권장하지 않는 독립적인 근거입니다. 멀티스레드의 이점은 결국 I/O로 놀고 있는 시간을 다른 스레드가 메우는 것인데, 그 놀 시간이 없으니 Context Switching 비용만 남습니다.

답 끝에 물음표를 붙였지만 내용은 맞았습니다. “뮤텍스 하나 = 사실상 직렬 실행”이라는 결론에 자신을 가져도 됩니다.

출처: server/game_server/6/2026-03-01-game_server_6_9.md


Q4. 🟢 쉬움 — ⬜ 0/10

문제: 빈칸을 채우시오.

게임 네트워크 엔진 프라우드넷(ProudNet)의 기본 네트워크 모듈은 두 가지다.

  • ___ 클래스: 게임 서버의 메인 모듈로, 클라이언트 연결을 받아들이고 메시지를 주고받으며, 각 클라이언트의 네트워크 상황을 열람할 수 있다.
  • ___ 클래스: 게임 클라이언트의 네트워크 모듈로, 서버로 연결을 맺고 메시지를 주고받으며, 다른 클라이언트와 ③ ___ 통신도 가능하다.

📝 내 답: (미응답)

정답:

① NetServer ② NetClient ③ P2P

해설:

프라우드넷의 기본 네트워크 모듈은 NetServerNetClient 두 개다.

  • NetServer: 게임 서버의 메인 모듈. 클라이언트 연결을 받고 메시지를 주고받으며, 각 클라이언트의 네트워크 상황(핑, 상태 등)을 열람할 수 있다.
  • NetClient: 게임 클라이언트의 네트워크 모듈. 서버와 연결을 맺어 메시지를 주고받는 것은 물론, 다른 클라이언트와 P2P 통신도 할 수 있다는 점이 특징이다.

🔍 피드백: 미응답으로 0점입니다. 꼭 기억할 것 — 프라우드넷 기본 모듈은 딱 두 개, NetServer(서버)와 NetClient(클라이언트)입니다. 이름이 역할을 그대로 말해주므로 따로 외울 것이 거의 없습니다.

구분 포인트 하나만 덧붙이면, NetClient는 서버하고만 말하는 게 아니라 다른 클라이언트와 P2P 통신도 한다는 점입니다. 이 P2P 기능이 프라우드넷을 쓰는 큰 이유 중 하나이고, 이후 6장 내용 대부분이 이 두 클래스 위에서 전개되니 이름만이라도 먼저 붙잡아 두세요.

출처: server/game_server/6/2026-02-18-game_server_6_2.md


Q5. 🔴 어려움 — 🔺 7/10

문제: encryptPassword에서 string encrypted;를 함수 맨 앞에 정의한 코드의 불필요한 비용 두 가지를 지적하고 개선 코드를 작성하시오. 또한 루프 안팎에 변수를 정의하는 A/B 두 방법의 생성자·소멸자·대입 호출 횟수와 B가 유리한 상황을 서술하시오.

string encryptPassword(const string& password) {
    string encrypted;

    if(password.length() < MinimumPasswordLength) {
        throw logic_error("Password is too short");
    }
    // ... encrypted를 채우는 작업 ...
    return encrypted;
}

📝 내 답:

(1) 생성자와 소멸자. encrypted는 잠깐을 위해 생성되고 소멸된다. string& encrypted를 변수의 인자로 넣는 방법이 생각난다.

(2) A: 생성 1, 대입 n, 소멸 1 / B: 생성 n, 대입 0, 소멸 n 반복문 내에서 잠깐 쓰고 버려지는 변수를 위해

정답:

(1) 불필요한 비용과 개선

변수를 정의하면 ① 정의 시점의 생성자 호출 비용과 ② 유효범위를 벗어날 때의 소멸자 호출 비용이 발생하며, 정의만 하고 쓰지 않아도 이 비용은 그대로 든다.

원래 코드의 문제는 두 가지다.

  • 불필요한 생성: encrypted를 길이 검사보다 먼저 정의했기 때문에, 비밀번호가 짧아 logic_error가 던져지면 encrypted한 번도 쓰이지 않은 채 생성·소멸된다.
  • 기본 생성 + 대입: 예외를 지나온 뒤에도 아래처럼 쓰면 기본 생성자 호출 후 대입 연산자 호출이라는 2단계 비용이 든다.
string encrypted;      // 기본 생성자
encrypted = password;  // 대입 연산자

개선 코드는 정의를 최대한 늦추고, 정의와 동시에 초기화한다.

string encryptPassword(const string& password) {
    if(password.length() < MinimumPasswordLength) {
        throw logic_error("Password is too short");
    }

    string encrypted(password); // 정의와 동시에 초기화 (복사 생성자 1번)
    // ...
    return encrypted;
}

이렇게 하면 불필요한 기본 생성과 대입이 사라지고, 의미가 확정된 시점에 변수를 정의하게 되어 코드 문서화 측면에서도 좋아진다.

(2) 루프에서의 호출 횟수와 선택 기준

방법\호출 생성자 소멸자 대입
A 방법 (루프 밖 정의) 1번 1번 n번
B 방법 (루프 안 정의) n번 n번 X
  • A 방법: 생성/소멸은 각각 1번뿐이지만 대입이 n번 발생하고, w유효 범위가 루프 밖까지 넓어져 유지보수성이 떨어진다.
  • B 방법: 매 반복마다 생성/소멸이 일어나지만 대입이 없고, 유효 범위가 루프 내부로 한정된다.

따라서 B 방법이 유리한 경우는 다음과 같다.

  • 대입 연산 비용이 (생성자 + 소멸자) 비용보다 클 때
  • 성능에 특별히 민감한 코드

🔍 피드백: 채점은 두 소문항 5점씩입니다.

  • (1) 3.5 / 5 — 두 비용이 생성자와 소멸자라는 것, 그리고 encrypted가 쓰이지도 않고 생성·소멸된다는 것까지 정확합니다. 다만 개선 방향이 빗나갔습니다. string& encrypted출력 파라미터로 빼는 것은 이 항목이 말하는 해법이 아닙니다(오히려 반환값 최적화를 막고 인터페이스를 나쁘게 만듭니다). 이 항목의 제목대로 “정의를 늦출 수 있는 데까지 늦추고, 정의와 동시에 초기화” 하는 것이 답입니다 — throw 아래로 내리고 string encrypted(password);로 쓰면 기본 생성 + 대입 2단계가 복사 생성 1번으로 줄어듭니다.
  • (2) 3.5 / 5 — 호출 횟수는 A 1/n/1, B n/0/n으로 완전히 정확합니다. 다만 “B가 유리한 상황”의 문장이 끝맺지 못했고, 정답인 “대입 비용이 생성자+소멸자 비용보다 클 때” 라는 비교 조건이 빠졌습니다. B의 장점을 유효 범위가 루프 안으로 좁아진다로 기억하면, 비용 비교 조건도 같이 떠올리기 쉽습니다.

출처: game_dev/cpp/chapter5/2025-07-04-cpp_5_26.md


📌 복습 포인트

  • Q2 DB 질의 결과 처리 — (나) 방식을 쓰는 이유는 SQL 인젝션이 아니라 타입 불일치 + NULL 가능성. “검증(isNull) → 변환(Convert)” 순서로 기억 → server/game_server/7/2026-03-06-game_server_7_10.md
  • Q2 INSERT 작성법 — 하드 코딩 대신 파라미터 바인딩, 한 단계 더 나아가면 저장 프로시저server/game_server/7/2026-03-06-game_server_7_10.md
  • Q3 멀티스레드 비권장 조건 — 뮤텍스 1개뿐 아니라 디바이스 타임(I/O 대기)이 없는 것도 독립적인 근거 → server/game_server/6/2026-03-01-game_server_6_9.md
  • Q4 프라우드넷 기본 모듈NetServer / NetClient, 그리고 NetClient의 P2P 통신 → server/game_server/6/2026-02-18-game_server_6_2.md
  • Q5 변수 정의 늦추기 — 개선 해법은 출력 파라미터가 아니라 정의를 늦추고 정의와 동시에 초기화game_dev/cpp/chapter5/2025-07-04-cpp_5_26.md
  • Q5 A/B 선택 기준 — B가 유리한 조건은 대입 비용 > 생성자 + 소멸자 비용game_dev/cpp/chapter5/2025-07-04-cpp_5_26.md

MallangQuiz 카테고리 내 다른 글 보러가기

댓글남기기