Step 95. Level 1 종합 프로젝트 — 네트워크 감시 도구 완성하기
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★★★☆ | 예상 소요 시간 4시간
전제: Step 41~94 전체. 특히 Step 79(포트 스캐너), Step 92~93(SQLite), Step 94(Flask·JSON)를 재활용합니다.
- 준비물: 파이썬, 그리고 지금까지 만든 코드 조각들(스캐너, DB 연동). 이 챕터의 실측은 파이썬 3.12, 스캔 대상은
127.0.0.1(내 컴퓨터)뿐입니다. - 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 스캔 대상은 내 컴퓨터(127.0.0.1)와 내 소유의 랩 대역뿐입니다. 이 챕터는 테스트입니다 — 요구사항 명세만 주어지고, 따라할 코드는 주어지지 않습니다.
지금까지 50개가 넘는 스텝을 지나왔습니다. 파이썬, 웹, 네트워크, 데이터베이스, Git, 문서화. 그런데 진짜 실력은 "따라하기를 해냈다"가 아니라 여기서 드러납니다 — 요구사항만 듣고 완성품을 만드는 것, 그것이 실력입니다. 중간에 막히는 부분이 나오면 축하할 일입니다. 여러분의 구멍을 정확히 알려 주는 신호이니, 그 스텝으로 돌아가 복습하고 다시 오면 됩니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 요구사항 명세만 보고 완성품을 만들 수 있다
- 큰 문제를 파이프라인(스캔→저장→리포트→비교)으로 쪼개고 각 단계를 독립 검증한다
- 소켓 스캔 + SQLite 저장 + 집계 리포트 + 집합 비교를 한 프로그램으로 통합한다
- "첫 실행" 같은 가장자리 경우(edge case)를 스스로 발견하고 처리한다
- README와 회고 문서로 작업을 마무리하는 습관을 완성한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3 (socket, sqlite3, sys, datetime — 전부 내장 모듈) |
| 오늘의 재료 | Step 79의 포트 스캔, Step 93의 바인딩·lastrowid·GROUP BY, Step 94의 JSON 감각 |
| 필요한 개념 | IP 대역 순회, 1대多 테이블 설계, 집합 연산(set), 명령행 인자(sys.argv), 변화 탐지(change detection) |
| 오늘의 산출물 | netwatch.py + netwatch.db + README.md + 위키 회고 |
2-1. 우리가 만들 것 — "넷와치"
기업 보안팀에는 "우리 네트워크에 어제 없던 포트가 오늘 열렸다"를 잡아내는 감시 도구가 있습니다. 침입자가 뒷문(백도어)을 열거나, 누군가 허락 없이 서버를 띄우면 — 변화로 잡아냅니다. 오늘 여러분이 만들 것은 그것의 축소판, NetWatch(넷와치)입니다. 주소 대역을 스캔하고, 결과를 DB에 저장하고, 리포트를 출력하고, 지난번 스캔과 비교해 "새로 열린 포트"를 경고하는 도구입니다. 축소판이라고 얕보지 마세요 — 뼈대의 구조는 실무 도구와 동일합니다.
2-2. 큰 문제를 쪼개는 법 — 파이프라인으로 생각하기
명세를 한 번에 짜려 하면 반드시 무너집니다. 고수의 비결은 쪼개기입니다. 아래 네 단계는 각각이 독립된 작은 프로젝트라고 생각하세요.
[1단계] 스캔만 — 대역을 받아 열린 포트 목록을 메모리에
[2단계] DB 저장 — 그 목록을 SQLite에 시각과 함께
[3단계] 리포트 — DB에서 읽어 요약 출력
[4단계] 비교 — 직전 스캔과 대조해 새 포트 경고
각 단계는 독립적으로 검증합니다. 1단계가 출력을 찍어 확인한 뒤에야 2단계로 갑니다. "전부 짜고 한 번에 실행"은 초보의 함정이고, "조금 짜고 계속 확인"이 프로의 리듬입니다.
2-3. 요구사항 명세 (이것이 오늘의 교과서 전부입니다)
■ 프로그램명: netwatch.py
■ 실행: python netwatch.py 127.0.0.1 127.0.0.1
(시작 IP와 끝 IP를 인자로 받는다. 인자 없으면 사용법 출력)
■ 기능 1 — 스캔
· 대역의 각 IP에 대해 주요 포트(22, 80, 443, 445, 3389 등 10개 내외)를 연결 시도
· 열린 포트는 배너(서비스가 먼저 보내는 인사말) 수집을 시도 (0.5초 제한)
■ 기능 2 — 저장 (netwatch.db)
· 테이블 scans: id, scan_time(스캔 시각)
· 테이블 findings: id, scan_id(어느 스캔인지), ip, port, banner
· 바인딩(?) 필수 — 문자열 결합 금지 (Step 93의 교훈)
■ 기능 3 — 리포트
· 스캔 완료 후 출력: 발견 기기 수, 열린 포트 총계, 기기별 열린 포트 목록
■ 기능 4 — 비교
· 직전 스캔과 비교해 이번에 "새로 열린 포트(IP+포트 조합)"가 있으면
"[!] NEW: 127.0.0.1:8001" 형태로 경고 출력
· 첫 스캔이면 "기준선 생성" 메시지
■ 마무리
· README.md: 도구 소개, 사용법, 동작 원리, 주의사항(합법 사용)
· Git 저장소에 의미 단위로 커밋
시간 배분 가이드
예상 소요 4시간의 권장 배분: 1단계 스캔 50분, 2단계 DB 50분, 3단계 리포트 30분, 4단계 비교 60분, 입력 검증·정리·문서화 50분. 계획보다 오래 걸리는 단계가 나오는 것은 지극히 정상입니다 — 그 단계가 바로 여러분의 복습 포인트이니, 넘어가기 전에 위키에 한 줄 적어 두세요. 이 테스트의 점수는 완성품이 아니라 그 지도에서 나옵니다.
3. 따라 하기
이번에는 따라할 코드가 없습니다. 대신, 각 단계를 통과했는지 스스로 검증하는 체크 포인트와, 검증 환경을 만드는 법을 드립니다.
3-1. 검증 환경 준비 — 피험자를 띄워 두기
스캔 도구를 검증하려면 "열린 포트"가 미리 있어야 합니다. 테스트용 서버를 두세 개 띄워 두세요.
python -m http.server 8000 --bind 127.0.0.1 # 터미널 1
python -m http.server 9000 --bind 127.0.0.1 # 터미널 2
읽는 법: 터미널 창을 여러 개 열어 각각 띄웁니다. 실험이 끝나면 각 창에서 Ctrl+C로 반드시 종료하세요.
왜: 도구를 믿기 전에, 도구를 시험하는 환경을 만드는 것 — 이 순서가 실무 검증의 철칙입니다.
3-2. 체크 포인트 1 — 스캔 단계
- Step 79의 스캐너 코드를 꺼내 함수로 포장하세요.
scan(ip_list, ports)→ 열린 것들의 리스트 반환. - IP 대역 만들기: 마지막 옥텟(마침표로 나눈 네 번째 숫자)만 돌리면 됩니다.
ip.split('.')과range()의 조합. - 검증:
127.0.0.1하나만 스캔해, 여러분이 일부러 띄운 서버(8000, 9000)가 잡히는지 확인. 포트 목록에 8000, 9000을 추가하는 것을 잊지 마세요.
3-3. 체크 포인트 2 — DB 저장 단계
- 테이블 두 개를 만드는 CREATE 문을 프로그램 시작 부분에 두세요.
IF NOT EXISTS를 붙이면 매번 실행해도 안전합니다. - 스캔 한 번 = scans에 행 하나. 그 스캔의 id를 꺼내는 방법은
cur.lastrowid(방금 넣은 행의 번호)입니다. - 검증: 실행 후
sqlite3 netwatch.db로 들어가.tables와SELECT * FROM findings;로 눈 확인.
3-4. 체크 포인트 3 — 리포트 단계
- Step 93의
GROUP BY가 여기서 빛을 발합니다. "기기별 포트 수"는SELECT ip, COUNT(*) FROM findings WHERE scan_id=? GROUP BY ip. - 검증: 출력이 사람이 읽기 편한 모양인가? "기기 1대, 열린 포트 총 4개"처럼 숫자가 먼저 나와야 합니다.
3-5. 체크 포인트 4 — 비교 단계 (오늘의 두뇌 운동)
- "직전 스캔"은 어떻게 찾을까요? scans에서 두 번째로 최신 것 —
ORDER BY id DESC LIMIT 2를 생각해 보세요. - 집합 연산: 이번 스캔의 (ip, port) 모음에서 직전 것을 빼면 "새로 열린 것"입니다. 파이썬의
set이 이 일을 잘합니다. - 검증: 스캔 → 아무 변화 없이 다시 스캔(경고 없어야 함) → 테스트 서버를 하나 더 띄우고 스캔(
[!] NEW가 떠야 함). 이 세 컷이 모두 맞아야 통과입니다.
3-6. 예측해 보기 — 비교 로직의 함정
예측: "새로 열린 포트"를 계산할 때, 이번 스캔과 직전 스캔의 결과를 단순히 비교하는 코드를 짰다고 합시다. 직전 스캔이 없는 경우(프로그램 생애 첫 실행)에는 무슨 일이 일어날까요?
- (a) 모든 포트가 "NEW"로 경고된다
- (b) 오류가 나며 죽는다
- (c) 조용히 넘어간다
직접 확인: DB를 지우고 첫 실행을 해 보세요. 여러분의 코드에 따라 (a)나 (b)가 될 것입니다. 그래서 명세에 "첫 스캔이면 기준선 생성 메시지"가 있는 것입니다 — 예외 상황을 먼저 처리하는 분기를 넣어야 합니다. 실무에서도 "첫 실행", "빈 파일", "목록이 0개" 같은 가장자리 경우가 버그의 절반을 만듭니다.
3-7. 체크 포인트 5 — 입력 검증과 사용법
테스트에는 "사용자가 이상하게 쓰는 경우"도 포함됩니다.
- 인자가 없거나 하나뿐일 때: 사용법을 출력하고 조용히 종료하세요(
sys.argv길이 검사). - IP 형식이 아닌 인자(
abc)가 들어올 때: 오류 메시지와 함께 종료.ip.split('.')의 결과가 0~255 숫자 네 개인지 검사합니다. - 시작 IP가 끝 IP보다 큰 경우: 뒤집어 주거나 거부하세요 — 어떤 결정이든 README에 적습니다.
검증: 세 가지 이상한 입력을 실제로 던져 보고, 프로그램이 죽지 않고 "사람이 읽을 수 있는 안내"를 뱉는지 확인합니다. 공격자는 항상 이상한 입력을 던집니다 — 입력 검증은 편의 기능이 아니라 보안 기능입니다.
3-8. 체크 포인트 6 — 코드 정리와 커밋 단위
기능이 다 되어도 끝이 아닙니다. 남이 읽을 코드로 다듬는 것이 마지막 단계입니다.
- 함수마다 한 줄 주석 — "이 함수는 무엇을 반환하는가".
- 숫자 상수(포트 목록, 타임아웃)는 파일 위쪽에 모으세요. 나중에 고치기 쉬워집니다.
- 커밋은 의미 단위로: "스캔 기능", "DB 저장", "비교 경고", "README"처럼 나누어 찍으면, 이 프로젝트의
git log자체가 포트폴리오의 한 페이지가 됩니다.
검증: git log --oneline을 찍어 보며, 각 커밋이 "무엇을 한 커밋인지" 한눈에 읽히는지 확인합니다.
4. 미션과 연습문제
미션 — NetWatch 완성과 세 시나리오 검증
- 명세의 기능 1~4가 전부 동작하는
netwatch.py를 완성합니다. - 세 시나리오를 모두 검증합니다: ① 첫 실행(기준선 생성) ② 변화 없음(경고 없음) ③ 새 포트 등장(
[!] NEW). - SQL은 전부 바인딩, 인자 검증과 사용법 출력이 있습니다.
- README.md에 소개·사용법·원리·합법 사용 주의를 적고, Git 저장소에 의미 단위로 커밋합니다.
- 위키에 회고를 작성합니다 — 어디서 막혔는지, 어떻게 뚫었는지.
연습문제
문제 1. scans와 findings를 두 테이블로 나눈 이유를 "1대多 관계"와 "중복"이라는 말로 설명해 보세요.
문제 2. "새로 열린 포트"를 구할 때 왜 리스트가 아니라 집합(set)의 뺄셈(now - prev)이 편한지 설명해 보세요.
문제 3. 배너 수집이 특정 포트에서 멈추는 현상의 원인과, 소켓에 걸어야 할 두 가지 안전장치를 말해 보세요.
문제 4. 이 도구의 구조(기준선 저장 → 주기적 비교 → 차이 경고)가 포트 감시 외에 어떤 실무 감시 도구들에도 통하는지 두 가지 예를 들어 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
여러분의 코드가 스스로 동작했다면 그것이 정답입니다. 아래는 참고용 해설 — 이 챕터의 실측 검증에 실제로 사용한 구현입니다. 먼저 보지 말고, 막힌 단계의 것만 골라 읽으세요.
"""NetWatch — 내 네트워크의 포트 변화를 감시하는 도구 (교육용)"""
import socket, sqlite3, sys
from datetime import datetime
PORTS = [21, 22, 23, 25, 80, 110, 443, 445, 3306, 3389, 8000, 8001, 9000]
TIMEOUT = 0.3
DB = "netwatch.db"
def make_ip_range(start, end):
"""'127.0.0.1'~'127.0.0.3' 같은 대역을 IP 리스트로 만든다."""
s, e = start.split("."), end.split(".")
if len(s) != 4 or len(e) != 4:
raise ValueError("IP 형식이 아닙니다.")
if not all(p.isdigit() and 0 <= int(p) <= 255 for p in s + e):
raise ValueError("IP 형식이 아닙니다.")
prefix = ".".join(s[:3])
return [f"{prefix}.{i}" for i in range(int(s[3]), int(e[3]) + 1)]
def scan(ip_list, ports):
"""열린 포트를 (ip, port, banner) 튜플 리스트로 반환한다."""
found = []
for ip in ip_list:
for port in ports:
s = socket.socket()
s.settimeout(TIMEOUT)
try:
s.connect((ip, port))
banner = ""
try:
banner = s.recv(64).decode("utf-8", "replace").strip()
except socket.timeout:
pass
found.append((ip, port, banner))
except OSError:
pass
finally:
s.close()
return found
def save_scan(conn, found):
"""스캔 한 번을 scans + findings에 저장하고 scan id를 반환한다."""
cur = conn.cursor()
cur.execute("INSERT INTO scans (scan_time) VALUES (?)",
(datetime.now().isoformat(timespec="seconds"),))
scan_id = cur.lastrowid
for ip, port, banner in found:
cur.execute(
"INSERT INTO findings (scan_id, ip, port, banner) VALUES (?, ?, ?, ?)",
(scan_id, ip, port, banner))
conn.commit()
return scan_id
def compare(conn, scan_id):
"""직전 스캔과 비교해 새로 열린 포트를 경고한다."""
ids = [r[0] for r in conn.execute(
"SELECT id FROM scans ORDER BY id DESC LIMIT 2").fetchall()]
if len(ids) < 2:
print("기준선 생성 — 첫 스캔이므로 비교할 과거가 없습니다.")
return
now = {(ip, p) for ip, p in conn.execute(
"SELECT ip, port FROM findings WHERE scan_id=?", (ids[0],))}
prev = {(ip, p) for ip, p in conn.execute(
"SELECT ip, port FROM findings WHERE scan_id=?", (ids[1],))}
for ip, port in sorted(now - prev):
print(f"[!] NEW: {ip}:{port}")
if now == prev:
print("변화 없음 — 직전 스캔과 동일합니다.")
세 시나리오 실측 (2026-09-09, 대상 127.0.0.1, 피험자 서버 8000·9000 기동):
### 첫 스캔 (8000, 9000 열림)
대상 1개 IP, 포트 13개 스캔 중...
발견 기기 1대, 열린 포트 총 4개
127.0.0.1: 22, 445, 8000, 9000
기준선 생성 — 첫 스캔이므로 비교할 과거가 없습니다.
### 두 번째 스캔 (변화 없음)
발견 기기 1대, 열린 포트 총 4개
127.0.0.1: 22, 445, 8000, 9000
변화 없음 — 직전 스캔과 동일합니다.
### 세 번째 스캔 (8001을 새로 띄운 뒤)
발견 기기 1대, 열린 포트 총 5개
127.0.0.1: 22, 445, 8000, 8001, 9000
[!] NEW: 127.0.0.1:8001
읽는 법: 두 가지를 눈여겨보세요. 첫째, 22번과 445번 — 이것은 제가 띄운 게 아니라 실측 컴퓨터에 원래 열려 있던 포트입니다. 내가 모르는 열린 문이 내 컴퓨터에도 있었다는 것, 이것이 감시 도구의 존재 이유입니다. 둘째, banner 칸이 비어 있습니다 — 파이썬의 http.server는 먼저 인사말을 보내지 않는 서비스라 배너가 없는 것이 정상입니다 (2026-09-09 실측). 배너는 SSH나 FTP처럼 "먼저 말을 거는" 서비스에서만 수집됩니다.
입력 검증도 실측했습니다 — 인자 없이 실행하면 사용법: python3 netwatch.py <시작IP> <끝IP>, abc를 넣으면 입력 오류: IP 형식이 아닙니다.를 출력하고 조용히 종료합니다 (2026-09-09).
연습문제 해답
문제 1 해답. 스캔 시각은 스캔마다 하나인데 발견된 포트는 스캔마다 여러 개 — 1대多 관계입니다. 한 장의 표에 욱여넣으면 시각이 모든 행에 중복됩니다. 그래서 scans(1)와 findings(多)로 나누고 scan_id 번호로 잇습니다. 리포트에서 JOIN으로 다시 붙일 수 있는 것이 이 설계의 증명입니다.
문제 2 해답. "A에는 있고 B에는 없는 것"은 집합의 차집합이 정확히 그 뜻이기 때문입니다. 리스트로 하면 이중 반복문 안에서 "직전 목록에 이게 있었나?"를 하나씩 물어야 하고, 집합은 now - prev 한 줄에 순서와 중복 걱정까지 없애 줍니다. 실측 구현도 {튜플들} 두 집합의 뺄셈으로 끝냈습니다.
문제 3 해답. 서비스가 배너를 보내지 않는데 recv가 무한 대기하기 때문입니다. 안전장치 두 가지: s.settimeout(0.3)으로 "최대 기다림"을 정하고, try/except로 감싸 실패 시 배너를 빈 문자열로 처리합니다. 기다리는 코드에는 항상 "최대 기다림"이 있어야 합니다. 그리고 실측에서 보았듯, 인사말을 안 하는 서비스(http.server)에서는 배너가 비는 것이 정상입니다 — 빈 배너를 오류로 취급하지 않는 것도 설계입니다.
문제 4 해답. 같은 구조의 사촌들: ① 파일 무결성 감시 — 서버의 파일 목록·해시를 기준선으로 저장하고 몰래 바뀐 파일을 경고. ② 계정 감시 — 어제 없던 관리자 계정이 생겼는지 비교. 관찰 대상만 바뀔 뿐, 기준선 저장 → 주기적 비교 → 차이 경고의 뼈대는 동일합니다.
완료 기준 체크리스트
- [ ] 명세만 보고 네 기능을 갖춘 도구를 완성할 수 있다
- [ ] 프로그램을 단계별로 쪼개 개발하고 각 단계를 검증할 수 있다
- [ ] "첫 실행" 등 가장자리 경우를 스스로 발견하고 처리할 수 있다
- [ ] SQL은 전부 바인딩이고, 인자 검증과 사용법 출력이 있다
- [ ] README와 회고 문서로 작업을 마무리했다
- [ ] Level 1의 어느 스텝이 내 약점인지 말할 수 있다
- [ ] 미션: 세 시나리오 검증까지 완료했다
6. 흔한 실수와 해결
벽 1. 스캔이 너무 느려요
증상: 대역 전체에 수 분이 걸립니다.
원인: 닫힌 포트는 연결 거절까지 기다립니다. 타임아웃이 길면 곱하기로 느려집니다.
해결: 소켓의 타임아웃을 0.2~0.5초로 짧게(Step 79 복습). 그래도 느리면 스캔 포트 수를 줄여 개발하고, 완성 후 늘리세요. 실측 구현은 127.0.0.1 × 13포트에 타임아웃 0.3초로 몇 초 안에 끝났습니다.
벽 2. lastrowid가 None이 나와요
증상: scans에 넣었는데 id를 못 가져옵니다.
원인: conn.execute로 바로 실행하면 커서를 못 잡습니다. cur = conn.cursor()로 커서를 만들고 cur.execute(...)한 뒤 cur.lastrowid를 읽어야 합니다.
해결: 커서를 명시적으로 만드는 흐름으로 바꾸세요 (모범 답안의 save_scan 참조).
벽 3. 비교가 엉켜요 — 다 NEW로 뜨거나, 떠야 할 게 안 떠요
증상: 변화가 없는데 경고가 뜹니다.
원인: 튜플 비교 시 자료형 불일치(포트가 한쪽은 숫자, 한쪽은 문자열)이거나, scan_id를 잘못 잡았거나.
해결: 두 스캔의 findings를 각각 SELECT로 출력해 눈으로 대조해 보세요. 집합에 넣기 전 print(now, prev)로 찍어 보는 것이 가장 빠른 진단입니다.
벽 4. 배너 수집에서 프로그램이 멈춰요
증상: 특정 포트에서 영원히 기다립니다.
원인: 서비스가 배너를 보내지 않는데 recv가 무한 대기합니다. 실측에서도 파이썬 http.server는 배너를 보내지 않았습니다 — 타임아웃이 없었다면 거기서 멈췄을 것입니다.
해결: recv 전에 소켓 타임아웃을 걸고, try/except로 감싸서 실패 시 배너를 빈 문자열로. 기다리는 코드에는 항상 "최대 기다림"이 있어야 합니다.
벽 5. 막힘이 코드가 아니라 마음에서 와요
증상: "명세만 있고 답이 없다"는 상황 자체가 낯설어 손이 멈춥니다. 이 챕터에서 가장 많은 포기가 나오는 지점입니다.
원인: 정상입니다. 모범 답안이 보이는 연습과, 명세만 있는 작업은 다른 근육을 씁니다.
해결: 세 가지 전술. 첫째, 막힘은 데이터입니다 — "배너 recv에서 30분"처럼 위키에 적으면 약점 지도가 완성됩니다. 둘째, 군소 스크립트로 실험하세요 — test_lastrowid.py처럼 다섯 줄짜리 실험 파일에서 궁금한 동작만 떼어 확인하고 본편에 이식합니다. 셋째, 한 문제에 40분 이상 붙잡히면 그 스텝의 본편으로 돌아가 따라하기를 다시 하는 쪽이 빠릅니다. 자존심의 문제가 아니라 전략의 문제입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 변화 탐지(change detection) | 기준선 저장 → 주기적 비교 → 차이 경고. 실무 감시 도구의 공통 뼈대 |
| 파이프라인 분할 | 스캔→저장→리포트→비교를 독립 검증 가능한 단계로 쪼개는 설계 |
| 1대多 관계 | 스캔 1번 : 발견 N건 — 표를 둘로 나누고 번호로 잇는다 |
| 집합 차집합 | now - prev — "새로 열린 것"의 정확한 정의 |
| 가장자리 경우(edge case) | 첫 실행, 빈 목록 — 버그의 절반을 만드는 예외 상황들 |
| 공격 표면의 자기 점검 | 내가 모르는 열린 포트(실측의 22, 445)가 내 컴퓨터에도 있다 |
오늘의 명령어·코드
| 코드 | 하는 일 |
|---|---|
sys.argv |
명령행 인자 — 길이 검사가 입력 검증의 시작 |
socket.socket() + settimeout + connect |
포트 연결 시도 (Step 79 재활용) |
CREATE TABLE IF NOT EXISTS |
매번 실행해도 안전한 테이블 준비 |
cur.lastrowid |
방금 INSERT한 행의 번호 — scans↔findings의 고리 |
GROUP BY ip + COUNT(*) |
기기별 포트 수 요약 |
ORDER BY id DESC LIMIT 2 |
최신 스캔 두 개 — 비교의 재료 |
set(...) - set(...) |
새로 열린 포트 계산 |
명령어보다 중요한 감각
요구사항 명세만으로 완성품을 만드는 것 — 그것이 이 테스트의 이름이자 실력의 정의입니다. 큰 문제는 파이프라인으로 쪼개고, 각 단계는 독립적으로 검증하고, 가장자리 경우를 먼저 생각합니다. 그리고 도구를 믿기 전에 도구를 시험하는 환경을 만듭니다. 이 네 가지는 이 프로젝트가 끝나도 남는 습관입니다.
완성했다면 스스로 채점해 보세요 — 스캔 정확도, DB 설계, 리포트 가독성, 비교 로직, 문서화의 다섯 항목 각 0~2점, 총 7점 이상이면 통과입니다. 설계는 정답이 아니라 선택과 이유입니다. 명세와 다른 설계를 택했다면, 그 이유를 README에 적으세요 — 이유 있는 다른 설계는 틀린 게 아닙니다.
통과하셨습니까? Level 1, 완주입니다. 컴퓨터 안쪽을 열어 보고, 네트워크의 대화를 엿듣고, 창고지기와 대화하고, 서버를 직접 만들어 보고, 마지막으로 명세만으로 도구를 완성했습니다. 오늘 만든 감시견은 작지만, 그 뼈대는 실무 변화 탐지 시스템 그대로입니다. 이 도구의 스캔 대상은 어디까지나 내 컴퓨터와 내 랩뿐이라는 약속과 함께, Level 1의 문을 닫으세요.
전부 체크되면 Step 95 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.