我实现了一个Java多线程服务器,它接收来自客户端的消息并将它们广播给其他客户端,但它无法正常工作。仅当客户端应用程序关闭(客户端套接字关闭)时,服务器才会收到客户端消息。该应用程序分为两个模块:client
和server
。代码有点长,我知道读取所有内容很烦人,但请帮我解决这个问题。
Here是GitHub应用程序链接,便于阅读。 请结帐test
分行。
server
模块类:
GameServer.java
/*
* This file contains the application core server responsible to wait and accept clients connections requests.
* */
package server;
import com.sun.istack.internal.NotNull;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Michael Pacheco <b>pacheco@decom.ufop.br</b>
* @version 1.0
*
* This class is responsible to receive clients connections and send messages to them.
* */
public class GameServer implements Runnable {
/**
* The port number used to start the server.
* */
private int port;
/**
* The socket used to accept clients connections.
* */
private ServerSocket serverSocket;
/**
* A {@link Logger} used to print messages for debug purpose.
* */
private final static Logger LOGGER = Logger.getLogger(GameServer.class.getName());
/**
* A hash set to store the clients sockets
* */
private HashSet<Socket> clientsSockets;
private GameServer() {
clientsSockets = new HashSet<>();
}
/**
* Instantiates a new {@link GameServer} with a given port number.
* @param port the port number used to start the server.
* */
public GameServer(int port) {
this();
this.port = port;
}
/**
* Override method from Runnable. This method is called when an attempt to close the application occur.
* */
@Override
public void run() {
Scanner s = new Scanner(System.in);
while (s.hasNext()) s.nextLine();
shutdown();
}
/**
* Start the server and listen for clients connections requests.
* */
public void start () {
try {
LOGGER.log(Level.INFO, "Trying to start the server...\n");
serverSocket = new ServerSocket(this.port);
final String ip = InetAddress.getLocalHost().getHostAddress();
LOGGER.log(Level.INFO, "Server started!\n\tPort: {0}\n\t IP: {1}\n", new Object[] {port, ip});
LOGGER.log(Level.INFO, "Press Ctrl-D to shutdown the server!\n");
waitForConnections();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to initialize the server! {0}\n", e.getMessage());
e.printStackTrace();
}
}
/**
* Wait for clients connections requests
* */
private void waitForConnections() {
new Thread(this).start();
try {
//noinspection InfiniteLoopStatement
while (true) {
Socket clientSocket = serverSocket.accept();
LOGGER.log(Level.INFO, "New client connected! {0}\n", clientSocket);
clientSocket.getOutputStream().write("You're now connected to the server\n".getBytes());
clientSocket.getOutputStream().flush();
allocateClient(clientSocket);
}
} catch (IOException e) {
// No need for printing stacktrace if the serverSocket was closed by the shutdown method
if (!serverSocket.isClosed())
e.printStackTrace();
}
}
/**
* This method is responsible to delegate the communication with the client to the {@link ClientListener}.
* @param clientSocket the client socket to delegate.
* */
private void allocateClient(@NotNull Socket clientSocket) {
clientsSockets.add(clientSocket);
new Thread(new ClientListener(clientSocket, this)).start();
}
/**
* Shutdown the server
* */
private void shutdown () {
try {
LOGGER.log(Level.INFO, "Trying to shutdown the server...\n");
// TODO Clear resources
for (Socket soc : clientsSockets) removeClient(soc);
serverSocket.close();
LOGGER.log(Level.INFO, "Server successfully shut down!\n");
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to shutdown the server! {0}\n", e.getMessage());
e.printStackTrace();
}
}
/**
* Send a message to a single client.
* @param message the message to be sent.
* @param clientSocket the socket of the client that will receive the message
* */
private void sendMessage (@NotNull Object message, @NotNull Socket clientSocket) {
try (PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
writer.println(message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Send a message to all clients except the given one.
* @param message the message to be sent.
* @param excludedClient the client that won't receive the message.
* */
void broadcast (@NotNull Object message, @NotNull Socket excludedClient) {
for (Socket client : clientsSockets) {
if (excludedClient == client)
continue;
sendMessage(message, client);
}
}
/**
* Remove the given client from server.
* @param clientSocket the client to be removed.
* */
void removeClient (@NotNull Socket clientSocket) {
try {
clientSocket.close();
clientsSockets.remove(clientSocket);
LOGGER.log(Level.INFO, "Client removed! {0}\n", clientSocket);
// TODO broadcast the client disconnection
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientListener.java
package server;
import com.sun.istack.internal.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Michael Pacheco <b>pacheco@decom.ufop.br</b>
* @version 1.0
*
* This class is responsible to listen messages of a single client and send them to the server and then to the other clients.
* */
public class ClientListener implements Runnable {
/**
* The socket used to communicate with the delegated client.
* */
private Socket clientSocket;
/**
* A reference to the {@link GameServer} used to call the {@link GameServer} broadcast method.
* @see GameServer
* */
private GameServer server;
/**
* A {@link Logger} used to print messages for debug purpose.
* */
private final static Logger LOGGER = Logger.getLogger(ClientListener.class.getName());
/**
* Instantiate a new {@link ClientListener} with a given client socket.
*
* @param clientSocket the socket of the delegated client.
* @param server the server reference used to call the broadcast method.
* */
ClientListener(@NotNull Socket clientSocket, @NotNull GameServer server) {
this.clientSocket = clientSocket;
this.server = server;
}
/**
* Listen for client messages and send it to the server.
* */
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
String message;
while ((message = reader.readLine()) != null) {
// send received message to the server
LOGGER.log(Level.INFO, "Message received!\n\t From: {0}\n\tMessage: {1}\n",
new Object[]{clientSocket, message});
server.broadcast(message, clientSocket);
}
} catch (IOException e) {
if (!clientSocket.isClosed())
e.printStackTrace();
} finally {
// send the client to server to be disconnected
server.removeClient(clientSocket);
}
}
}
client
模块类:
GameClient.java
package client;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GameClient {
public static void main(String[] args) {
final String serverAddress = args.length == 2 ? args[0] : "localhost";
final int port = args.length == 2 ? Integer.parseInt(args[1]) : 5000;
final Logger LOGGER = Logger.getLogger(GameClient.class.getName());
try {
Socket serverSocket = new Socket(serverAddress, port);
LOGGER.log(Level.INFO, "Connection successful! {0}\n", serverSocket);
new Thread(new ClientWriterThread(serverSocket)).start();
new Thread(new ClientReaderThread(serverSocket)).start();
} catch (IOException e) {
LOGGER.log(Level.SEVERE,"Failed to connect with the server\n", e);
e.printStackTrace();
}
}
}
ClientWriterThread.java
package client;
import com.sun.istack.internal.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author Michael Pacheco
* @version 1.0
* This class is responsible to read client messages and send it to the server.
* */
public class ClientWriterThread implements Runnable {
/**
* The socket used to send messages to the server.
* */
private Socket serverSocket;
/**
* Instantiate a new {@link ClientReaderThread} with a given server socket.
* @param serverSocket the socket used to send messages to the server.
* */
ClientWriterThread(@NotNull Socket serverSocket) {
this.serverSocket = serverSocket;
}
/**
* Read messages typed by the client and send it to the server.
* */
@Override
public void run() {
try {Thread.sleep(1000);}
catch (InterruptedException e) {e.printStackTrace();}
BufferedReader keyboardReader = null;
try (PrintWriter socketWriter = new PrintWriter(serverSocket.getOutputStream(), true)) {
keyboardReader = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = keyboardReader.readLine()) != null) socketWriter.write(line);
} catch (IOException e) {
if (!serverSocket.isClosed())
e.printStackTrace();
} finally {
try {
if (keyboardReader != null) keyboardReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ClientReaderThread.java
package client;
import com.sun.istack.internal.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author Michael Pacheco
* @version 1.0
* This class is responsible to read messages sent by the server and show them to the client.
*/
public class ClientReaderThread implements Runnable {
/**
* The socket used to read messages sent by the server.
*/
private Socket serverSocket;
ClientReaderThread(@NotNull Socket serverSocket) {
this.serverSocket = serverSocket;
}
/**
* Read messages sent by the server.
* */
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()))) {
String message;
while ((message = reader.readLine()) != null) System.out.println(message);
} catch (IOException e) {
if (!serverSocket.isClosed())
e.printStackTrace();
}
}
}
答案 0 :(得分:1)
请确保在{em> ClientWriterThread.java 中的每个PrintWriter.flush()
之后write()
发送您发送的邮件。
作为旁注,为了使代码更清晰,请将相应类中的serverSocket
变量名更改为clientSocket
( GameClient.java 和 ClientWriterThread的.java )。
答案 1 :(得分:1)
您将PrintWriter与autoflush一起使用,但不使用println(...)或format(...)。 .write()不受autoflush的约束。
仅供参考,clientsSockets不是线程安全的,您可以在不同的线程上添加/删除。不一定是bug,但要小心。此外,在不保留引用的情况下旋转非守护程序线程是有风险的;总是保持线程受到控制,并且不要对interrupt()/ InterruptedException / InterruptedIOException ...
计数太多