Java大作业聊天室

1.将用户数据保存到数据库

写这个聊天室呢,我的想法是先从界面写起,那么,对照qq,微信这些聊天室,第一步肯定是先要注册和登录,所以啊,我们就要将用户数据保存到数据库中,不然的话,当你第二次打开聊天室的时候又需要重新注册,这显然是不好的。

实现:
数据库的实现非常简单,我们只需要建立一个新的数据库再建立一张用户表即可,因为我不能实现qq号那样的主码,所以这里我是用用户名当主码的。
在这里插入图片描述
接下来就是在Idea中实现代码来连接到数据库了,这也没有什么难的,但是,
需要注意的是,由于MySQL使用的时间和我们不一样,所以要在url后面加上serverTimezone=UTC。

实现:

package DB;

import java.sql.*;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

/**
 * 
 * @author GUOFENG  --登录连接数据库
 * 
 */
public class UserDB {
	// 驱动程序名
	String driver = "com.mysql.cj.jdbc.Driver";
	// URL指向要访问的数据库名jdbc
	String url = "jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC";
	// MySQL配置
	String sqluser = "root";
	String sqlpassword = "helloworld1.cpp";

	String userpwd_;
	String username_;
	boolean n = false;

	public UserDB(String name, String pwd) {
		username_ = name;
		userpwd_ = pwd;
	}

	public Boolean selectsql() {
		n = false;
		try {
			// 加载驱动
			Class.forName(driver);
			// 连接数据库
			Connection conn = DriverManager.getConnection(url, sqluser,
					sqlpassword);
			if (!conn.isClosed())
				System.out.println("连接数据库成功!");
			// statement用来执行SQL语句
			Statement statement = conn.createStatement();
			// 要执行的SQL语句
			String sql = "select userpwd from hello where username=" + "'" + username_ + "';";
			// 结果集
			ResultSet rs = statement.executeQuery(sql);
			String readpwd = null;
			while (rs.next()) {
				// 选择password这列数据
				readpwd = rs.getString("userpwd");
				// 首先使用ISO-8859-1字符集将其解码为字节序列并将结果存储新的字节数组中。
				// 然后使用GB2312字符集解码指定的字节数组
				readpwd = new String(readpwd.getBytes("ISO-8859-1"), "GB2312");
				// 输出结果
				System.out.println(readpwd);
				if (readpwd.equals(userpwd_)) {
					n = true;
				}
			}
			rs.close();
			conn.close();
		} catch (ClassNotFoundException e) {
			System.out.println("加载MySQL驱动失败!");
		} catch (SQLException e1) {
			System.out.println("1.hellosql:" + e1.getMessage());
		} catch (Exception e2) {
			System.out.println("2.hellosql:" + e2.getMessage());
		}
		return n;
	}

	public boolean addsql() {
		int count = 0;
		n = false;
		try {
			// 加载驱动
			Class.forName(driver);
			// 连接数据库
			Connection conn = DriverManager.getConnection(url, sqluser,
					sqlpassword);
			if (!conn.isClosed())
				System.out.println("连接数据库成功!");

			String sql = "insert into hello (username, userpwd) values (?,?);";

			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, username_);
			ps.setString(2, userpwd_);
			count = ps.executeUpdate();
			if (this.selectsql() == true)
				{	n = true;
					System.out.println("***");
				}
			else {
				JOptionPane.showMessageDialog(new JFrame(), "注册失败!", "错误",
						JOptionPane.ERROR_MESSAGE);
			}
			ps.close();
			conn.close();
		} catch (ClassNotFoundException e) {
			System.out.println("加载MySQL驱动失败!");
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return n;
	}


在这里我没有对用户名和密码进行加密和解密以及防止sql注入(懒得写了),有兴趣的可以去了解一下。
还有一点就是,需要在项目中添加mysql-connector-java-.jar依赖,从flie-project structure 中的lib中下载就行。否则无法加载数据库驱动。

2.计划好聊天室要实现的功能。

1.群聊
2.私聊
3.传输图片
4.传输语音
那么怎么实现呢?
首先,每个功能肯定是要对应一个函数的,那么,如何让服务器知道你要进行的行为呢,所以啊,我们要在行为发生之前,给服务器传输一个指令,这样服务器就能知道你要干什么了。

//客户端接收服务器指令
public void run() {
			String message = "";
			while (true) {
				try {
					if (flag == 0) {
						message = reader.readLine();
						StringTokenizer stringTokenizer = new StringTokenizer(message, "/@");
						// 服务器消息处理
						String[] str_msg = new String[10];
						int j_ = 0;
						while (stringTokenizer.hasMoreTokens()) {
							str_msg[j_++] = stringTokenizer.nextToken();
						}
						String command = str_msg[1];// 信号

	//服务器接收客户端指令
	while ((content = readFromClient()) != null) {
						flag = 0;
						int user_list = Server.clients_string.valueSet().size();
						System.out.println("Msg_from_Client : " + content);

						StringTokenizer stringTokenizer = new StringTokenizer(content, "/@");
						String[] str_msg = new String[10];
						int j_ = 0;
						while (stringTokenizer.hasMoreTokens()) {
							str_msg[j_++] = stringTokenizer.nextToken();
						}
						String command = str_msg[1];// 信号

StringTokenizer,用来分割字符串。

3.实现群聊和私聊功能

Method:
使用SynchronizedMap实现并发编程

package Server_;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class soctet_stream_map<K, V> {
	public Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>());

	public synchronized void removeByValue(Object value) {
		for (Object key : map.keySet()) {
			if (map.get(key) == value) {
				map.remove(key);
				break;
			}
		}
	}

	public synchronized Set<V> valueSet() {
		Set<V> result = new HashSet<V>();
		map.forEach((key, value) -> result.add(value));
		return result;
	}

	public synchronized K getKeyByValue(V val) {
		for (K key : map.keySet()) {
			if (map.get(key) == val || map.get(key).equals(val)) {
				return key;
			}
		}
		return null;
	}

	public synchronized V put(K  key, V value) {
		return map.put(key, value);
	}
}

//群发消息
for (PrintStream ps_ : Server.clients_string.valueSet()) {
								ps_.println(content);
							}
//私聊
User user_ss = null;
for (User user_ : Server.clients_string.map.keySet())
if (user_.getName().equals(command)) {
user_ss = user_;
				break;
					}
			System.out.println("The whisper msg!");
Server.clients_string.map.get(user_ss).println(Server.clients_string.getKeyByValue(ps).getName() + " whispers to you : "
						+ str_msg[2] + "@" + "ONLY");
//服务器实例
public static soctet_stream_map<User, PrintStream> clients_string = new soctet_stream_map<>();

4.文件及时传输

很简单,导入org.apache.commons.lang3包就行了,使用base64utils加密和解密。

5.接收客户端文件并返回

//服务端接收文件代码
mGson = new Gson();
					while ((content = readFromClient()) != null) {
						trans = mGson.fromJson(content, Transmission.class);
						long fileLength = trans.fileLength;
						long transLength = trans.transLength;
						file_name_just = trans.fileName;
						
						System.out.println(file_name_just);
						if (file_is_create) {
							fos = new FileOutputStream(new File("D:/javaChatRoom/src/Server_/files", trans.fileName));
							file_is_create = false;
						}
						byte[] b = Base64Utils.decode(trans.content.getBytes());
						fos.write(b, 0, b.length);
						System.out.println("接收文件进度" + 100 * transLength / fileLength + "%...");
						if (transLength == fileLength) {
							file_is_create = true;
							fos.flush();
							fos.close();
							break;
						}
					}
					list.add(new File("D:/javaChatRoom/src/Server_/files" ,trans.fileName));
					System.out.println("上传文件结束");
					for (PrintStream ps_ : Server.clients_string.valueSet()) {
						ps_.println("Server" + "@" + "PIC_up_ok");
//服务端发送文件代码
else if (command.equals("PIC_up")) {
							flag = 1;
							break;
						} else if (command.equals("PIC_down")) {
							System.out.println("服务器收到客户端的文件上传成功命令,准备进行文件下载");
							try {
								file_name_just = getFileSort("D:/javaChatRoom/src/Server_/files/").get(0).getName();
								String doc_path = new String("D:/javaChatRoom/src/Server_/files/" + file_name_just);
								doc_read = new FileInputStream(doc_path);
								File file = new File(doc_path);
								mGson = new Gson();
								Transmission trans = new Transmission();
								trans.transmissionType = 3;
								trans.fileName = file.getName();
								trans.fileLength = file.length();
								trans.transLength = 0;
//								for (PrintStream ps_ : Server.clients_string.valueSet()) {
								byte[] sendByte = new byte[1024];
								int length = 0;
								while ((length = doc_read.read(sendByte, 0, sendByte.length)) != -1) {
									trans.transLength += length;
									trans.content = Base64Utils.encode(sendByte);
									ps.println(mGson.toJson(trans));
									System.out.println("下载文件进度" + 100 * trans.transLength / trans.fileLength + "%...");
									ps.flush();
								}
//								}
								System.out.println("Server下载执行结束");
							} catch (FileNotFoundException e1) {
								System.out.println("文件不存在!");
							} catch (IOException e2) {
								System.out.println("文件写入异常");
							} catch (RuntimeException e3) {
								System.out.println("e3: " + e3.getMessage());
								e3.printStackTrace();
							} finally {
								try {
									doc_read.close();
								} catch (IOException e1) {
									e1.printStackTrace();
								}
							}
//客户端上传文件代码
/ btn_pic发送图片事件
		btn_pic.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				Filechose();
				try {
					if (pic_path != null) {
						doc_read = new FileInputStream(pic_path);
						sendMessage(name + "@" + "PIC_up"); // 上传图片指令
					}
					File file = new File(pic_path);
					mGson = new Gson();
					Transmission trans = new Transmission();
					trans.transmissionType = 3;
					trans.fileName = file.getName();
					trans.fileLength = file.length();
					trans.transLength = 0;
					byte[] sendByte = new byte[1024];
					int length = 0;
					while ((length = doc_read.read(sendByte, 0, sendByte.length)) != -1) {
						trans.transLength += length;
						trans.content = Base64Utils.encode(sendByte);
						writer.write(mGson.toJson(trans) + "\r\n");
						System.out.println("上传文件进度" + 100 * trans.transLength / trans.fileLength + "%...");
						writer.flush();
					}
					System.out.println("文件上传完毕");
				} catch (FileNotFoundException e1) {
					System.out.println("文件不存在!");
				} catch (IOException e2) {
					System.out.println("文件写入异常");
				} finally {
					try {
						doc_read.close();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
			}
		});
//客户端接收文件代码
// 下载图片
						else if (command.equals("PIC_up_ok")) {
							sendMessage(name + "@" + "PIC_down");
							flag = 1;
							// break;
						}
						str_msg = null; // 消息数组置空
					} // if(flag == 0)
					else if (flag == 1) {
						System.out.println("客户端准备消息接受 。 。 。 ");

						mGson = new Gson();
						while ((message = reader.readLine()) != null) {
							trans = mGson.fromJson(message, Transmission.class);
							long fileLength = trans.fileLength;
							long transLength = trans.transLength;
							if (file_is_create) {
								fos = new FileOutputStream(new File(
										"D:/javaChatRoom/src/Client/files" ,trans.fileName));
								file_is_create = false;
							}
							byte[] b = Base64Utils.decode(trans.content.getBytes());
							fos.write(b, 0, b.length);
							System.out.println("接收文件进度" + 100 * transLength / fileLength + "%...");
							if (transLength == fileLength) {
								file_is_create = true;
								fos.flush();
								fos.close();
								if (trans.fileName.endsWith(".jpg")) {
									ImageIcon icon = new ImageIcon(
											"D:/javaChatRoom/src/Client/files/" + trans.fileName);
									// icon.
									SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");// 设置日期格式
									String time = df.format(new java.util.Date());
									StyledDocument doc = text_show.getStyledDocument();
									Document docs = text_show.getDocument();
									try {
										docs.insertString(docs.getLength(),
												"[" + time + "]\r\n" + name + " 说 : " + "\r\n", attrset);// 对文本进行追加
										text_show.setCaretPosition(doc.getLength());
										text_show.insertIcon(icon);
										docs = text_show.getDocument();
										docs.insertString(docs.getLength(), "\r\n", attrset);
									} catch (BadLocationException e) {
										e.printStackTrace();
									}
								} else if (trans.fileName.endsWith(".mp3")) {
									SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");// 设置日期格式
									String time = df.format(new java.util.Date());
									Document docs = text_show.getDocument();
									try {
										docs.insertString(docs.getLength(),
												"[" + time + "]\r\n" + name + " 说了一段话 : " + "\r\n\n", attrset);// 对文本进行追加
										playWAV.Play("" + trans.fileName);
									} catch (BadLocationException e) {
										e.printStackTrace();
									}
								}
								break;
							}
						}
						System.out.println("文件下载执行完毕");
						flag = 0;
					} /// else if
				} // try
				catch (IOException e1) {
					// ConnectServer();
					e1.printStackTrace();
					System.out.println("客户端接受 消息 线程 run() e1:" + e1.getMessage());
					break;
				} catch (Exception e2) {
					// ConnectServer();
					e2.printStackTrace();
					System.out.println("客户端接收 消息 线程 run() e2:" + e2.getMessage());
					break;
				}

6.客户端实现从本地选择文件上传

方法很简单,用JFileChooser即可实现`

// 文件选择,输出绝对路径
	public void Filechose() {
		JFileChooser jfc = new JFileChooser();
		jfc.setCurrentDirectory(new File(""));
		jfc.addChoosableFileFilter(new MyFileFilter());
		// jfc.
		JFrame pic_chose = new JFrame();
		pic_chose.setVisible(false);
		pic_chose.setBounds(100, 100, 800, 600);
		if (jfc.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
			pic_path = jfc.getSelectedFile().getAbsolutePath().toString();
			System.out.println(pic_path);
		}
	}

	// 文件类型过滤
	class MyFileFilter extends FileFilter {
		public boolean accept(File pathname) {
			if (pathname.getAbsolutePath().endsWith(".gif") || pathname.isDirectory()
					|| pathname.getAbsolutePath().endsWith(".png"))
				return true;
			return false;
		}

		public String getDescription() {
			return "图像文件";
		}
	}

7.实现语音的录制与发送

语音文件和图片文件是一样的,在上传语音文件时用图片上传指令就行,所以重点在于语音录制的实现。

/// 语音相关
	// 开始录音
	public void capture() {
		try {
			// af为AudioFormat也就是音频格式
			af = getAudioFormat();
			DataLine.Info info = new DataLine.Info(TargetDataLine.class, af);
			td = (TargetDataLine) (AudioSystem.getLine(info));
			// 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
			td.open(af);
			// 允许某一数据行执行数据 I/O
			td.start();
			// 创建播放录音的线程
			Record record = new Record();
			Thread t1 = new Thread(record);
			t1.start();

		} catch (LineUnavailableException ex) {
			ex.printStackTrace();
			return;
		}
	}

	// 停止录音
	public void stop() {
		stopflag = true;
	}

	// 保存录音
	public void save() {
		// 取得录音输入流
		af = getAudioFormat();

		byte audioData[] = baos.toByteArray();
		bais = new ByteArrayInputStream(audioData);
		ais = new AudioInputStream(bais, af, audioData.length / af.getFrameSize());
		// 定义最终保存的文件名
		File file = null;
		// 写入文件
		try {
			// 以当前的时间命名录音的名字
			mp4_path = new String("D:/javaChatRoom/src/Client/files/");
			File filePath = new File(mp4_path);
			if (!filePath.exists()) {// 如果文件不存在,则创建该目录
				filePath.mkdir();
				filePath.createNewFile();
			}
			file = new File("D:/javaChatRoom/src/Client/files" ,"/" + System.currentTimeMillis() + ".mp3");
			mp4_path += file.getName();
			System.out.println(mp4_path);
			AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			try {

				if (bais != null) {
					bais.close();
				}
				if (ais != null) {
					ais.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	// 设置AudioFormat的参数
	public AudioFormat getAudioFormat() {
		// 下面注释部分是另外一种音频格式,两者都可以
		AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
		float rate = 8000f;
		int sampleSize = 16;
		boolean bigEndian = true;
		int channels = 1;
		return new AudioFormat(encoding, rate, sampleSize, channels, (sampleSize / 8) * channels, rate, bigEndian);
		// //采样率是每秒播放和录制的样本数
		// float sampleRate = 16000.0F;
		// // 采样率8000,11025,16000,22050,44100
		// //sampleSizeInBits表示每个具有此格式的声音样本中的位数
		// int sampleSizeInBits = 16;
		// // 8,16
		// int channels = 1;
		// // 单声道为1,立体声为2
		// boolean signed = true;
		// // true,false
		// boolean bigEndian = true;
		// // true,false
		// return new AudioFormat(sampleRate, sampleSizeInBits, channels,
		// signed,bigEndian);
	}

	// 录音类,因为要用到MyRecord类中的变量,所以将其做成内部类
	class Record implements Runnable {
		// 定义存放录音的字节数组,作为缓冲区
		byte bts[] = new byte[10000];

		// 将字节数组包装到流里,最终存入到baos中
		// 重写run函数
		public void run() {
			baos = new ByteArrayOutputStream();
			try {
				System.out.println("ok3");
				stopflag = false;
				while (stopflag != true) {
					// 当停止录音没按下时,该线程一直执行
					// 从数据行的输入缓冲区读取音频数据。
					// 要读取bts.length长度的字节,cnt 是实际读取的字节数
					int cnt = td.read(bts, 0, bts.length);
					if (cnt > 0) {
						baos.write(bts, 0, cnt);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					// 关闭打开的字节数组流
					if (baos != null) {
						baos.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					td.drain();
					td.close();
				}
			}
		}

	}
//播放录音
package Client;

import java.io.File;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

/**
 * 播放音频 
 */
public class PlayWAV {
	private String str;

	public void Play(String s) {
		this.str = s;
		try {
			AudioInputStream ais = AudioSystem
					.getAudioInputStream(new File(str));// 获得音频输入流
			AudioFormat baseFormat = ais.getFormat();// 指定声音流中特定数据安排
			// System.out.println("baseFormat=" + baseFormat);
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,
					baseFormat);
			// System.out.println("info=" + info);
			SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
			// 从混频器获得源数据行
			// System.out.println("line=" + line);
			line.open(baseFormat);// 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
			line.start();// 允许数据行执行数据 I/O
			int BUFFER_SIZE = 4000 * 4;
			int intBytes = 0;
			byte[] audioData = new byte[BUFFER_SIZE];
			while (intBytes != -1) {
				intBytes = ais.read(audioData, 0, BUFFER_SIZE);// 从音频流读取指定的最大数量的数据字节,并将其放入给定的字节数组中。
				if (intBytes >= 0) {
					int outBytes = line.write(audioData, 0, intBytes);// 通过此源数据行将音频数据写入混频器。
				}
			}

		} catch (Exception e) {
		}
	}

}

8.配置文件

新建一个file,随喜好命名,然后找到propoties。
把你的客户端端口,ip,服务器端口写入其中。
这样可以方便修改。

//配置文件
serverPort=30000
clientIp=127.0.0.1
clientPort=30000
//静态代码块读取配置文件
//客户端
static {
		Properties prop = new Properties();
		try {
			prop.load(new FileReader("src/chat"));
			port = Integer.parseInt(prop.getProperty("clientPort"));
			ip = (prop.getProperty("clientIp"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
//服务端
static {
		Properties prop = new Properties();
		try {
			prop.load(new FileReader("src/chat"));
			SERVER_PORT = Integer.parseInt(prop.getProperty("serverPort"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

==记得将端口和ip的属性上加上static

9.完整代码链接

https://github.com/childrentime/Java-

  • 3
    点赞
  • 0
    评论
  • 25
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

儿时凿壁偷了谁家的光

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值