Java 1.7 の適切な機能 ( try-with-resources )を使用して、クライアント/サーバー ソケット通信を実装するための最適なパターンを探しています。すべてのスレッドとリソースが閉じられ、完全に解放されていることを確認する必要があります。効果的な Java /クリーン コード項目と、単純なデバッグ可能性 (各行に 1 つのステートメントのみ) を考慮する必要があります。
フィードバックや議論をいただければ幸いです。
最初のアプローチ
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import org.junit.Test;
public final class ThreadedServerTest1 {
private static final class ThreadedServer implements Runnable {
private final Socket socket;
ThreadedServer(final Socket socket) {
this.socket = socket;
}
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
try (final ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
final Socket socket = serverSocket.accept();
final ThreadedServer server = new ThreadedServer(socket);
final Thread thread = new Thread(server);
thread.start();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
// @formatter:off
try (
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7027552 and
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7013420:
final Socket socket = this.socket;
final InputStream inputStream = socket.getInputStream();
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
final OutputStream outputStream = socket.getOutputStream();
final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
final PrintWriter printWriter = new PrintWriter(outputStreamWriter);
) {
// @formatter:on
final String request = bufferedReader.readLine();
System.out.println(String.format("Received from client: %s", request));
printWriter.println("Hello client!");
printWriter.flush();
} catch (final Exception e) {
e.printStackTrace();
}
}
}
private static final class Client {
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
try {
final InetAddress loopbackAddress = InetAddress.getByName(null);
// @formatter:off
try (
final Socket socket = new Socket(loopbackAddress, port);
final OutputStream outputStream = socket.getOutputStream();
final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
final PrintWriter printWriter = new PrintWriter(outputStreamWriter);
final InputStream inputStream = socket.getInputStream();
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
) {
// @formatter:on
printWriter.println("Hello server!");
printWriter.flush();
final String answer = bufferedReader.readLine();
System.out.println(String.format("Answer from server: %s", answer));
} catch (final IOException e) {
e.printStackTrace();
}
} catch (final UnknownHostException e) {
e.printStackTrace();
}
}
}
@Test
public void test() {
final String[] arguments = new String[] { "9999" };
final Thread tread = new Thread() {
@Override
public void run() {
ThreadedServer.main(arguments);
};
};
tread.start();
Client.main(arguments);
}
}
長所:
- コード行の削減
短所:
- 大規模な try-with-resources ブロック (Eclipse フォーマッターではサポートされていません)
- try-with-resources を使用するために必要な2 番目
Socke
の t インスタンス UnknownHostException
fromによる深いネスティングInetAddress#getByName(null)
2 番目のアプローチ
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import org.junit.Test;
public final class ThreadedServerTest2 {
private static final class Connection implements AutoCloseable {
final Socket socket;
final InputStream inputStream;
final InputStreamReader inputStreamReader;
final BufferedReader bufferedReader;
final OutputStream outputStream;
final OutputStreamWriter outputStreamWriter;
final PrintWriter printWriter;
private Connection(final Socket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(inputStreamReader);
outputStream = socket.getOutputStream();
outputStreamWriter = new OutputStreamWriter(outputStream);
printWriter = new PrintWriter(outputStreamWriter);
}
static Connection serverConnection(final Socket socket) throws IOException {
return new Connection(socket);
}
static Connection clientConnection(final String hostname, final int port) throws IOException {
final InetAddress inetAddress = InetAddress.getByName(hostname);
final Socket socket = new Socket(inetAddress, port);
return new Connection(socket);
}
static Connection localhostCientConnection(final int port) throws IOException {
return clientConnection(null, port);
}
@Override
public void close() {
closeAndIgnoreException(printWriter, outputStreamWriter, outputStream, bufferedReader, inputStreamReader, inputStream, socket);
}
private void closeAndIgnoreException(final AutoCloseable... closeables) {
for (final AutoCloseable closeable : closeables) {
try {
closeable.close();
} catch (final Exception ignore) {
}
}
}
}
private static final class ThreadedServer implements Runnable {
private final Socket socket;
private ThreadedServer(final Socket socket) {
this.socket = socket;
}
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
try (final ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
final Socket socket = serverSocket.accept();
final ThreadedServer server = new ThreadedServer(socket);
final Thread thread = new Thread(server);
thread.start();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try (Connection connection = Connection.serverConnection(socket)) {
final String request = connection.bufferedReader.readLine();
System.out.println(String.format("Received from client: %s", request));
connection.printWriter.println("Hello client!");
connection.printWriter.flush();
} catch (final Exception e) {
e.printStackTrace();
}
}
}
private static final class Client {
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
try (Connection connection = Connection.localhostCientConnection(port)) {
connection.printWriter.println("Hello server!");
connection.printWriter.flush();
final String answer = connection.bufferedReader.readLine();
System.out.println(String.format("Answer from server: %s", answer));
} catch (final IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test() {
final String[] arguments = new String[] { "9999" };
final Thread tread = new Thread() {
@Override
public void run() {
ThreadedServer.main(arguments);
};
};
tread.start();
Client.main(arguments);
}
}
長所:
- きれいで読みやすい try-with-resources ブロック
- Eclipse フォーマッターでのサポート
短所:
- より多くのコード行
- 独自の
Connection
クラスが必要 AutoCloseable
クラス内のインスタンスを閉じるConnection
ことを忘れる可能性があります (エラーが発生しやすい)
長所と短所へのフィードバックや追加はありますか? これ以上の弱点はありますか?
3 番目のアプローチ
それが以下の議論の結果です。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import org.junit.Test;
public final class ThreadedServerTest3 {
private static final class ThreadedServer implements Runnable {
private final Socket socket;
private ThreadedServer(final Socket socket) {
this.socket = socket;
}
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
try (final ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
final Socket socket = serverSocket.accept();
final ThreadedServer server = new ThreadedServer(socket);
final Thread thread = new Thread(server);
thread.start();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
// @formatter:off
try (
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7027552 and
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7013420:
Socket socket = this.socket;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
) {
// @formatter:on
final String request = bufferedReader.readLine();
System.out.println(String.format("Received from client: %s", request));
printWriter.println("Hello client!");
printWriter.flush();
} catch (final Exception e) {
e.printStackTrace();
}
}
}
private static final class Client {
public static void main(final String arguments[]) {
final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
// @formatter:off
try (
Socket socket = new Socket(InetAddress.getByName(null), port);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()))
) {
// @formatter:on
printWriter.println("Hello server!");
printWriter.flush();
final String answer = bufferedReader.readLine();
System.out.println(String.format("Answer from server: %s", answer));
} catch (final IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test() {
final String[] arguments = new String[] { "9999" };
final Thread tread = new Thread() {
@Override
public void run() {
ThreadedServer.main(arguments);
};
};
tread.start();
Client.main(arguments);
}
}
長所:
- コード行の削減
- 複雑で読みやすい try-with-resources ブロックにはノー
- 無視された例外なし
短所:
- Eclipse フォーマッターでのサポートなし
- 「各行に 1 つのステートメントのみ」というルールのいくつかの違反 (単純なデバッグ可能性)