深入理解Zookeeper系列-4.Watcher原理

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • Zookeeper watcher
    • 为什么学习源码
    • watcher
      • 图解
    • 源码实现

Zookeeper watcher

为什么学习源码

公司要调用一个第三方的接口进行一个操作,但是这个操作可能会比较耗时,需要一段时间来响应,在第三方接口不能去修改的情况下,想去提升性能。

在这个过程中我们有很多方法去解决它,包括定时轮询,做事件响应机制,异步化的方式,即便第三方没办法修改的情况下,我们也可以在自己这边形成一个循环,形成一个高效的循环去处理这个调用。

在分析问题的时候,我们所学的事件的机制、异步化、基于线程的生产者消费者模型,其实它可以在刚刚的场景中去使用,比如我们发起这个请求,他可以将其放置在队列中,通过线程去和对应的接口做远程通信,这时我们可以解决我们这端的并发量的问题,他们第三方在不改变的情况下,我们这端某种程度上提升了一定的吞吐量,然后基于事件的机制就是我们调用第三方接口给返回的时候,基于这个返回结果发送一个通知去告诉调用者结果处理完了,你去拿这个结果做后续的处理

watcher

在api中,如果我们的客户端需要去实现watcher,就想zk做注册中心,配置中心的情况下,我们都需要实现在zk server上的配置变更和服务地址变更的通知 要去告诉我们的客户端,所有的客户端,你的数据发生了变化你需要采取一些行动,其实这就是一个通知的机制。

// standrd 标准监听 (一次性监听)ZooKeeper zooKeeper=new ZooKeeper("192.168.216.128:2181", 5000, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {//表示连接成功之后,会产生的回调时间}});
Stat stat=new Stat();
zooKeeper.getData("/first", new DataWatchListener(),stat); //针对当前节点class DataWatchListener implements Watcher{@Overridepublic void process(WatchedEvent watchedEvent) {String path=watchedEvent.getPath();//再次注册监听try {zooKeeper.getData(path,this,new Stat());} catch (KeeperException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}}// 在3.6.1 还有着新的监听 持久化监听 和 持久化递归监听
// 持久化监听:只需要注册一次事件
// 持久化递归监听:其子节点发生变化,都会触发监听// 默认情况下,是递归持久化监听
ZooKeeper.addWatch("path",new DataWatchListener(),Add.WatchMode.PERSISTENT_RECURSIVE)

图解

在这里插入图片描述

首先客户端要发起一个请求,客户端所有的请求先发到阻塞队列中,然后一个 SendThread线程 去轮询队列,通过take的方式,可以发现异步的方式能够很大程度上的提升整个的处理性能,发送过来的任务是一个request,它里面可以包括 crud exist等,我们后续的请求实际上就是NIO,实际上会将请求转换成序列化以后,自己会实现一个序列化机制,然后将这个请求发送到服务端,这个请求要做的就是 注册 watcher 带的内容是 path/watch:true,发送到服务端之后,因为客户端和服务端建立好连接以后,会维持这个会话,所以最终服务端会保存这个watcher,将其存储在HashMap中。

之所以是HashMap<String,HashSet> 是因为可能有多个客户端。

当注册成功会给一个返回,并且这个动作是异步化的操作。

然后客户端这边也需要存储一个事件的管理 提供了一个类 ZkWatcherManager,通过一些集合去存储客户端那边锁对应的watcher

当我们通过xshell来修改zk上对应监听的路径节点时,此时会触发对应节点的变化,服务端会判断这个节点是否存在监听,也就是在这个hashmap中查询是否有watcher。

因为之间说过zk服务端和客户端实际上存在着一个会话,所以,最后直接push发送给客户端即可

通过上图可以发现,所有和网络通信相关的,都是采用异步(生产者消费者模型)

当然上图中还有一些地方没有说清除,具体如下:

当ZooKeeper服务器检测到某个Znode的状态发生变化时,会向对该Znode注册了Watcher的客户端发送一个通知消息。这个通知消息包含了变化的类型(例如数据内容的变化、子节点的变化等)以及该Znode的最新状态信息。

当客户端接收到Watcher通知后,它会根据通知消息中的信息进行相应的处理。具体地,如果客户端的ZkWatcherManager中有对应的Watcher对象,就会调用该Watcher的process方法来处理通知消息。在process方法中,客户端可以根据变化的类型和最新的状态信息来进行相应的操作。

举个例子,假设客户端在启动时对一个Znode注册了一个数据内容变化的Watcher。当该Znode的数据内容发生变化时,ZooKeeper服务器会向该客户端发送一个通知消息,并在消息中指明变化的类型是“数据内容的变化”,同时包含最新的数据内容。客户端的ZkWatcherManager会找到对应的Watcher对象,并调用它的process方法。在process方法中,客户端可以根据最新的数据内容进行相应的操作,比如重新获取数据内容、重新注册Watcher等。

源码实现

zookeeper.exists();
---------------------------------------------------------------------------------------
public Stat exists(final String path, Watcher watcher) throws KeeperException, InterruptedException {final String clientPath = path;PathUtils.validatePath(clientPath); //节点校验// the watch contains the un-chroot pathWatchRegistration wcb = null;if (watcher != null) {wcb = new ExistsWatchRegistration(watcher, clientPath);}final String serverPath = prependChroot(clientPath);//请求头RequestHeader h = new RequestHeader();h.setType(ZooDefs.OpCode.exists);//请求对象ExistsRequest request = new ExistsRequest();request.setPath(serverPath); //firstrequest.setWatch(watcher != null); //true/false//response返回对象SetDataResponse response = new SetDataResponse();// cnxn 网络通信的负责处理的类ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);if (r.getErr() != 0) {//返回的错误码去判断返回的结果if (r.getErr() == KeeperException.Code.NONODE.intValue()) {return null;}throw KeeperException.create(KeeperException.Code.get(r.getErr()), clientPath);}//返回statareturn response.getStat().getCzxid() == -1 ? null : response.getStat();}
---------------------------------------------------------------------------------------
public ReplyHeader submitRequest(RequestHeader h,Record request,Record response,WatchRegistration watchRegistration,WatchDeregistration watchDeregistration) throws InterruptedException {ReplyHeader r = new ReplyHeader();//构建Packet 数据包,主要是需要传递的内容(queuePacket 实际上在这里是要将一个数据包发送到队列中,这符合我么图解中说明的那样)Packet packet = queuePacket(h,r,request,response,null,null,null,null,watchRegistration,watchDeregistration);synchronized (packet) {// 加锁if (requestTimeout > 0) { //如果携带了请求超时时间. 带超时时间的等待// Wait for request completion with timeoutwaitForPacketFinish(r, packet);} else {// Wait for request completion infinitelywhile (!packet.finished) { //只要packet没有处理完成,那么一直调用wait等待。packet.wait(); //阻塞}}}if (r.getErr() == Code.REQUESTTIMEOUT.intValue()) {sendThread.cleanAndNotifyState();}return r;}---------------------------------------------------------------------------------------
public Packet queuePacket(RequestHeader h,ReplyHeader r,Record request,Record response,AsyncCallback cb,String clientPath,String serverPath,Object ctx,WatchRegistration watchRegistration,WatchDeregistration watchDeregistration) {Packet packet = null;// Note that we do not generate the Xid for the packet yet. It is// generated later at send-time, by an implementation of ClientCnxnSocket::doIO(),// where the packet is actually sent.packet = new Packet(h, r, request, response, watchRegistration);packet.cb = cb;packet.ctx = ctx;packet.clientPath = clientPath;packet.serverPath = serverPath;packet.watchDeregistration = watchDeregistration;// The synchronized block here is for two purpose:// 1. synchronize with the final cleanup() in SendThread.run() to avoid race// 2. synchronized against each packet. So if a closeSession packet is added,// later packet will be notified.synchronized (state) {if (!state.isAlive() || closing) {conLossPacket(packet);} else {// If the client is asking to close the session then// mark as closingif (h.getType() == OpCode.closeSession) {closing = true;}//添加到阻塞队列outgoingQueue.add(packet);}}// 唤醒处于阻塞在selector.select上的线程// 那么sendThread是在哪里初始化的呢?sendThread.getClientCnxnSocket().packetAdded();return packet;}
---------------------------------------------------------------------------------------//在zookeeper的构造方法里面
public ZooKeeper(String connectString,int sessionTimeout,Watcher watcher,boolean canBeReadOnly,HostProvider aHostProvider) throws IOException {this(connectString, sessionTimeout, watcher, canBeReadOnly, aHostProvider, null);}public ZooKeeper(.........// 客户端和服务端的一个连接cnxn = createConnection(connectStringParser.getChrootPath(),hostProvider,sessionTimeout,this,watchManager,getClientCnxnSocket(),canBeReadOnly);cnxn.start();}public void start() {//发送线程sendThread.start();//事件线程(触发事件的线程,// 也就是说当服务端触发了事件通知到客户端之后,客户端需要从本地的事件列表中去读取watcher,并且进行回调)eventThread.start();}// 后续就是一堆nio netty等流程的内容,暂不关注---------------------------------------------------------------------------------------public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {// We have the request, now process and setup for nextInputStream bais = new ByteBufferInputStream(incomingBuffer);BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);RequestHeader h = new RequestHeader();h.deserialize(bia, "header"); //反序列化headercnxn.incrOutstandingAndCheckThrottle(h);incomingBuffer = incomingBuffer.slice();//根据请求类型进行不同的处理if (h.getType() == OpCode.auth) {// 授权} else if (h.getType() == OpCode.sasl) {processSasl(incomingBuffer, cnxn, h);} else {if (shouldRequireClientSaslAuth() && !hasCnxSASLAuthenticated(cnxn)) {ReplyHeader replyHeader = new ReplyHeader(h.getXid(), 0, Code.SESSIONCLOSEDREQUIRESASLAUTH.intValue());cnxn.sendResponse(replyHeader, null, "response");cnxn.sendCloseSession();cnxn.disableRecv();} else {Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), incomingBuffer, cnxn.getAuthInfo());int length = incomingBuffer.limit();if (isLargeRequest(length)) {// checkRequestSize will throw IOException if request is rejectedcheckRequestSizeWhenMessageReceived(length);si.setLargeRequestSize(length);}si.setOwner(ServerCnxn.me);submitRequest(si); //提交请求(异步有关系)}}}public void submitRequest(Request si) {enqueueRequest(si);}public void enqueueRequest(Request si) {// 有点类似限流的逻辑if (requestThrottler == null) {synchronized (this) {try {while (state == State.INITIAL) {wait(1000);}} catch (InterruptedException e) {LOG.warn("Unexpected interruption", e);}if (requestThrottler == null) {throw new RuntimeException("Not started");}}}requestThrottler.submitRequest(si);}public void submitRequest(Request request) {if (stopping) {//如果服务端在终止的过程,则删除这个请求LOG.debug("Shutdown in progress. Request cannot be processed");dropRequest(request);} else {submittedRequests.add(request);}}---------------------------------------------------------------------------------------// 此时终于找到了一直轮询的线程了public void run() {try {while (true) {if (killed) {break;}// 到这里 一个典型的生产者消费者的方式才很清晰Request request = submittedRequests.take();if (Request.requestOfDeath == request) {break;}if (request.mustDrop()) {continue;}// Throttling is disabled when maxRequests = 0//节流阀是否处于关闭状态,=0表示关闭if (maxRequests > 0) {while (!killed) {if (dropStaleRequests && request.isStale()) {// Note: this will close the connectiondropRequest(request);ServerMetrics.getMetrics().STALE_REQUESTS_DROPPED.add(1);request = null;break;}//限流动作if (zks.getInProcess() < maxRequests) {break;}//等待.throttleSleep(stallTime);}}if (killed) {break;}// A dropped stale request will be nullif (request != null) {if (request.isStale()) {ServerMetrics.getMetrics().STALE_REQUESTS.add(1);}zks.submitRequestNow(request);}}} catch (InterruptedException e) {LOG.error("Unexpected interruption", e);}int dropped = drainQueue();LOG.info("RequestThrottler shutdown. Dropped {} requests", dropped);}public void submitRequestNow(Request si) {if (firstProcessor == null) {synchronized (this) {try {// Since all requests are passed to the request// processor it should wait for setting up the request// processor chain. The state will be updated to RUNNING// after the setup.while (state == State.INITIAL) {wait(1000);}} catch (InterruptedException e) {LOG.warn("Unexpected interruption", e);}if (firstProcessor == null || state != State.RUNNING) {throw new RuntimeException("Not started");}}}try {touch(si.cnxn);boolean validpacket = Request.isValid(si.type);if (validpacket) {  //如果packet合法setLocalSessionFlag(si);//通过一个处理器链来处理这个请求// PrepRequestProcessor(SyncRequestProcessor(FinalRequestProcessor))//firstProcessor.processRequest(si);
---------------------------------------------------------------------------------------//构建一个请求处理链路//单机环境的处理链路:PrepRequestProcessor(SyncRequestProcessor(FinalRequestProcessor))protected void setupRequestProcessors() {//最终的处理器RequestProcessor finalProcessor = new FinalRequestProcessor(this);//SyncRequestProcessor 同步处理器  将数据同步到本地磁盘RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);// 同步处理器最终会有一个 写入到快照文件,也就是需要设置自己的磁盘同步策略// 其实就是性能 和 一致性的取舍问题((SyncRequestProcessor) syncProcessor).start();//PrepRequestProcessor 预处理器firstProcessor = new PrepRequestProcessor(this, syncProcessor);((PrepRequestProcessor) firstProcessor).start();}
---------------------------------------------------------------------------------------if (si.cnxn != null) {incInProcess();}} else {LOG.warn("Received packet at server of unknown type {}", si.type);// Update request accounting/throttling limitsrequestFinished(si);new UnimplementedRequestProcessor().processRequest(si);}} catch (MissingSessionException e) {LOG.debug("Dropping request.", e);// Update request accounting/throttling limitsrequestFinished(si);} catch (RequestProcessorException e) {LOG.error("Unable to process request", e);// Update request accounting/throttling limitsrequestFinished(si);}}---------------------------------------------------------------------------------------case OpCode.exists: {lastOp = "EXIS";// TODO we need to figure out the security requirement for this!ExistsRequest existsRequest = new ExistsRequest();ByteBufferInputStream.byteBuffer2Record(request.request, existsRequest);path = existsRequest.getPath();if (path.indexOf('\0') != -1) {throw new KeeperException.BadArgumentsException();}//通过zk得到stat// 从这里可以看出来前面图解中说到的HashMap 里面的 Set<Watcher> 中存储的watcher其实是网络对象// 之所以这样去实现当path发生变化的时候,需要告诉所有的监视者,记住这个网络连接将数据返回出去就行了。Stat stat = zks.getZKDatabase().statNode(path, existsRequest.getWatch() ? cnxn : null);rsp = new ExistsResponse(stat);requestPathMetricsCollector.registerRequest(request.type, path);break;}public Stat statNode(String path, Watcher watcher) throws KeeperException.NoNodeException {Stat stat = new Stat();DataNode n = nodes.get(path);// 到这里才到图解中注册的流程if (watcher != null) {//服务端的注册的流程dataWatches.addWatch(path, watcher);}if (n == null) {throw new KeeperException.NoNodeException();}synchronized (n) {n.copyStat(stat);}updateReadStat(path, 0L);return stat;}public boolean addWatch(String path, Watcher watcher) {/*** watcher 表示当前的一个注册监听的一个连接* path 表示监听的路径*/return addWatch(path, watcher, WatcherMode.DEFAULT_WATCHER_MODE);}STANDARD(false, false),PERSISTENT(true, false),PERSISTENT_RECURSIVE(true, true);public static final WatcherMode DEFAULT_WATCHER_MODE = WatcherMode.STANDARD;// 接下来就是自然而然的保存了// 表示节点到watcher集合的映射private final Map<String, Set<Watcher>> watchTable = new HashMap<>();// 表示从watcher到所有节点的映射private final Map<Watcher, Set<String>> watch2Paths = new HashMap<>();public synchronized boolean addWatch(String path, Watcher watcher, WatcherMode watcherMode) {if (isDeadWatcher(watcher)) {LOG.debug("Ignoring addWatch with closed cnxn");return false;}Set<Watcher> list = watchTable.get(path);if (list == null) {// don't waste memory if there are few watches on a node// rehash when the 4th entry is added, doubling size thereafter// seems like a good compromiselist = new HashSet<>(4);// 保存watchTable.put(path, list);}list.add(watcher);Set<String> paths = watch2Paths.get(watcher);if (paths == null) {// cnxns typically have many watches, so use default cap herepaths = new HashSet<>();watch2Paths.put(watcher, paths);}//设置监听模式watcherModeManager.setWatcherMode(watcher, path, watcherMode);return paths.add(path);}// 此时就完成了整个服务端的一个保存---------------------------------------------------------------------------------------
// 在SendThread中 有一个叫readResponse 的方法void readResponse(ByteBuffer incomingBuffer) throws IOException {ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);ReplyHeader replyHdr = new ReplyHeader();replyHdr.deserialize(bbia, "header");switch (replyHdr.getXid()) {case PING_XID:......case AUTHPACKET_XID:......case NOTIFICATION_XID:......default:break;}// If SASL authentication is currently in progress, construct and// send a response packet immediately, rather than queuing a// response as with other packets.if (tunnelAuthInProgress()) {GetSASLRequest request = new GetSASLRequest();request.deserialize(bbia, "token");zooKeeperSaslClient.respondToServer(request.getToken(), ClientCnxn.this);return;}Packet packet;synchronized (pendingQueue) {if (pendingQueue.size() == 0) {throw new IOException("Nothing in the queue, but got " + replyHdr.getXid());}packet = pendingQueue.remove();}/** Since requests are processed in order, we better get a response* to the first request!*/try {if (packet.requestHeader.getXid() != replyHdr.getXid()) {packet.replyHeader.setErr(KeeperException.Code.CONNECTIONLOSS.intValue());throw new IOException("Xid out of order. Got Xid " + replyHdr.getXid()+ " with err " + replyHdr.getErr()+ " expected Xid " + packet.requestHeader.getXid()+ " for a packet with details: " + packet);}packet.replyHeader.setXid(replyHdr.getXid());packet.replyHeader.setErr(replyHdr.getErr());packet.replyHeader.setZxid(replyHdr.getZxid());if (replyHdr.getZxid() > 0) {lastZxid = replyHdr.getZxid();}if (packet.response != null && replyHdr.getErr() == 0) {packet.response.deserialize(bbia, "response");}LOG.debug("Reading reply session id: 0x{}, packet:: {}", Long.toHexString(sessionId), packet);} finally {finishPacket(packet);}}在zookeeper.classpublic void register(int rc) {if (shouldAddWatch(rc)) {//如果服务端已经建立了映射关系,则需要在客户端建立好关系Map<String, Set<Watcher>> watches = getWatches(rc);synchronized (watches) {Set<Watcher> watchers = watches.get(clientPath);if (watchers == null) {watchers = new HashSet<Watcher>();watches.put(clientPath, watchers);}watchers.add(watcher);}}}/*
后续的流程,如果服务端返回成功的话,那么就保存好了,此时关系就建立好了,而触发监听的方式。服务端在一个地方发现数据发生变更的时候,直接在服务端找到一个对应的watcher,去推送消息就行了,客户端收到消息,判断消息类型,根据映射关系去找到watcher回调即可。
*/

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

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

相关文章

【LeetCode热题100】【双指针】盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例…

如何往excel中写子表?

with pd.ExcelWriter("C:/last_date.xlsx") as writer:for i in range(0, 10):df pd.DataFrame()df.to_excel(writer, indexFalse, sheet_namestr(days[i 1]))

【高效开发工具系列】gson入门使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

医院不良事件报告系统源码带鱼骨图分析

医院不良事件上报系统通过 “事前的人员知识培训管理和制度落地促进”、“事中的事件上报和跟进处理”、 以及 “事后的原因分析和工作持续优化”&#xff0c;结合预存上百套已正在使用的模板&#xff0c;帮助医院从对护理事件、药品事件、医疗器械事件、医院感染事件、输血事件…

肺癌二期治疗效果与方案

肺腺癌II期治疗方案主要包括手术治疗、化疗、放疗等&#xff0c;建议患者积极配合医生治疗。 1、手术治疗 肺腺癌属于肺部恶性肿瘤&#xff0c;生长速度比较缓慢&#xff0c;早期患者可以通过手术的方式切除病变部位&#xff0c;能够达到根治目的&#xff0c;术后患者要注意伤…

CTF特训日记day3

复现一下RWCTF5th shellfind题目 题目描述如下&#xff1a; Hello Hacker. You dont know me, but I know you. I want to play a game. Heres what happens if you lose. The device you are watching is hooked into your Saturday and Sunday. When the timer in the back …

没有哈希时间锁定合约的跨链原子交换

在上一篇文章中&#xff0c;我们介绍了使用哈希时间锁定合约&#xff08;HTLC&#xff09;的跨链原子交换实现。 今天&#xff0c;我们介绍一种无需 HTLC 即可实现的替代方法。 这将原子交换扩展到缺乏哈希锁和时间锁的区块链。 使用 SPV 证明交易已被挖掘 让我们按照商定的价…

支撑材料-软件项目质量保证措施-资料大全

一、 质量保障措施 二、 项目质量管理保障措施 &#xff08;一&#xff09; 资深的质量经理与质保组 &#xff08;二&#xff09; 全程参与的质量经理 &#xff08;三&#xff09; 合理的质量控制流程 1&#xff0e; 质量管理规范&#xff1a; 2&#xff0e; 加强协调管理…

【23-24 秋学期】NNDL 作业11 LSTM

习题6-4 推导LSTM网络中参数的梯度&#xff0c; 并分析其避免梯度消失的效果 习题6-3P 编程实现下图LSTM运行过程 李宏毅机器学习笔记&#xff1a;RNN循环神经网络_李宏毅rnn笔记_ZEERO~的博客-CSDN博客https://blog.csdn.net/weixin_43249038/article/details/132650998 L5W…

Spring-AOP与声明式事务

为什么要用AOP ①现有代码缺陷 针对带日志功能的实现类&#xff0c;我们发现有如下缺陷&#xff1a; 对核心业务功能有干扰&#xff0c;导致程序员在开发核心业务功能时分散了精力 附加功能分散在各个业务功能方法中&#xff0c;不利于统一维护 ②解决思路 解决这两个问题&…

Python基础快速过一遍

文章目录 一、变量及基本概念1、变量2、变量类型3、变量格式化输出4、type()函数5、input()函数6、类型转换函数7、注释 二、Python运算/字符1、算数运算2、比较运算3、逻辑运算4、赋值运算符5、转义字符6、成员运算符 三、判断/循环语句1、if判断语句2、while循环语句3、for循…

【ret2user】InCTF2021-Kqueue

前言 这题给了源码&#xff0c;感觉代码的问题很大。然后题目不算难&#xff0c;但是最后 ret2user 执行的代码很有意思。这里的思路是参考的 Roland_ 大佬的思路&#xff1a;[原创]InCTF 内核Pwn之 Kqueue-Pwn-看雪-安全社区|安全招聘|kanxue.com 最后不去泄漏 kernel_offse…

IDEA构建springBoot新项目时JDK只有17和21,无法选择JDK8解决方案

今天创建springboot新项目时&#xff0c;发现IDEA里JDK选项只有17和21&#xff0c;无法选择本机的JDK8&#xff0c;网上查资料后发现是springboot2.7于11.24号后停止维护&#xff0c;基于2.7和java8的spring Initializ官方不再维护&#xff0c;解决方案是在server URL栏&#x…

STM32CubeIde 实现printf打印输出

STM32CubeIde 实现printf打印输出&#xff0c;在IDE生成的程序的main中的/* USER CODE BEGIN 4 /和/ USER CODE END 4 */之间放下面代码&#xff1a; #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #define GETCHAR_PROTOTYPE int __io_getchar(FILE *…

集线器-交换机-路由器

1.集线器(Hub) 集线器就是将网线集中到一起的机器&#xff0c;也就是多台主机和设备的连接器。集线器的主要功能是对接收到的信号进行同步整形放大&#xff0c;以扩大网络的传输距离&#xff0c;是中继器的一种形式&#xff0c;区别在于集线器能够提供多端口服务&#xff0c;也…

OpenGL 和 OpenGL ES 2.0/3.X 一致性测试说明(CTS)

本文档介绍如何构建、移植和运行 OpenGL 和 OpenGL ES 2.0/3.X 一致性测试&#xff0c;以及如何验证和提交测试结果。 [TOC]目录 测试环境要求 一致性测试需要文件系统。文件系统需要支持长文件名&#xff08;即 > 8.3 名称格式&#xff09;。一致性测试中的源文件使用大…

面试题:MySQL为什么选择B+树作为索引结构

文章目录 前言二、平衡二叉树(AVL)&#xff1a;旋转耗时三、红黑树&#xff1a;树太高四、B树&#xff1a;为磁盘而生五、B树六、感受B树的威力七、总结 前言 在MySQL中&#xff0c;无论是Innodb还是MyIsam&#xff0c;都使用了B树作索引结构(这里不考虑hash等其他索引)。本文…

Redis命令详解

文章目录 Key&#xff08;键&#xff09; DEL EXISTS EXPIRE EXPIREAT PEXPIRE PEXPIREAT PERSIST KEYS TTL PTTL RENAME RENAMENX TYPE SCAN HSCAN SSCAN ZSCAN DUMP String&#xff08;字符串&#xff09; SET GET INCR DECR MSET MGET APPEND SETNX STRLEN INCRBY DECRBY IN…

opencv知识库:cv2.add()函数和“+”号运算符

需求场景 现有一灰度图像&#xff0c;需求是为该图像增加亮度。 原始灰度图像 预期目标图像 解决方案 不建议的方案——“”运算符 假设我们需要为原始灰度图像的亮度整体提升88&#xff0c;那么利用“”运算符的源码如下&#xff1a; import cv2img_path r"D:\pych…

Django二转Day03 04

0 cbv执行流程&#xff0c;self问题 path(index/, Myview.as_view()),Myview.as_view() 实例化后返回 变成return Myview.dispatch(request, *args, **kwargs)但是视图函数Myview中没有 dispatch 方法 所以去 父类View中寻找return View.dispatch(request, *args, **kwargs)调用…