因此,我刚刚编写了一个框架,它应该使使用本机java.net包创建基于网络的应用程序(客户机-服务器)变得更容易。我想知道我的代码的结构是否可以理解,以及是否有方法改进我的代码(在可读性和性能方面)。我提供了一个UML图,以便更容易地获得我编写的代码的快速概述:

下面是这些类的来源:
package com.joshuafeld.net;
import java.io.IOException;
import java.net.Socket;
/**
* A client can connect to a running server using native Java sockets. It can perform requests
* and perform actions based on potential requests it can get from the server.
*/
public abstract class AbstractClient extends AbstractSocketWrapper {
/**
* Creates a new client with a socket that is connected to the specified port number on the
* named host.
*
* <p>If the specified host is {@code null} it is the equivalent of specifying the address as
* {@link java.net.InetAddress#getByName(String) InetAddress.getByName}{@code (null)}. In
* other words, it is equivalent to specifying an address of the loopback interface.
*
* @param address The host name, or {@code null} for the loopback address.
* @param port The port number.
*
* @throws IOException If an I/O error occurs when creating the underlying socket.
*/
public AbstractClient(final String address, final int port) throws IOException {
super(new Socket(address, port));
new Thread(this).start();
}
/**
* This is just used to guarantee consistency in the client-server system. It calls the
* method {@link #handleMessage(String) handleMessage} which then can be extended by a subclass.
* This is created in the hope of making the system more understandable for a new user.
*
* @param message The message to handle.
*/
@Override
/* default */ void handle(final String message) {
handleMessage(message);
}
/**
* Handles an incoming message from the server.
*
* @param message The incoming message.
*/
public abstract void handleMessage(String message);
}package com.joshuafeld.net;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;
/**
* A server that is based on the native Java sever socket implementation. A server waits for
* requests to come in over the network. It performs some operation based on that request, and then
* possibly returns a result to the requester.
*/
public abstract class AbstractServer {
/**
* Logs messages to the console.
*/
private static final Logger logger = LoggerFactory.getLogger(AbstractServer.class);
/**
* Waits for requests to come in over the network.
*/
private ServerSocket serverSocket;
/**
* The currently open socket connections.
*/
private final List<ConnectionSocket> sockets;
/**
* Creates a server with a server socket, bound to the specified port. A port number of
* {@code 0} means that the port number is automatically allocated, typically from an ephemeral
* port range. This port number can then be retrieved by calling {@link #getPort() getPort}.
*
* @param port The port number, or {@code 0} to use a port number that is
* automatically allocated.
*
* @throws IllegalArgumentException If the port parameter is outside the specified range of
* valid port values which is between 0 and 65535, inclusive.
*/
public AbstractServer(final int port) {
if (port < 0 || port > 0xFFFF) {
throw new IllegalArgumentException("Port value out of range: " + port);
}
sockets = new LinkedList<>();
try {
serverSocket = new ServerSocket(port);
} catch (final IOException exception) {
logger.error("An I/O error occurred while opening the server socket", exception);
}
new Thread(new ConnectionAcceptor()).start();
}
/**
* Closes the server socket as well as all connected sockets.
*/
public void close() {
try {
serverSocket.close();
} catch (final IOException exception) {
logger.error("An I/O error occurred while closing the server socket", exception);
}
sockets.forEach(this::close);
}
/**
* Closes the specified socket and removes it from the list of currently connected sockets.
*
* @param socket The socket to close.
*/
public void close(final ConnectionSocket socket) {
handleDisconnect(socket);
sockets.remove(socket);
socket.close();
}
/**
* Broadcasts a message to all connected sockets/clients.
*
* @param message The message to broadcast.
*/
public void broadcast(final String message) {
sockets.forEach(socket -> socket.send(message));
}
/**
* Handles an incoming message from the specified socket.
*
* @param socket The socket at which the message arrived.
* @param message The message.
*/
public abstract void handleMessage(ConnectionSocket socket, String message);
/**
* Handles a newly connected socket.
*
* @param socket The newly connected socket.
*/
public abstract void handleConnect(ConnectionSocket socket);
/**
* Handles a disconnected/closed socket.
*
* @param socket The closed socket.
*/
public abstract void handleDisconnect(ConnectionSocket socket);
/**
* Returns the IP address to which the server socket is bound.
*
* <p>If the server socket was bound prior to being {@link #close() closed}, then this method
* will continue to return the IP address after the server socket is closed.
*
* @return The IP address to which the server socket is bound.
*/
public String getAddress() {
return serverSocket.getInetAddress().getHostAddress();
}
/**
* Returns the port number on which the server socket is listening.
*
* <p>If the server socket was bound prior to being {@link #close() closed}, then this method
* will continue to return the port number after the server socket is closed.
*
* @return The port number on which the server socket is listening or -1 if the socket is not
* bound yet.
*/
public int getPort() {
return serverSocket.getLocalPort();
}
/**
* Listens for a connection to be made to the server socket and accepts it.
*/
private class ConnectionAcceptor implements Runnable {
/**
* Creates a new connection acceptor.
*/
private ConnectionAcceptor() {
// empty on purpose
}
/**
* Listens for a connection to be made to the server socket and accepts it. The method
* blocks until a connection is made or the server is {@link #close() closed}.
*/
@Override
public void run() {
while (!serverSocket.isClosed()) {
try {
final ConnectionSocket socket = new ConnectionSocket(serverSocket.accept());
handleConnect(socket);
new Thread(socket).start();
sockets.add(socket);
} catch (final IOException exception) {
logger.error("An I/O error occurred while waiting for a connection", exception);
}
}
}
}
/**
* The socket that represents the connection to a specific client.
*/
public class ConnectionSocket extends AbstractSocketWrapper {
/**
* Creates a new wrapped socket. The socket has to be connected already.
*
* @param socket The socket to wrap.
*/
public ConnectionSocket(final Socket socket) {
super(socket);
}
/**
* Passes the handling of the message on to the method
* {@link #handleMessage(ConnectionSocket, String) handleMessage}.
*
* @param message The message to handle.
*/
@Override
void handle(final String message) {
handleMessage(this, message);
}
}
}package com.joshuafeld.net;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.net.Socket;
/**
* A simple wrapper for the native Java {@link Socket Socket} that automatically handles messages
* as soon as they are received by the underlying socket.
*/
public abstract class AbstractSocketWrapper implements Runnable {
/**
* Logs messages to the console.
*/
private static final Logger logger = LoggerFactory.getLogger(AbstractSocketWrapper.class);
/**
* The underlying socket/communication endpoint.
*/
private final Socket socket;
/**
* Reads incoming messages from the input stream.
*/
private BufferedReader input;
/**
* Writes messages to the output stream.
*/
private PrintWriter output;
/**
* Creates a new wrapped socket. The socket has to be connected already.
*
* @param socket The socket to wrap.
*/
public AbstractSocketWrapper(final Socket socket) {
this.socket = socket;
try {
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output = new PrintWriter(socket.getOutputStream(), true);
} catch (final IOException exception) {
logger.error("An I/O error occurred while creating the input/output stream or the "
+ "socket is not connected", exception);
}
}
/**
* Listens for new messages on the input stream and passes them on to the
* {@link #handle(String) handle} method for further handling.
*/
@Override
public void run() {
while (!socket.isClosed()) {
try {
input.lines().forEach(this::handle);
} catch (final UncheckedIOException exception) {
// This will happen if the input stream is closed. Because this is natural behavior
// and not really an error, we will just ignore this and not log it into the
// console.
break;
}
}
}
/**
* Sends a message to the connected socket. The message is terminated by a newline.
*
* @param message The message to send.
*/
public void send(final String message) {
if (!socket.isClosed()) {
output.println(message);
}
}
/**
* Closes the socket as well as the input reader and output writer.
*
* <p>Once a socket has been closed, it is not available for further networking use (i.e. can't
* be reconnected or rebound). A new socket needs to be created.
*/
public void close() {
try {
socket.close();
input.close();
} catch (final IOException exception) {
logger.error("An I/O error occurred while closing the socket/input", exception);
}
output.close();
}
/**
* Handles a received message.
*
* @param message The message to handle.
*/
/* default */ abstract void handle(final String message);
}使用以下代码的简单日间服务器/客户端示例:
package com.joshuafeld.net.daytime;
import com.joshuafeld.net.AbstractServer;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DaytimeServer extends AbstractServer {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
public DaytimeServer() {
super(13);
}
@Override
public void handleConnect(final ConnectionSocket socket) {
socket.send(DATE_FORMAT.format(new Date()));
socket.close();
}
@Override
public void handleMessage(final ConnectionSocket socket, final String message) {
// nothing to do here
}
@Override
public void handleDisconnect(final ConnectionSocket socket) {
// nothing to do here
}
public static void main(String[] args) {
new DaytimeServer();
}
}package com.joshuafeld.net.daytime;
import com.joshuafeld.net.AbstractClient;
import java.io.IOException;
public class DaytimeClient extends AbstractClient {
public DaytimeClient(String address) throws IOException {
super(address, 13);
}
@Override
public void handleMessage(final String message) {
System.out.println(message);
close();
}
public static void main(String[] args) throws IOException {
new DaytimeClient("localhost");
}
}发布于 2019-08-19 14:39:47
我翻阅了你的个人资料,看到你是一个17岁的开发人员。我不知道那个年纪的OO是什么。9~10成熟。我这样做是因为我觉得你是一位经验丰富的专业人士。
IOException。这将使套接字处于损坏状态。在堆栈上抛出任何异常,让更高级别的类决定如何处理损坏的套接字连接。run可以在套接字被优雅地关闭时( !socket.isClosed()或其他方式)中止:UncheckedIOException。但是容器类没有收到有关这方面的通知。也许你应该举办一个活动。如果没有,容器类应该定期检查是否存在挥之不去的套接字连接并关闭它们。send打印友好消息(带有新行)。这意味着API只能用于人类可读的消息,这限制了它的可用性。这种方法中的警卫也没有那么有用。断开连接的套接字应该被正确地处理,保护程序不能保证调用成功。close看起来很健壮,但是当socket.close();失败时,input.close();不会被调用,可能会导致不必要的行为(内存泄漏?)。run的实现不当。在handleConnect(socket);之前执行sockets.add(socket);。正如您在示例服务器中所看到的,服务器可以关闭handleConnect中的套接字。这意味着,即使套接字已经关闭,它也会被添加。close不具有鲁棒性。如果任何套接字未能关闭sockets.forEach(this::close);,其他套接字将不会关闭,从而导致持续的套接字连接。broadcast存在相同的问题,即向单个客户端的单播失败会阻止将其他客户端发送到。https://codereview.stackexchange.com/questions/226425
复制相似问题