本文档描述了Server端接收到Client的消息并转发给所有客户端或私发给某个客户端

服务端为当前客户端创建一个线程,此线程接收当前客户端的消息并转发给所有客户端或私发给某个客户端

一、Server:

1.1.Server端添加将消息转化给客户端的代码。有用集合保存输出流,、从集合获取输出流,广播数据给所有人的方法sendMessage()、将消息发送给固定某个客户端的方法sendMessageToSomeone()

Server端的ClientHandler线程类中添加如下代码(主要是在上一个文档的基础上添加返回消息给客户端的代码)。 

 

1.2.run()方法上边添加一个群发消息的方法sendMessage()还有另一个私发消息的方法sendMessageToSomeOne()

 

/**将消息发送给所有客户端*/
public void sendMessage(String message){
    System.out.println(message);
    synchronized (allOut) {
        //同步代码块synchronized(){}作用:
        //这里要求遍历集合时与增删元素互斥,因为迭代器要求遍历过程中不可以通过集合方法增删元素
        //这里用forEach循环迭代集合时,不能调用集合的put()add()和remove()方法否则抛异常。
        //所以应该把forEach循环放入到同步代码块synchronized(){}中
        //(因为:forEach循环会编译为Iterator迭代器方式)
        //for (PrintWriter o : allOut) {
        //    o.println(message);//从allOut集合中获取一个输出流o,再用输出流o的println返回数据给客户端
        //}
        //原本用List  allOut集合则用forEach循环时是直接调用的

        //换成Map allOut集合则应该先用allOut.values()方法获取所有value集合,再用forEach
        //遍历allOut的所有value
        Collection<PrintWriter> c = allOut.values();
        for(PrintWriter o : c){
            o.println(message);//从allOut集合中获取一个输出流o,再用输出流o的println返回数据给客户端
        }//循环完allOut集合中的所有输出流 可以将此message返回给每个客户端
    }
}
public void sendMessageToSomeone(String message){/**私聊功能*/
    //私聊消息的格式:   @对方昵称:聊天消息
    //私聊消息格式验证:  @.+:.+      //@开头然后有一个以上的字符,然后是":" , 最后也是一个以上的字符
    //                            //@张三: 你好
    if(message.matches("@.+:.+")){
        //从私聊信息message截取到对方的昵称(从聊天消息第1个字符开始截取到":"字符的位置<第0个字符@不需要>)
        // 比如截取 "@张三: 你好"  中的"张三"
        String toNickName = message.substring(1,message.indexOf(":"));
        //截取出要发送给对方的内容(从聊天消息的":"字符的后一个位置到消息的末尾之间的所有文字)
        String toMessage = message.substring(message.indexOf(":")+1);
        //从Map allOut集合通过对方的昵称作为key,获取他对应的输出流
        PrintWriter pw = allOut.get(toNickName);
        if(pw==null){//如果没有获取到输出流,则说明对方昵称对应的用户不存在(对方下线了)
       //从Map allOut集合获取发送消息的客户端的输出流,用此println()给发送消息的客户端返回 "用户[接收方昵称]不存在!"
            allOut.get(nickName).println("用户["+toNickName+"]不存在!");
        }else {
            //如果范传奇在控制台上输入:   @王克晶:晚上有空么?
            //则"王克晶"为toNickname    "晚上有空么?" 为toMessage       
            //   最终要向王克晶发送的消息内容是: “ 范传奇悄悄对你说:晚上有空么?”
            pw.println(nickName+"悄悄对你说:"+toMessage);
            //调用saveMessage()将该消息保存到数据库中      
        }
    }else{//私聊格式错误(如果发送的消息message不是以@开头)
        //就从Map  allOut集合获取发送消息的客户端对应的输出流,用println()返回给发送消息的客户端  "私聊格式错误..."
        allOut.get(nickName).println("私聊格式错误(@对方昵称:聊天内容),请重新输入");
    }
}

 

 

二、Client端:

客户端需要编写一个单独的线程类ServerHandler,用于接收服务端返回的数据 (main方法调用的start()方法内发送消息<main线程发送消息>)  (是为了发送消息的同时还能接收消息)

2.1.ServerHandler

编写用于接收服务端返回的消息的线程类:

/**该线程任务负责不断地接收来自服务端返回的消息*/
private class ServerHandler implements Runnable{
    public void run() {
        try {
            //通过socket获取输入流,用于读取服务端发送过来的消息
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
            BufferedReader br = new BufferedReader(isr);
            //循环读取来自服务端发送过来的消息并输出到客户端控制台上
            String message;
            while((message = br.readLine())!=null){
                System.out.println(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 2.2.创建线程类对象并启动:

Client类中的start方法中, 在打印”昵称不能为空”的下边添加如下启动线程的代码(并将此接收服务端消息的线程设置为后台线程。)

 

 运行效果:

 

 

 

 


 

                                                                                                                                                                       By   zhaoyq   2024-05-31