搞了个1.0版本,名字和预定的一样改成OIFaQ了

  服务器没启动的情况下直接启动客户端会报错,测试的时候无所谓,发布的时候一定要注意,下个版本修正。

  得给服务器写个GUI,测试的时候有命令行,发布之后就不对劲了,要关闭还得用任务管理器。

  客户端的独立线程在关闭框架的时候会报错,目前直接catch掉了,感觉不够优雅,之后修正。

  服务器端倒是好像没什么bug了。

  上边这句话说完我自己都不信。

  先把exe和jar发了。

  这小图标画得我自己都陶醉了。

  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 }
Server端
  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 }
Client端

 

posted @ 2014-01-19 13:15  Chihane  阅读(163)  评论(0编辑  收藏  举报