利用JavaSwing实现简单群聊天

Java实现简单群聊天

所用技术

Java基础知识,JavaSwing,Java多线程,JavaTCP网络编程

思路

服务器

写两个线程,一个实现接收客户端发送的消息功能,并采用消息队列的方式存储消息;一个实现群发消息队列中消息的功能。服务器启动时,使用while(true)不断尝试与客户端建立连接,每当建立一个连接后,将该连接保存到一个队列中,为群发消息时,找到发送对象做准备。

客户端

同样写两个线程,一个实现不断接收服务器发回来的消息,一个实现在按下发送消息后,将消息发送出去。

服务器

接收消息线程

采用继承Thread类的方式,循环与客户端建立连接,并以一个内部类的方式来接收消息,每当建立一个连接,便将该连接加入到连接对列,这里采用Set,并且启动一个内部类线程开始接收该连接的消息,接收到的消息加入消息队列,然后将消息队列及连接队列同步到发送消息线程,并启动发送消息线程,发送一次消息

import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import java.util.LinkedList;
/**
 * 接收消息,线程
 */
public class ReceiveNews extends Thread{

	//保存消息
	private LinkedList<String> queue = new LinkedList();
	
	//服务器Socket
	private ServerSocket server;
	
	//客户端Socket
	private Socket socket;
	
	//保存Socket
	HashSet<Socket> socketSet = new HashSet<>();
	
	public ReceiveNews(ServerSocket server) {
		this.server = server;
	}
	
	@Override
	public void run(){
		
		while(true){
			//接收连接
			try {
				socket = server.accept();
				socketSet.add(socket);
				//为该连接开启接收消息线程
				new Receive(socket).start();
			} catch (IOException e) {
				e.printStackTrace();
			}
		
		}
		
	}
	
	class Receive extends Thread {
		
		//接收消息
		private DataInputStream in;
		
		//Socket对象
		private Socket socket;
		
		public Receive(Socket socket){
			this.socket = socket;
			try {
				in = new DataInputStream(socket.getInputStream());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		@Override
		public void run(){
			while(true){
				String temp = "";

				try {
					temp = in.readUTF();
				} catch (IOException e) {
					e.printStackTrace();
				}
				//加入消息队列
				queue.add(temp);
				
				//将队列同步到发送消息线程
				new SendNews(queue,socketSet).start();
				
			}
		}
	}
}

发送消息线程

通过构造器来初始化消息队列及连接队列,若消息队列中存在未发送的消息,则取连接队列中的每个连接,依次发送消息给每个客户端,实现群聊天

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashSet;
import java.util.LinkedList;
/**
 * 发送消息线程
 */
public class SendNews extends Thread{

	//保存消息
	private LinkedList<String> queue = new LinkedList();
	
	//发送消息
	private DataOutputStream writer;
	
	//客户端Socket
	private HashSet<Socket> socketSet;
	
	public SendNews(LinkedList<String> queue,HashSet<Socket> socketSet){
		this.queue = queue;
		this.socketSet = socketSet;
	}
	
	@Override
	public void run(){
		
		//如果有消息未发
		if(queue.size() > 0){
			//获取队列中的消息
			String message = queue.pollFirst();;
			//给每个客户端发送消息
			for(Socket socket : socketSet){
				try {
					writer = new DataOutputStream(socket.getOutputStream());
					//发送消息
					System.out.println("发送消息");
					writer.writeUTF(message);
					writer.flush();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
		
}

主窗体

该窗体中保存有用户设置的端口号,并且以该端口号创建一个ServerSocket对象,并且通过接收线程类的构造器将这个对象传给接收消息线程,在创建该窗体的同时启动接收消息线程。

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.ServerSocket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import talking.thread.ReceiveNews;
/**
 * 服务器窗体
 */
public class MainFrame extends JFrame implements ActionListener{

	//关闭服务器按钮
	private JButton exit = new JButton("关闭服务器");
	
	//保存端口号
	private int post;
	
	//显示端口号
	private JLabel show;
	
	//接收消息线程 
	private ReceiveNews receive;
	
	//服务器Socket
	private ServerSocket server;
	
	public MainFrame(int post) {
		//初始化
		this.post = post;
		
		try {
			server = new ServerSocket(post);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		show = new JLabel("服务器已启动端口号:" + post);
		init();
		
		//接收消息线程启动
		receive = new ReceiveNews(server);
		receive.start();
	}
	
	private void init(){
		
		//设置窗口名称
		this.setTitle("服务器");
		
		//窗口大小固定
		this.setResizable(false);
		
		//设置窗口关闭
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		//设置字体
		exit.setFont(new Font("宋体",1,18));
		show.setFont(new Font("宋体",1,18));
		
		//添加监听
		exit.addActionListener(this);
		
		//设置布局
		this.setLayout(null);
		this.setBounds(700,300,300,500);
		exit.setBounds(70,300,150,30);
		show.setBounds(20,150,300,90);
		
		//添加组件
		this.add(exit);
		this.add(show);
		
		//窗体可见
		this.setVisible(true);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		JButton button = (JButton) e.getSource();
		if(button.equals(exit)){//退出
			System.exit(0);
		}
	}
	
}

窗体效果如下图
在这里插入图片描述

设置端口窗体

启动服务器时,首先显示该窗体,用以设置连接服务器的端口号,该窗体有三个组件,关闭程序的按钮,设置端口的按钮,以及填写要设置的端口号的文本框,当设置号端口号后,使该窗体销毁,并且启动主窗体

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
/**
 * 设置端口号窗体
 */
public class SetPost extends JFrame implements ActionListener{

	private static final long serialVersionUID = 1L;

	//确定按钮
	private JButton set = new JButton("设置端口号");
	
	//关闭窗口按钮
	private JButton exit = new JButton("关闭");
	
	//窗口输入
	private JTextField write = new JTextField();
	
	//端口号
	private JLabel post = new JLabel("端口号");
	
	public SetPost() {
		//初始化
		init();
	}
	
	private void init(){
		
		//设置窗口名称
		this.setTitle("服务器");
		
		//窗口大小固定
		this.setResizable(false);
		
		//设置窗口关闭
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		//设置字体
		exit.setFont(new Font("宋体",1,18));
		set.setFont(new Font("宋体",1,18));
		write.setFont(new Font("宋体",1,18));
		post.setFont(new Font("宋体",1,18));
		
		//添加监听
		exit.addActionListener(this);
		set.addActionListener(this);
		
		//设置布局
		this.setLayout(null);
		this.setBounds(600,300,400,280);
		set.setBounds(50,150,150,30);
		exit.setBounds(250,150,80,30);
		write.setBounds(100,80,200,20);
		post.setBounds(30,80,150,20);
		
		//添加组件
		this.add(exit);
		this.add(set);
		this.add(write);
		this.add(post);
		
		//窗体可见
		this.setVisible(true);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		JButton button = (JButton) e.getSource();
		if(button.equals(exit)){//退出
			System.exit(0);
		}else if(button.equals(set)){//确认
			String message = write.getText();
			int post;
			try{
				post = Integer.parseInt(message);
			}catch(NumberFormatException exception){
				JOptionPane.showMessageDialog(null, "请输入整数");
				write.setText("");
				return;
			}
			this.dispose();
			new MainFrame(post);
		}
	}
	
}

该窗体效果如下图
在这里插入图片描述

启动服务器的类

一个简单的main方法

import talking.frame.SetPost;
/**
 * 服务器启动
 */
public class Start {

	public static void main(String[] args) {
		
		//创建服务器窗体
		new SetPost();
		
	}
	
}

到此服务器就完成了,接下来开始写客户端

客户端

选择端口号及连接服务器名称窗体

该窗体用于输入需要连接的服务器的端口号,以及在群聊中以什么昵称发送消息。

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
/**
 * 设置端口号窗体
 */
public class SetPost extends JFrame implements ActionListener{

	private static final long serialVersionUID = 1L;

	//确定按钮
	private JButton set = new JButton("进入");
	
	//关闭窗口按钮
	private JButton exit = new JButton("关闭");
	
	//窗口输入
	private JTextField write = new JTextField();
	private JTextField name = new JTextField();
	
	//显示端口,姓名
	private JLabel showPost = new JLabel("端口号");
	private JLabel showName = new JLabel("姓 名");
	
	public SetPost() {
		//初始化
		init();
	}
	
	private void init(){
		
		//设置窗口名称
		this.setTitle("客户端");
		
		//窗口大小固定
		this.setResizable(false);
		
		//设置窗口关闭
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		//设置字体
		exit.setFont(new Font("宋体",1,18));
		set.setFont(new Font("宋体",1,18));
		write.setFont(new Font("宋体",1,18));
		name.setFont(new Font("宋体",1,18));
		showPost.setFont(new Font("宋体",1,18));
		showName.setFont(new Font("宋体",1,18));
		
		//添加监听
		exit.addActionListener(this);
		set.addActionListener(this);
		
		//设置布局
		this.setLayout(null);
		this.setBounds(700,300,400,280);
		set.setBounds(70,150,80,30);
		exit.setBounds(230,150,80,30);
		write.setBounds(90,50,200,25);
		name.setBounds(90,100,200,25);
		showPost.setBounds(30,47,150,30);
		showName.setBounds(36,97,120,30);
		
		//添加组件
		this.add(exit);
		this.add(set);
		this.add(write);
		this.add(name);
		this.add(showName);
		this.add(showPost);
		
		//窗体可见
		this.setVisible(true);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		JButton button = (JButton) e.getSource();
		if(button.equals(exit)){//退出
			System.exit(0);
		}else if(button.equals(set)){//确认
			String message = write.getText();
			int post;
			try{
				post = Integer.parseInt(message);
			}catch(NumberFormatException exception){
				JOptionPane.showMessageDialog(null, "请输入整数");
				write.setText("");
				return;
			}
			if("".equals(name.getText())){
				JOptionPane.showMessageDialog(null, "名称不能为空");
				return;
			}
			this.dispose();
			new MainFrame(post,name.getText());
		}
	}
	
}

该窗体效果如下图
在这里插入图片描述

主窗体

以两个内部类的形式来实现接收消息和发送消息线程,发送消息线程以while(true)不断接收来自服务器的消息并将其显示在历史消息处,而发送消息线程,每次启动会将此时消息框内的文本发送出去。窗体创建时,开启接收消息线程,每当按下发送消息按钮后,启动一次接收消息线程。

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.ServerSocket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import talking.thread.ReceiveNews;
/**
 * 服务器窗体
 */
public class MainFrame extends JFrame implements ActionListener{

	//关闭服务器按钮
	private JButton exit = new JButton("关闭服务器");
	
	//保存端口号
	private int post;
	
	//显示端口号
	private JLabel show;
	
	//接收消息线程 
	private ReceiveNews receive;
	
	//服务器Socket
	private ServerSocket server;
	
	public MainFrame(int post) {
		//初始化
		this.post = post;
		
		try {
			server = new ServerSocket(post);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		show = new JLabel("服务器已启动端口号:" + post);
		init();
		
		//接收消息线程启动
		receive = new ReceiveNews(server);
		receive.start();
	}
	
	private void init(){
		
		//设置窗口名称
		this.setTitle("服务器");
		
		//窗口大小固定
		this.setResizable(false);
		
		//设置窗口关闭
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		//设置字体
		exit.setFont(new Font("宋体",1,18));
		show.setFont(new Font("宋体",1,18));
		
		//添加监听
		exit.addActionListener(this);
		
		//设置布局
		this.setLayout(null);
		this.setBounds(700,300,300,500);
		exit.setBounds(70,300,150,30);
		show.setBounds(20,150,300,90);
		
		//添加组件
		this.add(exit);
		this.add(show);
		
		//窗体可见
		this.setVisible(true);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		JButton button = (JButton) e.getSource();
		if(button.equals(exit)){//退出
			System.exit(0);
		}
	}
	
}

该窗体效果如下图
上为历史消息框,下为消息框。
在这里插入图片描述

启动客户端的类

import talking.frame.SetPost;
/**
 * 服务器启动
 */
public class Start {

	public static void main(String[] args) {
		
		//创建服务器窗体
		new SetPost();
		
	}
	
}

到此该项目就写完了,每次启动时先起服务器后起客户端,客户端可起多个。

最后附上源代码及可执行jar包
链接:小聊天室
提取码:jbk0

学习阶段主要以实现功能为主,界面不够美观以及代码不够完善的地方请多包涵。

猜你喜欢

转载自blog.csdn.net/Z0921D/article/details/106272327