搞了个1.0版本,名字和预定的一样改成OIFaQ了
服务器没启动的情况下直接启动客户端会报错,测试的时候无所谓,发布的时候一定要注意,下个版本修正。
得给服务器写个GUI,测试的时候有命令行,发布之后就不对劲了,要关闭还得用任务管理器。
客户端的独立线程在关闭框架的时候会报错,目前直接catch掉了,感觉不够优雅,之后修正。
服务器端倒是好像没什么bug了。
上边这句话说完我自己都不信。
这小图标画得我自己都陶醉了。
1 import java.io.*; 2 import java.net.*; 3 import java.util.ArrayList; 4 5 /** 6 * 服务器端。 7 * 基本功能已经完成。 8 * 实现了对每个客户端启动一个线程,阻塞式地从每个客户端接收消息,接收到之后向所有客户端发送。 9 * 越级升到V1.0。 10 * 11 * @author mlxy 12 * @version 1.0 13 * */ 14 public class SimChatServer { 15 ServerSocket ss = null; 16 ArrayList<Client> clients = null; 17 18 /** 服务器构造方法,只用来初始化已连接客户端数。*/ 19 public SimChatServer() {clients = new ArrayList<Client>();} 20 21 /** 主方法。*/ 22 public static void main(String[] args) { 23 SimChatServer server = new SimChatServer(); 24 server.startUp(); 25 } 26 27 /** 启动服务器。*/ 28 public void startUp() { 29 // 尝试启动服务器,端口被占用则提示并退出。 30 try { 31 ss = new ServerSocket(2333); 32 } catch (BindException e) { 33 System.out.println("Port occupied."); 34 System.exit(0); 35 } catch (IOException ex) { 36 ex.printStackTrace(); 37 } 38 39 // 死循环,等待客户端连接,连接成功则启动一个新线程。 40 try { 41 while (true) { 42 Client newClient = new Client(ss.accept()); 43 newClient.start(); 44 clients.add(newClient); 45 System.out.println("Current clients: " + clients.size()); 46 } 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } 50 } 51 52 /** 53 * 内部类。 54 * 单个客户端线程,包含了处理客户端发来信息的功能,以及向客户端发送信息的方法。 55 * 56 * @author mlxy 57 * @version 1.0 58 */ 59 private class Client extends Thread { 60 Socket singleSocket; 61 ObjectInputStream singleInput; 62 ObjectOutputStream singleOutput; 63 64 /** 一个客户端线程的构造方法。*/ 65 public Client(Socket singleSocket) { 66 this.singleSocket = singleSocket; 67 System.out.println("Client connected."); 68 try { 69 singleInput = new ObjectInputStream(singleSocket.getInputStream()); 70 singleOutput = new ObjectOutputStream(singleSocket.getOutputStream()); 71 } catch (IOException e) { 72 e.printStackTrace(); 73 } 74 } 75 76 @Override 77 public void run() { 78 // 主运行部分,抓到客户端退出的异常就打印提示并退出。 79 try { 80 // 死循环,读包拆包打印。 81 while (true) { 82 String[] packReceived = (String[]) singleInput.readObject(); 83 System.out.println("[" + packReceived[0] + "]: " + packReceived[1]); 84 for (int i = 0; i < clients.size(); i++) { 85 clients.get(i).sendPackage(packReceived); 86 } 87 } 88 89 } catch (ClassNotFoundException e) { 90 e.printStackTrace(); 91 } catch (EOFException ex) { 92 System.out.println("Client disconnected."); 93 } catch (IOException exc) { 94 exc.printStackTrace(); 95 } finally { 96 // 进程准备结束,释放相关资源。 97 try { 98 if (singleSocket != null) 99 singleSocket.close(); 100 if (singleInput != null) 101 singleInput.close(); 102 if (singleOutput != null) 103 singleOutput.close(); 104 105 // 报告当前客户端退出。 106 clientOut(); 107 } catch (IOException e) { 108 e.printStackTrace(); 109 } 110 } 111 } 112 113 /** 向客户端发包的方法。*/ 114 public void sendPackage(Object o) { 115 try { 116 singleOutput.writeObject(o); 117 singleOutput.flush(); 118 } catch (IOException e) { 119 e.printStackTrace(); 120 } 121 } 122 123 /** 管理客户端退出的方法。*/ 124 private void clientOut() { 125 // 找出当前客户端所在下标并标记删除。不能一边遍历一边修改列表哇,同志。 126 int indexToRemove = -1; 127 for (int i = 0; i < clients.size(); i++) { 128 if (clients.get(i).equals(this)) { 129 indexToRemove = i; 130 break; 131 } 132 } 133 clients.remove(indexToRemove); 134 System.out.println("Current clients: " + clients.size()); 135 } 136 } 137 }
1 import java.awt.*; 2 import java.awt.event.*; 3 4 import javax.swing.*; 5 6 import java.io.*; 7 import java.net.*; 8 9 /** 10 * 客户端程序。 11 * 基本上实现了向服务器发包与从服务器收包的功能,已经达成基本需要,更新到V1.0。 12 * ServerWaiter类还不完善,具体请参考注释。 13 * 14 * @author mlxy 15 * @version 1.0 16 * */ 17 public class SimChatClient extends JFrame { 18 private static final long serialVersionUID = 1685352502599359548L; 19 20 // 框架相关变量。 21 private JPanel panel; 22 private JTextArea area; 23 private JTextField field; 24 25 // 用户相关变量。 26 private String userID; 27 private StringBuffer chatRecord = new StringBuffer(""); 28 29 // 网络相关变量。 30 //private String serverIP; *备用* 31 //private int serverPort; *备用* 32 private Socket socket = null; 33 private ObjectOutputStream oos = null; 34 private ObjectInputStream ois = null; 35 36 /** 默认构造方法,初始化用户ID及框架内容。 */ 37 public SimChatClient() { 38 String userName = (String) JOptionPane.showInputDialog( 39 null, "请输入用户ID:", "新用户", JOptionPane.PLAIN_MESSAGE, 40 null, null, new String("Default")); 41 if (userName == null) 42 System.exit(0); 43 else 44 userID = userName; 45 46 panel = new JPanel(); 47 panel.setLayout(new BorderLayout()); 48 49 area = new JTextArea(); 50 area.setLineWrap(true); 51 area.setWrapStyleWord(true); 52 area.setEditable(false); 53 area.setBackground(new Color(255, 204, 255)); 54 JScrollPane scrollPane = new JScrollPane(area); 55 56 field = new JTextField(); 57 field.setBackground(new Color(66, 204, 255)); 58 field.addActionListener(new FieldListener()); 59 60 panel.add(scrollPane, BorderLayout.CENTER); 61 panel.add(field, BorderLayout.SOUTH); 62 63 this.add(panel); 64 this.addWindowListener(new WindowAdapter() { 65 66 @Override 67 public void windowClosing(WindowEvent e) { 68 disconnect(); 69 System.exit(0); 70 } 71 72 @Override 73 public void windowActivated(WindowEvent e) { 74 super.windowActivated(e); 75 field.requestFocusInWindow(); 76 } 77 }); 78 } 79 80 /** 主运行方法。 */ 81 public static void main(String[] args) { 82 try { 83 UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); 84 } catch (UnsupportedLookAndFeelException e) { 85 e.printStackTrace(); 86 } catch (ClassNotFoundException e) { 87 e.printStackTrace(); 88 } catch (InstantiationException e) { 89 e.printStackTrace(); 90 } catch (IllegalAccessException e) { 91 e.printStackTrace(); 92 } 93 SimChatClient frame = new SimChatClient(); 94 frame.launchFrame(); 95 new Thread(frame.new ServerWaiter()).start(); 96 } 97 98 /** 框架参数初始化。 */ 99 public void launchFrame() { 100 setTitle("Simple Chat Online: " + userID); 101 setSize(400, 500); 102 setLocationRelativeTo(null); 103 setVisible(true); 104 105 connect(); 106 } 107 108 /** 连接到服务器端。 */ 109 private void connect() { 110 try { 111 socket = new Socket("192.168.1.104", 2333); 112 oos = new ObjectOutputStream(socket.getOutputStream()); 113 } catch (IOException e) { 114 e.printStackTrace(); 115 } 116 area.setText("Connection established."); 117 } 118 119 /** 释放资源,与服务器断开连接。 */ 120 private void disconnect() { 121 try { 122 ois.close(); 123 oos.close(); 124 System.exit(0); 125 } catch (IOException e) { 126 e.printStackTrace(); 127 } 128 } 129 130 /** 131 * 按照格式更新文本框内容。 132 * 133 * @param userName 用户名 134 * @param content 消息内容 135 */ 136 private void updateTextArea(String userName, String content) { 137 // 之后加上发送时间。 138 chatRecord.append("[ " + userName + " ] : " + content + "\n"); 139 area.setText(chatRecord.toString()); 140 } 141 142 /** 内部类,输入框的监听器。 */ 143 private class FieldListener implements ActionListener { 144 145 @Override 146 public void actionPerformed(ActionEvent e) { 147 // 按下回车后向服务器发包,然后清空输入框。 148 sendPackage(); 149 field.setText(""); 150 } 151 152 /** 私有方法,读取用户ID和输入内容,向服务器发送包。 */ 153 private void sendPackage() { 154 String[] packToSend = { userID, field.getText() }; 155 156 try { 157 oos.writeObject(packToSend); 158 oos.flush(); 159 } catch (IOException ex) { 160 ex.printStackTrace(); 161 } 162 } 163 } 164 165 /** 内部类,等待服务器发包的线程类。 166 * 167 * @author mlxy 168 * @version 0.8 169 * */ 170 class ServerWaiter implements Runnable { 171 public void run() { 172 try { 173 ois = new ObjectInputStream(socket.getInputStream()); 174 175 // 死循环,阻塞式地从服务器读包,之后更新文本框。 176 while (true) { 177 // 程序被readObject阻塞住的时候如果关闭框架会报错。之后解决。 178 String[] packReceived = (String[]) ois.readObject(); 179 updateTextArea(packReceived[0], packReceived[1]); 180 } 181 } catch (SocketException e) { 182 // 目前是简单粗暴地抓了异常就跳出程序。 183 System.out.println("Socket closed, client out."); 184 System.exit(-1); 185 } catch (IOException e) { 186 e.printStackTrace(); 187 } catch (ClassNotFoundException e) { 188 e.printStackTrace(); 189 } 190 } 191 } 192 }