Netty Review - 从BIO到NIO的进化推演

文章目录

  • BIO
    • DEMO 1
    • DEMO 2
    • 小结论
    • 单线程BIO的缺陷
    • BIO如何处理并发
    • 多线程BIO服务器的弊端
  • NIO
    • NIO要解决的问题
    • 模拟NIO
      • 方案一: (等待连接时和等待数据时不阻塞)
      • 方案二(缓存Socket,轮询数据是否准备好)
      • 方案二存在的问题
    • NIO是如何解决这些问题的
    • 使用select/poll/epoll和直接在应用层做轮询的区别
      • select底层逻辑
      • poll的底层逻辑
      • epoll的底层逻辑

在这里插入图片描述


BIO

要讲明白BIO和NIO,首先我们应该自己实现一个简易的服务器,单线程即可。

DEMO 1

package com.artisan.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class Server {
    public static void main(String[] args) {

        // BIO 面向字节
        byte[] bytes = new byte[1024];

        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(1234);
            System.out.println("服务端已开启端口");

            while (true) {
                System.out.println();

                System.out.println("服务端等待连接......");

                Socket socket = serverSocket.accept();
                System.out.println("服务端已经收到连接请求");

                System.out.println("服务端等待数据.....");

                socket.getInputStream().read(bytes);
                System.out.println("服务端等已收到数据.....");

                String msg = new String(bytes);

                System.out.println("接收到了数据: " + msg);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
    

创建了一个服务端类,在类中实现实例化了一个SocketServer并绑定了1234端口。之后调用accept方法来接收连接请求,并且调用read方法来接收客户端发送的数据。最后将接收到的数据打印。

package com.artisan.bio;

import java.io.IOException;
import java.net.Socket; 

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class Client {

    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("127.0.0.1",1234);

        socket.getOutputStream().write("数据数据数据".getBytes());

        socket.close();

    }
}
    

客户端,首先实例化Socket对象,并且绑定ip为127.0.0.1(本机),端口号为1234,调用write方法向服务器发送数据

运行测试会发现

在服务器启动后,客户端还没有连接服务器时,服务器由于调用了accept方法,将一直阻塞,直到有客户端请求连接服务器


DEMO 2

客户端的逻辑主要是:建立Socket –> 连接服务器 –> 发送数据,我们的数据是在连接服务器之后就立即发送的,现在我们来对客户端进行一次扩展,当我们连接服务器后,不立即发送数据,而是等待控制台手动输入数据后,再发送给服务端

客户端代码如下

 try {
         Socket socket = new Socket("127.0.0.1",1234);
           String message = null;
           Scanner sc = new Scanner(System.in);
           message = sc.next();
           socket.getOutputStream().write(message.getBytes());
           socket.close();
           sc.close();
   } catch (IOException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
   }

小结论

在这里插入图片描述

从上面的运行结果中我们可以看到,服务器端在启动后:

1)首先需要等待客户端的连接请求(第一次阻塞)

2)如果没有客户端连接,服务端将一直阻塞等待;

3)然后当客户端连接后,服务器会等待客户端发送数据(第二次阻塞)

4)如果客户端没有发送数据,那么服务端将会一直阻塞等待客户端发送数据。


服务端从启动到收到客户端数据的这个过程,将会有两次阻塞的过程:

  • 1)第一次在等待连接时阻塞;
  • 2)第二次在等待数据时阻塞。

BIO会产生两次阻塞,这就是BIO的非常重要的一个特点


单线程BIO的缺陷

当我们的服务器接收到一个连接后,并且没有接收到客户端发送的数据时,是会阻塞在read()方法中的,那么此时如果再来一个客户端的请求,服务端是无法进行响应的。换言之:在不考虑多线程的情况下,BIO是无法处理多个客户端请求的


BIO如何处理并发

单线程版的BIO并不能处理多个客户端的请求,那么如何能使BIO处理多个客户端请求呢?

我们只需要在每一个连接请求到来时,创建一个线程去执行这个连接请求,就可以在BIO中处理多个客户端请求了,这也就是为什么BIO的其中一条概念是服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。

【多线程BIO版本简易实现】

 package com.artisan.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ServerMultThread {

    public static void main(String[] args) {
        byte[] buffer = new byte[1024];
        try {
            ServerSocket serverSocket = new ServerSocket(1234);
            System.out.println("服务器已启动并监听8080端口");
            while (true) {
                System.out.println();
                System.out.println("服务器正在等待连接...");
                Socket socket = serverSocket.accept();
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + " 服务器已接收到连接请求...");
                    System.out.println();
                    System.out.println(Thread.currentThread().getName() + "服务器正在等待数据...");
                    try {
                        socket.getInputStream().read(buffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "服务器已经接收到数据");
                    System.out.println();
                    String content = new String(buffer);
                    System.out.println(Thread.currentThread().getName() + "接收到的数据:" + content);
                }).start();

            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

多启动几个客户端,测试结果如下

在这里插入图片描述


多线程BIO服务器的弊端

多线程BIO服务器虽然解决了单线程BIO无法处理并发的弱点,但是也带来一个问题:如果有大量的请求连接到我们的服务器上,但是却不发送消息,那么我们的服务器也会为这些不发送消息的请求创建一个单独的线程,那么如果连接数少还好,连接数一多就会对服务端造成极大的压力

所以:如果这种不活跃的线程比较多,我们应该采取单线程的一个解决方案,但是单线程又无法处理并发,这就陷入了一种很矛盾的状态,于是就有了NIO


NIO

NIO要解决的问题

我们先来看看单线程模式下BIO服务器的代码,其实NIO需要解决的最根本的问题就是存在于BIO中的两个阻塞,分别是等待连接时的阻塞和等待数据时的阻塞

package com.artisan.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class Server {
    public static void main(String[] args) {

        // BIO 面向字节
        byte[] bytes = new byte[1024];

        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(1234);
            System.out.println("服务端已开启端口");

            while (true) {
                System.out.println();

                System.out.println("服务端等待连接......");

                Socket socket = serverSocket.accept();
                System.out.println("服务端已经收到连接请求");

                System.out.println("服务端等待数据.....");

                socket.getInputStream().read(bytes);
                System.out.println("服务端等已收到数据.....");

                String msg = new String(bytes);

                System.out.println("接收到了数据: " + msg);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
    

如果单线程服务器在等待数据时阻塞,那么第二个连接请求到来时,服务器是无法响应的。如果是多线程服务器,那么又会有为大量空闲请求产生新线程从而造成线程占用系统资源,线程浪费的情况。

那么我们的问题就转移到,如何让单线程服务器在等待客户端数据到来时,依旧可以接收新的客户端连接请求


模拟NIO

如果要解决上文中提到的单线程服务器接收数据时阻塞,而无法接收新请求的问题,那么其实可以让服务器在等待数据时不进入阻塞状态,问题不就迎刃而解了吗?

方案一: (等待连接时和等待数据时不阻塞)

package com.artisan.bio;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class MyServer {

    public static void main(String[] args) throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            //Java为非阻塞设置的类
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(1234));

            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel == null) {
                    //表示没人连接
                    System.out.println(Thread.currentThread().getName() + " 正在等待客户端请求连接...");
                    Thread.sleep(5000);
                } else {
                    System.out.println(Thread.currentThread().getName() + "当前接收到客户端请求连接...");
                }
                if (socketChannel != null) {
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);
                    byteBuffer.flip();//切换模式  写-->读
                    int effective = socketChannel.read(byteBuffer);
                    if (effective != 0) {
                        String content = Charset.forName("utf-8").decode(byteBuffer).toString();
                        System.out.println(content);
                    } else {
                        System.out.println(Thread.currentThread().getName() + "当前未收到客户端消息");
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端测试代码

package com.artisan.bio;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ClientWithInput {
    private static Socket socket;
    private static Scanner sc;

    public static void main(String[] args) throws IOException {

        try {
            while (true) {
                socket = new Socket("127.0.0.1", 1234);
                String message = null;
                sc = new Scanner(System.in);
                message = sc.next();
                socket.getOutputStream().write(message.getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
            sc.close();
        }
    }
}
    

在这里插入图片描述

在这里插入图片描述

在这种解决方案下,虽然在接收客户端消息时不会阻塞,但是又开始重新接收服务器请求,用户根本来不及输入消息,服务器就转向接收别的客户端请求了,换言之,服务器弄丢了当前客户端的请求


方案二(缓存Socket,轮询数据是否准备好)

package com.artisan.bio;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public class MyServer {

    public static void main(String[] args) throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        List<SocketChannel> socketList = new ArrayList<SocketChannel>();
        try {
            //Java为非阻塞设置的类
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(1234));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel == null) {
                    //表示没人连接
                    System.out.println(Thread.currentThread().getName() + "正在等待客户端请求连接...");
                    Thread.sleep(5000);
                } else {
                    System.out.println(socketChannel.getRemoteAddress() + "当前接收到客户端请求连接...");
                    socketList.add(socketChannel);
                }
                for (SocketChannel socket : socketList) {
                    socket.configureBlocking(false);
                    int effective = socket.read(byteBuffer);
                    if (effective != 0) {
                        byteBuffer.flip();//切换模式  写-->读
                        String content = Charset.forName("UTF-8").decode(byteBuffer).toString();
                        System.out.println(socket.getRemoteAddress() + "接收到消息:" + content);
                        System.out.println();
                        byteBuffer.clear();
                    } else {
                        // System.out.println(socket.getRemoteAddress() + "当前未收到客户端消息");
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端我们使用如下代码去模拟

package com.artisan.bio;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ClientWithInput {
    private static Socket socket;
    private static Scanner sc;

    public static void main(String[] args) throws IOException {

        try {
            while (true) {
                socket = new Socket("127.0.0.1", 1234);
                String message = null;
                sc = new Scanner(System.in);
                message = sc.next();
                socket.getOutputStream().write(message.getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
            sc.close();
        }
    }
}
    

在这里插入图片描述

在这里插入图片描述

我们可以发现
1. 消息并没有丢失
2. server端并没有开启多线程来处理消息,均是在main线程

在解决方案一中,我们采用了非阻塞方式,但是发现一旦非阻塞,等待客户端发送消息时就不会再阻塞了,而是直接重新去获取新客户端的连接请求,这就会造成客户端连接丢失。

而在解决方案二中,我们将连接存储在一个list集合中,每次等待客户端消息时都去轮询,看看消息是否准备好,如果准备好则直接打印消息。

可以看到,从头到尾我们一直没有开启第二个线程,而是一直采用单线程来处理多个客户端的连接,这样的一个模式可以很完美地解决BIO在单线程模式下无法处理多客户端请求的问题,并且解决了非阻塞状态下连接丢失的问题。


方案二存在的问题

从刚才的运行结果中其实可以看出,消息没有丢失,程序也没有阻塞。

但是,在接收消息的方式上可能有些许不妥,我们采用了一个轮询的方式来接收消息,每次都轮询所有的连接,看消息是否准备好,测试用例中只是三个连接,所以看不出什么问题来,但是我们假设有1000万连接,甚至更多,采用这种轮询的方式效率是极低的

另外,1000万连接中,我们可能只会有100万会有消息,剩下的900万并不会发送任何消息,那么这些连接程序依旧要每次都去轮询,这显然是不合适的


NIO是如何解决这些问题的

在真实NIO中,并不会在Java层上来进行一个轮询,而是将轮询的这个步骤交给我们的操作系统来进行,他将轮询的那部分代码改为操作系统级别的系统调用(select函数,在linux环境中为epoll),在操作系统级别上调用select函数,主动地去感知有数据的socket


使用select/poll/epoll和直接在应用层做轮询的区别

NIO使用了操作系统底层的轮询系统调用 select/epoll(windows:select,linux:epoll),那么为什么不直接实现而要去调用系统来做轮询呢?


select底层逻辑

假设有A、B、C、D、E五个连接同时连接服务器,那么根据我们上文中的设计,程序将会遍历这五个连接,轮询每个连接,获取各自数据准备情况,那么和我们自己写的程序有什么区别呢?

首先:我们写的Java程序其本质在轮询每个Socket的时候也需要去调用系统函数,那么轮询一次调用一次,会造成不必要的上下文切换开销

而:Select会将五个请求从用户态空间全量复制一份到内核态空间,在内核态空间来判断每个请求是否准备好数据,完全避免频繁的上下文切换。所以效率是比我们直接在应用层写轮询要高的。

如果:select没有查询到到有数据的请求,那么将会一直阻塞(是的,select是一个阻塞函数)。如果有一个或者多个请求已经准备好数据了,那么select将会先将有数据的文件描述符置位,然后select返回。返回后通过遍历查看哪个请求有数据。


select的缺点

  • 1)底层存储依赖bitmap,处理的请求是有上限的,为1024;
  • 2)文件描述符是会置位的,所以如果当被置位的文件描述符需要重新使用时,是需要重新赋空值的;
  • 3)fd(文件描述符)从用户态拷贝到内核态仍然有一笔开销;
  • 4)select返回后还要再次遍历,来获知是哪一个请求有数据。

poll的底层逻辑

poll的工作原理和select很像,先来看一段poll内部使用的一个结构体

struct pollfd{
    int fd;
    short events;
    short revents;
}
  • poll同样会将所有的请求拷贝到内核态,和select一样,poll同样是一个阻塞函数,当一个或多个请求有数据的时候,也同样会进行置位,但是它置位的是结构体pollfd中的events或者revents置位,而不是对fd本身进行置位,所以在下一次使用的时候不需要再进行重新赋空值的操作。

  • poll内部存储不依赖bitmap,而是使用pollfd数组的这样一个数据结构,数组的大小肯定是大于1024的。

解决了select 1、2两点的缺点。


epoll的底层逻辑

epoll是最新的一种多路IO复用的函数。这里只说说它的特点。

  • epoll和上述两个函数最大的不同是,它的fd是共享在用户态和内核态之间的,所以可以不必进行从用户态到内核态的一个拷贝,这样可以节约系统资源。

  • 另外,在select和poll中,如果某个请求的数据已经准备好,它们会将所有的请求都返回,供程序去遍历查看哪个请求存在数据,但是epoll只会返回存在数据的请求,这是因为epoll在发现某个请求存在数据时,首先会进行一个重排操作,将所有有数据的fd放到最前面的位置,然后返回(返回值为存在数据请求的个数N),那么我们的上层程序就可以不必将所有请求都轮询,而是直接遍历epoll返回的前N个请求,这些请求都是有数据的请求。

高性能网络编程 - select、 poll 、epoll 、libevent

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

225.用队列实现栈(LeetCode)

思路 思路&#xff1a;用两个队列实现栈后进先出的特性 &#xff0c;两个队列为空时&#xff0c;先将数据都导向其中一个队列。 当要模拟出栈时&#xff0c;将前面的元素都导入另一个空队列&#xff0c;再将最后一个元素移出队列 实现 实现&#xff1a; 因为C语言没有库可以…

网络运维Day17

文章目录 什么是数据库MySQL介绍实验环境准备构建MySQL服务连接数据库修改root密码 数据库基础常用的SQL命令分类SQL命令使用规则MySQL基本操作创建库创建表查看表结构 记录管理命令 数据类型数值类型 数据类型日期时间类型时间函数案例枚举类型 约束条件案例修改表结构添加新字…

C++实现ransac

目录 一、ransac算法原理 1.1、算法概念 1.2、图解 二、c实现ransac 2.1、设置随机样本和离群点 2.2、随机抽取样本 2.3、内点计算 2.4、更新参数 2.2、完整代码 一、ransac算法原理 1.1、算法概念 随机抽样一致性 (RANSAC) 是一种迭代方法&#xff0c;用于根据一组包…

【Java 进阶篇】JQuery DOM操作:CRUD操作的前端魔法

在前端开发的舞台上&#xff0c;CRUD&#xff08;Create, Read, Update, Delete&#xff09;操作是一种极为重要的技能&#xff0c;它涉及对页面元素的增删改查。而JQuery&#xff0c;这位前端开发的魔法师&#xff0c;为我们提供了便捷而强大的方法&#xff0c;使得CRUD操作变…

IP地址如何实现定位功能?

网络犯罪、保护网络安全的重要手段。近日&#xff0c;一则新闻引起了广大网友的关注&#xff1a;IP也能实现定位功能&#xff0c;这是如何做到的呢&#xff1f;本文将对此进行深入解析。 首先&#xff0c;我们需要了解什么是IP地址定位。IP地址定位是通过IP地址确定网络用户所在…

【Windows 开发环境配置——NVIDIA 篇】CUDA、cuDNN、TensorRT 三件套安装

CUDA 从CUDA Toolkit Archive下载相应版本的离线安装包&#xff0c;这里以11.7为例。 打开安装包&#xff0c;在安装选项选择自定义模式&#xff0c;点击下一步。 在自定义安装选项中&#xff0c;仅选择CUDA组件&#xff08;其中Nsight相关组件用于代码调试与性能分析&#xff…

Linux--线程概念+线程控制

1.什么是线程 相对于进程而言&#xff0c;进程是承担资源调度的实体&#xff0c;线程在进程内部运行&#xff0c;是操作系统调度的基本单位。 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控制序列…

Qt QWebEngine 加载网页及交互,实现C++与JS 相互调用

目录 前言1、QtWebEngine介绍2、安装3、核心类介绍3.1 QWebEngineView3.2 QWebEnginePage3.3 QWebEngineProfile3.4 QWebEngineHistory3.5 QWebEngineSettings 4、加载网页5、C调用JS5.1 无返回值5.2 有返回值 6、JS调用C6.1 新建WebObject 类继承自QObject。6.2 将WebObject对…

Day30力扣打卡

打卡记录 最长回文子序列&#xff08;区间DP&#xff09; 链接 class Solution:def longestPalindromeSubseq(self, s: str) -> int:n len(s)f [[0] * n for _ in range(n)]max lambda x, y: x if x > y else yfor i in range(n - 1, -1, -1):f[i][i] 1for j in ra…

数据结构第三课 -----线性表之双向链表

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

用Java开发一个扫雷游戏

以下是用Java开发一个扫雷游戏的基本步骤&#xff1a; 创建一个窗口和画布&#xff0c;用来显示游戏界面。 创建一个游戏逻辑类&#xff0c;包含一些基本的操作&#xff0c;如生成地雷、计算周围地雷数量、打开方格等。 在画布上绘制游戏界面&#xff0c;包括格子、数字、地雷…

人工智能与大数据:驱动现代业务转型的双引擎

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;和大数据已成为驱动业务和技术创新的关键力量。它们的结合不仅重塑了传统行业&#xff0c;也催生了新的商业模式和服务方式。 AI与大数据在零售行业的应用 在零售行业&#xff0c;AI和大数据的应用已经成为提…

《洛谷深入浅出进阶篇》 P2367语文成绩——差分

上链接&#xff1a;P2367 语文成绩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P2367 上题干&#xff1a; 题目背景 语文考试结束了&#xff0c;成绩还是一如既往地有问题。 题目描述 语文老师总是写错成绩&#xff0c;所以当她修改成绩的…

三、Eureka注册中心

目录 一、作用及调用方式 二、搭建eureka注册中心 三、注册user-service和order-service 四、新增实例 五、服务拉取 六、总结 一、作用及调用方式 在服务提供者启动时&#xff0c;它会向eureka注册中心提供自己的信息&#xff0c;并每30秒进行一次刷新eureka注册中心保存…

golang中context使用总结

一、context使用注意事项 在使用context时&#xff0c;有一些需要注意的事项&#xff0c;以及一些与性能优化相关的建议&#xff1a; 避免滥用context传递数据&#xff1a;context的主要目的是传递请求范围的数据和取消信号&#xff0c;而不是用于传递全局状态或大量数据。滥用…

SARAS算法

SARAS算法 代码仓库:https://github.com/daiyizheng/DL/tree/master/09-rl Sarsa算法是一种强化学习算法&#xff0c;用于解决马尔可夫决策过程&#xff08;MDP&#xff09;问题。它是一种基于值函数的方法&#xff0c;可以用于学习最优策略。本文将介绍Sarsa算法的流程。 S…

汽车以太网IOP测试新利器

IOP测试目的 汽车以太网物理层IOP&#xff08;Interoperability &#xff09;测试&#xff0c;即测试被测对象以太网物理层之间的互操作性。用于验证车载以太网PHY能否在有限时间内建立稳定的链路&#xff1b;此外&#xff0c;还用于验证车载以太网PHY可靠性相关的诊断特性&am…

滚雪球学Java(63):Java高级集合之TreeSet:什么是它,为什么使用它?

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

Java Elasticsearch 按一定时间间隔(timeInterval)循环查询数据

最近有个需求&#xff0c;前端传入时间间隔&#xff0c;去elasticsearch按照时间间隔统计每个时间间隔内数据量。 public List<HashMap<String,Object>> getCount(RequestParam Integer time, RequestParam String selectedDatedTime) {SimpleDateFormat format n…

Karmada调度器

调度器就像一个发动机&#xff0c;如果没有了发动机输入动力&#xff0c;是无法正常运行的。就像 Kubernetes 的调度器&#xff0c;它会负责根据节点的资源状态、Pod 的运行状态&#xff0c;判断 Pod 是调度到怎样的集群节点上去。对于 Karmada 这样的多云能力的调度器来说&…