使用Swing GUI的简单客户端 – 服务器程序

我正在制作一个简单的,无线程的客户端服务器程序,其中GUI在服务器 客户端都有一个按钮。 当客户端按下按钮时,它将按钮上的文本更改为“C”并发送到服务器 “C”字符串,因此服务器端的按钮将文本更改为“C”服务器客户端的工作方式类似,但发送“S”而不是“C” 。 它们轮流工作:当客户轮到它时, 服务器的按钮被锁定,他无法改变他的按钮。 客户总是先开始。

客户端按下按钮时它工作正常,但是当服务器按下按钮时,它会在服务器端将按钮更改为“S” ,但不会在客户端更改。 我知道我做错了什么。

服务器代码:

public class Serv implements ActionListener { private JButton button; private boolean myTurn; private ServerSocket sock; private Socket s; private BufferedReader input; private PrintStream output; public Serv() throws UnknownHostException, IOException { button = new JButton(); myTurn = false; sock = new ServerSocket(9001); s = null; button = new JButton(); } public void createGUI() { JFrame frame = new JFrame("TicTacToe - Server"); JPanel mainPanel = new JPanel(); mainPanel.setPreferredSize(new Dimension(100, 100)); button = new JButton(""); button.setPreferredSize(new Dimension(100, 100)); button.setFont(new Font(button.getFont().getName(), button.getFont().getStyle(), 70)); button.setActionCommand("1"); button.addActionListener(this); mainPanel.add(button); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(mainPanel); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public void startMyGame() throws IOException { createGUI(); s = sock.accept(); input = new BufferedReader(new InputStreamReader(s.getInputStream())); output = new PrintStream(s.getOutputStream(), true); while(true) { if(myTurn == false) { myTurn = true; String out = input.readLine(); button.setText(out); } } } public static void main(String args[]) { Serv tc = null; try { tc = new Serv(); tc.startMyGame(); } catch(Exception ex) { ex.printStackTrace(); } finally { try { tc.close(); } catch(Exception ex) { ex.printStackTrace(); } } } private void close() throws IOException { this.sock.close(); this.input.close(); this.output.close(); } @Override public void actionPerformed(ActionEvent e) { if(myTurn == true) { if(e.getActionCommand().equals("1")) { JButton b = (JButton) e.getSource(); b.setText("S"); output.println("S"); myTurn = false; } } } } 

客户代码:

 public class Cli implements ActionListener { private JButton button; private boolean myTurn; private Socket sock; private BufferedReader input; private PrintStream output; public Cli() throws UnknownHostException, IOException { button = new JButton(); myTurn = true; sock = new Socket("127.0.0.1", 9001); input = new BufferedReader(new InputStreamReader(sock.getInputStream())); output = new PrintStream(sock.getOutputStream(), true); } public void createGUI() { JFrame frame = new JFrame("TicTacToe - Client"); JPanel mainPanel = new JPanel(); mainPanel.setPreferredSize(new Dimension(100, 100)); button = new JButton(""); button.setPreferredSize(new Dimension(100, 100)); button.setFont(new Font(button.getFont().getName(), button.getFont().getStyle(), 70)); button.setActionCommand("1"); button.addActionListener(this); mainPanel.add(button); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(mainPanel); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public void startMyGame() throws IOException { createGUI(); while(true) { if(myTurn == false) { myTurn = true; String out = input.readLine(); button.setText(out); } } } private void close() throws IOException { this.sock.close(); this.input.close(); this.output.close(); } public static void main(String args[]) { Cli tc = null; try { tc = new Cli(); tc.startMyGame(); } catch(Exception ex) { ex.printStackTrace(); } finally { try { tc.close(); } catch(Exception ex) { ex.printStackTrace(); } } } @Override public void actionPerformed(ActionEvent e) { if(myTurn == true) { if(e.getActionCommand().equals("1")) { JButton b = (JButton) e.getSource(); if(!b.getText().equals("X") || !b.getText().equals("O")) { b.setText("C"); output.println("C"); myTurn = false; } } } } } 

我删除了导入,因此代码会更短。

您当前的代码问题:

  • 您正在创建一个Swing GUI并将其 Swing事件派发线程或EDT中运行。 应该在事件线程启动GUI,以便保证所有Swing代码都在单个线程上运行。
  • 你有一个长时间运行的while循环,它正在进行Swing突变调用,更新JButton的状态。 如果此代码在Swing事件线程上运行,它将阻止/冻结GUI。 应该在不是EDT的后台线程中显式调用此块,并且应根据Lesson:Swurrency中的并发教程将所有Swing调用排队到事件线程上。
  • 您在不同的线程中使用非易失性布尔值,冒着应该更改时不会更改变量的风险
  • 您似乎立即关闭了流,阻止了关注点之间的充分沟通。

在一个更清洁的例子上工作…….

例如:

 import java.awt.Dimension; import java.awt.event.ActionEvent; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import javax.swing.*; public class SimpleServerClient { private static final int PORT = 9001; public static void main(String[] args) { SwingUtilities.invokeLater(() -> { try { SimpleServer server = new SimpleServer(PORT, "Server", false); SimpleClient client = new SimpleClient(PORT, "Client", true); server.createGui(); client.createGui(); } catch (IOException e) { e.printStackTrace(); } }); } } 

 interface SimpleGui { void sendLine(String nextLine); } 

 // background thread handles listening to the Scanner // which scans a Socket's InputStream class MyWorker extends SwingWorker { public static final String LINE = "line"; private Scanner inputScanner; private SimpleGui gui; private String line = ""; public MyWorker(Scanner inputScanner, SimpleGui gui) { this.inputScanner = inputScanner; this.gui = gui; } @Override protected Void doInBackground() throws Exception { while (inputScanner.hasNext()) { // get line from Scanner // use the setter method in case we want to use a PropertyChangeListener later setLine(inputScanner.nextLine()); // send line to the GUI gui.sendLine(getLine()); } return null; } public String getLine() { return line; } // again rigged up to allow use of PropertyChangeListeners public void setLine(String line) { this.line = line; firePropertyChange(LINE, null, line); } } 

 // code that both the client and server GUI classes share abstract class DefaultGui implements SimpleGui { // this guy ***must**** be volitile! private volatile boolean myTurn; protected Scanner inputScanner; protected PrintStream out; protected JButton button = new JButton("Blank"); protected Socket socket; protected String name; protected int port; public DefaultGui(int port, String name, boolean myTurn) { this.port = port; this.name = name; this.myTurn = myTurn; } @Override public void sendLine(String nextLine) { button.setText(nextLine); myTurn = true; } public void createGui() { button.addActionListener(e -> actionPerformed(e)); JPanel panel = new JPanel(); panel.setPreferredSize(new Dimension(300, 300)); panel.add(button); JFrame frame = new JFrame(getName()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(panel); frame.pack(); frame.setLocationByPlatform(true); frame.setVisible(true); } protected void actionPerformed(ActionEvent e) { if (!myTurn) { return; } out.println(getName()); button.setText(getName()); myTurn = false; } public String getName() { return name; } } 

 class SimpleServer extends DefaultGui { private ServerSocket serverSocket; public SimpleServer(int port, String name, boolean myTurn) throws IOException { super(port, name, myTurn); serverSocket = new ServerSocket(port); new Thread(() -> { try { // accept() blocks the current thread, so must be called on a background thread socket = serverSocket.accept(); inputScanner = new Scanner(socket.getInputStream()); out = new PrintStream(socket.getOutputStream(), true); new MyWorker(inputScanner, this).execute(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } 

 class SimpleClient extends DefaultGui { public SimpleClient(int port, String name, boolean myTurn) throws IOException { super(port, name, myTurn); socket = new Socket("localhost", port); inputScanner = new Scanner(socket.getInputStream()); out = new PrintStream(socket.getOutputStream()); new MyWorker(inputScanner, this).execute(); } }