QQ聊天项目演示
项目QQ演示为什么选择这个项目项目开发流程需求分析界面设计功能实现 -- 用户登录
QQ用户登录1QQ用户登录2QQ用户登录3QQ用户登录4QQ用户登录5 功能实现 - 拉取在线用户列表
拉取在线用户1拉取在线用户2拉取在线用户3 功能实现 - 无异常退出功能实现 - 私聊功能实现 - 群聊功能实现 - 发送文件
发送文件实现1发送文件实现2 功能实现 - 服务端推送新闻扩展功能 - 实现离线留言/发送文件
连接视频
为什么选择这个项目
1、有趣
2、涉及到java各个方面的技术
项目框架设计java面向对象编程网络编程多线程IO流Mysql / 使用集合充当内存数据库
3、巩固旧知识,学习新知识
项目开发流程
需求分析 --> 设计阶段 --> 编码实现 --> 测试阶段 --> 实施阶段
1、用户登录
2、拉取在线用户列表
3、无异常退出(客户端、服务端)
4、私聊
5、群聊
6、发文件
7、服务器推送新闻
1、用户登录
2、拉取在线用户列表
3、私聊
4、群聊
5、发文件
6、文件服务器推送新闻
1、功能说明
因为还没有学习数据库,我们 人为规定 用户/id=100,密码=123456 就可以登录,其它用户不能登录
后面使用 HashMap模拟数据库,可以多个用户登录
2、思路分析 + 程序框架图
3、代码实现
QQ用户登录1新建两个项目 QQServer 和 QQClient
package com.zzpedu.qqcommon;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;//用户id/用户名
private String passwd;//用户密码
public User(){}
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getPasswd() { return passwd; }
public void setPasswd(String passwd) { this.passwd = passwd; }
}
package com.zzpedu.qqcommon;
import java.io.Serializable;
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 messType;//消息类型[可以在接口定义消息类型]
public String getSender() { return sender; }
public void setSender(String sender) { this.sender = sender; }
public String getGetter() { return getter; }
public void setGetter(String getter) { this.getter = getter; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getSendTime() { return sendTime; }
public void setSendTime(String sendTime) { this.sendTime = sendTime; }
public String getMessType() { return messType; }
public void setMessType(String messType) { this.messType = messType; }
}
package com.zzpedu.qqcommon;
public interface MessageType {
//1. 在接口定义了一下常量
//2. 不同的常量的值,表示不同的消息类型
String MESSAGE_LONGIN_SUCCEED = "1";//表示登录成功
String MESSAGE_LONGIN_FAIL = "2";//表示登录失败
}
以上三个类,QQServer 和 QQClient 都共有
QQ用户登录2
QQClient 端, 演示界面菜单代码:
package com.zzpedu.qqclient.utils;
import java.util.Locale;
import java.util.Scanner;
public class Utility {
//静态属性...
private static Scanner scanner = new Scanner(System.in);
public static char readMenuSelection(){
char c;
for(; ;){
String str = readKeyBoard(1,false);//包含一个字符串
c = str.charAt(0);//将字符串转换成字符char类型
if(c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5' && c != '6'){
System.out.println("选择错误,请重新输入(1-6):");
}else break;
}
return c;
}
public static char readChar(){
String str = readKeyBoard(1,false);//就是一个字符
return str.charAt(0);
}
public static char readChar(char defaultValue){
String str = readKeyBoard(1,true);//要么是空字符串,要么就是一个字符
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
public static int readInt(){
int n = 0;
for (; ;){
String str = readKeyBoard(10,false);//一个整数,长度小于10位
try {
n = Integer.parseInt(str);//将字符串转成整数
break;
}catch (NullPointerException e){
System.out.println("数字输入错误,请重新输入:");
}
}
return n;
}
public static int rendInt(int defaultValue){
int n;
for (; ;){
String str = readKeyBoard(10,true);
if(str.equals("")){
return defaultValue;
}
//异常处理...
try {
n = Integer.parseInt(str);
break;
}catch (NullPointerException e){
System.out.println("数字输入错误,请重新输入:");
}
}
return n;
}
public static String readString(int limit){
return readKeyBoard(limit,false);
}
public static String readString(int limit,String defaultValue){
String str = readKeyBoard(limit,true);
return str.equals("") ? defaultValue : str;
}
public static char read/confirm/iSelection(){
System.out.print("请输入你的选择(Y/N): 小心选择: ");
char c;
for(; ;){//无限循环
//在这里,将接受到字符,转成大写字母
//y => Y n => N
String str = readKeyBoard(1,false).toUpperCase(Locale.ROOT);
c = str.charAt(0);
if(c == 'Y' || c == 'N'){
break;
}else {
System.out.print("选择错误,请重新输入(Y/N): ");
}
}
return c;
}
private static String readKeyBoard(int limit, boolean flag) {
String str = "";
do {
//nextLine():
//1.以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
//2.可以获得空白。
str = scanner.nextLine();
if(str.length() == 0){
if(flag) return str;//如果flag=true,可以返回空字符串
else continue;//如果flag=false,不接受空字符串,必须输入内容
}
//如果用户输入的内容大于了 limit ,就提示输入
//如果用户输入的内容 >0 <= limit,就返回退出
if (str.length() < 1 || str.length() > limit){
System.out.print("输入长度(不能大于" + limit +")错误,请重新输入:");
}else {
return str;
}
}while (true);
}
}
package com.zzpedu.qqclient.view;
import com.zzpedu.qqclient.utils.Utility;
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//显示主菜单
private void mainMenu(){
while (loop){
System.out.println("============欢迎登录网络通信系统============");
System.out.println("tt 1 登录系统");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
//根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
System.out.print("请求输入用户号:");
String userId = Utility.readString(50);
System.out.print("请输入密 码:");
String pwd = Utility.readString(50);
// 需要到服务端去验证该用户是否合法
// 大量代码... 我们编写一个类 UserClientService[用户登录/用户注册]
if(true){//还没写完,先把逻辑打通...
System.out.println("============欢迎 ("+ userId + " 登录成功) ============");
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
System.out.println("tt 1 显示在线用户列表");
System.out.println("tt 2 群发消息");
System.out.println("tt 3 私聊消息");
System.out.println("tt 4 发送文件");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
switch (key){
case "1":
System.out.println("显示在线用户列表");
break;
case "2":
System.out.println("群发消息");
break;
case "3":
System.out.println("私聊消息");
break;
case "4":
System.out.println("发送文件");
break;
case "9":
loop = false;
break;
}
}
}else {//登录服务器失败
System.out.println("n==========登录失败==========");
}
break;
case "9":
System.out.println("退出系统");
loop = false;
break;
}
}
}
public static void main(String[] args) {
//测试
new QQView().mainMenu();
System.out.println("客户端退出系统~~");
}
}
QQ用户登录3
QQClient 端,创建UserClientService类,ClientConnectServerThread类,ManageClientConnectServerThread类
package com.zzpedu.qqclient.service;
import com.zzpedu.qqcommon.Message;
import com.zzpedu.qqcommon.MessageType;
import com.zzpedu.qqcommon.User;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class UserClientService {
//因为我们可能在其他地方使用user信息,因此做成成员属性
private User user = new User();
//因为Socket在其他地方也可能使用,因此做成属性
private Socket socket;
//根据 userId 和 pwd 到服务器验证该用户是否合法
public boolean checkUser(String userId,String pwd){
boolean b = false;
//创建User对象
user.setUserId(userId);
user.setPasswd(pwd);
//连接到服务端,发送user对象
try {
socket = new Socket(InetAddress.getLocalHost(), 9999);
//得到 ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//发送user对象
oos.writeObject(user);
//读取从服务器回复的Message对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message msg = (Message)ois.readObject();
if(msg.getMessType().equals(MessageType.MESSAGE_LONGIN_SUCCEED)){//登录成功
//创建一个和服务器端保持通信的线程 -> 创建一个类 ClientConnectServerThead
ClientConnectServerThread connectServerThread = new ClientConnectServerThread(socket);
//启动客户端的线程
connectServerThread.start();
//这里为了后面客户端的扩展,我们将线程放入到集合管理
ManageClientConnectServerThread.addClientConnectServerThread(userId,connectServerThread);
b = true;
}else {//登录失败
//如果登录失败,我们不能启动和服务器通信的线程,关闭socket
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
}
package com.zzpedu.qqclient.service;
import com.zzpedu.qqcommon.Message;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ClientConnectServerThread extends Thread {
//该线程需要持有 Socket
private Socket socket;
//构造器,可以接收一个Socket对象
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为Thead需要在后台和服务器通信,因此我们使用while循环
while (true){
System.out.println("客户端线程,等待从读取从服务器端发送的消息...");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程阻塞在这里
Message message = (Message)ois.readObject();
//后面处理...
} catch (Exception e) {
e.printStackTrace();
}
}
}
//为了更加方便得到 Socket
public Socket getSocket() {
return socket;
}
}
package com.zzpedu.qqclient.service;
import java.util.HashMap;
public class ManageClientConnectServerThread {
//我们把多个线程放入一个HashMap集合中,key 就是用户id,value 就是线程
private static HashMap hashMap = new HashMap<>();
//将某个线程加入集合中
public static void addClientConnectServerThread(String userId, ClientConnectServerThread connectServerThread){
hashMap.put(userId,connectServerThread);
}
//通过UserId 可以得到对应的线程
public static ClientConnectServerThread getClientConnectServerThread(String userId){
return hashMap.get(userId);
}
}
在QQView类,添加UserClientService对象,效验登录信息
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//显示主菜单
private void mainMenu(){
while (loop){
....
// 需要到服务端去验证该用户是否合法
// 大量代码... 我们编写一个类 UserClientService[用户登录/用户注册]
if(userClientService.checkUser(userId,pwd)){//还没写完,先把逻辑打通...
System.out.println("============欢迎 ("+ userId + " 登录成功) ============");
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
....
}
}else {//登录服务器失败
System.out.println("n==========登录失败==========");
}
break;
case "9":
System.out.println("退出系统");
loop = false;
break;
}
}
}
}
QQ用户登录4
QQServer 服务端,新建QQServer类,ServerConnectClientThread类,ManageClientThread类
package com.zzpedu.qqserver.service;
import com.zzpedu.qqcommon.Message;
import com.zzpedu.qqcommon.MessageType;
import com.zzpedu.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class QQServer {
private ServerSocket serverSocket = null;
public QQServer(){
System.out.println("服务端在 9999 端口监听...");
//注意:端口可以写在配置文件中
try {
serverSocket = new ServerSocket(9999);
while (true){//当和某个客户端连接后,会继续监听,因此是while
Socket socket = serverSocket.accept();//如果没有客户端连接,就会阻塞在这里
//得到socket关联的对象输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//得到socket关联的对象输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//读取客户端发送的User对象
User user = (User) ois.readObject();
//创建一个Message对象,准备回复客户端
Message message = new Message();
//验证用户
if(user.getUserId().equals("100") && user.getPasswd().equals("123456")){//登录成功
message.setMessType(MessageType.MESSAGE_LONGIN_SUCCEED);
//将message对象回复客户端
oos.writeObject(message);
//创建一个线程,和客户端保持通信,该线程需要持有socket对象
ServerConnectClientThread scct = new ServerConnectClientThread(socket,user.getUserId());
//启动该线程
scct.start();
//把该线程对象,放入一个集合中,进管理
ManageClientThread.addClientThread(user.getUserId(),scct);
}else {//登录失败
System.out.println("用户 id=" + user.getUserId() + " pwd=" + user.getPasswd() + " 验证失败");
message.setMessType(MessageType.MESSAGE_LONGIN_FAIL);
//将message对象回复客户端
oos.writeObject(message);
//关闭socket
socket.close();
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
//如果服务器退出while,说明服务器端不在监听,因此关闭ServerSocket
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.zzpedu.qqserver.service;
import com.zzpedu.qqcommon.Message;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//后面使用 message ...
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//为了更加方便得到 Socket
public Socket getSocket() {
return socket;
}
}
package com.zzpedu.qqserver.service;
import java.util.HashMap;
public class ManageClientThread {
private static HashMap hashMap = new HashMap<>();
//添加线程对象到 hashMap集合
public static void addClientThread(String userId,ServerConnectClientThread connectClientThread){
hashMap.put(userId,connectClientThread);
}
//根据UserId 返回 ServerConnectClientThread线程
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hashMap.get(userId);
}
}
QQ用户登录5
在QQServer 服务端,新建QQframe类
package com.zzpedu.qqframe;
import com.zzpedu.qqserver.service.QQServer;
public class QQframe {
public static void main(String[] args) {
new QQServer();
}
}
QQServer 服务端启动
然后再 QQClient 客户端,QQView类启动
验证效果:
客户端:
服务器端:
使用HashMap模拟数据库
在 QQServer 服务端,在QQServer类,添加集合对象
public class QQServer {
private ServerSocket serverSocket = null;
//创建一个集合,存放多个用户信息,如果是这些用户登录,就认为是合法的
//这里我们也可以使用 ConcurrentHashMap,可以在处理并发的集合,没有线程安全
// HashMap 没有处理线程安全,因此在多线程下是不安全的
// ConcurrentHashMap 处理的线程安全,即线程同步处理,在多线程情况下是安全的
private static HashMap validUsers = new HashMap<>();
static {//在静态代码块,在加载类时 初始化一次 validUsers
validUsers.put("100",new User("100","123456"));
validUsers.put("200",new User("200","123456"));
validUsers.put("300",new User("300","123456"));
validUsers.put("至尊宝",new User("至尊宝","123456"));
validUsers.put("紫霞仙子",new User("紫霞仙子","123456"));
validUsers.put("菩提老祖",new User("菩提老祖","123456"));
}
//验证用户是否有效的方法
private boolean checkUser(String userId,String pwd){
User user = validUsers.get(userId);
if(user == null){//说明userId没有存在 validUsers 的key中
return false;
}
if(!user.getPasswd().equals(pwd)){//密码错误
return false;
}
return true;
}
public QQServer(){
System.out.println("服务端在 9999 端口监听...");
//注意:端口可以写在配置文件中
try {
serverSocket = new ServerSocket(9999);
while (true){//当和某个客户端连接后,会继续监听,因此是while
....
//验证用户
if(checkUser(user.getUserId(), user.getPasswd())){//登录成功
//提示:如果单点登录,根据 线程集合 的key判断是否有存在 socket ManageClientThread
....
}else {//登录失败
....
}
}
}
}
}
验证测试:
在 QQClient 客户端,多创建一个测试启动类,QQView类的mainMenu方法改成public
public class AppQQ {
public static void main(String[] args) {
//测试
new QQView().mainMenu();
System.out.println("客户端退出系统~~");
}
}
验证效果:
客户端:
服务器端:
功能实现 - 拉取在线用户列表
1、 功能说明:
2、思路分析 + 程序框架图
3、代码实现
拉取在线用户1
QQServer 和 QQClient 客户端,对MessageType类进行类型扩展
public interface MessageType {
//1. 在接口定义了一下常量
//2. 不同的常量的值,表示不同的消息类型
String MESSAGE_LONGIN_SUCCEED = "1";//表示登录成功
String MESSAGE_LONGIN_FAIL = "2";//表示登录失败
String MESSAGE_COMM_MES = "3";//普通的信息包
String MESSAGE_GET_ONLINE_FRIEND = "4";//要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND = "5";//返回在线用户列表
String MESSAGE_CLIENT_EXIT = "6";//客户端请求退出
}
在 QQClient 客户端,修改QQView类,显示在线用户列表
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//显示主菜单
public void mainMenu(){
while (loop){
System.out.println("============欢迎登录网络通信系统============");
System.out.println("tt 1 登录系统");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
//根据用户的输入,来处理不同的逻辑
switch (key){
....
//进入二级菜单
while (loop){
...
switch (key){
case "1":
//System.out.println("显示在线用户列表");
//这里写一个方法,来换取在线用户列表
userClientService.onlineFriedList();
break;
.....
}
在 QQClient 客户端,修改UserClientService类,添加获取在线用户列表方法
public class UserClientService {
//因为我们可能在其他地方使用user信息,因此做成成员属性
private User user = new User();
//因为Socket在其他地方也可能使用,因此做成属性
private Socket socket;
...
//向服务器端请求在线用户列表
public void onlineFriedList(){
//发送一个Message,类型 MESSAGE_GET_ONLINE_FRIEND
Message message = new Message();
message.setMessType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
message.setSender(user.getUserId());
//发送给服务器
try {
//从管理线程的集合中,通过userId,得到这个线程对象
ClientConnectServerThread clientConnectServerThread =
ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId());
//通过这个线程得到关联的Socket
Socket socket = clientConnectServerThread.getSocket();
//应该得到当前线程的Socket,对应的 ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);//发送一个Message对象,向服务端要求拉取在线用户列表
} catch (IOException e) {
e.printStackTrace();
}
}
}
在 QQClient 客户端,修改ClientConnectServerThread类,run方法获取Message信息类型处理
public class ClientConnectServerThread extends Thread {
//该线程需要持有 Socket
private Socket socket;
//构造器,可以接收一个Socket对象
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为Thead需要在后台和服务器通信,因此我们使用while循环
while (true){
System.out.println("客户端线程,等待从读取从服务器端发送的消息...");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程阻塞在这里
Message message = (Message)ois.readObject();
//判断这个message类型,然后做相应的业务处理
//如果是读取到的是 服务器端返回的 在线用户类列表型
if(message.getMessType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
//取出在线用户列表信息,并显示
//规定 在线用户列表形式 空格 " "
String[] onlineUsers = message.getContent().split(" ");
System.out.println("n==========当前在线用户列表==========");
for (int i = 0; i < onlineUsers.length; i++){
System.out.println("用户:" + onlineUsers[i]);
}
}else {
System.out.println("是其他类型的message,暂时不处理...");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
...
}
拉取在线用户2
在 QQServer客户端,修改ServerConnectClientThread类,run方法获取Message信息类型处理,ManageClientThread类,添加获取在线用户列表方法
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//根据message的类型,做相应的业务处理
if(message.getMessType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
//客户端要 在线用户列表
System.out.println(message.getSender() + " 要在线用户列表");
String onlineUser = ManageClientThread.getOnlineUser();
//返回Message
Message message2 = new Message();
message2.setMessType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message2.setContent(onlineUser);
message2.setGetter(message.getSender());
//返回给客户端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message2);
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
public class ManageClientThread {
private static HashMap hashMap = new HashMap<>();
...
//这里编写方法,可以返回在线用户列表
public static String getOnlineUser(){
//集合遍历,遍历 hashMap的key
Iterator iterator = hashMap.keySet().iterator();
String onlineUserList = "";
while (iterator.hasNext()){
onlineUserList += iterator.next() + " ";
}
return onlineUserList;
}
}
拉取在线用户3
测试 在线拉取用户功能,
先启动 QQServer 服务端
后启动 QQClient 客户端,启动三个main方法
QQClient 客户端,运行效果:
QQServer 客户端,运行效果:
1、功能说明
2、思路分析 - 程序框架图
3、代码实现
在QQCilent 客户端,QQView类mainMenu方法调退出方法,UserClientService类编写退出方法
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//显示主菜单
public void mainMenu(){
while (loop){
...
//根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
System.out.print("请求输入用户号:");
String userId = Utility.readString(50);
System.out.print("请输入密 码:");
String pwd = Utility.readString(50);
// 需要到服务端去验证该用户是否合法
// 大量代码... 我们编写一个类 UserClientService[用户登录/用户注册]
if(userClientService.checkUser(userId,pwd)){//还没写完,先把逻辑打通...
System.out.println("============欢迎 ("+ userId + " 登录成功) ============");
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
....
switch (key){
...
case "9":
//调用方法,给服务器发送退出系统的message
userClientService.logout();
loop = false;
break;
}
}
}else {//登录服务器失败
System.out.println("n==========登录失败==========");
}
break;
case "9":
loop = false;
break;
}
}
}
}
public class UserClientService {
//因为我们可能在其他地方使用user信息,因此做成成员属性
private User user = new User();
//因为Socket在其他地方也可能使用,因此做成属性
private Socket socket;
...
//编写方法,退出客户端,并给服务端发送一个退出系统的message对象
public void logout(){
Message message = new Message();
message.setMessType(MessageType.MESSAGE_CLIENT_EXIT);
//一定要指定是哪个客户端id
message.setSender(user.getUserId());
//发送message
try {
//ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectOutputStream oos = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(
user.getUserId()).getSocket().getOutputStream());
oos.writeObject(message);
System.out.println(user.getUserId() + " 退出系统");
System.exit(0);//结束进程
} catch (IOException e) {
e.printStackTrace();
}
}
}
在 QQServer 服务器端,ServerConnectClientThread类添加message类型判断,ManageClientThread类添加删除key方法
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
...
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//根据message的类型,做相应的业务处理
if(message.getMessType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
....
}else if(message.getMessType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
//客户端退出
System.out.println(message.getSender() + " 退出");
//将这个客户端对应的线程,从集合中删除
ManageClientThread.removeServerConnectClientThread(message.getSender());
socket.close();//关闭连接
//退出线程
break;
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
public class ManageClientThread {
private static HashMap hashMap = new HashMap<>();
....
//增加一个方法,从集合中,移除某个线程对象
public static void removeServerConnectClientThread(String userId){
hashMap.remove(userId);
}
}
测试:
启动 服务器端,然后启动 客户端
客户端 运行效果:
服务器端 运行效果:
1、功能说明
2、思路分析 + 程序框架图
3、代码实现
QQClient 客户端,在QQView类,调用私聊方法,新增MessageClientService类用于聊天,ClientConnectServerThread类处理接收message类型处理显示
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//这个对象用户消息的私聊/群聊
private MessageClientService messageClientService = new MessageClientService();
//显示主菜单
public void mainMenu(){
while (loop){
System.out.println("============欢迎登录网络通信系统============");
...
//根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
...
if(userClientService.checkUser(userId,pwd)){//还没写完,先把逻辑打通...
System.out.println("============欢迎 ("+ userId + " 登录成功) ============");
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
...
key = Utility.readString(1);
switch (key){
...
case "3":
System.out.print("请输入想聊天的用户号(在线):");
String getterId = Utility.readString(50);
System.out.print("请求输入想说的话:");
String content = Utility.readString(100);
//编写一个方法,将消息发送给服务端
messageClientService.sendMessageToOne(content,userId,getterId);
break;
case "4":
System.out.println("发送文件");
break;
case "9":
//调用方法,给服务器发送退出系统的message
userClientService.logout();
loop = false;
break;
}
}
}else {//登录服务器失败
System.out.println("n==========登录失败==========");
}
break;
case "9":
loop = false;
break;
}
}
}
}
package com.zzpedu.qqclient.service;
import com.zzpedu.qqcommon.Message;
import com.zzpedu.qqcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
public class MessageClientService {
public void sendMessageToOne(String content,String senderId,String getterId){
//构建Message
Message message = new Message();
message.setMessType(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();
}
}
}
public class ClientConnectServerThread extends Thread {
//该线程需要持有 Socket
private Socket socket;
//构造器,可以接收一个Socket对象
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为Thead需要在后台和服务器通信,因此我们使用while循环
while (true){
System.out.println("客户端线程,等待从读取从服务器端发送的消息...");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程阻塞在这里
Message message = (Message)ois.readObject();
//判断这个message类型,然后做相应的业务处理
//如果是读取到的是 服务器端返回的 在线用户类列表型
if(message.getMessType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
...
}else if(message.getMessType().equals(MessageType.MESSAGE_COMM_MES)){//普通的聊天消息
//把从服务器转发的消息,显示到控制台即可
System.out.println("n" + message.getSender() + " 对 "
+ message.getGetter() + " 说:" + message.getContent());
}else {
System.out.println("是其他类型的message,暂时不处理...");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
QQServer 服务器端,ServerConnectClientThread类添加message类型处理
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//根据message的类型,做相应的业务处理
if(message.getMessType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
...
}else if(message.getMessType().equals(MessageType.MESSAGE_COMM_MES)){
//根据message获取getterId,然后在得到对应的线程
ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(message.getGetter());
//得到对应的socket的对象输出流,将message对象转发给指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//转发,提示客户不在线,可以保存到数据库,这样可以实现离线留言
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
测试效果:
客户端:
服务器端:
1、功能说明
2、思路分析 + 程序框架图
3、代码实现
在公共类MessageType类,添加一个类型
public interface MessageType {
...
String MESSAGE_TO_ALL_MES = "7";//群发类型
}
QQClient 客户端,在QQView类,调用私聊方法,新增MessageClientService类用于聊天,ClientConnectServerThread类处理接收message类型处理显示
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//这个对象用户消息的私聊/群聊
private MessageClientService messageClientService = new MessageClientService();
//显示主菜单
public void mainMenu(){
while (loop){
....
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
System.out.println("tt 1 显示在线用户列表");
System.out.println("tt 2 群发消息");
System.out.println("tt 3 私聊消息");
System.out.println("tt 4 发送文件");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
switch (key){
...
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 MessageClientService {
public void sendMessageToAll(String content,String senderId){
//构建Message
Message message = new Message();
message.setMessType(MessageType.MESSAGE_TO_ALL_MES);//群发类型
message.setSender(senderId);
message.setContent(content);
System.out.println(senderId + " 对大家说 " + content);
//发送给服务端
try {
ObjectOutputStream oos = new ObjectOutputStream(
ManageClientConnectServerThread.getClientConnectServerThread(senderId)
.getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
QQServer 服务器端,ServerConnectClientThread类添加message类型处理,在ManageClientThread类添加获取hashMap方法
public class ManageClientThread {
private static HashMap hashMap = new HashMap<>();
..
//返回 hashMap
public static HashMap getHashMap() {
return hashMap;
}
}
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
..
}else if(message.getMessType().equals(MessageType.MESSAGE_TO_ALL_MES)){
//群发消息
//需要遍历 管理线程的集合,把所有的线程的socket得到,把message进行转发即可
HashMap hashMap = ManageClientThread.getHashMap();
Iterator iterator = hashMap.keySet().iterator();
while (iterator.hasNext()){
//取出在线用户的id
String onlineUserId = iterator.next();
if(!onlineUserId.equals(message.getSender())){//排除群发消息的这个用户
//进行转发message
ObjectOutputStream oos = new ObjectOutputStream(hashMap.get(onlineUserId).getSocket().getOutputStream());
oos.writeObject(message);//转发
}
}
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
测试:
客户端 效果
1、功能说明
2、思路分析 + 程序框架图
3、代码实现
发送文件实现1在公共类MessageType类,添加一个类型
public interface MessageType {
...
String MESSAGE_FILE_MES = "8";//文件消息(发送文件)
}
在公共类Message类,进行扩展
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
...
//进行扩展 和文件相关的成员
private byte[] fileBytes;
private int fileLen = 0;
private String dest;//将文件传输到哪里
private String src;//源文件路径
public byte[] getFileBytes() { return fileBytes; }
public void setFileBytes(byte[] fileBytes) { this.fileBytes = fileBytes; }
public int getFileLen() { return fileLen; }
public void setFileLen(int fileLen) { this.fileLen = fileLen; }
public String getDest() { return dest; }
public void setDest(String dest) { this.dest = dest; }
public String getSrc() { return src; }
public void setSrc(String src) { this.src = src; }
}
QQClient 客户端,新增FileClientService类
package com.zzpedu.qqclient.service;
import com.zzpedu.qqcommon.Message;
import com.zzpedu.qqcommon.MessageType;
import java.io.*;
import java.net.Socket;
import java.util.Date;
public class FileClientService {
public void sendFileToOne(String src,String dest,String senderId,String getterId){
//读取 src文件 --> message
Message message = new Message();
message.setMessType(MessageType.MESSAGE_FILE_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setSrc(src);
message.setDest(dest);
message.setSendTime(new Date().toString());
//需要将文件读取
FileInputStream fis = null;
byte[] fileBytes = new byte[(int)new File(src).length()];
try {
fis = new FileInputStream(src);
//将src文件读入到程序的字节数组
fis.read(fileBytes);
//将文件对应的字节数组设置message
message.setFileBytes(fileBytes);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭
try {
if(fis != null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//提示信息
System.out.println("n" + senderId + " 给" + getterId + " 发送文件: " + src
+ " 到对方的电脑目录: " + dest);
try {
//发送
ClientConnectServerThread clientConnectServerThread =
ManageClientConnectServerThread.getClientConnectServerThread(senderId);
//通过这个线程得到关联的Socket
Socket socket = clientConnectServerThread.getSocket();
//应该得到当前线程的Socket,对应的 ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
发送文件实现2
在 QQServer 服务器端,ServerConnectClientThread类添加类型,转发message信息
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//根据message的类型,做相应的业务处理
...
}else if(message.getMessType().equals(MessageType.MESSAGE_FILE_MES)){//发送文件
//转发文件
//根据getterId 获取到对应的线程,将message对象转发
ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(message.getGetter());
//得到对应的socket的对象输出流,将message对象转发给指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//转发
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
在 QQClient 客户端,ClientConnectServerThread类添加message类型处理,在QQView类,调用发送文件方法
public class ClientConnectServerThread extends Thread {
//该线程需要持有 Socket
private Socket socket;
//构造器,可以接收一个Socket对象
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为Thead需要在后台和服务器通信,因此我们使用while循环
while (true){
System.out.println("客户端线程,等待从读取从服务器端发送的消息...");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程阻塞在这里
Message message = (Message)ois.readObject();
//判断这个message类型,然后做相应的业务处理
//如果是读取到的是 服务器端返回的 在线用户类列表型
if(message.getMessType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
....
}else if(message.getMessType().equals(MessageType.MESSAGE_FILE_MES)){//如果是文件消息
System.out.println("n" + message.getSender() + " 给 " + message.getGetter()
+ " 发送文件: " + message.getSrc() + " 到我的电脑的目录: " + message.getDest());
//取出message的文件字节数组,通过文件输出流写入到磁盘
FileOutputStream fos = new FileOutputStream(message.getDest());
fos.write(message.getFileBytes());
fos.close();
System.out.println("n 保存文件成功~");
}else {
System.out.println("是其他类型的message,暂时不处理...");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
//这个对象是用于登录服务/注册用户
private UserClientService userClientService = new UserClientService();
//这个对象用户消息的私聊/群聊
private MessageClientService messageClientService = new MessageClientService();
//这个对象用户传输文件
private FileClientService fileClientService = new FileClientService();
//显示主菜单
public void mainMenu(){
while (loop){
System.out.println("============欢迎登录网络通信系统============");
System.out.println("tt 1 登录系统");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
//根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
...
if(userClientService.checkUser(userId,pwd)){//还没写完,先把逻辑打通...
System.out.println("============欢迎 ("+ userId + " 登录成功) ============");
//进入二级菜单
while (loop){
System.out.println("n==========网络通信系统二级菜单(用户 " + userId + " ) ==========");
System.out.println("tt 1 显示在线用户列表");
System.out.println("tt 2 群发消息");
System.out.println("tt 3 私聊消息");
System.out.println("tt 4 发送文件");
System.out.println("tt 9 退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
switch (key){
case "1":
...
case "4":
System.out.print("请输入你想把文件发送给的用户(在线用户): ");
getterId = Utility.readString(50);
System.out.print("请输入发送文件的路径(形式 f:\xxx.jpg):");
String src = Utility.readString(100);
System.out.print("请输入把文件发送到对方的路径(形式 f:\yyy.jpg)");
String dest = Utility.readString(100);
fileClientService.sendFileToOne(src,dest,userId,getterId);
break;
....
}
}
}
测试:
1、功能说明
2、思路分析 + 程序框架图
3、代码实现
在 QQServer 服务器端,新建SendNewsToAllService类,在QQServer类调用
package com.zzpedu.qqserver.service;
import com.zzpedu.qqcommon.Message;
import com.zzpedu.qqcommon.MessageType;
import com.zzpedu.utils.Utility;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
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.setMessType(MessageType.MESSAGE_TO_ALL_MES);
message.setSender("服务器");
message.setContent(news);
message.setSendTime(new Date().toString());
System.out.println("服务器推送消息给所有人 说: " + news);
//变量当前所有通信线程,得到socket,并发送message
HashMap hashMap = ManageClientThread.getHashMap();
Iterator iterator = hashMap.keySet().iterator();
while (iterator.hasNext()){
String onLineUserId = iterator.next();
try {
ObjectOutputStream oos =
new ObjectOutputStream(hashMap.get(onLineUserId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class QQServer {
public QQServer(){
System.out.println("服务端在 9999 端口监听...");
//启动推送新闻的线程
new Thread(new SendNewsToAllService()).start();
...
}
测试:
服务器端
客户端
思路分析 + 程序框架图:
代码实现:
在 QQServer 服务器端,QQServer类新建集合用于保存离线消息,新建SendOffLineDbService类,用了发送离线消息,ServerConnectClientThread类添加离线message消息
public class QQServer {
private ServerSocket serverSocket = null;
...
//离线消息保存集合
private static ConcurrentHashMap> offLineDb = new ConcurrentHashMap<>();
//发送离线消息
private SendOffLineDbService sendOffLineDbService = new SendOffLineDbService();
//验证用户是否有效的方法
private boolean checkUser(String userId,String pwd){
User user = validUsers.get(userId);
if(user == null){//说明userId没有存在 validUsers 的key中
return false;
}
if(!user.getPasswd().equals(pwd)){//密码错误
return false;
}
return true;
}
public QQServer(){
System.out.println("服务端在 9999 端口监听...");
//启动推送新闻的线程
new Thread(new SendNewsToAllService()).start();
//注意:端口可以写在配置文件中
try {
serverSocket = new ServerSocket(9999);
while (true){//当和某个客户端连接后,会继续监听,因此是while
Socket socket = serverSocket.accept();//如果没有客户端连接,就会阻塞在这里
//得到socket关联的对象输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//得到socket关联的对象输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//读取客户端发送的User对象
User user = (User) ois.readObject();
//创建一个Message对象,准备回复客户端
Message message = new Message();
//验证用户
if(checkUser(user.getUserId(), user.getPasswd())){//登录成功
//提示:如果单点登录,根据 线程集合 的key判断是否有存在 socket ManageClientThread
message.setMessType(MessageType.MESSAGE_LONGIN_SUCCEED);
//将message对象回复客户端
oos.writeObject(message);
//创建一个线程,和客户端保持通信,该线程需要持有socket对象
ServerConnectClientThread scct = new ServerConnectClientThread(socket,user.getUserId());
//启动该线程
scct.start();
//把该线程对象,放入一个集合中,进管理
ManageClientThread.addClientThread(user.getUserId(),scct);
//发送离线消息
sendOffLineDbService.sendOffLineDb(user.getUserId());
}else {//登录失败
System.out.println("用户 id=" + user.getUserId() + " pwd=" + user.getPasswd() + " 验证失败");
message.setMessType(MessageType.MESSAGE_LONGIN_FAIL);
//将message对象回复客户端
oos.writeObject(message);
//关闭socket
socket.close();
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
//如果服务器退出while,说明服务器端不在监听,因此关闭ServerSocket
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void addOffLineDb(String userId,Message message){
ArrayList messages = offLineDb.get(userId);
if(messages == null){//不存在messages 新增集合
messages = new ArrayList<>();
}
messages.add(message);
offLineDb.put(userId, messages);
}
//获取离线消息
public static ArrayList getOffMessages(String userId){
return offLineDb.get(userId);
}
public static void removeOffMessages(String userId){
offLineDb.remove(userId);
}
}
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
@Override
public void run() {//这里线程处于run的状态,可以发送/接收消息
while (true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
ObjectInputStream ois;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
//根据message的类型,做相应的业务处理
if(message.getMessType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
}else if(message.getMessType().equals(MessageType.MESSAGE_COMM_MES)){
//根据message获取getterId,然后在得到对应的线程
ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(message.getGetter());
if(null == serverConnectClientThread){
//保存离线消息
QQServer.addOffLineDb(message.getGetter(),message);
continue;
}
//得到对应的socket的对象输出流,将message对象转发给指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//转发,提示客户不在线,可以保存到数据库,这样可以实现离线留言
}else if(message.getMessType().equals(MessageType.MESSAGE_FILE_MES)){//发送文件
//转发文件
//根据getterId 获取到对应的线程,将message对象转发
ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(message.getGetter());
if(null == serverConnectClientThread){
//保存离线消息
QQServer.addOffLineDb(message.getGetter(),message);
continue;
}
//得到对应的socket的对象输出流,将message对象转发给指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//转发
}else {
System.out.println("其他类型的message,暂时不处理...");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
package com.zzpedu.qqserver.service;
import com.zzpedu.qqcommon.Message;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class SendOffLineDbService {
public void sendOffLineDb(String userId){
//获取离线消息
ArrayList offMessages = QQServer.getOffMessages(userId);
if(offMessages == null){
return;
}
try {
for (Message msg : offMessages){
//获取userId对应的socket
ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(userId);
ObjectOutputStream oos =
new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
//删除对应userId离线集合
QQServer.removeOffMessages(userId);
}
}
测试:
客户端



