重构—Sockets中的良好实践—简单的服务器-客户端Swing应用程序
我使用单例和观察者模式编写了一个带有 Swing 接口的简单服务器-客户端程序。每个客户端都连接到服务器并可以发送消息。服务器将其收到的消息转发给其余的客户端。客户端使用 GUI,允许它们随时连接和断开与服务器的连接。 该程序运行得很好,因为我到处都有 try-catch 来处理可能发生的每个异常。但如果你使用它,你会在控制台中看到一百万个异常。这可能是因为设计不佳,但我已尽力编写干净的代码。 所以问题是:
任何人都可以给我提示并建议如何重构代码以使其更加正确和干净(特别是在断开客户端与服务器的连接时)?
CustomServer
类中有一个 main 方法来启动服务器,还有一个 控制器中的 main 方法启动客户端(使用 GUI)。
感谢您的热心帮助!
代码如下:
CustomServer类
package model;
import java.io.*;
import java.net.*;
import controller.Controller;
public class CustomServer extends Thread{
private ServerSocket ss = null;;
public CustomServer() throws Exception {
ss = new ServerSocket(4444);
this.start();
}
@Override
public void run(){
while (true){
try {
Socket connSoc = ss.accept();
new Connect(connSoc);
} catch (IOException e) {
e.printStackTrace();
try{
ss.close();
}catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
private class Connect extends Thread{
ObjectOutputStream out;
ObjectInputStream in;
public Connect(Socket connSoc) {
final IOController server = IOController.getInstance();
try {
out = new ObjectOutputStream(connSoc.getOutputStream());
in = new ObjectInputStream(connSoc.getInputStream());
server.add(in, out);
}catch (Exception e) {
e.printStackTrace();
try{
connSoc.close();
}catch (Exception ex) {
e.printStackTrace();
}
}
this.start();
}
}
public static void main(String[] args) throws Exception{
new CustomServer();
}
}
CustomClient类
package model;
import java.io.*;
import java.net.*;
import java.util.*;
public class CustomClient extends Observable{
private Socket connSocket;
private ObjectOutputStream out;
private ObjectInputStream in;
private boolean isOn = true;
private Thread receiver;
public CustomClient() throws Exception{
System.out.println("inside CClient");
Socket soc = new Socket();
soc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 4444));
out = new ObjectOutputStream(soc.getOutputStream());
in = new ObjectInputStream(soc.getInputStream());
}
public void transmit(Object obj){
System.out.println("CClient - transitmin - start");
try {
out.writeObject(obj);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("CClient - transitmin - end");
}
public void reveive(){
System.out.println("CClient - recieve - start");
receiver = new Thread(new Runnable() {
@Override
public void run() {
while (isOn){
Object obj = null;
try {
obj = in.readObject();
setChanged();
} catch (Exception ex){
ex.printStackTrace();
}
if (hasChanged()){
notifyObservers(obj);
}
}
}
});
receiver.start();
System.out.println("CClient - recieve - end");
}
public void closeConnection(){
try{
in.close();
} catch (Exception e) {
System.err.println("CAUGHT");
e.printStackTrace();
}
try{
out.close();
} catch (Exception e) {
System.err.println("CAUGHT");
e.printStackTrace();
}
finally {
try {
isOn = false;
in = null;
out = null;
connSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
connSocket = null;
}
}
public Socket getSocket(){
return connSocket;
}
}
IOController类
package model;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class IOController{
ArrayList<ObjectInputStream> ins = new ArrayList<ObjectInputStream>();
ArrayList<ObjectOutputStream> outs = new ArrayList<ObjectOutputStream>();
private static IOController instance = new IOController();
private IOController() {
}
public static IOController getInstance(){
return instance;
}
public void add(final ObjectInputStream in, final ObjectOutputStream out){
ins.add(in);
outs.add(out);
new Connect(in);
}
private class Connect extends Thread{
ObjectInputStream in;
public Connect(ObjectInputStream in) {
this.in = in;
this.start();
}
@Override
public void run() {
boolean isOn = true;
ArrayList<ObjectOutputStream> toBeRemoved = new ArrayList<ObjectOutputStream>();
while(isOn){
try {
Object obj = in.readObject();
for (ObjectOutputStream out : outs){
try {
out.writeObject(obj);
out.flush();
}catch (Exception ex){
toBeRemoved.add(out);
ex.printStackTrace();
}
}
for (ObjectOutputStream oos : toBeRemoved){
outs.remove(oos);
}
}catch (Exception ex){
ins.remove(in);
isOn = false;
in = null;
ex.printStackTrace();
}
}
}
}
}
控制器类
package controller;
import java.awt.*;
import view.GUI;
import model.CustomClient;
public class Controller {
private GUI gui;
private CustomClient client;
public Controller() {
gui = new GUI();
gui.addConnectButtonActionListener(new ConnectButtonActionListener());
gui.addTextFieldKeyListner(new TextFieldKeyListener());
gui.addDisconnectButtonActionListener(new DisconnectButtonActionListener());
gui.addCustomWindowListener(new GuiWindowListener());
}
private class DisconnectButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
client.closeConnection();
client = null;
gui.getDisconnectButton().setEnabled(false);
gui.getConnectButton().setEnabled(true);
}
}
private class ConnectButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
try {
client = new CustomClient();
client.addObserver(gui);
client.reveive();
gui.getConnectButton().setEnabled(false);
gui.getDisconnectButton().setEnabled(true);
}catch (Exception ex) {
ex.printStackTrace();
}
}
}
private class TextFieldKeyListener extends KeyAdapter{
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode()==KeyEvent.VK_ENTER){
String msg = gui.getTextField().getText();
gui.getTextField().setText("");
if (client != null){
client.transmit(msg);
}
}
}
}
private class GuiWindowListener extends WindowAdapter{
@Override
public void windowClosing(WindowEvent e) {
try{
if (client != null){
client.closeConnection();
client = null;
}
}catch (Exception e2) {
}
System.out.println("closed");
}
}
public static void main(String[] args) {
new Controller();
}
}
和GUI类
package view;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class GUI extends JFrame implements Observer{
private JTextField textField;
private JTextArea displayArea;
private JButton connectButton;
private JButton disconnectButton;
public GUI() {
init();
setDefaultCloseOperation(EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
pack();
setVisible(true);
textField.requestFocusInWindow();
}
});
}
public void addConnectButtonActionListener(ActionListener al){
connectButton.addActionListener(al);
}
public void addDisconnectButtonActionListener(ActionListener al){
disconnectButton.addActionListener(al);
}
public void addTextFieldKeyListner(KeyListener kl){
textField.addKeyListener(kl);
}
public void addCustomWindowListener(WindowListener guiWindowListener) {
addWindowListener(guiWindowListener);
}
public void appendText(String text){
displayArea.append("\n"+ text);
}
private void init() {
JPanel panel = new JPanel();
JPanel southPanel = new JPanel();
JPanel northPanel = new JPanel();
connectButton = new JButton("connect");
connectButton.setFocusable(false);
disconnectButton = new JButton("disconnect");
disconnectButton.setFocusable(false);
textField = new JTextField(20);
displayArea = new JTextArea();
displayArea.setEditable(false);
displayArea.setPreferredSize(new Dimension(300,250));
panel.setLayout(new BorderLayout());
southPanel.setLayout(new FlowLayout());
northPanel.setLayout(new FlowLayout());
northPanel.add(displayArea);
southPanel.add(connectButton);
southPanel.add(disconnectButton);
panel.add(textField,BorderLayout.CENTER);
panel.add(southPanel, BorderLayout.SOUTH);
panel.add(northPanel, BorderLayout.NORTH);
this.getContentPane().add(panel);
disconnectButton.setEnabled(false);
System.out.println(textField.hasFocus());
}
public JTextField getTextField() {
return textField;
}
public void setTextField(JTextField textField) {
this.textField = textField;
}
public JTextArea getDisplayArea() {
return displayArea;
}
public void setDisplayArea(JTextArea displayArea) {
this.displayArea = displayArea;
}
public JButton getConnectButton() {
return connectButton;
}
public void setConnectButton(JButton connectButton) {
this.connectButton = connectButton;
}
public JButton getDisconnectButton() {
return disconnectButton;
}
public void setDisconnectButton(JButton disconnectButton) {
this.disconnectButton = disconnectButton;
}
@Override
public void update(Observable observable, Object object) {
displayArea.append("\n"+(String)object);
}
}
I have written a simple server - client program with Swing interface using singleton and observer pattern. Each client connects to the server and can send messages. The server forwards the messages it receives to the rest of the clients. The clients are using a GUI which allows them to connect and disconnect to the server at any time.
The program works pretty well because I have try - catch everywhere that handle each exception that may occur. But if you play with it you will see in the console a million of exceptions. That is probably because of poor design but i have tried my best to do a clean code.
so the question is :
Can anybody give me hints and advises on how to refactor the code to be more correct and clean (Especially when it comes to disconnect a client from the server)?
There is a main method in the CustomServer
class to start the server and a
main method in the Controller to start the client (with GUI).
Thank you for your kind help!
Here is the code:
CustomServer class
package model;
import java.io.*;
import java.net.*;
import controller.Controller;
public class CustomServer extends Thread{
private ServerSocket ss = null;;
public CustomServer() throws Exception {
ss = new ServerSocket(4444);
this.start();
}
@Override
public void run(){
while (true){
try {
Socket connSoc = ss.accept();
new Connect(connSoc);
} catch (IOException e) {
e.printStackTrace();
try{
ss.close();
}catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
private class Connect extends Thread{
ObjectOutputStream out;
ObjectInputStream in;
public Connect(Socket connSoc) {
final IOController server = IOController.getInstance();
try {
out = new ObjectOutputStream(connSoc.getOutputStream());
in = new ObjectInputStream(connSoc.getInputStream());
server.add(in, out);
}catch (Exception e) {
e.printStackTrace();
try{
connSoc.close();
}catch (Exception ex) {
e.printStackTrace();
}
}
this.start();
}
}
public static void main(String[] args) throws Exception{
new CustomServer();
}
}
CustomClient Class
package model;
import java.io.*;
import java.net.*;
import java.util.*;
public class CustomClient extends Observable{
private Socket connSocket;
private ObjectOutputStream out;
private ObjectInputStream in;
private boolean isOn = true;
private Thread receiver;
public CustomClient() throws Exception{
System.out.println("inside CClient");
Socket soc = new Socket();
soc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 4444));
out = new ObjectOutputStream(soc.getOutputStream());
in = new ObjectInputStream(soc.getInputStream());
}
public void transmit(Object obj){
System.out.println("CClient - transitmin - start");
try {
out.writeObject(obj);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("CClient - transitmin - end");
}
public void reveive(){
System.out.println("CClient - recieve - start");
receiver = new Thread(new Runnable() {
@Override
public void run() {
while (isOn){
Object obj = null;
try {
obj = in.readObject();
setChanged();
} catch (Exception ex){
ex.printStackTrace();
}
if (hasChanged()){
notifyObservers(obj);
}
}
}
});
receiver.start();
System.out.println("CClient - recieve - end");
}
public void closeConnection(){
try{
in.close();
} catch (Exception e) {
System.err.println("CAUGHT");
e.printStackTrace();
}
try{
out.close();
} catch (Exception e) {
System.err.println("CAUGHT");
e.printStackTrace();
}
finally {
try {
isOn = false;
in = null;
out = null;
connSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
connSocket = null;
}
}
public Socket getSocket(){
return connSocket;
}
}
IOController class
package model;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class IOController{
ArrayList<ObjectInputStream> ins = new ArrayList<ObjectInputStream>();
ArrayList<ObjectOutputStream> outs = new ArrayList<ObjectOutputStream>();
private static IOController instance = new IOController();
private IOController() {
}
public static IOController getInstance(){
return instance;
}
public void add(final ObjectInputStream in, final ObjectOutputStream out){
ins.add(in);
outs.add(out);
new Connect(in);
}
private class Connect extends Thread{
ObjectInputStream in;
public Connect(ObjectInputStream in) {
this.in = in;
this.start();
}
@Override
public void run() {
boolean isOn = true;
ArrayList<ObjectOutputStream> toBeRemoved = new ArrayList<ObjectOutputStream>();
while(isOn){
try {
Object obj = in.readObject();
for (ObjectOutputStream out : outs){
try {
out.writeObject(obj);
out.flush();
}catch (Exception ex){
toBeRemoved.add(out);
ex.printStackTrace();
}
}
for (ObjectOutputStream oos : toBeRemoved){
outs.remove(oos);
}
}catch (Exception ex){
ins.remove(in);
isOn = false;
in = null;
ex.printStackTrace();
}
}
}
}
}
Controller class
package controller;
import java.awt.*;
import view.GUI;
import model.CustomClient;
public class Controller {
private GUI gui;
private CustomClient client;
public Controller() {
gui = new GUI();
gui.addConnectButtonActionListener(new ConnectButtonActionListener());
gui.addTextFieldKeyListner(new TextFieldKeyListener());
gui.addDisconnectButtonActionListener(new DisconnectButtonActionListener());
gui.addCustomWindowListener(new GuiWindowListener());
}
private class DisconnectButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
client.closeConnection();
client = null;
gui.getDisconnectButton().setEnabled(false);
gui.getConnectButton().setEnabled(true);
}
}
private class ConnectButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
try {
client = new CustomClient();
client.addObserver(gui);
client.reveive();
gui.getConnectButton().setEnabled(false);
gui.getDisconnectButton().setEnabled(true);
}catch (Exception ex) {
ex.printStackTrace();
}
}
}
private class TextFieldKeyListener extends KeyAdapter{
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode()==KeyEvent.VK_ENTER){
String msg = gui.getTextField().getText();
gui.getTextField().setText("");
if (client != null){
client.transmit(msg);
}
}
}
}
private class GuiWindowListener extends WindowAdapter{
@Override
public void windowClosing(WindowEvent e) {
try{
if (client != null){
client.closeConnection();
client = null;
}
}catch (Exception e2) {
}
System.out.println("closed");
}
}
public static void main(String[] args) {
new Controller();
}
}
and GUI class
package view;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class GUI extends JFrame implements Observer{
private JTextField textField;
private JTextArea displayArea;
private JButton connectButton;
private JButton disconnectButton;
public GUI() {
init();
setDefaultCloseOperation(EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
pack();
setVisible(true);
textField.requestFocusInWindow();
}
});
}
public void addConnectButtonActionListener(ActionListener al){
connectButton.addActionListener(al);
}
public void addDisconnectButtonActionListener(ActionListener al){
disconnectButton.addActionListener(al);
}
public void addTextFieldKeyListner(KeyListener kl){
textField.addKeyListener(kl);
}
public void addCustomWindowListener(WindowListener guiWindowListener) {
addWindowListener(guiWindowListener);
}
public void appendText(String text){
displayArea.append("\n"+ text);
}
private void init() {
JPanel panel = new JPanel();
JPanel southPanel = new JPanel();
JPanel northPanel = new JPanel();
connectButton = new JButton("connect");
connectButton.setFocusable(false);
disconnectButton = new JButton("disconnect");
disconnectButton.setFocusable(false);
textField = new JTextField(20);
displayArea = new JTextArea();
displayArea.setEditable(false);
displayArea.setPreferredSize(new Dimension(300,250));
panel.setLayout(new BorderLayout());
southPanel.setLayout(new FlowLayout());
northPanel.setLayout(new FlowLayout());
northPanel.add(displayArea);
southPanel.add(connectButton);
southPanel.add(disconnectButton);
panel.add(textField,BorderLayout.CENTER);
panel.add(southPanel, BorderLayout.SOUTH);
panel.add(northPanel, BorderLayout.NORTH);
this.getContentPane().add(panel);
disconnectButton.setEnabled(false);
System.out.println(textField.hasFocus());
}
public JTextField getTextField() {
return textField;
}
public void setTextField(JTextField textField) {
this.textField = textField;
}
public JTextArea getDisplayArea() {
return displayArea;
}
public void setDisplayArea(JTextArea displayArea) {
this.displayArea = displayArea;
}
public JButton getConnectButton() {
return connectButton;
}
public void setConnectButton(JButton connectButton) {
this.connectButton = connectButton;
}
public JButton getDisconnectButton() {
return disconnectButton;
}
public void setDisconnectButton(JButton disconnectButton) {
this.disconnectButton = disconnectButton;
}
@Override
public void update(Observable observable, Object object) {
displayArea.append("\n"+(String)object);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在不彻底检查您的代码的情况下,我认为这种实现观察者模式的方法没有问题。
MVCGame
是一个相关示例。同步仍然是一个潜在的陷阱:感谢在 事件调度线程,但请注意append()
在 Java 7 中不再是线程安全的。此示例显示了也使用invokeLater()
。Without examining your code thoroughly, I see no problem with this approach to implementing the observer pattern.
MVCGame
is a related example. Synchronization remains a potential pitfall: Kudos for starting on the event dispatch thread, but beware thatappend()
is no longer thread safe in Java 7. This example shows an alternative that also usesinvokeLater()
.