网络编程基础:Linux网络I/O模型、JavaI/O模型、Netty

news2024/11/25 20:23:11

文章目录

  • 一、Linux网络I/O模型简介
    • 0.文件描述符和系统调用
    • 1. 阻塞I/O模型
    • 2. 非阻塞I/O模型(轮询)
    • 3. I/O复用模型(轮询、事件驱动)
  • 二、Java的I/O演进
    • 1.BIO(阻塞)
      • (1)套接字
      • (2)线程池
      • (3)工作流程
      • (4)代码实现
      • (5)缺点
    • 2.NIO(轮询)
      • (1)工作流程
      • (2)代码实现
    • 3.NIO2.0——AIO(异步/事件驱动)
  • 三、Netty(NIO+AIO)
    • 1.Reactor线程模型
      • (1)单Reactor单线程模型
      • (2)单Reactor多线程模型
      • (3)主从Reactor多线程模型(Netty)
    • 2.Netty快速上手
    • 3.Netty核心组件
      • (1)网络通信层
      • (2)事件调度层
      • (3)服务编排层
  • 参考

一、Linux网络I/O模型简介

Linux的I/O模型是指在进行输入输出操作时,操作系统处理数据的方式。它涉及到如何处理数据的传输,以及应用程序在数据就绪时如何进行读取和写入。

0.文件描述符和系统调用

Linux的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。

  1. 外部设备视为文件:
    在Linux操作系统中,所有外部设备(如磁盘、串口、网络套接字等)都被抽象地看作文件。这种抽象使得对这些设备的操作和对文件的操作类似,从而使操作系统的编程模型更加一致和统一。

  2. 系统调用和文件描述符:
    当应用程序需要与文件或设备进行交互时,它会调用内核提供的系统调用。系统调用是应用程序和操作系统内核之间的接口,用于执行底层操作。例如,对于文件的读写操作,应用程序会调用系统调用,如read()write()

  3. 文件描述符(File Descriptor):
    在进行文件或设备操作时,内核为每个操作返回一个唯一的标识符,称为文件描述符。文件描述符是一个整数,作为操作系统在内部维护的标识符,用于识别打开的文件、设备或套接字。

  4. Socket描述符:
    对于网络套接字,也存在类似于文件描述符的概念,称为Socket描述符(socketfd)。套接字描述符用于标识应用程序与网络上的其他设备或程序之间的通信通道。

  5. 描述符指向结构体:
    文件描述符(或Socket描述符)是一个数字,它在内核中对应于一个数据结构,通常是一个记录文件属性、位置、权限等信息的结构体。这个结构体包含了文件或套接字的相关信息,内核使用它来管理文件的状态以及对其进行读写等操作。

总之,Linux将外部设备和网络套接字都看作文件,并使用系统调用获得文件描述符或Socket描述符,以便于应用程序与内核进行文件和设备操作的交互。描述符则是在内核中维护的数据结构的引用,用于管理对文件和设备的操作。

1. 阻塞I/O模型

系统调用:

  • 对于阻塞I/O模型,主要的系统调用是read()write(),用于读取和写入数据。

阻塞等待:

  • 当应用程序调用阻塞式I/O操作,例如read(),内核会检查数据是否已经准备好。如果数据没有准备好,进程将被阻塞,直到数据就绪。
  • 在阻塞等待期间,操作系统会将该进程设置为睡眠状态,直到所需数据就绪。这样,CPU不会浪费在轮询上。

数据准备就绪:

  • 当数据准备就绪,比如一个文件中的数据被读取到缓冲区中,内核会从睡眠状态唤醒进程,将数据从内核空间拷贝到用户空间,并返回到应用程序。

阻塞特性:

  • 阻塞I/O模型的特点是,在数据就绪之前,应用程序会一直等待,不会执行其他任务。这会导致并发性能较低,因为进程在等待I/O完成期间无法处理其他任务。

2. 非阻塞I/O模型(轮询)

Linux的非阻塞I/O模型是一种基于事件驱动的I/O处理方式,它在进行I/O操作时不会阻塞整个进程,而是通过轮询或异步通知的方式来实现。以下是Linux非阻塞I/O模型的底层原理解释:

非阻塞系统调用:
在非阻塞I/O模型中,应用程序使用非阻塞的系统调用(如read()和write())来进行数据读写操作。这些非阻塞系统调用会立即返回,无论数据是否可用,从而避免了阻塞进程的情况。

轮询机制:
在非阻塞I/O中,应用程序会通过不断地轮询来检查文件描述符(或Socket描述符)是否有可读或可写的数据。这样,当应用程序需要读取数据时,它会重复调用非阻塞的read()系统调用,如果数据还未准备好,则read()会立即返回,不会阻塞进程。

这种模型需要应用程序不断地轮询数据是否就绪,可能会导致CPU资源的浪费。

3. I/O复用模型(轮询、事件驱动)

Linux I/O 复用模型是一种高效的事件驱动编程方式,它允许单个进程监视多个文件描述符(如套接字、文件、管道等),并在这些描述符上等待事件的发生,从而实现了高并发的网络编程。下面是 Linux I/O 复用模型的底层原理解释:

  1. 系统调用: 在 Linux 中,有几种 I/O 复用模型,如 select()poll()epoll() 等。这些都是系统调用,可以用来监听多个文件描述符上的事件。

  2. select(): select() 是传统的 I/O 复用模型,它使用一个文件描述符集合来监听多个文件描述符。应用程序通过调用 select() 等待事件发生,当有一个或多个文件描述符上的事件发生时,select() 会返回,然后应用程序需要遍历所有文件描述符,找出哪些发生了事件。

  3. poll(): poll()select() 类似,但是它没有最大文件描述符数量的限制,而且更加灵活。应用程序通过调用 poll() 等待事件发生,当有文件描述符上的事件发生时,poll() 会返回,然后应用程序遍历找出哪些发生了事件。

  4. epoll(): epoll() 是 Linux 中性能最好的 I/O 复用模型,它使用事件驱动的方式,避免了遍历所有文件描述符。应用程序通过调用 epoll_create() 创建一个 epoll 实例,并通过 epoll_ctl() 注册文件描述符。当有事件发生时,epoll_wait() 会返回有事件发生的文件描述符列表,应用程序只需处理这些文件描述符。

在这里插入图片描述

使用场景分析:
当连接数较少,并且读写较为活跃时,使用select、poll能获得更好的性能;
当连接数较多,并且大多都不活跃时,使用基于回调机制的epoll性能更好,而基于遍历的select、poll性能会急速下降。
由于epoll的连接数无限制,特别适合高并发场景下使用。相比之下select、poll单个进程所打开的FD是有一定限制的。

二、Java的I/O演进

1.BIO(阻塞)

网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。

在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。

BIO,即Blocking IO,阻塞型I/O。阻塞体现在两个地方,连接线程的阻塞和读写的阻塞。

(1)套接字

在计算机网络编程技术中,两个进程或者说两台计算机可以通过一个网络通信连接实现数据的交换,这种通信链路的端点就被称为“套接字”(英文名称也就是Socket)。在Java语言中,服务端使用ServerSocket套接字,客户端使用Socket套接字。

  • ServerSocket:
// 创建ServerSocket对象
ServerSocket(int port):这个使用指定的端口去创建ServerSocketIP地址使用默认的本地IP地址
ServetSocket(int port,int backlog):除了端口外,还有一个用来改变队列长度参数的backlog,指定当服务器繁忙时,可以与之保持连接请求的客户端数量,默认为50
ServetSocket(int port,int backlog,InetAddress ip):这个使用指定的端口、backlog、地址去创建ServetSocket

// 两个静态方法获取InetAddress对象:
getByName(String hostName)
getByAddress(byte[] address)

// accept()
serverSocket.accept():没有参数,返回一个Socket,如果接收到客户端的一个Socket,则返回,否则一直处于等待状态,线程也被阻塞。
  • Socket:
// 创建Socket对象
Socket(InetAddress address,int port):使用指定IP与指定端口构造Socket,默认使用本地ip,端口则动态分配
Socket(String address,int port):使用String表示IP
Socket(InetAddress address,int port,InetAddress localAddr,int localPort):创建指定了远程IP、远程端口、本地IP、本地端口的Socket
Socket(String address,int port,InetAddress localAddr,int localPort):使用String表示远程IP,用InetAddress表示本地IP
  • 简单代码示例:
package socket;

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

public class Server {
    public static void main(String[] args) throws IOException{
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();//阻塞等待客户端连接
            PrintStream printStream = new PrintStream(socket.getOutputStream());//创建输出流
            printStream.println("message from server 8888");
            printStream.close();
            socket.close();
        }
    }
}
package socket;

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

public class Client {
    public static void main(String[] args) throws IOException{
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("This message comes from server:"+bufferedReader.readLine());
        bufferedReader.close();
        socket.close();
    }
}

跑一下试试,成功!

在这里插入图片描述

(2)线程池

请看我写的另一篇博客:跳转

(3)工作流程

服务端启动ServerSocket;
客户端启动 Socket 对服务器进行通信,服务端对每个客户端建立一个线程与之通讯(可以使用线程池进行优化);
客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待(即阻塞);
如果有响应,客户端线程会等待请求结束后,再继续执行。

在这里插入图片描述

(4)代码实现

  • 服务端:
package bio;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BIOServer {
    //创建一个线程池,用于处理客户端连接后的工作
    public static ThreadPoolExecutor pool=new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    public static void main(String[] args) throws IOException{
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            //1 等待客户端连接是阻塞的
            Socket socket=serverSocket.accept();
            System.out.println("客户端连接上了");
            //2 连接上以后向线程池提交一个任务用于处理连接
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try{
                            //读写也是阻塞的
                            //创建输出流,server向client输出
                            PrintStream printStream = new PrintStream(socket.getOutputStream());
                            printStream.println("message from server 8888");
                            printStream.close();
                            socket.close();
                        }catch(IOException e){
                            e.printStackTrace();
                        }
                        
                    }
                }
            });
        }
    }
}

  • 客户端:
package bio;

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

public class BIOClient {
    public static void main(String[] args) throws IOException{
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("This message comes from server:"+bufferedReader.readLine());
        bufferedReader.close();
        socket.close();
    }
}

在这里插入图片描述

(5)缺点

  • accept()等待客户端连接是阻塞的,有时候需要进行无谓的等待,效率低下,浪费资源。
  • 引入线程池进行优化提升了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃。
  • 读写操作仍然是阻塞的,如果客户端半天没有操作,也会浪费资源,因此效率不高。

2.NIO(轮询)

NIO,即non-blocking lO,非阻塞型IO。

  • 非阻塞——减少线程资源的浪费:
    BIO提供非阻塞读写模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。可以做到 用一个线程来处理多个操作,体现了一种多路复用的思想。 而不是像BIO那样,一个连接过来就得分配一个线程,造成资源的浪费。

  • 三大核心组件——提升效率:
    NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

(1)工作流程

Channel(通道),Buffer(缓冲区), Selector(选择器)为NIO的三大核心组件。

  • Channel(通道):
    相比于BIO流的读写,Channel的读写是双向的,既可以从通道中读取数据,又可以写数据到通道。通道可以非阻塞读取和写入通道/缓冲区,也支持异步地读写。

  • Buffer(缓冲区):
    在客户端和Channel之间,增加Buffer缓冲区的支持,更加容易操作和管理。

  • Selector(选择器):
    用来 轮询 检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,提高效率。

在这里插入图片描述

(2)代码实现

代码来自:here

  • 服务端:
package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 创建一个Selector对象,
        Selector selector = Selector.open();

        // 绑定端口6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 把serverSocketChannel注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环等待用户连接
        while (true){
            if (selector.select(1000) == 0){ //等待(阻塞)一秒, 没有事件发生
//            if (selector.selectNow() == 0){ // 也可以设置成非阻塞的
                System.out.println("服务器等待了一秒,无连接");
                continue;
            }

            // 如果返回的>0 , 说明客户端有了动作,就获取相关的selectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 返回关注事件的集合

            // 遍历selectionKeys
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()){
                // 获取到selectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的通道获取事件并做相应处理
                if (key.isAcceptable()){
                    //如果是OP_ACCEPT, 表示有新的客户端产生
                    //给该客户端生成SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将socketChannnel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector上, 设置事件为OP_READ,同时给socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if (key.isReadable()){
                    // 发生了OP_READ
                    SocketChannel channel=(SocketChannel)key.channel();
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端"+new String(buffer.array()));
                }

                // 手动从集合中移除当前的selectionKey, 防止多线程情况下的重复操作
                keyIterator.remove();

            }


        }

    }
}

  • 客户端:
package nio;

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

public class NIOClient {

    public static void main(String[] args) throws IOException {
        // 获取一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        //设置服务器端ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        if (!socketChannel.connect(inetSocketAddress)){

            while (!socketChannel.finishConnect()){
                //如果没有连接成功,客户端是非阻塞的,可以做其它工作
                System.out.println("等待连接...");
            }
        }

        // 如果连接成功,就发送数据
        String str = "hello world";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        // 发送数据 , 将buffer中的数据写入到channel中
        socketChannel.write(buffer);
        System.in.read();

    }

}

在这里插入图片描述

3.NIO2.0——AIO(异步/事件驱动)

AIO,即Asynchronous I/O,异步非阻塞IO。AIO提供的最大的特点是具备异步功能,采用“订阅-通知”模式,即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。

在这里插入图片描述

下面是一段简单的代码示例:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

public class NIO2AsyncFileIOExample {

    public static void main(String[] args) {
        try {
            // 通过路径获取文件通道
            Path path = Paths.get("test.txt");
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                    path, StandardOpenOption.READ, StandardOpenOption.WRITE);

            // 分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 异步读取文件
            Future<Integer> readResult = fileChannel.read(buffer, 0);
            while (!readResult.isDone()) {
                // 在等待异步读取完成时可以进行其他操作
                System.out.println("Waiting for read operation to complete...");
            }

            // 打印读取结果
            buffer.flip();
            System.out.println("Read data: ");
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
            System.out.println();

            // 异步写入数据
            String newData = "Hello, NIO 2.0!";
            buffer.clear();
            buffer.put(newData.getBytes());
            buffer.flip();
            Future<Integer> writeResult = fileChannel.write(buffer, 0);
            while (!writeResult.isDone()) {
                // 在等待异步写入完成时可以进行其他操作
                System.out.println("Waiting for write operation to complete...");
            }
            System.out.println("Data written to file.");

            // 关闭文件通道
            fileChannel.close();

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

三、Netty(NIO+AIO)

  • 为什么不建议使用原生Java NIO:

开发出高质量的NIO程序并不是一件简单的事情,除去NIO固有的复杂性和BUG不谈,作为一个NIO服务端,需要能够处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写等情况,如果你没有足够的NIO编程经验积累,一个NIO框架的稳定往往需要半年甚至更长的时间。更为糟糕的是,一旦在生产环境中发生问题,往往会导致跨节点的服务调用中断,严重的可能会导致整个集群环境都不可用,需要重启服务器,这种非正常停机会带来巨大的损失。

从可维护性角度看,由于NIO采用了异步非阻塞编程模型,而且是一个I/O线程处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。

  • Netty的优点:

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty作为底层通信框架;很多其他业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

通过对Netty的分析,我将它的优点总结如下:

◎ API使用简单,开发门槛低;
◎ 功能强大,预置了多种编解码功能,支持多种主流协议;
◎ 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
◎ 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
◎ 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
◎ 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
◎ 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。

正是因为这些优点,Netty逐渐成为Java NIO编程的首选框架。

1.Reactor线程模型

(1)单Reactor单线程模型

只有一个线程来执行所有的任务,效率低下,并且也有可靠性问题。

在这里插入图片描述

(2)单Reactor多线程模型

相比于上一个模型,增加了线程池的支持,从一定程度上提升了并发效率,但是引入线程池可能会涉及到数据同步问题。Redis底层就是基于这种模型。

在这里插入图片描述

(3)主从Reactor多线程模型(Netty)

在上一个模型的基础上,一个Reactor变成了两个,主Reactor创建连接,从Reactor分发读写任务,能支持更高的并发量。Netty是基于这种模型。
在这里插入图片描述

2.Netty快速上手

Netty实战:开发一个仿WeChat聊天工具SmartChat

3.Netty核心组件

Netty的设计目标是提供高性能、高可靠性的网络通信框架。因此,开发者可以在Netty的基础上实现自己的协议栈,从而构建各种类型的网络应用,包括传统的TCP/UDP应用、HTTP服务、WebSocket服务器等。这种灵活性使得Netty成为了一个通用的网络编程框架,可以满足不同领域的需求。

在这里插入图片描述

(1)网络通信层

  • Bootstrap:
    负责客户端启动并用来连接远程Netty Server。

  • ServerBootstrap:
    负责服务端监听指定端口。

  • Channel:
    完成网络通信的载体。

(2)事件调度层

  • EventLoopGroup:
    线程池,负责接收IO请求,并分配线程执行任务。

  • EventLoop:
    线程池中的线程。

(3)服务编排层

  • ChannelHandler:
    通过指定的handler处理数据IO。

  • ChannelHandlerContext:
    保存ChannelHandler的上下文。

  • ChannelPipeline:
    将多个ChannelHandler链接在一起。

参考

套接字:https://blog.csdn.net/qq_27525611/article/details/102633014
BIO:https://blog.csdn.net/java_lg/article/details/126274158
NIO:https://blog.csdn.net/K_520_W/article/details/123454627
NIO:https://www.cnblogs.com/jobbible/p/16913990.html
AIO:https://zhuanlan.zhihu.com/p/504968873
《Netty权威指南》李林锋

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

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

相关文章

安达发|疫情影响下的APS排程可以给制造业解决哪些问题

随着市场需求的不断变化&#xff0c;特别是对柔性、小单量多批次的需求日益增长&#xff0c;再加上疫情的影响&#xff0c;企业随时可能面临延期交货的风险。这使得行业供应链管理的复杂性不断加剧&#xff0c;企业对于生产管理高效性的需求也日益显著。 同时&#xff0c;我们…

vm workstation pro安装centos7

官网下载vm workstation pro&#xff0c;参考上一篇文章&#xff1a;https://editor.csdn.net/md/?articleId132208423安装centos7 官网下载的vm&#xff0c;是有一个镜像iso文件的 完成后稍等&#xff1a; 这一步很重要&#xff0c;别急着点开始安装&#xff0c;看到有感叹…

.NET 8 Preview 6 中推出 .NET MAUI: 欢迎使用 VS Code 和 VS for Mac

作者&#xff1a;David Ortinau 排版&#xff1a;Alan Wang .NET 8 Preview 6 推出了.NET MAUI&#xff0c;同时修复了 23 个关键问题&#xff0c;并引入了适用于 iOS 的 Native AOT。此外&#xff0c;您现在可以在 .NET 8 中使用 .NET MAUI&#xff0c;只要安装 Visual Studio…

excel将主信息和明细信息整理为多对多(每隔几行空白如何填充)

excel导出的数据是主信息和明细信息形式。 原始数据图&#xff1a; 最终效果:

UML之四种事物

目录 结构事物 行为事物 分组事物&#xff1a; 注释事物 结构事物 1.类(Class) -类是对一组具有相同属性、方法、关系和语义的对象的描述。一个类实现一个或多个接口 2.接口(interface) -接口描述 了一个类或构件的一个服务的操作集。接口仅仅是定义了一组操作的规范&…

23、springboot日志使用入门-- SLF4J+Logback 实现(springboot默认的日志实现),日志打印到控制台及日志输出到指定文件

springboot日志使用入门 ★ 典型的Spring Boot日志依赖&#xff1a; spring-boot-start.jar -- spring-boot-starter-logging.jar (Spring Boot的日志包&#xff09;-- logback&#xff08;core、classic&#xff09;-- log4j-to-slf4j.jar-- jul-to-slf4j.jar就是springboo…

【el-upload】批量上传图片时在before-upload中添加弹窗判断时的踩坑记录

一、初始代码 1. 初始使用组件代码片段 <!-- 上传 --> <DialogUploadFile ref"uploadFile" success"refresh" />// 上传 const uploadHandle () > {if (selections.value.length ! 1) {onceMessage.warning(请选择一条数据操作)return}u…

并行FIR滤波器

FIR 滤波器原理 FIR 滤波器是有限长单位冲击响应滤波器&#xff0c;又称为非递归型滤波器。FIR 滤波器具有严格的线性相频特性&#xff0c;同时其单位响应是有限长的&#xff0c;因而是稳定的系统。 FIR 滤波器本质上就是输入信号与单位冲击响应函数的卷积&#xff0c;表达式…

MapBox加载不同风格

初始化MapBox地图&#xff1a; var map new mapboxgl.Map({container: map,zoom: 3,center: [105, 34],//此处更改地图风格style: mapbox://styles/mapbox/satellite-v9,hash: false,});1.户外地图&#xff08;mapbox://styles/mapbox/basic-v9&#xff09;新版&#xff1a;&a…

python——案例15:判断奇数还是偶数

案例15&#xff1a;判断奇数还是偶数numint(input(输入数值&#xff1a;))if(num%2)0: #通过if语句判断print("{0}是偶数".format(num))else: #通过else语句判断print("{0}是奇数".format(num))

Linux Linux基础命令

1.pwd——显示当前位置的绝对路径 2.cd——切换目录&#xff0c;cd 后的参数表示要切换到的位置 &#xff08;1&#xff09;cd后面的参数为绝对路径&#xff1a; &#xff08;2&#xff09;cd后面的参数为相对路径&#xff1a; &#xff08;3&#xff09;cd ~回到家目录&#…

Ansys Lumerical | 针对多模干涉耦合器的仿真设计与优化

说明 本示例演示通过12端口多模干涉(MMI)耦合器计算宽带传输和光损耗&#xff0c;并使用S参数在 INTERCONNECT 中创建 MMI 的紧凑模型。(联系我们获取文章附件) 综述 低损耗光耦合器和光分路器是基于 Mach-Zehnder 的光调制器的基本组件&#xff0c;是集成电路的关键组成部分。…

数据结构——双向链表

双向链表实质上是在单向链表的基础上加上了一个指针指向后面地址 单向链表请参考http://t.csdn.cn/3Gxk9 物理结构 首先我们看一下两种链表的物理结构 我们可以看到&#xff1a;双向在单向基础上加入了一个指向上一个地址的指针&#xff0c;如此操作我们便可以向数组一样操作…

【Android NDK开发】Android Studio 编写 JNI (C++)代码无提示

随笔记 Android Studio在编写C代码时候&#xff0c;引入对应的头文件&#xff0c;Android Studio里却不提示对应的方法&#xff0c;需要在Studio中设置一下。 Mac中&#xff0c;选择 Android Studio > Preferences&#xff0c;选择Clangd >>Disable Clangd completio…

【Vue3】自动引入插件-`unplugin-auto-import`

Vue3自动引入插件-unplugin-auto-import&#xff0c;不必再手动 import 。 自动导入 api 按需为 Vite, Webpack, Rspack, Rollup 和 esbuild 。支持TypeScript。由unplugin驱动。 插件安装&#xff1a;unplugin-auto-import 配置vite.config.ts&#xff08;配置完后需要重启…

(二) 【屠龙刀】 vsomeip协议栈的编译与使用

前言 上一篇文章介绍了SOME/IP协议的报文格式,本片文章主要来介绍SOME/IP协议的具体实现,即vsomeip协议栈。 vsomeip由GENIVI组织根据SOME/IP协议标准实现的协议栈,如果说SOME/IP协议是一个人的灵魂,那么vsomeip就是受灵魂指导的肉体。本文将从如下几点去展开本文,手把手…

米尔瑞萨RZ/G2L开发板-01 开箱+环境搭建+交叉编译FFMPEG

标题有点长哈&#xff0c;首先要感谢米尔电子提供的开发板&#xff0c;异构的板子说实话还真的是最近才开始接触的&#xff0c;在我提交申请后&#xff0c;很快就收到板子了&#xff0c;而且还是顺丰给发来的&#xff0c;其实我估计很多人就是为了骗板子&#xff0c;因为米尔的…

【学习FreeRTOS】第2章——FreeRTOS基础知识

1.任务调度 1.1.任务调度简介 调度器就是使用相关的调度算法来决定当前需要执行的哪个任务FreeRTOS 一共支持三种任务调度方式&#xff1a; 抢占式调度&#xff1a;针对优先级不同的任务&#xff0c;每个任务都有一个优先级&#xff0c;优先级高的任务可以抢占优先级低的任务…

2.CUDA 编程手册中文版---编程模型

2.编程模型 更多精彩内容&#xff0c;请扫描下方二维码或者访问https://developer.nvidia.com/zh-cn/developer-program 来加入NVIDIA开发者计划 本章通过概述CUDA编程模型是如何在c中公开的&#xff0c;来介绍CUDA的主要概念。 编程接口中给出了对 CUDA C 的广泛描述。 本章…

linux环形缓冲区kfifo实践3:IO多路复用poll和select

基础知识 poll和select方法在Linux用户空间的API接口函数定义如下。 int poll(struct pollfd *fds, nfds_t nfds, int timeout); poll()函数的第一个参数fds是要监听的文件描述符集合&#xff0c;类型为指向struct pollfd的指针。struct pollfd数据结构定义如下。 struct poll…