Netty应用(六) 之 异步 Channel

目录

12.Netty异步的相关概念

12.1 异步编程的概念

12.2 方式1:主线程阻塞,等待异步线程完成调用,然后主线程发起请求IO

12.3 方式2:主线程注册异步线程,异步线程去回调发起请求IO

12.4 细节注释

12.5 异步的好处

13.Netty中异步设计(内部原理)

13.1 关于Future

13.1.1 JDK中的future

13.1.2 netty中的future

13.2 关于promise

13.3 本章问题

14.Channel

14.1 netty封装之后的常见API

14.1.1 writeAndFlush(Object msg)

14.1.2 write(Object msg)

14.1.3 close

14.1.4 优雅的关闭 shutdownGracefully()


12.Netty异步的相关概念

12.1 异步编程的概念

异步编程和多线程编程

多线程编程:

多线程编程中每一个线程都是公平,平等的关系,举例子:多个客户端client请求与服务端交互,假设说服务端给每一个客户端都开启一个线程去处理,那么这多个线程是公平,平等的

异步编程:

通常是在一个主要的线程(main线程)中,异步开启一个其他线程去处理一些阻塞耗时的操作,但是当该异步开启的其他线程处理完耗时阻塞的操作后,要把最终得出的结果返回给主要的线程(main线程)。异步编程是分主次的,而不是平等的关系。

不过二者都是多线程!

异步编程有两种方式:

1、主线程阻塞,等待连接线程完成调用,然后主线程发起请求IO。

这个方式不好,因为执行下面发送IO的操作是在主线程,他阻塞等待了连接的异步线程。实际上还是在等待。

2、主线程注册异步线程,异步线程去回调发起请求IO。

这种方式是更高效的,因为主线程没有阻塞,他等着回调就完了,主线程继续往下做他的事。

在Netty中有一个原则:

如果是关于网络IO的地方,都设计为异步处理。比如上面说的connect(),网络连接方法自然是网络IO,是异步的。

还有就是我们代码中的channel.writeAndFlush("hello netty");这里再写数据就是IO,也是异步线程执行的。

所以如果你的channel.writeAndFlush("hello netty");这一行后面需要他的返回结果,也需要阻塞等待。类似这样。

Channel channel = future.channel();

ChannelFuture writeAndFlushFuture = channel.writeAndFlush("hello netty");

writeAndFlushFuture.sync();

还有就是.close()关闭socket也是异步的。关闭socket自然要做网络IO。

所以就是一旦是异步,如果你下面要处理就需要考虑阻塞等待,或者监听回调。

12.2 方式1:主线程阻塞,等待异步线程完成调用,然后主线程发起请求IO

package com.messi.netty_core_02.netty03;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyServer {private static final Logger log = LoggerFactory.getLogger(MyNettyServer.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(defaultEventLoopGroup,new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("服务端读取到的数据为:{}",msg);}});}});serverBootstrap.bind(8000);}}
package com.messi.netty_core_02.netty03;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.net.InetSocketAddress;public class MyNettyClient {private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);public static void main(String[] args) throws Exception{Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(new NioEventLoopGroup());bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());}});ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));future.sync();Channel channel = future.channel();channel.writeAndFlush("leomessi");log.info("--------netty-sync--------");System.out.println("处理业务操作......");log.info("--------netty-sync--------");}}
  • 测试与分析

存在性能瓶颈,影响后续业务操作,所以引出方式2

12.3 方式2:主线程注册异步线程,异步线程去回调发起请求IO

  • 代码
package com.messi.netty_core_02.netty03;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyServer {private static final Logger log = LoggerFactory.getLogger(MyNettyServer.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(defaultEventLoopGroup,new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("服务端读取到的数据为:{}",msg);}});}});serverBootstrap.bind(8000);}}
package com.messi.netty_core_02.netty03;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.net.InetSocketAddress;public class MyNettyClient {private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);public static void main(String[] args) throws Exception{Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(new NioEventLoopGroup());bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());}});ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));future.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {Channel channel = future.channel();channel.writeAndFlush("leomessi");}});log.info("--------netty-sync--------");System.out.println("处理业务操作......");log.info("--------netty-sync--------");}}
  • 测试与分析

测试结果:

异步新线程回调注册监听的匿名内部类重写的方法:

12.4 细节注释

  • 细节1

为什么需要future.sync()使得main线程阻塞等待到异步线程处理完网络连接操作后才能让main线程继续执行?

因为网络连接操作是后续一切网络IO的基础,如果你网络连接这一io都没做好,那么你后续read,write这些网络io你怎么做?你没法保证,所以就像方式1那种main线程阻塞等待的方式,其实就是为了保证网络连接正确建立,避免后续其他网络io出问题。

但是由于main线程同步阻塞的方式太低效,所以才引出了后续注册监听,异步回调的方式来优化性能!也就是方式2。但是有一点仍然需要注意,我们要把网络io操作(read或write)放在异步线程处理完连接后所回调的回调方法中!为什么?还问为什么吗,见上即可,就是为了保证read,write这些网络io操作的执行是建立在正确无误完成网络连接之后的呀。对于一些无关网络io的业务操作,main线程由于不阻塞了,所以也可以执行。这样是不是把这个同步阻塞的范围从阻塞"整个main线程"缩小到只阻塞"必须网络连接完成后才能执行read,write等网络io"了。【代码详细见方式1和方式2】

12.5 异步的好处

1.提高系统的吞吐量

分析:这个肯定,可以在相同时间内处理更多的客户端操作或业务操作

2.效率上的提高

分析:原来需要t ms处理完的操作,异步后可能只需要t/2 ms。但绝对不是1+1=2,1+1<2,即两个线程达到的性能高度绝对不是理论上两个线程的性能水平,因为线程间通信,切换都需要耗费性能。

13.Netty中异步设计(内部原理)

先给一个结论:

jdk的future只支持阻塞同步式的future

netty的future是在jdk的future的基础上做的扩展,支持阻塞同步式的future,也支持异步监听处理式的future

netty的Promise是在netty的future的基础上做的扩展,支持阻塞同步式的future,也支持异步监听处理式的future,同样支持得到异步监听的结果是正确还是失败的(这一点前面二者都是不支持的)

我们之前用的bootstrap.connect(new InetSocketAddress(8000));返回的就是ChannelFuture这个异步化的支持方式。

而在netty中还实现了Promise这个异步支持,他的功能是最全的,所以他用的比较多。而且实际上我们上面的代码中,ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));

这个ChannelFuture 其实也是promise的类型,可以断点验证一下:可以看到Future实际上是一个promise类型的内部类。Promise是netty中最底层的继承类,所以他最强大,所以自然也就用的多一些。

13.1 关于Future

我们上面看来netty中的future就是继承自JDK中的future,并且对他做了扩展。下面我们分别来看看他们的区别和差异

13.1.1 JDK中的future

测试:

13.1.2 netty中的future
  • 等待阻塞方式

测试:

  • 监听的方式

13.2 关于promise

后续编码Promise用的不多,Handler用的多,但是Netty底层Promise用的多

上面我们看到了netty中的future提供了异步监听的方式来实现异步处理,我们在future中异步执行了callable中的call方法,最后返回结果的时候回调了listener中的回调方法,把future当成参数传给了回调方法,我们在回调方法中就能通过future.get()获取到call中的执行结果了。

于是问题来了:

1.但是如果我们编程的时候用的不是callable,而是使用的Runnable,runnable是没有返回值的,你这时候这个操作就无法得到返回值了。于是这个当前的架构就不行了。

而我们说promise是继承自future(netty)的,它的功能更加强大了,它实际上就能解决这个Runnable没有返回值的问题,因为promise可以进行设置结果是成功还是失败。

2.有些人会说既然你Runnable不可以有返回值,那么使用Callable问题不就解决了吗?但是你应该清楚,即便我们使用了Callable接口,我们在call中返回的值在实际开发中可能是一个code码值,或者是一个调用结果。你如何知道这个返回结果是正常成功的还是失败的呢?你总的要告诉承接你这个结果的地方是正常的还是失败的吧。也就是你要正常和异常都要给出一个结果返回,你的异步到底执行的如何了。这个需要返回让外部其他线程(main线程)知道。

而我的主线程和你异步线程交互的东西就是异步的返回值,那么这时候就得知道这个返回。所以你不仅仅要把处理的数据作为返回,还要把处理的结果也返回。你也可以包一层比如一个类Result里面有data还有code码,你封装起来返回。但是这就引入新的类了,我们希望返回的数据就是数据,结果就是结果。于是netty为我们封装了一个新的类型就是Promise。

promise不仅仅告诉你结果返回的是什么,还告诉你结果是成功的还是失败的

  • promise的异步阻塞操作

测试:

  • promise的异步监听操作

测试:

  • 总结

Promise让无返回值的Runnable可以设置返回值并且设置返回值是正确还是失败的

13.3 本章问题

关于监听器的执行顺序问题

DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup(2);
EventLoop eventLoop = defaultEventLoopGroup.next();
// 1
Future<String> future = eventLoop.submit(new Callable<String>() {@Overridepublic String call() throws Exception {logger.debug("call begin ***************");TimeUnit.SECONDS.sleep(2);logger.debug("thread is {}", Thread.currentThread().getName());return "hello netty";}
});
// 2
future.addListener(new GenericFutureListener<Future<? super String>>() {@Overridepublic void operationComplete(Future<? super String> future) throws Exception{logger.debug("get call result is {}", future.get());}
})

之前我们有这么一段代码,我们说eventLoop提交了异步任务,异步线程去执行,然后下面的future添加监听器来监听,等到异步任务执行结束了,就回调这个监听器里面的operationComplete方法。既然是异步的多线程必然就有CPU调度问题,假如在1处的异步代码执行完了,2处的代码还没被调度,也就是异步执行完了,监听器还没注册进去,那你的监听器是不是就无法监听到异步结果了呢????意思就是你异步执行完了的回调是要回调谁呢?源码中利用兜底操作解决这一CPU调度问题。

来看源码:(future的实际执行类型是promise,也就是我们直接去DefaultPromise去查看这个addListener的方法)

// io.netty.util.concurrent.DefaultPromise#addListener
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>>
listener) {checkNotNull(listener, "listener");// 这里是添加监听器的地方synchronized (this) {addListener0(listener);}// 这里是这个问题的重点,当异步线程执行完成后 isDone()返回true。否则返回false。
//如果返回true且你已经完成监听,那么回调operationComplete方法
//如果返回true但是你没监听,也就是你没有来得及监听,异步结果就执行完了,这里也会再次执行一遍监听操作,添加进去然后进行回调//所以这其实是个兜底操作。if (isDone()) {notifyListeners();}return this;
}
  • WebFlux

WebFlux Spring响应式框架。

WebFlux百分之80都是Netty的API,其余扩展都是在Handler上做的扩展。

14.Channel

1.首先netty中的channel是对原来jdk中NIO下面的ServerSocketChannel和SocketChannel的封装

2.这个封装最终体现出来的有两个好处:

(1)统一了channel的编程模型,客户无需再区分ServerSocketChannel还是SocketChannel,都是bootstrap,channel

(2)提供了更加强大和丰富的功能,各种编解码处理器,TCP Socket缓冲区大小的设计。滑动窗口大小的设计。你像你之前使用NIO编程,你怎么设置这些操作系统级别的参数?设置不了的。但是netty这里可以,后续都会一一讲到这些API

这些增强的功能也就是我们需要关注的那些API实现

14.1 netty封装之后的常见API

14.1.1 writeAndFlush(Object msg)

以前我们在NIO的时候,写出数据用的是NIO的write(ByteBuffer byteBuffer),你要写一个字符串出去,还得把字符串转为ByteBuffer作为参数传递进去,才能写出去。现在Netty里面的writeAndFlush(Object msg)里面接收的是一个Object对象。你不用区分任何的类型,直接写就行了。

而且这个方法你一看名字就知道,写入并且刷新,意思就是写入到操作系统内核的Socket缓冲区后立马刷新通过网络发出。所以当你使用这个方法发数据时,服务端立马能接收到的。

但是注意:netty中网络IO都会异步开启一个新线程(NioEventLoop)来执行

14.1.2 write(Object msg)

这个方法也是接收的Object,不用转为ByteBuffer类型就能发送,不用问你在网络传输不能直接传字符串,肯定是要转的,netty其实也就是封装了NIO,所以他底层肯定给你转成了ByteBuffer,只是不用你做这个操作了,参数接收的更统一了。

但是这个方法就只是写出了,写到了操作系统socket缓冲区里面,除非缓冲区写满了,不然他不会写完就发出数据,你需要接一步,channel.flush()才能把数据及时刷出去。

14.1.3 close

以前我们刚开始写netty代码的时候是如下这样:

// 客户端连接端口到服务端,连接到了返回的ChannelFuture是个异步操作。,这里是新的线程处理的连接

ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));

// 在这里阻塞,等到连接上了,成功了在返回,然后往下走

connect.sync();

// 连接上了,在这个连接获取通道,也就是当初NIO的SC。此时这里是main线程

Channel channel = connect.channel();

// 往服务端写数据,往出写

channel.writeAndFlush("hello netty");

channel发送完了数据之后就不管了,实际上这里之后程序还没有结束,因为启动了一些socket程序,是后台线程,导致这个程序不会结束。

但是实际上我们开发可能会要结束这个程序,就需要channel.close();加一句。

这句代码会帮你关闭一些后台资源。

但是这个时候有个需求,我们在执行完close之后要执行一些业务逻辑,这时候我们第一反应可能就是这样,有两种方案:1.同步阻塞等待close关闭完成 2.监听

  • 方案1
public class MyNettyClient {private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap() ;NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group);bootstrap.channel(NioSocketChannel.class);bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new StringEncoder());}});ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));future.sync();Channel channel = future.channel();channel.writeAndFlush("leomessi");System.out.println("MyNettyClient.main");ChannelFuture close = channel.close();close.sync();log.info("当channel关闭后,执行一些业务逻辑.....");}
}
  • 方案2:

  • 引入LoggingHandler

  • 测试

其实同步阻塞和监听都无所谓,测试一个即可,这里以监听方案进行测试:

  • 测试存在的问题和细节

你debug测试的时候,把断点处改为Thread,这样断点调试阻塞的是当前main线程,而不是所有线程。因为netty针对所有网络IO都是异步开启新线程的方式进行处理,你要是设置为ALL阻塞所有线程,你异步开启的新线程是不是也阻塞住了,那么你怎么write写出,怎么flush刷新给出去呢????都阻塞了,啥都别想了。。

  • 引入LoggingHandler打印输出日志,使用Grep这一功能

选中然后右键

看到如下日志:

14.1.4 优雅的关闭 shutdownGracefully()

我们可以看到一个现象:

我们看到日志显示事件和通道实际上被关闭了。但是左边的红点显示程序还没被关闭。这个不难理解, 我们上面用的是bootstrap.group(new NioEventLoopGroup());,这个NioEventLoopGroup被创建出来,其实是个线程池,这个线程池还活着呢,所以还有后台线程在,你这个程序结束不掉。netty认为直接结束不安全,万一除了通道事件还有别的线程在做任务,这样直接全结束不安全。

  • 补充

因为我们new NioEventLoopGroup(),按照netty以及本计算机核数来计算的话,创建出32个线程。虽然说NioEventLoopGroup的线程是被用作网络IO做异步线程开启。但是假设说如果使用得当,当我们处理普通任务时,也可以使用NioEventLoopGroup中的线程去做异步(只需要把任务提交给NioEventLoop即可)。

当channel.close()调用后,close方法只占用一个NioEventLoop线程,其他31个线程就算不都运行着,其中可能还有很多线程是有未执行完的任务的,所以如果channel.close()后就关闭整个client的所有线程,那么太不地道,太绝户了。所以使用的是eventLoopGroup.shutdownGracefully(),优雅的关闭,当所有的线程(32个线程)都结束处理之后,才会真正关闭client

  • 所以引入优雅的关闭:eventLoopGroup.shutdownGracefully()

测试:

  • 那么问题来了,如果我们不关闭通道,直接优雅关闭会咋样。试一下:
package com.messi.netty_core_02.netty04;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.net.InetSocketAddress;public class MyNettyClient {private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap() ;NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group);bootstrap.channel(NioSocketChannel.class);bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new StringEncoder());}});ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));future.sync();Channel channel = future.channel();channel.writeAndFlush("leomessi");System.out.println("MyNettyClient.main");//        ChannelFuture close = channel.close();//        close.sync();
//
//        log.info("当channel关闭后,执行一些业务逻辑.....");
//        close.addListener(new GenericFutureListener<Future<? super Void>>() {
//            @Override
//            public void operationComplete(Future<? super Void> future) throws Exception {
//                log.info("当channel关闭后,执行一些业务逻辑.....");
//            }
//        });
//        //优雅的关闭group.shutdownGracefully();}}

测试结果:

这个优雅的关闭同样会close关闭channel,但是不会打印输出,可能底层源码走的逻辑不一样,但是肯定是把channel关闭了,因为状态已经显示为INACTIVE。为什么会这样?因为对于channel的操作,比如说连接或关闭channel通道,其实都是NioEventLoopGroup其中一个线程去做的,那么优雅的关闭是关闭该线程组中的所有,只不过是等待他们都做完自己的线程任务再优雅的关闭,最终都会关闭。

实际就是结束的客户端,既然是优雅关闭,他关闭的实际就是等待EventLoopGroup里面所有线程的任 务都结束才完成关闭,实际上这就是优雅关闭的定义,后面好好研究一下优雅关闭(kill -15和kill -9的故事,以及jdk中对于Linux关闭事件的捕获实现)。

注释:kiil -9 +进程号A :强制关闭进程号A所对应的进程,即使你进程A中还有很多线程没有执行完毕,也得强制关闭掉。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯Python】试题 算法训练 数组移动

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 初始数组A[N]中为1,2,..,N&#xff0c;N个数字&#xff0c;现要进行M次操作&#xff0c;每次操作给定一个数字i&#xff0c;记…

【C语言】通过socket看系统调用过程

一、通过socket看系统调用过程 在Linux操作系统中&#xff0c;系统调用是用户空间与内核空间之间交互的一种方式。当一个应用程序需要执行操作系统级别的任务时&#xff0c;比如创建一个网络套接字&#xff08;socket&#xff09;&#xff0c;它必须通过系统调用请求内核来执行…

物联网和工业4.0

在当今这个快速发展的技术时代&#xff0c;物联网&#xff08;IoT&#xff09;和工业4.0成为了推动全球进入新工业时代的两大驱动力。对于刚入行的人来说&#xff0c;深入理解这两个概念及其背后的技术原理&#xff0c;对于把握未来的职业机会至关重要。 物联网&#xff0c;简…

点云标注工具

目录 3d手势识别 c 3d关键点&#xff0c;Bounding Box Labels Rectangle Labels KITTI 3D Ground Truth Annotator c标注工具 3d手势识别 GitHub - 99xtaewoo/Automated-Hand-3D-pose-annotation-Tool: Automated Hand 3D pose annotation Tool c 3d关键点&#xff0c;Bou…

【数据结构和算法】--- 基于c语言排序算法的实现(2)

目录 一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare法1.2.2 挖坑法1.2.3 前后指针法 1.3 快速排序优化1.3.1 三数取中法选key1.3.2 递归到小的子区间使用插入排序 1.4 快排非递归版 二、归并排序2.1 归并排序2.1.1 递归版2.1.2 非递归版 一、交换排序 基本思想&#xff1a…

锐捷(十九)锐捷设备的接入安全

1、PC1的IP地址和mac地址做全局静态ARP绑定; 全局下&#xff1a;address-bind 192.168.1.1 mac&#xff08;pc1&#xff09; G0/2:ip verify source port-securityarp-check 2、PC2的IP地址和MAC地址做全局IPMAC绑定&#xff1a; Address-bind 192.168.1.2 0050.7966.6807Ad…

C#在窗体正中输出文字以及输出文字的画刷使用

为了在窗体正中输出文字&#xff0c;需要获得输出文字区域的宽和高&#xff0c;这使用MeasureString方法&#xff0c;方法返回值为Size类型&#xff1b; 然后计算输出的起点的x和y坐标&#xff0c;就可以输出了&#xff1b; using System; using System.Collections.Generic; …

Fink CDC数据同步(四)Mysql数据同步到Kafka

依赖项 将下列依赖包放在flink/lib flink-sql-connector-kafka-1.16.2 创建映射表 创建MySQL映射表 CREATE TABLE if not exists mysql_user (id int,name STRING,birth STRING,gender STRING,PRIMARY KEY (id) NOT ENFORCED ) WITH (connector mysql-cdc,hostn…

从REPR设计模式看 .NET的新生代类库FastEndpoints的威力

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !序言 又到了一年年末,春节将至…

Vue源码系列讲解——模板编译篇【一】(综述)

目录 1. 前言 2. 什么是模板编译 3. 整体渲染流程 4. 模板编译内部流程 4.1 抽象语法树AST 4.2 具体流程 5. 总结 1. 前言 在前几篇文章中&#xff0c;我们介绍了Vue中的虚拟DOM以及虚拟DOM的patch(DOM-Diff)过程&#xff0c;而虚拟DOM存在的必要条件是得先有VNode&…

2024 CKS 题库 | 4、RBAC - RoleBinding

CKS 题库 4、RBAC - RoleBinding Context 绑定到 Pod 的 ServiceAccount 的 Role 授予过度宽松的权限。完成以下项目以减少权限集。 Task 一个名为 web-pod 的现有 Pod 已在 namespace db 中运行。 编辑绑定到 Pod 的 ServiceAccount service-account-web 的现有 Role&#…

ChatGPT高效提问—prompt常见用法(续篇四)

ChatGPT高效提问—prompt常见用法&#xff08;续篇四&#xff09; 1.1 知识生成 ​ 知识生成是指使用自然语言处理技术&#xff0c;通过ChatGPT等AI模型生成与特定主题相关的知识、文本或回答。在知识生成过程中&#xff0c;模型接收prompt输入的问题、指令或上下文信息&…

HTTP 超文本传送协议

1 超文本传送协议 HTTP HTTP 是面向事务的 (transaction-oriented) 应用层协议。 使用 TCP 连接进行可靠的传送。 定义了浏览器与万维网服务器通信的格式和规则。 是万维网上能够可靠地交换文件&#xff08;包括文本、声音、图像等各种多媒体文件&#xff09;的重要基础。 H…

24个已知403绕过方法的利用脚本

介绍 一个简单的脚本&#xff0c;仅供自用&#xff0c;用于绕过 403 在curl的帮助下使用24个已知的403绕过方法 它还可用于比较各种条件下的响应&#xff0c;如下图所示 用法 ./bypass-403.sh https://example.com admin ./bypass-403.sh website-here path-here 安装 git …

新春快乐(烟花、春联)【附源码】

新春快乐 一&#xff1a; C语言 -- 烟花二&#xff1a;Python -- 春联三&#xff1a;Python -- 烟花四&#xff1a;HTML -- 烟花 一&#xff1a; C语言 – 烟花 运行效果&#xff1a; #include <graphics.h> #include <math.h> #include <time.h> #include…

深入学习《大学计算机》系列之第1章 1.7节——图灵机的一个例子

一.欢迎来到我的酒馆 第1章 1.7节&#xff0c;图灵机的一个例子。 目录 一.欢迎来到我的酒馆二.图灵机2.1 艾伦-图灵简介2.2 图灵机简介 三.图灵机工作原理3.1 使用图灵机打印二进制数3.2 图灵机工作原理总结 四.总结 二.图灵机 本节内容主要介绍计算机科学之父——艾伦-图灵、…

云卷云舒:论超级数据库、算网数据库、智算数据库

笔者大胆提出一种“超级数据库”的概念设想。 一、超级能力 就像当初提出“超级计算机”一样&#xff0c;我们是否同样可以提出“超级数据库”的概念呢&#xff1f;当然不是不可以。 二、超级计算机 我们回忆一下“超级计算机”的发展之路&#xff0c;大致经过了如下几个环…

详细分析Redis中数值乱码的根本原因以及解决方式

目录 前言1. 问题所示2. 原理分析3. 拓展 前言 对于这方面的相关知识推荐阅读&#xff1a; Redis框架从入门到学精&#xff08;全&#xff09;Java关于RedisTemplate的使用分析 附代码java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; …

单片机学习笔记---蜂鸣器播放提示音音乐(天空之城)

目录 蜂鸣器播放提示音 蜂鸣器播放音乐&#xff08;天空之城&#xff09; 准备工作 主程序 中断函数 上一节讲了蜂鸣器驱动原理和乐理基础知识&#xff0c;这一节开始代码演示&#xff01; 蜂鸣器播放提示音 先创建工程&#xff1a;蜂鸣器播放提示音 把我们之前模块化的…

Python 错误及其解决方法

Python 是一种易于学习的编程语言&#xff0c;但初学者在学习和使用 Python 的过程中难免会遇到一些错误。以下是一些常见的 Python 错误及其解决方法&#xff1a; 1. 语法错误&#xff08;SyntaxError&#xff09;&#xff1a; python # 错误示例 print("Hello, World!…