Многопоточный сервер принимает данные от одного клиента и сигнализирует другому - PullRequest
0 голосов
/ 03 мая 2018

Я работаю над проблемой из Java How-to-Code, вопрос 28.20. Книга настроила многопоточный клиент / сервер, чтобы позволить двум пользователям играть в тактики друг с другом. Проблема просит нас изменить существующий код, чтобы добавить определенные функции. Я застрял в том, чтобы добавить кнопку «Новая игра». Я добавил кнопку и получил клиента, который нажимает кнопку, чтобы уведомить сервер, но я не могу понять, как это сделать, - сервер отправит сообщение другому клиенту. Прямо сейчас он будет реагировать только на тот, который нажал кнопку. Как я могу получить его для отправки сообщения другому или обоим клиентам? Спасибо.

Вот код клиента:

// Fig. 27.15: TicTacToeClient.java
// Client side of client/server Tic-Tac-Toe program.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.Socket;
import java.net.InetAddress;
import java.io.IOException;
import javax.swing.*;
import java.util.Formatter;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class TicTacToeClient extends JFrame implements Runnable
{
private JTextField idField; // textfield to display player's mark
private JTextArea displayArea; // JTextArea to display output
private JPanel boardPanel; // panel for tic-tac-toe board
private JPanel panel2; // panel to hold board
private JButton playAgain;
private JButton disconnect;
private Square[][] board; // tic-tac-toe board
private Square currentSquare; // current square
private Socket connection; // connection to server
private Scanner input; // input from server
private Formatter output; // output to server
private String ticTacToeHost; // host name for server
private String myMark; // this client's mark
private boolean myTurn; // determines which client's turn it is
private final String X_MARK = "X"; // mark for first client
private final String O_MARK = "O"; // mark for second client

// set up user-interface and board
public TicTacToeClient( String host )
{
    ticTacToeHost = host; // set name of server
    displayArea = new JTextArea( 4, 30 ); // set up JTextArea
    displayArea.setEditable( false );
    add( new JScrollPane( displayArea ), BorderLayout.SOUTH );

    boardPanel = new JPanel(); // set up panel for squares in board
    boardPanel.setLayout( new GridLayout( 3, 3, 0, 0 ) );

    board = new Square[ 3 ][ 3 ]; // create board

    // loop over the rows in the board
    for ( int row = 0; row < board.length; row++ )
    {
        // loop over the columns in the board
        for ( int column = 0; column < board[ row ].length; column++ )
        {
            // create square
            board[ row ][ column ] = new Square( " ", row * 3 + column );
            boardPanel.add( board[ row ][ column ] ); // add square
        } // end inner for
    } // end outer for


    idField = new JTextField(); // set up textfield
    idField.setEditable( false );
    add(idField, BorderLayout.NORTH);

    panel2 = new JPanel(); // set up panel to contain boardPanel
    panel2.add(boardPanel, BorderLayout.CENTER); // add board panel
    add( panel2, BorderLayout.CENTER ); // add container panel

    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout(new BorderLayout(0,5));
    playAgain = new JButton("Play again?");
    playAgain.setFont(new Font("NewFont",Font.PLAIN,12));
    playAgain.setEnabled(false);
    buttonPanel.add(playAgain, BorderLayout.NORTH);

    disconnect = new JButton("Disconnect");
    disconnect.setFont(new Font("NewFont2", Font.PLAIN, 12));
    buttonPanel.add(disconnect, BorderLayout.SOUTH);
    add(buttonPanel, BorderLayout.EAST);

    setSize( 350, 250 ); // set size of window
    setVisible( true ); // show window

    startClient();
} // end TicTacToeClient constructor

// start the client thread
public void startClient()
{
    try // connect to server and get streams
    {
        // make connection to server
        connection = new Socket(
                InetAddress.getByName( ticTacToeHost ), 12345 );

        // get streams for input and output
        input = new Scanner( connection.getInputStream() );
        output = new Formatter( connection.getOutputStream() );
    } // end try
    catch ( IOException ioException )
    {
        ioException.printStackTrace();
    } // end catch

    // create and start worker thread for this client
    ExecutorService worker = Executors.newFixedThreadPool( 1 );
    worker.execute( this ); // execute client
} // end method startClient

// control thread that allows continuous update of displayArea
public void run()
{
    myMark = input.nextLine(); // get player's mark (X or O)

    SwingUtilities.invokeLater(
            new Runnable()
            {
                public void run()
                {
                    // display player's mark
                    idField.setText( "You are player \"" + myMark + "\"" );
                } // end method run
            } // end anonymous inner class
    ); // end call to SwingUtilities.invokeLater

    myTurn = ( myMark.equals( X_MARK ) ); // determine if client's turn

    playAgain.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            output.format("New Game\n");
            output.flush();

            // loop over the rows in the board
            for ( int row = 0; row < board.length; row++ )
            {
                // loop over the columns in the board
                for ( int column = 0; column < board[ row ].length;column++ )
                {
                    // create square
                    setMark(board[row][column], "");
                } // end inner for
            } // end outer for
        }
    });

    // receive messages sent to client and output them
    while ( true )
    {
        if ( input.hasNextLine() )
            processMessage( input.nextLine() );
    } // end while


} // end method run

// process messages received by client
private void processMessage( String message )
{
    // valid move occurred
    if ( message.equals( "Valid move." ) )
    {
        displayMessage( "Valid move, please wait.\n" );
        setMark( currentSquare, myMark ); // set mark in square
    } // end if
    else if ( message.equals( "Invalid move, try again" ) )
    {
        displayMessage( message + "\n" ); // display invalid move
        myTurn = true; // still this client's turn
    } // end else if
    else if ( message.equals( "Opponent moved" ) )
    {
        int location = input.nextInt(); // get move location
        input.nextLine(); // skip newline after int location
        int row = location / 3; // calculate row
        int column = location % 3; // calculate column

        setMark(  board[ row ][ column ],
                ( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
        displayMessage( "Opponent moved. Your turn.\n" );
        myTurn = true; // now this client's turn
    } // end else if
    else if(message.equals("Game over. Players Tied")){
        int location = input.nextInt(); // get move location
        input.nextLine(); // skip newline after int location
        int row = location / 3; // calculate row
        int column = location % 3; // calculate column
        setMark(  board[ row ][ column ],
                ( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
        displayMessage("Game over. Players tied");
        playAgain.setEnabled(true);
    }//else if
    else if(message.equals("Game over, you lose")){
        int location = input.nextInt(); // get move location
        input.nextLine(); // skip newline after int location
        int row = location / 3; // calculate row
        int column = location % 3; // calculate column
        setMark(  board[ row ][ column ],
                ( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
        displayMessage("Game over, you lose.");
        playAgain.setEnabled(true);
    }//else if
    else if(message.equals("Game over. You win!")){
        displayMessage("Game over. You win!");
        playAgain.setEnabled(true);
    }else if(message.equals("New Game")){
        displayMessage("\nNew game requested");
        // loop over the rows in the board
        for ( int row = 0; row < board.length; row++ )
        {
            // loop over the columns in the board
            for ( int column = 0; column < board[ row ].length; column++ )
            {
                // create square
                setMark(board[row][column], "");
            } // end inner for
        } // end outer for
        playAgain.setEnabled(false);
    }
    else
        displayMessage( message + "\n" ); // display the message
} // end method processMessage

// manipulate displayArea in event-dispatch thread
private void displayMessage( final String messageToDisplay )
{
    SwingUtilities.invokeLater(
            new Runnable()
            {
                public void run()
                {
                    displayArea.append( messageToDisplay ); // updates output
                } // end method run
            }  // end inner class
    ); // end call to SwingUtilities.invokeLater
} // end method displayMessage

// utility method to set mark on board in event-dispatch thread
private void setMark( final Square squareToMark, final String mark )
{
    SwingUtilities.invokeLater(
            new Runnable()
            {
                public void run()
                {
                    squareToMark.setMark( mark ); // set mark in square
                } // end method run
            } // end anonymous inner class
    ); // end call to SwingUtilities.invokeLater
} // end method setMark

// send message to server indicating clicked square
public void sendClickedSquare( int location )
{
    // if it is my turn
    if ( myTurn )
    {
        output.format( "%d\n", location ); // send location to server
        output.flush();
        myTurn = false; // not my turn any more
    } // end if
} // end method sendClickedSquare

// set current Square
public void setCurrentSquare( Square square )
{
    currentSquare = square; // set current square to argument
} // end method setCurrentSquare

// private inner class for the squares on the board
private class Square extends JPanel
{
    private String mark; // mark to be drawn in this square
    private int location; // location of square

    public Square( String squareMark, int squareLocation )
    {
        mark = squareMark; // set mark for this square
        location = squareLocation; // set location of this square

        addMouseListener(
                new MouseAdapter()
                {
                    public void mouseReleased( MouseEvent e )
                    {
                        setCurrentSquare( Square.this ); // set current

                        // send location of this square
                        sendClickedSquare( getSquareLocation() );
                    } // end method mouseReleased
                } // end anonymous inner class
        ); // end call to addMouseListener
    } // end Square constructor

    // return preferred size of Square
    public Dimension getPreferredSize()
    {
        return new Dimension( 30, 30 ); // return preferred size
    } // end method getPreferredSize

    // return minimum size of Square
    public Dimension getMinimumSize()
    {
        return getPreferredSize(); // return preferred size
    } // end method getMinimumSize

    // set mark for Square
    public void setMark( String newMark )
    {
        mark = newMark; // set mark of square
        repaint(); // repaint square
    } // end method setMark

    // return Square location
    public int getSquareLocation()
    {
        return location; // return location of square
    } // end method getSquareLocation

    // draw Square
    public void paintComponent( Graphics g )
    {
        super.paintComponent( g );

        g.drawRect( 0, 0, 29, 29 ); // draw square
        g.drawString( mark, 11, 20 ); // draw mark
    } // end method paintComponent
} // end inner-class Square
} // end class TicTacToeClient

код сервера

// Fig. 27.13: TicTacToeServer.java
// Server side of client/server Tic-Tac-Toe program.
import java.awt.BorderLayout;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
import java.util.Formatter;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TicTacToeServer extends JFrame
{
private String[] board = new String[ 9 ]; // tic-tac-toe board
private JTextArea outputArea; // for outputting moves
private Player[] players; // array of Players
private ServerSocket server; // server socket to connect with clients
private int currentPlayer; // keeps track of player with current move
private final static int PLAYER_X = 0; // constant for first player
private final static int PLAYER_O = 1; // constant for second player
private final static String[] MARKS = { "X", "O" }; // array of marks
private ExecutorService runGame; // will run players
private Lock gameLock; // to lock game for synchronization
private Condition otherPlayerConnected; // to wait for other player
private Condition otherPlayerTurn; // to wait for other player's turn

// set up tic-tac-toe server and GUI that displays messages
public TicTacToeServer()
{
    super( "Tic-Tac-Toe Server" ); // set title of window

    // create ExecutorService with a thread for each player
    runGame = Executors.newFixedThreadPool( 2 );
    gameLock = new ReentrantLock(); // create lock for game

    // condition variable for both players being connected
    otherPlayerConnected = gameLock.newCondition();

    // condition variable for the other player's turn
    otherPlayerTurn = gameLock.newCondition();

    for ( int i = 0; i < 9; i++ )
        board[ i ] = ""; // create tic-tac-toe board
    players = new Player[ 2 ]; // create array of players
    currentPlayer = PLAYER_X; // set current player to first player

    try
    {
        server = new ServerSocket( 12345, 2 ); // set up ServerSocket
    } // end try
    catch ( IOException ioException )
    {
        ioException.printStackTrace();
        System.exit( 1 );
    } // end catch

    outputArea = new JTextArea(); // create JTextArea for output
    add( outputArea, BorderLayout.CENTER );
    outputArea.setText( "Server awaiting connections\n" );

    setSize( 300, 300 ); // set size of window
    setVisible( true ); // show window
} // end TicTacToeServer constructor

// wait for two connections so game can be played
public void execute()
{
    // wait for each client to connect
    for ( int i = 0; i < players.length; i++ )
    {
        try // wait for connection, create Player, start runnable
        {
            players[ i ] = new Player( server.accept(), i );
            runGame.execute( players[ i ] ); // execute player runnable
        } // end try
        catch ( IOException ioException )
        {
            ioException.printStackTrace();
            System.exit( 1 );
        } // end catch
    } // end for

    gameLock.lock(); // lock game to signal player X's thread

    try
    {
        players[ PLAYER_X ].setSuspended( false ); // resume player X
        otherPlayerConnected.signal(); // wake up player X's thread
    } // end try
    finally
    {
        gameLock.unlock(); // unlock game after signalling player X
    } // end finally
} // end method execute

// display message in outputArea
private void displayMessage( final String messageToDisplay )
{
    // display message from event-dispatch thread of execution
    SwingUtilities.invokeLater(
            new Runnable()
            {
                public void run() // updates outputArea
                {
                    outputArea.append( messageToDisplay ); // add message
                } // end  method run
            } // end inner class
    ); // end call to SwingUtilities.invokeLater
} // end method displayMessage

// determine if move is valid
public boolean validateAndMove( int location, int player )
{
    // while not current player, must wait for turn
    while ( player != currentPlayer )
    {
        gameLock.lock(); // lock game to wait for other player to go

        try
        {
            otherPlayerTurn.await(); // wait for player's turn
        } // end try
        catch ( InterruptedException exception )
        {
            exception.printStackTrace();
        } // end catch
        finally
        {
            gameLock.unlock(); // unlock game after waiting
        } // end finally
    } // end while

    // if location not occupied, make move
    if ( !isOccupied( location ) )
    {
        board[ location ] = MARKS[ currentPlayer ]; // set move on board
        currentPlayer = 1- currentPlayer; // change player

        // let new current player know that move occurred
        players[ currentPlayer ].otherPlayerMoved( location );

        gameLock.lock(); // lock game to signal other player to go

        try
        {
            if(!isWin() && !isTie())
                otherPlayerTurn.signal(); // signal other player to continue
        } // end try
        finally
        {
            if(!isWin() && !isTie())
                gameLock.unlock(); // unlock game after signaling
        } // end finally

        return true; // notify player that move was valid
    } // end if
    else // move was not valid
        return false; // notify player that move was invalid
} // end method validateAndMove

// determine whether location is occupied
public boolean isOccupied( int location )
{
    if ( board[ location ].equals( MARKS[ PLAYER_X ] ) ||
            board [ location ].equals( MARKS[ PLAYER_O ] ) )
        return true; // location is occupied
    else
        return false; // location is not occupied
} // end method isOccupied

// place code in this method to determine whether game over
public boolean isWin()
{
    boolean isGameOver = false;

    if(!board[0].equals("") && board[0].equals(board[1]) 
    && board[1].equals(board[2]))
        isGameOver = true;
    if(!board[3].equals("") && board[3].equals(board[4]) 
    && board[4].equals(board[5]))
        isGameOver = true;
    if(!board[6].equals("") && board[6].equals(board[7]) 
    && board[7].equals(board[8]))
        isGameOver = true;

    if(!board[0].equals("") && board[0].equals(board[3]) 
    && board[3].equals(board[6]))
        isGameOver = true;
    if(!board[1].equals("") && board[1].equals(board[4]) 
    && board[4].equals(board[7]))
        isGameOver = true;
    if(!board[2].equals("") && board[2].equals(board[5]) 
    && board[5].equals(board[8]))
        isGameOver = true;

    if(!board[0].equals("") && board[0].equals(board[4]) 
    && board[4].equals(board[8]))
        isGameOver = true;
    if(!board[2].equals("") && board[2].equals(board[4]) 
    && board[4].equals(board[6]))
        isGameOver = true;
    return isGameOver; // this is left as an exercise
} // end method isWin

public boolean isTie(){
    boolean isTie = false;
    if(!board[0].equals("") && !board[1].equals("") 
    && !board[2].equals("") && !board[3].equals("") && !board[4].equals("")
            && !board[5].equals("") && !board[6].equals("") 
    && !board[7].equals("") && !board[8].equals(""))
        isTie = true;

    return isTie;
}//end method is Tie

// private inner class Player manages each Player as a runnable
private class Player implements Runnable
{
    private Socket connection; // connection to client
    private Scanner input; // input from client
    private Formatter output; // output to client
    private int playerNumber; // tracks which player this is
    private String mark; // mark for this player
    private boolean suspended = true; // whether thread is suspended

    // set up Player thread
    public Player( Socket socket, int number )
    {
        playerNumber = number; // store this player's number
        mark = MARKS[ playerNumber ]; // specify player's mark
        connection = socket; // store socket for client

        try // obtain streams from Socket
        {
            input = new Scanner( connection.getInputStream() );
            output = new Formatter( connection.getOutputStream() );
        } // end try
        catch ( IOException ioException )
        {
            ioException.printStackTrace();
            System.exit( 1 );
        } // end catch
    } // end Player constructor

    // send message that other player moved
    public void otherPlayerMoved( int location )
    {
        if (!isTie() && !isWin()) {
            output.format("Opponent moved\n");
            output.format("%d\n", location); // send location of move
            output.flush(); // flush output
        }else if(isWin()){
            output.format("Game over, you lose\n");
            output.format("%d\n", location);
            output.flush();
        }else if( isTie()){
            output.format("Game over. Players Tied\n");
            output.format("%d\n", location);
            output.flush();
        }
    } // end method otherPlayerMoved

    // control thread's execution
    public void run()
    {
        // send client its mark (X or O), process messages from client
        try
        {
            displayMessage( "Player " + mark + " connected\n" );
            output.format( "%s\n", mark ); // send player's mark
            output.flush(); // flush output

            // if player X, wait for another player to arrive
            if ( playerNumber == PLAYER_X )
            {
                output.format( "%s\n%s", "Player X connected",
                        "Waiting for another player\n" );
                output.flush(); // flush output

                gameLock.lock(); // lock game to  wait for second player

                try
                {
                    while( suspended )
                    {
                        otherPlayerConnected.await(); // wait for player O
                    } // end while
                } // end try
                catch ( InterruptedException exception )
                {
                    exception.printStackTrace();
                } // end catch
                finally
                {
                    gameLock.unlock(); // unlock game after second player
                } // end finally

                // send message that other player connected
                output.format( "Other player connected. Your move.\n" );
                output.flush(); // flush output
            } // end if
            else
            {
                output.format( "Player O connected, please wait\n" );
                output.flush(); // flush output
            } // end else

            // while game not over
            while ( true )
            {
                int location = 9; // initialize move location


                if ( input.hasNextInt() )
                    location = input.nextInt(); // get move location
                if ( input.hasNextLine() )
                    processMessage( input.nextLine() );
                // check for valid move
                if ( validateAndMove( location, playerNumber ) )
                {
                    displayMessage( "\nlocation: " + location );
                    output.format( "Valid move.\n" ); // notify client
                    output.flush(); // flush output

                    if (isWin()){
                        output.format("Game over. You win!\n");
                        //output.format("%d\n", location);
                        output.flush();
                    }else if (isTie()){
                        output.format("Game over. Players Tied\n");
                        output.format("%d\n", location);
                        output.flush();
                    }

                } // end if
                else // move was invalid
                {
                    output.format( "Invalid move, try again\n" );
                    output.flush(); // flush output
                } // end else
            } // end while

        } // end try
        finally
        {
            try
            {
                connection.close(); // close connection to client
            } // end try
            catch ( IOException ioException )
            {
                ioException.printStackTrace();
                System.exit( 1 );
            } // end catch
        } // end finally
    } // end method run

    // set whether or not thread is suspended
    public void setSuspended( boolean status )
    {
        suspended = status; // set value of suspended
    } // end method setSuspended

    public void processMessage(String message){
        if ( message.equals( "New Game" ) )
        {
            displayMessage( "\nPlayer requested new game" );
            for(int i=0; i<board.length; i++){
                board[i] = "";
            }

            output.format("New Game\n");
            output.flush();

        } // end if
    }
} // end class Player
} // end class TicTacToeServer

1 Ответ

0 голосов
/ 03 мая 2018

Ваши клиенты должны будут слушать сервер. В идеале сообщения о действиях, отправляемые клиентами, должны быть независимыми от сообщений о состоянии, отправляемых сервером. Такое разъединение позволяет создавать более надежные сценарии, чем тот, который вы выполняете «каждый клиент по очереди». В частности, сервер будет отправлять сообщения о состоянии всем клиентам, а клиенты будут обновлять свое состояние на основе этих сообщений, а не своих собственных действий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...