多线程聊天室


  • 利用 Java 的网络套接字和 Swing ,设计一个简单的网络多线程聊天室。

一、服务器套接字

  • 首先是服务器端的套接字,其具体代码如下:
package chating;

// !/usr/bin/env jdk1.8
// encoding:utf-8
//@software:IntelliJ IDEA
//@pack:chating
//@user:彭友聪
//@date:2019/07/27
//@time:上午 9:47
//@project:IDEA_JAVA
//@file:ChatRoomServer.java
//Author:御承扬
//email:2923616405@qq.com


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;

public class ChatRoomServer {
    private ServerSocket serverSocket; // 服务器套接字
    private HashSet<Socket> allSockets; // 客户端套接字集合

    // 聊天室构造方法
    public ChatRoomServer(){
        try {
            serverSocket = new ServerSocket( 4569 );
        } catch (IOException e) {
            e.printStackTrace();
        }
        allSockets = new HashSet<>(  ); //  实例化客户端套接字集合
    }

    // 启动聊天室的方法
    private void startService() throws IOException {
        int i=0;
        while(true) {
            i++;
            Socket s = serverSocket.accept(); // 获得一个客户端连接
            System.out.printf( "用户%d已经进入聊天室\n",i );
            allSockets.add(s);
            new ServerThread(s).start();
        }
    }

    // 服务器线程内部类
    private class ServerThread extends Thread {
        Socket socket; // 客户端套接字

        ServerThread(Socket socket) {
            this.socket = socket;
        }

        public void run() {
            BufferedReader br = null;
            try {
                // 将客户端套接字输入流转为字节流读取
                br = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
                while (true ) {
                    String str = br.readLine(); // 读取到一行之后,则赋值给字符串
                    if(str.contains("%EXIT%")) { // 如果文本内容包含"%EXIT%"
                        allSockets.remove( socket ); // 集合删除此客户端连接
                        // 服务器向所有客户端接口发出退出通知
                        sendMessageToAllClient(str.split( ":" )[1]+"用户已退出聊天室");
                        socket.close();
                        return;
                    }
                    sendMessageToAllClient(str); // 向所有客户端发送此客户端发送来的文本消息
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /* *
         * 发送消息给客户端的方法
         *
         * @param message
         */
         final void sendMessageToAllClient(String message) throws IOException {
             for (Socket s : allSockets) { // 循环集合中所有的客户端连接
                 PrintWriter pw = new PrintWriter( s.getOutputStream() ); // 创建输出流
                 pw.println( message ); // 输入写入文本
                 pw.flush();
             }
         }

    public static void main(String[] args) {
        try {
            new ChatRoomServer().startService();
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

二、客户端套接字

  • 有了服务器套接字,就要有与之相配套的客户端套接字,代码如下:
package chating;

// !/usr/bin/env jdk1.8
// encoding:utf-8
//@software:IntelliJ IDEA
//@pack:chating
//@user:彭友聪
//@date:2019/07/27
//@time:上午 10:14
//@project:IDEA_JAVA
//@file:ChatRoomClient.java
//Author:御承扬
//email:2923616405@qq.com

/*
 *   聊天室客户端
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class ChatRoomClient {
    private Socket socket; // 客户端套接字
    private BufferedReader bufferedReader; // 字节流读取套接字输入流
    private PrintWriter pWriter; // 字节流写入套接字输出流

    /*
     * 聊天室客户端构造方法
     * @param host
     *          服务器 IP 地址
     * @param port
     *          服务器与客户端互联的端口
     */
    public ChatRoomClient(String host, int port) throws UnknownHostException, IOException {
        socket = new Socket( host, port ); // 连接服务器
        bufferedReader = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // 字节流读取套接字输入流
        pWriter = new PrintWriter( socket.getOutputStream() ); // 字节流写入套接字输出流
    }

    /*
     * 聊天室客户端发送消息的方法
     * @param str 客户端发送的消息
     */
    public final void sendMessage(String str) {
        pWriter.println( str );
        pWriter.flush();
    }

    /*
     * 聊天客户端获取消息的方法
     * @return 读取某个客户端发送的消息
     */
    final String receiveMessage() {
        try {
            return bufferedReader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /*
     * 关闭套接字
     */
    public final void close() {
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、用户界面

  • 前面两个部分,是属于后台部分,接下来是属于前台部分的用户界面。
  • 用户界面由两个部分组成,一个登陆和连接界面,一个是聊天界面。

登陆连接界面

  • 登陆连接界面的代码如下:
package chating;

// !/usr/bin/env jdk1.8
// encoding:utf-8
//@software:IntelliJ IDEA
//@pack:chating
//@user:彭友聪
//@date:2019/07/27
//@time:下午 4:28
//@project:IDEA_JAVA
//@file:LinkServerFrame.java
//Author:御承扬
//email:2923616405@qq.com


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class LinkServerFrame extends JFrame {
    private LinkServerFrame() {
        setLayout( null );
        setBounds(540,200, 400,250 );
        setVisible( true );
        setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
        JLabel lbIP = new JLabel( "服务器 IP 地址:" );
        lbIP.setBounds( 10,10,100,30 );
        lbIP.setVisible( true );
        JLabel userName = new JLabel( "用户名:" );
        userName.setBounds( 40,60,50,30 );
        userName.setVisible( true );
        JTextField jfIP = new JTextField(  ); // IP 地址输入框
        jfIP.setBounds( 110,10,250,30 );
        jfIP.setVisible( true );
        JTextField jfUser = new JTextField(  );
        jfUser.setBounds( 110,60,250,30 );
        jfUser.setVisible( true );
        JButton btn = new JButton( "连接服务器" );
        btn.setBounds( 120,100,100,20 );
        btn.setVisible( true );
        btn.addActionListener( new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!jfIP.getText().equals( "" ) && !jfUser.getText().equals( "" )) {
                    dispose(); // 销毁当前窗体
                    // 创建客户端窗体并传参
                    ClientFrame clientFrame = new ClientFrame( jfIP.getText().trim(), jfUser.getText().trim() );
                } else {
                    JOptionPane.showMessageDialog( null,"文本框里的内容不能为空!","警告",JOptionPane.WARNING_MESSAGE );
                }
            }
        } );
        Container center = getContentPane();
        center.add(lbIP);
        center.add( jfIP );
        center.add( userName );
        center.add( jfUser );
        center.add( btn );
        center.setBackground( Color.WHITE );
    }

    public static void main(String[] args) {
       new LinkServerFrame();
    }
}
  • 运行效果如下图:

聊天界面

  • 聊天界面的代码如下:
package chating;

// !/usr/bin/env jdk1.8
// encoding:utf-8
//@software:IntelliJ IDEA
//@pack:chating
//@user:彭友聪
//@date:2019/07/27
//@time:下午 3:31
//@project:IDEA_JAVA
//@file:ClientFrame.java
//Author:御承扬
//email:2923616405@qq.com


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ClientFrame extends JFrame {
    private JTextField tfMessage; // 信息发送文本框
    private JTextArea textArea; // 信息接收文本域
    private ChatRoomClient client; // 客户端连接对象

    // 构造方法
    public ClientFrame(String ip, String userName) {
        super("多线程聊天室");
        setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
        setLayout( null );
        setBounds( 100,50,800,680 );
        Container c = getContentPane();
        // 用户名称
        this.addWindowListener( new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent atg0) {
                int op = JOptionPane.showConfirmDialog( ClientFrame.this,
                        "确定要退出聊天室吗?", "确定", JOptionPane.YES_NO_OPTION); // 弹出提示框
                if (op == JOptionPane.YES_OPTION) {
                    client.sendMessage( "%EXIT%:"+userName ); // 发送信息
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    client.close();
                    System.exit( 0 );
                }
            }
        } );
        // 发送按钮
        JButton btnSend = new JButton( "发送" );
        btnSend.setBounds( 680,550,100,30 );
        btnSend.setVisible( true );
        btnSend.addActionListener( new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Date date = new Date();
                SimpleDateFormat df = new SimpleDateFormat( "yyyy年MM月dd日 HH:mm:ss" ); // 设定日期格式
                client.sendMessage( userName+" "+df.format( date )+": \n"+tfMessage.getText() ); // 向服务器发送消息
                tfMessage.setText(""); // 输入框为空
            }
        } );
        // 实例化客户端对象
        try {
            client = new ChatRoomClient( ip,4569 ); // 创建客户端对象
        } catch(UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        ReadMessageThread messageThread = new ReadMessageThread(); // 创建读取客户端消息的线程类对象
        messageThread.start();
        // 控件初始化
        JLabel text = new JLabel( "消息对话框:" );
        text.setBounds( 10,0,200,30 );
        text.setVisible( true );
        // 消息对话框
        textArea = new JTextArea(  );
        textArea.setBounds( 10,30,750,420 );
        textArea.setVisible( true );
        textArea.setBackground( Color.LIGHT_GRAY);
        textArea.setFont(new Font("标楷体", Font.BOLD, 16)); //设置当前字体。
        // 显示用户名
        JLabel lblUserName = new JLabel( userName );
        lblUserName.setBounds( 10,550,50,30 );
        lblUserName.setVisible( true );
        tfMessage = new JTextField(  );
        tfMessage.setBounds( 50,550,600,30 );
        tfMessage.setVisible( true );
        // 下方面板
        c.add(text);
        c.add(textArea);
        c.add(lblUserName);
        c.add(tfMessage);
        c.add(btnSend);
        c.setBackground( Color.WHITE );
        setVisible( true );
    }
    private class ReadMessageThread extends Thread{
        private ReadMessageThread() {
        }

        public void run() {
            while (true) {
                String str = client.receiveMessage(); // 客户端收到服务器发来的文本内容
                textArea.append( str+"\n" ); // 向文本框添加内容
            }
        }
    }

    public static void main(String[] args) {
        new ClientFrame( "192.168.0.105", "吴雅男" );
    }
}
  • 先运行服务器套接字,在运行连接界面两次,效果如下: