基于TCP/IP 协议的多人同步聊天,多线程(UI)

这次实现的是,多人同步聊天,而且客户端是具有UI的客户端聊天界面的。如下图所示:
基于TCP/IP 协议的多人同步聊天,多线程(UI)_第1张图片
我开启了三个客户端,每个客户端都有一个自己的聊天界面,并且每个人说话都是可以实时的显示在别人的UI界面中的。并且聊天窗口实现了滚屏的效果。很棒!

下面我先把代码的结构截图一下:
基于TCP/IP 协议的多人同步聊天,多线程(UI)_第2张图片
可以看到,在Chat2的模块下,我们有三个包,一个是Client客户端,一个是Server服务端,另外一个是Util工具包。
1、首先来介绍Server包:

  • Server 类是用来创建服务端的ServerScoket的对象的,等待连接的客户端。
  • MyChannel 类是一个线程类,每当有一个客户端进来连接的时候,我们都会创建这个线程类,与客户端进行连接,接收客户端发来的信息,并转发给在线的每一个客户端。
    2、Client包:
  • Client 类是客户端的类,包含了建立UI的界面,实现了界面监听器的接口。在发送的窗口上,监听发送的动作,我们便会读取在文本框内中内容,写在文本域中,并发送给与之连接的服务器端。
  • Receive类,是客户端另外开启的一个线程类,每当我们创建一个客户端对象的时候,我们会开启一个接收信息的线程类,当我们从服务器端收到发来的信息的时候,便会把信息写在客户端UI界面的文本域中,这样我们便实现了群聊的功能。
    3、Util包:
  • 是帮助我们释放资源的,其实参数是可变参数。

下面我们来看一下具体的代码实现:

1、Client:
-Client:

package com.Client;

import jdk.nashorn.internal.runtime.Scope;

import javax.sound.sampled.ReverbType;
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.*;
import java.net.Socket;
import java.util.Scanner;

public class Client extends JFrame implements ActionListener, KeyListener {

    // 属性
    // 文本域
    private JTextArea jta;
    // 滚动条
    private JScrollPane jsp;
    // 面板
    private JPanel jp;
    // 文本框
    private JTextField jtf;
    // 按钮
    private JButton jb;

    // 客户端登录的用户名字
    private String username;

    // 字符缓冲输入输出流
    private BufferedReader br;
    private BufferedWriter bw;

    // 构造方法
    private Client() {
        // 初始化组件
        jta = new JTextArea();
        // 设置文本默认不可编辑
        jta.setEditable(false);
        // 注意,需要将文本域添加到滚动条中,实现滚动效果
        jsp = new JScrollPane(jta);
        // 面板
        jp = new JPanel();
        // 文本框
        jtf = new JTextField(10);
        // 按钮
        jb = new JButton("发送");

        // 把文本框添和按钮添加到面板中
        jp.add(jtf);
        jp.add(jb);

        // 把滚动条和面板添加到窗体中
        this.add(jsp, BorderLayout.CENTER);
        this.add(jp, BorderLayout.SOUTH);

        // 注意,设置 标题,大小,位置,关闭,是否可见
        this.setTitle("客户端窗口");
        this.setSize(300, 300);
        this.setLocation(300, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);

        // 给按钮添加一个监听器
        jb.addActionListener(this);
        //给文本框绑定一个键盘监听时间
        jtf.addKeyListener(this);

        try {
            // 1、创建客户端连接
            Socket client = new Socket("localhost",9999);
            // 2、写入该客户端的名字
            System.out.println("请输入您的名字:");
            Scanner sc= new Scanner(System.in);
            this.username=sc.nextLine();
            // 3、创建出入输出流,采用字符缓冲流
            br=new BufferedReader(new InputStreamReader(client.getInputStream()));
            bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));

            // 4、给客户端读取内容创建一个线程并启动
            Receive receive = new Receive(br,jta);
            new Thread(receive).start();
            // 5、关闭资源
            // 因为是循环读取我们在此就不关闭资源了

        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // 主函数并启动客户端
    public static void main(String[] args) {
        new Client();
    }


    // 实现actionPerformed接口的方法
    // 读取文本框的内容,并写入自己的文本域中以及传给服务器端
    @Override
    public void actionPerformed(ActionEvent e) {
        sendDataScoket();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode()== KeyEvent.VK_ENTER) {
            sendDataScoket();
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }


    @Override
    public void keyReleased(KeyEvent e) {

    }

    // 定义一个方法,实现发送数据
    private void sendDataScoket(){
        try {
            // 获取文本内容
            String text=jtf.getText();
            // 文本内容加上客户端的名字
            text=username+"说-->"+text;
            // 加到自己UI界面上并换行
            jta.append(text+System.lineSeparator());
            // 把文本内容写给服务器端并换行和刷新
            // 注意服务器端和客户端都使用同样的字符缓冲流
            bw.write(text);
            bw.newLine();
            bw.flush();
            // 清空文本框内容
            jtf.setText("");
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }
}

  • Receive:
package com.Client;

import com.Util.CloseUtil;

import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;

public class Receive implements Runnable{
    // 字符缓冲输入流
    private BufferedReader br;
    // 文本域
    private JTextArea jta;
    // 读写状态
    private boolean flag=true;

    // 构造方法并初始化属性
    public Receive(BufferedReader br,JTextArea jta){
        this.br=br;
        this.jta=jta;
    }

    // 从服务器端读取内容并返回
    private String getMessage(){
        String str="";
        try {
            str = br.readLine(); // 堵塞状态
        } catch (IOException e) {
            flag=false;
            CloseUtil.closeAll(br);
        }
        return str;
    }

    @Override
    public void run() {
        while (flag){
            // 给文本域添加读取的内容并换行
            jta.append(getMessage()+System.lineSeparator());
        }
    }
}

2、Server

  • Server:
package com.Server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    // 使用静态的
    public static List<MyChannel> list = new ArrayList<>();

    public static void main(String[] args) {

        try {
            // 1、创建一个服务端的套接字
            ServerSocket server = new ServerSocket(9999);
            while (true){
                // 2、创建循环,等待客户端的连接
                Socket socket =server.accept(); // 阻塞状态
                // 创建一个线程对象
                MyChannel channel = new MyChannel(socket);
                // 添加到集合中
                list.add(channel);
                System.out.println("当前的在线人数为: "+ list.size()+"人");
                // 启动线程
                new Thread(channel).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • MyChannel:
package com.Server;

import com.Util.CloseUtil;

import java.io.*;
import java.net.Socket;
import java.util.List;

public class MyChannel implements Runnable {

    private BufferedReader br;
    private BufferedWriter bw;

    private boolean flag = true;

    public MyChannel(Socket client) {

        try {
            br = new BufferedReader(new InputStreamReader(client.getInputStream()));
            bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        } catch (IOException e) {
            flag = false;
            CloseUtil.closeAll(br, bw);
        }

    }

    // 接收数据的方法
    private String receive() {
        String str = "";
        try {
            str = br.readLine();
        } catch (IOException e) {
            flag = false;
            CloseUtil.closeAll(br, bw);
            Server.list.remove(this);
        }
        return str;
    }

    // 发送数据的方法
    private void send(String str) {
        if (str != null && str.length() != 0) {
            try {
                bw.write(str);
                bw.newLine();
                // 注意刷新
                bw.flush();
            } catch (IOException e) {
                flag = false;
                CloseUtil.closeAll(br, bw);
                Server.list.remove(this);
            }
        }
    }

    // 发送给其他的客户端
    private void sendOther() {
        String str = this.receive();
        List<MyChannel> list = Server.list;
        for (MyChannel other : list) {
            if (other == this) continue;// 不发给自己
            other.send(str); // 发送给其他人
        }
    }

    @Override
    public void run() {
        while (flag) {
            // 调用发送给其他客户端的方法
            sendOther();
        }
    }
}

3、Util

  • CloseUtil:
package com.Util;

import java.io.Closeable;
import java.io.IOException;

public class CloseUtil {

    // 工具类设为静态方法
    // 参数为可变参数
    public static void closeAll(Closeable... able){
        for(Closeable c:able){
            if(c!=null){
                try {
                    c.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

!这样就实现了简单的带有UI界面的群聊功能。
接下来我会丰富代码,实现更多的功能。大家一起加油!

你可能感兴趣的:(java学习,java,多线程,socket,gui)