利用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

學習階段主要以實現功能爲主,界面不夠美觀以及代碼不夠完善的地方請多包涵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章