) e.getKey()).set(e.getValue());
}
try {
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);
}
}
a) 将我们在启动流程中设置的childHandler(如,「serverBootstrap.childHandler(new MyServerInitializer())」)以及childOptions(如,「serverBootstrap.childOption(ChannelOption.ALLOW_HALF_CLOSURE, true)」)、childAttrs(如,「serverBootstrap.childAttr(AttributeKey.valueOf("userID"), UUID.randomUUID().toString())」)设置到这个NioSocketChannel中。 b) 将配置好的NioSocketChannel注册到childGroup中,并注册了一个监听器,因为注册是一个异步操作,该监听器会得以调用在注册操作完成时(完成包括,成功的完成、失败的完成、取消的完成)。该监听器实现:如果发现该注册操作失败了,则会强制关闭当前这个NioSocketChannel。 而将NioSocketChannel注册到childGroup的操作和将NioServerSocketChannel注册到parentGroup的流程也是极其类似的。详细的说明请参阅Netty 源码解析 ——— 服务端启动流程 (下)。这里做一个简单的概述。 整个注册流程: a) 首先会通过轮询的方式从childGroup中获取一个NioEventLoop,将当前的NioSocketChannel注册到这个NioEventLoop上。 b) 将当前的SocketChannel注册到NioEventLoop中的Selector上,并将NioSocketChannel作为附加属性设置到SelectionKey中。 c) 回调我们自定义的ChannelInitializer的initChannel方法,将我们定义的一个个ChannelHandler添加到当前NioSocketChannel所关联的ChannelPipeline上,然后将ChannelInitializer本身从ChannelPipeline中移除。 d) 标记注册这个异步操作标志为成功完成。 e) 触发ChannelRegistered事件,该事件会在ChannelPipeline中得以传播。 f) 触发ChannelActive事件,该事件也是一个入站事件,它会从ChannelPipeline中的head开始传播,而head的channelActive方法除了将ChannelActive事件传播给下一个ChannelInboundHandler之外,还调用一个readIfIsAutoRead()方法。 而readIfIsAutoRead()最终会调用到「AbstractNioChannel.doBeginRead()」方法:
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();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
而这个方法中就完成了将readInterestOp(即,上面在说明NioSocketChannel的构建过程中已经提及,该成员变量值为SelectionKey.OP_READ)设置为感兴趣的事件。这样一来,Selector就会监听该SocketChannel的读事件了。
到目前为止,NioServerSocketChannel通过accept接受了一个客户端的连接请求的整个流程就完成了。这里NioSocketChannel的创建是由NioServerSocketChannel所在的NioEventLoop( 实际上是NioEventLoop所在的线程上 )完成的。然后将创建好的NioSocketChannel注册到childGroup中,也就是通过轮询的方式的方式从childGroup中获取一个NioEventLoop,然后将NioSocketChannel注册的其上。在注册的过程中也完成了将SocketChannel注册到Selector,并设置SelectionKey.OP_READ为感兴趣的事件,这样Selector就会开始监听这个SocketChannel的读事件了。
SelectionKey.OP_CONNECT 事件处理流程
当SelectionKey.OP_CONNECT(连接事件)准备就绪时,我们执行如下操作:
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
① 将SelectionKey.OP_CONNECT事件从SelectionKey所感兴趣的事件中移除,这样Selector就不会再去监听该连接的SelectionKey.OP_CONNECT事件了。而SelectionKey.OP_CONNECT连接事件是只需要处理一次的事件,一旦连接建立完成,就可以进行读、写操作了。
② unsafe.finishConnect():
注意,当连接操作既没有被取消也没有超时的情况下,该方法才会被事件循环所调用。
a) boolean wasActive = isActive():
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
因为此时「SocketChannel.finishConnect()」还没调用,所以「ch.isConnected()」将返回false,因此isActive()的结果为false。 b) doFinishConnect():
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
该方法会调用SocketChannel.finishConnect()来标识连接的完成,如果我们不调用该方法,就去调用read/write方法,则会抛出一个NotYetConnectedException异常。 注意,无论如何在connect后finishConnect()方法都是需要被调用的。调用finishConnect()的三种返回:
如果你在connect()后直接调用了finishConnect()( 并非在CONNECT事件中调用 ),则若finishConnect()返回了true,则表示channel连接已经建立,而且CONNECT事件也不会被触发了。
如果finishConnect()方法返回false,则表示连接还未建立好。那么就可以通过CONNECT事件来监听连接的完成。
如果finishConnect()方法抛出了一个IOException异常,则表示连接操作失败。
c) fulfillConnectPromise(connectPromise, wasActive):
I. 当异步的“连接尝试”操作通过取消来关闭了,那么则直接返回。因为当“连接尝试”操作被取消时,connectPromise会被置为null。
II. isActive():获取当前SocketChannel的状态,因为此时「SocketChannel.finishConnect()」已经被调用过了,因此该方法会返回true。 III. boolean promiseSet = promise.trySuccess(): 将当前的异步的“连接尝试”操作尝试标记为成功。如果用户取消了“连接尝试(即,调用connect操作后,用户调用了cancel来取消该操作)”的操作的话,该方法将返回false;否则返回true,并且如果有ChannelFutureListener注册到了这个ChannelFuture(即,ChannelPromise)上,那么监听器的operationComplete方法将得以回调。 IV. 无论当前的“连接尝试”操作是否被取消,channelActive()事件都将被触发,因为此时channel确实已经处于active状态了。 channelActive是一个入站事件,该事件会从ChannelPipeline的head开始传播至tail间的所有ChannelInboundHandler,并回调它们的channelActive方法。
我们来深入看看head对channelActive事件的处理:
它除了将channelActive事件传播给下一个ChannelInboundHandler外,还会进行一个「readIfIsAutoRead()」操作:
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
因为NioSocketChannel的NioSocketChannelConfig对象的autoRead属性默认就为1,因此isAutoRead()为true。那么就会调用channel.read()操作,这将触发一个read事件在ChannelPipeline中,而read是一个出站操作。它会从ChannelPipeline的尾部开始传播至head间的每个ChannelOutboundHandler。 我们接着来看head对read事件的处理:
// DefaultChannelPipeline#HeadContext#read()
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
// AbstractChannel#AbstractUnsafe#beginRead()
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
// AbstractNioChannel#doBeginRead()
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();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
这里的readInterestOp是SelectionKey.OP_READ,是在构建NioSocketChannel对象时传进来的。因此,我们可以知道,此时的read事件主要是完成了,在Selector中对已经注册到其上的NioSocketChannel的OP_READ标识为感兴趣的事件。这样Selector就监控该SocketChannel的读操作了。 V. 如果用户执行了取消“连接尝试”的操作,那么就关闭channel,并触发channelInactive事件。 d) 如果成员变量connectTimeoutFuture非空,则说明该“连接尝试”操作设置了一个连接超时时间。那么,此时连接已经完成了,我们就可以取消这个连接超时检测的定时任务了。超时任务会记录本次“连接尝试”操作为失败状态,并且会将connectTimeoutFuture成员变量置为null。 比如,可能存在这样一种情况:也就是当程序执行完fulfillConnectPromise方法中的「promise.trySuccess()」之后,以及在执行finally代码块之前,“连接尝试”的已经完成,并且ChannelPromise已标记为了true。但是此时设置的连接超时时间已到并且连接超时任务被得以执行,此时超时任务发现ChannelPromise的状态已经被标识过了也就不会进行关闭channel的操作。 因此如上流程我们知道,『connectTimeoutFuture == null』有两种情况:1,如果没有设置连接超时;2,设置了连接超时,并且只因为超时或其他原因已经执行了 NioSocketChannel 的 close() 操作。『connectTimeoutFuture#operationComplete』也有两种情况:1,在超时时间内连接还没建立,则执行相应的close操作;2,连接已经建立了,但是还未执行到connectTimeoutFuture#cancel(false)操作,connectTimeoutFuture 就触发了,此时因为 promise的状态已经被表示过了,也不会进行close操作。
总的来说,OP_CONNECT事件的触发时,表示当前的socket处于了可连接的状态了,需要调用SocketChannel.finishConnect()来完成连接的后续事件。同时会触发ChannelActive事件,该事件为一个入站事件,它会在NioSocketChannel所关联的ChannelPipeline管道得以传播,即,回调head到tail之间所有的ChannelInboundHandler的channelActive方法。而head的channelActive方法中又会触发一个channel的read操作,该操作最终会在NioSocketChannel所注册的Selector中标识OP_READ为感兴趣的事件,这使得Selector会监听NioSocketChannel上是否有可读的数据准备好被读取了。
SelectionKey.OP_READ 事件处理流程
当有可读数据准备被读取时,‘SelectionKey.OP_READ’将会触发。在NioEventLoop的事件循环中会对该事件进行处理:
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
我们来看看unsafe.read()的实现,在NioSocketChannel中unsafe是一个NioByteUnsafe实例:
这里,我们主要针对读数据涉及的逻辑进行分析,关于allocHandle如何计算出一个最优缓冲区的大小用于创建缓冲区,以及判断读循环是否应该继续,已经在 Netty 源码解析 ——— AdaptiveRecvByteBufAllocator做了详细的分析了。所以此处,我们主要关注的是真实的读数据操作所涉及的流程。
① 首先,在循环前将分配器处理器中累加的消息/字节计数重置。接着,进行一个读循环操作:
I. 根据给定的分配器以及预测的最优缓冲区容量大小创建一个缓冲区用于准备接受可读取的数据。在Netty中,分配器(allocator)默认为PooledByteBufAllocator实例,而PooledByteBufAllocator实现通过使用jemalloc算法来实现高效的内存分配。
II. allocHandle.lastBytesRead(doReadBytes(byteBuf)):
使用已经分配好的ByteBuf来读取数据。并在分配器处理器中记录本次读取的字节数。
这里会将SocketChannel中的内容写到byteBuf中,从byteBuf的writerIndex开始写入数据,writerIndex会增加所写入的字节数。并且设置了最大可从SocketChannel读取的数据大小为“allocHandle.attemptedBytesRead()”,这也是byteBuf的容量大小。
III. 判断本次读操作所读取到的字节数:
a) 若‘读取到的字节数 < 0’,即为’-1’时,说明对端已经关闭了。close变量会被标记为true。因为没有读取到数据,因此调用‘byteBuf.release()’来释放bytebuf(因为,此时 bytebuf 不会在通过 channelPipeline进行传输了,也就是,这个 bytebuf 最后使用的地方就是当前方法,因此应该调用 release() 方法来声明对其的释放),然后退出读循环操作(break)。需要说明的是,因为byteBuf是通过PooledByteBufAllocator来分配的缓冲区,是一个池中的ByteBuf,因此是要通过release()方法来减小bytebud的引用计数,当bytebuf的引用计数为0时,则说明此时已经没有引用指向这个bytebuf了,那么它就会被“回收”;
b) 若‘读取到的字节数 == 0’,仅仅说明本次读操作没有读取到数据,那么就会执行同上面一样的释放bytebuf操作,即,‘byteBuf.release()’,然后退出读循环操作(break);
c) 若‘读取到的字节数 > 0’,说明本次读操作已经到达有效的数据了。那么执行:
[1]. 「allocHandle.incMessagesRead(1)」对读消息的次数进行累加。
[2]. 然后标识readPending为false,表示本次读操作已经读取到了有效数据,无需等待再一次的读操作。
[3]. 接着触发ChannelRead事件,它会在ChannelPipeline中传播,「pipeline.fireChannelRead(byteBuf)」。这是一个入站事件,它会从ChannelPipeline的head开始传播,依次顺序回调ChannelInboundHandler的channelRead()方法。这里可以看到,是读循环中每一次有效的读操作都对触发一次ChannelRead事件,并不是在所有数据都读取到之后才触发一次ChannelRead事件。因此,我们需要提供一系列的编解码器来将收到的数据分割成我们一个个的逻辑数据包,对此Netty也提供了一系列拆箱即有的编解码器为我们解决相关的问题。
IV. 根据当前的NioSocketChannel是否是自动读取的配置,以及已经读取的数据字节数,以及已经进行的读操作次数,以及最近一次读取的字节数来判断是否需要继续进行读循环操作。若需要则继续读循环操作;否则退出读循环,继续后面的流程。
② allocHandle.readComplete():在本次读循环结束后调用一次「allocHandle.readComplete()」来记录本次读循环的数据信息以用于预测下一次读事件触发时,应该分配多大的ByteBuf容量更加合理些。
③ pipeline.fireChannelReadComplete():触发ChannelReadComplete事件,用于表示当前读操作的最后一个消息已经被ChannelRead所消费。ChannelReadComplete是一个入站消息,它会从ChannelPipeline的head开始传播,依次顺序回调ChannelInboundHandler的channelReadComplete方法。
ChannelRead vs ChannelReadComplete
当Channel检测到对端有数据可以读取的时候,channelRead方法会被调用。
channelRead方法可能会被调用多次,当channelReadComplete方法被回调的时候,标识着数据已经都读取完了。也就是说,channelRead方法会被调用多次,当所有消息都读取完后channelReadComplete方法会得到一次调用。
④ 如果close被标识为了true,则说明对端已经关闭了连接。(即,读操作中读取的字节数量为-1,则表示远端已经关闭了),则执行「closeOnRead(pipeline)」
首先需要补充一点,在NIO传输模式下,当SocketChannel的read操作返回’-1’时,有两种情况:a) 对端已经关闭了连接,即SocketChannel被关闭了;b) 当前端执行了「socketChannel.shutdownInput()」。
I. 若“isInputShutdown0()”返回false,则说明是远端连接已经关闭了。那么此时,如果我们的程序配置了“ChannelOption.ALLOW_HALF_CLOSURE”属性(即,可以在启动引导类时通过option(ChannelOption.ALLOW_HALF_CLOSURE, true)来启用配置),那么就会进行shutdownInput()操作,并触发一个用户自定义的ChannelInputShutdownEvent.INSTANCE事件,在ChannelPipeline中传播。该事件是一个入站事件,它会从ChannelPipeline中的head开始传播,异常顺序调用ChannelInboundHandler的userEventTriggered方法。
II. 若“isInputShutdown0()”返回false,则说明是远端连接已经关闭了。并且此时我们的程序并没有配置启动“ChannelOption.ALLOW_HALF_CLOSURE”。那么此时就会进行相应SocketChannel的关闭等相关操作。
III. 若“isInputShutdown0()”返回true,则说明是当前的NioSocketChannel自动调用了shutdownInput()方法来关闭了输入流。那么此时就会触发一个用户自定义的ChannelInputShutdownEvent.INSTANCE事件,在ChannelPipeline中传播。该事件是一个入站事件,它会从ChannelPipeline中的head开始传播,异常顺序调用ChannelINboundHandler的userEventTriggered方法。
关于「SocketChannel.shutdownInput()」:关闭一个连接的读,但不关闭这个通道。一旦关闭了读,那么在这之后调用channel的read都将返回’-1’,来表示’流的结尾’。如果往已经关闭的输入流中发送数据,都会默认被丢弃。
⑤ 如果本次读操作已经读取到有效数据(即,最近一次读操作返回的读取字节数>0),并且当前的NioSocketChannel的配置为非自动读取(disable autoRead,说明此时用户不希望Selector去监听当前SocketChannel的读事件,用户可以根据业务逻辑的需要,在希望读取数据时再去添加OP_READ事件到Selector中。并且在每次读取到数据后就将OP_READ事件从所感兴趣的事件中移除),那么此时需要将OP_READ事件从所感兴趣的事件中移除,这样Selector就不会继续监听该SocketChannel的读事件了。
removeReadOp()操作就是将OP_READ从SelectionKey的interestOps集合中移除:
在NioSocketChannel中成员变量readInterestOp就是SelectionKey.OP_READ。
PS:注意,如果在当前端主动调用「channel.shutdownInput()」方法时,需要在处理’ChannelInputShutdownReadComplete’这个用户自定义的事件时调用「channel().config().setAutoRead(false);」来将autoRead置为false。不然,OP_READ事件会一直被触发,而上的步骤’III’会一直被调用,这会导致一些问题,比如不必要的CPU消耗。 调用方式类似:
public class MyServerHandler extends SimpleChannelInboundHandler {
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Channel serverChannel = ctx.channel();
if(serverChannel instanceof NioSocketChannel) {
System.out.println("server shutdownInput...");
((NioSocketChannel) serverChannel).shutdownInput();
}
}
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof ChannelInputShutdownReadComplete) {
System.out.println("detail ChannelInputShutdownEvent event......");
ctx.channel().config().setAutoRead(false);
}
super.userEventTriggered(ctx, evt);
}
}
SelectionKey.OP_WRITE 事件处理流程
在NioEventLoop的事件循环中’SelectionKey.OP_WRITE’事件的处理流程如下:
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
关于OP_WRITE事件: OP_WRITE事件的就绪条件并不是发生在调用channel的write方法之后,而是在当底层缓冲区有空闲空间的情况下。因为写缓冲区在绝大部分时候都是有空闲空间的,所以如果你注册了写事件,这会使得写事件一直处于就就绪,选择处理线程就会一直占用着CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 SocketChannel会在写数据时,若发现当buffer还有数据,但写缓冲区已经满的情况下,socketChannel.write(buffer)会返回已经写出去的字节数,此时为0。那么这个时候我们就需要注册OP_WRITE事件,这样当写缓冲区又有空闲空间的时候就会触发OP_WRITE事件,这样我们就可以继续将没写完的数据继续写出了。而且在写完后,一定要记得将OP_WRITE事件注销。 比如,来看看NioSocketChannel的doWrite()操作(「 ch.unsafe().forceFlush()」方法最终也就是会调用到这里):
就像上面所说的那样,当发现socketChannel.write(buffer)返回的已经写出去的字节数为0时,则说明此时写缓冲区已经满了无法写入,因此就需要注册一个OP_WRITE事件,这样当写缓存有空间来继续接受数据时,该写事件就会被触发,这样我们就可以继续将没写完的数据继续写出了。而且在写完后,一定要记得将OP_WRITE事件注销。
关于写操作的具体流程分析请参见 Netty 源码解析 ——— writeAndFlush流程分析
后记
本文主要对NioEventLoop中涉及到的四种NIO事件的处理流程进行了分析。四个看似简单的处理流程,深入探索后发现其实并不简单,其实可以展开的点还有很多,特别是关于写事件涉及到ChannelOutboundBuffer以及Netty默认使用的PooledByteBufAllocator实现了jemalloc算法来完成高效的内存分配等等,希望在后面的文章中能继续和大家分享我的分析以及想法。 若文章有任何错误,望大家不吝指教:)
你可能感兴趣的:(Netty 源码解析 ——— 基于 NIO 网络传输模式的 OP_ACCEPT、OP_CONNECT、OP_READ、OP_WRITE 事件处理流程)
Shell脚本传递参数的4种方式
yan_baby_liu
Shell linux
#脚本内容script.sh【使用位置参数】#!/bin/bashif[$1-gt18];thenecho"Youareanadult."elseecho"Youarenotanadultyet."fi#脚本调用./script.sh19#for语句foriin12345;doecho$idone#while语句【使用了变量】i=0while[$i-lt10];doecho$ii=$((i+1))
chatgpt赋能python:Python获取短信验证码:想省时省力,就得尝试!
pythonxxoo
ChatGpt chatgpt 爬虫 计算机
Python获取短信验证码:想省时省力,就得尝试!作为一名有10年python编程经验的工程师,我深知其中的难点和麻烦。很多人甚至会担心,网络上关于Python的短信验证码获取有很多风险,这一点当然不能忽略。但在我看来,只要遵循正确的步骤和方法,那么获取短信验证码只是手到擒来之事。以下是我几点看法:Python获取短信验证码的重要性在如今的互联网环境中,短信验证码已经成为了大多数网站和应用程序中常
Auto.js学习笔记1:开发需要准备什么工具和编程语言知识?
PYB3
Auto.js # 学习 # 实战 android javascript vscode
什么是Auto.js?根据官方文档定义:Auto.js是一款无需root权限的JavaScript自动化软件。Auto.js是一款安卓手机的应用主要用webview,和微信一样,安装在手机上;Auto.js是开发app脚本(自动化操作、引流脚本、游戏脚本、简单app);Auto.js脚本开发语言是JavaScript;Auto.js看作手机版本的按键精灵就全明白了,但又不能局限于按键精灵(自行补脑
Qt学习记录--04 Qt的对话框介绍
Barry.Ji
Qt C++ qt C++
一引言:熟悉win32(MFC)的小伙伴们会知道,对话框分为模态对话框和非模态对话框,他们的明显差异是:模态对话框在弹出后,会阻塞同一应用程序中其它窗体的输入,即无法获取鼠标和键盘等响应。模态对话框很常见,比如记事本的“打开文件”功能。当“打开文件”对话框弹出后,我们无法对此外的窗口进行操作的。而非模态对话框在弹出后,其他窗体依旧可以获取响应。例如记事本的“查找”对话框,我们可以在查找的同时,继续
准确-NGINX 1.26.2配置正向代理并编译安装的完整过程
P7进阶路
面试 学习路线 阿里巴巴 nginx 运维
NGINX1.26.2配置正向代理并编译安装的完整过程,使用了ngx_http_proxy_connect_module模块。1.环境准备1.1安装依赖确保系统安装了以下必要的依赖:sudoyuminstall-ygccgcc-c++makepcre-develzlib-developenssl-devel1.2下载NGINX源码从NGINX官方下载指定版本(1.26.2)的源码:wgethttp
HTML超链接 表格 列表 表单
s_kzn
html html5
目录一、HTML超链接1.外部链接2.内部链接3.空链接#4.下载链接5.网页元素的链接6.锚点链接二、表格1.表格标签的属性2.合并单元格三、列表1.无序列表2.有序列表3.自定义列表四、表单1.表单标签2.用户注册表单实例一、HTML超链接超链接可以是一个字,一个词,或者一组词,也可以是一幅图像,可以点击这些内容来跳转到新的文档或者当前文档中的某个部分。超链接标签:一个链接到另一个链接【Anc
陶瓷产品质量追溯系统方案
liu854046222
技术解决方案 网络 大数据
一、背景目标随着消费者对产品质量和安全性的关注度日益提升,陶瓷产品作为人们日常生活中不可或缺的一部分,其质量安全问题愈发受到重视。然而,陶瓷产品的生产流程复杂,涉及原材料采购、生产、加工、销售等多个环节,任何一个环节出现问题都可能导致产品质量问题。传统的质量管理模式往往难以实现对产品全过程的追溯和监控,因此,建立陶瓷产品质量追溯系统显得尤为重要。提高产品质量:通过追溯系统,企业可以实时掌握原材料的
供水管网漏损及智能化监测改造解决方案
liu854046222
技术解决方案 解决方案 供水 智慧供水 智能化监测
一、背景随着城市化进程的加快,供水系统的复杂性和规模性日益增强,传统的供水管网管理模式已难以满足当前高效、智能的管理需求。供水管网漏损问题作为城市供水系统中的一大难题,不仅造成了水资源的大量浪费,还增加了供水企业的运营成本。因此,针对供水管网漏损及智能化监测进行改造升级,构建一套全面、高效、智能的监测与管理系统,已成为提升城市供水管理水平、保障供水安全、降低漏损率的重要途径。二、调研结果2.1、调
centos7中Open-Webui的部署
linuxxx110
linux 运维 服务器 ai
前期中部署了ollama及deepseek-r1,为了有web界面访问,需要部署open-webui系统要求是python3.11以上版本,一、先升级openssl1.安装依赖yuminstall-ygccgcc-c++autoconfautomakezlibzlib-develpcre-devel2.下载源码包并解压wgethttps://www.openssl.org/source/opens
c++ std::list使用笔记
JANGHIGH
C++ c++ list 笔记
c++std::list使用笔记1.包含头文件2.创建和初始化`std::list`3.添加元素4.删除元素5.访问元素6.遍历`std::list`7.容量相关操作8.其他常用操作9.示例代码总结std::list是C++标准库中的一个双向链表容器。与std::vector不同,std::list不支持随机访问,但它在任意位置插入和删除元素的操作效率更高(时间复杂度为O(1))。以下是std::
醒醒!临时抱佛脚背Java面试题的在面试官面前是根本没有用的
java15655057970
面试 学习路线 阿里巴巴 java 开发语言
醒醒!临时抱佛脚背Java面试题的在面试官面前是根本没有用的!Java架构老李2020-11-1917:17:09744收藏33分类专栏:Java面试编程语言文章标签:数据库面试javamysql编程语言版权前言其实我之前就跟大家说过,我其实特别不喜欢那种临近面试就提前背啊,记各种题的行为,非常反对!我觉得这种方法特别极端,并且在稍有一点经验的面试官面前是根本没有用的。还有粉丝跟我留言说道:“我们
华为云专家出品《深入理解边缘计算》电子书上线
华为云PaaS服务小智
华为云 边缘计算 人工智能
华为开发者大会PaaS生态电子书推荐,助你成为了不起的开发者!什么是边缘计算?边缘计算的应用场景有哪些?华为云出品《深入理解边缘计算》电子书上线带你系统理解云、边、端协同的相关原理了解开源项目的源码分析流程学成能够对云、边、端主流开源实现进行定制开发!【适用人群】1.对云原生感兴趣的开发者2.对边缘计算有学习需求或想拓展业务之外开发技能的开发者【精彩导读】首先,介绍边缘计算概念、边缘计算系统具体组
探索未来之声:趣玩语音识别新篇章——FunASR
乌芬维Maisie
探索未来之声:趣玩语音识别新篇章——FunASR去发现同类优质开源项目:https://gitcode.com/在这个数字时代,语音识别技术如同开启智能交互的金钥匙,而【FunASR】正是这把钥匙中的璀璨明珠。FunASR,一款由阿里巴巴达摩院倾力打造的基础语音识别工具包,不仅连接着学术探索的深邃与产业应用的实践,更是以“让语音识别更有趣”为使命,引领了一场声音转换为文字的技术革命。技术剖析:全面
发现声音处理的新大陆:Fish Audio Preprocessor
幸竹任
发现声音处理的新大陆:FishAudioPreprocessoraudio-preprocess项目地址:https://gitcode.com/gh_mirrors/au/audio-preprocess项目介绍在这个数字时代,音频处理成为了多媒体领域不可或缺的一环。引入《FishAudioPreprocessor》,一款专为简化音频预处理任务而设计的开源神器。它集结了一系列核心功能,覆盖从基本
AI环境初识
网络飞鸥
AI 人工智能
在搭建AI环境时,当前流行的技术涉及多个方面,包括开发框架、深度学习库、硬件支持以及具体的应用技术等。以下是一些主要的技术趋势和流行技术:一、开发框架与深度学习库TensorFlow:由谷歌开发的一个开源机器学习库,广泛用于研究和生产环境。它提供了强大的张量计算能力和灵活的架构,支持广泛的机器学习和深度学习算法。PyTorch:由Facebook推出,也是一个广受欢迎的开源机器学习库。PyTorc
DeepSeek模型微调的原理和方法
alankuo
人工智能
DeepSeek模型微调的原理迁移学习基础DeepSeek模型微调基于迁移学习的思想。预训练模型在大规模通用数据上进行了无监督或有监督的训练,学习到了丰富的语言知识、语义表示和通用模式。这些知识和模式具有一定的通用性,可以迁移到其他相关的任务中。在微调时,我们利用预训练模型已经学到的这些通用知识,针对特定的目标任务进行进一步的调整和优化,使得模型能够更好地适应新任务的需求。微调的参数更新机制在微调
php过滤文字中的表情字符和mysql服务端对emoji的支持
angzhan5306
php 移动开发 数据库
1.过滤emoji表情的原因在我们的项目开发中,emoji表情是个麻烦的东西,即使我们可以能存储,也不一定能完美显示,因为它的更新速度很快:在iOS以外的平台上,例如PC或者android。如果你需要显示emoji,就得准备一大堆emoji图片并使用第三方前端类库才行。即便如此,还是可能因为emoji图片不够全而出现无法显示的情况在大多数业务场景下,emoji也不是非要不可的。我们可以适当地考虑干
亚马逊云科技MySQL托管服务:Amazon RDS for MySQL的技术优势与成本优化实践
AWS官方合作商
科技 mysql 数据库
引言:在数字化转型的浪潮中,数据库作为企业核心业务的“中枢神经”,其稳定性、性能及成本直接影响企业的运营效率和竞争力。然而,自建MySQL数据库的复杂性、运维成本高企、扩展性不足等问题,始终是开发者与企业的痛点。亚马逊云科技推出的AmazonRDSforMySQL,作为一款全托管的云数据库服务,凭借其技术先进性和灵活的成本模型,正在成为企业上云的首选方案。一、技术优势:告别繁琐运维,聚焦核心业务1
HTML 表单 表格 列表
mio_SG
html 前端
目录HTML表单type=""标签文本域密码字段单选按钮简单的下拉列表提交按钮重置完整代码HTML表格HTML和表格边框属性HTML表格头HTML无序列表HTML表单HTML表单用于搜集不同类型的用户输入,简而言之,就是用来搜集用户数据的。HTML表单都是存在于标签里面的。表单元素。表单元素指的是不同类型的input元素、复选框、单选按钮、提交按钮等等。这些东西都具有一个交互性的特点。type="
Bright Master 多屏幕亮度调节工具
Ylsh3702
github sql windows
BrightMaster亲爱的朋友们,今天要为大家介绍的也是一款多屏幕亮度调节工具。与前面提到的MonitorBrightnessAdjuster相比,这款工具有一个非常贴心的设计——它可以在系统任务栏实时显示当前屏幕的亮度状态。这样一来,您无需打开软件界面,就能随时掌握屏幕亮度的变化。不过,它的默认图标颜色是白色,可能会不太显眼。比如阿枫,就将图标颜色改成了红色,这样看起来就更加醒目了。您看,这
史上最全的Unity面试题(含答案)
Sindywangkeai
unity unity 面试题
一.什么是渲染管道?是指在显示器上为了显示出图像而经过的一系列必要操作。渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。主要步骤有:本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。二.如何优化内存?有很多种方式,例如1.压缩自带类库;2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;3.释放AssetBundle占用的
在嵌入式Linux中实现高并发TCP服务器:从select到epoll的演进与实战
W说编程
嵌入式 网络编程 C/C++ 服务器 linux tcp/ip c语言 嵌入式硬件
在嵌入式Linux中实现高并发TCP服务器:从select到epoll的演进与实战1.引言:嵌入式网络通信的挑战与机遇在物联网(IoT)和工业4.0的推动下,嵌入式设备逐渐从单机控制转向网络互联。然而,嵌入式系统的资源限制(如内存、CPU性能)与复杂的网络环境(高延迟、低带宽)对网络编程提出了严峻挑战。核心痛点:如何用有限的资源支持数百甚至上千的并发连接?如何确保数据传输的实时性与可靠性?本文将以
QT常用控件—菜单栏和对话框
Qt开发老杰
qt 开发语言 c++ c语言
引言QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menubar)、多个工具栏(toolbars)、多个锚接部件(dockwidgets)、一个状态栏(statusbar)及一个中心部件(centralwidget),是许多应用程序的基础,如文本编辑器,图片编辑器等。(本篇主要介绍菜单栏和工具栏)本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt
超高频RFID涉密资产智能档案柜应用
RFID铨顺宏
RFID档案管理 RFID资产管理 RFID档案管理 RFID智能档案柜 RFID涉密资产管理
1.行业背景近年来,我国党政机关大力推进信息化建设,积极发展电子政务,工作效率不断提高。但涉密物品,包括档案文件、U盘、硬盘、光碟等资产的管理因涉及到党和国家秘密的安全,长期以来一直依赖于人工,成为党政机关提高工作效率的瓶颈之一。人工管理方式给工作带来了种种不便。首先,人工管理方式效率较低,在管理人员缺乏的情况下无法做到24小时全天候监控管理。涉密物品的登记、存储、借出、归还、维修、报废等环节都需
垂类大模型微调(二):使用LLaMA-Factory
CITY_OF_MO_GY
从零到亿大模型微调 llama
上一篇博文和大家一起安装了LLaMA-Factory工具,并下载了大模型在上面进行了简单的加载和推理,今天尝试通过LoRa技术对大模型进行微调;一、训练集准备1.1介绍训练集结构这里演示对Qwen2.5-0.5B-Instruct-GPTQ-Int4模型进行LoRA微调,大家可以根据垂类大模型微调(一)从魔塔中下载对应模型;目前该工具支持指令监督微调(Alpaca格式)和多轮对话微调(
豪越消防一体化安全管控平台新亮点: AI功能、智能运维以及消防处置知识库
豪越
人工智能 大数据 运维开发
在当今社会,随着科技的飞速发展,消防安全已不再局限于传统的物理防范措施,而是步入了智能化、一体化的全新时代。豪越科技,作为消防安全管理领域的先行者,凭借其创新技术和深厚经验,推出了消防一体化安全管控平台,该平台集成了多项前沿功能,旨在为重点单位提供全方位、智能化的消防安全管理解决方案。豪越消防一体化安全管控平台,是集智慧消防营区综合管理于一体的创新解决方案。平台通过统一应用、数据互通与智慧物联,实
AI大模型-提示工程学习笔记13—自动提示工程师 (Automatic Prompt Engineer)
9命怪猫
AI 人工智能 大模型 ai prompt
卷首语:我所知的是我自己非常无知,所以我要不断学习。写给AI入行比较晚的小白们(比如我自己)看的,大神可以直接路过无视了。自动提示工程师(APE)是一种利用大语言模型(LLM)自动生成和优化提示(Prompt)的框架,旨在减少人工设计提示的工作量,并提高LLM在特定任务上的性能。与手动设计提示不同,APE通过让LLM自身生成和评估提示,自动探索更有效的提示策略,从而实现提示工程的自动化。以下是对A
v4l2子系统学习(三)编写虚拟摄像头驱动
林政硕(vx : Cohen0415)
V4L2子系统 v4l2
文章目录1、声明2、前言3、虚拟摄像头驱动编写3.1、编写硬件相关代码3.2、程序示例1、声明本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。韦老师的《驱动大全》:商品详情其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git2、前言前面两章,我
DeepSeek不好用?那是你还没有清华大学指导手册
悉数之淀
pdf
「清华Deepseek最新使用手册教程资源」链接:https://pan.quark.cn/s/c9c795c32bed终于等到清华出了deepseek手册,前几天也看了一些花里胡哨的教程,大多言之无物,就是把之前gpt的手册换了个皮直接套给deepseek。果然清华出手就是不一样,从科普原理,到手把手教你科学使用。而且不光是告诉你怎么问,还会告诉你为什么要这么问,教你提示词的底层逻辑。✅这才是授
顺序表和STL——vector【 复习笔记】
wanjiazhongqi
复习笔记 数据结构 笔记 c++ 算法
1.顺序表1.1顺序表的概念要理解顺序表,首先得理解线性表线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,n=0时,称为空表,线性表逻辑结构是线性结构而线性表使用顺序存储就是顺序表,顺序表通过数组实现1.2顺序表的实现1.2.1实现方式1.数组采用静态分配,顺序表为静态顺序表2.数组采用动态分配,顺序表为动态顺序表静态分配:直接向内存申请一大块连续的区域动态分配:按需申请合适的空间二
Algorithm
香水浓
java Algorithm
冒泡排序
public static void sort(Integer[] param) {
for (int i = param.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
int current = param[j];
int next = param[j + 1];
mongoDB 复杂查询表达式
开窍的石头
mongodb
1:count
Pg: db.user.find().count();
统计多少条数据
2:不等于$ne
Pg: db.user.find({_id:{$ne:3}},{name:1,sex:1,_id:0});
查询id不等于3的数据。
3:大于$gt $gte(大于等于)
&n
Jboss Java heap space异常解决方法, jboss OutOfMemoryError : PermGen space
0624chenhong
jvm jboss
转自
http://blog.csdn.net/zou274/article/details/5552630
解决办法:
window->preferences->java->installed jres->edit jre
把default vm arguments 的参数设为-Xms64m -Xmx512m
----------------
文件上传 下载 解析 相对路径
不懂事的小屁孩
文件上传
有点坑吧,弄这么一个简单的东西弄了一天多,身边还有大神指导着,网上各种百度着。
下面总结一下遇到的问题:
文件上传,在页面上传的时候,不要想着去操作绝对路径,浏览器会对客户端的信息进行保护,避免用户信息收到攻击。
在上传图片,或者文件时,使用form表单来操作。
前台通过form表单传输一个流到后台,而不是ajax传递参数到后台,代码如下:
<form action=&
怎么实现qq空间批量点赞
换个号韩国红果果
qq
纯粹为了好玩!!
逻辑很简单
1 打开浏览器console;输入以下代码。
先上添加赞的代码
var tools={};
//添加所有赞
function init(){
document.body.scrollTop=10000;
setTimeout(function(){document.body.scrollTop=0;},2000);//加
判断是否为中文
灵静志远
中文
方法一:
public class Zhidao {
public static void main(String args[]) {
String s = "sdf灭礌 kjl d{';\fdsjlk是";
int n=0;
for(int i=0; i<s.length(); i++) {
n = (int)s.charAt(i);
if((
一个电话面试后总结
a-john
面试
今天,接了一个电话面试,对于还是初学者的我来说,紧张了半天。
面试的问题分了层次,对于一类问题,由简到难。自己觉得回答不好的地方作了一下总结:
在谈到集合类的时候,举几个常用的集合类,想都没想,直接说了list,map。
然后对list和map分别举几个类型:
list方面:ArrayList,LinkedList。在谈到他们的区别时,愣住了
MSSQL中Escape转义的使用
aijuans
MSSQL
IF OBJECT_ID('tempdb..#ABC') is not null
drop table tempdb..#ABC
create table #ABC
(
PATHNAME NVARCHAR(50)
)
insert into #ABC
SELECT N'/ABCDEFGHI'
UNION ALL SELECT N'/ABCDGAFGASASSDFA'
UNION ALL
一个简单的存储过程
asialee
mysql 存储过程 构造数据 批量插入
今天要批量的生成一批测试数据,其中中间有部分数据是变化的,本来想写个程序来生成的,后来想到存储过程就可以搞定,所以随手写了一个,记录在此:
DELIMITER $$
DROP PROCEDURE IF EXISTS inse
annot convert from HomeFragment_1 to Fragment
百合不是茶
android 导包错误
创建了几个类继承Fragment, 需要将创建的类存储在ArrayList<Fragment>中; 出现不能将new 出来的对象放到队列中,原因很简单;
创建类时引入包是:import android.app.Fragment;
创建队列和对象时使用的包是:import android.support.v4.ap
Weblogic10两种修改端口的方法
bijian1013
weblogic 端口号 配置管理 config.xml
一.进入控制台进行修改 1.进入控制台: http://127.0.0.1:7001/console 2.展开左边树菜单 域结构->环境->服务器-->点击AdminServer(管理) &
mysql 操作指令
征客丶
mysql
一、连接mysql
进入 mysql 的安装目录;
$ bin/mysql -p [host IP 如果是登录本地的mysql 可以不写 -p 直接 -u] -u [userName] -p
输入密码,回车,接连;
二、权限操作[如果你很了解mysql数据库后,你可以直接去修改系统表,然后用 mysql> flush privileges; 指令让权限生效]
1、赋权
mys
【Hive一】Hive入门
bit1129
hive
Hive安装与配置
Hive的运行需要依赖于Hadoop,因此需要首先安装Hadoop2.5.2,并且Hive的启动前需要首先启动Hadoop。
Hive安装和配置的步骤
1. 从如下地址下载Hive0.14.0
http://mirror.bit.edu.cn/apache/hive/
2.解压hive,在系统变
ajax 三种提交请求的方法
BlueSkator
Ajax jqery
1、ajax 提交请求
$.ajax({
type:"post",
url : "${ctx}/front/Hotel/getAllHotelByAjax.do",
dataType : "json",
success : function(result) {
try {
for(v
mongodb开发环境下的搭建入门
braveCS
运维
linux下安装mongodb
1)官网下载mongodb-linux-x86_64-rhel62-3.0.4.gz
2)linux 解压
gzip -d mongodb-linux-x86_64-rhel62-3.0.4.gz;
mv mongodb-linux-x86_64-rhel62-3.0.4 mongodb-linux-x86_64-rhel62-
编程之美-最短摘要的生成
bylijinnan
java 数据结构 算法 编程之美
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class ShortestAbstract {
/**
* 编程之美 最短摘要的生成
* 扫描过程始终保持一个[pBegin,pEnd]的range,初始化确保[pBegin,pEnd]的ran
json数据解析及typeof
chengxuyuancsdn
js typeof json解析
// json格式
var people='{"authors": [{"firstName": "AAA","lastName": "BBB"},'
+' {"firstName": "CCC&
流程系统设计的层次和目标
comsci
设计模式 数据结构 sql 框架 脚本
流程系统设计的层次和目标
RMAN List和report 命令
daizj
oracle list report rman
LIST 命令
使用RMAN LIST 命令显示有关资料档案库中记录的备份集、代理副本和映像副本的
信息。使用此命令可列出:
• RMAN 资料档案库中状态不是AVAILABLE 的备份和副本
• 可用的且可以用于还原操作的数据文件备份和副本
• 备份集和副本,其中包含指定数据文件列表或指定表空间的备份
• 包含指定名称或范围的所有归档日志备份的备份集和副本
• 由标记、完成时间、可
二叉树:红黑树
dieslrae
二叉树
红黑树是一种自平衡的二叉树,它的查找,插入,删除操作时间复杂度皆为O(logN),不会出现普通二叉搜索树在最差情况时时间复杂度会变为O(N)的问题.
红黑树必须遵循红黑规则,规则如下
1、每个节点不是红就是黑。 2、根总是黑的 &
C语言homework3,7个小题目的代码
dcj3sjt126com
c
1、打印100以内的所有奇数。
# include <stdio.h>
int main(void)
{
int i;
for (i=1; i<=100; i++)
{
if (i%2 != 0)
printf("%d ", i);
}
return 0;
}
2、从键盘上输入10个整数,
自定义按钮, 图片在上, 文字在下, 居中显示
dcj3sjt126com
自定义
#import <UIKit/UIKit.h>
@interface MyButton : UIButton
-(void)setFrame:(CGRect)frame ImageName:(NSString*)imageName Target:(id)target Action:(SEL)action Title:(NSString*)title Font:(CGFloa
MySQL查询语句练习题,测试足够用了
flyvszhb
sql mysql
http://blog.sina.com.cn/s/blog_767d65530101861c.html
1.创建student和score表
CREATE TABLE student (
id INT(10) NOT NULL UNIQUE PRIMARY KEY ,
name VARCHAR
转:MyBatis Generator 详解
happyqing
mybatis
MyBatis Generator 详解
http://blog.csdn.net/isea533/article/details/42102297
MyBatis Generator详解
http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.
让程序员少走弯路的14个忠告
jingjing0907
工作 计划 学习
无论是谁,在刚进入某个领域之时,有再大的雄心壮志也敌不过眼前的迷茫:不知道应该怎么做,不知道应该做什么。下面是一名软件开发人员所学到的经验,希望能对大家有所帮助
1.不要害怕在工作中学习。
只要有电脑,就可以通过电子阅读器阅读报纸和大多数书籍。如果你只是做好自己的本职工作以及分配的任务,那是学不到很多东西的。如果你盲目地要求更多的工作,也是不可能提升自己的。放
nginx和NetScaler区别
流浪鱼
nginx
NetScaler是一个完整的包含操作系统和应用交付功能的产品,Nginx并不包含操作系统,在处理连接方面,需要依赖于操作系统,所以在并发连接数方面和防DoS攻击方面,Nginx不具备优势。
2.易用性方面差别也比较大。Nginx对管理员的水平要求比较高,参数比较多,不确定性给运营带来隐患。在NetScaler常见的配置如健康检查,HA等,在Nginx上的配置的实现相对复杂。
3.策略灵活度方
第11章 动画效果(下)
onestopweb
动画
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/
FAQ - SAP BW BO roadmap
blueoxygen
BO BW
http://www.sdn.sap.com/irj/boc/business-objects-for-sap-faq
Besides, I care that how to integrate tightly.
By the way, for BW consultants, please just focus on Query Designer which i
关于java堆内存溢出的几种情况
tomcat_oracle
java jvm jdk thread
【情况一】:
java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环; 如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决: <jvm-arg>-Xms3062m</jvm-arg> <jvm-arg>-Xmx
Manifest.permission_group权限组
阿尔萨斯
Permission
结构
继承关系
public static final class Manifest.permission_group extends Object
java.lang.Object
android. Manifest.permission_group 常量
ACCOUNTS 直接通过统计管理器访问管理的统计
COST_MONEY可以用来让用户花钱但不需要通过与他们直接牵涉的权限
D