解决退出 子线程 不结束
客户端解决方法
1.在main线程调用方法,给服务器端发送一个退出系统的message 对象2.调用System.exit(0) //正常退出
服务器端
1.服务器端和某个客户端通信的线程如果接收到了一个退出系统的message后
2.把这个线程持有的socket关闭
3.退出该线程的run方法(退出while),就是退出线程
客户端提供核心方法
UserClientService
//编写方法,退出客户端,并给服务端发送一个退出系统的message对象
public void logout() {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(u.getUserId());//一定要指定我是哪个客户端id
//发送message
try {
//ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId())
.getSocket().getOutputStream());
oos.writeObject(message);
System.out.println(u.getUserId() + " 退出系统 ");
System.exit(0);//结束进程
} catch (IOException e) {
e.printStackTrace();
}
}
View类调用
case "9":
//调用方法,给服务器发送一个退出系统的message
userClientService.logout();
loop = false;
break;
服务端 关闭线程
public class ServerConnectClientThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//客户端退出
System.out.println(message.getSender() + " 退出");
//将这个客户端对应线程,从集合删除.
ManageClientThreads.removeServerConnectClientThread(message.getSender());
socket.close();//关闭连接
//退出线程
break;
} else {
System.out.println("其他类型的message , 暂时不处理");
}
优化客户端异常 关闭程序
- SocketException 报错的原因是:Connection reset
//服务端,这个线程 在 保持通信
public class ServerConnectClientThread extends Thread {
@Override
public void run() { //这里线程处于run的状态,可以发送/接收消息
while (true) {
try {
System.out.println("服务端和客户端" + userId + " 保持通信,读取数据...");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();//这里会 卡着
} catch (Exception e) {
e.printStackTrace();
System.out.println(userId + " 异常退出了");
//异常后,移除连接
ManageClientThreads.removeServerConnectClientThread(userId);
try {
socket.close();//关闭连接
} catch (IOException ex) {
ex.printStackTrace();
}
//异常了
break;
}//catch
}//while
}//run
}//类
私聊
客户端
1.接收用户希望给某个其它在线用户聊天的内容。
2.将消息构建成Message对象,通过对应的socket发送给服务器
3.在他的线程(通信线程中),读权到发送的message消息,并显示即可
服务端
1.可以读取到客户端发送给某个客户的消息
2.从管理线程的集合中,根据message对象的getterid获取到对应线程的socket
3.然后将message对象转发给指定客户
创建消息服务
* 该类/对象,提供和消息相关的服务方法
*/
public class MessageClientService {
/**
* @param content 内容
* @param senderId 发送用户id
* @param getterId 接收用户id
*/
public void sendMessageToOne(String content, String senderId, String getterId) {
//构建message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_COMM_MES);//普通的聊天消息这种类型
message.setSender(senderId);
message.setGetter(getterId);
message.setContent(content);
message.setSendTime(new Date().toString());//发送时间设置到message对象
System.out.println(senderId + " 对 " + getterId + " 说 " + content);
//发送给服务端
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
View类增加功能
case "3":
System.out.print("请输入想聊天的用户号(在线): ");
String getterId = Utility.readString(50);
System.out.print("请输入想说的话: ");
String content = Utility.readString(100);
//编写一个方法,将消息发送给服务器端
messageClientService.sendMessageToOne(content, userId, getterId);
break;
服务端负责转发
public class ServerConnectClientThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
//获取 接收者的 线程
//根据message获取getter id, 然后在得到对应先线程
ServerConnectClientThread serverConnectClientThread =
ManageClientThreads.getServerConnectClientThread(message.getGetter());
//得到对应socket的对象输出流,将message对象转发给指定的客户端
ObjectOutputStream oos =
new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//转发,提示如果客户不在线,可以保存到数据库,这样就可以实现离线留言
}
}
客户端收到 展示
public class ClientConnectServerThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//普通的聊天消息
//把从服务器转发的消息,显示到控制台即可
System.out.println("\n" + message.getSender()
+ " 对 " + message.getGetter() + " 说: " + message.getContent());
}
}
群聊
群聊的核心逻辑
* 该类/对象,提供和消息相关的服务方法
public class MessageClientService {
/**
* @param content 内容
* @param senderId 发送者
*/
public void sendMessageToAll(String content, String senderId) {
//构建message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);//群发消息这种类型
message.setSender(senderId);
message.setContent(content);
message.setSendTime(new Date().toString());//发送时间设置到message对象
System.out.println(senderId + " 对大家说 " + content);
//发送给服务端
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
View 增加功能
key = Utility.readString(1);
switch (key) {
case "1":
//这里老师准备写一个方法,来获取在线用户列表
userClientService.onlineFriendList();
break;
case "2":
System.out.println("请输入想对大家说的话: ");
String s = Utility.readString(100);
messageClientService.sendMessageToAll(s, userId);
break;
case "3":
服务端群发
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
//需要遍历 管理线程的集合,把所有的线程的socket得到,然后把message进行转发即可
HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();
Iterator<String> iterator = hm.keySet().iterator();
while (iterator.hasNext()) {
//取出在线用户id
String onLineUserId = iterator.next().toString();
if (!onLineUserId.equals(message.getSender())) {//排除群发消息的这个用户
//进行转发message
ObjectOutputStream oos =
new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());
oos.writeObject(message);
}
}
}
客户端 接收 服务端的群发
public class ClientConnectServerThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
//显示在客户端的控制台
System.out.println("\n" + message.getSender() + " 对大家说: " + message.getContent());
}
}
发送文件
客户端
1.先把文件a.jpg读取到客户端,字节数组
2.把文件对应的字节数组封装到message对象[包含文件内容,sender,getter]
3.将message对象发送给服务端.
4.在接收到包含有文件的消息后,将将该文件保存到磁盘
服务端
1.接收到message对象
2.拆解message对象的getterid,获取该用户的通信线程
3.把message对象转发给指定用户.
扩展消息类
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//消息内容
private String sendTime;//发送时间
private String mesType;//消息类型[可以在接口定义消息类型]
//进行扩展 和文件相关的成员
private byte[] fileBytes;
private int fileLen = 0;
private String dest; //将文件传输到哪里
private String src; //源文件路径
}
发送文件的核心逻辑
* 该类/对象完成 文件传输服务
public class FileClientService {
/**
*
* @param src 源文件
* @param dest 把该文件传输到对方的哪个目录
* @param senderId 发送用户id
* @param getterId 接收用户id
*/
public void sendFileToOne(String src, String dest, String senderId, String getterId) {
//读取src文件 --> message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_FILE_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setSrc(src);
message.setDest(dest);
//需要将文件读取
FileInputStream fileInputStream = null;
byte[] fileBytes = new byte[(int)new File(src).length()];
try {
fileInputStream = new FileInputStream(src);
fileInputStream.read(fileBytes);//将src文件读入到程序的字节数组
//将文件对应的字节数组设置message
message.setFileBytes(fileBytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭
if(fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//提示信息
System.out.println("\n" + senderId + " 给 " + getterId + " 发送文件: " + src
+ " 到对方的电脑的目录 " + dest);
//发送
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端负责转发
* 该类的一个对象和某个客户端保持通信
public class ServerConnectClientThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
//根据getter id 获取到对应的线程,将message对象转发
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
//转发
oos.writeObject(message);
}
}
客户端 进行保存
public class ClientConnectServerThread extends Thread {
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//如果是文件消息
//让用户指定保存路径。。。
System.out.println("\n" + message.getSender() + " 给 " + message.getGetter()
+ " 发文件: " + message.getSrc() + " 到我的电脑的目录 " + message.getDest());
//取出message的文件字节数组,通过文件输出流写出到磁盘
FileOutputStream fileOutputStream = new FileOutputStream(message.getDest(), true);
fileOutputStream.write(message.getFileBytes());
fileOutputStream.close();
System.out.println("\n 保存文件成功~");
}
}
View增加功能
case "4":
System.out.print("请输入你想把文件发送给的用户(在线用户): ");
getterId = Utility.readString(50);
System.out.print("请输入发送文件的路径(形式 d:\\xx.jpg)");
String src = Utility.readString(100);
System.out.print("请输入把文件发送到对应的路径(形式 d:\\yy.jpg)");
String dest = Utility.readString(100);
fileClientService.sendFileToOne(src,dest,userId,getterId);
break;
服务器群发
服务端
1.推送消息/新闻,本质其实就是群发消息2.在服务器启动一条独立线程,专门负责发送推送新闻
服务端增加处理线程
public class SendNewsToAllService implements Runnable {
@Override
public void run() {
//为了可以推送多次新闻,使用while
while (true) {
System.out.println("请输入服务器要推送的新闻/消息[输入exit表示退出推送服务线程]");
String news = Utility.readString(100);
if("exit".equals(news)) {
break;
}
//构建一个消息 , 群发消息
Message message = new Message();
message.setSender("服务器");
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setContent(news);
message.setSendTime(new Date().toString());
System.out.println("服务器推送消息给所有人 说: " + news);
//遍历当前所有的通信线程,得到socket,并发送message
HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();
Iterator<String> iterator = hm.keySet().iterator();
while (iterator.hasNext()) {
String onLineUserId = iterator.next().toString();
try {
ObjectOutputStream oos =
new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
启动这个线程
public QQServer() {
//注意:端口可以写在配置文件.
try {
System.out.println("服务端在9999端口监听...");
//启动推送新闻的线程
new Thread(new SendNewsToAllService()).start();
ss = new ServerSocket(9999);
}
}
其他功能思考
1.实现离线留言,如果某个用户没有在线,
当登录后,可以接受离线的消息
2.实现离线发文件,如果某个用户没有在线,当登录后,可以接受离线的文件
服务端
1.当有客户发送消息/文件,如果用户不在线
2.把message存放到服务的db[CHM]3, key -> getterid value->ArrayList ,ArrayList存放message
4.当用户登录后,到服务端db去查找,如果有getter=userid ,就取出ArrayList的Message 对象,发送给对应客户端即可
集合ConcurrentHashMap存放离线message db
//private static ConcurrentHashMap<String, ArrayList<Message>> offLineDb = new ConcurrentHashMap<>();