此聊天程序,可以作为java网络学习的入门例子,程序虽小,五脏俱全,不过很多细节问题需要花时间完善。
1、聊天系统主要有两个模块:Client和Server
2、Client端功能:
①产生一个聊天窗口
②根据服务端的ip+端口后,通过Socket连接到服务端
③向服务端发送消息,并接受服务端的消息
④同时将消息显示在聊天窗口上面
3、Server端功能:
①监听端口
②接受服务端的请求,并与服务端建立连接
③接受消息,转发消息至所有服务端
4、代码ChatClient.java
import java.awt.BorderLayout; import java.awt.Frame; import java.awt.TextArea; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; public class ChatClient extends Frame { private Socket s = null; DataOutputStream dos; DataInputStream dis; private boolean bConnected = false; TextField tfTxt; TextArea taContent; // 定义接收消息的线程 Thread tRecv = new Thread(new RecvThread()); public static void main(String[] args) { new ChatClient().lanuchFrame(); } public void lanuchFrame() { this.setLocation(400, 300); this.setSize(300, 300); tfTxt = new TextField(); taContent = new TextArea(); taContent.setEditable(false); this.add(tfTxt, BorderLayout.SOUTH); this.add(taContent, BorderLayout.NORTH); this.pack(); // 添加匿名监听器 this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { disConnect(); System.exit(0); } }); tfTxt.addActionListener(new TFListener()); this.setVisible(true); connect(); tRecv.start(); } /** * 连接服务器 */ public void connect() { try { s = new Socket("127.0.0.1", 8888); //这个地方用来添加服务器ip+端口,根据实际改 dos = new DataOutputStream(s.getOutputStream()); dis = new DataInputStream(s.getInputStream()); System.out.println("connected!"); bConnected = true; } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void disConnect() { /* try { // ①让接收消息的线程停下来 bConnected = false; // ②结束线程 tRecv.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { dos.close(); dis.close(); s.close(); } catch (IOException e) { // TODO Auto-generated catch e.printStackTrace(); } } */ try { // ①让接收消息的线程停下来 bConnected = false; // ②结束线程 tRecv.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 添加监听器的三种方式:①外部类 ②内部类 ③匿名类 private class TFListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { String str = tfTxt.getText().trim(); // taContent.setText(str); tfTxt.setText(""); try { dos.writeUTF(str); dos.flush(); /* * //当dis关闭时,里面包装的流都会被关闭, 导致发一次消息就不能再放第二次了,所有要注释掉dos.close(); */ // dos.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } private class RecvThread implements Runnable { @Override public void run() { try { while (bConnected) { String str = dis.readUTF(); // System.out.println(str); taContent.setText(taContent.getText() + str + "\n"); } } catch (SocketException e) { System.out.println("退出了!和你们说88了"); } catch (IOException e) { // TODO Auto-generated catch block System.out.println("退出了!和你们说88了"); } } } }
5、代码ChatServer.java
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; /** * 设计思路:1 在主线程里面启动一个ServerSocket,不停的监听客户端的请求 * 2:每当接收到了一个客服端,将其Socket包装到一个线程里面,让线程来 处理服务器与客户端的通信 * 3:这样主线程用来接收客服端,子线程用来和每个客户端通信 * * 注意:内部类的权限和非静态实例的一样,不能在静态方法里面new内部类, 只能用外部实例来new内部类 * * @author Administrator * */ public class ChatServer { ServerSocket ss = null; boolean started = false; List<Client> clients = new ArrayList<Client>(); public static void main(String[] args) { new ChatServer().start(); } public void start() { // ①建立一个ServerSocket try { ss = new ServerSocket(8888); started = true; } catch (BindException e1) { // TODO Auto-generated catch block System.out.println("端口使用中..."); System.out.println("请关闭相关的程序并重新运行服务器!"); System.exit(0); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // ②连接客户端 try { while (started) { Socket s = ss.accept(); // 服务器接收一个Socket后,启动一个线程来处理客服端的 // 通信 Client c = new Client(s); System.out.println("a client connected!"); // 使用线程来接收客服端的信息 new Thread(c).start(); clients.add(c); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { ss.close();// 关闭ServerSocket } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /* * 客户端在服务端的一个包装 */ class Client implements Runnable { private Socket s; private DataInputStream dis = null; private DataOutputStream dos = null; private boolean bConnected = false; public Client(Socket s) { this.s = s; try { dis = new DataInputStream(s.getInputStream()); dos = new DataOutputStream(s.getOutputStream()); bConnected = true; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void send(String str) { try { dos.writeUTF(str); } catch (IOException e) { clients.remove(this); System.out.println("对方退出了!我从List里面去掉了"); // e.printStackTrace(); } } @Override public void run() { Client c=null; try { while (bConnected) { String str = dis.readUTF(); // 每接收一个字符串,就全部转发给其他的客户端 System.out.println(str); // for (Client c : clients) { // c.send(str); // } for(int i=0;i<clients.size();i++){ c=clients.get(i); c.send(str); } } } catch (EOFException e) { System.out.println("Client closed!"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (dis != null) dis.close(); if (dos != null) dos.close(); if (s != null) { s.close(); // s=null; } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } } }
6、运行:1)运行ChatServer.java:①为其指定监听端口,我这里设置为8888,
2)运行ChatClient.java:①为其指定连接的ip+端口:这里我用的是本地127.0.0.1:8888,这根据ChatServer运行的主机为主
3)效果图: