HOWTO · Java

Java.Net.SocketTimeoutException: 연결 시간 초과

오늘의 기사에서는 java.net.SocketTimeoutException: Connection timed out에 대해 설명합니다.

이 페이지의 내용

java.net.SocketTimeoutException: Connection timed out은 네트워크 작업이 허용된 시간 안에 완료되지 않았다는 뜻입니다. 클라이언트 연결을 여는 중에 발생했다면 대상 호스트와 포트, 네트워크 경로, 연결을 수신하는 서비스, 설정된 시간 제한을 확인하세요. 메시지만으로는 어느 항목이 원인인지 알 수 없습니다. Java 또는 하위 네트워크 스택은 연결 실패를 다른 예외 유형이나 메시지로 보고할 수도 있으므로, 메시지뿐 아니라 작업과 대상 엔드포인트도 함께 진단해야 합니다.

java.net.SocketTimeoutException: Connection timed out의 의미

TCP 소켓은 애플리케이션 간 연결의 한쪽 끝점을 나타냅니다. 클라이언트는 java.net.Socket을 사용해 서버의 java.net.ServerSocket에 연결합니다. 서버는 연결을 수락하고 해당 클라이언트와 통신할 별도의 소켓을 받습니다. 시간 제한은 해당 작업이 기다릴 수 있는 시간을 제한합니다. 네트워크 경로를 복구하거나 연결할 수 없는 서비스를 접근 가능하게 만들지는 않습니다.

클라이언트가 Socket.connect(SocketAddress, timeout)을 호출하면 양수인 시간 제한 값이 연결 시도를 밀리초 단위로 제한합니다. 연결이 완료되기 전에 시간이 만료되면 Java가 시간 초과를 보고합니다. 이 설정은 소켓 읽기 시간 제한과 다릅니다. 연결에 성공한 뒤에도 데이터를 기다리는 읽기 작업은 너무 오래 걸릴 수 있습니다.

연결 시간 초과와 읽기 및 accept 시간 초과 구분하기

코드가 어디에서 기다리는지에 따라 설정을 선택하세요.

  • 연결 시간 초과는 클라이언트가 외부 연결을 설정하는 동안 적용됩니다. 시간 제한을 지정해 Socket.connect를 사용하세요.
  • 읽기 시간 초과는 연결된 소켓이 데이터를 기다리는 동안 적용됩니다. Socket.setSoTimeout으로 설정하며 연결 설정 시간은 제한하지 않습니다.
  • accept 시간 초과는 서버가 들어오는 클라이언트 연결을 기다리는 동안 적용됩니다. ServerSocket.setSoTimeout으로 설정하며 클라이언트의 외부 연결 시도 시간은 제한하지 않습니다.

이 제한은 서로 독립적입니다. 제한이 필요한 각 작업을 따로 설정하고, 예상되는 네트워크 및 서비스 지연 시간에 맞는 값을 선택하세요. 이 소켓 API에서 시간 제한을 0으로 설정하면 무한히 기다리므로 유한한 제한 시간을 원한다면 0을 사용하지 마세요.

일반적인 원인과 진단 방법

가능성 있는 원인을 가설로 보고 Java 프로세스가 실행되는 컴퓨터와 네트워크에서 확인하세요.

  1. 시간 제한이 너무 짧습니다. 일반적인 연결 지연 시간과 애플리케이션의 지연 시간 예산을 비교하세요. 느려도 성공하는 연결이 예상되는 경우에만 제한 시간을 늘리세요. 제한이 커지면 실패가 드러날 때까지 더 오래 걸립니다.
  2. 호스트나 포트가 잘못되었거나 수신 중인 서비스가 없습니다. 설정된 호스트 이름, 확인된 주소, 포트, 서버의 수신 상태를 점검하세요. 서비스가 클라이언트에서 접근할 수 있는 주소에 바인딩되어 있는지도 확인하세요.
  3. 네트워크 경로를 사용할 수 없습니다. 클라이언트 환경에서 DNS 확인과 네트워크 라우팅을 점검하세요. 해당하는 경우 컨테이너 또는 클라우드 네트워크 설정도 확인하세요.
  4. 방화벽이나 원격 정책이 트래픽을 필터링합니다. 같은 클라이언트 네트워크에서의 접근을 허용된 네트워크에서의 접근과 비교하고, 관련 방화벽 규칙이나 서비스 로그를 살펴보세요.
  5. 서버가 느리거나 중단되었거나 과부하 상태입니다. 연결 시도 시점의 서버 상태와 로그를 확인하세요. 연결 시간 초과가 발생했다고 해서 서버가 정상이라는 뜻도, 시간 초과만이 문제라는 뜻도 아닙니다.

연결 거부와 연결 시간 초과는 서로 다른 네트워크 결과를 나타낼 수 있지만, 메시지는 운영체제와 네트워크 경로에 따라 달라집니다. 설정을 변경하기 전에 대상, 작업, 경과 시간, 설정된 시간 제한, 원래 예외의 세부 정보를 기록하세요.

클라이언트 소켓의 연결 시간 제한 설정

연결되지 않은 Socket을 만든 다음 대상과 양수인 유한한 시간 제한을 지정해 Socket.connect를 호출하세요. 핵심 호출은 Socket client = new Socket();과 client.connect(new InetSocketAddress(host, port), 5000);입니다. 사용 후 소켓이 닫히도록 try-with-resources를 사용하고, 진단 로그에 호스트와 포트를 남기세요.

아래 독립 실행 예제는 임시 포트를 사용하는 로컬 서버에 클라이언트가 연결되는 모습을 보여 줍니다. 연결 제한 시간은 1초이며 서버는 클라이언트 하나를 수락합니다. 블록이 끝나면 두 소켓이 모두 닫힙니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.net.ServerSocket;
import java.net.Socket;

public class LocalConnect {
    public static void main(String[] args) throws Throwable {
        try (ServerSocket server = new ServerSocket(0)) {
            Thread worker = new Thread(() -> {
                try {
                    server.accept().close();
                } catch (Throwable ignored) {
                }
            });
            worker.start();
            try (Socket client = new Socket()) {
                client.connect(new java.net.InetSocketAddress("127.0.0.1", server.getLocalPort()), 1000);
                System.out.println("Connected to local server");
            }
            worker.join();
        }
    }
}
Connected to local server

제공된 실행은 종료 코드 0으로 완료되었고 표시된 줄을 출력했습니다. 이 로컬 성공은 API 사용 패턴을 보여 줄 뿐, 원격 운영 환경의 호스트에 연결할 수 있음을 입증하지는 않습니다.

서버 소켓의 accept 시간 제한 설정

accept()를 호출하기 전에 양수인 밀리초 값을 지정해 ServerSocket.setSoTimeout을 호출하세요. 예를 들어 server.setSoTimeout(10000);은 server.accept()가 클라이언트를 기다리는 시간을 제한합니다. 해당 시간 안에 클라이언트가 도착하지 않으면 대기 중인 accept 작업이 SocketTimeoutException을 발생시키며 종료됩니다. 서버 소켓 자체는 계속 사용할 수 있으므로 서버 반복문에서 시간 초과를 처리한 뒤 계속 진행하거나 유휴 상태를 보고할 수 있습니다. 작업이 끝나면 수락한 클라이언트 소켓과 수신 대기 중인 서버 소켓을 닫으세요. 이 설정은 수락한 연결이 유지되는 시간이나 해당 연결에서 읽기 작업이 기다리는 시간을 제어하지 않습니다.

아래 독립 실행 예제에는 연결을 시도하는 클라이언트가 없습니다. accept 대기 시간이 만료되고 그 뒤에도 수신 대기 소켓을 사용할 수 있음을 보여 줍니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.net.ServerSocket;
import java.net.SocketTimeoutException;

public class AcceptDeadline {
    public static void main(String[] args) throws Exception {
        try (ServerSocket server = new ServerSocket(0)) {
            server.setSoTimeout(100);
            try {
                server.accept();
                System.out.println("Unexpected client connection");
            } catch (SocketTimeoutException timeout) {
                System.out.println("Accept deadline reached");
            }
            System.out.println("Listening socket open: " + !server.isClosed());
        }
    }
}
Accept deadline reached
Listening socket open: true

제공된 실행은 종료 코드 0으로 완료되었고 표시된 두 줄을 출력했습니다.

HttpURLConnection의 시간 제한 설정

HttpURLConnection으로 URL 연결을 열 때 HttpURLConnection.setConnectTimeout은 연결 설정 시간을 제한하고 setReadTimeout은 응답 데이터를 기다리는 시간을 제한합니다. 예를 들어 url.openConnection()을 HttpURLConnection으로 형변환한 뒤, 연결하거나 읽기 전에 connection.setConnectTimeout(8000);과 connection.setReadTimeout(10000);을 사용하세요. 값의 단위는 밀리초이며 0은 시간 제한이 없다는 뜻입니다. 응답을 읽고 try-with-resources로 스트림을 닫은 다음 요청이 완료되면 disconnect()를 호출하세요. 시간 초과를 별도로 처리하려면 SocketTimeoutException을 잡고, 그 밖의 네트워크 오류는 더 넓은 범위의 IOException으로 처리하세요. 클래스 이름은 정확히 HttpURLConnection입니다.

SocketTimeoutException 처리 및 수정 사항 확인

시간 초과가 발생할 수 있는 작업을 감싸 SocketTimeoutException을 잡고, 호스트, 포트, 작업, 설정된 제한 시간, 경과 시간 등 유용한 정보를 로그에 남기세요. 아무 제한 없이 계속 재시도하지 마세요. 작업을 반복해도 안전한 경우에만 재시도 횟수를 제한하고 각 시도가 전체 요청 기한 안에 끝나도록 하세요. 실패한 시도에서 리소스가 누수되지 않도록 finally 블록이나 try-with-resources로 소켓과 스트림을 닫으세요.

변경한 뒤에는 실패가 발생했던 것과 동일한 런타임 환경에서 확인하세요. 주소와 포트를 확인하고, 서버가 연결을 수신 중이며 접근 가능한지 점검한 다음 연결 설정, 읽기, accept 중 해당 작업을 시험하세요. 측정한 시간과 설정된 제한 시간을 비교하고 서버 로그도 살펴보세요. 시간 제한을 늘려도 같은 실패가 늦게 나타날 뿐이라면 적절한 제한 값으로 되돌리고 라우팅, 트래픽 필터링, 수신 상태 또는 서비스 상태를 조사하세요. 로컬 서버에 성공적으로 연결했다고 해서 원격 엔드포인트나 운영 네트워크에 접근할 수 있다는 뜻은 아닙니다.