简体   繁体   中英

Multithreading/IO Stream Issues in a Java TCP Chat Client

I have a (you guessed it) homework assignment in Java that has been driving me up the wall. I am making a chat client based on TCP that will eventually use Swing as a GUI, but right now I'm just trying to get the thing working on a CLI.

I have marked in the code (ClientHandler) where the server seems to hang. I'm sure it's a problem of IO streams, but as to what exactly, I'm a bit stumped.

The program should add a client when the client enters his/her username (on the same port, or else the program blows up (I haven't gotten that far yet)), but the server thread hangs and doesn't actually add the client.

The input stream at the marked bit of code doesn't seem to work. At all. I'm still kinda new to the whole ObjectInputStream/OutputStream bit (okay, streams in general aren't my specialty).

I have a Swing window made in WindowBuilderPro3 that I will implement later, but again, I'm just trying to get the model working.

I have searched the many similar questions here --and they have helped-- but I am still stumped. If someone can point me in the right direction (even to an answered question that does a good job answering my question), I would be most grateful. Thank you all very much.

Note: I removed imports and some comments as well as the very simple Message and User classes (they just hold (respectively) from username, to username, timestamp, message body (all Strings); username, socket) to meet the char limit

Server:

/**
 * Defines the server of a TCP-based chat program
 */
public class TCPChatServer
{
    private static final Logger _LOG = Logger.getLogger( TCPChatServer.class );

    // Default value for the server port
    private static final int DEFAULT_PORT = 5555;

    /**
     * Main method
     * @param args
     * @throws IOException
     */
    public static void main( String[] args ) throws IOException
    {
        _LOG.trace( "Entering main()" );
        _LOG.info( "args == " + args );

        // Set up the server for accepting clients
        ServerSocket serverSocket = setupServer( args );

        while( true )
        {
            // Wait for a client to connect to the server
            Socket socket = serverSocket.accept();
            _LOG.debug( "socket == " + socket.toString() );

            // When a client connects, display their info
            // TODO this is really only for testing
            printClientSocketInfo(socket);

            // Start the clientHandler thread for each client
            ClientHandler clientHandler = new ClientHandler( socket );
            _LOG.debug( "clientHandler == " + clientHandler.toString() );
            clientHandler.start();
        }
    }

    /**
     * Prints information of the client based on the client socket
     * NOTE: this will be removed in the final version and is being used for
     * testing purposes
     */
    private static void printClientSocketInfo( Socket socket )
    {
        _LOG.trace( "Entering printClientSocketInfo()" );

        // Get the IP address
        InetAddress clientAddress = socket.getInetAddress();
        _LOG.info( "clientAddress == " + clientAddress );

        // Get the port that the connection came from
        int clientPort = socket.getPort();
        System.out.println( "Adding client at port:[" + clientPort + "]" );
        _LOG.info( "clientPort == " + clientPort );

        // Print out client address and hostname
        System.out.println( "Client IP Address:[" + clientAddress
                + "], hostname:[" + clientAddress.getHostName() + "]" );
        _LOG.info( "hostName == " + clientAddress.getHostName() );

        _LOG.trace( "Exiting printClientSocketInfo()" );
    }

    /**
     * Starts the server up based on port number given on the command line
     */
    private static ServerSocket setupServer( String[] args ) throws IOException
    {
        _LOG.trace( "Entering setupServer()" );

        // The default port
        int serverPort = DEFAULT_PORT;

        // Get the port to use from the command line
        if( args.length > 0 )
        {
            serverPort = Integer.valueOf( args[0] ).intValue();
            System.out.println( "Server using port=[" + serverPort + "]" );
        }

        // Otherwise, use the default port
        else
        {
            System.out.println( "Server using port=[" + serverPort + "]" );
        }
        _LOG.info( "serverPort == " + serverPort );

        // Open a new server socket on the specified port
        ServerSocket serverSocket = new ServerSocket( serverPort );
        _LOG.debug( "serverSocket == " + serverSocket );

        // Print a status message TODO again, really only for testing
        System.out.println( "Server loaded" );
        System.out.println();

        _LOG.trace( "Exiting setupServer()" );
        return serverSocket;
    }

}

/**
 * A thread class that handles the I/O of a client
 */
class ClientHandler extends Thread
{
    private static final Logger _LOG = Logger.getLogger( ReceiverThread.class );

    // Tag for joining users
    private static final String JOIN_TAG = ".JOIN";

    // Tag for client exiting the server
    private static final String EXIT_TAG = ".EXIT";

    // Tag for all users
    private static final String ALL_CLIENTS = ".ALL";

    // Make a set of the Users on the server
    private static Set<User> userSet = new HashSet<User>();

    // The socket the client is connected to
    private Socket _socket = new Socket();

    // Constructor
    public ClientHandler( Socket socket )
    {
        _LOG.info( "Constructing ClientHandler" );
        _socket = socket;
        _LOG.debug( "socket == " + socket );
    }

    @Override
    public void run()
    {
        _LOG.trace( "Entering run()" );

        // These first few bits handle a client joining
        try
        {
            // Create out input streams
            ObjectInputStream input = new ObjectInputStream(
                    new DataInputStream( _socket.getInputStream() ) );
            _LOG.debug( "input == " + input.toString() );

            // Used for accepting incoming messages
            Message incomingMessage = null;
            Object initialObject = null;

            initialObject = input.readObject();
            _LOG.debug( "initialObject == " + initialObject.toString() );

            // Check to see if the incoming object is a Message
            if( initialObject instanceof Message )
            {
                _LOG.debug( "initialObject instanceof Message" );
                incomingMessage = (Message) initialObject;
                _LOG.info( "incoming message == " 
                        + incomingMessage.toString() );
            }
            else
            {
                _LOG.fatal( "initialObject not a Message" );
                // This case shouldn't happen, but if it does...
                return;
            }

            // Username of the client sending the message
            String username = incomingMessage.getFromUserName();

            // Register the client as a user
            User user = new User( username, _socket);

            //TODO testing stuff
            System.out.println( "User: " + user.toString() );
            System.out.println( "Message: " + incomingMessage.toString() );

            // Set the message to be returned to the incoming message
            Message returnMessage = incomingMessage;

            // Get who to send the message to
            String toUser = incomingMessage.getToUserName();

            // Once we have connected with a client, continue working with them
            while( true )
            {
                //TODO IT'S THIS ONE
                // For some reason, this isn't working and is keeping the 
                // clients from properly joining
                incomingMessage = (Message) input.readObject(); 
                _LOG.info( "incoming message == " + incomingMessage.toString() );

                // The message to send out to the clients
                returnMessage = incomingMessage;
                System.out.println( "return message pre-type check: "
                        + returnMessage.toString() );
                toUser = returnMessage.getToUserName();
                _LOG.info( "toUser == " + toUser );

                // If it's a join message, add the client to the server
                if( JOIN_TAG.equals( toUser ) )
                {
                    returnMessage = registerClient( incomingMessage, user );
                }
                else if( EXIT_TAG.equals( toUser ) )
                {
                    returnMessage = removeClient( incomingMessage, user, _socket );
                }

                _LOG.info( "returnMessage == " + returnMessage.toString() );

                // Print the message to be sent on the Server's console TODO
                System.out.println( "return message post-type check: " 
                        + returnMessage.toString() );

                // toUser may have changed in joining or exiting
                toUser = returnMessage.getToUserName();
                _LOG.info( "toUser (may have changed if client added or removed) == " + toUser );

                // Send the message as a broadcast to all clients
                if( ALL_CLIENTS.equals( toUser ) )
                {
                    _LOG.info( "Broadcasting message" );
                    broadcast( returnMessage );
                }

                // Send a message to a specific client
                else
                {
                    _LOG.info( "Sending private message to: [" 
                            + toUser
                            + "]" );
                    sendPrivateMessage( returnMessage );
                }
            }
        }
        catch( IOException ioe )
        {
            _LOG.error( "IOException caught" );
            ioe.printStackTrace();
        }
        catch( ClassNotFoundException cnfe )
        {
            _LOG.error( "ClassNotFoundException caught" );
            cnfe.printStackTrace();
        }

        _LOG.trace( "Exiting run()" );
    }

    /**
     * Adds a new client to the server
     */
    private Message registerClient( Message joinMessage, User user )
    {
        _LOG.trace( "Entering registerClient()" );

        _LOG.info( "client joining" );

        // Get the username of the new user
        String username = joinMessage.getFromUserName();

        _LOG.info( "username == " + username );

        // Add client to the user set
        System.out.println( "Adding " + username + " to chat registry" );

        user.setUsername( username );
        boolean added = userSet.add( user );
        _LOG.info( user + " has been added: " + added );

        // Set up a broadcast message showing the user joined to send
        String joinBody = ( username + " joined the chatroom" );
        joinMessage.setBody( joinBody );
        joinMessage.setToUserName( ALL_CLIENTS );

        // Return the message to be sent
        System.out.println( "Join message: " + joinMessage.toString() );
        _LOG.info( "joinMessage == " + joinMessage.toString() );

        // return the message to send
        _LOG.trace( "Exiting registerClient()" );
        return joinMessage;
    }

    /**
     * Removes a client from the server
     */
    private Message removeClient( Message clientMessage, User user, Socket socket )
    {
        _LOG.trace( "Entering removeClient()" );

        // Set the message to an exit message
        String username = user.getUsername();
        String exitBody = ( username + " has left the chatroom" );
        _LOG.info( "client [" + username + "] leaving" );

        // The message will be sent as a broadcast
        clientMessage.setBody( exitBody );
        clientMessage.setToUserName( ALL_CLIENTS );

        // Remove the user from the userSet
        boolean removedUser = userSet.remove( user );
        _LOG.debug( "Removed user [" + username + "] == " + removedUser );

        // Close the socket with the user
        try
        {
            _LOG.info( "Closing socket" );
            socket.close();
        }
        catch( IOException ioe )
        {
            _LOG.error( "IOException caught" );
            ioe.printStackTrace();
        }

        _LOG.trace( "Exiting removeClient()" );
        return clientMessage;
    }

    /**
     * Sends a message to all clients registered on a server
     */
    private void broadcast( Message broadcastMessage )
    {
        _LOG.trace( "Entering broadcast()" );

        // Create an iterator of all the users registered
        Iterator<User> userIterator = userSet.iterator();
        _LOG.debug( "userIterator == " + userIterator );

        // Used to temporarily store user info
        User tempUser = new User();

        // Used to send the message
        ObjectOutputStream output = null;

        try
        {
            // Go through and send the message to each user
            while( userIterator.hasNext() )
            {
                // Get the socket of each user
                tempUser = userIterator.next();
                _LOG.debug( "tempUser == " + tempUser.toString() );

                Socket socket = tempUser.getSocket();

                // Send the message
                System.out.println( "Broadcast message to " + tempUser.getUsername() );
                _LOG.info( "Broadcast message to " + tempUser.getUsername() );

                output = new ObjectOutputStream( new DataOutputStream( socket.getOutputStream() ) );
                _LOG.debug( "output == " + output.toString() );
                output.writeObject( broadcastMessage );

                _LOG.info( "closing output" );
                output.close();
            }
        }
        catch( IOException ioe )
        {
            _LOG.error( "IOException caught" );
            ioe.printStackTrace();
        }   

        _LOG.trace( "Exiting broadcast()" );
    }

    /**
     * Sends a message to a specific user
     */
    private void sendPrivateMessage( Message privateMessage )
    {
        _LOG.trace( "Entering sendPrivateMessage()" );

        // Create an iterator to find the recipient from a list of clients
        Iterator<User> userIterator = userSet.iterator();
        _LOG.debug( "userIterator == " + userIterator );

        // Used to store the recipient of the private message
        User recipient = new User();

        // Used to compare the message target
        String sendTo = privateMessage.getToUserName();
        _LOG.info( "sendTo == " + sendTo );

        // Used to send the message
        ObjectOutputStream output = null;

        try
        {
            // Go through the set of users looking for the recipient
            while( userIterator.hasNext() )
            {
                _LOG.info( "userIterator hasNext" );
                recipient = userIterator.next();
                _LOG.debug( "potential recipient == " + recipient.toString() );

                // If the username matches the intended target send the message
                if( recipient.getUsername() == sendTo )
                {
                    _LOG.info( "recipient found" );

                    // Get the socket of the recipient
                    Socket socket = recipient.getSocket();

                    // Send the message to the recipient
                    output = new ObjectOutputStream( new DataOutputStream( socket.getOutputStream() ) );
                    _LOG.debug( "output == " + output.toString() );
                    output.writeObject( privateMessage );

                    // As we've sent the message, break from the loop
                    _LOG.info( "breaking from loop" );
                    break;
                }
            }
        }
        catch( IOException ioe )
        {
            ioe.printStackTrace();
            System.exit( 1 );
        }

        _LOG.trace( "Exiting sendPrivateMessage()" );
    }
}

Client

/**
 * Defines the client of a TCP-based chat program
 */
public class TCPChatClient
{
    private static final Logger _LOG = Logger.getLogger( TCPChatClient.class );

    // The default port the client will use
    private static final int DEFAULT_PORT = 5555;

    public static void main( String args[] ) throws Exception
    {
        _LOG.trace( "Entering main()" );
        _LOG.info( "args == " + args );

        // The default port
        int clientPort = DEFAULT_PORT;

        // NOTE: localhost will only work on the client's machine
        String host = "localhost";

        // Username of the client
        String username = new String();

        // enter the client's username
        System.out.print( "Please enter desired username: " );
        Scanner scanner = new Scanner( System.in );
        username = scanner.nextLine();
        _LOG.info( "username == " + username );

        // Get the port number to use from the command line
        if( args.length > 0 )
        {
            host = args[0];
            clientPort = Integer.valueOf( args[0] ).intValue();
            System.out.println( username + " now using host:[" + host
                    + "], port:[" + clientPort + "]" );
        }

        // Else, use the default port
        else
        {
            System.out.println( username + " now using host:[" + host
                    + "], port:[" + clientPort + "]" );
        }
        _LOG.info( "host == " + host );
        _LOG.info( "port == " + clientPort );

        // Get the IP address of the local machine
        InetAddress address = InetAddress.getByName( host );
        _LOG.info( "address == " + address );

        // Print out an intro to the client
        System.out.println( "Welcome to the chatroom, " + username + "." );

        // Attempt to connect to the server
        Socket socket = new Socket( address, clientPort );
        _LOG.debug( "socket == " + socket.toString() );

        // Start our sender thread
        ObjectOutputStream output = new ObjectOutputStream( new DataOutputStream( socket.getOutputStream() ) );
        SenderThread sender = new SenderThread( socket, username, output );
        sender.start();
        _LOG.debug( "sender == " + sender );

        // Start our receiver thread
        ObjectInputStream input = new ObjectInputStream( new DataInputStream( sender.getSocket().getInputStream() ) );
        ReceiverThread receiver = new ReceiverThread( sender.getSocket(), input );
        receiver.start();
        _LOG.debug( "receiver == " + receiver );

        _LOG.trace( "Exiting main()" );
    }
}

/**
 * Defines the thread that handles the sending of data to the server
 */
class SenderThread extends Thread
{
    private static final Logger _LOG = Logger.getLogger( SenderThread.class );

    // Tag for all users
    private static final String ALL_CLIENTS = ".ALL";

    // Tag for joining users
    private static final String JOIN_TAG = ".JOIN";

    // Tag for client exiting the server
    private static final String EXIT_TAG = ".EXIT";

    // The socket of the client used
    private Socket _clientSocket;

    // Determines whether or not thread continues
    private boolean _stopped = false;

    // Username of the current client
    private String _username = new String();

    //TODO
    private ObjectOutputStream _output = null;

    // Constructor
    public SenderThread( Socket clientSocket, String username, ObjectOutputStream output ) throws SocketException
    {
        _LOG.info( "constructing SenderThread" );

        _clientSocket = clientSocket;
        _LOG.info( "_clientSocket == " + _clientSocket );

        _username = username;
        _LOG.info( "_username == " + _username );

        //TODO
        _output = output;
        _LOG.info( "_output == " + _username );
    }

    public void halt()
    {
        _LOG.trace( "Entering/exiting halt()" );
        _stopped = true;
    }

    public Socket getSocket()
    {
        _LOG.trace( "Entering/exiting getSocket()" );
        return _clientSocket;
    }

    public void run()
    {
        _LOG.trace( "entering run()" );

        try
        {
            // send join message (only done once)
            Message joinMessage = new Message( _username, JOIN_TAG, "" );
            _LOG.debug( "joinMessage == " + joinMessage.toString() );

            _output.writeObject( joinMessage );
            _output.flush();

            while( true )
            {
                // Exit if the stopped flag has been flipped (shouldn't happen)
                _LOG.info( "_stopped == " + _stopped );

                if( _stopped )
                {
                    _LOG.info( "returning" );
                    return;
                }

                // Create input stream
                Scanner scanner = new Scanner( System.in );
                _LOG.debug( "input == " + scanner );

                // Message to send
                String messageBody = scanner.nextLine().trim();
                _LOG.info( "messageBody == " + messageBody );

                // TODO Figure a way to set the message to a specific user
                Message outgoingMessage = new Message( _username, ALL_CLIENTS, messageBody );

                // Exit the chatroom program
                if( outgoingMessage.getToUserName() == EXIT_TAG )
                {
                    _LOG.info( "client exiting server" );

                    // Close our streams (output closed by server)
                    scanner.close();

                    // Send the ".EXIT" message to the server
                    _output.writeObject( outgoingMessage );
                    _output.flush();

                    // Give exit message to client
                    System.out.println( "Goodbye, " + _username );

                    // Exit
                    halt();
                    _LOG.info( "Exiting system" );
                    System.exit( 1 );
                }

                // Send the message to the server
                _output.writeObject( outgoingMessage );
                _LOG.info( "message sent == " + outgoingMessage.toString() );
                _output.flush();

                // Allow the receiver thread to run
                _LOG.info( "yielding thread" );
                Thread.yield();
            }
        }
        catch( IOException ioe )
        {
            _LOG.error( "IOException caught" );
            ioe.printStackTrace();
            System.exit( 1 );
        }

        _LOG.trace( "leaving run()" );
    }
}

/**
 * Defines the thread that handles the receiving of data from the server
 */
class ReceiverThread extends Thread
{
    /** Our Logger */
    private static final Logger _LOG = Logger.getLogger( ReceiverThread.class );

    // Socket that the current client will use
    private Socket _clientSocket = null;

    // Flag to determine if the thread has stopped
    private boolean _stopped = false;

    //TODO
    private ObjectInputStream _input = null;

    // Constructor
    public ReceiverThread( Socket socket, ObjectInputStream input ) throws SocketException
    {
        _LOG.info( "constructing ReceiverThread" );
        _clientSocket = socket;
        _LOG.info( "_clientSocket == " + _clientSocket.toString() );

        //TODO
        _input = input;
        _LOG.debug( "_input == " + _input.toString() );
    }

    public void halt()
    {
        _LOG.trace( "entering/exiting halt()" );
        _stopped = true;
    }

    public void run()
    {
        _LOG.trace( "entering run()" );

        while( true )   // Infinite loop
        {
            _LOG.info( "_stopped == " + _stopped );
            if( true == _stopped )
            {
                return;
            }

            Message reply = null;
            try
            {
                reply = (Message) _input.readObject();
                _LOG.debug( "reply == " + reply );

                if( reply == null )
                {
                    _LOG.debug( "Null reply" );
                }
                // print the message
                reply.printMessage();
                _LOG.debug( "reply (past null check) == " + reply.toString() );

                // Allow the sender thread to run
                _LOG.info( "yielding thread" );
                Thread.yield();
            }
            catch( IOException ioe )
            {
                _LOG.error( "IOException caught" );
                ioe.printStackTrace();
                System.exit( 1 );
            }
            catch( ClassNotFoundException cnfe )
            {
                _LOG.error( "ClassNotFoundException caught" );
                cnfe.printStackTrace();
                System.exit( 1 );
            }

            _LOG.trace( "leaving run()" );
        }
    }
}

You don't need to use DataInput/OutputStream with ObjectInput/OutputStream.

I suggest using PrintWriter/BufferedReader as these are simple. eg you can telnet to your sever and see what it sends and type what you want to send.

If you use your debugger (in you IDE) you don't need any where as many trace messages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM