就这样吧

  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

 

你可能感兴趣的:(就这样吧)