네트워크 타임 아웃의 간단한 처리

많은 프로그래머는 네트워크 시간 초과 처리에 대한 생각을 두려워합니다. 일반적인 두려움은 시간 제한을 지원하지 않는 단순한 단일 스레드 네트워크 클라이언트가 네트워크 시간 제한을 감지하는 데 필요한 별도의 스레드와 차단 된 스레드와 기본 응용 프로그램 사이의 알림 프로세스 형태로 복잡한 다중 스레드 악몽에 빠질 것이라는 것입니다. 이것은 개발자를위한 하나의 옵션이지만 유일한 옵션은 아닙니다. 네트워크 시간 초과를 처리하는 것은 어려운 작업 일 필요가 없으며 많은 경우 추가 스레드에 대한 코드 작성을 완전히 피할 수 있습니다.

네트워크 연결 또는 모든 유형의 I / O 장치로 작업 할 때 두 가지 작업 분류가 있습니다.

  • 차단 작업 : 읽기 또는 쓰기 중단, I / O 장치가 준비 될 때까지 작업 대기
  • Nonblocking 작업 : 읽기 또는 쓰기 시도가 이루어지고 I / O 장치가 준비되지 않은 경우 작업이 중단됩니다.

Java 네트워킹은 기본적으로 I / O 차단의 한 형태입니다. 따라서 Java 네트워킹 애플리케이션이 소켓 연결에서 읽을 때 즉각적인 응답이없는 경우 일반적으로 무기한 대기합니다. 사용 가능한 데이터가 없으면 프로그램이 계속 대기하고 더 이상 작업을 수행 할 수 없습니다. 문제를 해결하지만 약간의 복잡성이 추가되는 한 가지 해결책은 두 번째 스레드가 작업을 수행하도록하는 것입니다. 이렇게하면 두 번째 스레드가 차단 된 경우에도 응용 프로그램은 사용자 명령에 응답 할 수 있으며 필요한 경우 중단 된 스레드를 종료 할 수도 있습니다.

이 솔루션은 종종 사용되지만 훨씬 더 간단한 대안이 있습니다. 자바는 네트워크를 블로킹 지원 I / 어떤에서 활성화 될 수 O, Socket, ServerSocket, 또는 DatagramSocket. 읽기 또는 쓰기 작업이 응용 프로그램에 다시 제어를 반환하기 전에 중단되는 최대 시간을 지정할 수 있습니다. 네트워크 클라이언트의 경우 이것은 가장 쉬운 솔루션이며 더 간단하고 관리하기 쉬운 코드를 제공합니다.

Java에서 비 차단 네트워크 I / O의 유일한 단점은 기존 소켓이 필요하다는 것입니다. 따라서이 방법은 일반적인 읽기 또는 쓰기 작업에 적합하지만 연결 작업에 대한 제한 시간을 지정하는 방법이 없기 때문에 연결 작업이 훨씬 더 오랜 기간 동안 중단 될 수 있습니다. 많은 응용 프로그램에는이 기능이 필요합니다. 그러나 추가 코드를 작성하는 추가 작업을 쉽게 피할 수 있습니다. 연결에 대한 시간 제한 값을 지정할 수있는 작은 클래스를 작성했습니다. 두 번째 스레드를 사용하지만 내부 세부 정보는 추상화됩니다. 이 접근 방식은 비 차단 I / O 인터페이스를 제공하고 두 번째 스레드의 세부 정보가 보이지 않기 때문에 잘 작동합니다.

비 차단 네트워크 I / O

어떤 일을하는 가장 간단한 방법이 종종 최선의 방법으로 판명됩니다. 스레드를 사용하고 I / O를 차단해야하는 경우도 있지만 대부분의 경우 비 차단 I / O는 훨씬 더 명확하고 우아한 솔루션을 제공합니다. 몇 줄의 코드만으로 모든 소켓 응용 프로그램에 대한 제한 시간 지원을 통합 할 수 있습니다. 나를 믿지 않습니까? 읽어.

Java 1.1이 릴리스되었을 때 java.net프로그래머가 소켓 옵션을 지정할 수 있도록 패키지 에 대한 API 변경 사항이 포함되었습니다 . 이 옵션은 프로그래머가 소켓 통신을 더 잘 제어 할 수 있도록합니다. 특히 한 가지 옵션 SO_TIMEOUT은 프로그래머가 읽기 작업이 차단되는 시간을 지정할 수 있기 때문에 매우 유용합니다. 짧은 지연을 지정하거나 전혀 지정하지 않고 네트워킹 코드를 차단하지 않도록 설정할 수 있습니다.

이것이 어떻게 작동하는지 살펴 보겠습니다. setSoTimeout ( int )다음 소켓 클래스에 새 메서드 가 추가되었습니다.

  • java.net.Socket
  • java.net.DatagramSocket
  • java.net.ServerSocket

이 방법을 사용하면 다음 네트워크 작업이 차단할 최대 시간 초과 길이 (밀리 초)를 지정할 수 있습니다.

  • ServerSocket.accept()
  • SocketInputStream.read()
  • DatagramSocket.receive()

이러한 메서드 중 하나가 호출 될 때마다 시계가 똑딱 거리기 시작합니다. 작업이 차단되지 않으면 재설정되고 이러한 메서드 중 하나가 다시 호출 된 후에 만 ​​다시 시작됩니다. 결과적으로 네트워크 I / O 작업을 수행하지 않으면 시간 초과가 발생하지 않습니다. 다음 예제는 다중 실행 스레드에 의존하지 않고 시간 초과를 처리하는 것이 얼마나 쉬운 지 보여줍니다.

// 들어오는 UDP 패킷을 수신하기 위해 포트 2000에 데이터 그램 소켓을 생성합니다. DatagramSocket dgramSocket = new DatagramSocket (2000); // 5 초 제한 시간을 지정하여 I / O 작업 차단을 비활성화합니다. dgramSocket.setSoTimeout (5000);

시간 제한 값을 할당하면 네트워크 작업이 무기한 차단되지 않습니다. 이 시점에서 네트워크 작업이 시간 초과되면 어떻게 될지 궁금 할 것입니다. 개발자가 항상 확인하지는 않는 오류 코드를 반환하는 대신 a java.io.InterruptedIOException가 발생합니다. 예외 처리는 오류 조건을 처리하는 훌륭한 방법이며 일반 코드를 오류 처리 코드와 분리 할 수 ​​있습니다. 게다가, 누가 null 참조에 대한 모든 반환 값을 종교적으로 확인합니까? 예외를 던짐으로써 개발자는 시간 초과에 대한 catch 핸들러를 제공해야합니다.

다음 코드 스 니펫은 TCP 소켓에서 읽을 때 시간 초과 작업을 처리하는 방법을 보여줍니다.

// 소켓 타임 아웃 10 초 설정 connection.setSoTimeout (10000); try {// 소켓에서 읽기위한 DataInputStream 생성 DataInputStream din = new DataInputStream (connection.getInputStream ()); // (;;) {String line = din.readLine ();에 대한 데이터 끝까지 데이터 읽기 if (줄! = null) System.out.println (줄); 그렇지 않으면 휴식; }} // 네트워크 시간 초과 발생시 예외 발생 catch (InterruptedIOException iioe) {System.err.println ( "읽기 작업 중 원격 호스트 시간 초과"); } // 일반 네트워크 I / O 오류 발생시 예외 발생 catch (IOException ioe) {System.err.println ( "Network I / O error-"+ ioe); }

try {}catch 블록에 대해 몇 줄의 추가 코드 만 있으면 네트워크 시간 초과를 포착하기가 매우 쉽습니다. 그런 다음 응용 프로그램은 자체 중단없이 상황에 응답 할 수 있습니다. 예를 들어, 사용자에게 알리거나 새 연결을 시도하여 시작할 수 있습니다. 전달을 보장하지 않고 정보 패킷을 보내는 데이터 그램 소켓을 사용하는 경우 응용 프로그램은 전송 중에 손실 된 패킷을 다시 보내 네트워크 시간 초과에 응답 할 수 있습니다. 이 시간 제한 지원을 구현하는 데는 시간이 거의 걸리지 않으며 매우 깨끗한 솔루션으로 이어집니다. 실제로 nonblocking I / O가 최적의 솔루션이 아닌 유일한 경우는 연결 작업에서 시간 초과를 감지해야하거나 대상 환경이 Java 1.1을 지원하지 않는 경우입니다.

연결 작업에 대한 시간 초과 처리

완전한 시간 초과 감지 및 처리를 목표로하는 경우 연결 작업을 고려해야합니다. 의 인스턴스를 만들 때 java.net.Socket연결 설정을 시도합니다. 호스트 시스템이 활성 상태이지만 java.net.Socket생성자에 지정된 포트에서 실행중인 서비스가 없으면 a ConnectionException가 throw되고 제어가 응용 프로그램으로 반환됩니다. 그러나 시스템이 다운되거나 해당 호스트에 대한 경로가없는 경우 소켓 연결은 결국 훨씬 나중에 자체적으로 시간 초과됩니다. 그 동안 애플리케이션은 고정 된 상태로 유지되며 시간 제한 값을 변경할 방법이 없습니다.

소켓 생성자 호출이 결국 반환되지만 상당한 지연이 발생합니다. 이 문제를 처리하는 한 가지 방법은 잠재적으로 차단 연결을 수행하는 두 번째 스레드를 사용하고 연결이 설정되었는지 확인하기 위해 해당 스레드를 지속적으로 폴링하는 것입니다.

그러나 이것이 항상 우아한 솔루션으로 이어지지는 않습니다. 예, 네트워크 클라이언트를 다중 스레드 응용 프로그램으로 변환 할 수 있지만이를 수행하는 데 필요한 추가 작업의 양이 엄청납니다. 이는 코드를 더 복잡하게 만들고 단순한 네트워크 애플리케이션 만 작성할 때 필요한 노력의 양을 정당화하기 어렵습니다. 많은 네트워크 응용 프로그램을 작성하면 바퀴를 자주 재발 명하게됩니다. 그러나 더 간단한 해결책이 있습니다.

자신의 응용 프로그램에서 사용할 수있는 간단하고 재사용 가능한 클래스를 작성했습니다. 이 클래스는 오랜 시간 동안 중단없이 TCP 소켓 연결을 생성합니다. getSocket호스트 이름, 포트 및 시간 초과 지연을 지정하여 메서드 를 호출 하고 소켓을 받기만하면됩니다. 다음 예는 연결 요청을 보여줍니다.

// 4 초의 시간 제한으로 호스트 이름으로 원격 서버에 연결 Socket connection = TimedSocket.getSocket ( "server.my-network.net", 23, 4000); 

모든 것이 잘되면 표준 java.net.Socket생성자 처럼 소켓이 반환됩니다 . 지정된 시간 초과가 발생하기 전에 연결을 설정할 수없는 경우 메서드를 java.io.InterruptedIOException사용하여 시간 초과가 지정된 경우 다른 소켓 읽기 작업 과 마찬가지로 메서드가 중지되고을 throw합니다 setSoTimeout. 아주 쉬워요?

Encapsulating multithreaded network code into a single class

While the TimedSocket class is a useful component in itself, it's also a very good learning aid for understanding how to deal with blocking I/O. When a blocking operation is performed, a single-threaded application will become blocked indefinitely. If multiple threads of execution are used, however, only one thread need stall; the other thread can continue to execute. Let's take a look at how the TimedSocket class works.

When an application needs to connect to a remote server, it invokes the TimedSocket.getSocket() method and passes details of the remote host and port. The getSocket() method is overloaded, allowing both a String hostname and an InetAddress to be specified. This range of parameters should be sufficient for the majority of socket operations, though custom overloading could be added for special implementations. Inside the getSocket() method, a second thread is created.

The imaginatively named SocketThread will create an instance of java.net.Socket, which can potentially block for a considerable amount of time. It provides accessor methods to determine if a connection has been established or if an error has occurred (for example, if java.net.SocketException was thrown during the connect).

While the connection is being established, the primary thread waits until a connection is established, for an error to occur, or for a network timeout. Every hundred milliseconds, a check is made to see if the second thread has achieved a connection. If this check fails, a second check must be made to determine whether an error occurred in the connection. If not, and the connection attempt is still continuing, a timer is incremented and, after a small sleep, the connection will be polled again.

This method makes heavy use of exception handling. If an error occurs, then this exception will be read from the SocketThread instance, and it will be thrown again. If a network timeout occurs, the method will throw a java.io.InterruptedIOException.

The following code snippet shows the polling mechanism and error-handling code.

for (;;) { // Check to see if a connection is established if (st.isConnected()) { // Yes ... assign to sock variable, and break out of loop sock = st.getSocket(); break; } else { // Check to see if an error occurred if (st.isError()) { // No connection could be established throw (st.getException()); } try { // Sleep for a short period of time Thread.sleep ( POLL_DELAY ); } catch (InterruptedException ie) {} // Increment timer timer += POLL_DELAY; // Check to see if time limit exceeded if (timer > delay) { // Can't connect to server throw new InterruptedIOException ("Could not connect for " + delay + " milliseconds"); } } } 

Inside the blocked thread

While the connection is regularly polled, the second thread attempts to create a new instance of java.net.Socket. Accessor methods are provided to determine the state of the connection, as well as to get the final socket connection. The SocketThread.isConnected() method returns a boolean value to indicate whether a connection has been established, and the SocketThread.getSocket() method returns a Socket. Similar methods are provided to determine if an error has occurred, and to access the exception that was caught.

이러한 모든 메서드 SocketThread는 전용 멤버 변수의 외부 수정을 허용하지 않고 인스턴스에 제어 된 인터페이스를 제공 합니다. 다음 코드 예제는 스레드의 run()메서드 를 보여줍니다 . 소켓 생성자가를 반환 Socket하면 접근 자 메서드가 액세스를 제공하는 전용 멤버 변수에 할당됩니다. 다음에 연결 상태를 쿼리 할 때 SocketThread.isConnected()메서드를 사용하여 소켓을 사용할 수 있습니다. 동일한 기술이 오류를 감지하는 데 사용됩니다. a java.io.IOException가 발견되면 개인 멤버에 저장되며 isError()getException()접근 자 메서드 를 통해 액세스 할 수 있습니다 .