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-
来源:CSDN
作者:儿时凿壁偷了谁家的光
链接:https://blog.csdn.net/qq_44756558/article/details/103401532