1.1 主程序入口
在主程序入口处,通过设置MyWindow的第一个参数,如果为true则为服务器,如果为false,则为客户端,当然也可以设置第二个参数,区分客户端和服务器的窗口标题。
public class JavaMain { public static void main(String[] args) { MyWindow w=new MyWindow(false,"QQ聊天"); //运行时将false改成true, 先启动服务端,然后再改成false启动客户端 w.setNet("192.168.1.103", 12345); } }
1.2 界面程序
界面程序根据主程序传来的参数不同而创建客户端和服务器窗口,根据界面的构造函数中第一个参数,isServer设置服务器窗体或者是客户端窗体。
public class MyWindow extends JFrame { private static final long serialVersionUID = 1L; // 定义一个成员变量 Client myClient = null; Server myServer=null; JTextArea area=null; // 设置默认的IP地址和端口 private String ipAddress="127.0.0.1"; private int nPort=50000; private boolean isServer=false; // 构造函数 public MyWindow(boolean isServer,String title) { this.isServer=isServer; setTitle(title); setSize(300, 400); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLayout(null); initComponents(); setVisible(true); } // 为外界提供设置IP地址和端口的方法 public void setNet(String ip,int port){ ipAddress=ip; nPort=port; // 对通信接口初始化 initCommication(); } // 通过构造函数,将端口和文本显示区传递给myServer对象 public void initCommication(){ if (isServer) { myServer=new Server(nPort,area); }else{ myClient=new Client(ipAddress,nPort,area); } } public void initComponents() { // 添加一个文本区 area = new JTextArea(); area.setBounds(10, 20, 260, 200); add(area); // 添加一个文本框 final JTextField text = new JTextField(); text.setBounds(10, 240, 260, 30); add(text); // 添加一个发送按钮 JButton button = new JButton("发送"); button.setBounds(10, 290, 80, 30); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (isServer) { myServer.sendServerMsg(text.getText()); }else{ myClient.sendMsg(text.getText()); } } }); add(button); } }
1.3 服务器类
服务器类比较复杂,这里用到了内部类,内部类的好处就是能随时访问外部类的成员和方法,而无需通过传参数的方法达到目的。
// 监听主线程 public class Server extends Thread { // 服务器Socket private ServerSocket serverSocket = null; private ArrayList<ServerThread> clientList = null; // 显示区 private JTextArea jTextArea = null; // 构造函数 public Server(int port, JTextArea area) { jTextArea = area; try { // 开始绑定端口 serverSocket = new ServerSocket(port); // 初始化客户端连接的列表 clientList = new ArrayList<ServerThread>(); } catch (IOException e) { System.err.println("服务器端口初始化失败! "); } jTextArea.setText("服务器成功启动,等待客户连接! "); start(); // 启动线程 } public void sendServerMsg(String msg) { jTextArea.append(msg + " "); for (int i = clientList.size() - 1; i >= 0; i--) { clientList.get(i).getWriter().println("服务器:" + msg); clientList.get(i).getWriter().flush(); } } // 线程程序 public void run() { while (true) { try { // 用阻塞的方式,等待用户连接请求 Socket socket = serverSocket.accept(); // 启动一条为客户端服务的线程 ServerThread svthread = new ServerThread(socket); svthread.start(); // 将该客户加入列表中 clientList.add(svthread); } catch (IOException e) { e.printStackTrace(); } } } // 服务线程(内部类),用于处理客户端的服务线程 class ServerThread extends Thread { // 当前正在连接的Socket Socket socket = null; // 当前连接的Socket的输入和输出流(数据出入口) private PrintWriter writer = null; private BufferedReader reader = null; // 构造函数 public ServerThread(Socket s) { socket = s; try { // 获取输入输出流 reader = new BufferedReader(new InputStreamReader( s.getInputStream())); writer = new PrintWriter(s.getOutputStream()); // 在此可以写接收用户端的信息,解析出来(IP地址) } catch (Exception e) { e.printStackTrace(); } } // 获得输入流,供外界调用 public BufferedReader getReader() { return reader; } // 获得输出流,供外界调用 public PrintWriter getWriter() { return writer; } // 获得socket public Socket getSocket() { return socket; } // 线程服务程序 public void run() { // 创建一个变量,用于接收客户端发来的信息 String message = null; while (true) { try { // 读取输入流 message = reader.readLine(); // 如果是下线命令 if (message.equals("Bye")) { // 在客户端列表上删除该用户 ServerThread temp = null; for (int i = clientList.size() - 1; i >= 0; i--) { temp = clientList.get(i); if (temp.getSocket().equals(socket)) { clientList.remove(i); } temp.stop(); } // 断开连接释放资源 reader.close(); writer.close(); socket.close(); return; } else { // 在文本区显示该消息 jTextArea.append(message + " "); // 将该消息广播给其他用户 broadcastMsg(message); } } catch (Exception e) { // TODO: handle exception } } } // 广播消息 public void broadcastMsg(String msg) { for (int i = clientList.size() - 1; i >= 0; i--) { clientList.get(i).getWriter().println(msg); clientList.get(i).getWriter().flush(); } } } }
1.4 客户端类
客户端类比较简单,只要创建一个线程,与服务器连接即可。
public class Client extends Thread { // 写该类的成员变量 private Socket socket=null; private PrintWriter out=null; private BufferedReader in=null; private JTextArea area; // 写构造函数,完成初始化 public Client(String ip,int nPort,JTextArea area){ try { socket=new Socket(ip, nPort); } catch (UnknownHostException e) { System.err.println("初始化失败!"); } catch (IOException e) { System.err.println("初始化失败!"); } start(); // 启动线程,让线程开始工作 this.area=area; this.area.setText("初始化成功,连接上服务器! "); } // 发送消息 public void sendMsg(String msg){ if(socket.isConnected()==true){ out.println("客户端:"+msg); out.flush();// 将消息推送给客户端 } } public void run(){ while(true){ try { // 获取客户端的输入流 InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); in = new BufferedReader(isr); // 获取客户端的输出流 out = new PrintWriter(socket.getOutputStream(), true); while(true){ // 不停的读取从服务器发来的信息 String info = in.readLine(); area.append(info + " "); } } catch (Exception e) { System.err.println("发生数据流读取错误,程序退出!"); System.exit(1); } finally{ try { // 结束,扫尾工作 out.close(); in.close(); socket.close(); } catch (Exception e2) { } } } } }
1.5 运行结果
运行结果如下所示: