32.Netty源码之服务端如何处理客户端新建连接
highlight: arduino-light
服务端如何处理客户端新建连接
Netty 服务端完全启动后,就可以对外工作了。接下来 Netty 服务端是如何处理客户端新建连接的呢? 主要分为四步:
md Boss NioEventLoop 线程轮询客户端新连接 OP_ACCEPT 事件; 构造 初始化Netty 客户端 NioSocketChannel; 注册 Netty 客户端 NioSocketChannel 到 Worker 工作线程中; 从 Worker group 选择一个 eventLoop 工作线程;注册到选择的eventLoop的Selector 注册 OP_READ 事件到 NioSocketChannel 的事件集合。
下面我们对每个步骤逐一进行简单的介绍。
接收新连接
bossGroup的EventLoop是一个线程是一个线程是一个线程。所以等服务器端启动起来以后就会执行线程的run方法逻辑。
java protected void run() { for (;;) { try { try { switch (selectStrategy.calculateStrategy (selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.BUSY_WAIT: case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 轮询 I/O 事件 if (wakenUp.get()) { selector.wakeup(); } default: } } catch (IOException e) { rebuildSelector0(); handleLoopException(e); continue; } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { try { // 处理 I/O 事件 processSelectedKeys(); } finally { runAllTasks(); // 处理所有任务 } } else { final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); // 处理 I/O 事件 } finally { final long ioTime = System.nanoTime() - ioStartTime; // 处理完 I/O 事件,再处理异步任务队列 runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } } catch (Throwable t) { handleLoopException(t); } try { if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { return; } } } catch (Throwable t) { handleLoopException(t); } } }
NioEventLoop#processSelectedKeys
java // processSelectedKeys private void processSelectedKeys() { if (selectedKeys != null) { //不用JDK的selector.selectedKeys(), 性能更好(1%-2%),垃圾回收更少 processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }
服务器端监听 OP_ACCEPT 事件读取消息
NioEventLoop#processSelectedKeysOptimized
Netty 中 Boss NioEventLoop 专门负责接收新的连接,关于 NioEventLoop 的核心源码我们下节课会着重介绍,在这里我们只先了解基本的处理流程。当客户端有新连接接入服务端时,Boss NioEventLoop 会监听到 OP_ACCEPT 事件,源码如下所示:
```java private void processSelectedKeysOptimized() { for (int i = 0; i < selectedKeys.size; ++i) { final SelectionKey k = selectedKeys.keys[i]; // null out entry in the array to allow to have it GC'ed once the Channel close // See https://github.com/netty/netty/issues/2363 selectedKeys.keys[i] = null;
//呼应于channel的register中的this: //selectionKey = javaChannel().register(eventLoop()// .unwrappedSelector(), 0, this);final Object a = k.attachment();//因为客户端和服务器端的都继承自AbstractNioChannelif (a instanceof AbstractNioChannel) {//会进入判断processSelectedKey(k, (AbstractNioChannel) a);} else {@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (needsToSelectAgain) {// null out entries in the array to allow to have it GC'ed once the Channel close// See https://github.com/netty/netty/issues/2363selectedKeys.reset(i + 1);selectAgain();i = -1;}}
}
```
NioEventLoop#processSelectedKey
```java private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); if (!k.isValid()) { final EventLoop eventLoop; try { eventLoop = ch.eventLoop(); } catch (Throwable ignored) { return; }
if (eventLoop != this || eventLoop == null) {return;}// close the channel if the key is not valid anymoreunsafe.close(unsafe.voidPromise());return;}try {int readyOps = k.readyOps();if ((readyOps & SelectionKey.OP_CONNECT) != 0) {int ops = k.interestOps();ops &= ~SelectionKey.OP_CONNECT;k.interestOps(ops);unsafe.finishConnect();}if ((readyOps & SelectionKey.OP_WRITE) != 0) {ch.unsafe().forceFlush();}//处理读请求(断开连接)或接入连接if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT))!= 0 || readyOps == 0) {//开始处理请求 服务器端处理的是OP_ACCEPT 接收新连接 unsafe.read();}} catch (CancelledKeyException ignored) {unsafe.close(unsafe.voidPromise());}
}
```
NioMessageUnsafe#read
NioServerSocketChannel 所持有的 unsafe 是 NioMessageUnsafe 类型。
我们看下 NioMessageUnsafe.read() 方法中做了什么事。
```java public void read() { assert eventLoop().inEventLoop(); final ChannelConfig config = config(); final ChannelPipeline pipeline = pipeline(); final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); allocHandle.reset(config); boolean closed = false; Throwable exception = null; try { try {
do {//readBuf一开始是一个空List//while 循环不断读取 Buffer 中的数据//创建底层SocketChannel并封装为NioSocketChannel放到readBuf返回1int localRead = doReadMessages(readBuf); //执行完上面的方法 readBuf放的是新创建的NioSocketChannelif (localRead == 0) {break;}if (localRead < 0) {closed = true;break;}allocHandle.incMessagesRead(localRead);//是否需要继续读 因为是建立连接 所以总共读取的字节数是0 不会继续进入循环//这里一次最多处理16个连接} while (allocHandle.continueReading());} catch (Throwable t) {exception = t;}int size = readBuf.size();//readBuf放的是新创建的NioSocketChannelfor (int i = 0; i < size; i ++) {readPending = false;// 对于服务器端NioServerSocketChannel来说 // handler有// 1.head // 2.ClientLoggingHandler // 3.ServerBootstrapAcceptor// 4.tail// 接下来就是调用服务器端的每个handler的channelRead方法 传播读取事件// 比如ClientLoggingHandler的channelRead用于打印接收到的消息到日志// 比如 serverBootStrapAcceptor的channelRead //用于向客户端的SocketChannel的pipeline添加handler//就是把服务器端方法中的childHandler都添加到客户端的NioSocketChannel的pipeline//具体看serverBootStrapAcceptor的channelRead 方法pipeline.fireChannelRead(readBuf.get(i)); }readBuf.clear();allocHandle.readComplete();// 传播读取完毕事件pipeline.fireChannelReadComplete(); // 省略其他代码
} finally {if (!readPending && !config.isAutoRead()) {removeReadOp();}
}
} ```
可以看出 read() 方法的核心逻辑就是通过 while 循环不断读取数据,然后放入 List 中,这里的数据其实就是新连接。每次最多处理16个。
需要重点跟进一下 NioServerSocketChannel 的 doReadMessages() 方法。
接前面NioMessageUnsafe#read
继续接着NioMessageUnsafe#read看
1.NioServerSocketChannel #doReadMessages
接收&&创建&初始化客户端连接
```java protected int doReadMessages(List
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent); this.ch = ch; //设置读事件到NioSocketChannel this.readInterestOp = readInterestOp; try { //非阻塞模式 ch.configureBlocking(false); } catch (IOException e) { try { ch.close(); } catch (IOException e2) { logger.warn( "Failed to close a partially initialized socket.", e2); }
throw new ChannelException("Failed to enter non-blocking mode.", e);}
}
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException { try { return AccessController.doPrivileged(new PrivilegedExceptionAction () { @Override public SocketChannel run() throws IOException { // 非阻塞模式下,没有连接请求时,返回null return serverSocketChannel.accept(); } }); } catch (PrivilegedActionException e) { throw (IOException) e.getCause(); } } ```
这时就开始执行第二个步骤:构造 Netty 客户端 NioSocketChannel。Netty 先通过 JDK 底层的 accept() 获取 JDK 原生的 SocketChannel,然后将它封装成 Netty 自己的 NioSocketChannel。
新建 Netty 的客户端 Channel 的实现原理与上文中我们讲到的创建服务端 Channel 的过程是类似的,只是服务端 Channel 的类型是 NioServerSocketChannel,而客户端 Channel 的类型是 NioSocketChannel。
NioSocketChannel 的创建同样会完成几件事:创建核心成员变量 id、unsafe、pipeline;
注册 SelectionKey.OP_READ 事件;设置 Channel 的为非阻塞模式;新建客户端 Channel 的配置。
成功构造客户端 NioSocketChannel 后,接下来会通过 pipeline.fireChannelRead() 触发 channelRead 事件传播。对于服务端来说,此时 Pipeline 的内部结构如下图所示。
2.pipeline.fireChannelRead
上文中我们提到了一种特殊的处理器 ServerBootstrapAcceptor,在下面它就发挥了重要的作用。channelRead 事件会传播到 ServerBootstrapAcceptor.channelRead() 方法,channelRead() 会将客户端 Channel 分配到工作线程组中去执行。具体实现如下:
触发服务器端hanlder#channelRead
ServerBootstrapAcceptor#channelRead
java //ServerBootstrapAcceptor负责接收客户端连接 创建连接后,对连接的初始化工作。 // ServerBootstrapAcceptor.channelRead() 方法 public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; //childHandler是我们自定义的EchoServer的代理类 child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); setAttributes(child, childAttrs); try { // 注册客户端 Channel到工作线程组 //1.MultithreadEventLoopGroup#register childGroup.register(child).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { forceClose(child, future.cause()); } } }); } catch (Throwable t) { forceClose(child, t); } }
ServerBootstrapAcceptor 开始就把 msg 强制转换为 Channel。难道不会有其他类型的数据吗?
因为 ServerBootstrapAcceptor 是服务端 Channel 中一个特殊的处理器,而服务端 Channel 的 channelRead 事件只会在新连接接入时触发,所以这里拿到的数据都是客户端新连接。
register():注册客户端 Channel
java //MultithreadEventLoopGroup#register //从workGroup中选择一个EventLoop注册到channel @Override public ChannelFuture register(Channel channel) { return next().register(channel); }
```java //io.netty.channel.nio.AbstractChannel#register0 private void register0(ChannelPromise promise) { try { if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } boolean firstRegistration = neverRegistered; //绑定选择器 doRegister(); neverRegistered = false; registered = true; //给客户端添加处理器 pipeline.invokeHandlerAddedIfNeeded(); safeSetSuccess(promise); pipeline.fireChannelRegistered();
//NioServerSocketChannel的注册不会走进下面if(isActive())//NioSocketChannel可以走进去if(isActive())。因为accept后就active了。if (isActive()) {if (firstRegistration) {//第一次注册需要触发pipeline上的hanlder的read事件//实际上就是注册OP_ACCEPT/OP_READ事件:创建连接或者读事件//首先会进入DefaultChannelPipeLine的read方法pipeline.fireChannelActive();} else if (config().isAutoRead()) {//第二次注册的时候beginRead();}}} catch (Throwable t) {// Close the channel directly to avoid FD leak.closeForcibly();closeFuture.setClosed();safeSetFailure(promise, t);}}
```
客户端SocketChannel绑定selector
AbstractNioChannel#doRegister
java //io.netty.channel.nio.AbstractNioChannel#doRegister @Override protected void doRegister() throws Exception { boolean selected = false; for (;;) { try { logger.info("initial register: " + 0); //这里的事件类型仍然是0 //attachement是NioSocketChannel selectionKey = javaChannel().register (eventLoop().unwrappedSelector(), 0, this); return; } catch (CancelledKeyException e) { if (!selected) { // Force the Selector to select now as the "canceled" //SelectionKey may still be // cached and not removed because no //Select.select(..) operation was called yet. eventLoop().selectNow(); selected = true; } else { // We forced a select operation on the selector before but the SelectionKey is still cached // for whatever reason. JDK bug ? throw e; } } } }
DefaultChannelPipeline.HeadContext#read
java //io.netty.channel.DefaultChannelPipeline.HeadContext#read @Override public void read(ChannelHandlerContext ctx) { //实际上就是注册OP_ACCEPT/OP_READ事件:创建连接或者读事件 unsafe.beginRead(); }
```java @Override public final void beginRead() { assertEventLoop();
if (!isActive()) {return;}try {doBeginRead();} catch (final Exception e) {invokeLater(new Runnable() {@Overridepublic void run() {pipeline.fireExceptionCaught(e);}});close(voidPromise());}}
```
```java @Override protected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { return; }
readPending = true;final int interestOps = selectionKey.interestOps();//super(parent, ch, SelectionKey.OP_READ);//假设之前没有监听readInterestOp,则监听readInterestOpif ((interestOps & readInterestOp) == 0) {//NioServerSocketChannel: readInterestOp = OP_ACCEPT = 1 << 4 = 16logger.info("interest ops: " + readInterestOp);selectionKey.interestOps(interestOps | readInterestOp);}
}
```
ServerBootstrapAcceptor 通过 childGroup.register() 方法会完成第三和第四两个步骤.
1.将 NioSocketChannel 注册到 Worker 工作线程中
2.并注册 OP_READ 事件到 NioSocketChannel 的事件集合。
在注册过程中比较有意思的一点是,它会调用 pipeline.fireChannelRegistered() 方法传播 channelRegistered 事件,然后再调用 pipeline.fireChannelActive() 方法传播 channelActive 事件。
兜了一圈,这又会回到之前我们介绍的 readIfIsAutoRead() 方法,此时它会将 SelectionKey.OP_READ 事件注册到 Channel 的事件集合。
添加自定义handler到客户端SocketChannel
pipeline.invokeHandlerAddedIfNeeded
总结
java •接受连接本质: selector.select()/selectNow()/select(timeoutMillis) 发现 OP_ACCEPT 事件,处理: •SocketChannel socketChannel = serverSocketChannel.accept() •selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); •selectionKey.interestOps(OP_READ);
关于服务端如何处理客户端新建连接的具体源码,我在此就不继续展开了。这里留一个小任务,建议你亲自动手分析下 childGroup.register() 的相关源码,从而加深对服务端启动以及新连接处理流程的理解。有了服务端启动源码分析的基础,再去理解客户端新建连接的过程会相对容易很多。
相关文章:
32.Netty源码之服务端如何处理客户端新建连接
highlight: arduino-light 服务端如何处理客户端新建连接 Netty 服务端完全启动后,就可以对外工作了。接下来 Netty 服务端是如何处理客户端新建连接的呢? 主要分为四步: md Boss NioEventLoop 线程轮询客户端新连接 OP_ACCEPT 事件ÿ…...
代码随想录day11
20. 有效的括号 ● 力扣题目链接 ● 给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。 ● 有效字符串需满足: ● 左括号必须用相同类型的右括号闭合。 ● 左…...
RabbitMQ实习面试题
RabbitMQ实习面试题 在 RabbitMQ 中,确保生产者消息正确发布以及确保消费者已经消费是非常重要的任务。以下是一些方法和策略,可以帮助您在 RabbitMQ 中实现这些目标: 确保生产者消息正确发布: 持久化消息:将消息设…...
Electron入门,项目运行,只需四步轻松搞定。
electron 简单介绍: 实现:HTML/CSS/JS桌面程序,搭建跨平台桌面应用。 electron 官方文档: [https://electronjs.org/docs] 本文是基于以下2篇文章且自行实践过的,可行性真实有效。 文章1: https://www.cnbl…...
【C++】visualstudio环境安装
记录了部分安装步骤,可能有点不全,参考下,需要的磁盘空间差不多20GB; 下载 https://visualstudio.microsoft.com/zh-hans/vs/ 下载完成: 双击进入安装状态: 根据自己的需求勾选安装项: 选择…...
使用MySQL:5.6和owncloud镜像搭建个人网盘
拉取镜像 [rootkvm ~]# docker pull mysql:5.6[rootkvm ~]# docker pull owncloud启动mysql容器 [rootkvm ~]# docker run -d --name db1 -e MYSQL_ROOT_PASSWORDroot mysql:5.6 db832e4e4333a0e9a7c152a67272721fdbe5381054090c5eb24f90455390a852 [rootkvm ~]# docker ps …...
php中创建对象时传递的参数是构造方法
PHP中创建对象时,可以通过构造方法的参数来传递参数值。构造方法是一个特殊的方法,在创建对象时会自动调用,用于进行对象的初始化操作。 以下是一个示例代码,展示了如何在PHP中使用构造方法传递参数: class MyClass …...
C++并发及互斥保护示例
最近要写一个多线程的并发数据库,主要是希望使用读写锁实现库的并发访问,同时考虑到其他平台(如Iar)没有C的读写锁,需要操作系统提供,就将读写锁封装起来。整个过程还是比较曲折的,碰到了不少问题,在此就简…...
新手常犯的错误,anzo capital昂首资本一招避免少走弯路
新手是不是经常交易中赚不到钱,今天anzo capital昂首资本就盘点一下新手常犯的错误,一招教你少走弯路。 一.随便选择交易账户 开立实时账户时选择正确的账户类型,anzo capital昂首资本教你比较所有提供的账户类型,选择最符合财务…...
Java Vue (el-date-picker组件) 前后端 关于时间格式数据的处理方法
前端使用 elment-ui 组件 el-date-picker 其中组件需要格式化时间,增加属性 value-format"yyyy-MM-dd" 后端 Java 接收参数类型 后端Dto 使用Date接收,并添加JsonFormat注解 JsonFormat(pattern"yyyy-MM-dd") private Date testTi…...
Python爬虫——scrapy_多条管道下载
定义管道类(在pipelines.py里定义) import urllib.requestclass DangDangDownloadPipelines:def process_item(self, item, spider):url http: item.get(src)filename ../books_img/ item.get(name) .jpgurllib.request.urlretrieve(url, filename…...
lombok启动不生效(什么方法都试了,可还是不生效怎么办 ?! 救救我)
使用IntelliJ IDEA 2021.1.3(Ultimate Edition)时提示Lombok不生效 java: You aren’t using a compiler supported by lombok, so lombok will not work and has been disabled. 方式一:我们手动更新一下版本到以下版本 <!--Lombok--&…...
element文本域禁止手动拉伸、两种方式、textarea
文章目录 style方式element自带的禁止拉伸方法建议 style方式 html <el-inputv-model"content":rows"3"class"r_n"type"textarea"maxlength"40"placeholder""style"height: 100%;" />css style…...
c#中lambda表达式缩写推演
Del<string> ml new Del<string>(Notify);//泛型委托的实例化,并关联Nofity方法 Del<string> ml new Del<string>(delegate (string str) { return str.Length; });//将Nofity变更为匿名函数 Del<string> ml delegate(string str)…...
无涯教程-PHP - 循环语句
PHP中的循环用于执行相同的代码块指定的次数。 PHP支持以下四种循环类型。 for - 在代码块中循环指定的次数。 while - 如果且只要指定条件为真,就会循环遍历代码块。 do ... while - 循环执行一次代码块…...
思维进化算法(MEA)优化BP神经网络
随着计算机科学的发展,人们借助适者生存这一进化规则,将计算机科学和生物进化结合起来,逐渐发展形成一类启发式随机搜索算法,这类算法被称为进化算法(Evolutionary Com-putation, EC)。最著名的进化算法有:遗传算法、进化策略、进化规划。与传统算法相比,进化算法的特点是群体搜…...
Kotlin 中的 设计模式
单例模式 饿汉模式 饿汉模式在类初始化的时候就创建了对象,所以不存在线程安全问题。 局限性: 1、如果构造方法中有耗时操作的话,会导致这个类的加载比较慢; 2、饿汉模式一开始就创建实例,但是并没有调用…...
Vulnhub: ICMP: 1靶机
kali:192.168.111.111 靶机:192.168.111.208 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.208 80端口的cms为Monitorr 1.7.6m 搜索发现该版本的cms存在远程代码执行 searchsploit monitorr 漏洞利用 nc本地监听&…...
我的创作纪念日(C++修仙练气期总结)
分享自己最喜欢的一首歌:空想フォレスト—伊東歌詞太郎 机缘 现在想想自己在CSDN创作的原因,一开始其实就是想着拿着博客当做自己的学习笔记,笔记嘛,随便写写,自己看得懂就ok了的态度凸(艹皿艹 )。也是用来作为自己学习…...
css的常见伪元素使用
1.first-line 元素首行设置特殊样式。 效果演示: <div class"top"><p>可以使用 "first-line" 伪元素向文本的首行设置特殊样式。<br> 换行内容 </p></div> .top p::first-line {color: red;} 2.first-lette…...
91. 解码方法
递归法:超时了 从字符串的后面向前计算,每一次递归都缩小子集 public class Solution {public int NumDecodings(string s) {return RecursiveAdd(s, s.Length - 1);}public int RecursiveAdd(string s, int index) {// 已经到最后一个元素if(index <…...
docker搭建opengrok环境2
引言: 虚拟机关闭后重新开启,理论上是需要重新启动一下docker的,以重新启动其中的服务。 命令基础: docker images:查看docker中现有的镜像 docker container ls -all:查看docker中目前在运行的containe…...
【校招VIP】java语言考点之ConcurrentHashMap1.7和1.8
考点介绍: ConcurrentHashMap是JAVA校招面试的热门考点,主要集中在1.7和1.8的底层结构和相关的性能提高。 理解这个考点要从map本身的并发问题出发,再到hashTable的低性能并发安全,引申到ConcurrentHashMap的分块处理。同时要理解…...
php如何实现5x+2x+1x=100
要实现5x 2x 1x 100的计算,可以使用PHP来解方程。以下是一个简单的PHP代码示例: php <?php $x 1; // 初始化x的值while (5*$x 2*$x 1*$x ! 100) { // 循环直到方程成立$x; // 每次循环增加x的值 }echo "x " . $x; // 输出x的值 ?…...
机器人项目:从 ROS2 切换到 ROS1 的原因
一、说明 机器人操作系统ROS是使用最广泛的机器人中间件平台。它在机器人社区中使用了10多年,无论是在业余爱好者领域还是在工业领域。ROS可用于各种微控制器和计算机,从Arduino到Raspberry Pi再到Linux工作站,它为电机控制器,视觉…...
Vault主题 - UiCore多用途Elementor WordPress主题
你可以使用Vault主题 – UiCore多用途Elementor WordPress主题构建什么? Vault主题拥有专业、像素级完美且干净的现代布局,几乎适合您需要的任何网站: 小型企业网站企业网站着陆页面权威博客销售和营销页面网上商店 自由职业者的最佳选择 …...
G0第26章:微服务概述与gRPCprotocol buffers
Go微服务与云原生 1、微服务架构介绍 单体架构(电商) SOA架构(电商) 微服务架构(电商) 优势 挑战 拆分 发展史 第一代:基于RPC的传统服务架构 第二代:Service Mesh(istio) 微服务架构分层 核心组件 Summar…...
三款远程控制软件对比,5大挑选指标:安全、稳定、易用、兼容、功能
陈老老老板🤴 🧙♂️本文专栏:生活(主要讲一下自己生活相关的内容)生活就像海洋,只有意志坚强的人,才能到达彼岸。 🧙♂️本文简述:三款远程控制软件对比,5大挑选指标࿱…...
Java中static的应用之单例模式
单例模式是一种创建对象的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。由于单例模式只允许存在一个实例,因此它可以节省系统资源并提高程序的性能。在许多情况下,单例模式在应用程序中都是非常有用的,例…...
TypeError: Cannot read properties of undefined (reading ‘container‘)
问题环境: element项目 el-table的错误 项目是由 webpack项目迁移为 vite项目 问题描述: errorLog.js?t1692581753160:17 TypeError: Cannot read properties of undefined (reading container) at unbind (infinite-scroll.js:259:31) …...
苏州企业网站/网络优化需要哪些知识
题目链接 Round Subset 题意 在n个数中选择k个数,求这k个数乘积末尾0个数的最大值。 首先我们预处理出每个数5的因子个数c[i]和2的因子个数d[i] 然后就可以背包了。 设f[i][j]为选i个数,5的因子总和为j时,2的因子总和的最大值。 则状态转移…...
公司网站建设维护/百度收录查询工具
安装JUnit 使用快捷键 ctrl shift s 或点击 File->Settings 点击 Plugins 查看插件 点击下方 Browse repositories… 查找插件 在搜索栏输入 JUnitGenerator V2.0,点击 install 安装 ,如果没有 install 按钮证明你已经安装过了,可以在…...
大渡口的网站开发公司电话/如何广告推广
好文章,要转载! 前面讲了paint,后面会花几篇主要讲讲canvas,并且由于最近项目比较紧,所以近期的文章都会“短小精悍”; paint 作为画笔,里面有非常多而强大的设置方法,比如设置颜…...
dedecms 安防监控行业网站模板/凡科建站收费价目表
telnet命令通常用来远程登录。telnet程序是基于TELNET协议的远程登录客户端程序。Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的 能力。在终端使用者的电脑上使用telnet程序&…...
风景区网站建设项目建设可行性/培训机构网站制作
1.HTTPS对称加密 服务器每次发送真实数据前,会先生成一把密钥传输(以明文方式传输密钥容易被劫持)给客户端,服务器给客户端发送的真实数据会先用这把密钥进行加密,客户端收到加密数据后再用密钥进行解密(客户端给服务器发送数据同理) 2.HTTP…...
手机软件制作和做网站相同/搜狗官方网站
核心步骤 阿里云准备 centos_8_0,安全组设置(可以虚拟机代替)linux下jdk maven git 配置添加SSH公钥从gitee克隆源代码liunx下docker安装git拉取项目代码执行项目中sql的数据库脚本修改项目配置并启动 新建目录 #tmp存放临时安装包 mkdir …...