套接字未从/向服务器

时间:2018-04-14 19:20:26

标签: java multithreading sockets server io

我实现了一个Java多线程服务器,它接收来自客户端的消息并将它们广播给其他客户端,但它无法正常工作。仅当客户端应用程序关闭(客户端套接字关闭)时,服务器才会收到客户端消息。该应用程序分为两个模块:clientserver。代码有点长,我知道读取所有内容很烦人,但请帮我解决这个问题。

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();
        }
    }
}

2 个答案:

答案 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 ...

计数太多