【韩老师零基础30天学会Java 22】socket聊天项目02,客户端退出通知服务端,客户端异常退出,私聊,群聊,发文件,服务器群发

解决退出 子线程 不结束

客户端解决方法
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<>();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值