[FPGA]淺談LCD1602字符型液晶显示器(Verilog)

概述

本文圍繞LCD1602字符型液晶显示器展開,並在FPGA開發板上用VerilogHDL語言實現模塊驅動.

首先來一張效果展示

那麼怎麼在這塊綠油油的平面上显示出點陣構成的字符呢?本文將為你提供一些思路.

注:本文僅討論寫入操作,實現在LCD1602上显示指定字符串,不講解讀取相關操作.

LCD1602

LCD1602是什麼?

LCD1602是一種字符型液晶显示模塊,不同於七段數碼管,它可以通過點陣的形式显示出各種圖案或字符,可拓展性較強.

其名稱中”LCD”即為 Liquid Crystal Display (液晶显示器),”1602″代表显示屏上可同時显示32個字符(16×2).

LCD1602的管腳

LCD1602共有16根管腳(部分型號只有14根,沒有背光管腳),管腳功能表如下

符號 管腳說明 符號 管腳說明
VSS 電源地 D2 數據
VDD 電源正極 D3 數據
VL 偏壓 D4 數據
RS 數據/命令選擇 D5 數據
R/W 讀/寫選擇 D6 數據
E 使能 D7 數據
D0 數據 BLA 背光正極
D1 數據 BLK 背光負極

其中需要我們關心的只有RS,E,和D0-D7.

RS_數據/命令選擇

RS端用來控制輸入給D0-D7的序列代表命令還是數據.

如果代表輸入命令,則輸入給D0-D7的序列相當於對模塊進行設置(下文會有輸入序列對應的指令表及其功能);如果代表輸入數據,則輸入給D0-D7的序列相當於寫入需要显示的字符串(輸入的是每個字符所對應的地址碼).

若RS為低電平,代表輸入命令;若RS為高電平,代表輸入數據.

E_使能

E端是用來執行命令的使能引腳,當它從高電平變成低電平時(下降沿),液晶模塊執行命令.

D0-D7

八位雙向并行數據線,在本文中僅作輸入端(寫入).

LCD1602有個DDRAM

DDRAM( Display Data Random Access Memory )即為显示數據隨機存取存儲器,相當於”顯存”,用來存放待显示的字符代碼.

DDRAM一共有80個字節,它和1602的显示屏上32個字符位的對應地址如下圖

第一行的16個字符位的地址對應0x00-0x0F,第二行則對應0x40-0x4F(“0x”代表16進制數).

LCD1602還有個CGROM

CGROM( Character Generator Read-Only Memory )即為字符產生只讀存儲器,用來存放192個常用字符的字模.

值得一提的是,表中的左半部分字符和他們的ASCII碼是對應的,所以在寫代碼時可以直接寫成”A”而不必要寫成”0x41″.

另外還有一個CGRAM用來存放用戶自定義的字符,可存放8個5×8字符或4個5×10字符,不過這不在本文討論範圍內.

指令集

前文已經提到,當RS為低電平時,代表輸入命令,那麼這些命令都有哪些呢?

將能實現某種功能的序列稱為一條命令,每條命令有幾個固定的位和幾個可變的位,可變的位可以改變功能/模式,將這些命令總稱為指令集.全體指令集如下錶

指令 RS R/W D7 D6 D5 D4 D3 D2 D1 D0
清屏 0 0 0 0 0 0 0 0 0 1
光標複位 0 0 0 0 0 0 0 0 0 x
進入模式設置 0 0 0 0 0 0 0 1 I/D S
显示開關設置 0 0 0 0 0 0 1 D C B
移位控制 0 0 0 0 0 1 S/C R/L x x
工作方式設置 0 0 0 0 1 DL N F x x
字符發生器地址設置 0 0 0 1 a a a a a a
數據存儲器地址設置 0 0 1 b b b b b b b
讀忙標誌或地址 0 1 BF c c c c c c c
寫入數據至CDRAM或DDRAM 1 0 d d d d d d d d
從CGRAM或DDRAM中讀取數據 1 1 e e e e e e e e

注:其中a代表字符發生存儲器地址,b代表显示數據存儲器地址,c代表計數器地址,d代表要寫入的數據內容,e代表讀取的數據內容.

我們關心的是其中的清屏,進入模式設置,显示開關設置,工作方式設置,數據存儲器地址設置.

清屏

清除屏幕显示內容,光標返回屏幕左上角.

執行這個指令時需要一定時間.

進入模式設置

I/D = 1:寫入新數據后光標右移,I/D = 0:寫入新數據后光標左移

S = 1:显示移動,S = 0:显示不移動.

显示開關設置

D = 1:显示功能開,D = 0,显示功能關(但是DDRAM中的數據依然保留).

C = 1:有光標,C = 0,沒有光標.

B = 1:光標閃爍,B = 0.光標不閃爍.

工作方式設置

DL = 1:8位數據接口(D7-D0),DL = 0:4位數據接口(D7-D4).

N = 0:一行显示,N = 1;兩行显示.

F = 0: 5×8點陣字符,F = 1: 5×10點陣字符.

數據存儲器地址設置

在對DDRAM進行讀寫之前,首先要設置DDRAM地址,然後才能進行讀寫.

地址設置見.

Verilog驅動

了解了1602的原理和功能后,就可以着手編寫驅動模塊了.想要讓LCD1602显示指定的字符,需要有一個驅動程序將模塊和用戶連接起來,實現輸入什麼就輸出什麼的功能,並能夠簡單的進行設置.

接下來開始寫驅動(造輪子).分為若干個次級模塊逐個分析.

模塊定義

模塊共有5個端口(其中8個數據端合為一個8位寬端口),分別為CLK時鐘輸入端,_RST低電平有效的複位端,LCD_E使能端,LCD_RS數據/命令選擇端,LCD_DATA數據端.

module LCD1602
(input CLK
,input _RST
,output LCD_E 
,output reg LCD_RS
,output reg[7:0]LCD_DATA
);

上電穩定

這是一個簡單的初始化模塊,數據手冊要求要先通電20ms才可以進行下一步操作,為了使之上電穩定.

parameter TIME_20MS=1_000_000;//需要20ms以達上電穩定(初始化)
reg[19:0]cnt_20ms;
always@(posedge CLK or negedge _RST)
    if(!_RST)
        cnt_20ms<=1'b0;
    else if(cnt_20ms==TIME_20MS-1'b1)
        cnt_20ms<=cnt_20ms;
    else
        cnt_20ms<=cnt_20ms+1'b1 ;

wire delay_done=(cnt_20ms==TIME_20MS-1'b1)?1'b1:1'b0;//上電延時完畢

工作周期分頻

LCD1602的工作周期為500Hz,所以要進行分頻(板載晶振為50MHz).

parameter TIME_500HZ=100_000;//工作周期
reg[19:0]cnt_500hz;
always@(posedge CLK or negedge _RST)
    if(!_RST)
        cnt_500hz<=1'b0;
    else if(delay_done)
        if(cnt_500hz==TIME_500HZ-1'b1)
            cnt_500hz<=1'b0;
        else
            cnt_500hz<=cnt_500hz+1'b1;
    else
        cnt_500hz<=1'b0;

assign LCD_E=(cnt_500hz>(TIME_500HZ-1'b1)/2)?1'b0:1'b1;//使能端,每個工作周期一次下降沿,執行一次命令
wire write_flag=(cnt_500hz==TIME_500HZ-1'b1)?1'b1:1'b0;//每到一個工作周期,write_flag置高一周期

狀態機

模塊工作採用狀態機驅動.

//狀態機有40種狀態,此處用了格雷碼,一次只有一位變化(在二進制下)
parameter IDLE=8'h00;
parameter SET_FUNCTION=8'h01;
parameter DISP_OFF=8'h03;
parameter DISP_CLEAR=8'h02;
parameter ENTRY_MODE=8'h06;
parameter DISP_ON=8'h07;
parameter ROW1_ADDR=8'h05;
parameter ROW1_0=8'h04;
parameter ROW1_1=8'h0C;
parameter ROW1_2=8'h0D;
parameter ROW1_3=8'h0F;
parameter ROW1_4=8'h0E;
parameter ROW1_5=8'h0A;
parameter ROW1_6=8'h0B;
parameter ROW1_7=8'h09;
parameter ROW1_8=8'h08;
parameter ROW1_9=8'h18;
parameter ROW1_A=8'h19;
parameter ROW1_B=8'h1B;
parameter ROW1_C=8'h1A;
parameter ROW1_D=8'h1E;
parameter ROW1_E=8'h1F;
parameter ROW1_F=8'h1D;
parameter ROW2_ADDR=8'h1C;
parameter ROW2_0=8'h14;
parameter ROW2_1=8'h15;
parameter ROW2_2=8'h17;
parameter ROW2_3=8'h16;
parameter ROW2_4=8'h12;
parameter ROW2_5=8'h13;
parameter ROW2_6=8'h11;
parameter ROW2_7=8'h10;
parameter ROW2_8=8'h30;
parameter ROW2_9=8'h31;
parameter ROW2_A=8'h33;
parameter ROW2_B=8'h32;
parameter ROW2_C=8'h36;
parameter ROW2_D=8'h37;
parameter ROW2_E=8'h35;
parameter ROW2_F=8'h34;

reg[5:0]c_state;//Current state,當前狀態
reg[5:0]n_state;//Next state,下一狀態

always@(posedge CLK or negedge _RST)
    if(!_RST)
        c_state<=IDLE;
    else if(write_flag)//每一個工作周期改變一次狀態
        c_state<=n_state;
    else
        c_state<=c_state;

always@(*)
    case (c_state)
        IDLE:n_state=SET_FUNCTION;
        SET_FUNCTION:n_state=DISP_OFF;
        DISP_OFF:n_state=DISP_CLEAR;
        DISP_CLEAR:n_state=ENTRY_MODE;
        ENTRY_MODE:n_state=DISP_ON;
        DISP_ON:n_state=ROW1_ADDR;
        ROW1_ADDR:n_state=ROW1_0;
        ROW1_0:n_state=ROW1_1;
        ROW1_1:n_state=ROW1_2;
        ROW1_2:n_state=ROW1_3;
        ROW1_3:n_state=ROW1_4;
        ROW1_4:n_state=ROW1_5;
        ROW1_5:n_state=ROW1_6;
        ROW1_6:n_state=ROW1_7;
        ROW1_7:n_state=ROW1_8;
        ROW1_8:n_state=ROW1_9;
        ROW1_9:n_state=ROW1_A;
        ROW1_A:n_state=ROW1_B;
        ROW1_B:n_state=ROW1_C;
        ROW1_C:n_state=ROW1_D;
        ROW1_D:n_state=ROW1_E;
        ROW1_E:n_state=ROW1_F;
        ROW1_F:n_state=ROW2_ADDR;
        ROW2_ADDR:n_state=ROW2_0;
        ROW2_0:n_state=ROW2_1;
        ROW2_1:n_state=ROW2_2;
        ROW2_2:n_state=ROW2_3;
        ROW2_3:n_state=ROW2_4;
        ROW2_4:n_state=ROW2_5;
        ROW2_5:n_state=ROW2_6;
        ROW2_6:n_state=ROW2_7;
        ROW2_7:n_state=ROW2_8;
        ROW2_8:n_state=ROW2_9;
        ROW2_9:n_state=ROW2_A;
        ROW2_A:n_state=ROW2_B;
        ROW2_B:n_state=ROW2_C;
        ROW2_C:n_state=ROW2_D;
        ROW2_D:n_state=ROW2_E;
        ROW2_E:n_state=ROW2_F;
        ROW2_F:n_state=ROW1_ADDR;//循環到1-1進行掃描显示
        default:;
    endcase

RS端控制

控制輸入為數據或命令

always@(posedge CLK or negedge _RST)
    if(!_RST)
        LCD_RS<=1'b0;//為0時輸入指令,為1時輸入數據
    else if(write_flag)
        //當狀態為七個指令任意一個,將RS置為指令輸入狀態
        if((n_state==SET_FUNCTION)||(n_state==DISP_OFF)||(n_state==DISP_CLEAR)||(n_state==ENTRY_MODE)||(n_state==DISP_ON)||(n_state==ROW1_ADDR)||(n_state==ROW2_ADDR))
            LCD_RS<=1'b0; 
        else
            LCD_RS<=1'b1;
    else
        LCD_RS<=LCD_RS;

显示控制

always@(posedge CLK or negedge _RST)
    if(!_RST)
        LCD_DATA<=1'b0;
    else if(write_flag)
        case(n_state)
            IDLE:LCD_DATA<=8'hxx;
            SET_FUNCTION:LCD_DATA<=8'h38;//8'b0011_1000,工作方式設置:DL=1(DB4,8位數據接口),N=1(DB3,兩行显示),L=0(DB2,5x8點陣显示).
            DISP_OFF:LCD_DATA<=8'h08;//8'b0000_1000,显示開關設置:D=0(DB2,显示關),C=0(DB1,光標不显示),D=0(DB0,光標不閃爍)
            DISP_CLEAR:LCD_DATA<=8'h01;//8'b0000_0001,清屏
            ENTRY_MODE:LCD_DATA<=8'h06;//8'b0000_0110,進入模式設置:I/D=1(DB1,寫入新數據光標右移),S=0(DB0,显示不移動)
            DISP_ON:LCD_DATA<=8'h0c;//8'b0000_1100,显示開關設置:D=1(DB2,显示開),C=0(DB1,光標不显示),D=0(DB0,光標不閃爍)
            ROW1_ADDR:LCD_DATA<=8'h80;//8'b1000_0000,設置DDRAM地址:00H->1-1,第一行第一位
            //將輸入的row_1以每8-bit拆分,分配給對應的显示位
            ROW1_0:LCD_DATA<=row_1[127:120];
            ROW1_1:LCD_DATA<=row_1[119:112];
            ROW1_2:LCD_DATA<=row_1[111:104];
            ROW1_3:LCD_DATA<=row_1[103: 96];
            ROW1_4:LCD_DATA<=row_1[ 95: 88];
            ROW1_5:LCD_DATA<=row_1[ 87: 80];
            ROW1_6:LCD_DATA<=row_1[ 79: 72];
            ROW1_7:LCD_DATA<=row_1[ 71: 64];
            ROW1_8:LCD_DATA<=row_1[ 63: 56];
            ROW1_9:LCD_DATA<=row_1[ 55: 48];
            ROW1_A:LCD_DATA<=row_1[ 47: 40];
            ROW1_B:LCD_DATA<=row_1[ 39: 32];
            ROW1_C:LCD_DATA<=row_1[ 31: 24];
            ROW1_D:LCD_DATA<=row_1[ 23: 16];
            ROW1_E:LCD_DATA<=row_1[ 15:  8];
            ROW1_F:LCD_DATA<=row_1[  7:  0];
            ROW2_ADDR:LCD_DATA<=8'hc0;//8'b1100_0000,設置DDRAM地址:40H->2-1,第二行第一位
            ROW2_0:LCD_DATA<=row_2[127:120];
            ROW2_1:LCD_DATA<=row_2[119:112];
            ROW2_2:LCD_DATA<=row_2[111:104];
            ROW2_3:LCD_DATA<=row_2[103: 96];
            ROW2_4:LCD_DATA<=row_2[ 95: 88];
            ROW2_5:LCD_DATA<=row_2[ 87: 80];
            ROW2_6:LCD_DATA<=row_2[ 79: 72];
            ROW2_7:LCD_DATA<=row_2[ 71: 64];
            ROW2_8:LCD_DATA<=row_2[ 63: 56];
            ROW2_9:LCD_DATA<=row_2[ 55: 48];
            ROW2_A:LCD_DATA<=row_2[ 47: 40];
            ROW2_B:LCD_DATA<=row_2[ 39: 32];
            ROW2_C:LCD_DATA<=row_2[ 31: 24];
            ROW2_D:LCD_DATA<=row_2[ 23: 16];
            ROW2_E:LCD_DATA<=row_2[ 15:  8];
            ROW2_F:LCD_DATA<=row_2[  7:  0];
        endcase
    else
        LCD_DATA<=LCD_DATA;

自定義字符輸入

輸入要显示的字符.

wire[127:0]row_1;
wire[127:0]row_2;
assign row_1 ="   Welcome to   ";//第一行显示的內容(16個字符)
assign row_2 ="    My Blog!    ";//第二行显示的內容(16個字符)

效果展示

將以上代碼有機整合后,燒錄至開發板上,按下複位鍵即可看到显示屏上显示出了指定字樣.

你可以修改字符串來讓屏幕显示出不同的內容,甚至可以調整模式讓显示屏滾動显示大於16字符的字符串.

總結

LCD1602是一個很基礎的模塊,把這個掌握后對以後的學習幫助很大,所以很有必要學習.

這個模塊不止可以通過Verilog驅動,也可以用其他語言或其他開發板來實現,例如STM32,51單片機或者SV,VHDL語言,都可以寫一套讓他工作的驅動.

另外,如果有現成的輪子,為什麼還要自己造一個出來呢?在碰到類似情況時可以藉助互聯網參考一下別人對此問題有怎麼樣的解決方案,加以借鑒並內化於心,才能達到最高效率的學習.

參考資料

[1] aslmer. “verilog寫的LCD1602 显示”[ED/OL]. https://www.cnblogs.com/aslmer/p/5819422.html ,2016(8).

[2] aslmer. “LCD1602指令集解讀”[ED/OL]. https://www.cnblogs.com/aslmer/p/5801363.html ,2016(8).

[3] 阿忠ZHONG. “單片機显示原理(LCD1602)”[ED/OL]. https://www.cnblogs.com/hui088/p/4732034.html 2015(8).

[4] 百度百科. “詞條-LCD1602″[ED/OL]. https://baike.baidu.com/item/LCD1602/6014393 ,2019(9).

[5] HITACHI©Ltd. “HD44780U (LCD-II)(Dot Matrix Liquid Crystal Display Controller/Driver)”[M]. Japan HITACHI,1998.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Viterbi(維特比)算法在CRF(條件隨機場)中是如何起作用的?

之前我們介紹過BERT+CRF來進行命名實體識別,並對其中的BERT和CRF的概念和作用做了相關的介紹,然對於CRF中的最優的標籤序列的計算原理,我們只提到了維特比算法,並沒有做進一步的解釋,本文將對維特比算法做一個通俗的講解,以便大家更好的理解CRF為什麼能夠得到最優的標籤序列。

通過閱讀本文你將能回答如下問題:

  • 什麼是維特比算法?
  • 為什麼說維特比算法是一種動態規劃算法?
  • 維特比算法具體怎麼實現?

首先,讓我們簡單回顧一下BERT和CRF在命名實體識別中各自的作用:
命名實體識別中,BERT負責學習輸入句子中每個字和符號到對應的實體標籤的規律,而CRF負責學習相鄰實體標籤之間的轉移規則。詳情可以參考這篇文章。該文章中我們對CRF做了簡單易懂的介紹,其中提到CRF的損失函數計算要用到最優路徑,因為CRF的損失函數是求最優路徑的概率占所有路徑概率和的比例,而我們的目標是最大化這個比例。那麼這裏就涉及到計算最優路徑的問題。這裏的路徑在命名實體識別的例子中,就是最終輸出的與句子中的字或符號一 一對應的標籤序列。不同標籤序列的順序組成了不同的路徑。而CRF就是要找出最正確的那條標籤序列路徑,也就是說這條標籤路徑的概率將是所有路徑中最大的,那麼我們可以窮舉出所有可能的標籤路徑,計算出每條路徑的概率和,然後比較出最大的那條,但是這樣做的代價太大了,所以crf選擇了一種稱為維特比的算法來求解此類問題。

維特比算法(英語:Viterbi algorithm)是一種動態規劃算法。它用於尋找最有可能產生觀測事件序列的維特比路徑。

看看下面這個命名實體識別的例子:

上圖共有5層(觀測序列的長度),每層3個節點(狀態的個數),我們的目標就是找到從第一層到第五層的最優路徑。
首先,我們分別計算紅、黃、藍三個節點的輸入連線的概率,以紅色節點舉例,我們先假設紅色節點在最優路徑上,那麼輸入到該節點的三條連線中,概率最大的那條一定在最優路徑上,同理,我們再分別假設黃色和藍色節點在最優路徑上,我們也能各找到一條概率最大的連線,這樣就得到了下面的圖:

然後,我們接着剛才的思路繼續找後面一層的三條最優的連線:

假設找到的最優連線如下:

然後,接着在後面的層應用這個方法:

此時,看上面最後一張圖,我們有了3條候選最優路徑,分別是棕色、綠色和紫色,用標籤來表達如下:

那麼哪條才是最優路徑呢?
就是看哪條路徑的概率和最大,那條路徑就是最優路徑。
但是在實際實現的時候,一般會在計算各層的最優候選連線的時候,就記錄下前繼連線的概率和,並記錄下對應的狀態節點索引(這裏將已經計算出的結果記錄下來供後續使用的方式,就是維特比算法被稱為動態規劃算法的原因),這樣到最後一層的時候,最後一層各候選連線中概率最大的,就是在最優路徑上的那條連線了,然後從這條連線回溯,找出完整的路徑就是最優路徑了。

一直在說概率最大的路徑,那麼這個概率具體指什麼呢?
還記得上一篇文章介紹條件隨機場(CRF)的時候提到,條件隨機場其實是給定了觀測序列的馬爾可夫隨機場,在一階馬爾可夫模型中,定義了以下三個概念:

  • 狀態集合Q,對應到上面的例子就是:
    {B-P, I-P, O}
  • 初始狀態概率向量Π,對應到上面的例子就是:
    {B-P:0.3, I-P:0.2, O:0.5}
    這裏的概率數值是隨便假設的,僅為了方便舉例說明。
  • 狀態轉移概率矩陣A:

CRF中給定了觀測序列做為先驗條件,對應到上面的例子就是:

其中的概率數值同樣是隨便假設的,為了方便舉例。

下圖中紅色節點的概率(可以看成是一個虛擬的開始節點到該節點的連線的概率)的計算方式如下:
初始狀態為B-P的概率Π(B-P) * 該節點的觀測概率P(小|B-P)

下圖中紅色節點的三條連線概率的計算方式如下:
上一層對應節點的概率 * 上層對應節點到該節點的轉移概率 * 該節點的觀測概率P(明|B-P)

其它層之間的節點連線的概率同理計算可得,然後通過上面介紹的維特比算法過程就可以計算出最優路徑了。

ok,本篇就這麼多內容啦~,感謝閱讀O(∩_∩)O。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Alibaba Nacos 學習(五):K8S Nacos搭建,使用nfs

 

準備環境

Centos7  192.168.50.21 k8s-master 2G
Centos7  192.168.50.22 k8s-node01 2G
Centos7  192.168.50.23 k8s-node02 2G

K8S集群搭建參考 

 

master安裝好Git ,yum install git

master,node01,node02  安裝 nfs-utils

yum install nfs-utils

master,node01,node02添加nfs exports配置,為了解決後續的nfs報錯異常

/data/mysql-slave *(insecure,fsid=0,rw,async,no_root_squash)
/data/mysql-master *(insecure,fsid=0,rw,async,no_root_squash)
/data/nfs-share *(rw,fsid=0,sync,no_root_squash)
mysql-slave 數據庫從庫 
mysql-master 數據庫主庫
nfs-share nocas文件掛在目錄

後面的yml中會提到
master,node01,node02創建目錄
mkdir /data/mysql-slave
mkdir /data/mysql-master
mkdir /data/nfs-share 

 master 克隆代碼

   git clone https://github.com/nacos-group/nacos-k8s.git

克隆完成進入以下目錄

 cd /opt/nacos-k8s/deploy/

 

1.nfs安裝

kubectl create -f nfs/rbac.yaml 
kubectl create -f nfs/class.yaml 

修改nfs/deployment.yaml IP配置

 

 

 

kubectl create -f nfs/deployment.yaml

查看安裝狀態

kubectl get pod -l app=nfs-client-provisioner

 

 

 

2.mysql部署

cd /opt/nacos-k8s/deploy/mysql/

修改數據配置文件ip

vi mysql-master-nfs.yaml

 

 

 部署主庫

kubectl create -f mysql-master-nfs.yaml 

修改存庫ip

vi mysql-slave-nfs.yaml
kubectl create -f mysql-slave-nfs.yaml 

主從部署非常慢 耐心等待,如果報nfs相關的錯,重啟nfs即可

service nfs restart

 

 

3. 部署nacos

cd /opt/nacos-k8s/deploy/nacos/

 

 

 

 

 

kubectl create -f nacos-pvc-nfs.yaml 

 查看訪問端口

kubectl get svc|grep nacos

 

 

 

 

 查看K8S集群狀態

 

 Failed to pull image “nacos/nacos-server:latest”: rpc error: code = Unknown desc = context canceled

進去對應節點機器 ,拉取鏡像后,重新應用即可

kubectl apply -f

 4. 部署問題

部署過程中大部分都是NFS問題

可以參考

mount.nfs: No route to host
Warning FailedMount 100s (x5 over 10m) kubelet, node2 Unable to mount volumes for pod “nfs-client-provisioner-594f778474-whhb5_default(56aef93a-9d31-11e9-a4c4-00163e069f44)”: timeout expired waiting for volumes to attach or mount for pod “default”/”nfs-client-provisioner-594f778474-whhb5”. list of unmounted volumes=[nfs-client-root]. list of unattached volumes=[nfs-client-root nfs-client-provisioner-token-8dcrx]

修改deployment.yaml中server的IP地址為某個node節點的內網IP地址,圖1已標註

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

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

Redis 4.0鮮為人知的功能將加速您的應用程序

來源:Redislabs

作者:Kyle Davis

翻譯:Kevin (公眾號:中間件小哥)

Redis 4.0給Redis生態帶來了一個驚人的功能:Modules(模塊)。Modules是Redis的一大轉變,它是Redis內部自定義數據類型和全速計算的開放環境。但是,儘管對該版本的大多數關注都集中在Modules上,但新版本還引入了一個非常重要的命令,它就是遊戲規則的改變者:UNLINK。

您可以使用redis-cli連接redis-server執行info命令,去查看當前redis版本中是否可以使用UNLINK命令。info響應將告訴您有關服務器的所有信息。在第一部分(#Server)中,返回結果有一行值為redis_version。如果該值大於4.0,則可以使用UNLINK命令。並非所有Redis提供商都保持最新版本,因此最好在更改代碼之前檢查redis版本。

讓我們回顧一下Redis的關鍵架構功能之一:“單線程”。Redis在大多數情況下是一個單線程應用程序。它一次只做一件事,這樣可以把這些事做的更快。多線程有點複雜,並且引入了鎖和其他可能降低應用程序速度的問題。儘管Redis(最高4.0版)通過多線程方式執行了少量操作,但它通常在啟動另一個命令之前先要完成一個命令。

相比於快速讀寫,您可能會覺得使用DEL命令去刪除一個鍵值不需要考慮太多,但是在很多情況下,刪除數據同樣很重要。與Redis中的大多數命令一樣,DEL命令在單個線程中運行,如果您獲取一個幾千字節的鍵值,花費不到一毫秒的時間,這是您所感知不到的。然而,當您獲取的鍵值大小是兆字節、100兆字節或者500兆字節會發生什麼呢?哈希、排序、列表等數據結構會隨着時間的推移而添加更多的數據進去,這樣會生成一個數GB大小的數據集。然後用DEL命令去刪除大Key時會發生什麼呢?由於Redis是單線程操作的,處理這種請求時整個服務都處於等待中,需要等待該命令執行完成才能執行其它操作。同時,我們考慮更複雜的一種場景,這些鍵中保存的數據可能已經包含數以千萬個微小請求,因此應用程序或操作員可能無法真正了解刪除這些數據需要花費多長時間。

理智會告訴我們不要在擁有100萬元素的排序集上運行如下這樣的命令:

> ZRANGE some-zset 0 -1

但是,在上面的some-zset集合中執行DEL命令將花費和上面一樣的時間-中間沒有傳輸開銷,但是它會一直去分配內存,而且您會一直卡死在CPU繁忙中。在使用UNLINK之前,您可能會結合SCAN命令採用非原子性的方法進行一些少量刪除,去避免這種持續分配內存的噩夢。上面無論使用哪種方式,都是讓人無法接受的。

您可能已經猜到了,就是使用UNLINK命令來替換DEL!從語法上講,UNLINK與DEL相同,但UNLINK提供了更為理想的解決方案。首先,它將鍵值從整個鍵值空間中刪除。然後,在另一個線程中,它開始回收內存。從多線程的角度來看,這是一種安全的操作,因為它(在主線程中)從鍵空間中刪除了該項,從而使Redis其它命令無法訪問。

如果你有一個快速增長的鍵值-不管鍵值的大小如何,UNLINK都是O(1)操作(每個鍵;在主線程中)。使用DEL刪除一個大值可能需要幾百毫秒或更長時間,而UNLINK將在不到一毫秒的時間內完成(包括網絡往返)。當然,您的服務器仍將需要花一些時間在另一個線程中重新分配該值的內存(其中的工作是O(N),其中N是已刪除值的分配數),但是主線程的性能不會被另一個線程中正在進行的操作嚴重影響到。

因此,您是否應該用UNLINK命令替換代碼中的所有DEL命令?當然,在少數情況下,DEL正是您所需要的。這裏我可以想到兩點:

1、   在MULTI / EXEC或pipeline中,在添加和刪除大值時DEL命令是一種理想選擇。在這種情況下,UNLINK不會立即釋放空間,並且在處理繁忙的情況下(如果內存已滿),您可能會遇到麻煩。

2、   在更緊急的情況下,在無快速響應驅逐數據下您可以寫入數據。

在沒有極端內存限制的理想環境中,很難想到不使用UNLINK的情況。UNLINK將提供更一致的行為,總體上具有更好的性能,並且代碼更改非常小(如果可以在客戶端中重命名命令,則無需更改)。如果UNLINK適合您的應用程序,請就此將您的DEL更改為UNLINK,然後查看它的性能提高。

 

更多優質中間件技術資訊/原創/翻譯文章/資料/乾貨,請關注“中間件小哥”公眾號!

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Java 數據持久化系列之JDBC

前段時間小冰在工作中遇到了一系列關於數據持久化的問題,在排查問題時發現自己對 Java 後端的數據持久化框架的原理都不太了解,只有不斷試錯,因此走了很多彎路。於是下定決心,集中精力學習了持久化相關框架的原理和實現,總結出這個系列。

上圖是我根據相關源碼和網上資料總結的有關 Java 數據持久化的架構圖(只代表本人想法,如有問題,歡迎留言指出)。最下層就是今天要講的 JDBC,上一層是數據庫連接池層,包括 HikariCP 和 Druid等;再上一層是分庫分表中間件,比如說 ShardingJDBC;再向上是對象關係映射層,也就是 ORM,包括 Mybatis 和 JPA;最上邊是 Spring 的事務管理。

本系列的文章會依次講解圖中各個開源框架的基礎使用,然後描述其原理和代碼實現,最後會着重分析它們之間是如何相互集成和配合的。

廢話不多說,我們先來看 JDBC。

JDBC 定義

JDBC是Java Database Connectivity的簡稱,它定義了一套訪問數據庫的規範和接口。但它自身不參与數據庫訪問的實現。因此對於目前存在的數據庫(譬如Mysql、Oracle)來說,要麼數據庫製造商本身提供這些規範與接口的實現,要麼社區提供這些實現。

如上圖所示,Java 程序只依賴於 JDBC API,通過 DriverManager 來獲取驅動,並且針對不同的數據庫可以使用不同的驅動。這是典型的橋接的設計模式,把抽象 Abstraction 與行為實現Implementation 分離開來,從而可以保持各部分的獨立性以及應對他們的功能擴展。

JDBC 基礎代碼示例

單純使用 JDBC 的代碼邏輯十分簡單,我們就以最為常用的MySQL 為例,展示一下使用 JDBC 來建立數據庫連接、執行查詢語句和遍歷結果的過程。

public static void connectionTest(){

    Connection connection = null;
    Statement statement = null;
    ResultSet resultSet = null;

    try {
        // 1. 加載並註冊 MySQL 的驅動
        Class.forName("com.mysql.cj.jdbc.Driver").newInstance();

        // 2. 根據特定的數據庫連接URL,返回與此URL的所匹配的數據庫驅動對象
        Driver driver = DriverManager.getDriver(URL);
        // 3. 傳入參數,比如說用戶名和密碼
        Properties props = new Properties();
        props.put("user", USER_NAME);
        props.put("password", PASSWORD);

        // 4. 使用數據庫驅動創建數據庫連接 Connection
        connection = driver.connect(URL, props);

        // 5. 從數據庫連接 connection 中獲得 Statement 對象
        statement = connection.createStatement();
        // 6. 執行 sql 語句,返回結果
        resultSet = statement.executeQuery("select * from activity");
        // 7. 處理結果,取出數據
        while(resultSet.next())
        {
            System.out.println(resultSet.getString(2));
        }

        .....
    }finally{
        // 8.關閉鏈接,釋放資源  按照JDBC的規範,使用完成后管理鏈接,
        // 釋放資源,釋放順序應該是: ResultSet ->Statement ->Connection
        resultSet.close();
        statement.close();
        connection.close();
    }
}

代碼中有詳細的註釋描述每一步的過程,相信大家也都對這段代碼十分熟悉。

唯一要提醒的是使用完之後的資源釋放順序。按照 JDBC 規範,應該依次釋放 ResultSet,Statement 和 Connection。當然這隻是規範,很多開源框架都沒有嚴格的執行,但是 HikariCP卻嚴格准守了,它可以帶來很多優勢,這些會在之後的文章中講解。

上圖是 JDBC 中核心的 5 個類或者接口的關係,它們分別是 DriverManager、Driver、Connection、Statement 和 ResultSet。

DriverManager 負責管理數據庫驅動程序,根據 URL 獲取與之匹配的 Driver 具體實現。Driver 則負責處理與具體數據庫的通信細節,根據 URL 創建數據庫連接 Connection。

Connection 表示與數據庫的一個連接會話,可以和數據庫進行數據交互。Statement 是需要執行的 SQL 語句或者存儲過程語句對應的實體,可以執行對應的 SQL 語句。ResultSet 則是 Statement 執行后獲得的結果集對象,可以使用迭代器從中遍曆數據。

不同數據庫的驅動都會實現各自的 Driver、Connection、Statement 和 ResultSet。而更為重要的是,眾多數據庫連接池和分庫分表框架也都是實現了自己的 Connection、Statement 和 ResultSet,比如說 HikariCP、Druid 和 ShardingJDBC。我們接下來會經常看到它們的身影。

接下來,我們依次看一下這幾個類及其涉及的操作的原理和源碼實現。

載入 Driver 實現

可以直接使用 Class#forName的方式來載入驅動實現,或者在 JDBC 4.0 后則基於 SPI 機制來導入驅動實現,通過在 META-INF/services/java.sql.Driver 文件中指定實現類的方式來導入驅動實現,下面我們就來看一下兩種方式的實現原理。

Class#forName 作用是要求 JVM 查找並加載指定的類,如果在類中有靜態初始化器的話,JVM 會執行該類的靜態代碼段。加載具體 Driver 實現時,就會執行 Driver 中的靜態代碼段,將該 Driver 實現註冊到 DriverManager 中。我們來看一下 MySQL 對應 Driver 的具體代碼。它就是直接調用了 DriverManager的 registerDriver 方法將自己註冊到其維護的驅動列表中。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        // 直接調用 DriverManager的 registerDriver 將自己註冊到其中
        DriverManager.registerDriver(new Driver());
    }
}

SPI 機制使用 ServiceLoader 類來提供服務發現機制,動態地為某個接口尋找服務實現。當服務的提供者提供了服務接口的一種實現之後,必須根據 SPI 約定在 META-INF/services 目錄下創建一個以服務接口命名的文件,在該文件中寫入實現該服務接口的具體實現類。當服務調用 ServiceLoader 的 load 方法的時候,ServiceLoader 能夠通過約定的目錄找到指定的文件,並裝載實例化,完成服務的發現。

DriverManager 中的 loadInitialDrivers 方法會使用 ServiceLoader 的 load 方法加載目前項目路徑下的所有 Driver 實現。

public class DriverManager {
    // 程序中已經註冊的Driver具體實現信息列表。registerDriver類就是將Driver加入到這個列表
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    // 使用ServiceLoader 加載具體的jdbc driver實現
    static {
        loadInitialDrivers();
    }
    private static void loadInitialDrivers() {
        // 省略了異常處理
        // 獲得系統屬性 jdbc.drivers 配置的值
        String drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                // 通過 ServiceLoader 獲取到Driver的具體實現類,然後加載這些類,會調用其靜態代碼塊
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
                return null;
            }
        });

        String[] driversList = drivers.split(":");
        // for 循環加載系統屬性中的Driver類。
        for (String aDriver : driversList) {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        }
    }
}

比如說,項目引用了 MySQL 的 jar包 mysql-connector-java,在這個 jar 包的 META-INF/services 文件夾下有一個叫 java.sql.Driver 的文件,文件的內容為 com.mysql.cj.jdbc.Driver。而 ServiceLoader 的 load 方法找到這個文件夾下的文件,讀取文件的內容,然後加載出文件內容所指定的 Driver 實現。而正如之前所分析的,這個 Driver 類被加載時,會調用 DriverManager 的 registerDriver 方法,從而完成了驅動的加載。

Connection、Statement 和 ResultSet

當程序加載完具體驅動實現后,接下來就是建立與數據庫的連接,執行 SQL 語句並且處理返回結果了,其過程如下圖所示。

建立 Connection

創建 Connection 連接對象,可以使用 Driver 的 connect 方法,也可以使用 DriverManager 提供的 getConnection 方法,此方法通過 url 自動匹配對應的驅動 Driver 實例,然後還是調用對應的 connect 方法返回 Connection 對象實例。

建立 Connection 會涉及到與數據庫進行網絡請求等大量費時的操作,為了提升性能,往往都會引入數據庫連接池,也就是說復用 Connection,免去每次都創建 Connection 所消耗的時間和系統資源。

Connection 默認情況下,對於創建的 Statement 執行的 SQL 語句都是自動提交事務的,即在 Statement 語句執行完后,自動執行 commit 操作,將事務提交,結果影響到物理數據庫。為了滿足更好地事務控制需求,我們也可以手動地控制事務,手動地在Statement 的 SQL 語句執行後進行 commit 或者rollback。

connection = driver.connect(URL, props);
// 將自動提交關閉
connection.setAutoCommit(false);

statement = connection.createStatement();
statement.execute("INSERT INTO activity (activity_id, activity_name, product_id, start_time, end_time, total, status, sec_speed, buy_limit, buy_rate) VALUES (1, '香蕉大甩賣', 1, 530871061, 530872061, 20, 0, 1, 1, 0.20);");
// 執行後手動 commit
statement.getConnection().commit();

Statement

Statement 的功能在於根據傳入的 SQL 語句,將傳入 SQL 經過整理組合成數據庫能夠識別的執行語句(對於靜態的 SQL 語句,不需要整理組合;而對於預編譯SQL 語句和批量語句,則需要整理),然後傳遞 SQL 請求,之後會得到返回的結果。對於查詢 SQL,結果會以 ResultSet 的形式返回。

當你創建了一個 Statement 對象之後,你可以用它的三個執行方法的任一方法來執行 SQL 語句。

  • boolean execute(String SQL) : 如果 ResultSet 對象可以被檢索,則返回的布爾值為 true ,否則返回 false 。當你需要使用真正的動態 SQL 時,可以使用這個方法來執行 SQL DDL 語句。
  • int executeUpdate(String SQL) : 返回執行 SQL 語句影響的行的數目。使用該方法來執行 SQL 語句,是希望得到一些受影響的行的數目,例如,INSERT,UPDATE 或 DELETE 語句。
  • ResultSet executeQuery(String SQL) : 返回一個 ResultSet 對象。當你希望得到一個結果集時使用該方法,就像你使用一個 SELECT 語句。

對於不同類型的 SQL 語句,Statement 有不同的接口與其對應。

接口 | 介紹
-|-| Statement | 適合運行靜態 SQL 語句,不接受動態參數 PreparedStatement | 計劃多次使用並且預先編譯的 SQL 語句,接口需要傳入額外的參數 CallableStatement | 用於訪問數據庫存儲過程

Statement 主要用於執行靜態SQL語句,即內容固定不變的SQL語句。Statement每執行一次都要對傳入的SQL語句編譯一次,效率較差。而 PreparedStatement則解決了這個問題,它會對 SQL 進行預編譯,提高了執行效率。

PreparedStatement pstmt = null;
    try {
        String SQL = "Update activity SET activity_name = ? WHERE activity_id = ?";
        pstmt = connection.prepareStatement(SQL);
        pstmt.setString(1, "測試");
        pstmt.setInt(2, 1);
        pstmt.executeUpdate();
    }
    catch (SQLException e) {
    }
    finally {
        pstmt.close();
    }
}

除此之外, PreparedStatement 還可以預防 SQL 注入,因為 PreparedStatement 不允許在插入參數時改變 SQL 語句的邏輯結構。

PreparedStatement 傳入任何數據不會和原 SQL 語句發生匹配關係,無需對輸入的數據做過濾。如果用戶將”or 1 = 1”傳入賦值給佔位符,下述SQL 語句將無法執行:select * from t where username = ? and password = ?。

ResultSet

當 Statement 查詢 SQL 執行后,會得到 ResultSet 對象,ResultSet 對象是 SQL語句查詢的結果集合。ResultSet 對從數據庫返回的結果進行了封裝,使用迭代器的模式可以逐條取出結果集中的記錄。

while(resultSet.next()) {
    System.out.println(resultSet.getString(2));
}

ResultSet 一般也建議使用完畢直接 close 掉,但是需要注意的是關閉 ResultSet 對象不關閉其持有的 Blob、Clob 或 NClob 對象。 Blob、Clob 或 NClob 對象在它們被創建的的事務期間會一直持有效,除非其 free 函數被調用。

參考

    • https://blog.csdn.net/wl044090432/article/details/60768342
    • https://blog.csdn.net/luanlouis/article/details/29850811

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

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

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

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

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

http協議詳細介紹

1.1 HTTP協議簡介
我們日常生活中經常會使用瀏覽器訪問Web站點,但是大家有思考過在這個過程中到底發生了什麼嗎?為什麼我們在瀏覽器地址欄上面輸入要訪問的URL后就可以訪問到Web頁面呢?

1.1.1 瀏覽器背後的故事
當我們在瀏覽器地址欄上輸入要訪問的URL后,瀏覽器會分析出URL上面的域名,然後通過DNS服務器查詢出域名映射的IP地址,瀏覽器根據查詢到的IP地址與Web服務器進行通信,而通信的協議就是HTTP協議。

我們可以把這個過程類比成一個電話對話的過程。當我們要打電話給某個人,首先要知道對方的電話號碼,然後進行撥號。打通電話后我們會進行對話,當然要對話肯定需要共同的語言,如果一個人說國語,而另一個人說英語,那肯定不能進行溝通的。在本例中,電話號碼相當於上面的IP地址,而共同語言相當於HTTP協議。

我們通過一個簡單的圖來闡述這個過程:

瀏覽器與Web服務器使用HTTP協議進行通信,那麼什麼是HTTP協議呢?接下來我們會詳細介紹HTTP協議的相關知識。

1.1.2 TCP/IP協議
HTTP協議是構建在TCP/IP協議之上的,是TCP/IP協議的一個子集,所以要理解HTTP協議,有必要先了解下TCP/IP協議相關的知識。

由於TCP/IP協議族包含眾多的協議,在這裏我們無法一一討論。接下來,我們僅介紹理解HTTP協議需要掌握的TCP/IP協議族的一些相關知識點。如果想深入理解TCP/IP協議,可以參考經典書籍《TCP/IP詳解》。

TCP/IP協議族分層

TCP/IP協議族是由一個四層協議組成的系統,這四層分別為:應用層、傳輸層、網絡層和數據鏈路層。如圖所示

分層的好處是把各個相對獨立的功能解耦,層與層之間通過規定好的接口來通信。如果以後需要修改或者重寫某一個層的實現,只要接口保持不變也不會影響到其他層的功能。接下來,我們將會介紹各個層的主要作用。

1) 應用層

應用層一般是我們編寫的應用程序,其決定了向用戶提供的應用服務。應用層可以通過系統調用與傳輸層進行通信。

處於應用層的協議非常多,比如:FTP(File Transfer Protocol,文件傳輸協議)、DNS(Domain Name System,域名系統)和我們本章討論的HTTP(HyperText Transfer Protocol,超文本傳輸協議)等。

2) 傳輸層

傳輸層通過系統調用嚮應用層提供處於網絡連接中的兩台計算機之間的數據傳輸功能。

在傳輸層有兩個性質不同的協議:TCP(Transmission Control Protocol,傳輸控制協議)和UDP(User Data Protocol,用戶數據報協議)。

3) 網絡層

網絡層用來處理在網絡上流動的數據包,數據包是網絡傳輸的最小數據單位。該層規定了通過怎樣的路徑(傳輸路線)到達對方計算機,並把數據包傳輸給對方。

4) 鏈路層

鏈路層用來處理連接網絡的硬件部分,包括控制操作系統、硬件設備驅動、NIC(Network Interface Card,網絡適配器)以及光纖等物理可見部分。硬件上的範疇均在鏈路層的作用範圍之內。

數據包封裝

上層協議數據是如何轉變為下層協議數據的呢?這是通過封裝(encapsulate)來實現的。應用程序數據在發送到物理網絡之前,會沿着協議棧從上往下傳遞。每層協議都將在上層協議數據的基礎上加上自己的頭部信息(鏈路層還會加上尾部信息),以為實現該層功能提供必要的信息。如圖所示:

發送端發送數據時,數據會從上層傳輸到下層,且每經過一層都會被打上該層的頭部信息。而接收端接收數據時,數據會從下層傳輸到上層,傳輸前會把下層的頭部信息刪除。過程如圖所示:

數據傳輸過程

由於下層協議的頭部信息對上層協議是沒有實際的用途,所以在下層協議傳輸數據給上層協議的時候會把該層的頭部信息去掉,這個封裝過程對於上層協議來說是完全透明的。這樣做的好處是,應用層只需要關心應用服務的實現,而不用管底層的實現。

TCP三次握手

從上面的介紹可知,傳輸層協議主要有兩個:TCP協議和UDP協議。TCP協議相對於UDP協議的特點是:TCP協議提供面向連接、字節流和可靠的傳輸。

使用TCP協議進行通信的雙方必須先建立連接,然後才能開始傳輸數據。TCP連接是全雙工的,也就是說雙方的數據讀寫可以通過一個連接進行。為了確保連接雙方可靠性,在雙方建立連接時,TCP協議採用了三次握手(Three-way handshaking)策略。
過程如圖所示:

TCP協議三次握手的描述如下:

第一次握手:客戶端發送帶有SYN標誌的連接請求報文段,然後進入SYN_SEND狀態,等待服務端的確認。

第二次握手:服務端接收到客戶端的SYN報文段后,需要發送ACK信息對這個SYN報文段進行確認。同時,還要發送自己的SYN請求信息。服務端會將上述的信息放到一個報文段(SYN+ACK報文段)中,一併發送給客戶端,此時服務端將會進入SYN_RECV狀態。

第三次握手:客戶端接收到服務端的SYN+ACK報文段后,會想服務端發送ACK確認報文段,這個報文段發送完畢后,客戶端和服務端都進入ESTABLISHED狀態,完成TCP三次握手。

當三次握手完成后,TCP協議會為連接雙方維持連接狀態。為了保證數據傳輸成功,接收端在接收到數據包后必須發送ACK報文作為確認。如果在指定的時間內(這個時間稱為重新發送超時時間),發送端沒有接收到接收端的ACK報文,那麼就會重發超時的數據。

1.1.3 DNS服務
前面介紹了與HTTP協議有着密切關係的TCP/IP協議,接下來介紹的DNS服務也是與HTTP協議有着密不可分的關係。

通常我們訪問一個網站,使用的是主機名或者域名來進行訪問的。因為相對於IP地址(一組純数字),域名更容易讓人記住。但TCP/IP協議使用的是IP地址進行訪問的,所以必須有個機制或服務把域名轉換成IP地址。DNS服務就是用來解決這個問題的,它提供域名到IP地址之間的解析服務。

如下圖所示,展示了DNS服務把域名解析成IP地址的過程:

DNS服務是通過DNS協議進行通信的,而DNS協議跟HTTP協議一樣也是應用層協議。由於我們的重點是HTTP協議,所以這裏不打算對DNS協議進行詳細的分析,我們只需要知道可以通過DNS服務把域名解析成IP地址即可。

1.1.4 HTTP與TCP/IP、DNS的關係
到現在,我們介紹了與HTTP協議有密切關係的TCP/IP協議和DNS服務,接下來我們通過下圖來整理一下HTTP協議與它們之間的關係:

HTTP與TCP/IP、DNS的關係
從上圖中可以知道,當客戶端訪問Web站點時,首先會通過DNS服務查詢到域名的IP地址。然後瀏覽器生成HTTP請求,並通過TCP/IP協議發送給Web服務器。Web服務器接收到請求後會根據請求生成響應內容,並通過TCP/IP協議返回給客戶端。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

salesforce lightning零基礎學習(十五) 公用組件之 獲取表字段的Picklist(多語言),salesforce 零基礎學習(六十二)獲取sObject中類型為Picklist的field values(含record type)

此篇參考:

 我們在lightning中在前台會經常碰到獲取picklist的values然後使用select option進行渲染成下拉列表,此篇用於實現針對指定的sObject以及fieldName(Picklist類型)獲取此字段對應的所有可用的values的公用組件。因為不同的record type可能設置不同的picklist values,所以還有另外的參數設置針對指定的record type developer name去獲取指定的record type對應的Picklist values.

一. PicklistService公用組件聲明實現

Common_PicklistController.cls:有三個形參,其中objectName以及fieldName是必填參數,recordTypeDeveloperName為可選參數。

 1 public without sharing class Common_PicklistController {
 2     
 3     @AuraEnabled(cacheable=true)
 4     public static List<Map<String,String>> getPicklistValues(String objectName, String fieldName,String recordTypeDeveloperName) {
 5         //1. use object and field name get DescribeFieldResult and also get all the picklist values
 6         List<Schema.PicklistEntry> allPicklistValuesByField;
 7         try {
 8             List<Schema.DescribeSobjectResult> objDescriptions = Schema.describeSObjects(new List<String>{objectName});
 9             Schema.SObjectField field = objDescriptions[0].fields.getMap().get(fieldName);
10             Schema.DescribeFieldResult fieldDescribeResult = field.getDescribe();
11             allPicklistValuesByField = fieldDescribeResult.getPicklistValues();
12         } catch (Exception e) {
13             throw new AuraHandledException('Failed to retrieve values : '+ objectName +'.'+ fieldName +': '+ e.getMessage());
14         }
15         
16         //2. get all active field name -> label map
17         List<Map<String,String>> activeOptionMapList = new List<Map<String,String>>();
18         Map<String,String> optionValue2LabelMap = new Map<String,String>();
19         List<String> optionValueList;
20         for(Schema.PicklistEntry entry : allPicklistValuesByField) {
21             if (entry.isActive()) {
22                 System.debug(LoggingLevel.INFO, '*** entry: ' + JSON.serialize(entry));
23                 optionValue2LabelMap.put(entry.getValue(), entry.getLabel());
24             }
25         }
26 
27         //3. generate list with option value(with/without record type)
28         if(String.isNotBlank(recordTypeDeveloperName)) {
29             optionValueList = PicklistDescriber.describe(objectName,recordTypeDeveloperName,fieldName);
30         } else {
31             optionValueList = new List<String>(optionValue2LabelMap.keySet());
32         }
33 
34         //4. generate and format result
35         if(optionValueList != null) {
36             for(String option : optionValueList) {
37                 String optionLabel = optionValue2LabelMap.get(option);
38                 Map<String,String> optionDataMap = new Map<String,String>();
39                 optionDataMap.put('value',option);
40                 optionDataMap.put('label', optionLabel);
41                 activeOptionMapList.add(optionDataMap);
42             }
43         }
44 
45         return activeOptionMapList;
46     }
47 }

 Common_PicklistService.cmp:聲明了getPicklistInfo方法,有以下三個主要參數.objectName對應sObject的API名字,fieldName對應的此sObject中的Picklist類型的字段,recordTypeDeveloperName對應這個sObject的record type的developer name

1 <aura:component access="global" controller="Common_PicklistController">
2     <aura:method access="global" name="getPicklistInfo"  description="Retrieve active picklist values and labels mapping with(without) record type" action="{!c.getPicklistInfoAction}">
3         <aura:attribute type="String" name="objectName" required="true" description="Object name"/>
4         <aura:attribute type="String" name="fieldName" required="true" description="Field name"/>
5         <aura:attribute type="String" name="recordTypeDeveloperName" description="record type developer name"/>
6         <aura:attribute type="Function" name="callback" required="true" description="Callback function that returns the picklist values and labels mapping as [{value: String, label: String}]"/>
7     </aura:method>
8 </aura:component>

 Common_PicklistServiceController.js: 獲取傳遞過來的參數,調用後台方法並對結果放在callback中。

 1 ({
 2     getPicklistInfoAction : function(component, event, helper) {
 3         const params = event.getParam('arguments');
 4         const action = component.get('c.getPicklistValueList');
 5         action.setParams({
 6             objectName : params.objectName,
 7             fieldName : params.fieldName,
 8             recordTypeDeveloperName : params.recordTypeDeveloperName
 9         });
10         action.setCallback(this, function(response) {
11             const state = response.getState();
12             if (state === 'SUCCESS') {
13                 params.callback(response.getReturnValue());
14             } else if (state === 'ERROR') {
15                 console.error('failed to retrieve picklist values for '+ params.objectName +'.'+ params.fieldName);
16                 const errors = response.getError();
17                 if (errors) {
18                     console.error(JSON.stringify(errors));
19                 } else {
20                     console.error('Unknown error');
21                 }
22             }
23         });
24 
25         $A.enqueueAction(action);
26     }
27 })

二. 公用組件調用

 上面介紹了公用組件以後,下面的demo是如何調用。

SimplePicklistDemo引入Common_PicklistService,設置aura:id用於後期獲取到此component,從而調用方法

 1 <aura:component implements="flexipage:availableForAllPageTypes">
 2     <!-- include common picklist service component -->
 3     <c:Common_PicklistService aura:id="service"/>
 4     <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
 5 
 6     <aura:attribute name="accountTypeList" type="List"/>
 7     <aura:attribute name="accountTypeListByRecordType" type="List"/>
 8 
 9     <lightning:layout verticalAlign="center" class="x-large">
10         <lightning:layoutItem flexibility="auto" padding="around-small">
11             <lightning:select label="account type">
12                 <aura:iteration items="{!v.accountTypeList}" var="type">
13                     <option value="{!type.value}" text="{!type.label}"></option>
14                 </aura:iteration>
15             </lightning:select>
16         </lightning:layoutItem>
17 
18         <lightning:layoutItem flexibility="auto" padding="around-small">
19             <lightning:select label="account type with record type">
20                 <aura:iteration items="{!v.accountTypeListByRecordType}" var="type">
21                     <option value="{!type.value}" text="{!type.label}"></option>
22                 </aura:iteration>
23             </lightning:select>
24         </lightning:layoutItem>
25     </lightning:layout>
26     
27 </aura:component>

SimplePicklistDemoController.js:初始化方法用於獲取到公用組件component然後獲取Account的type的values,第一個是獲取所有的values/labels,第二個是獲取指定record type的values/labels。

 1 ({
 2     doInit : function(component, event, helper) {
 3         const service = component.find('service');
 4         service.getPicklistInfo('Account','type','',function(result) {
 5             component.set('v.accountTypeList', result);
 6         });
 7 
 8         service.getPicklistInfo('Account','type','Business_Account',function(result) {
 9             component.set('v.accountTypeListByRecordType',result);
10         });
11     }
12 })

三.效果展示:

1. account type的展示方式

 

 2. account type with record type的展示方式。

 

 總結:篇中介紹了Picklist values針對with/without record type的公用組件的使用,感興趣的可以進行優化,篇中有錯誤的歡迎指出,有不懂的歡迎留言。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

如何使用偽類選擇器

偽類選擇器介紹

  • 偽類選擇器就是用來給超級鏈接設置不同的狀態樣式。
  • 超級鏈接分為4種狀態如:正常狀態、訪問過後狀態、鼠標放上狀態、激活狀態。

偽類選擇器說明表

選擇器 描述
:link 向未被訪問的超級鏈接添加樣式,正常狀態。
:visited 向已經被訪問的超級鏈接添加樣式,訪問過後狀態。
:hover 當鼠標懸浮在超級鏈接上方時,向超級鏈接添加樣式,鼠標放上狀態。
:active 鼠標放在超級鏈接上並且點擊的一瞬間,向超級鏈接添加樣式,激活狀態。

偽類選擇器實踐

  • 讓我們進入偽類選擇器實踐,實踐內容將超級鏈接4種狀態進行演示,演示效果如:將向未被訪問的超級鏈接文本顏色設置為紅色、已經被訪問的超級鏈接文本顏色設置為綠色、當鼠標懸浮在超級鏈接上文本顏色設置為紫色、用鼠標點擊超級鏈接的一瞬間文本顏色設置為藍色

  • 代碼塊

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>偽類選擇器</title>
    <style>
        a:link{
            color:red;
        }
        a:visited{
            color: lime;
        }
        a:hover{
            color: purple;
        }
        a:active{
            color: blue;
        }
    </style>
</head>
  
<body>
    <a href="https://www.cnblogs.com/lq000122/">微笑是最初的信仰</a>
</body>
</html>
  • 正常狀態結果圖

  • 鼠標放上狀態結果圖

  • 激活狀態結果圖

  • 訪問過後狀態

總結

  • 超級鏈接的不同狀態他其實是由順序,也就是說偽類選擇器設置其實是順序的,如果按照偽類選擇器的順序,那麼設置的樣式就不會被渲染。
  • 順序:linkvisitedhoveractive

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

3c收購,鏡頭 收購有可能以全新價回收嗎?

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

Clean Code 筆記 之 第四章 如何應用註釋

繼上一篇筆記之後,今天我們討論一下 代碼中是存在註釋是否是一件好的事情。

 

在我們開發的過程中講究“名副其實,見名識意”,這也往往是很多公司的要求,但是有了這些要求是不是我們的代碼中如果存在註釋是不是意味着我們的 函數,變量,以及類 的命名就不符合了“名副其實,見名識意”。

我們先區分一下註釋的類別,註釋一般分為以下幾種:

  • 1, 單行註釋
  • 2, 多行註釋
  • 3, 文檔註釋
  • 4, #region 摺疊註釋,可以將 代碼摺疊

 註釋的類別

1, 單行註釋:

在以 “//” 開頭,用以說明一行代碼的作用放置位置 看習慣或者公司要求合理就行。常用於函數內部,在很多的開源代碼中文件的頭部我同樣見到很多人使用單行註釋進行說明,靈活就好。
體現形式如下:

 public List<string> getVipUserNameByUserType()
          {
            // Vip user name list
            var vipUserNames = new List<string>();

            foreach (var user in Users)
            {

                if (user.Type = "VIP")

                    vipUserNames.Add(user.Name);
            }
            return vipUserNames;

          }

View Code

2, 多行註釋:

以“/*”開頭 “*/” 結尾 常用於描述說明一段代碼以及類註釋或者說代碼塊常用於文件的頂部。說明作者信息,時間如果是開源的這包含開源的更多說明。
通常使用如下:

/*
    * 作者:〈版權〉
    * 描述:〈描述〉
    * 創建時間:YYYY-MM-DD
    * 作用:
*/

View Code

3, 文檔註釋:

也就是常用的XML 註釋:它的說明性更加的強烈,多用於類以及函數上,當然屬性上同樣可以使用:
如下所示:

        /// <summary>
        /// MyClass
        /// </summary>
        public class MyClass
        {
            /// <summary>
            /// MyProperty
            /// </summary>
            public int MyProperty { get; set; }
            /// <summary>
            /// MyMethod
            /// </summary>
            public void MyMethod(){  }
        }

View Code

以下是官方建議的文檔標記 點擊標籤會制動跳轉

 

4, #region : 摺疊註釋,常用於描述多個函數的基本作用

書中最喜歡的話

好的註釋不能美化糟糕的代碼,真正好的註釋是你想盡辦法不去謝的註釋。懷註釋都是糟糕代碼的支撐或借口,或者是對錯誤決策的修正。

下面看一個例子

       //Check to see if the employee is eligible for full benefits1)If((employee.flags & HOURLY_FLAG)&& (employee.age>65))

(2)If(employee.isEligibleForFullBenefits()))

  這兩個你更喜歡哪個

View Code

好的註釋的特徵:

1:表示法律信息(這樣的註釋一般出現在文檔頂部說明作用以及協議)

// Copyright (c) .NET Foundation. All rights reserved
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

View Code

2:提供信息的註釋(指無法通過命名提供信息如要 註釋輔助的)

public void ConfigureServices(IServiceCollection services)
{

// These two middleware are registered via an IStartupFilter in UseIISIntegration but you can configure them here.

services.Configure<IISOptions>(options =>
{});

}

View Code

3:對意圖的解釋

4: 警示:告知其他人會出現某種後果的註釋

5: TODO註釋: 主要描述應該做的目前還沒有做的事情。

6: 放大:提示不合理之物的重要性。

應避免的註釋

應該避免以下幾點:

1: 誤導性註釋

2: 日誌式註釋

3: 廢話註釋

4: 標記位置的註釋

5: 括號后的註釋

6: 歸屬與簽名

7: 註釋掉的代碼

8: Html 註釋

以上沒有一一舉例的原因是我的PPT是一份演示的PPT,裏面很多公司的代碼不便貼出,抱歉。

不足之處還請指出

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

PHP 的 self 關鍵字用法

之前有人詢問 self 關鍵字的用法,答案是比較明顯的:靜態成員函數內不能用 this 調用非成員函數,但可以用 self 調用靜態成員函數/變量/常量;其他成員函數可以用 self 調用靜態成員函數以及非靜態成員函數。隨着討論的深入,發現 self 並沒有那麼簡單。鑒於此,本文先對幾個關鍵字做對比和區分,再總結 self 的用法。

parentstatic 以及 this 的區別

要想將徹底搞懂 self ,要與 parentstatic 以及 this 區分開。以下分別做對比。

parent

selfparent 的區分比較容易: parent 引用父類/基類被隱蓋的方法(或變量), self則引用自身方法(或變量)。例如構造函數中調用父類構造函數:

class Base {
    public function __construct() {
        echo "Base contructor!", PHP_EOL;
    }
}

class Child {
    public function __construct() {
        parent::__construct();
        echo "Child contructor!", PHP_EOL;
    }
}

new Child;
// 輸出:
// Base contructor!
// Child contructor!

 

static

static 常規用途是修飾函數或變量使其成為類函數和類變量,也可以修飾函數內變量延長其生命周期至整個應用程序的生命周期。但是其與 self 關聯上是PHP 5.3以來引入的新用途:靜態延遲綁定。

有了 static 的靜態延遲綁定功能,可以在運行時動態確定歸屬的類。例如:

class Base {
    public function __construct() {
        echo "Base constructor!", PHP_EOL;
    }

    public static function getSelf() {
        return new self();
    }

    public static function getInstance() {
        return new static();
    }

    public function selfFoo() {
        return self::foo();
    }

    public function staticFoo() {
        return static::foo();
    }

    public function thisFoo() {
        return $this->foo();
    }

    public function foo() {
        echo  "Base Foo!", PHP_EOL;
    }
}

class Child extends Base {
    public function __construct() {
        echo "Child constructor!", PHP_EOL;
    }

    public function foo() {
        echo "Child Foo!", PHP_EOL;
    }
}

$base = Child::getSelf();
$child = Child::getInstance();

$child->selfFoo();
$child->staticFoo();
$child->thisFoo();

 

程序輸出結果如下:

Base constructor!
Child constructor!
Base Foo!
Child Foo!
Child Foo!

 

在函數引用上, selfstatic 的區別是:對於靜態成員函數, self 指向代碼當前類, static 指向調用類;對於非靜態成員函數, self 抑制多態,指向當前類的成員函數, static 等同於 this ,動態指向調用類的函數。

parentselfstatic 三個關鍵字聯合在一起看挺有意思,分別指向父類、當前類、子類,有點“過去、現在、未來”的味道。

this

selfthis 是被討論最多,也是最容易引起誤用的組合。兩者的主要區別如下:

  1. this 不能用在靜態成員函數中, self 可以;
  2. 對靜態成員函數/變量的訪問, 建議 用 self ,不要用 $this::$this-> 的形式;
  3. 對非靜態成員變量的訪問,不能用 self ,只能用 this ;
  4. this 要在對象已經實例化的情況下使用, self 沒有此限制;
  5. 在非靜態成員函數內使用, self 抑制多態行為,引用當前類的函數;而 this 引用調用類的重寫(override)函數(如果有的話)。

self 的用途

看完與上述三個關鍵字的區別, self 的用途是不是呼之即出?一句話總結,那就是: self總是指向“當前類(及類實例)”。詳細說則是:

  1. 替代類名,引用當前類的靜態成員變量和靜態函數;
  2. 抑制多態行為,引用當前類的函數而非子類中覆蓋的實現;

槽點

  1. 這幾個關鍵字中,只有 this 要加 $ 符號且必須加,強迫症表示很難受;
  2. 靜態成員函數中不能通過 $this-> 調用非靜態成員函數,但是可以通過 self:: 調用,且在調用函數中未使用 $this-> 的情況下還能順暢運行。此行為貌似在不同PHP版本中表現不同,在當前的7.3中ok;
  3. 在靜態函數和非靜態函數中輸出 self ,猜猜結果是什麼?都是 string(4) "self" ,迷之輸出;
  4. return $this instanceof static::class; 會有語法錯誤,但是以下兩種寫法就正常:
    $class = static::class;
    return $this instanceof $class;
    // 或者這樣:
    return $this instanceof static;

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

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