Java 多人聊天室

Java实训做的多人聊天室,效果如图:

Java 多人聊天室_第1张图片

 功能:

  • 能够实现多个客户端之间的互相聊天,服务端来负责接收数据和转发数据。
  • 每个客户端可以自己设置和修改自己的名称,并传给服务器让其他客户端知道。
  • 当有客户端加入和退出时,群聊里每个客户端都有提示。(优化了异常检测,意外断开终止时也能不报错并提示用户xxx退出聊天室)
  • 当服务器异常断开或正常退出,客户端都可以提示并且程序不报错。
  • 服务端能够实时的显示在线的人数、  

关键技术: 

  • 集合(服务端并不是对一个客户端进行操作,是多个客户端之间来回通信充当转发数据的功能。服务端需要创建一个集合用来存放服务端连接对象。这样只要监听到客户端发送过来的数据,就可以遍历整个集合,转发给每一个客户端)
  • 多线程(客户端通过主线程来发送数据,还需要再启动一个线程来不断循环接收服务端转发过来的消息。服务端主线程使用while循环来不停的接收socket连接,然后为每个客户端的socket通道分配一个子线程接收)
  • 网络TCP传输的嵌套字socket(实现客户端和服务端之间的数据传输,选择TCP更安全可靠)
  • IO流(通道间的数据传输需要用到io流)

服务端代码:

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.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Iterator;

public class ServerChar extends JFrame {
    private static final int PORT = 8888;
    JTextArea jta = new JTextArea();
    JScrollPane jsp = new JScrollPane(jta);
    private JPanel south = new JPanel(); //面板
    private JLabel jb = new JLabel();
    private JButton stopBtn = new JButton("停止服务器");

    private boolean isStart = false;//服务器是否启动

    private ServerSocket serverSocket = null;

    private ArrayList clientConn = new ArrayList<>();
    private int peopleOnline = 0;

    public ServerChar(){
        this.setTitle("服务端");
        this.add(jsp, BorderLayout.CENTER);
        jta.setEditable(false); //不能输入
        south.add(jb);
        jb.setText("聊天室在线人数: "+peopleOnline+"   ");
        south.add(stopBtn);
        this.add(south,BorderLayout.SOUTH);

        stopBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isStart = false;
                    try {
                        if (serverSocket != null){
                            serverSocket.close();
                            isStart = false;
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }finally {
                        System.exit(0);
                    }
            }
        });

        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                isStart = false;
                try {
                    if (serverSocket != null){
                        serverSocket.close();
                        isStart = false;
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }finally {
                    System.exit(0);
                }
            }
        });
        this.setBounds(200,100,500,500);
        this.setVisible(true);
        startServer();
    }

    //服务器启动的方法
    public void startServer()  {
        try{
            try {
                serverSocket = new ServerSocket(PORT);
                isStart = true;
                jta.append("服务器已启动,等待客户端连接..."+System.lineSeparator());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            while(isStart){
                Socket socket = serverSocket.accept();
                clientConn.add(new ClientConn(socket));
                jta.append("客户端"+"["+socket.getInetAddress()+"-"+socket.getPort()+"]"+"加入聊天室"+System.lineSeparator());
                jb.setText("聊天室在线人数: "+(++peopleOnline)+"   ");
            }
        }catch (SocketException e){
            System.out.println("服务器中断");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    //这个对象属于服务器端的连接对象
    class ClientConn implements Runnable{
        Socket socket = null;
        //构造器 传入socket
        public ClientConn(Socket socket){
            this.socket=socket;
            (new Thread(this)).start();
        }

        @Override//同时接收客户端信息
        public void run() {
            String name = socket.getInetAddress()+"-"+socket.getPort();
            try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            while (isStart){
                //读过来
                String str = dis.readUTF();
                if (str.startsWith("##")){
                    name = str.substring("##".length(),str.length());
                    jta.append("客户端"+"["+socket.getInetAddress()+"-"+socket.getPort()+"]"+"将名称设置为"+"["+name+"]"+System.lineSeparator());
                    Iterator it = clientConn.iterator();
                    while(it.hasNext()) {
                        ClientConn o = it.next();
                        o.send(name+"加入了聊天室"+System.lineSeparator());
                    }
                }else{
                    jta.append("["+name+"]"+"说:"+str+System.lineSeparator());
                    //发过去
                    String strSend = "["+name+"]"+"说:"+str+System.lineSeparator();
                    //要遍历所有的客户端来发送 遍历ccList 调用send方法
                    Iterator it = clientConn.iterator();
                    while(it.hasNext()) {
                        ClientConn o = it.next();
                        o.send(strSend);
                    }
                }
                }
            } catch (SocketException e){
                jta.append("客户端"+"["+name+"]"+"下线了"+System.lineSeparator());
                Iterator it = clientConn.iterator();
                while(it.hasNext()) {
                    ClientConn o = it.next();
                    o.send(name+"退出了聊天室"+System.lineSeparator());
                }
                jb.setText("聊天室在线人数: "+(--peopleOnline)+"   ");
            } catch (IOException e) {
            e.printStackTrace();
            }
    }

        //对每个连接发送信息的方法
        public void send(String str){
            try {
                DataOutputStream dos = new DataOutputStream(this.socket.getOutputStream());
                dos.writeUTF(str);
                dos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ServerChar();
    }

}

 客户端代码: 

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

public class ClientChar extends JFrame implements KeyListener{
    public static void main(String[] args) throws IOException {
        new ClientChar().init();
    }
    //属性
    private boolean isConn = false;
    String name = "";

    private JTextArea jta;//文本域
    private JScrollPane jsp;//滚动条
    private JPanel jp;//面板
    private JTextField jtf;//文本框
    private JButton jb1;//发送信息
    //通道
    private Socket socket = null;
    private static final String CONNSTR = "127.0.0.1";
    private static final int CONNPORT = 8888;
    //流
    private DataOutputStream dos = null;

    //构造器
    public ClientChar(){
        //初始化组件
        jta = new JTextArea();
        jta.setEditable(false);//设置为不可编辑
        jsp = new JScrollPane(jta); //让文本域实现滚动效果
        //面板
        jp = new JPanel();
        jtf = new JTextField(15);
        jb1 = new JButton("发送信息");
    }

    public void init(){
        //将文本框和按钮添加到面板中
        jp.add(jtf);
        jtf.addKeyListener(this);
        jp.add(jb1);
        jb1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取文本框内容
                String text = jtf.getText();
                sendMessage(text);
                if (text.length()==0){
                    return;
                }
                //拼接内容并发送
                jtf.setText("");
            }
        });

        //将滚动条与面板全部添加到窗口中
        this.add(jsp, BorderLayout.CENTER);
        this.add(jp, BorderLayout.SOUTH);

        //设置窗口
        this.setBounds(700, 300, 300, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        try {
            socket = new Socket(CONNSTR,CONNPORT);
            isConn = true;
            name = JOptionPane.showInputDialog(this.getContentPane(), "请输入你的用户名:");
            dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF("##"+name);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        this.setTitle("聊天室-"+name);
        //启动线程类接收
        new Thread(new Receive()).start();

    }

    //发送信息到服务器的方法
    private void sendMessage(String str) {
        try {
            if(jtf.getText().length()==0){
                return;
            }
            dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(str);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode()==KeyEvent.VK_ENTER){
            //回去输入框的内容
            String text = jtf.getText();
            //调用发送信息的方法
            sendMessage(text);
            //发送完后让输入框为空
            jtf.setText("");
        }
    }

    //多线程的类,实现了Runnable接口 用来接收
    class Receive implements Runnable{
        @Override
        public void run() {
            try {
                while (isConn) {
                    DataInputStream dis = new DataInputStream(socket.getInputStream());
                    String str = dis.readUTF(); //从管道读出服务端转发的数据 放到文本框里面
                    jta.append(str);
                    }
                } catch (SocketException e) {
                    System.out.println("服务器终止");
                    jta.append("服务器终止"+System.lineSeparator());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void keyTyped(KeyEvent e) {

    }
}

如果有帮助的话麻烦点赞收藏,谢谢。你的支持是对我最大的鼓励。

你可能感兴趣的:(java)