ネットワークタイムアウトの簡単な処理

多くのプログラマーは、ネットワークのタイムアウトを処理するという考えを恐れています。一般的な懸念は、タイムアウトをサポートしない単純なシングルスレッドネットワーククライアントが複雑なマルチスレッドの悪夢に膨れ上がり、ネットワークタイムアウトを検出するために個別のスレッドが必要になり、ブロックされたスレッドとメインアプリケーションの間で何らかの形式の通知プロセスが機能することです。これは開発者にとって1つのオプションですが、それだけではありません。ネットワークタイムアウトの処理は難しい作業である必要はなく、多くの場合、追加のスレッドのコードを書くことを完全に回避できます。

ネットワーク接続または任意のタイプのI / Oデバイスを操作する場合、操作には2つの分類があります。

  • ブロッキング操作:読み取りまたは書き込みストール、操作はI / Oデバイスの準備ができるまで待機します
  • ノンブロッキング操作:読み取りまたは書き込みが試行され、I / Oデバイスの準備ができていない場合は操作が中止されます

Javaネットワーキングは、デフォルトでは、I / Oをブロックする形式です。したがって、Javaネットワークアプリケーションがソケット接続から読み取る場合、即時の応答がない場合、通常は無期限に待機します。利用可能なデータがない場合、プログラムは待機し続け、それ以上の作業は実行できません。問題を解決するが少し複雑さを増す1つの解決策は、2番目のスレッドに操作を実行させることです。このようにして、2番目のスレッドがブロックされた場合でも、アプリケーションはユーザーコマンドに応答したり、必要に応じて停止したスレッドを終了したりすることができます。

このソリューションはよく使用されますが、はるかに簡単な代替手段があります。 JavaSocketServerSocket、任意の、、、またはでアクティブ化できるノンブロッキングネットワークI / OもサポートしていますDatagramSocket。制御をアプリケーションに戻す前に、読み取りまたは書き込み操作が停止する最大時間を指定することができます。ネットワーククライアントの場合、これは最も簡単なソリューションであり、よりシンプルで管理しやすいコードを提供します。

JavaでのノンブロッキングネットワークI / Oの唯一の欠点は、既存のソケットが必要になることです。したがって、この方法は通常の読み取りまたは書き込み操作には最適ですが、接続操作のタイムアウト期間を指定する方法がないため、接続操作ははるかに長い期間停止する可能性があります。多くのアプリケーションはこの機能を必要とします。ただし、追加のコードを作成するという余分な作業を簡単に回避できます。接続のタイムアウト値を指定できる小さなクラスを作成しました。2番目のスレッドを使用しますが、内部の詳細は抽象化されています。このアプローチは、ノンブロッキングI / Oインターフェイスを提供し、2番目のスレッドの詳細が表示されないため、うまく機能します。

ノンブロッキングネットワークI / O

何かをする最も簡単な方法が最良の方法であることがよくあります。スレッドとブロッキングI / Oを使用する必要がある場合もありますが、ほとんどの場合、ノンブロッキングI / Oは、はるかに明確で洗練されたソリューションに役立ちます。ほんの数行のコードで、任意のソケットアプリケーションのタイムアウトサポートを組み込むことができます。私を信じないの?読む。

Java 1.1がリリースされたとき、java.netプログラマーがソケットオプションを指定できるようにするパッケージへのAPIの変更が含まれていました。これらのオプションにより、プログラマーはソケット通信をより細かく制御できます。特に1つのオプションは、SO_TIMEOUTプログラマーが読み取り操作がブロックする時間を指定できるため、非常に便利です。短い遅延を指定することも、まったく指定しないこともでき、ネットワークコードを非ブロッキングにすることができます。

これがどのように機能するかを見てみましょう。setSoTimeout ( int )次のソケットクラスに新しいメソッドが追加されました。

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

このメソッドを使用すると、次のネットワーク操作がブロックする最大タイムアウト長をミリ秒単位で指定できます。

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

これらのメソッドのいずれかが呼び出されると、時計が刻み始めます。操作がブロックされていない場合、操作はリセットされ、これらのメソッドの1つが再度呼び出されたときにのみ再起動します。その結果、ネットワークI / O操作を実行しない限り、タイムアウトが発生することはありません。次の例は、複数の実行スレッドに頼ることなく、タイムアウトを処理することがいかに簡単であるかを示しています。

//ポート2000にデータグラムソケットを作成して、着信UDPパケットをリッスンしますDatagramSocket dgramSocket = new DatagramSocket(2000); // 5秒のタイムアウトを指定して、ブロックI / O操作を無効にしますdgramSocket.setSoTimeout(5000);

タイムアウト値を割り当てると、ネットワーク操作が無期限にブロックされるのを防ぐことができます。この時点で、ネットワーク操作がタイムアウトしたときに何が起こるのか疑問に思われるかもしれません。開発者が常にチェックするとは限らないエラーコードを返すのではなく、ajava.io.InterruptedIOExceptionがスローされます。例外処理はエラー状態を処理する優れた方法であり、通常のコードをエラー処理コードから分離することができます。さらに、null参照のすべての戻り値を宗教的にチェックするのは誰ですか?例外をスローすることにより、開発者はタイムアウトのキャッチハンドラーを提供することを余儀なくされます。

次のコードスニペットは、TCPソケットから読み取るときにタイムアウト操作を処理する方法を示しています。

//ソケットタイムアウトを10秒に設定しますconnection.setSoTimeout(10000); try {//ソケットから読み取るためのDataInputStreamを作成しますDataInputStreamdin = new DataInputStream(connection.getInputStream()); //(;;){String line = din.readLine();のデータが終了するまでデータを読み取ります if(line!= null)System.out.println(line); そうでなければ壊れます。}} //ネットワークタイムアウトが発生したときにスローされる例外catch(InterruptedIOException iioe){System.err.println( "読み取り操作中にリモートホストがタイムアウトしました"); } //一般的なネットワークI / Oエラーが発生したときにスローされる例外catch(IOException ioe){System.err.println( "ネットワークI / Oエラー-" + ioe); }

try {}catchブロックのコードを数行追加するだけで、ネットワークのタイムアウトを簡単にキャッチできます。その後、アプリケーションはそれ自体を停止させることなく状況に対応できます。たとえば、ユーザーに通知したり、新しい接続を確立しようとしたりすることから始めることができます。配信を保証せずに情報のパケットを送信するデータグラムソケットを使用する場合、アプリケーションは、転送中に失われたパケットを再送信することにより、ネットワークタイムアウトに応答できます。このタイムアウトサポートの実装にはほとんど時間がかからず、非常にクリーンなソリューションにつながります。実際、ノンブロッキングI / Oが最適なソリューションではないのは、接続操作のタイムアウトも検出する必要がある場合、またはターゲット環境がJava1.1をサポートしていない場合のみです。

接続操作のタイムアウト処理

If your goal is to achieve complete timeout detection and handling, then you'll need to consider connect operations. When creating an instance of java.net.Socket, an attempt to establish a connection is made. If the host machine is active, but no service is running on the port that is specified in the java.net.Socket constructor, a ConnectionException will be thrown and control will return to the application. However, if the machine is down, or if there is no route to that host, the socket connection will eventually time out on its own much later. In the meantime, your application remains frozen, and there is no way to change the timeout value.

Though the socket constructor call will eventually return, it introduces a significant delay. One way of dealing with this problem is to employ a second thread, which will perform the potentially blocking connect, and to continually poll that thread to see if a connection has been established.

This does not, however, always lead to an elegant solution. Yes, you could convert your network clients into multithreaded applications, but often the amount of extra work required to do this is prohibitive. It makes the code more complex, and when writing only a simple network application, the amount of effort required is difficult to justify. If you write a lot of network applications, you'd find yourself reinventing the wheel frequently. There is, however, a simpler solution.

I've written a simple, reusable class that you can use in your own applications. The class generates a TCP socket connection without stalling for long time periods. You simply call a getSocket method, specifying the hostname, port, and timeout delay, and receive a socket. The following example shows a connection request:

// Connect to a remote server by hostname, with a four second timeout Socket connection = TimedSocket.getSocket("server.my-network.net", 23, 4000); 

If all goes well, a socket will be returned, just like the standard java.net.Socket constructors. If the connection cannot be established before your specified timeout occurs, the method will stop, and will throw an java.io.InterruptedIOException, just as other socket-read operations would when a timeout has been specified using a setSoTimeout method. Pretty easy, huh?

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()メソッドを使用して、ソケットを使用できるようになります。同じ手法を使用してエラーを検出します。ajava.io.IOExceptionがキャッチされると、プライベートメンバーに格納され、isError()およびgetException()アクセサメソッドを介してアクセスできます。