2.2 网络多线程(私聊、群发、发送文件、推送新闻、离线留言)

news2024/11/19 0:24:57

文章目录

  • 一、私聊
    • 1.1 分析
    • 1.2 客户端
      • 1.2.1 MessageClientService 私聊类
      • 1.2.2 ClientConnectServerThread 线程类
    • 1.3 服务端
      • 1.3.1 ServerConnectClientThread 线程类
    • 1.4功能演示
  • 二、群发消息
    • 2.1 分析
    • 2.2 客户端
      • 2.2.1 MessageClientService类
      • 2.2.2 ClientConnectServerThread 线程类
    • 2.3 服务端
      • 2.3.1 ServerConnectClientThread 线程类
    • 2.4 测试
  • 三、发送文件
    • 3.0 消息类扩展
    • 3.1 分析
    • 3.2 客户端
      • 3.2.1 FileClientService 文件传输类
      • 3.2.2 ClientConnectServerThread 线程类接收文件
    • 3.3 服务端
      • 3.3.1 ServerConnectClientThread 线程类
    • 3.4 测试结果
  • 四、服务端推送新闻
    • 4.1 分析
    • 4.2 客户端
      • 4.2.1 ClientConnectServerThread 线程类
    • 4.3 服务端
      • 4.3.1 SendNewsAllService推送消息
      • 4.3.2 QQServer启动线程
    • 4.4 测试
  • 五、离线消息
  • 六、代码总结
    • 6.1 公共类代码
      • 6.1.1 消息类
      • 6.1.2 消息类型类
      • 6.1.3 客户类
      • 6.1.4 控制台读取类
    • 6.2 客户端代码
      • 6.2.1 QQView 客户端页面
      • 6.2.2 ClientConnectServerThread线程类
      • 6.2.3 ManagerClientConnectServerThread线程管理类
      • 6.2.4 MessageClientService发送消息类
      • 6.2.5 FileClientService发送文件类
      • 6.2.6 UserClientService 用户登录验证类
    • 6.3 服务端代码
      • 6.3.1 后台启动
      • 6.3.2 服务器
      • 6.3.3 ServerConnectClientThread线程类
      • 6.3.4 ManagerServerConnectServerThread管理线程类
      • 6.3.5 SendNewsAllService 推送新闻类

一、私聊

1.1 分析

客户端A和客户端B私聊的时候,其实服务端在中间做了一个转发

流程

  • 客户端A —> 服务端 —> 客户端B

  • 客户端B —> 服务端 —> 客户端A

服务端可以读取到客户端A发送给客户端B的消息,服务端再从管理线程的集合中获取接收者客户端B的线程,也就能获取到其socket,此线程在服务端就会将消息发送给客户端B,也就是服务器只需要做个转发即可

image-20231208224106294

1.2 客户端

1.2.1 MessageClientService 私聊类

/**
 * 该类提供和消息相关的服务方法
 */
public class MessageClientService {
    /**
     * @param content  内容
     * @param senderId 发送用户id
     * @param getterId 接收用户id
     */
    public void sendMessageToOne(String content, String senderId, String getterId) {
        //封装消息
        Message message = new Message();
        message.setContent(content);
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_COMM_MES.getCode());//普通消息
        System.out.println("用户"+senderId+"和用户"+getterId+"说:"+content);
        //获取senderId对应的socket
        ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(senderId);
        Socket socket = clientConnectServerThread.getSocket();

        //输出消息
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

1.2.2 ClientConnectServerThread 线程类

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientConnectServerThread extends Thread {
    //该线程需要持有Socket属性
    private Socket socket;


    /**
     *因为Thread需要在后台跟我们的服务器进行通信(保持一个联系),因此我们使用while循环来控制
     */
    @Override
    public void run() {
        while(true){
            //一直读取从服务器端回收的消息
            System.out.println("客户端线程,等待读取从服务端发送的消息....");

            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
                //这就是一个堵塞式网络编程,效率是相对比较低的
                Message message = (Message)ois.readObject();

                //判断message的类型,然后做响应的业务处理
                if (message.getMesType().equals(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode())){
                    //获取在线用户,取出在线列表信息并显示
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("当前在线用户列表如下");
                    for (int i=0;i<onlineUsers.length;i++){
                        System.out.println("用户:"+onlineUsers[i]);
                    }
                }else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {
                    //转发给指定客户端,假如说客户不在线的话,可以保存到数据库,这样就可以实现离线留言
                    System.out.println("用户"+message.getGetter()+"收到来自用户"+message.getSender()+"的消息:"+message.getContent());
                }else{
                    System.out.println("其他类型的message,暂时不处理");
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

1.3 服务端

1.3.1 ServerConnectClientThread 线程类

/**
 * 该类对应的对象和某个客户端保持通信
 */
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerConnectClientThread extends Thread {

    /**
     * 可以区分此socket是和哪个用户进行关联的
     */
    private String userId;//连接到服务端的这个用户id

    private Socket socket;

    /**
     * 线程处于run状态,可以发送或者接收客户端的消息
     */
    @Override
    public void run() {
        //不断的从socket中读数据和写数据
        while (true) {
            System.out.println("服务端和客户端保持通信,读取数据.... userId:" + userId);
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                //读取数据
                Message message = (Message) ois.readObject();

                //根据Message的类型,判断客户端想要执行什么操作
                if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())) {
                    System.out.println("用户" + userId + "获取在线用户");
                    //拉取在线用户(客户端要拉取在线用户列表)
                    Socket socket = ManagerServerConnectServerThread.getClientThread(userId).getSocket();

                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    //构建Message发送给服务端
                    Message returnMessage = new Message();
                    returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());
                    returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());
                    //说明要发送给谁
                    returnMessage.setGetter(message.getSender());
                    //返回给客户端
                    oos.writeObject(returnMessage);
                    oos.flush();
                } else if (MessageType.MESSAGE_CLIENT_EXIT.getCode().equals(message.getMesType())) {
                    //说明客户端想要退出,服务端要将socket关闭并退出线程就可以了
                    //将客户端对应的线程从集合中删除
                    ManagerServerConnectServerThread.remove(userId);
                    //关闭socket
                    socket.close();
                    System.out.println("用户" + userId + "退出系统");
                    //退出循环
                    return;
                } else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {
                    //转发给指定客户端,假如说客户不在线的话,可以保存到数据库,这样就可以实现离线留言
                    Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                    oos.writeObject(message);
                    oos.flush();

                } else {
                    System.out.println("其他类型暂时不处理");
                }

            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
            //读取客户端发送的User对象

        }
    }
}

1.4功能演示

客户端A

image-20231208233050369

客户端B

image-20231208233107188

服务端

image-20231208233117506

二、群发消息

将消息发送给所有的在线用户

2.1 分析

客户端A群发消息后,服务端会遍历线程集合,将消息发送给除了客户端A以外的所有客户端,完成群发功能

2.2 客户端

2.2.1 MessageClientService类

/**
 * 群发消息
 * @param userId 发送消息的用户id
 * @param content 需要发送的内容
 */
public void sendMessageToOnlineUser(String userId, String content) {
    Message message = new Message();
    message.setContent(content);
    message.setSender(userId);
    message.setSendTime(new Date().toString());
    message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());//普通消息
    System.out.println("用户"+userId+"群发消息说:"+content);

    ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(userId);
    Socket socket = clientConnectServerThread.getSocket();

    //输出消息
    try {
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(message);
        oos.flush();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.2.2 ClientConnectServerThread 线程类

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {
    //群发消息
    System.out.println("\n用户"+message.getGetter()+"收到来自用户"+message.getSender()+"的群发消息:"+message.getContent());

}

2.3 服务端

2.3.1 ServerConnectClientThread 线程类

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {
    //群发消息
    //遍历线程集合取出所有线程对应的socket发送消息即可
    HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();
    Iterator<String> iterator = hm.keySet().iterator();
    while (iterator.hasNext()) {
        //取出在线人的id
        String onlineId = iterator.next();
        if (!onlineId.equals(message.getSender())) {
            ObjectOutputStream oos = new ObjectOutputStream(
                    hm.get(onlineId).getSocket().getOutputStream()
            );
            oos.writeObject(message);
            oos.flush();
        }

    }

}

2.4 测试

客户端A

客户端B

image-20231209001512979

服务端

image-20231209001526468

三、发送文件

3.0 消息类扩展

@Data
public class Message implements Serializable {
    private static final long serialVersionUID = -3567747187962510012L;

    /**
     * 消息类型:发送文件、纯文本、视频聊天....
     */
    private String mesType;

    /*
     *发送者
     */
    private String sender;

    /**
     * 接收者
     */
    private String getter;

    /**
     * 消息内容
     */
    private String content;

    /**
     * 发送时间
     */
    private String sendTime;

    /**
     * 扩展好文件香菇那的成员变量
     */
    //字节数组存储文件字节
    private byte[] fileBytes;

    //文件大小的长度初始化为0
    private int fileLen = 0;

    //文件的目的地是哪个位置
    private String dest;

    //传输的是哪个文件(原文件路径)
    private String src;


}

3.1 分析

image-20231209003348777

3.2 客户端

3.2.1 FileClientService 文件传输类

向服务器发送文件

/**
 * 该类完成文件的传输
 */
public class FileClientService {

    public void sendFileToOne(String src, String dest, String sender, String getter) {
        //读取src文件
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_FILE_MES.getCode());
        message.setSender(sender);
        message.setGetter(getter);
        message.setSrc(src);
        message.setDest(dest);
        //需要将文件从客户端读取
        FileInputStream fileInputStream = null;
        byte[] fileBytes = new byte[(int) new File(src).length()];
        // 二进制流

        try {
            //读取文件
            fileInputStream = new FileInputStream(src);
            //将src文件读入到程序的字节数组中
            fileInputStream.read(fileBytes);

            //将文件对应的字节数粗设置到message
            message.setFileBytes(fileBytes);


        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //提示信息
        System.out.println("用户" + sender + "向用户" + getter + "发送文件" + src + "并存储到对方电脑目录" + dest);

        //向服务端发送Message
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManagerClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("发送文件完毕");
    }

}

3.2.2 ClientConnectServerThread 线程类接收文件

 else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())) {
    System.out.println("用户" + message.getGetter() + "收到用户" + message.getSender() + "发送的文件" + message.getSrc() + "并存储到我方电脑目录" + message.getDest());
    FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
    fileOutputStream.write(message.getFileBytes());
    fileOutputStream.flush();
    fileOutputStream.close();
    System.out.println("保存文件成功");
}

3.3 服务端

3.3.1 ServerConnectClientThread 线程类

服务端起到一个转发的作用而已

else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())){
                    System.out.println("用户" + message.getSender() + "向用户" + message.getGetter() + "发送文件" + message.getSrc() + "并存储到对方电脑目录" + message.getDest());
                    //发送文件
                    Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                    oos.writeObject(message);
                    oos.flush();
                }

3.4 测试结果

客户端A

image-20231209205819824

客户端B

这个地方的用户名错了,就不截取第二次了

image-20231209205836682

服务端

image-20231209205852008

四、服务端推送新闻

4.1 分析

服务端推送新闻本质就是群发消息

在服务器启动一条独立的线程,专门负责发送推送新闻

image-20231209213009521

4.2 客户端

4.2.1 ClientConnectServerThread 线程类

这个方法我们之前使用过

else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {
    //群发消息
    System.out.println("\n用户收到来自用户" + message.getSender() + "的群发消息:" + message.getContent());

}

4.3 服务端

4.3.1 SendNewsAllService推送消息

/**
 * 发送新闻
 */
public class SendNewsAllService implements Runnable {


    @Override
    public void run() {
        //多次推送新闻,使用while循环
        while (true) {
            System.out.println("请输入服务器要推送的信息/消息【输入exit表示退出】");
            String content = Utility.readString(500);
            if ("exit".equals(content)) {
                break;
            }
            //构建消息类型
            Message message = new Message();
            message.setSender("服务器");
            message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());
            message.setContent(content);
            message.setSendTime(new Date().toString());
            System.out.println("服务器推送消息给所有人 说:" + content);

            //遍历当前所有的通信线程得到socket
            HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();
            Iterator<String> iterator = hm.keySet().iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                ServerConnectClientThread serverConnectClientThread = hm.get(next);
                try {
                    //给每个用户发送消息
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    objectOutputStream.writeObject(message);
                    objectOutputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

4.3.2 QQServer启动线程

/**
 * 这是服务器,在监听9999,等待客户端的连接,并保持通信
 */
@Data
public class QQServer {

    //创建一个集合存放多个用户,如果是此用户登录,便认为是合法的
    //也可以使用ConcurrentHashMap,可以在并发的环境下处理(没有线程安全问题)
    //HashMap是没有处理线程安全的,因此在多线程情况下是不安全的
    private static HashMap<String, User> validUser = new HashMap<>();

    private ServerSocket serverSocket = null;

    /**
     * 进行类加载的时候会执行下面这个代码
     */
    static {
        validUser.put("100", new User("100", "123456"));
        validUser.put("200", new User("200", "123456"));
        validUser.put("300", new User("300", "123456"));
        validUser.put("至尊宝", new User("至尊宝", "123456"));
        validUser.put("紫霞仙子", new User("紫霞仙子", "123456"));
        validUser.put("菩提老祖", new User("菩提老祖", "123456"));
    }

    /**
     * 这是一个循环监听的过程
     * 并不是客户端A发送完信息服务器接收到后此服务器就关闭,而是一直监听,因为还有可能其他客户端发送过来信息
     */
    public QQServer() {
        System.out.println("服务端在9999端口监听....");
        //启动推送新闻的线程
        new Thread(new SendNewsAllService()).start();

        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try {
            this.serverSocket = new ServerSocket(9999);

            //监听是一直进行,当和某个客户端连接后,会继续监听,因此使用while循环
            while (true) {
                //没有客户端连接9999端口时,程序会堵塞,等待连接
                Socket socket = serverSocket.accept();

                ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
                //读取客户端发送的User对象
                User user = (User) ois.readObject();

                //创建Message对象,准备恢复客户端
                Message message = new Message();
                oos = new ObjectOutputStream(socket.getOutputStream());
                //验证用户是否合法
                User userValid = validUser.get(user.getUserId());
                if (userValid != null && userValid.getUserId().equals(user.getUserId()) && userValid.getPasswd().equals(user.getPasswd())) {
                    //合法用户
                    message.setMesType(MessageType.find(1));
                    //给客户端进行回复
//                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
                    oos.flush();

                    //创建一个线程,和客户端保持通信。
                    //该线程需要持有Socket对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(user.getUserId(), socket);
                    serverConnectClientThread.start();

                    //把该线程对象放入到一个集合中
                    ManagerServerConnectServerThread.addClientThread(user.getUserId(), serverConnectClientThread);

                } else {
                    //登录失败
                    message.setMesType(MessageType.find(2));
                    oos.writeObject(message);
                    oos.flush();

                    socket.close();
                }
            }


        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
//          如果服务端退出了while循环,说明服务器端不再监听了,因此需要关闭资源
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

4.4 测试

服务端

image-20231209223129728

客户端

image-20231209223147358

五、离线消息

客户端A给离线客户端B发送消息

我们可以在服务端创建一个集合,集合(HashMap就行)存放离线Message

对于集合的Key接收者的id,value是一个ArrayList,此ArrayList存放Message,因为客户端A可以给离线用户客户端B发送多条消息

当客户端B登录之后,服务端会首先到此HashMap集合中读取看看有没有离线消息,如果有的话从服务端发送到客户端B即可

image-20231209225316565

六、代码总结

6.1 公共类代码

6.1.1 消息类

@Data
public class Message implements Serializable {
    private static final long serialVersionUID = -3567747187962510012L;

    /**
     * 消息类型:发送文件、纯文本、视频聊天....
     */
    private String mesType;

    /*
     *发送者
     */
    private String sender;

    /**
     * 接收者
     */
    private String getter;

    /**
     * 消息内容
     */
    private String content;

    /**
     * 发送时间
     */
    private String sendTime;

    /**
     * 扩展好文件香菇那的成员变量
     */
    //字节数组存储文件字节
    private byte[] fileBytes;

    //文件大小的长度初始化为0
    private int fileLen = 0;

    //文件的目的地是哪个位置
    private String dest;

    //传输的是哪个文件(原文件路径)
    private String src;


}

6.1.2 消息类型类

/**
 * 消息类型
 * 不同行亮的值表示不同的消息类型
 */
@Getter
public enum MessageType {
    /**
     * 登录成功
     */
    MESSAGE_LOGIN_SUCCEED("1"),
    /**
     * 登录失败
     */
    MESSAGE_LOGIN_FAIL("2"),
    /**
     * 普通信息对象
     */
    MESSAGE_COMM_MES("3"),

    /**
     * 获取在线用户
     * 要求服务器返回在线用户列表
     */
    MESSAGE_GET_ONLINE_FRIEND("4"),

    /**
     * 服务器返回在线用户列表
     */
    MESSAGE_RETTURN_ONLINE_FRIEND("5"),

    /**
     * 客户端请求退出
     */
    MESSAGE_CLIENT_EXIT("6"),
    /**
     * 群发消息
     */
    MESSAGE_TO_ALL_EXIT("7"),
    /**
     * 发送文件
     */
    MESSAGE_FILE_MES("8"),
    ;

    private final String code;


    MessageType(String code) {
        this.code = code;
    }


    public static String find(Integer code) {
        for (MessageType value : MessageType.values()) {
            if (code.toString().equals(value.getCode())) {
                return value.getCode();
            }
        }
        return null;
    }
}

6.1.3 客户类

/**
 * 客户信息
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 4300366482842276408L;
    private String userId; //用户id
    private String passwd; //用户密码
}

6.1.4 控制台读取类

public class Utility {

    private static Scanner scanner;

    static {
        scanner = new Scanner(System.in);
    }

    public Utility() {

    }

    public static char readMenuSelection() {
        while (true) {
            String str = readKeyBoard(1, false);
            char c = str.charAt(0);
            if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {
                return c;
            }

            System.out.print("选择错误,请重新输入:");
        }
    }

    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() {
        while (true) {
            String str = readKeyBoard(2, false);
            try {
                int n = Integer.parseInt(str);
                return n;
            } catch (NumberFormatException var3) {
                System.out.println("数字输入错误,请重新输入:");
            }
        }
    }

    public static int readInt(int defaultValue) {
        while (true) {
            String str = readKeyBoard(2, true);
            if (str.equals("")) {
                return defaultValue;
            }

            try {
                int n = Integer.parseInt(str);
                return n;
            } catch (NumberFormatException var4) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
    }

    private static String readKeyBoard(int limit, boolean blankReturn) {
        String line = "";

        while (scanner.hasNextLine()) {
            line = scanner.nextLine();
            if (line.length() == 0) {
                if (blankReturn) {
                    return line;
                }
            } else {
                if (line.length() >= 1 && line.length() <= limit) {
                    break;
                }
                System.out.println("输入长度(不大于" + limit + ")错误,请重新输入:");
            }
        }
        return line;
    }
    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }



    public static char readConfirmSelection(){
        while (true){
            String str=readKeyBoard(1,false).toUpperCase();
            char c=str.charAt(0);
            if(c=='Y'||c=='N'){
                return c;
            }
            System.out.print("选择错误,请重新输入:");
        }
    }

}

6.2 客户端代码

6.2.1 QQView 客户端页面

/**
 * 菜单界面
 */
public class QQView {

    /**
     * 控制是否显示菜单
     */
    private boolean loop = true;
    /**
     * 接收用户的键盘输入
     */
    private String key = "";

    /**
     * 完成用户登录验证和用户注册等功能
     */
    public UserClientService userClientService = new UserClientService();

    public MessageClientService messageClientService = new MessageClientService();

    private FileClientService fileClientService = new FileClientService();

    public static void main(String[] args) {
        QQView qqView = new QQView();
        qqView.mainMenu();
        System.out.println("退出客户端系统");
    }

    /**
     * 显示主菜单
     */
    private void mainMenu() {
        while (loop) {
            System.out.println("***********欢迎登录网络通信系统*************");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");
            System.out.print("请输入你的选择:");
            key = Utility.readString(1);

            //根据用户的输入来处理不同的逻辑
            switch (key) {
                case "1":
                    System.out.print("请输入用户号");
                    String userId = Utility.readString(50);
                    System.out.print("请输入密  码");
                    String password = Utility.readString(50);

                    //TODO 到服务端验证用户是否合法
                    if (userClientService.checkUser(userId,password)) {
                        //进入二级菜单
                        System.out.println(String.format("网络通信系统二级菜单(用户%s)", userId));
                        while (loop) {
                            System.out.println(String.format("\n========网络通信系统二级菜单(用户%s)===========", userId));
                            System.out.println("\t\t 1.显示在线用户列表");
                            System.out.println("\t\t 2.群发消息");
                            System.out.println("\t\t 3.私聊消息");
                            System.out.println("\t\t 4.发送文件");
                            System.out.println("\t\t 9.退出系统");

                            System.out.print("请输入你的选择:");
                            key = Utility.readString(1);
                            switch (key) {
                                case "1":
                                    //获取在线用户列表
                                    userClientService.onlineFriendList();
                                    break;
                                case "2":
                                    //群发消息
                                    System.out.print("请输入想说的话:");
                                    String content = Utility.readString(100);
                                    messageClientService.sendMessageToOnlineUser(userId,content);
                                    break;
                                case "3":
                                    //私发消息
                                    System.out.print("请输入想聊天的在线用户号:");
                                    //用户号最长为50
                                    String getterId = Utility.readString(50);
                                    System.out.print("请输入想说的话:");
                                    String contentToAll = Utility.readString(100);
                                    messageClientService.sendMessageToOne(contentToAll,userId,getterId);
                                    break;
                                case "4":
                                    System.out.println("正在发送文件....");
                                    System.out.print("请输入文件接收者:");
                                    String getter = Utility.readString(50);
                                    System.out.print("\n请输入想要发送文件的路径:");
                                    String src = Utility.readString(50);
                                    System.out.print("\n请输入想要将文件存储在对方哪里:");
                                    String dest = Utility.readString(50);
                                    fileClientService.sendFileToOne(src,dest,userId,getter);
                                    break;
                                case "9":
                                    loop = false;
                                    //调用方法,给服务器发送一个退出系统的Message
                                    System.out.println("退出系统");
                                    userClientService.logout();
                                    break;
                            }
                        }
                    }else {
                        System.out.println("登录服务器失败,用户名或密码存在问题");
                    }
                    break;
                case "9":
                    loop = false;
                    System.out.println("退出系统");
            }
        }
    }
}

6.2.2 ClientConnectServerThread线程类

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientConnectServerThread extends Thread {
    //该线程需要持有Socket属性
    private Socket socket;


    /**
     * 因为Thread需要在后台跟我们的服务器进行通信(保持一个联系),因此我们使用while循环来控制
     */
    @Override
    public void run() {
        while (true) {
            //一直读取从服务器端回收的消息
            System.out.println("客户端线程,等待读取从服务端发送的消息....");

            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
                //这就是一个堵塞式网络编程,效率是相对比较低的
                Message message = (Message) ois.readObject();

                //判断message的类型,然后做响应的业务处理
                if (message.getMesType().equals(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode())) {
                    //获取在线用户,取出在线列表信息并显示
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("当前在线用户列表如下");
                    for (int i = 0; i < onlineUsers.length; i++) {
                        System.out.println("用户:" + onlineUsers[i]);
                    }
                } else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {
                    //转发给指定客户端,假如说客户不在线的话,可以保存到数据库,这样就可以实现离线留言
                    System.out.println("\n用户" + message.getGetter() + "收到来自用户" + message.getSender() + "的消息:" + message.getContent());
                } else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {
                    //群发消息
                    System.out.println("\n用户收到来自用户" + message.getSender() + "的群发消息:" + message.getContent());

                } else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())) {
                    System.out.println("用户" + message.getGetter() + "收到用户" + message.getSender() + "发送的文件" + message.getSrc() + "并存储到我方电脑目录" + message.getDest());
                    FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
                    fileOutputStream.write(message.getFileBytes());
                    fileOutputStream.flush();
                    fileOutputStream.close();
                    System.out.println("保存文件成功");
                } else {
                    System.out.println("其他类型的message,暂时不处理");
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

6.2.3 ManagerClientConnectServerThread线程管理类

/**
 * 管理客户端连接到服务端线程的一个类
 */
public class ManagerClientConnectServerThread {
    //把多个线程放入一个HashMap中进行管理,key是用户id,value是客户端与服务端通信的线程
    private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

    //将某个线程加入到集合中
    public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    public static ClientConnectServerThread getClientConnectServerThread(String userId) {
        return hm.get(userId);
    }
}

6.2.4 MessageClientService发送消息类

/**
 * 该类提供和消息相关的服务方法
 */
public class MessageClientService {
    /**
     * @param content  内容
     * @param senderId 发送用户id
     * @param getterId 接收用户id
     */
    public void sendMessageToOne(String content, String senderId, String getterId) {
        //封装消息
        Message message = new Message();
        message.setContent(content);
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_COMM_MES.getCode());//普通消息
        System.out.println("用户"+senderId+"和用户"+getterId+"说:"+content);
        //获取senderId对应的socket
        ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(senderId);
        Socket socket = clientConnectServerThread.getSocket();

        //输出消息
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 群发消息
     * @param userId 发送消息的用户id
     * @param content 需要发送的内容
     */
    public void sendMessageToOnlineUser(String userId, String content) {
        Message message = new Message();
        message.setContent(content);
        message.setSender(userId);
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());//普通消息
        System.out.println("用户"+userId+"群发消息说:"+content);

        ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(userId);
        Socket socket = clientConnectServerThread.getSocket();

        //输出消息
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6.2.5 FileClientService发送文件类

/**
 * 该类完成文件的传输
 */
public class FileClientService {

    public void sendFileToOne(String src, String dest, String sender, String getter) {
        //读取src文件
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_FILE_MES.getCode());
        message.setSender(sender);
        message.setGetter(getter);
        message.setSrc(src);
        message.setDest(dest);
        //需要将文件从客户端读取
        FileInputStream fileInputStream = null;
        byte[] fileBytes = new byte[(int) new File(src).length()];
        // 二进制流

        try {
            //读取文件
            fileInputStream = new FileInputStream(src);
            //将src文件读入到程序的字节数组中
            fileInputStream.read(fileBytes);

            //将文件对应的字节数粗设置到message
            message.setFileBytes(fileBytes);


        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //提示信息
        System.out.println("用户" + sender + "向用户" + getter + "发送文件" + src + "并存储到对方电脑目录" + dest);

        //向服务端发送Message
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManagerClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("发送文件完毕");
    }

}

6.2.6 UserClientService 用户登录验证类

/**
 * 完成用户登录验证和用户注册等功能
 */
@Data
public class UserClientService {

    //其他地方也会使用user信息,所以将其作为一个属性
    private User user = new User();

    private Socket socket = null;

    /**
     *根据userId和pwd到服务器验证该用户是否合法
     */
    public boolean checkUser(String userId, String pwd) {
        //临时变量b,用户是否合法的标志
        boolean b = false;

        //TODO 创建User对象
        user.setUserId(userId);
        user.setPasswd(pwd);

        try {
            //TODO 连接到服务端,发送User对象
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
            //得到ObjectOutputStream对象流(序列化流,也是字节流中一种)
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(user);
            oos.flush();

            //TODO 读取从服务器回复的Message对象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message msg = (Message) ois.readObject();

            if (MessageType.find(1).equals(msg.getMesType())) {

                //登录成功
                //一旦登录成功,我们需要启动一个线程维护或者持有此socket,保持此线程可以跟我们服务器端一直进行通信
                //不启动线程的话此Socket不好维护。如果我们有数据发送或者接收,我们可以从这个线程里面进行拉取
                //为什么将Socket放入一个线程中管理?
                // 1.如果不创建这个线程的话,一个客户端会有多个socket,socket管理起来就比较麻烦
                // 2.需要socket不断的从数据通道中读写数据,所以也必须做成一个线程
                ClientConnectServerThread ccst = new ClientConnectServerThread(socket);
                //启动客户端的线程
                ccst.start();
                //为了后面客户端的扩展,我们将线程放入到集合中管理
                ManagerClientConnectServerThread.addClientConnectServerThread(userId, ccst);

                b = true;
            } else {
                //登录失败
                //我们是有Socket的,但是没有线程,即登录失败,不能启动和服务器通信的线程
                //关闭socket
                socket.close();
            }

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        return b;
    }

    /**
     * 向服务器端请求在线用户列表
     */
    public void onlineFriendList(){
        //发送一个message,并且消息的类型是MESSAGE_GET_ONLINE_FRIEND
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode());
        message.setSender(user.getUserId());
        //发送给服务器
        //得到当前线程的Socket对应的ObjectOutputStream
        //clientConnectServerThread线程一直在运行过程中,监听从服务器传输过来的消息
        ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());
        try {

            ObjectOutputStream oos = new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());
            oos.writeObject(message);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 编写方法退出客户端,并给服务端发送一个退出系统的Message对象
     */
    public void logout(){
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT.getCode());
        // 要退出这个用户
        message.setSender(user.getUserId());
        ClientConnectServerThread clientConnectServerThread = ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());
        try {

            ObjectOutputStream oos = new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());
            oos.writeObject(message);
            oos.flush();
           System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     */
}

6.3 服务端代码

6.3.1 后台启动

/**
 * 此类创建一个QQServer对象,启动后台的服务
 */
public class QQFrame {
    public static void main(String[] args) {
        //创建QQServer对象,会启动QQServer构造器
        QQServer qqServer = new QQServer();

    }
}

6.3.2 服务器

/**
 * 这是服务器,在监听9999,等待客户端的连接,并保持通信
 */
@Data
public class QQServer {

    //创建一个集合存放多个用户,如果是此用户登录,便认为是合法的
    //也可以使用ConcurrentHashMap,可以在并发的环境下处理(没有线程安全问题)
    //HashMap是没有处理线程安全的,因此在多线程情况下是不安全的
    private static HashMap<String, User> validUser = new HashMap<>();

    private ServerSocket serverSocket = null;

    /**
     * 进行类加载的时候会执行下面这个代码
     */
    static {
        validUser.put("100", new User("100", "123456"));
        validUser.put("200", new User("200", "123456"));
        validUser.put("300", new User("300", "123456"));
        validUser.put("至尊宝", new User("至尊宝", "123456"));
        validUser.put("紫霞仙子", new User("紫霞仙子", "123456"));
        validUser.put("菩提老祖", new User("菩提老祖", "123456"));
    }

    /**
     * 这是一个循环监听的过程
     * 并不是客户端A发送完信息服务器接收到后此服务器就关闭,而是一直监听,因为还有可能其他客户端发送过来信息
     */
    public QQServer() {
        System.out.println("服务端在9999端口监听....");
        //启动推送新闻的线程
        new Thread(new SendNewsAllService()).start();

        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try {
            this.serverSocket = new ServerSocket(9999);

            //监听是一直进行,当和某个客户端连接后,会继续监听,因此使用while循环
            while (true) {
                //没有客户端连接9999端口时,程序会堵塞,等待连接
                Socket socket = serverSocket.accept();

                ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
                //读取客户端发送的User对象
                User user = (User) ois.readObject();

                //创建Message对象,准备恢复客户端
                Message message = new Message();
                oos = new ObjectOutputStream(socket.getOutputStream());
                //验证用户是否合法
                User userValid = validUser.get(user.getUserId());
                if (userValid != null && userValid.getUserId().equals(user.getUserId()) && userValid.getPasswd().equals(user.getPasswd())) {
                    //合法用户
                    message.setMesType(MessageType.find(1));
                    //给客户端进行回复
//                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
                    oos.flush();

                    //创建一个线程,和客户端保持通信。
                    //该线程需要持有Socket对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(user.getUserId(), socket);
                    serverConnectClientThread.start();

                    //把该线程对象放入到一个集合中
                    ManagerServerConnectServerThread.addClientThread(user.getUserId(), serverConnectClientThread);

                } else {
                    //登录失败
                    message.setMesType(MessageType.find(2));
                    oos.writeObject(message);
                    oos.flush();

                    socket.close();
                }
            }


        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
//          如果服务端退出了while循环,说明服务器端不再监听了,因此需要关闭资源
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

6.3.3 ServerConnectClientThread线程类

/**
 * 该类对应的对象和某个客户端保持通信
 */
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerConnectClientThread extends Thread {

    /**
     * 可以区分此socket是和哪个用户进行关联的
     */
    private String userId;//连接到服务端的这个用户id

    private Socket socket;

    /**
     * 线程处于run状态,可以发送或者接收客户端的消息
     */
    @Override
    public void run() {
        //不断的从socket中读数据和写数据
        while (true) {
            System.out.println("服务端和客户端保持通信,读取数据.... userId:" + userId);
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                //读取数据
                Message message = (Message) ois.readObject();

                //根据Message的类型,判断客户端想要执行什么操作
                if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())) {
                    System.out.println("用户" + userId + "获取在线用户");
                    //拉取在线用户(客户端要拉取在线用户列表)
                    Socket socket = ManagerServerConnectServerThread.getClientThread(userId).getSocket();

                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    //构建Message发送给服务端
                    Message returnMessage = new Message();
                    returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());
                    returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());
                    //说明要发送给谁
                    returnMessage.setGetter(message.getSender());
                    //返回给客户端
                    oos.writeObject(returnMessage);
                    oos.flush();
                } else if (MessageType.MESSAGE_CLIENT_EXIT.getCode().equals(message.getMesType())) {
                    //说明客户端想要退出,服务端要将socket关闭并退出线程就可以了
                    //将客户端对应的线程从集合中删除
                    ManagerServerConnectServerThread.remove(userId);
                    //关闭socket
                    socket.close();
                    System.out.println("用户" + userId + "退出系统");
                    //退出循环
                    return;
                } else if (MessageType.MESSAGE_COMM_MES.getCode().equals(message.getMesType())) {
                    //转发给指定客户端,假如说客户不在线的话,可以保存到数据库,这样就可以实现离线留言
                    Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                    oos.writeObject(message);
                    oos.flush();

                } else if (MessageType.MESSAGE_TO_ALL_EXIT.getCode().equals(message.getMesType())) {
                    //群发消息
                    //遍历线程集合取出所有线程对应的socket发送消息即可
                    HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();
                    Iterator<String> iterator = hm.keySet().iterator();
                    while (iterator.hasNext()) {
                        //取出在线人的id
                        String onlineId = iterator.next();
                        if (!onlineId.equals(message.getSender())) {
                            ObjectOutputStream oos = new ObjectOutputStream(
                                    hm.get(onlineId).getSocket().getOutputStream()
                            );
                            oos.writeObject(message);
                            oos.flush();
                        }

                    }

                }else if (MessageType.MESSAGE_FILE_MES.getCode().equals(message.getMesType())){
                    System.out.println("用户" + message.getSender() + "向用户" + message.getGetter() + "发送文件" + message.getSrc() + "并存储到对方电脑目录" + message.getDest());
                    //发送文件
                    Socket socket = ManagerServerConnectServerThread.getClientThread(message.getGetter()).getSocket();
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                    oos.writeObject(message);
                    oos.flush();
                }else {
                    System.out.println("其他类型暂时不处理");
                }

            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            //如果服务器端没有发送消息过来,这个地方会堵塞,此线程会一直等待
            //读取客户端发送的User对象

        }
    }
}

6.3.4 ManagerServerConnectServerThread管理线程类

/**
 * 该类用于管理和客户端通信的线程
 */
@Data
public class ManagerServerConnectServerThread {
    private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();


    public static HashMap<String, ServerConnectClientThread> getHm() {
        return hm;
    }

    /**
     * 添加线程对象到hm集合
     */
    public static void addClientThread(String userId, ServerConnectClientThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    /**
     * 从集合中获取对应线程对象
     */
    public static ServerConnectClientThread getClientThread(String userId) {
        return hm.get(userId);
    }

    /**
     * 获取在线用户
     */
    public static String getOnlineUser() {
        //集合遍历,遍历hashMap的key
        Iterator<String> iterator = hm.keySet().iterator();
        String onlineUserList = "";

        while (iterator.hasNext()) {
            onlineUserList += iterator.next().toString() + " ";
        }
        return onlineUserList;
    }

    /**
     * 从集合中删除掉某个线程对象
     */
    public static void remove(String userId) {
       hm.remove(userId);
    }

}

6.3.5 SendNewsAllService 推送新闻类

/**
 * 发送新闻
 */
public class SendNewsAllService implements Runnable {


    @Override
    public void run() {
        //多次推送新闻,使用while循环
        while (true) {
            System.out.println("请输入服务器要推送的信息/消息【输入exit表示退出】");
            String content = Utility.readString(500);
            if ("exit".equals(content)) {
                break;
            }
            //构建消息类型
            Message message = new Message();
            message.setSender("服务器");
            message.setMesType(MessageType.MESSAGE_TO_ALL_EXIT.getCode());
            message.setContent(content);
            message.setSendTime(new Date().toString());
            System.out.println("服务器推送消息给所有人 说:" + content);

            //遍历当前所有的通信线程得到socket
            HashMap<String, ServerConnectClientThread> hm = ManagerServerConnectServerThread.getHm();
            Iterator<String> iterator = hm.keySet().iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                ServerConnectClientThread serverConnectClientThread = hm.get(next);
                try {
                    //给每个用户发送消息
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    objectOutputStream.writeObject(message);
                    objectOutputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298460.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

★102. 二叉树的层序遍历

102. 二叉树的层序遍历 很巧妙的&#xff0c;又学习了一种层次遍历的方法&#xff0c;就是说根据当前的队列的长度去遍历&#xff0c;遍历的当前队列的长度就是该层次的节点个数。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* Tr…

基于JavaWeb+SSM+Vue童装商城小程序系统的设计和实现

基于JavaWebSSMVue童装商城小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 目 录 摘 要 III Abstract 1 1 系统概述 2 1.1 概述 3 1.2课题意义 4 1.3 主要内容 5…

GoldWave注册机 最新中文汉化破解版-安装使用教程

GoldWave是一个功能强大的数字音乐编辑器&#xff0c;是一个集声音编辑、播放、录制和转换的音频工具。它还可以对音频内容进行转换格式等处理。它体积小巧&#xff0c;功能却无比强大&#xff0c;支持许多格式的音频文件&#xff0c;包括WAV、OGG、VOC、 IFF、AIFF、 AIFC、AU…

Temu卖家如何获取流量?Temu新手卖家流量来源哪里?——站斧浏览器

流量对于每个平台来说都是很重要的&#xff0c;那么Temu卖家如何获取流量&#xff1f;流量来源哪里&#xff1f; Temu卖家如何获取流量&#xff1f; 1、优化产品标题和描述&#xff1a;在Temu平台上&#xff0c;买家通常通过搜索关键词来寻找他们感兴趣的产品。因此&#xff…

curl 18 HTTP/2 stream

cd /Users/haijunyan/Desktop/CustomKit/KeepThreadAlive/KeepThreadAlive //Podfile所在文件夹 git config --global https.postBuffer 10485760000 git config --global http.postBuffer 10485760000 pod install https://blog.csdn.net/weixin_41872403/article/details/86…

(七)Python 命令模式

文章目录 1 命令模式简介2 命令模式的特点和目的2.1 命令模式通常使用以下术语:2.1.1 命令模式的UML类图 2.2 命令模式的主要目的如下2.3 命令模式可用于以下各种情景: 3 命令模式python代码示例4 命令模式的优点和缺点4.1 优点4.2 缺点 1 命令模式简介 正如我们在上一章中所看…

【Com通信】Com模块详细介绍

目录 前言 1. Com模块功能介绍 2.关键概念理解 3.功能详细设计 3.1 Introduction 3.2 General Functionality 3.2.1 AUTOSAR COM basis 3.2.2 Signal Values 3.2.3 Endianness Conversion and Sign Extension 3.2.4 Filtering 3.2.5 Signal Gateway 3.3 Normal Ope…

Jenkins入门手册

目录 第一章、Jenkins是什么&#xff1f; 1、Jenkins的由来 1.1 、Jenkins 的目标 第二章、Jenkins安装与配置 2、Jenkins 安装 3、Jenkins 配置 3.1 系统管理 3.1.1 、提示信息 3.1.1.1 、Utf-8 编码 3.1.1.2 、新的版本 3.1.1.3、安全设置 3.1.2、系统设置 3…

Go语言深度优先搜索(DFS)

Go 语言代码段实现了深度优先搜索&#xff08;DFS&#xff09;算法&#xff0c;该算法用于遍历图数据结构。以下是代码的主要要点和执行流程的总结&#xff1a; 深度优先搜索函数 (DFS): 接收图的邻接表 (map[int][]int)、访问记录 (map[int]bool) 和当前节点作为参数。将当前…

go sort.Search()

函数 func Search(n int, f func(int) bool) int {} 函数作用 通过二分法查找&#xff0c;找到已经排序好的数组[0,n)中第一个使f为true的索引&#xff0c;如果没有找到返回n 为什么要用二分查找&#xff1f; 因为二分查找相比普通依次遍历而言&#xff0c;速度能有巨幅提升…

延时消息+递归导致重复消费爆炸问题

背景 公司所用消息队列为RoucketMQ&#xff0c;版本为4.x。最近公司有业务需要&#xff0c;将某个处理延迟到第二天的白天再进行。由于4.x版本队列&#xff0c;默认延时时间是按等级来延时的&#xff0c;默认有18个等级&#xff0c;如下图&#xff1a; 默认的延时等级&#xff…

我对迁移学习的一点理解(系列2)

文章目录 我对迁移学习的一点理解 我对迁移学习的一点理解 源域和目标域是相对的概念&#xff0c;指的是在迁移学习任务中涉及到的两个不同的数据集或领域。 源域&#xff08;Source Domain&#xff09;通常指的是已经进行过训练和学习的数据集&#xff0c;它被用来提取特征、…

盒马补贴量价-2021KDD

概述&#xff1a; 电商商品定价三个关键问题&#xff1a; 在只有观测数据的时候&#xff0c;怎么构建价格弹性&#xff0c;现在来看这就是一个反事实推断的问题&#xff0c;不仅是如何做的问题&#xff0c;还有如何评估的问题。长周期的规划决策问题怎么建模 & 求解&#…

基于状态机的单片机编程

前言 在设计锂离子电池充电器时&#xff0c;对于以前的根据系统状态进行判断&#xff0c;置标志位的方法&#xff0c;会显得程序臃肿&#xff0c;且架构混乱&#xff0c;变量交错复杂&#xff0c;移植困难。 所以结合实际项目&#xff0c;给出了一种基于状态机的编程方法。 …

线程上下文切换

线程上下文切换 巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间&#xff0c;然后把当前任务的状态保存下来&#xff0c;在加载下一任务的状态后&#xff0c;继续服务下一任务&#xff0c;任务的状态保存及再加载, 这段过程就叫做上下文切换。时间片轮转的方式…

对String类的操作 (超细节+演示)

[本节目标] 1.认识String类 2.了解String类的基本用法 3.熟练掌握String类的常见操作 4.认识字符串常量池 5.认识StringBuffer和StringBuilder 1.String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&…

差异计算基础知识 - 了解期末业务操作、WIP 和差异

原文地址&#xff1a;Basics of variance calculation-Understanding Period End activities, WIP and Variances | SAP Blogs 大家好&#xff0c; 这是我在成本核算方面的第六份文件&#xff0c;旨在解释期末的差异计算和相关活动。 我将引导您完成期末活动和差异计算。在本文…

Spring Cloud Gateway 网关的基础使用

1. 什么是网关&#xff1f;网关有什么用&#xff1f; 在微服务架构中&#xff0c;网关就是一个提供统一访问地址的组件&#xff0c;它解决了内部微服务与外部的交互问题。网关主要负责流量的路由和转发&#xff0c;将外部请求引到对应的微服务实例上。同时提供身份认证、授权、…

前后端数据传输格式(下)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上篇主要复习了HTTP以及…

解释AI决策,这10个强大的 Python 库记得收藏!

本文整理了10个常用于可解释AI的Python库&#xff0c;方便我们更好的理解AI模型的决策。 什么是XAI&#xff1f; XAI&#xff08;Explainable AI&#xff09;的目标是为模型的行为和决策提供合理的解释&#xff0c;这有助于增加信任、提供问责制和模型决策的透明度。XAI 不仅…