这次实现的是,多人同步聊天,而且客户端是具有UI的客户端聊天界面的。如下图所示:
我开启了三个客户端,每个客户端都有一个自己的聊天界面,并且每个人说话都是可以实时的显示在别人的UI界面中的。并且聊天窗口实现了滚屏的效果。很棒!
下面我先把代码的结构截图一下:
可以看到,在Chat2的模块下,我们有三个包,一个是Client客户端,一个是Server服务端,另外一个是Util工具包。
1、首先来介绍Server包:
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();
}
}
}
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
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();
}
}
}
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
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界面的群聊功能。
接下来我会丰富代码,实现更多的功能。大家一起加油!