JavaSE简单实现多线程聊天

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 运行结果

运行结果如下所示:

原文地址:https://www.cnblogs.com/StanLong/p/6781132.html