在这个版本已经实现了基本的聊天,群聊以及文件发送功能.
为了图简单,只要是在线的用户就都会出现在所有客户端列表中,没有单独为每个用户保存好友,群聊所有人都会收到消息.
注册的用户信息都保存在文件中,每次服务器启动就先读取数据.
客户端连接上之后,就给所有在线的其他客户端发送自己上线的消息,然后其他客户端就更新好友列表.
双击即可打开对话窗口进行聊天或者传文件.
界面演示图:
之前两个版本:
第一版:
http://479001499.iteye.com/blog/2100893
第二版:
http://479001499.iteye.com/blog/2101491
这个版本在第二版之上改进的功能:
增加头像设置,文件传输.
在User类中增加一个属性,String 用来保存图片路径.设置头像实际为设置String的值.然后再生成ImageIcon.
if (source.equals("头像")) {
System.out.println("点击了更换头像");
JFileChooser jfc = new JFileChooser("images\\");
int state = jfc.showOpenDialog(MyInfo.this);
if (state == 0) {
user.headIcon = jfc.getSelectedFile().getAbsolutePath();
head.setIcon(new ImageIcon(user.headIcon));
// 将这个消息传至服务器
Message msg = new Message();
msg.type = MessageUtil.ChangeInfo;
msg.senderNum = user.num;
msg.receiverNum = 2;// 所有人加上服务器都要接受并修改信息
msg.content = user;
try {
MessageUtil.send(os, msg);
} catch (Exception ee) {
ee.printStackTrace();
}
}
}
修改之后,像所有客户端发送消息,让他们更新消息:
if (msg.type == MessageUtil.ChangeInfo) {
System.out.println("接受到用户修改信息");
// 标记修改了信息,当这个为true的时候.再打开对话窗口就需要更新其头像,否则再打开就只需要setVisible
havaChanged = true;
User us = (User) msg.content;
String olaNik = map.get(msg.senderNum).name;
// 将map<账号,昵称>中的元素进行修改
mapN2N.remove(olaNik);
ChatAllUI.dlm.removeElement(olaNik);
// 将map<昵称,账号>中的元素进行修改
mapN2N.put(us.name, msg.senderNum);
ChatAllUI.dlm.addElement(us.name);
map.put(msg.senderNum, us);
// 更新各个地方的显示
if (us.num == user.num) {
nik.setText(user.name);
textField_1.setText(user.qianming);
dlm.set(0, user.name);
} else {
int temp = dlm.indexOf(olaNik);
dlm.removeElement(olaNik);
dlm.add(temp, us.name);
}
}
在原有基础上增加一个文件收发的工具类FileTransfer,用来从文件中读取数据和保存数据到文件.
package v140813;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FileTransfer {
/**
* 文件发送
*
* @param os
* 输出流
* @param file
* 文件
*/
public static void fileSend(OutputStream os, File file) {
try {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(os);
int t = bis.read();
while (t != -1) {
bos.write(t);
t = bis.read();
}
bos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 收到了开始接收文件的消息里面的内容为文件名,保存在fileRec 目录下
*
* @param is
* 输入流
* @param fileName
* 文件名
* @param size
* 文件大小
* @param num
* 接收者的num
*/
public static void fileReceive(InputStream is, String fileName, long size,
int num) {
File f = new File("fileRec\\" + num + "\\");
if (!f.exists()) {
f.mkdir();
}
File file = new File("fileRec\\" + num + "\\" + fileName);
try {
BufferedInputStream bis = new BufferedInputStream(is);
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
for (long i = 0; i < size; i++) {
int t = bis.read();
// System.out.println(t);
bos.write(t);
}
bos.flush();
System.out.println("接收完毕");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端这边,发送文件之前,首先向服务器发送一条消息,内容为文件名和文件大小.
服务器启动一个线程接收文件,在线程中需要将输入流锁起.
服务器收完文件之后,再向接收文件的客户端发送一条消息,内容为文件名和文件大小
同样,客户端开始接收文件.接收完毕之后,向发送者发出消息说已经接收了文件.
public class ReceiveFileThread extends Thread {
String fileName;
long size;
public ReceiveFileThread(String content) {
String contents[] = content.split("#");
this.fileName = contents[0];
size = Long.parseLong(contents[1]);
System.out.println("准备接收 文件大小:" + size);
}
public void run() {
try {
synchronized (is) {
FileTransfer.fileReceive(is, fileName, size);
}
System.out.println("接收完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 发送文件的线程
*
* @author Huangbin
*
*/
class SendFileThread extends Thread {
File file;
OutputStream os;
String content;
int receiverNum;
public SendFileThread(File file, int receiverNum, String content) {
this.file = file;
this.os = Server.slist.get(receiverNum).os;
this.content = content;
this.receiverNum = receiverNum;
}
public void run() {
synchronized (os) {
try {
MessageUtil.send(os, new Message(8, num, receiverNum,
content));
System.out.println("准备发送");
// 开始发送
System.out.println("开始发送");
FileTransfer.fileSend(os, file);
System.out.println("发送完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(这里的接收文件线程和发送文件线程可有可无,因为流只有一个,传文件同时就不能传消息,有没有线程都一样的,初步预计的解决办法是传文件的时候再和服务器建立一个单独的连接,专门用来文件的传输,这样就不会因为只有一个流而不能同时工作了.)
接收客户端的处理:
if (msg.type == MessageUtil.FileTransfer) {
String fileName = ((String) msg.content)
.split("#")[0];
if (pcMap.containsKey(msg.senderNum)) {
// 消息中的发送者在我这边是待会儿的接收者
pcMap.get(msg.senderNum).textArea
.append(map.get(msg.senderNum).name
+ " 给你发送了一个文件"
+ msg.content + "\r\n");
} else {
ChatOneUI coui = new ChatOneUI(os, is, num,
msg.senderNum, map);
coui.textArea
.append(map.get(msg.senderNum).name
+ " 给你发送了一个文件: "
+ fileName + "\r\n");
pcMap.put(msg.senderNum, coui);
}
System.out.println(msg.content);
ReceiveFileThread rft = new ReceiveFileThread(
(String) msg.content);
rft.start();
// 给线程缓冲时间,不然这个接收消息的线程又去读取消息去了
while (rft.isAlive()) {
}
// 接收完毕了,发送消息给对方告诉他已经收到了
pcMap.get(msg.senderNum).textArea
.append("接收完毕。");
MessageUtil.send(os, new Message(
MessageUtil.ONEUSER, num,
msg.senderNum, "已经接收了你发送的文件: "
+ fileName + "\r\n"));
}
附上所有的代码:(图片素材在第二版里面,将images文件夹放到ChatClient文件夹下)
客户端每个包下都有一个说明文件,里面说明了这个包在上一个包的基础上进行了一些什么修改