Android4.4 RIL軟體框架

来源:https://www.cnblogs.com/hackfun/archive/2019/10/17/11693444.html
-Advertisement-
Play Games

本文主要對android4.4 RIL的telephony與modem的命令交互流程進行分析,當然本文不是重點介紹telephony。telephony涉及具體業務邏輯內容比較多,包括sim、dail、sms、network等等,以後會針對這些內容學習分析。 RIL在Android體系中的位置: ( ...


    文主要對android4.4 RIL的telephony與modem的命令交互流程進行分析,當然本文不是重點介紹telephony。
telephony涉及具體業務邏輯內容比較多,包括sim、dail、sms、network等等,以後會針對這些內容學習分析。

RIL在Android體系中的位置:

 

(A) 應用層發起訪問modem的請求

(B) RILD進程

 

(A) 應用層發起訪問modem的請求

       frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java中的類RIL,提供了一系
列的介面給上層應用調用,以訪問modem。當然這些介面並不是直接給APP使用,而是由framework中sim、dail、
sms、network等相關服務調用。
如: 以查詢SIM卡狀態getIccCardStatus()為例,該API為UiccController模塊所調用:

完整的SIM卡請求log:
10-11 12:21:43.630 D/RILJ ( 1833): [3653]> GET_SIM_STATUS
10-11 12:21:43.630 D/RILC ( 1286): [0005]> GET_SIM_STATUS
10-11 12:21:43.630 D/RILC ( 1286): onRequest: GET_SIM_STATUS
10-11 12:21:43.630 D/ATC ( 1286): AT> AT+CPIN?
10-11 12:21:43.640 D/ATC ( 1286): AT< +CPIN: READY
10-11 12:21:43.640 D/ATC ( 1286): AT< OK
10-11 12:21:43.640 D/RILC ( 1286): [0005]< GET_SIM_STATUS {[app_type=1,app_state=5,perso_substate=2,aid_ptr=(null),app_label_ptr=(null),pin1_replaced=0,pin1=0,pin2=0],}
10-11 12:21:43.640 D/RILJ ( 1833): [3653]< GET_SIM_STATUS IccCardState {CARDSTATE_PRESENT,PINSTATE_UNKNOWN,num_apps=1,gsm_id=0{APPTYPE_SIM,APPSTATE_READY},cdma_id=8,ims_id=8}

發起請求:

       frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

@Override                                                                                                                            
    public void                                                                                                                          
    getIccCardStatus(Message result) {                                                                                                   
        //Note: This RIL request has not been renamed to ICC,                                                                            
        //       but this request is also valid for SIM and RUIM                                                                         
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_SIM_STATUS, result);             /* 獲取一個RILRequest */                                                       
                                                                                                                                         
        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));   /* 參考log:[3653]> GET_SIM_STATUS */                                              
                                                                                                                                         
        send(rr);                                                                          /* 發送請求 */                                                                                                                     
    }

 

       RILRequest.obtain是從記憶體池獲取一個RILRequest實例,並初始化:

       frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

class RILRequest {
            static final String LOG_TAG = "RilRequest";
            
            ... ...
            
            static RILRequest obtain(int request, Message result) {
                RILRequest rr = null;                                                                                                            
                                                                                                                                                 
                synchronized(sPoolSync) {   /* 從記憶體池中取出一個RILRequest對象*/                                                                
                    if (sPool != null) {                                                                                                         
                        rr = sPool;                                                                                                              
                        sPool = rr.mNext;                                                                                                        
                        rr.mNext = null;                                                                                                         
                        sPoolSize--;                                                                                                             
                    }                                                                                                                            
                }                                                                                                                                
                                                                                                                                                 
                if (rr == null) {           /* 如果記憶體池中沒有,則實例化一個 */                                                                 
                    rr = new RILRequest();                                                                                                       
                }                                                                                                                                
                                                                                                                                                 
                rr.mSerial = sNextSerial.getAndIncrement();   /* 獲取串號,並且+1 */                                                             
                                                                                                                                                 
                rr.mRequest = request;      /* 對應RIL_REQUEST_*請求 */                                                                          
                rr.mResult = result;                                                                                                             
                rr.mParcel = Parcel.obtain();   /* 初始化一個Parcel,用於封包以發送給底層RILD */                                                 
                                                                                                                                                 
                if (result != null && result.getTarget() == null) {                                                                              
                    throw new NullPointerException("Message target must not be null");                                                           
                }                                                                                                                                
                                                                                                                                                 
                // first elements in any RIL Parcel                                                                                              
                rr.mParcel.writeInt(request);   /* 每個RIL Parcel最開始也最基本的兩個元素 */                                                     
                rr.mParcel.writeInt(rr.mSerial);

                return rr;                  /* 返回這個從記憶體池中獲取的實例 */
            }
            
            ... ...
            
            String
            serialString() {
                //Cheesy way to do %04d
                StringBuilder sb = new StringBuilder(8);   /* 創建一個StringBuilder實例用於操作字元串 */
                String sn;

                long adjustedSerial = (((long)mSerial) - Integer.MIN_VALUE)%10000;

                sn = Long.toString(adjustedSerial);        /* 把數值轉換成字元串 */

                //sb.append("J[");
                sb.append('[');
                for (int i = 0, s = sn.length() ; i < 4 - s; i++) {
                    sb.append('0');
                }

                sb.append(sn);
                sb.append(']');
                return sb.toString();                      /* 轉換出來的字元串格式: [xxxx] */
            }
            
            ... ...
    }  

 

send(rr)發送請求到服務端:
frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

private void
    send(RILRequest rr) {                                                                                                                
        Message msg;                                                                                                                     
        
        ... ...                                                                                                                              
        
        msg = mSender.obtainMessage(EVENT_SEND, rr); /* 發送EVENT_SEND時間,時間參數為RILRequest */                                      
        
        acquireWakeLock();                           /* 獲取wakelock,禁止進入休眠 */                                                    
        
        msg.sendToTarget();                          /* message從handler類獲取,從而可以直接向該handler對象發送消息。target就是創建message的handler */
    } 

 

        實際上telephony無法直接與modem通訊,由於每個廠商的modem都不一樣,modem存在於系統中的方式
也不一樣,如:有的CPU晶元廠商的modem是以一個CP核的方式集成在基帶上(高通、展訊等),有的CPU芯
片(Exynos 4412等)需要通過串口/USB外接modem模塊,如:BC72 LTE模塊等。
send(rr)向RILD發送請求,這裡涉及一個進程間通信問題,而且Java側與C++側的進程通訊,當然這裡並沒
有用Android開發的朋友都熟悉的Bind,而是socket。
telephony/Java側RIL命令交互的處理,暫且稱為RILJ。
RILJ作為socket的客戶端,RILD(rild進程)作為服務端,後面會分析rild進程。

socket客戶端的創建:

        frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {                                                        
        ... ...
                                                                                                                                     
        mSenderThread = new HandlerThread("RILSender");               /* 創建RILSender線程 */                                                                             
        mSenderThread.start();                                                                                                           
                                                                                                                                         
        Looper looper = mSenderThread.getLooper();                                                                                       
        mSender = new RILSender(looper);                                                                                                 
                                                                                                                                         
        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(                                                          
                Context.CONNECTIVITY_SERVICE);                                                                                           
        if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {                                                           
            riljLog("Not starting RILReceiver: wifi-only");                                                                              
        } else {                                                                                                                         
            riljLog("Starting RILReceiver");                                                                                             
            mReceiver = new RILReceiver();                                                                                               
            mReceiverThread = new Thread(mReceiver, "RILReceiver");   /* 創建RILReceiver線程 */
            mReceiverThread.start();  
            
            ... ...   
        }
        
        ... ...
    }
    
    class RILReceiver implements Runnable {                                                                                              
        byte[] buffer;                                                                                                                   
        
        RILReceiver() {                                                     /* 構造時,分配一個數組 */                                   
            buffer = new byte[RIL_MAX_COMMAND_BYTES];                                                                                    
        }                                                                                                                                
        
        @Override
        public void                                                         
        run() {                                                             /* 迴圈讀取從RILD返回或主動上報的數據 */                     
            int retryCount = 0;                                                                                                          
            
            try {for (;;) { 
                LocalSocket s = null;                                                                                                    
                LocalSocketAddress l;                                                                                                    
                
                try { 
                    s = new LocalSocket();                                  /* 創建一個socket客戶端 */                                   
                    l = new LocalSocketAddress(SOCKET_NAME_RIL,
                            LocalSocketAddress.Namespace.RESERVED);         
                    s.connect(l);                                           /* 連接伺服器 */  
                }
                
                ... ...
            }
            
            ... ... 
            
            try {
                InputStream is = mSocket.getInputStream();                    /* 迴圈讀取socket的數據 */

                for (;;) {
                    Parcel p;

                    length = readRilMessage(is, buffer);                      /* 解析socket數據 */

                    if (length < 0) {
                        // End-of-stream reached
                        break;
                    }

                    p = Parcel.obtain();                                      /* 獲取一個Parcel */
                    p.unmarshall(buffer, 0, length);                          /* 讀取出來的就是之前序列化的byte數組,所以要進行一個反序列化操作 */
                    p.setDataPosition(0);                                     /* 從buffer轉換到Parcel之後,需要將指針手動指向到最初的位置 */

                    //Rlog.v(RILJ_LOG_TAG, "Read packet: " + length + " bytes");

                    processResponse(p);
                    p.recycle();                                              /* 數據處理完後,需要回收Parcel的記憶體 */
                }
            }
            
            ... ...
    }

 

        RILReceiver線程創建socket客戶端,連接服務端,然後進入等待服務端的processResponse消息處理迴圈,RILJ
接收到RILD回覆的response返回RIL請求的發起者,以getIccCardStatus(Message result)為例,processResponse(p)
會把DRILD的response返回給UiccController

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

private static int readRilMessage(InputStream is, byte[] buffer)
            throws IOException {
        int countRead;
        int offset;
        int remaining;
        int messageLength;

        // First, read in the length of the message
        offset = 0;
        remaining = 4;
        do {
            countRead = is.read(buffer, offset, remaining);                   /* 讀出消息的4位元組長度 */

            if (countRead < 0 ) {
                Rlog.e(RILJ_LOG_TAG, "Hit EOS reading message length");
                return -1;
            }

            offset += countRead;
            remaining -= countRead;
        } while (remaining > 0);

        messageLength = ((buffer[0] & 0xff) << 24)                            /* 獲取長度 */
                | ((buffer[1] & 0xff) << 16)
                | ((buffer[2] & 0xff) << 8)
                | (buffer[3] & 0xff);

        // Then, re-use the buffer and read in the message itself
        offset = 0;
        remaining = messageLength;
        do {
            countRead = is.read(buffer, offset, remaining);                   /* 讀取剩餘的數據 */

            if (countRead < 0 ) {
                Rlog.e(RILJ_LOG_TAG, "Hit EOS reading message.  messageLength=" + messageLength
                        + " remaining=" + remaining);
                return -1;
            }

            offset += countRead;
            remaining -= countRead;
        } while (remaining > 0);

        return messageLength;
    }

    private void
    processResponse (Parcel p) {
        int type;

        type = p.readInt();                          /* 從RILD返回的數據第一個位元組,表示請求的返回類型:RESPONSE_UNSOLICITED/RESPONSE_SOLICITED */

        if (type == RESPONSE_UNSOLICITED) {
            processUnsolicited (p);                  /* 主動上報 */
        } else if (type == RESPONSE_SOLICITED) {
            RILRequest rr = processSolicited (p);    /* 普通請求對應的同步上報 */
            if (rr != null) {
                rr.release();                        /* 釋放對應的RILRequest記憶體和wakelock */
                decrementWakeLock();
            }
        }
    }

 

        RILD的response一般有兩種,一種是RILJ普通請求,RILD對RILJ請求的response (RESPONSE_SOLICITED),另一種是RILD主動上報的
response (RESPONSE_UNSOLICITED), processResponse (Parcel p)分別對這兩種情況的response進行處理。

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

private RILRequest
    processSolicited (Parcel p) {
        int serial, error;
        boolean found = false;

        serial = p.readInt();                          /* 串號,也就是token */
        error = p.readInt();                           /* 錯誤碼 */

        RILRequest rr;

        rr = findAndRemoveRequestFromList(serial);     /* 根據taken取出對應的RILRequest */

        ... ...
             */
            case RIL_REQUEST_GET_SIM_STATUS: ret =  responseIccCardStatus(p); break;

        ... ...
        
            if (rr.mResult != null) {
                AsyncResult.forMessage(rr.mResult, ret, null);        /* 把rr.mResult存到AsyncResult.userObj,並把rr.mResult.obj轉換為AsyncResult */
                rr.mResult.sendToTarget();                            /* msg發送到對應的target(Handler) */
            }
            
        ... ...    
    }   
    
    private Object
    responseIccCardStatus(Parcel p) {
        IccCardApplicationStatus appStatus;
        
        ... ...
        
        appStatus = new IccCardApplicationStatus();
        
        ... ...

        return cardStatus;
    }

 

        回到剛纔send(rr),send(rr)並不是直接發送到socket服務端RILD,而是通過一個Message發送到RILSender線程,
在handleMessage中,把請求發到socket服務端RILD。

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java

class RILSender extends Handler implements Runnable {            /* 繼承Handler,實現Runnable */                                                         
        public RILSender(Looper looper) {                                                                                                                    
            super(looper);                                                                                                                                   
        }                                                                                                                                                    
        
        ... ...                                                                                                                                                                                                                                                                                                        
        
        //***** Handler implementation                                                                                                                       
        @Override public void 
        handleMessage(Message msg) {                                 /* 繼承Handler的handleMessage */
            RILRequest rr = (RILRequest)(msg.obj);                   /* Maessage中攜帶的RILRequest對象 */                                                    
            RILRequest req = null;                                                                                                                           
            
            switch (msg.what) {
                case EVENT_SEND:                                     /* 發送RIL請求事件 */                                                                   
                    try {
                        LocalSocket s;                                                                                                                       
                        
                        s = mSocket;                                 /* RILReceiver中創建的用於與RILD通訊的socket */                                         
                        
                        ... ...                                                                                                                                  
                        
                        synchronized (mRequestList) {                /* 多線程保護操作mRequestList */
                            mRequestList.append(rr.mSerial, rr);     /* 把接受到的RILRequest和對應的串號,存到mRequestList數據 */                            
                        }                                                                                                                                    

                        byte[] data;
                        data = rr.mParcel.marshall();                /* 把Parcel中的數據轉換為byte數據 */
                        rr.mParcel.recycle();                        /* Parcel的記憶體回收 */
                        rr.mParcel = null;

                        ... ...

                        // parcel length in big endian
                        dataLength[0] = dataLength[1] = 0;           /* RIL請求包的大小為4個位元組 */
                        dataLength[2] = (byte)((data.length >> 8) & 0xff);
                        dataLength[3] = (byte)((data.length) & 0xff);

                        //Rlog.v(RILJ_LOG_TAG, "writing packet: " + data.length + " bytes");

                        s.getOutputStream().write(dataLength);       /* 把包大小和包數據發送出去 */
                        s.getOutputStream().write(data);
                    } catch (IOException ex) {
                        Rlog.e(RILJ_LOG_TAG, "IOException", ex);
                        req = findAndRemoveRequestFromList(rr.mSerial);    /* 如果出現異常,則把串號對應的RILRequest從mRequestList中刪除 */
                        // make sure this request has not already been handled,
                        // eg, if RILReceiver cleared the list.
                        if (req != null) {
                            rr.onError(RADIO_NOT_AVAILABLE, null);
                            rr.release();
                            decrementWakeLock();
                        }
                    } 
                    
                    ... ...
            }
        }
    } 

 

 

(B) RILD進程

        RILD作為一個獨立的進程,telephony與modem之間的通訊通道。抽象出一些介面以適配不同的modem廠商,無需關心具體的
硬體操作,或者以哪種形式存存在於系統(modem作為CP集成於CPU或CPU通過串口/USB連接,如: BC72 LTE模塊)。因為這些介面
由廠商去實現具體的硬體操作細節,這些介面都在libreference-ril中,在Android中使用BC72 LTE模塊,只要移植
libreference-ril就行。

1. RILD的啟動
RILD有init進程直接啟動,啟動後就監聽RILJ客戶端,等待RILJ連接請求。
device/samsung/smdk4x12/conf/init.smdk4x12.rc

service ril-daemon /system/bin/rild -l /system/lib/libreference-ril.so
    class main
    socket rild stream 660 root radio
    socket rild-debug stream 660 radio system
    user root

 

        hardware/ril/rild/rild.c為RILD進程入口:

        hardware/ril/rild/rild.c

int main(int argc, char **argv)
        {
            ... ...
            
            dlHandle = dlopen(rilLibPath, RTLD_NOW);    /* 打開/system/lib/libreference-ril.so */

            if (dlHandle == NULL) {
                RLOGE("dlopen failed: %s", dlerror());
                exit(-1);
            }

            RIL_startEventLoop();                       /* 創建eventLoop線程, 在ril_event_loop()中監聽多路IO的事件,如主動喚醒事件(pipe)、RILJ的請求等 */

            /* 獲取/system/lib/libreference-ril.so中RIL_Init函數指針 */
            rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
            if (rilInit == NULL) {
                RLOGE("RIL_Init not defined or exported in %s\n", rilLibPath);
                exit(-1);
            }

            if (hasLibArgs) {
                rilArgv = argv + i - 1;
                argc = argc -i + 1;
            } else {
                static char * newArgv[MAX_LIB_ARGS];
                static char args[PROPERTY_VALUE_MAX];
                rilArgv = newArgv;
                property_get(LIB_ARGS_PROPERTY, args, "");
                argc = make_argv(args, rilArgv);
            }

            // Make sure there's a reasonable argv[0]
            rilArgv[0] = argv[0];

            funcs = rilInit(&s_rilEnv, argc, rilArgv);          /* 初始化Vender RIL */

            RIL_register(funcs);                                /* 註冊RIL */
            
            ... ...
        }

 

hardware/ril/libril/ril.cpp

extern "C" void
        RIL_startEventLoop(void) {
            
            ... ...
            
            ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);        /* 創建eventLoop線程 */

            ... ...
        }
        
        static void *
        eventLoop(void *param) {
            int ret;
            int filedes[2];

            ril_event_init();                                 /* 初始化事件鏈表,timer_list,pending_list, watch_table */

            pthread_mutex_lock(&s_startupMutex);

            s_started = 1;
            pthread_cond_broadcast(&s_startupCond);

            pthread_mutex_unlock(&s_startupMutex);

            ret = pipe(filedes);                              /* 創建一個pipe,用於每次添加一個新事件時,喚醒selet()返回,更新fd_set使select監聽新的事件 */

            if (ret < 0) {
                RLOGE("Error in pipe() errno:%d", errno);
                return NULL;
            }

            s_fdWakeupRead = filedes[0];                      /* filedes[0]用於讀pipe, filedes[1]用於寫pipe */
            s_fdWakeupWrite = filedes[1];

            fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);       /* 以非阻塞的方式讀pipe */

            ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,    /* 讀pipe描述符綁定到s_wakeupfd_event事件,指定回調processWakeupCallback */
                        processWakeupCallback, NULL);

            rilEventAddWakeup (&s_wakeupfd_event);            /* 添加s_wakeupfd_event事件到watch_table,更新readFds集合,使select監聽該事件,並觸發該事件 */

            // Only returns on error
            ril_event_loop();                                 /* 進入多路IO事件監聽迴圈 */
            RLOGE ("error in event_loop_base errno:%d", errno);
            // kill self to restart on error
            kill(0, SIGKILL);

            return NULL;
        }            

 

        main函數主要啟動eventLoop線程,在ril_event_loop()中監聽多路IO的事件,如主動喚醒事件(pipe)、RILJ的請求等,
註冊vendor RIL介面(libreference-ril)
        註意這裡pipe的主要作用是喚醒select返回,因為每次動態的添加一個事件,都要更新readFds集合,方便select監聽
集合中新的IO。
        rilEventAddWakeup()添加新事件後,都會觸發select返回

hardware/ril/libril/ril.cpp

static void rilEventAddWakeup(struct ril_event *ev) {
            ril_event_add(ev);       /* 添加事件 */
            triggerEvLoop();         /* 觸發事件, 每添加一個事件,都通過寫pipe喚醒select,以更新多路IO集合,使能夠監聽該事件 */
        }

 

hardware/ril/libril/ril_event.cpp

void ril_event_add(struct ril_event * ev)
        {   
            dlog("~~~~ +ril_event_add ~~~~");
            MUTEX_ACQUIRE();
            for (int i = 0; i < MAX_FD_EVENTS; i++) {
                if (watch_table[i] == NULL) {
                    watch_table[i] = ev;                  /* 將新事件添加到watch_table */
                    ev->index = i;
                    dlog("~~~~ added at %d ~~~~", i);
                    dump_event(ev);
                    FD_SET(ev->fd, &readFds);             /* 更新readFds集合 */
                    if (ev->fd >= nfds) nfds = ev->fd+1;  /* 更新nfds */
                    dlog("~~~~ nfds = %d ~~~~", nfds);
                    break;
                }                                 
            }       
            MUTEX_RELEASE();
            dlog("~~~~ -ril_event_add ~~~~");
        }

 

 hardware/ril/libril/ril.cpp

static void triggerEvLoop() {
            int ret;
            if (!pthread_equal(pthread_self(), s_tid_dispatch)) {
                /* trigger event loop to wakeup. No reason to do this,
                 * if we're in the event loop thread */
                 do {
                    ret = write (s_fdWakeupWrite, " ", 1);        /* 向pipe寫入一個" ",以喚醒select */
                 } while (ret < 0 && errno == EINTR);
            }
        } 

 

在ril_event_loop()接收到事件或socket客戶端RILJ發過來的請求後,firePending()根據事件請求,調用相應的處理函數
hardware/ril/libril/ril_event.cpp

void ril_event_loop()
        {
            int n;
            fd_set rfds;
            struct timeval tv;
            struct timeval * ptv;


            for (;;) {

                // make local copy of read fd_set
                memcpy(&rfds, &readFds, sizeof(fd_set));
                if (-1 == calcNextTimeout(&tv)) {                        /* 計算timer_list鏈表中每個事件對應的超時時間 */
                    // no pending timers; block indefinitely
                    dlog("~~~~ no timers; blocking indefinitely ~~~~");
                    ptv = NULL;
                } else {
                    dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec);
                    ptv = &tv;
                }
                printReadies(&rfds);
                n = select(nfds, &rfds, NULL, NULL, ptv);                /* 等待readFds集合中的事件喚醒 */
                printReadies(&rfds);
                dlog("~~~~ %d events fired ~~~~", n);
                if (n < 0) {
                    if (errno == EINTR) continue;

                    RLOGE("ril_event: select error (%d)", errno);
                    // bail?
                    return;
                }

                // Check for timeouts
                processTimeouts();                                       /* 檢查timer_list鏈表中是否有事件已經超時 */
                // Check for read-ready
                processReadReadies(&rfds, n);                            /* 從watch_table中取出監聽到的事件, 並添加到pending_list鏈表 */
                // Fire away
                firePending();                                           /* 從pending_list依次取出事件,並執行該事件的回調 */
            }
        }
        
        static void processTimeouts()
        {
            dlog("~~~~ +processTimeouts ~~~~");
            MUTEX_ACQUIRE();
            
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文翻譯自官網,官網地址:(https://docs.influxdata.com/influxdb/v1.7/query_language/data_exploration/) 在InfluxDB中查詢時,預設返回的數據時按照time升序排序的。而通過ORDER BY time DESC子句,可以 ...
  • 本文翻譯自官網,官網地址:(https://docs.influxdata.com/influxdb/v1.7/query_language/data_exploration/) GROUP BY子句通過用戶自己制定的tags set或time區間,來將查詢結果進行分組。 一、GROUP BY ta ...
  • 本文翻譯自官網,官網地址:(https://docs.influxdata.com/influxdb/v1.7/query_language/data_exploration/) WHERE子句 語法: 註 :在WHERE子句中,支持在fields, tags, and timestamps上進行條 ...
  • 本文翻譯自官網,官方文檔地址:(https://docs.influxdata.com/influxdb/v1.7/query_language/data_exploration/) InfluxQL是用於在InfluxDB中進行數據探索的類似於SQL的查詢語法。下麵將詳細介紹在InfluxDB中使 ...
  • 本文翻譯自官網,官方文檔地址: 1.下載官網示例數據 命令如下: 圖示: 可見,執行完命令之後,在當前目錄可以看到下載成功的數據NOAA_data.txt。通過cat命令還可以看到文件中的內容。 2.將數據導入到influxdb中 命令如下: 圖示: 3.查看結果 進入到InfluxDB中。如下圖, ...
  • 原文鏈接:https://www.cnblogs.com/su-root/p/9689787.html 作 者:小柏 出 處:https://www.cnblogs.com/su-root 關於博主:運維、編程路上的小學生,愛鑽研技術,評論和私信會在第一時間回覆。 版權聲明:本文版權歸作者和博客園共 ...
  • 0x00 基礎操作介紹 在本文中將介紹InfluxDB常用的基礎操作,幫助讀者建立對InfluxDB的感性認識,快速的動手玩起來,持續查詢(Continuous Queies)、Group by、Series、行協議(Line Protocol)、InfluxQL等高級特性和細節,將會在後續文章中逐 ...
  • 修改客戶表 編號為 0101007002,0101007003的樓棟號 007-1-102,007-1-201 UPDATE gas_customerSET building= CASEWHEN govid=380 and customer_no='0101007002' THEN '007-1-1 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...