Java 套接字未从服务器接收或向服务器发送消息

Java 套接字未从服务器接收或向服务器发送消息,java,multithreading,sockets,server,io,Java,Multithreading,Sockets,Server,Io,我正在实现一个Java多线程服务器,它接收来自客户机的消息并将它们广播到其他客户机,但它不起作用。服务器仅在客户端应用程序关闭(客户端套接字关闭)时接收客户端消息。该应用程序分为两个模块:客户端和服务器。代码有点长,我知道读起来很烦人,但是请帮我解决这个问题 是GitHub应用程序链接,以方便阅读请签出测试分支。 服务器模块类: GameServer.java /* * This file contains the application core server responsible to

我正在实现一个Java多线程服务器,它接收来自客户机的消息并将它们广播到其他客户机,但它不起作用。服务器仅在客户端应用程序关闭(客户端套接字关闭)时接收客户端消息。该应用程序分为两个模块:
客户端
服务器
。代码有点长,我知道读起来很烦人,但是请帮我解决这个问题

是GitHub应用程序链接,以方便阅读请签出
测试
分支。


服务器
模块类:

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();
        }
    }
}
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);
        }
    }
}
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();
        }
    }
}
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();
            }
        }
    }
}
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();
        }
    }
}
客户端
模块类:

GameClient.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();
        }
    }
}
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);
        }
    }
}
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();
        }
    }
}
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();
            }
        }
    }
}
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();
        }
    }
}
ClientWriterThread.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();
        }
    }
}
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);
        }
    }
}
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();
        }
    }
}
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();
            }
        }
    }
}
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();
        }
    }
}
ClientReaderThread.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();
        }
    }
}
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);
        }
    }
}
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();
        }
    }
}
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();
            }
        }
    }
}
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();
        }
    }
}

确保在ClientWriterThread.java中每次
write()
之后发送的消息都
PrintWriter.flush()


作为旁注,为了使代码更清晰,请在适当的类(GameClient.java和ClientWriterThread.java)中将
serverSocket
变量名更改为
clientSocket

确保
PrintWriter.flush()
在ClientWriterThread.java中每次
write()
之后发送的消息


作为旁注,为了使代码更清晰,请在适当的类(GameClient.java和ClientWriterThread.java)中将
serverSocket
变量名更改为
clientSocket

您将PrintWriter与autoflush一起使用,但不使用println(…)或format(…)。.write()不受自动刷新的约束


仅供参考,clientsSockets不是线程安全的,您可以在不同的线程上添加/删除。不一定是错误,但要小心。而且,在不保留引用的情况下旋转非守护进程线程是有风险的;始终控制线程,不要过多地依赖中断()/InterruptedException/InterruptedIOException…

您使用带有自动刷新的PrintWriter,但不使用println(…)或format(…)。.write()不受自动刷新的约束


仅供参考,clientsSockets不是线程安全的,您可以在不同的线程上添加/删除。不一定是错误,但要小心。而且,在不保留引用的情况下旋转非守护进程线程是有风险的;始终控制线程,不要过多地依赖中断()/InterruptedException/InterruptedIOException…

但是我用
autoFlush
标记和
true
实例化了
PrintWriter
,因此,所有
write()
调用都是自动刷新的。我不知道这个标记。我当时没有任何线索。GameClient.java变量中的
serverSocket
,之所以命名为so,是因为返回的套接字对应于服务器端套接字。在ClientWriterThread中,我将变量命名为
serverSocket
,因为这个套接字用于与服务器通信,而不是客户端。实际上,我的线索只是一个猜测,我现在写在这里:尝试避免将InputStreamReader包装到BufferedReader中。哦,我的糟糕。。。我没有很好地阅读文档。flush标志仅用于调用
println()
printf()
而不用于
write()
。也许这就是问题所在。但是我用
true
autoFlush
标志实例化了
PrintWriter
,所以所有
write()
调用都是自动刷新的。我不知道这个标志。我当时没有任何线索。GameClient.java变量中的
serverSocket
,之所以命名为so,是因为返回的套接字对应于服务器端套接字。在ClientWriterThread中,我将变量命名为
serverSocket
,因为这个套接字用于与服务器通信,而不是客户端。实际上,我的线索只是一个猜测,我现在写在这里:尝试避免将InputStreamReader包装到BufferedReader中。哦,我的糟糕。。。我没有很好地阅读文档。flush标志仅用于调用
println()
printf()
而不用于
write()
。也许这就是问题所在。