我正在寻找使用 Java 1.7 ( try-with-resources ) 的适当特性来实现客户端/服务器套接字通信的最佳模式。必须确保所有线程和资源都完全关闭和释放。应考虑有效的 Java / Clean Code项以及简单的可调试性(每行中只有一个语句)。
任何反馈或讨论将不胜感激。
第一种方法
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 格式化程序不支持)
Socke
使用 try-with-resources 所需的第二个t 实例- 由于
UnknownHostException
from的深层嵌套InetAddress#getByName(null)
第二种方法
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
(容易出错)
您对利弊有反馈或补充吗?还有更多的弱点吗?
第三种方法
这是下面讨论的结果:
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 格式化程序不支持
- 一些违反规则“每行只有一个语句”(简单可调试性)