【吃透Java手写】6-Netty-NIO-BIO-简易版

Netty

1 BIO&NIO模型

在这里插入图片描述

1.1 BIO

在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端线程会等待请求结束后才继续执行。

在这里插入图片描述

1.1.1 BIO-demo

1.1.1.1 SocketClient

创建com.sjb.bio.BIOSocketServer

public class BIOSocketServer {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(9000);while (true) {System.out.println("等待连接...");// 阻塞方法Socket clientSocket = serverSocket.accept();System.out.println("有客户端连接了...");handler(clientSocket);}}private static void handler(Socket clientSocket) {byte[] bytes = new byte[1024];System.out.println("准备read...");// 接收客户端的数据,阻塞方法,没有数据可读时就阻塞try {int read = clientSocket.getInputStream().read(bytes);System.out.println("read完毕...");if (read != -1) {System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));}//clientSocket.getOutputStream().write("HelloClient".getBytes());//clientSocket.getOutputStream().flush();} catch (IOException e) {e.printStackTrace();}}
}

测试一下,运行起来,用命令行

telnet localhost 9000

ctrl+】进入命令行模式

在这里插入图片描述

发送

send hello csdn-bblb

在这里插入图片描述

很基础的socket代码,没什么好说的。如果使用C语言完成网络编程的话这块应该非常熟悉。

但是我们这个代码有点问题,就是并发能力太弱了,自然而然的想到用线程来进行处理,创建线程或者线程池用来处理这个问题。

1.1.1.2 为SocketClient添加线程池

创建定长线程池

public class BIOSocketServer {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(9000);//创建定长线程池ExecutorService executorService = Executors.newFixedThreadPool(20);while (true) {System.out.println("等待连接...");// 阻塞方法Socket clientSocket = serverSocket.accept();System.out.println("有客户端连接了...");executorService.execute(new SocketProcessor(clientSocket));}}
}

executorService.execute(new SocketProcessor(clientSocket));new一个SocketProcessor对象再调用execute执行线程逻辑

创建com.sjb.bio.SocketProcessor处理socket逻辑

public class SocketProcessor implements Runnable{private Socket socket;public SocketProcessor(Socket socket) {this.socket = socket;}@Overridepublic void run() {processSocket(socket);}private void processSocket(Socket clientSocket) {//处理socketbyte[] bytes = new byte[1024];System.out.println("准备read...");// 接收客户端的数据,阻塞方法,没有数据可读时就阻塞try {int read = clientSocket.getInputStream().read(bytes);System.out.println("read完毕...");if (read != -1) {System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));}//clientSocket.getOutputStream().write("HelloClient".getBytes());//clientSocket.getOutputStream().flush();} catch (IOException e) {e.printStackTrace();}}
}

测试运行三个客户端

在这里插入图片描述

显示客户端端口不一样,说明我们的线程还是有用的。

但是当我们在处理业务逻辑时,会遇到一些类似的问题C10K或者C10M,意思是瞬间有10K或者10M的线程需要并发处理,这时候我们的无论我们选择怎样的线程池,都会面临创建线程内存爆掉的问题。这时候就引入我们的NIO了。

1.2 NIO

  • 同步非阻塞的IO(non-blocking IO)

    • 三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
    • NIO面向缓冲区编程 ,增加了操作的灵活性。
    • NIO读和写都是非阻塞的,当 读操作–>当前资源未就绪 或者 写操作–>当前未完全写入 时,当前线程都可以做其他处理。也就是说,多个请求发过来,不必分配相同线程数去处理请求(例如1000个请求,根据实际情况可以分配5-10个线程处理即可)。
  • NIO 和 BIO 对比

    • BIO 以流的方式处理数据,而 NIO 以缓冲区的方式处理数据,缓冲区 I/O 的效率比流 I/O 高很多
    • BIO 是阻塞的,NIO则是非阻塞的
    • BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据 总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的 事件(比如:连接请求, 数据到达等),因此使用单个线程就可以监听多个客户端通道。

1.2.1 NIO-demo

1.2.2.1 NIOServer

创建com.sjb.nio.NIOServer

public class NIOServer {//保存客户端连接private static List<SocketChannel> clientList = new ArrayList<>();public static void main(String[] args) throws IOException {//创建NIO ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(9000));//设置ServerSocketChannel为非阻塞serverSocketChannel.configureBlocking(false);System.out.println("服务启动成功");while (true){//非阻塞模式accept方法不会阻塞,否则会阻塞//NIO的非阻塞就是通过设置为非阻塞模式,底层调用了Linux内核的accept函数SocketChannel socketChannel = serverSocketChannel.accept();if (socketChannel != null){ //有客户端连接System.out.println("连接成功");//设置SocketChannel为非阻塞socketChannel.configureBlocking(false);//保存客户端连接在List中clientList.add(socketChannel);}//遍历连接进行数据读取Iterator<SocketChannel> iterator= clientList.iterator();while (iterator.hasNext()){SocketChannel sc = iterator.next();//读取数据ByteBuffer buffer = ByteBuffer.allocate(128);//设置为非阻塞模式read方法不会阻塞int len = sc.read(buffer);//如果有数据,把数据打印出来if (len > 0){System.out.println("接收到消息:" + new String(buffer.array()));}else if (len == -1){ //如果客户端断开连接,把SocketChannel从List中去掉iterator.remove();System.out.println("客户端断开连接");}}}}
}

这里非阻塞就可以让一个进程处理很多客户端连接,但是也有缺点:

  • 没有客户端连接的时候,在空转
  • 有和多客户端连接的时候,每次都要遍历list去找到谁发的read。

我们需要改成事件驱动。

1.2.2.2 NIOSelectorServer

在这里插入图片描述

创建com.sjb.nio.NIOSelectorServer

public class NIOSelectorServer {public static void main(String[] args) throws IOException {//创建NIO ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(9000));//设置ServerSocketChannel为非阻塞serverSocketChannel.configureBlocking(false);//打开Selector处理Channel,即创建epollSelector selector = Selector.open();//把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务启动成功");while(true){//阻塞等待需要处理的事件发生selector.select();//获取selector中注册的全部事件的 SelectionKey 实例Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();//遍历SelectionKey对事件进行处理while (iterator.hasNext()){SelectionKey key = iterator.next();//如果是OP_ACCEPT事件,则进行连接获取和事件注册if (key.isAcceptable()){ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel socketChannel = server.accept();socketChannel.configureBlocking(false);//这里只注册了读事件,如果需要给客户端发送数据可以注册写事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("客户端连接成功");}else if (key.isReadable()){ //如果是OP_READ事件,则进行读取和打印SocketChannel client = (SocketChannel) key.channel();client.configureBlocking(false);ByteBuffer byteBuffer = ByteBuffer.allocate(128);int len = client.read(byteBuffer);//如果有数据,把数据打印出来if (len > 0){System.out.println("接收到消息:" + new String(byteBuffer.array()));}else if (len == -1){ //如果客户端断开连接,关闭SocketSystem.out.println("客户端断开连接");client.close();}}//从事件集合里删除本次处理的key,防止下次select重复处理iterator.remove();}}}
}

2 Epoll非阻塞反应堆

2.1 Epoll

2.1.1 LT 水平触发

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你

2.1.2 ET 边缘触发

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你

2.1.3 socket文件描述符

关于socket中的阻塞与非阻塞,首先明白的是,阻塞与非阻塞是文件(文件描述符)的性质,而不是函数的性质

  • 客户端:

    • connfd:客户端创建socket时候得到的文件描述符。connect使用这个描述符主动发起链接。
  • 服务端:

    • listenfd:创建socket得到的文件描述符,同时bind和listen使用的也是这个文件描述符
    • clientfd:调用accept得到的文件描述符,也就是用于通信的文件描述符
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK);                                            //将socket设为非阻塞

2.2 Epoll非阻塞过程

简单来说就是 epoll的ET模式+非阻塞轮询+多态下针对不同的文件描述符的回调函数,反应堆不仅要监听读事件,也要监听写事件。

这里处理的事件是大小写转换。

2.2.1 服务器端

第一阶段:

  1. lfd=socket()创建lfd套接字
  2. bind()绑定地址
  3. listen()设置监听上限
  4. epoll_create()创建监听红黑树()
  5. epoll_ctl()把lfd加入红黑树
  6. while(1) 服务器端上线等待连接。

第二阶段:

  1. epoll_wait()服务器监听
  2. 有事件发生,epoll_wait()返回监听满足数组
  3. 一旦有事件发生则lfd一定在满足数组中,lfd进行accept()
  4. 用lfd进行accept()返回的连接套接字cfd放到红黑树中
  5. 执行读操作—>进行大小写转换操作—>把cfd节点的属性从EPOLLIN变为EPOLLOUT(可读变可写)
  6. 再把cfd重新挂上红黑树去监听写事件
  7. 等待epoll_wait()返回—>有返回说明能写—>执行写操作
  8. 把cfd从红黑树中拿下来—>再把cfd节点的属性从EPOLLOUT变为EPOLLIN
  9. 再把cfd重新挂上红黑树去监听读事件—>epoll_wait()服务器监听。

2.2.2 挂上红黑树的操作

void eventadd(int efd, int events, struct myevent_s *ev)
{struct epoll_event epv = {0, {0}};int op;epv.data.ptr = ev;epv.events = ev->events = events;       //EPOLLIN 或 EPOLLOUTif (ev->status == 0) {                                          //已经在红黑树 g_efd 里op = EPOLL_CTL_ADD;                 //将其加入红黑树 g_efd, 并将status置1ev->status = 1;}if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       //实际添加/修改printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);elseprintf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);return ;
}

epoll_ctl 是用来控制 epoll 实例的函数,可以用于添加、修改或删除文件描述符和其对应的事件。

  • efd:epoll 实例的文件描述符,通过 epoll_create 或者 epoll_create1 函数创建得到。
  • op:操作类型,可以是 EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
    • EPOLL_CTL_ADD:添加一个新的文件描述符到 epoll 实例中。
    • EPOLL_CTL_MOD:修改一个已经在 epoll 实例中的文件描述符的事件属性。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • ev->fd:要进行操作的文件描述符。
  • &epv:指向 struct epoll_event 结构体的指针,用于设置文件描述符的事件属性。

2.3 select、poll、epoll的区别

在这里插入图片描述

3 响应式编程

为什么Redis是单线程却能支持上万的并发?是因为Redis也是多路复用的机制。

3.1 Netty

Netty 是一个 NIO 客户端服务器框架,可快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。

“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty 经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP 以及各种基于二进制和文本的旧式协议)的实施经验。结果,Netty 成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。

在这里插入图片描述

3.1.1 核心组件

Channel

Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。

EventLoop 与 EventLoopGroup

EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。

EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。

Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。

一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。

ServerBootstrap 与 Bootstrap

Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。

  • Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。

  • ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。

ChannelHandler 与 ChannelPipeline

ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。

ChannelFuture

Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。

Netty 的异步编程模型都是建立在 Future 与回调概念之上

3.2 Netty源码

3.2.1 NettyServer

public class NettyServer {public static void main(String[] args) throws InterruptedException {//创建两个线程组bossGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍//bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成EventLoopGroup bossGroup =new NioEventLoopGroup(1);EventLoopGroup workerGroup =new NioEventLoopGroup(10);try{//创建服务器端的启动对象ServerBootstrap serverBootstrap = new ServerBootstrap();//使用链式编程来配置参数serverBootstrap.group(bossGroup, workerGroup) //设置两个线程组//使用NioServerSocketChannel作为服务器的通道实现.channel(NioServerSocketChannel.class)//初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接//多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理.option(ChannelOption.SO_BACKLOG, 1024)//创建通道初始化对象,设置初始化参数.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {//对workerGroup的SocketChannel设置处理器ch.pipeline().addLast(new NettyServerHandler());}});System.out.println("...服务器 is ready...");//绑定一个端口并且同步,生成一个ChannelFuture对象,通过isDone()等方法可以判断异步事件的执行情况//启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();System.out.println("...服务器 is starting...");//对关闭通道进行监听channelFuture.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

3.2.2 NettyServerHandler

ch.pipeline().addLast(new NettyServerHandler());通过这个来添加处理方法进行回调

public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("客户端连接通道建立成功...");}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {System.out.println("Client Address ====== " + ctx.channel().remoteAddress());ByteBuf buf = (ByteBuf) msg;System.out.println("Client Message ====== " + buf.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

在这里插入图片描述

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

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

相关文章

TikTok Shop认知课 打通TK小店全流程

资料 001-先导课.mp4 002-如何用思维导图工具做课程笔记.mp4 003-TTS入驻模式.mp4 004-如何获取店铺.mp4 005-TTS店铺注册全流程,mp4 006-店铺整体运营思路.mp4 007-运营的几个误区.mp4 008-新店起店准备工作,mp4 009-规店铺风控注意事项,mp4 010-店铺基础设置之店铺…

基于火山引擎云搜索的混合搜索实战

在搜索应用中&#xff0c;传统的 Keyword Search 一直是主要的搜索方法&#xff0c;它适合精确匹配查询的场景&#xff0c;能够提供低延迟和良好的结果可解释性&#xff0c;但是 Keyword Search 并没有考虑上下文信息&#xff0c;可能产生不相关的结果。最近几年&#xff0c;基…

单文件组件,为什么要使用 SFC

介绍 Vue 的单文件组件 (即 *.vue 文件&#xff0c;英文 Single-File Component&#xff0c;简称 SFC) 是一种特殊的文件格式&#xff0c;使我们能够将一个 Vue 组件的模板、逻辑与样式封装在单个文件中。下面是一个单文件组件的示例&#xff1a; <script setup> impor…

优秀博士学位论文分享:复杂场景下高精度有向目标检测的研究

优秀博士学位论文代表了各学科领域博士研究生研究成果的最高水平&#xff0c;本公众号近期将推出“优秀博士学位论文分享”系列文章&#xff0c;对人工智能领域2023年优秀博士学位论文进行介绍和分享&#xff0c;方便广大读者了解人工智能领域最前沿的研究进展。 “博士学位论…

C++11 新特性 常量表达式 constexpr

为了解决常量无法确定的问题&#xff0c;C11在新标准中提出了关键字constexpr&#xff0c;它能够有效地定义常量表达式&#xff0c;并且达到类型安全、可移植、方便库和嵌入式系统开发的目的。 一、常量的不确定性 在C11标准以前&#xff0c;我们没有一种方法能够有效地要求一…

LLama3大模型本地部署 仅需6步完成对话模型本地安装部署。附送可视化ui安装、自定义模型目录,修改模型保存地址,第三方微调模型、中文模型下载地址

本篇分为三部分 一&#xff1a;6步完成llama3大模型本地部署 二&#xff1a;8步完成llama3可视化对话界面安装 三&#xff1a;重设模型文件路径 四&#xff1a;微调模型、中文模型下载资源分享 一、LLama3 大模型本地部署安装 首先去mata官网下载ollama客户端 Ollama 选择合适…

linux 环境下 分布式文件搭建fastDFS

1.软件信息 地址&#xff1a;happyfish100 (YuQing) GitHub 1.fastdfs-master.zip 2.fastdfs-nginx-module-master.zip 3.libfastcommon-master.zip 4.libserverframe-master.zip yum install make cmake gcc gcc-c perl 2.安装libfastcommon unzip libfastcommon-mast…

MQTT_客户端安装_1.4

下载地址 MQTTX 下载 下一步直接安装即可 界面介绍

人工智能项目,如何解决大模型的数据私有化

这个问题是最近走访百家企业&#xff0c;客户问的最多的问题。人工智能是对数据集中后&#xff0c;再利用的智能化手段&#xff0c;ChatGPT还在持续的投入&#xff0c;汇集数据、训练模型&#xff0c;微软也不过是做了一个办公客户端的智能工具&#xff0c;那么行业应运之时&am…

基于CentOS-7搭建hadoop3.3.6大数据集群(保姆级教程)

目录 安装虚拟机 为hadoop用户添加权限 关闭防火墙 修改主机名以及ip地址映射 配置ip 连接xshell &#xff0c;以hadoop用户登录 创建目录并将该文件夹权限赋予hadoop用户 安装配置jdk 关闭虚拟机&#xff0c;克隆其他两个节点 修改主机名和ip地址 配置免密登录 安装…

API低代码平台介绍3-异构数据源的数据查询功能

异构数据源的数据查询功能 在上一篇文章中我们通过API平台定义了一个最基本的数据查询接口&#xff0c;本篇文章我们将上升难度&#xff0c;在原有接口的基础上&#xff0c;实现在MySQL数据库和Oracle数据库同时进行数据查询。   什么场景会需要同时对异构数据源进行查询&…

基于FPGA的NC图像质量评估verilog实现,包含testbench和MATLAB辅助验证程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 vivado2019.2和matlab2022a测试&#xff0c;结果如下&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 timescale …

【C语言习题】6.逆序输出

文章目录 1.描述输入描述&#xff1a;输出描述&#xff1a;示例图&#xff1a; 2.解题思路3.具体代码4.代码讲解 1.描述 输入10个整数&#xff0c;要求按输入时的逆序把这10个数打印出来。逆序输出&#xff0c;就是按照输入相反的顺序打印这10个数。 输入描述&#xff1a; 一…

SDL系列(三)—— SDL2.0 扩展库:SDL_image与SDL_mixer

SDL_image SDL 默认支持的&#xff0c;只能打开 BMP 格式的图片 。 然而我们常见的是 Png jpg 格式的图片&#xff0c;于是我们这节完成 SDL 借用 自带的三方库 &#xff0c;来 完成加载渲染 png 等其他图片格式。 SDL_image 简介 使用 SDL_image &#xff0c;您…

[笔试训练](二十三)067:打怪068:字符串分类069:城市群数量

目录 067:打怪 068:字符串分类 069:城市群数量 067:打怪 题目链接:打怪 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 直接计算结果&#xff1a; 1.一只怪物能抗几次攻击 int m(H/a)(H%a0?0:1); 2.杀死一只怪物&#xff0c;玩家要抗几次攻击 int nm-1; *3.杀死一只…

jmeter指南:JMeter 安装、配置和性能测试

使用 JMeter 进行性能测试 1. Java 版本要求 JMeter 要求与 Java 8 或更高版本兼容。为了确保安全性和性能&#xff0c;建议安装最新次要版本的主要 Java 版本。鉴于 JMeter 仅使用标准 Java API&#xff0c;如果由于 JRE 实现问题而无法运行 JMeter&#xff0c;请不要提交错…

VMware Workstation 安装CentOS Linux操作系统

1.我们已经下载好VMware 创建新的虚拟机 2.选择典型 3.安装程序光盘映像文件 4.配置用户名密码 5.命名虚拟机&#xff0c;并确定位置 6.如图所示设置 7.等待&#xff08;时间会有点久&#xff09; 8.输入密码登入账号

工单系统有哪些?

市面上的工单系统真的非常多&#xff0c;一个个列举肯定说不完&#xff0c;我大致给它们按照不同的依据&#xff0c;进行了一下分类&#xff1a; 1、按部署方式分类&#xff1a; 本地化部署工单系统&#xff1a;适用于对数据安全性要求较高的企业&#xff0c;需要企业在本地服…

uniapp小程序控制页面元素滚动指定距离

要实现页面元素滚动&#xff0c;最好还是使用 scroll-view 来实现&#xff0c;官方文档地址&#xff1a;scroll-view | uni-app官网 通过设置scroll事件来实现滚动监听&#xff0c;当滚动的元素的时候&#xff0c;就会触发这个事件&#xff0c;并且事件里面包含有滚动距离&…

PCIE协议-2-事务层规范-Virtual Channel (VC) Mechanism

2.5 虚拟通道&#xff08;VC&#xff09;机制 虚拟通道&#xff08;VC&#xff09;机制提供了对可以在整个结构中传输使用TC&#xff08;流量类别&#xff09;标签区分的流量的支持。VC的基础是独立的结构资源&#xff08;队列/缓冲区及其相关的控制逻辑&#xff09;。这些资源…