就这样吧

  bug基本解决了,用两台电脑加上hamachi测试了一下,能用。

  干,刚说完拿自己的外网IP测试了一下,报空指针异常,懒得改了。理由大概是虽然连不上服务器的IP会报错,但是其实你随便连个谁的IP也能连上,所以程序还会执行下去,但是因为对面没有人给你接收,发包没人接,就 报错了。

  我猜的。

  有外网服务器当然更好,没有就只能挂hamachi或者用内网映射什么的我不懂的东西。

  

  分成两个部分,随便哪台机器运行OIFaQ-Server启动服务器,是第三者的机器也无所谓。

  然后用户启动OIFaQ-Client就完事了。

 

  还是挺直观的。

 

  OIFaQV1.1.rar

  惯例发一下代码,就这样吧,将来重构:

  1 import java.io.*;
  2 import java.net.*;
  3 import java.util.ArrayList;
  4 import java.awt.BorderLayout;
  5 import java.awt.Color;
  6 import java.awt.Dimension;
  7 
  8 import javax.swing.*;
  9 
 10 /**
 11  * 服务器端。
 12  * 增加了GUI来显示服务器日志。
 13  * 
 14  * @author mlxy
 15  * @version 1.1
 16  * */
 17 public class SimChatServer extends JFrame {
 18     private static final long serialVersionUID = 1L;
 19     ServerSocket ss = null;
 20     ArrayList<Client> clients = null;
 21     
 22     JTextArea log = null;
 23     JScrollPane scrollPane;
 24     
 25     /** 服务器构造方法。*/
 26     public SimChatServer() {
 27         // 初始化已连接客户端列表。
 28         clients = new ArrayList<Client>();
 29         
 30         // 初始化框架内容。
 31         JPanel mainPanel = new JPanel();
 32         mainPanel.setLayout(new BorderLayout());
 33         mainPanel.setPreferredSize(new Dimension(400, 500));
 34         
 35         log = new JTextArea();
 36         log.setBackground(new Color(66, 204, 255));
 37         log.setEditable(false);
 38         log.setLineWrap(true);
 39         log.setWrapStyleWord(true);
 40         
 41         scrollPane = new JScrollPane(log);
 42         
 43         mainPanel.add(scrollPane, BorderLayout.CENTER);
 44         add(mainPanel);
 45         pack();
 46     }
 47     
 48     /** 主方法。*/
 49     public static void main(String[] args) {
 50         try {
 51             UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
 52         } catch (UnsupportedLookAndFeelException e) {
 53             e.printStackTrace();
 54         } catch (ClassNotFoundException e) {
 55             e.printStackTrace();
 56         } catch (InstantiationException e) {
 57             e.printStackTrace();
 58         } catch (IllegalAccessException e) {
 59             e.printStackTrace();
 60         }
 61         
 62         SimChatServer server = new SimChatServer();
 63         server.launchFrame();
 64         server.startUp();
 65     }
 66     
 67     /** 初始化框架。*/
 68     public void launchFrame() {
 69         setTitle("OIFaQ服务端日志");
 70         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 71         setLocationRelativeTo(null);
 72         setVisible(true);
 73     }
 74     
 75     /** 启动服务器。*/
 76     public void startUp() {
 77         // 尝试启动服务器,端口被占用则弹窗提示并退出。
 78         try {
 79             ss = new ServerSocket(2333);
 80         } catch (BindException e) {
 81             JOptionPane.showMessageDialog(null,
 82                     "Port occupied.", "Error", JOptionPane.WARNING_MESSAGE);
 83             System.exit(0);
 84         } catch (IOException ex) {
 85             ex.printStackTrace();
 86         }
 87 
 88         // 死循环,等待客户端连接,连接成功则启动一个新线程。
 89         try {
 90             while (true) {
 91                 Client newClient = new Client(ss.accept());
 92                 newClient.start();
 93                 clients.add(newClient);
 94                 updateLog("Current clients: " + clients.size());
 95             }
 96         } catch (IOException e) {
 97             e.printStackTrace();
 98         }
 99     }
100     
101     /**
102      * 内部类。
103      * 单个客户端线程,包含了处理客户端发来信息的功能,以及向客户端发送信息的方法。
104      * 
105      * @author mlxy
106      * @version 1.0
107      */
108     private class Client extends Thread {
109         Socket singleSocket;
110         ObjectInputStream singleInput;
111         ObjectOutputStream singleOutput;
112         
113         /** 一个客户端线程的构造方法。*/
114         public Client(Socket singleSocket) {
115             this.singleSocket = singleSocket;
116             updateLog("Client connected.");
117             try {
118                 singleInput = new ObjectInputStream(singleSocket.getInputStream());
119                 singleOutput = new ObjectOutputStream(singleSocket.getOutputStream());
120             } catch (IOException e) {
121                 e.printStackTrace();
122             }
123         }
124 
125         @Override
126         public void run() {
127             // 主运行部分,抓到客户端退出的异常就打印提示并退出。
128             try {
129                 // 死循环,读包拆包打印。
130                 while (true) {
131                     String[] packReceived = (String[]) singleInput.readObject();
132                     updateLog("[" + packReceived[0] + "]: " + packReceived[1]);
133                     for (int i = 0; i < clients.size(); i++) {
134                         clients.get(i).sendPackage(packReceived);
135                     }
136                 }
137                 
138             } catch (ClassNotFoundException e) {
139                 e.printStackTrace();
140             } catch (EOFException ex) {
141                 updateLog("Client disconnected.");
142             } catch (IOException exc) {
143                 exc.printStackTrace();
144             } finally {
145                 // 进程准备结束,释放相关资源。
146                 try {
147                     if (singleSocket != null)
148                         singleSocket.close();
149                     if (singleInput != null)
150                         singleInput.close();
151                     if (singleOutput != null)
152                         singleOutput.close();
153                     
154                     // 报告当前客户端退出。
155                     clientOut();
156                 } catch (IOException e) {
157                     e.printStackTrace();
158                 }
159             }
160         }
161         
162         /** 向客户端发包的方法。*/
163         public void sendPackage(Object o) {
164             try {
165                 singleOutput.writeObject(o);
166                 singleOutput.flush();
167             } catch (IOException e) {
168                 e.printStackTrace();
169             }
170         }
171         
172         /** 管理客户端退出的方法。*/
173         private void clientOut() {
174             /*
175             // 找出当前客户端所在下标并标记删除。不能一边遍历一边修改列表哇,同志。
176             int indexToRemove = -1;
177             for (int i = 0; i < clients.size(); i++) {
178                 if (clients.get(i).equals(this)) {
179                     indexToRemove = i;
180                     break;
181                 }
182             }
183             clients.remove(indexToRemove);
184             */
185             
186             // 同志我跟你讲,你看着像个呆逼。
187             clients.remove(this);
188             updateLog("Current clients: " + clients.size());
189         }
190     }
191     
192     /** 把一个字符串追加到当前日志内容的末尾。*/
193     private void updateLog(String s) {
194         log.setText(log.getText() + s + "\n");
195         JScrollBar tempBar = scrollPane.getVerticalScrollBar();
196         tempBar.setValue(tempBar.getMaximum());
197     }
198 }
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  * 修改了更新文本框内容的方法,删掉了用来存储内容的StringBuffer,改为动态更新。
 12  * 在服务器未启动的情况下启动客户端会弹窗报错并退出。
 13  * ServerWaiter类没找到更好的方法,留待将来解决吧。
 14  * 
 15  * @author mlxy
 16  * @version 1.1
 17  * */
 18 public class SimChatClient extends JFrame {
 19     private static final long serialVersionUID = 1L;
 20     
 21     // 框架相关变量。
 22     private JPanel panel;
 23     private JTextArea area;
 24     private JTextField field;
 25     private JScrollPane scrollPane;
 26     
 27     // 用户相关变量。
 28     private String userID;
 29 
 30     // 网络相关变量。
 31     private String serverIP;
 32     //private int serverPort; *备用*
 33     private Socket socket = null;
 34     private ObjectOutputStream oos = null;
 35     private ObjectInputStream ois = null;
 36 
 37     /** 默认构造方法,初始化用户ID及框架内容。 */
 38     public SimChatClient() {
 39         String userName = (String) JOptionPane.showInputDialog(
 40                 null, "请输入用户ID:", "新用户", JOptionPane.PLAIN_MESSAGE,
 41                 null, null, new String("Default"));
 42         if (userName == null)
 43             System.exit(0);
 44         else
 45             userID = userName;
 46         
 47         String ip = (String) JOptionPane.showInputDialog(
 48                 null, "请输入服务器IP:", "确认客户端IP", JOptionPane.PLAIN_MESSAGE,
 49                 null, null, new String("192.168.1.104"));
 50         if (ip == null)
 51             System.exit(0);
 52         else
 53             serverIP = ip;
 54 
 55         panel = new JPanel();
 56         panel.setLayout(new BorderLayout());
 57 
 58         area = new JTextArea();
 59         area.setLineWrap(true);
 60         area.setWrapStyleWord(true);
 61         area.setEditable(false);
 62         area.setBackground(new Color(255, 204, 255));
 63         scrollPane = new JScrollPane(area);
 64 
 65         field = new JTextField();
 66         field.setBackground(new Color(66, 204, 255));
 67         field.addActionListener(new FieldListener());
 68 
 69         panel.add(scrollPane, BorderLayout.CENTER);
 70         panel.add(field, BorderLayout.SOUTH);
 71 
 72         this.add(panel);
 73         this.addWindowListener(new WindowAdapter() {
 74 
 75             @Override
 76             public void windowClosing(WindowEvent e) {
 77                 disconnect();
 78                 System.exit(0);
 79             }
 80 
 81             @Override
 82             public void windowActivated(WindowEvent e) {
 83                 super.windowActivated(e);
 84                 field.requestFocusInWindow();
 85             }
 86         });
 87     }
 88 
 89     /** 主运行方法。 */
 90     public static void main(String[] args) {
 91         // 设置UI风格。
 92         try {
 93             UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
 94         } catch (UnsupportedLookAndFeelException e) {
 95             e.printStackTrace();
 96         } catch (ClassNotFoundException e) {
 97             e.printStackTrace();
 98         } catch (InstantiationException e) {
 99             e.printStackTrace();
100         } catch (IllegalAccessException e) {
101             e.printStackTrace();
102         }
103         
104         SimChatClient frame = new SimChatClient();
105         frame.launchFrame();
106     }
107 
108     /** 框架参数初始化。 */
109     public void launchFrame() {
110         setTitle("Simple Chat Online: " + userID);
111         setSize(400, 500);
112         setLocationRelativeTo(null);
113         setVisible(true);
114 
115         connect();
116     }
117 
118     /** 连接到服务器端。 */
119     private void connect() {
120         try {
121             socket = new Socket(serverIP, 2333);
122             oos = new ObjectOutputStream(socket.getOutputStream());
123             new Thread(new ServerWaiter()).start();
124         } catch(ConnectException e) {
125             JOptionPane.showMessageDialog(null, 
126                     "服务器未启动,请联系系统管理员。", "连接失败", JOptionPane.WARNING_MESSAGE);
127             System.exit(0);
128         } catch (IOException e) {
129             e.printStackTrace();
130         }
131         area.setText("Connection established.\n");
132     }
133 
134     /** 释放资源,与服务器断开连接。 */
135     private void disconnect() {
136         try {
137             if (ois != null)
138                 ois.close();
139             if (oos != null)
140                 oos.close();
141         } catch (IOException e) {
142             e.printStackTrace();
143         }
144     }
145     
146     /** 
147      * 按照格式更新文本框内容。
148      * 
149      * @param userName 用户名
150      * @param content 消息内容
151      */
152     private void updateTextArea(String userName, String content) {
153         // 之后加上发送时间。
154         area.setText(area.getText() + "[ " + userName + " ] : " + content + "\n");
155         JScrollBar tempBar = scrollPane.getVerticalScrollBar();
156         tempBar.setValue(tempBar.getMaximum());
157     }
158 
159     /** 内部类,输入框的监听器。 */
160     private class FieldListener implements ActionListener {
161 
162         @Override
163         public void actionPerformed(ActionEvent e) {
164             // 按下回车后向服务器发包,然后清空输入框。
165             sendPackage();
166             field.setText("");
167         }
168 
169         /** 私有方法,读取用户ID和输入内容,向服务器发送包。 */
170         private void sendPackage() {
171             String[] packToSend = { userID, field.getText() };
172 
173             try {
174                 oos.writeObject(packToSend);
175                 oos.flush();
176             } catch (IOException ex) {
177                 ex.printStackTrace();
178             }
179         }
180     }
181 
182     /** 内部类,等待服务器发包的线程类。
183      * 
184      * @author mlxy
185      * @version 0.8
186      * */
187     class ServerWaiter implements Runnable {
188         public void run() {
189             try {
190                 ois = new ObjectInputStream(socket.getInputStream());
191 
192                 // 死循环,阻塞式地从服务器读包,之后更新文本框。
193                 while (true) {
194                     // 程序被readObject阻塞住的时候如果关闭框架会报错。之后解决。
195                     String[] packReceived = (String[]) ois.readObject();
196                     updateTextArea(packReceived[0], packReceived[1]);
197                 }
198             } catch (SocketException e) {
199                 // 目前是简单粗暴地抓了异常就跳出程序。
200                 System.out.println("Socket closed, client out.");
201                 System.exit(-1);
202             } catch (IOException e) {
203                 e.printStackTrace();
204             } catch (ClassNotFoundException e) {
205                 e.printStackTrace();
206             }
207         }
208     }
209 }
Client

 

posted @ 2014-01-19 22:23  Chihane  阅读(179)  评论(0编辑  收藏  举报