JAVA-IO模型的理解(BIO、NIO)

news2025/1/21 15:06:23

前言

        (本文是作者学习制作rpc框架时,一些自用的笔记,并不会完整详细的介绍某个模块,会写大概的流程及一些相关概念,供日后复习使用~)

IO模型

先理解基本的IO流程:

  1. 应用A把消息发送到 TCP发送缓冲区
  2. TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区
  3. B再从TCP接收缓冲区去读取属于自己的数据

1.阻塞IO模型

A发送消息到TCP发送缓冲区的过程,同样会出现以下情况:

  1. TCP发送缓冲区满了,也就是说A发送的消息TCP发送缓冲区接收不了
  2. A无法成功的将消息发送到TCP发送缓冲区

这时候TCP发送缓冲区产生了一个问题?

  1. 告诉A,现在缓冲区满了,你该干啥干啥去
  2. 告诉A,现在缓冲区满了,你等一会,有空间的时候,你再拷贝数据到缓冲区(阻塞)

以应用A为例,阻塞IO就是,当A发起 发送数据的请求,发送缓冲区一直处于满的状态,则A一直处于等待状态,直到内核将数据发送出去有空间,A发送出为止。

术语描述:在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的,称为阻塞IO。

recvfrom用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间。

流程:

  1. 应用进程向内核发起recvfrom读取数据
  2. 准备数据报(应用进程阻塞)
  3. 将数据从内核复制到应用空间
  4. 复制完成后,返回成功提示

2.非阻塞IO模型

        很明显,非阻塞IO就是,内核数据没有准备好之前,会直接通知B未准备好,让B不要等待了

B 问 准备好了吗?内核 没准备好 过了一会 

术语:非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。B又问:准备好了吗?

流程:

  1. 应用进程向内核发起recvfrom读取数据
  2. 没有数据报准备好,即刻返回EWOULDBLOCK错误码
  3. 应用进程向内核发起recvfrom读取数据
  4. 已有数据包准备好就进行一下 步骤,否则还是返回错误码
  5. 将数据从内核拷贝到用户空间
  6. 完成后,返回成功提示

        但会存在一个问题,如果只有一个线程的话,会忙等待,即一直询问数据有无准备好,没有准备好返回错误,造成cpu空转,浪费资源 

而且如果是多线程的话,对于每个连接请求都要建立一个线程来处理,也会造成资源的浪费

3.IO多路复用模型

        就如刚刚所说的:

        假设B从TCP缓冲区中读取数据,如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建N个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图:

        这时我们可以以下这样处理:

        可以有一个或者多个线程监控多个网络请求(fd文件描述符linux系统把每个网络请求以一个fd来标识),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路

        但这个模型依然存在一个问题:我们该如何得知每个网络请求的状态?

        这个模型一般来说是轮询每个网络连接的状态,找到需要发出请求(读or写)的网络连接,然后分配线程来处理。但是大多数情况下很多线程都是空闲状态,不会频繁发起读写请求,所以采用轮询的方式,也会造成资源的浪费(为了一个连接请求,需要遍历全部的连接请求)

        术语描述:进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据

        复用IO的基本思路就是通过select或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

4.信号驱动IO模型

        为了解决以上问题,提出了该方案。

        不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,这就衍生了信号驱动IO模型

        术语描述:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。(侧重点是告诉你该进行下一步的IO操作了

IO复用模型里面的select虽然可以监控多个fd了,但select其实现的本质上还是通过不断的轮询fd来监控数据状态, 因为大部分轮询请求其实都是无效的,所以信号驱动IO意在通过这种建立信号关联的方式,实现了发出请求后只需要等待数据就绪的通知即可,这样就可以避免大量无效的数据状态轮询操作。

 

        以上模型虽然数据准备就绪后,会发送信号通知内核进行读取,但是读取的过程依然是阻塞的,直到读取完毕 

5.异步IO

        不管IO复用还是信号驱动,我们读取数据都要进行两个步骤:

1.发送select请求,将自己加入到select进行监控,询问状态是否准备好

2、发送recvfrom请求读取数据(阻塞)

为了解决第二步读取数据时会阻塞的问题,这里提出了异步IO。

        应用只需要向内核发送一个read 请求,告诉内核它要读取数据后立即返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用读取完毕

        术语描述: 应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,异步IO与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们什么时候可以开始下一个IO操作,而异步IO模型是由内核通知我们该操作什么时候完成。

异步IO的优化思路是解决了应用程序需要先发送询问请求、后发送接收数据请求两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态询问和数拷贝的所有操作。

 BIO、NIO

BIO

        BIO 全称Block-IO, 是一种同步且阻塞的通信模式

        在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们称为同步请求;反之,如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步

        如上图,是典型的BIO模型,每当有一个连接到来,经过协调器的处理,就开启一个对应的线程进行接管。如果连接有1000条,那就需要1000个线程。线程资源是非常昂贵的,除了占用大量的内存,还会占用非常多的CPU调度时间,所以BIO在连接非常多的情况下,效率会变得非常低。

下面的代码是使用ServerSocket实现的一个简单socket服务器,监听在8888端口:

public class BIO {
    static boolean stop = false;
    public static void main(String[] args) throws Exception {
        int connectionNum = 0;
        int port = 8888;
        ExecutorService service = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(port);
        while (!stop) {
            if (10 == connectionNum) {
                stop = true;
            }
            Socket socket = serverSocket.accept();
            service.execute(() -> {
                try {
                    Scanner scanner = new Scanner(socket.getInputStream());
                    PrintStream printStream = new PrintStream(socket.getOutputStream());
                    while (!stop) {
                        String s = scanner.next().trim();
                        printStream.println("PONG:" + s);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            });
            connectionNum++;
        }
        service.shutdown();
        serverSocket.close();
    }
}

        可以看到,BIO的读写操作是阻塞的,线程的整个生命周期和连接的生命周期是一样的,而且不能够被复用。

        就单个阻塞IO来说,它的效率并不比NIO慢。但是当服务的连接增多,考虑到整个服务器的资源调度和资源利用率等因素,NIO就有了显著的效果,NIO非常适合高并发场景。

 

NIO

Java NIO,全称 Non-Block IO ,是Java SE 1.4版以后,针对网络传输效能优化的新功能。是一种非阻塞同步的通信模式

NIO主要有三种实现:select,poll,epoll。这三种都是IO多路复用的机制。

I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

Java的NIO,在Linux上底层是使用epoll实现的

  • fd 每条连接、每个文件,都对应着一个描述符,比如端口号。内核在定位到这些连接的时候,就是通过fd进行寻址的
  • event 事件

Select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。

调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。

当select函数返回后,需要通过遍历fdset,来找到就绪的描述符

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。

select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

 

Poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。

和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

 Epoll

epoll对select中存在的问题都逐一解决,简单来说epoll的优势包括:

  • 对fd数量没有限制(当然这个在poll也被解决了)
  • 抛弃了bitmap数组实现了新的结构来存储多种事件类型
  • 无需重复拷贝fd 随用随加 随弃随删
  • 采用事件驱动避免轮询查看可读写事件

        epoll出现之后大大提高了并发量,能够轻松应对C10K问题


//用户数据载体
typedef union epoll_data {
   void    *ptr;
   int      fd;
   uint32_t u32;
   uint64_t u64;
} epoll_data_t;
//fd装载入内核的载体
 struct epoll_event {
     uint32_t     events;    /* Epoll events */
     epoll_data_t data;      /* User data variable */
 };
 //三板斧api
int epoll_create(int size); 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
int epoll_wait(int epfd, struct epoll_event *events,
                 int maxevents, int timeout);
  • int epoll_create(int size)

    创建一个epoll的句柄,后续的操作都是基于此fd的。

    size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。 当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

    函数是对指定描述符fd执行op操作。

  • - epfd:是epoll_create()的返回值。

  • - op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。

  • - fd:是需要监听的fd(文件描述符)

  • - epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:

    struct epoll_event {
      __uint32_t events;  /* Epoll events */
      epoll_data_t data;  /* User data variable */
    };
    
    //events可以是以下几个宏的集合:
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
    
  • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

    等待epfd上的io事件,最多返回maxevents个事件。 参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

  • epoll_event是用户态需监控fd的代言人,后续用户程序对fd的操作都是基于此结构的;

通俗描述:

  • epoll_create场景

    大学开学第一周,你作为班长需要帮全班同学领取相关物品,你在学生处告诉工作人员,我是xx学院xx专业xx班的班长,这时工作人员确定你的身份并且给了你凭证,后面办的事情都需要用到(也就是调用epoll_create向内核申请了epfd结构,内核返回了epfd句柄给你使用);

  • epoll_ctl场景

    你拿着凭证在办事大厅开始办事,分拣办公室工作人员说班长你把所有需要办理事情的同学的学生册和需要办理的事情都记录下来吧,于是班长开始在每个学生手册单独写对应需要办的事情:

    李明需要开实验室权限、孙大熊需要办游泳卡......就这样班长一股脑写完并交给了工作人员(也就是告诉内核哪些fd需要做哪些操作);

  • epoll_wait场景

    你拿着凭证在领取办公室门前等着,这时候广播喊xx班长你们班孙大熊的游泳卡办好了速来领取、李明实验室权限卡办好了速来取....还有同学的事情没办好,所以班长只能继续(也就是调用epoll_wait等待内核反馈的可读写事件发生并处理);

 工作模式

epoll对文件描述符的操作有两种模式:LT(level trigger)ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。   

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

1. LT模式

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

2. ET模式

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知。

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

NIO代码示例

package com.mszlu.rpc.io.nio;

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.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NIO {
    static boolean stop = false;
    public static void main(String[] args) throws Exception {
        int connectionNum = 0;
        int port = 8888;
        ExecutorService service = Executors.newCachedThreadPool();
        //创建了一个服务端ssc,并开启一个新的事件选择器,监听它的OP_ACCEPT事件。
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress("localhost", port));
        Selector selector = Selector.open();
        //共有4种事件类型。
        // 分别是新连接事件(OP_ACCEPT)、连接就绪事件(OP_CONNECT)、读就绪事件(OP_READ)、写就绪事件(OP_WRITE)。
        // 任何网络和文件操作,都可以抽象成这四个事件
        //SelectionKey.OP_ACCEPT = 16 ssc.validOps()=16
        ssc.register(selector, ssc.validOps());
        while (!stop) {
            if (10 == connectionNum) {
                stop = true;
            }
            //在while循环里,使用select函数,阻塞在主线程里。
            // 所谓阻塞,就是操作系统不再分配CPU事件片到当前线程中,所以select函数是几乎不占用任何系统资源的
            int num = selector.select();
            if (num == 0) {
                continue;
            }
            //一旦有新的事件到达,比如有新的连接到来,主线程就能够被调度到,程序就能够向下执行。
            // 这时候,就能够根据订阅的事件通知,持续获取订阅的事件。
            //由于注册到selector的连接和事件可能会有多个,所以这些事件也会有多个
            //使用安全的迭代器循环进行处理,在处理完毕之后,将它删除。
            //如果事件不删除的话,或者漏掉了某个事件的处理,会怎么样呢?
            // 后果还是比较严重的,由于事件总是存在,我们的程序会陷入无休无止的循环之中。
            Iterator<SelectionKey> events = selector.selectedKeys().iterator();
            while (events.hasNext()) {
                SelectionKey event = events.next();

                if (event.isAcceptable()) {
                    //NIO操作的对象是抽象的概念Channel,通过缓冲区进行数据交换
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    //订阅OP_READ事件,数据流读取
                    sc.register(selector, SelectionKey.OP_READ);
                    connectionNum++;
                } else if (event.isReadable()) {
                    try {
                        SocketChannel sc = (SocketChannel) event.channel();
                        //创建了一个1024字节的缓冲区,用于数据的读取
                        //如果连接中的数据,大于1024字节怎么办?
                        //水平触发 (level-triggered) 称作LT模式。只要缓冲区有数据,事件就会一直发生
                        //边缘触发 (edge-triggered) 称作ET模式。缓冲区有数据,仅会触发一次。事件想要再次触发,必须先将fd中的数据读完才行
                        //java的NIO使用了LT模式,LT模式频繁环唤醒线程,效率相比较ET模式低,所以Netty使用JNI的方式,实现了ET模式,效率上更高一些
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        //这依旧是阻塞的
                        int size = sc.read(buf);
                        if(-1==size){
                            sc.close();
                        }
                        String result = new String(buf.array()).trim();
                        ByteBuffer wrap = ByteBuffer.wrap(("PONG:" + result).getBytes());
                        sc.write(wrap);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                } else if (event.isWritable()) {
                    SocketChannel sc = (SocketChannel) event.channel();
                }

                events.remove();
            }
        }
        service.shutdown();
        ssc.close();
    }
}

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

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

相关文章

【Spring】原型 Bean 被固定

问题描述 在定义 Bean 时&#xff0c;有时候我们会使用原型 Bean&#xff0c;例如定义如下&#xff1a; Service Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ServiceImpl { }然后我们按照下面的方式去使用它&#xff1a; RestController public class Hello…

奉加微PHY6230兼容性:部分手机不兼容

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

Python脚本实现通过JLink烧录Hex文件

1 安装JLink驱动程序 驱动安装包下载路径&#xff1a;https://www.segger.com/downloads/jlink/ 选择对应的版本下载&#xff1a; 将下载的安装文件双击进行安装。 2 安装 pylink 包 pip install pylink3 查询 JLink 设备的 serial number 将JLink通过USB线插入电脑。 w…

【Qt】04-Lambda表达式

前言一、概念引入二、使用方法2.1 基本用法代码示例2.2 捕获外部变量2.3 参数列表 三、完整代码mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 总结 前言 一、概念引入 Lambda表达式&#xff08;Lambda Expressions&#xff09;是C11标准引入的一种匿名函数对象&…

[STM32 HAL库]串口中断编程思路

一、前言 最近在准备蓝桥杯比赛&#xff08;嵌入式赛道&#xff09;&#xff0c;研究了以下串口空闲中断DMA接收不定长的数据&#xff0c;感觉这个方法的接收效率很高&#xff0c;十分好用。方法配置都成功了&#xff0c;但是有一个点需要进行考虑&#xff0c;就是一般我们需要…

汇编与逆向(一)-汇编工具简介

RadASM是一款著名的WIN32汇编编辑器&#xff0c;支持MASM、TASM等多种汇编编译器&#xff0c;Windows界面&#xff0c;支持语法高亮&#xff0c;自带一个资源编辑器和一个调试器。 一、汇编IDE工具&#xff1a;RadASM RadASM有内置的语言包 下载地址&#xff1a;RadASM asse…

Langchain+FastApi+Vue前后端Ai对话(超详细)

一、引入 首先可以先看下作者的文章 FastApi相关文章&#xff1a;创建最简单FastApi的项目Vue相关文章&#xff1a;最简单的aixos二次封装Langchain相关文章&#xff1a;如何使用LangSmith跟踪deepseek模型 二、后端搭建 1 项目文件结构 routers&#xff1a;存放api接口se…

leetcode49-字母异位词分组

leetcode 49 思路 通过一个哈希表进行记录每个分组&#xff0c;遍历strs&#xff0c;然后对每个字符串item进行排序&#xff0c;比如&#xff1a;acb bac cab都会被排序为’abc’,然后以abc作为map的key&#xff0c;value就是存放所有匹配出来为key的值&#xff0c;最后把ma…

深度学习 DAY1:RNN 神经网络及其变体网络(LSTM、GRU)

实验介绍 RNN 网络是一种基础的多层反馈神经网络&#xff0c;该神经网络的节点定向连接成环&#xff0c;其内部状态可以展示动态时序行为。相比于前馈神经网络&#xff0c;该网络内部具有很强的记忆性&#xff0c;它可以利用它内部的记忆来处理任意时序的输入序列&#xff0c;…

跨境电商使用云手机用来做什么呢?

随着跨境电商的发展&#xff0c;越来越多的卖家开始尝试使用云手机来协助他们的业务&#xff0c;这是因为云手机具有许多优势。那么&#xff0c;具体来说&#xff0c;跨境电商使用云手机可以做哪些事情呢&#xff1f; &#xff08;一&#xff09;实现多账号登录和管理 跨境电商…

【Linux】gawk编辑器二

一、变量 gawk编程语言支持两种变量&#xff1a;内建变量和自定义变量。 1、内建变量 gawk使用内建变量来引用一些特殊的功能。 字段和记录分隔符变量 数据字段变量 此变量允许使用美元符号&#xff08;$&#xff09;和字段在记录中的位置值来引用对应的字段。要引用记录…

部署Metricbeat监测ES

官方参考文档 安装Metricbeat curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-7.17.27-linux-x86_64.tar.gztar xzvf metricbeat-7.17.27-linux-x86_64.tar.gz设置 Metricbeat连接到 Elasticsearch 进入metricbeat目录配置metricbeat.yml …

Linux--运维

Mysql主从同步 通过将MySQL的某一台主机&#xff08;master&#xff09;的数据复制到其他主机&#xff08;slaves&#xff09;上&#xff0c;并重新执行一遍来执行 复制过程中一台服务器充当主服务器&#xff0c;而其他一个或多个其他服务器充当从服务器 为什么要做主从复制 …

【odbc】odbc连接kerberos认证的 hive和spark thriftserver

hive odbc驱动&#xff0c;以下两种都可以 教程&#xff1a;使用 ODBC 和 PowerShell 查询 Apache HiveHive ODBC Connector 2.8.0 for Cloudera Enterprise spark thriftserver本质就是披着hiveserver的外壳的spark server 完成kerberos认证: &#xff08;1&#xff09;可以…

家政服务小程序,打造智慧家政新体验

春节即将来临&#xff0c;家政市场呈现出了火热的场景&#xff0c;大众对家政服务的需求持续增加。 近年来&#xff0c;家政市场开始倾向数字化、智能化&#xff0c;借助科学技术打造家政数字化平台&#xff0c;让大众在手机上就可以预约家政服务&#xff0c;减少传统家政市场…

SQL在线格式化 - 加菲工具

SQL在线格式化 - 加菲工具 打开网站 加菲工具 https://www.orcc.online 选择“SQL 在线格式化” 或者直接访问网址 https://www.orcc.online/tools/sql 输入sql&#xff0c;点击上方的格式化按钮即可 输入框得到格式化后的sql结果

WPF1-从最简单的xaml开始.md

1. 最简单的WPF应用 1.1. App.config1.2. App.xaml 和 App.xaml.cs1.3. MainWindow.xaml 和 MainWindow.xaml.cs 2. 正式开始分析 2.1. 声明即定义2.2. 命名空间 2.2.1. xaml的Property和Attribute2.2.2. xaml中命名空间2.2.3. partial关键字 学习WPF&#xff0c;肯定要先学…

cursor重构谷粒商城02——30分钟构建图书管理系统【cursor使用教程番外篇】

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…

Linux探秘坊-------3.开发工具详解(1)

1 初识vim编辑器 创建第一个vim编辑的代码 1.新建文件 2.使用vim打开 3.打开默认是命令模式&#xff0c;写代码需要在屏幕上输出“i”字符 1.写完代码后要按Esc键退出到指令模式2.再按shift:wq即可保存并退出vim &#xff08;因为不支持鼠标&#xff0c;通常 使用键盘上的箭…

ESP-Skainet语音唤醒技术,设备高效语音识别方案,个性化交互应用

在当今数字化、智能化飞速发展的时代&#xff0c;物联网&#xff08;IoT&#xff09;与人工智能&#xff08;AI&#xff09;的深度融合正在重塑我们的生活和工作方式。 在智能家居的生态系统中&#xff0c;语音唤醒技术不仅能够为用户提供个性化的服务&#xff0c;还能通过定制…