실습으로 진행할 HTTP 웹 서버의 핵심이 되는 코드는 webserver패키지의 WebServer와 RequestHandler 클래스이다.
먼저 이 WebServer 클래스는 “웹서버”라기보다는 정확히는
- 특정 포트에서 TCP 연결을 받는 프로그램
- 연결이 들어오면, 그 연결(Socket)을 넘겨서 요청/응답 처리를 다른 쓰레드로 맡기는 프로그램
이다.
즉, 여기서는 “서버가 접속을 받는 역할”까지만 담당하고, 실제 HTTP 처리는 RequestHandler가 한다.
public class WebServer {
private static final Logger log = LoggerFactory.getLogger(WebServer.class);
private static final int DEFAULT_PORT = 8080;
public static void main(String args[]) throws Exception {
int port = 0;
if (args == null || args.length == 0) {
port = DEFAULT_PORT;
} else {
port = Integer.parseInt(args[0]);
}
// 서버소켓을 생성한다. 웹서버는 기본적으로 8080번 포트를 사용한다.
try (ServerSocket listenSocket = new ServerSocket(port)) {
log.info("Web Application Server started {} port.", port);
// 클라이언트가 연결될때까지 대기한다.
Socket connection;
while ((connection = listenSocket.accept()) != null) {
RequestHandler requestHandler = new RequestHandler(connection);
requestHandler.start();
}
}
}
}
이게 전체 코드이고 하나씩 살펴 보겠다.
main()에서 하는 첫 번째 일: 포트 결정
int port = 0;
if (args == null || args.length == 0) {
port = DEFAULT_PORT;
} else {
port = Integer.parseInt(args[0]);
}
의미
- 프로그램 실행할 때 포트를 안 주면 → 기본 8080
- 실행할 때 포트를 주면 → 그 숫자를 포트로 사용
예:
- java webserver.WebServer → 8080 사용
- java webserver.WebServer 7070 → 7070 사용
여기서 7070이 main의 args로 들어간다.
여기서 args는 “명령줄 인자”이다. args[0]은 첫 번째 인자(포트번호)이다.
서버가 “열리는” 핵심: new ServerSocket(port)
try (ServerSocket listenSocket = new ServerSocket(port)) {
...
}
ServerSocket은?
- 서버 쪽에서 쓰는 소켓으로,
- “이 포트로 들어오는 연결 요청을 받겠다” 라고 OS에 등록하는 것이다.
쉽게 말하면:
- 내 서버의 8080번 문 앞에 “대기 직원”을 세우는 것이다.
- 이 순간부터 외부에서 http://IP:8080 같은 접속이 들어올 수 있는 “입구”가 열린다.
왜 try-with-resources?
- try (...) {} 형태는 블록이 끝나면 자동으로 close() 해준다.
- 서버가 종료될 때 포트/소켓이 깔끔하게 정리되게 하는 안전장치이다.
try-with-resources란
try ( ... ) { ... } 괄호 안에 닫아야 하는 자원을 선언하는 형태를 “try-with-resources”
서버 시작 로그
log.info("Web Application Server started {} port.", port);
- “서버가 어느 포트에서 열렸는지” 로그로 남긴다.
- 실제로 운영에서 이런 로그는 매우 중요하다(포트/환경 확인).
이 코드의 핵심: accept 루프
Socket connection;
while ((connection = listenSocket.accept()) != null) {
RequestHandler requestHandler = new RequestHandler(connection);
requestHandler.start();
}
1) listenSocket.accept()가 하는 일
- 클라이언트가 연결을 시도할 때까지 기다린다.
- 기다리는 동안 아무것도 안 하고 “멈춰있는 상태”가 된다. (이걸 블로킹이라고 합니다)
- 연결이 들어오면, Socket 객체를 하나 만들어서 반환한다.
즉,
- ServerSocket: “문(포트)”
- accept(): “손님이 오면 문 열고 들여보내기”
- Socket connection: “그 손님과 1:1로 통신하는 전화선/대화 채널”
중요한 점: accept()는 ‘요청 한 건(HTTP 한 번)’을 받는 게 아니라 ‘연결(connection)’을 받는 것이다.
HTTP는 그 연결 위에서 흐르는 데이터(요청/응답)일 뿐이다.
2) while을 왜 돌리나?
서버는 딱 한 명만 받고 끝나는 프로그램이 아니라,
- 손님이 오면 받고,
- 또 오면 받고,
- 계속 받아야 하니까
무한 루프 형태로 “계속 accept” 하는 것이다.
여기서 accept()는 보통 절대 null을 주지 않는다.
그래서 ( ... ) != null은 사실상 “계속 돌아라”에 가까운 관용적 코드이다.
연결을 받으면 “처리 담당자”에게 넘긴다: RequestHandler
RequestHandler requestHandler = new RequestHandler(connection);
requestHandler.start();
의미
- 방금 연결된 손님(connection)을 RequestHandler에게 넘기고
- start()로 실행을 시작
여기서 궁금한점:
(1) 왜 RequestHandler를 새로 만들어?
- 클라이언트 접속 하나당 Socket이 하나 생기고,
- 그 Socket을 처리할 담당자가 필요하니까 “한 접속당 한 핸들러”로 만든다.
(2) 왜 start()를 호출해?
- RequestHandler는 Thread를 상속한 클래스라서,
- start()를 호출하면 새 쓰레드에서 run()을 실행하게 된다.
즉,
- 메인 쓰레드(현재 while 루프 돌고 있는 쓰레드)는 다시 accept()로 돌아가서 다음 접속을 받을 준비
- 동시에, 새로 생성된 쓰레드는 그 접속(Socket)을 가지고 요청 처리/응답 전송을 수행
이 구조가 없으면 어떤 문제가 생기냐면:
- 한 클라이언트 처리하는 동안 다음 클라이언트 접속을 못 받거나, 서버가 느려진다.
정확히 이해해야 하는 포인트 3개
- 포트는 프로그램 실행 인자(args)로 바꿀 수 있고, 기본은 8080이다.
- ServerSocket은 ‘연결을 받는 문’이고, accept()는 연결이 올 때까지 기다렸다가 Socket을 준다.
- Socket을 받으면 RequestHandler에게 넘기고, start()로 새 쓰레드에서 처리한다.
“HTTP는 어디서 시작?”
이 파일에서는 HTTP가 아직 시작 안 했음.
- 여기서는 TCP 연결을 받고
- HTTP 요청 읽고/응답 쓰는 건 RequestHandler에서 시작
출처 : 《자바 웹 프로그래밍 Next Step》, 박재성, 로드북
'Book > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
| 3장_ 요구사항1: index.html 응답하기 (0) | 2026.01.30 |
|---|---|
| 3장_ 코드 이해: RequestHandler 클래스 (0) | 2026.01.29 |
| 3장_ 소스코드 재배포 (0) | 2026.01.29 |
| 3장_ 방화벽 설정 (0) | 2026.01.29 |
| 3장_ 원격 서버 띄우기 (0) | 2026.01.29 |
