基于MQTT协议的 org.eclipse.paho.client.mqttv3 源码学习(二)

2023-10-30

http://blog.csdn.net/chenbifeng/article/details/25067761

一、主要类介绍


二、重点类代码分析

对于长连接,一般是直接从消息的接收和发送类开始读,上面知道paho中消息发送和接收是在CommsSender和CommsReceiver实现的,

所以直接差看CommsSender代码。

[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. public void run() {  
  2.         final String methodName = "run";  
  3.         MqttWireMessage message = null;  
  4.         while (running && (out != null)) {  
  5.             try {  
  6.                 message = clientState.get();  
  7.   
  8.                 log("sender 802:begin->" + message.toString());  
  9.                 if (message != null) {  
  10.                     // @TRACE 802=network send key={0} msg={1}  
  11.                     log.fine(className, methodName, "802"new Object[] { message.getKey(), message });  
  12.   
  13.                     if (message instanceof MqttAck) {  
  14.                         out.write(message);  
  15.                         out.flush();  
  16.                     } else {  
  17.                         MqttToken token = tokenStore.getToken(message);  
  18.                         // While quiescing the tokenstore can be cleared so need  
  19.                         // to check for null for the case where clear occurs  
  20.                         // while trying to send a message.  
  21.                         if (token != null) {  
  22.                             synchronized (token) {  
  23.                                 out.write(message);  
  24.                                 try {  
  25.                                     out.flush();  
  26.                                 } catch (IOException ex) {  
  27.                                     // The flush has been seen to fail on  
  28.                                     // disconnect of a SSL socket  
  29.                                     // as disconnect is in progress this should  
  30.                                     // not be treated as an error  
  31.                                     if (!(message instanceof MqttDisconnect))  
  32.                                         throw ex;  
  33.                                 }  
  34.                                 clientState.notifySent(message);  
  35.                             }  
  36.                         }  
  37.                     }  
  38.                     log("sender 805:send success.");  
  39.                 } else { // null message  
  40.                     // @TRACE 803=get message returned null, stopping}  
  41.                     log.fine(className, methodName, "803");  
  42.   
  43.                     running = false;  
  44.                     log("sender 805:send empty.");  
  45.                 }  
  46.             } catch (MqttException me) {  
  47.                 log("sender 804:MqttException-> " + me.getLocalizedMessage());  
  48.                 handleRunException(message, me);  
  49.             } catch (Exception ex) {  
  50.                 log("sender 804:exception-> " + ex.getLocalizedMessage());  
  51.                 handleRunException(message, ex);  
  52.             }  
  53.         } // end while  
  54.   
  55.         // @TRACE 805=<  
  56.         log.fine(className, methodName, "805");  
  57.   
  58.     }  

代码可以看到,是直接一个线程无效循环获取消息然后发送,
message = clientState.get();进入查看消息获取代码

[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. protected MqttWireMessage get() throws MqttException {  
  2.         final String methodName = "get";  
  3.         MqttWireMessage result = null;  
  4.   
  5.         synchronized (queueLock) {  
  6.             while (result == null) {  
  7.                 // If there is no work wait until there is work.  
  8.                 // If the inflight window is full and no flows are pending wait until space is freed.  
  9.                 // In both cases queueLock will be notified.  
  10.                 if ((pendingMessages.isEmpty() && pendingFlows.isEmpty()) ||   
  11.                     (pendingFlows.isEmpty() && actualInFlight >= this.maxInflight)) {  
  12.                     try {  
  13.                         long ttw = getTimeUntilPing();  
  14.                         //@TRACE 644=wait for {0} ms for new work or for space in the inflight window   
  15.                         log.fine(className,methodName, "644"new Object[] {new Long(ttw)});                          
  16.    
  17.                         queueLock.wait(getTimeUntilPing());  
  18.                     } catch (InterruptedException e) {  
  19.                     }  
  20.                 }  
  21.                   
  22.                 // Handle the case where not connected. This should only be the case if:   
  23.                 // - in the process of disconnecting / shutting down  
  24.                 // - in the process of connecting  
  25.                 if (!connected &&   
  26.                     (pendingFlows.isEmpty() || !((MqttWireMessage)pendingFlows.elementAt(0instanceof MqttConnect))) {  
  27.                     //@TRACE 621=no outstanding flows and not connected  
  28.                     log.fine(className,methodName,"621");  
  29.   
  30.                     return null;  
  31.                 }  
  32.   
  33.                 // Check if there is a need to send a ping to keep the session alive.   
  34.                 // Note this check is done before processing messages. If not done first  
  35.                 // an app that only publishes QoS 0 messages will prevent keepalive processing  
  36.                 // from functioning.  
  37.                 checkForActivity();  
  38.                   
  39.                 // Now process any queued flows or messages  
  40.                 if (!pendingFlows.isEmpty()) {  
  41.                     // Process the first "flow" in the queue  
  42.                     result = (MqttWireMessage)pendingFlows.elementAt(0);  
  43.                     pendingFlows.removeElementAt(0);  
  44.                     if (result instanceof MqttPubRel) {  
  45.                         inFlightPubRels++;  
  46.   
  47.                         //@TRACE 617=+1 inflightpubrels={0}  
  48.                         log.fine(className,methodName,"617"new Object[]{new Integer(inFlightPubRels)});  
  49.                     }  
  50.           
  51.                     checkQuiesceLock();  
  52.                 } else if (!pendingMessages.isEmpty()) {  
  53.                     // If the inflight window is full then messages are not   
  54.                     // processed until the inflight window has space.   
  55.                     if (actualInFlight < this.maxInflight) {  
  56.                         // The in flight window is not full so process the   
  57.                         // first message in the queue  
  58.                         result = (MqttWireMessage)pendingMessages.elementAt(0);  
  59.                         pendingMessages.removeElementAt(0);  
  60.                         actualInFlight++;  
  61.       
  62.                         //@TRACE 623=+1 actualInFlight={0}  
  63.                         log.fine(className,methodName,"623",new Object[]{new Integer(actualInFlight)});  
  64.                     } else {  
  65.                         //@TRACE 622=inflight window full  
  66.                         log.fine(className,methodName,"622");                 
  67.                     }  
  68.                 }             
  69.             }  
  70.         }  
  71.         return result;  
  72.     }  

大致就是阻塞式获取消息,在一个心跳时间内如果没有消息就一直阻塞,超过心跳间隔,自动往队列中加入心跳包 MqttPingReq.

由此可以看出 CommsSender 发送的消息主要是从 ClientState 这个类中get 出来,而ClientState 这个类的作用在上面也说过:

保存正在发布的消息和将要发布的消息的状态信息,对应状态下消息进行必要的处理。

处理方式参见MQTT协议中客户端与服务器connent public unsubscribe,subscribe等消息的交互方式,

我们来看下这个类的主要成员 :


查看ClientState 类说明

[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * The core of the client, which holds thestate information for pending and 
  3.  * in-flight messages. 
  4.  * 
  5.  * Messages that have been accepted fordelivery are moved between several objects 
  6.  * while being delivered. 
  7.  * 
  8.  * 1) When the client is not running messagesare stored in a persistent store that 
  9.  * implements the MqttClientPersistent Interface.The default is MqttDefaultFilePersistencew 
  10.  * which stores messages safely across failuresand system restarts. If no persistence 
  11.  * is specified there is a fall back toMemoryPersistence which will maintain the messages 
  12.  * while the Mqtt client isinstantiated. 
  13.  * 
  14.  * 2) When the client or specificallyClientState is instantiated the messages are 
  15.  * read from the persistent store into: 
  16.  * - outboundqos2 hashtable if a QoS 2 PUBLISH orPUBREL 
  17.  * - outboundqos1 hashtable if a QoS 1 PUBLISH 
  18.  * (see restoreState) 
  19.  * 
  20.  * 3) On Connect, copy messages from the outboundhashtables to the pendingMessages or 
  21.  * pendingFlows vector in messageidorder. 
  22.  * - Initial message publish goes onto the pendingmessagesbuffer. 
  23.  * - PUBREL goes onto the pendingflows buffer 
  24.  * (see restoreInflightMessages) 
  25.  * 
  26.  * 4) Sender thread reads messages from the pendingflowsand pendingmessages buffer 
  27.  * one at a time.  The message is removed from the pendingbufferbut remains on the 
  28.  * outbound* hashtable.  The hashtable is the place where thefull set of outstanding 
  29.  * messages are stored in memory. (Persistenceis only used at start up) 
  30.  *  
  31.  * 5) Receiver thread - receives wire messages: 
  32.  *  - if QoS 1 thenremove from persistence and outboundqos1 
  33.  *  - if QoS 2 PUBRECsend PUBREL. Updating the outboundqos2 entry with the PUBREL 
  34.  *    andupdate persistence. 
  35.  *  - if QoS 2 PUBCOMPremove from persistence and outboundqos2  
  36.  * 
  37.  * Notes: 
  38.  * because of the multithreaded natureof the client it is vital that any changes to this 
  39.  * class take concurrency into account.  For instance as soon as a flow / message isput on 
  40.  * the wire it is possible for the receivingthread to receive the ack and to be processing 
  41.  * the response before the sending side hasfinished processing.  For instance aconnect may 
  42.  * be sent, the conack received beforethe connect notify send has been processed! 
  43.  * 
  44.  */  

大致意思就是,程序已运行,但是消息链路还没有开启的情况下,我们从通过MqttClientPersistent 这个接口读取缓存信息;
Qos 2 的PUBLISH消息和 PUBREL 存储到outboundqos2 中,Qos 1的消息存到 outboundqos1 中。消息通过messgeId 作为可以来缓存,
messgeId的范围是1-65535,所以当缓存的值超做这个,消息就会替换掉。
每次发送的时候,客户端读取 pendingMessages,pendingFlows这两个vertor中的数据,初始的消息存pendingMessages,PUBREL消息存储
到 pendingFlows中,消息发送完成后移除,但是outboundQoS2 outboundQoS1等队列中的消息会保留直到接收线程中收到消息回应。
如果Qos 1 移除持久化数据和 outboundqos1 数据
如果Qos 为2 的 PUBREC 则返回 PUBREL响应,更新持久化数据与outboundqos1中消息状态为 PUBREL
如果Qos 2 的PUBCOM 则移除持久化中数据和 outboundqos2中消息。
具体流程可以对比查看 ClientStatesend函数

[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. public void send(MqttWireMessage message, MqttToken token) throws MqttException {  
  2.         final String methodName = "send";  
  3.         if (message.isMessageIdRequired() && (message.getMessageId() == 0)) {  
  4.             message.setMessageId(getNextMessageId());  
  5.         }  
  6.         if (token != null ) {  
  7.             try {  
  8.                 token.internalTok.setMessageID(message.getMessageId());  
  9.             } catch (Exception e) {  
  10.             }  
  11.         }  
  12.               
  13.         if (message instanceof MqttPublish) {  
  14.             synchronized (queueLock) {  
  15.                 if (actualInFlight >= this.maxInflight) {  
  16.                     //@TRACE 613= sending {0} msgs at max inflight window  
  17.                     log.fine(className, methodName, "613"new Object[]{new Integer(actualInFlight)});  
  18.   
  19.                     throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);  
  20.                 }  
  21.                   
  22.                 MqttMessage innerMessage = ((MqttPublish) message).getMessage();  
  23.                 //@TRACE 628=pending publish key={0} qos={1} message={2}  
  24.                 log.fine(className,methodName,"628"new Object[]{new Integer(message.getMessageId()), new Integer(innerMessage.getQos()), message});  
  25.   
  26.                 switch(innerMessage.getQos()) {  
  27.                     case 2:  
  28.                         outboundQoS2.put(new Integer(message.getMessageId()), message);  
  29.                         persistence.put(getSendPersistenceKey(message), (MqttPublish) message);  
  30.                         break;  
  31.                     case 1:  
  32.                         outboundQoS1.put(new Integer(message.getMessageId()), message);  
  33.                         persistence.put(getSendPersistenceKey(message), (MqttPublish) message);  
  34.                         break;  
  35.                 }  
  36.                 tokenStore.saveToken(token, message);  
  37.                 pendingMessages.addElement(message);  
  38.                 queueLock.notifyAll();  
  39.             }  
  40.         } else {  
  41.             //@TRACE 615=pending send key={0} message {1}  
  42.             log.fine(className,methodName,"615"new Object[]{new Integer(message.getMessageId()), message});  
  43.               
  44.             if (message instanceof MqttConnect) {  
  45.                 synchronized (queueLock) {  
  46.                     // Add the connect action at the head of the pending queue ensuring it jumps  
  47.                     // ahead of any of other pending actions.  
  48.                     tokenStore.saveToken(token, message);  
  49.                     pendingFlows.insertElementAt(message,0);  
  50.                     queueLock.notifyAll();  
  51.                 }  
  52.             } else {  
  53.                 if (message instanceof MqttPingReq) {  
  54.                     this.pingCommand = message;  
  55.                 }  
  56.                 else if (message instanceof MqttPubRel) {  
  57.                     outboundQoS2.put(new Integer(message.getMessageId()), message);  
  58.                     persistence.put(getSendConfirmPersistenceKey(message), (MqttPubRel) message);  
  59.                 }  
  60.                 else if (message instanceof MqttPubComp)  {  
  61.                     persistence.remove(getReceivedPersistenceKey(message));  
  62.                 }  
  63.                   
  64.                 synchronized (queueLock) {  
  65.                     if ( !(message instanceof MqttAck )) {  
  66.                         tokenStore.saveToken(token, message);  
  67.                     }  
  68.                     pendingFlows.addElement(message);  
  69.                     queueLock.notifyAll();  
  70.                 }  
  71.             }  
  72.         }  
  73.     }  
接下来我们在来查看下CommsReceiver 接收端的代码

[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. public void run() {  
  2.         final String methodName = "run";  
  3.         MqttToken token = null;  
  4.   
  5.         while (running && (in != null)) {  //无限循环  
  6.             try {  
  7.                 // @TRACE 852=network read message  
  8.                 log.fine(className, methodName, "852");  
  9. //阻塞式读取消息  
  10.                 MqttWireMessage message = in.readMqttWireMessage();  
  11.                 log("Receiver 852 message:" + message.toString());  
  12.                 if (message instanceof MqttAck) {  
  13.                     token = tokenStore.getToken(message);  
  14.                     if (token != null) {  
  15.                         synchronized (token) {  
  16.                             // Ensure the notify processing is done under a lock  
  17.                             // on the token  
  18.                             // This ensures that the send processing can  
  19.                             // complete before the  
  20.                             // receive processing starts! ( request and ack and  
  21.                             // ack processing  
  22.                             // can occur before request processing is complete  
  23.                             // if not!  
  24.                             //通知回复确认消息  
  25.                             clientState.notifyReceivedAck((MqttAck) message);  
  26.                         }  
  27.                     } else {  
  28.                         // It its an ack and there is no token then something is  
  29.                         // not right.  
  30.                         // An ack should always have a token assoicated with it.  
  31.                         throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);  
  32.                     }  
  33.                 } else {  
  34.                     //通知有新的消息达到,我们进入此查看  
  35.                     // A new message has arrived  
  36.                     clientState.notifyReceivedMsg(message);  
  37.                 }  
  38.             } catch (MqttException ex) {  
  39.                 // @TRACE 856=Stopping, MQttException  
  40.                 log.fine(className, methodName, "856"null, ex);  
  41.   
  42.                 log("Receiver 856:exception->" + ex.toString());  
  43.                 running = false;  
  44.                 // Token maybe null but that is handled in shutdown  
  45.                 clientComms.shutdownConnection(token, ex);  
  46.             } catch (IOException ioe) {  
  47.                 // @TRACE 853=Stopping due to IOException  
  48.                 log.fine(className, methodName, "853");  
  49.   
  50.                 log("Receiver 853:exception->" + ioe.getLocalizedMessage());  
  51.                 log("Receiver 853:exception->" + ioe.toString());  
  52.                 running = false;  
  53.                 // An EOFException could be raised if the broker processes the  
  54.                 // DISCONNECT and ends the socket before we complete. As such,  
  55.                 // only shutdown the connection if we're not already shutting  
  56.                 // down.  
  57.                 if (!clientComms.isDisconnecting()) {  
  58.                     clientComms.shutdownConnection(token, new MqttException(  
  59.                             MqttException.REASON_CODE_CONNECTION_LOST, ioe));  
  60.                 } // else {  
  61.             }  
  62.         }  
  63.   
  64.         // @TRACE 854=<  
  65.         log.fine(className, methodName, "854");  
  66.     }  
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. 点击进入  clientState.notifyReceivedMsg(message)  
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. protected void notifyReceivedMsg(MqttWireMessage message) throws MqttException {  
  2.         final String methodName = "notifyReceivedMsg";  
  3.         this.lastInboundActivity = System.currentTimeMillis();  
  4.   
  5.         // @TRACE 651=received key={0} message={1}  
  6.         log.fine(className, methodName, "651"new Object[] {  
  7.                 new Integer(message.getMessageId()), message });  
  8.           
  9.         if (!quiescing) {  
  10.             if (message instanceof MqttPublish) {  
  11.                 MqttPublish send = (MqttPublish) message;  
  12.                 switch (send.getMessage().getQos()) {  
  13.                 case 0:  
  14.                 case 1:  
  15.                     if (callback != null) {  
  16.                         callback.messageArrived(send);  
  17.                     }  
  18.                     break;  
  19.                 case 2:  
  20.                     persistence.put(getReceivedPersistenceKey(message),  
  21.                             (MqttPublish) message);  
  22.                     inboundQoS2.put(new Integer(send.getMessageId()), send);  
  23.                     this.send(new MqttPubRec(send), null);  
  24.                 }  
  25.             } else if (message instanceof MqttPubRel) {  
  26.                 MqttPublish sendMsg = (MqttPublish) inboundQoS2  
  27.                         .get(new Integer(message.getMessageId()));  
  28.                 if (sendMsg != null) {  
  29.                     if (callback != null) {  
  30.                         callback.messageArrived(sendMsg);  
  31.                     }  
  32.                 } else {  
  33.                     // Original publish has already been delivered.  
  34.                     MqttPubComp pubComp = new MqttPubComp(message  
  35.                             .getMessageId());  
  36.                     this.send(pubComp, null);  
  37.                 }  
  38.             }  
  39.         }  
  40.     }  
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. 这里可以看出,如果是Qos 1的消息或者Qos 2 MqttPubRel,我们直接回调告诉消息已到达,点击进入 callback.messageArrived(sendMsg);     
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. public void messageArrived(MqttPublish sendMessage) {  
  2.         final String methodName = "messageArrived";  
  3.         if (mqttCallback != null) {  
  4.             // If we already have enough messages queued up in memory, wait  
  5.             // until some more queue space becomes available. This helps  
  6.             // the client protect itself from getting flooded by messages  
  7.             // from the server.  
  8.             synchronized (spaceAvailable) {  
  9.                 if (!quiescing && messageQueue.size() >= INBOUND_QUEUE_SIZE) {  
  10.                     try {  
  11.                         // @TRACE 709=wait for spaceAvailable  
  12.                         log.fine(className, methodName, "709");  
  13.                         spaceAvailable.wait();  
  14.                     } catch (InterruptedException ex) {  
  15.                     }  
  16.                 }  
  17.             }  
  18.             if (!quiescing) {  
  19.                 messageQueue.addElement(sendMessage);  
  20.                 // Notify the CommsCallback thread that there's work to do...  
  21.                 synchronized (workAvailable) {  
  22.                     // @TRACE 710=new msg avail, notify workAvailable  
  23.                     log.fine(className, methodName, "710");  
  24.                     workAvailable.notifyAll();  
  25.                 }  
  26.             }  
  27.         }  
  28.     }  
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. 所做的操作就是将数据插入到 消息队列中,然后唤醒 workAvailable 这个锁,在 CommsCallback类中所有这个锁对应的地方,可以查看到  
[java]  view plain  copy   在CODE上查看代码片 派生到我的代码片
  1. public void run() {  
  2.     final String methodName = "run";  
  3.     while (running) {  
  4.         try {  
  5.             // If no work is currently available, then wait until there is  
  6.             // some...  
  7.             try {  
  8.                 synchronized (workAvailable) {  
  9.                     if (running & messageQueue.isEmpty() && completeQueue.isEmpty()) {  
  10.                         // @TRACE 704=wait for workAvailable  
  11.                         log.fine(className, methodName, "704");  
  12.                         workAvailable.wait();  
  13.                     }  
  14.                 }  
  15.             } catch (InterruptedException e) {  
  16.             }  
  17.   
  18.             if (running) {  
  19.                 // Check for deliveryComplete callbacks...  
  20.                 if (!completeQueue.isEmpty()) {  
  21.                     // First call the delivery arrived callback if needed  
  22.                     MqttToken token = (MqttToken) completeQueue.elementAt(0);  
  23.                     handleActionComplete(token);  
  24.                     completeQueue.removeElementAt(0);  
  25.                 }  
  26.   
  27.                 // Check for messageArrived callbacks...  
  28.                 if (!messageQueue.isEmpty()) {  
  29.                     // Note, there is a window on connect where a publish  
  30.                     // could arrive before we've  
  31.                     // finished the connect logic.  
  32.                     MqttPublish message = (MqttPublish) messageQueue.elementAt(0);  
  33.   
  34.                     handleMessage(message);  
  35.                     messageQueue.removeElementAt(0);  
  36.                 }  
  37.             }  
  38.   
  39.             if (quiescing) {  
  40.                 clientState.checkQuiesceLock();  
  41.             }  
  42.   
  43.             synchronized (spaceAvailable) {  
  44.                 // Notify the spaceAvailable lock, to say that there's now  
  45.                 // some space on the queue...  
  46.   
  47.                 // @TRACE 706=notify spaceAvailable  
  48.                 log.fine(className, methodName, "706");  
  49.                 spaceAvailable.notifyAll();  
  50.             }  
  51.         } catch (Throwable ex) {  
  52.             // Users code could throw an Error or Exception e.g. in the case  
  53.             // of class NoClassDefFoundError  
  54.             // @TRACE 714=callback threw exception  
  55.             log.fine(className, methodName, "714"null, ex);  
  56.             running = false;  
  57.             clientComms.shutdownConnection(nullnew MqttException(ex));  
  58.         }  
  59.     }  
  60. }  


程序通过handleActionComplete(token);handleMessage(message);通知用户发布一个消息完成和 有新的订阅消息到达。

三、总结
ClientState 类中, pendingMessages容器存放MqttPubish消息,而pendingFlows消息则存放 MqttPubRel,MqttConnect,MqttPingReq,MqttAck等
Send 方法将消息放入到容器中,同时唤醒等待发送的线程。
 
CommsSender 这个发送线程 通过 ClientStatele类get() 方法等待 pendingMessages和pendingFlows 这个这两个队列中放入消息,
如果有消息放入,且同时被唤醒,那么就执行消息发送操作。
 
CommsSender 接收线程中阻塞式获取消息根据不通的消息类型已经Qos level,通过CommsCallback及ClientStatele中notifyReceivedMsg 来执行相应的操作。


    


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于MQTT协议的 org.eclipse.paho.client.mqttv3 源码学习(二) 的相关文章

随机推荐

  • 数据结构之---C语言实现拓扑排序AOV图

    有向图的拓扑排序 杨鑫 include
  • React TypeScript

    1 安装 就像安装其他插件库一样 在项目文件夹下执行 npm install antd save 如果你安装了 yarn 也可以执行 yarn add antd 2 引用 import Button Tooltip from antd im
  • window下C语言中strtok函数的使用

    基础知识 原型 char strtok char str const char delim 功能 分解字符串为一组字符串 参数说明 str为要分解的字符串 delim为分隔符字符串 其中 str 不能用指针来存储 因为这个方法的本质是 找到
  • IOS 使用自定义View实现圆形布局(Swift)

    前面写过用安卓实现 还是同个需求 只不过现在需要做苹果版本 网上搜到了类似的案列点击打开链接只不过他的是用UICollectionView 跟我的需求有点不符合 没有搜到完全符合的案例 没办法自己写个 记录一下 也给有同种需求的童鞋填个坑
  • C#泛型List删除多个元素的方法

    泛型List如果删除一个 很简单 直接 RemoveAt index 即可 但如果有多个元素 那么删除起来并不是特别简单 需要使用 for 循环的倒叙删除 例子如下 class Program public class Students p
  • windows10安装linux环境

    Windows里玩转Linux 目标 一般的做法 神仙般的做法 可能会遇到的问题 目标 想要在windows里玩转linux 一般的做法 在windows里安装vmware或virtual box 新建一个虚拟机 在虚拟机里通过iso安装l
  • Python ttkbootstrap 制作账户注册信息界面

    前言 ttkbootstrap 是一个基于 tkinter 的界面美化库 使用这个工具可以开发出类似前端 bootstrap 风格的 tkinter 桌面程序 ttkbootstrap 不仅有丰富的案例 同时还有完善的官方文档 可惜是英文的
  • 宽高都200px的div在浏览器窗口居中(水平垂直都居中)

    1 fixed 从中间移动定位 position fixed width 200px height 200px left 50 top 50 margin left 100px margin top 100px 第一行设置完 盒子的左上角的
  • 国茂股份全面迁移到亚马逊云科技,降本增效,驱动业务增长

    亚马逊云科技宣布 中国通用机械工业减速机行业的标杆企业江苏国茂股份有限公司 简称 国茂股份 正在全面迁移到亚马逊云科技 在中国大陆 西云数据运营宁夏区域 光环新网运营北京区域 将ERP 企业资源计划系统 APS 高级计划与排程系统 MES
  • Docker安装使用阿里云镜像

    registry mirrors https kfwkfulq mirror aliyuncs com https 2lqq34jg mirror aliyuncs com https pee6w651 mirror aliyuncs co
  • 【满分】【华为OD机试真题2023 JS】最小的调整次数

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 最小的调整次数 知识点队列栈 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 有一个特异性的双端队列 该队列可以从头部或尾部添加数据 但是只能从头部移出数据 小A
  • dns服务器响应 异常,DNS云学堂|快速定位DNS解析异常问题,牢记这四种DNS状态码...

    DNS的状态码在进行故障排查的时候起着至关重要的作用 在DNS的维护中会经常遇到DNS解析异常问题 通过DNS的状态码可以初步判断DNS解析的异常问题 本期云学堂通过详解DNS状态码的定义 给出常见状态码的场景举例 enjoy 写在前面 本
  • (一)Matlab三日基础入门——矩阵和数组

    目录 创建数组 方式一 直接创建 方式二 调函数创建 zeros 功能 创建由0组成的数组 ones 功能 创建由1组成的数组 rand 功能 创建 0 1 之间均匀分布的随机数生成的数组 矩阵和数组运算 单一运算符 转置 行列互换 计算矩
  • [leetcode]刷题--关于位运算的几道题

    1 位运算的本质 其实是对二进制补码储存形式的修改 位运算常见的运算符为 lt lt 左移n个位置 算数移位 符号位不变 gt gt 右移动n个位置 采用直接丢弃末尾数字的方法 符号位不变 移位都是算数移位 按位取反 对于包括符号位在内全部
  • 两种公钥加密算法——Merkle-Hellman背包、RSA

    今天看了一些加密体制 很厉害 佩服之余顺便总结下公钥 对称密钥很多啊 历史比较有名的有DES AES RC系列 水平不够说不清楚 所以不写了 自己以后也要看 所以尽量通俗易懂 其实是不怎么会很官方很学术 顺道说 明天就七夕了 我还在搞些啥跟
  • ubuntu 20.04.4编译 继续尝试编译Android 12,13

    之前使用虚拟机编译过Android10 现在开始记录编译12 上次忘记给镜像了这次补上镜像ubuntu 20 04 4 desktop amd64 链接 https pan baidu com s 1REJ2cIJyqupLRQjN9SW0
  • 深搜Dfs遍历节点以及寻路

    深搜遍历从起点出发能走的所有节点 对于一个节点 只要发现了没走过的点就走到它 如果有多个点可走就任选一个 递归调用 由于是从起点开始遍历 因此遍历过程也是产生路径的过程 因此深搜遍历是有路径信息的 单纯的根据数据结构遍历所有点是没有路径信息
  • 关于ABAP批次或生产订单特性值的随记

    阅读目录 特性值 后台表 批次特性值 生产订单特性值 特性值 特性值是为了控制一些特定属性 特性字段可以在SAP里配置 以方便管控物料或者订单 后台表 特性值涉及到的SAP后台表有 CABN CABNT CAWN CAWNT KSML KL
  • Vue、Vue CLI、Webpack 和 Webpack CLI 的版本兼容对照表

    Vue Vue CLI Webpack 和 Webpack CLI 的版本兼容对照表 Vue Vue 2 x x Webpack 3 x Vue Loader 14 x Vue 3 x x Webpack 4 x Vue Loader 15
  • 基于MQTT协议的 org.eclipse.paho.client.mqttv3 源码学习(二)

    http blog csdn net chenbifeng article details 25067761 一 主要类介绍 二 重点类代码分析 对于长连接 一般是直接从消息的接收和发送类开始读 上面知道paho中消息发送和接收是在Comm