標籤: USB CONNECTOR

  • 菲亞特董事長公開闢謠 廣汽入主消息不實

    據《歐洲汽車新聞》報導,菲亞特克萊斯勒公司(FCA)董事長約翰•埃爾坎(John Elkann)明確指出,未與中國廣汽集團簽訂股權出售協定。

    2015年FCA與廣汽延續廣汽菲亞特的合作關係,成立了廣汽菲克合資公司,投產Jeep自由光和自由俠。近日,《義大利日報》發佈了一篇爆料新聞,除在生產方面的合作外,廣汽集團還有意入主FCA,展開資本層面的聯姻。

    埃爾坎同時也是菲亞特控股集團EXOR的董事長,25日召開了Exor股東大會。針對《義大利日報》的爆料,埃爾坎當日公開闢謠,公司與廣汽集團未簽訂股權出售協定。

    埃爾坎也被詢問標緻雪鐵龍集團(PSA集團)是否可成為FCA的良好合作夥伴,該董事長則指出其公司正在尋求的推動轉型的合作夥伴,PSA並不在公司的考慮範圍內。

    據法國《回聲報》的報導,法國政府正在考慮出售其持有的PSA 14%的部分或全部股權。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 財政部將公開新能源汽車推廣騙補核查和處理結果

    5月28日,財政部發佈聲明稱,關於新能源汽車推廣騙補核查,現場核查已經完成,目前處於會審階段。核查及處理情況,將按資訊公開有關規定及時公開。

    中國新能源汽車產業的發展受到政策的強力推動。從2010年開始,我國便實施新能源汽車補貼政策,由於監督機制不完善,騙補隨之愈演愈烈。2016年1月份,工信部、財政部、科技部、發改委聯合啟動對新能源汽車相關情況的專項核查工作,新能源汽車生產企業、運營企業、租賃企業、企事業單位等新能源汽車使用者全部列入核查物件。國務院也把遏制新能源汽車騙補行為作為重點工作之一。

    此前,央視曝光了10家涉及騙補的企業,分別是蘇州吉姆西客車製造有限公司、陝西通家汽車股份有限公司、重慶力帆乘用車有限公司、江蘇陸地方舟新能源電動汽車有限公司、奇瑞萬達貴州客車股份有限公司、國宏汽車有限公司、江蘇奧新新能源汽車有限公司、蕪湖寶騏汽車製造有限公司、重慶力帆汽車有限公司,以及金華青年汽車製造有限公司。這些企業的共同特點是,2015年12月單月產量(主要依據是機動車出廠合格證)均超過全年產量的50%。

    對於騙補行為,工信部部長苗圩曾公開表示,“局部地區確實存在少部分企業騙補的現象,對於騙補企業,沒補貼的錢不會下發,已補貼的錢一定要扣回。依法進行處置,直至取消這些企業的資質”。

    對於騙補的企業到底有哪些?將受到怎樣的處罰?這些問題一直受到業界的關注和猜測。此次,財政部公開發佈新能源汽車推廣核查有關情況的聲明,表示“現場核查已經完成,目前處於會審階段”。聲明特別強調“財政部和部內有關司局至今未接受過媒體採訪,核查及處理情況,將按資訊公開有關規定及時公開”。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 大眾計畫為高端車型開發第二個電動汽車平臺

    據外媒報導稱,大眾正在考慮開發第二個電動汽車平臺,用於更大、更高端的車型。

    大眾的第一個電動汽車平臺名叫MEB,曾在CES上展示 過,BUDD-e概念車就是用該平臺開發的。該平臺專門針對小型汽車、輕型商務用車,充電一次行駛距離大約155英里至310英里(250-500公 裡)。目前大眾正在調查MEB平臺的彈性,看它是否可以用在高端車型上,比如Phaeton(輝騰),未來大眾可能會為高端車型專門開發一個新平臺。

    按照大眾的計畫,2019年之前MEB平臺準備就緒,因為該平臺主要針對的是電動汽車,擴展性更強。也就是說汽車製造商調整軸距、軌距、座位位置更加容易一些,它們可以將平臺用在更多的車型上。落地式電池尤其引人注目,因為工程師可以根據車型改變電池的大小。

    大眾集團電子研發主管沃克馬•坦尼伯格(Volkmar Tanneberger)稱,‘巧克力電池’(chocolate battery)製造更容易,可以進行大規模工業化生產。MEB能夠用在所有車型上,包括超小型Polo和中型汽車帕薩特(Passat),未來MEB有可能會成為大眾所有產品線的基石。如果MEB架構無法用在更大型的汽車上,比如輝騰,開發第二個電動汽車平臺就會變得有意義。保時捷Mission E和奧迪e-tron Quattro將會擁有自己的平臺,因為早在MEB架構公佈之前,它們已經設計成型。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 別翻了,這篇文章就是要讓你入門java多線程!

    別翻了,這篇文章就是要讓你入門java多線程!

    目錄

    就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時內心是小鹿亂撞啊….於是這幾天茶不思飯不想,好幾天深夜皆是輾轉反側,兩目深凝,以至於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,一般兩步,走在大馬路中央上差點被打~我承認太誇張了,感覺又要被打~。最終還是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像可以看的出來,這位朋友絕bi歷經滄桑,對生活無盡的坦然浩對,看透俗世凡塵、世態炎涼、趨炎附勢,擁有着極高的安心恬盪情懷…啥?啥子?這個是系統默認頭像….嗯嗯嗯呃。。。那個那個宜春啥都沒說哈,別把什麼事都扯宜春身上,你們一天天的,我啥都沒說(理直氣壯)…

    @

    1. 理解線程與進程

    由於併發肯定涉及到多線程,因此在進入併發編程主題之前,我們先來了解一下進程和線程的由來,這對後面對併發編程的理解將會有很大的幫助。

    進程和線程的對比這一知識點由於過於基礎,正因為過於基礎,所以我們更應該透徹它!我們必須掌握什麼是線程和進程,掌握線程與進程的關係、區別及優缺點 !

    1.1、何為進程?

    首先我們來看一下進程的概念:

    進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

    看完之後,是不是感覺很抽象?很懵bi?懵bi就對了,說明你和我智商一樣高….~開個玩笑~

    不妨先憋棄上面的概念,放鬆一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出遊戲並且保持微笑,然後正襟危坐心平氣和的看宜春寫的博客….

    這個時候的你不僅僅是愉快的擼了一把遊戲,而且還親自體驗擼了一把進程…其實在你雙擊打開LOL的時候就已經創建了進程,此話怎講?眾所周知,我們的電腦安裝的軟件比如:LOL、微信、谷歌等等都是存儲在我們的硬盤上的,硬盤上的數據可以說是永久存儲(ORM),當我們雙擊LOL的時候,LOL程序執行就進入了內存中,所有的程序必須進入內存中才能執行,內存屬於臨時存儲(RAM),而進入內存的程序都可以叫做是進程,把LOL程序退出的時候,LOL程序就會退出內存,進程也就隨之銷毀了!因此說各位擼了一把進程也不為過吧。

    啥?字太多了,看的不夠明了,不如看圖得勁….額。。。

    上面主要是通過抽象的描述了進程,其實進程是可以很直觀的看的到的,我們可以再電腦底部任務欄,右鍵—–>打開任務管理器,可以查看當前任務的進程:

    其實,關於線程博主我完全可以一兩句話概括,但是這樣並不負責,畢竟這篇文章標題就是要讓你徹底入門java多線程。如果連進程都理解不好談何徹底理解多線程?

    1.2、何為線程?

    同樣的,我們先來看線程的概念

    線程是進程中的一個執行單位,負責當前進程中程序的執行。一個進程中至少有一個線程,也就是說一個進程可以有多個線程的,而多個線程的進程運用程序就叫做多線程程序

    線程的概念稍微好理解很多,但是想更深層次的去理解光靠上面一段文字的概述是完全不夠的!

    這不打LOL的過程中,屬實卡的一批,果然花高價998買的6手戴爾筆記本打LOL屬實像極了愛情。這個時候不得不雙擊打開電腦安全管家進行殺毒,果然2500天沒有進行過病毒查殺,我天。。。其實我相信很多人都用過電腦管家或者手機管家之類的安全軟件,我們都很清楚我們開啟病毒查殺之後一般要幾分鐘掃描查殺,這個時候我們是可以讓它後台進行的,我們不會等而是開啟另一個垃圾清理的功能,這個時候我們也不會等而是再去啟動電腦加速功能。等到 這些操作都完成之後果斷退出電腦管家,繼續LOL,果然高價998買的6手戴爾筆記本再怎麼殺毒打LOL還是照樣的卡….

    其實清楚線程必然涉及到CPU的相關概念了,將上面文字所描述的用圖片概括,大致為:

    1.3、何為多線程?

    從上一節中,我們也提到過多線程,所以理解起來應該不難。

    多線程就是多個線程同時運行交替運行

    單核CPU:交替運行。
    多核CPU:同時運行。

    其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。

    1.4、何為線程調度優先級?

    說起線程調度優先級這個概念,就讓我想到現在我們大部分人投簡歷一樣。如果你的學歷或者工作經驗越高,那麼你的優先級就越高,面試官很大幾率就會讓你去面試但也不是一定只是幾率特別大,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性)!在我們每個人的電腦中線程是可以設置線程的優先級的,但是生活中沒有優先級(學歷、工作經驗)的孩子就只能靠自己的能力了~媽耶,太真實了…~

    線程優先級具有繼承特性比如A線程啟動B線程,則B線程的優先級和A是一樣的。

    線程優先級具有隨機性也就是說線程優先級高的不一定每一次都先執行完,只是被執行的可能性更大。

    在今後的多線程學習旅遊中我們會使用到getPriority()方法獲取線程的優先級。

    1.5、為什麼提倡使用多線程而不是多進程?

    線程與進程相似,但線程是一個比進程更小的執行單位,是程序執行的最小單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。同時線程是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是因為線程間的切換和調度的成本遠遠小於進程。

    而使用多線程,多線程會將程序運行方式從串行運行變為併發運行,效率會有很大提高。

    2、理解并行和併發

    在博主認為併發和并行是兩個非常容易被混淆的概念。為了防止繞暈大家,所以我選擇長話短說!

    1. 併發:一個時間段內同時發生(並不是同時發生)。
    2. 并行:同一時刻發生(真正的同時發生)。

    它們都可以表示兩個或者多個任務一起執行,但是偏重點有些不同。

    於此同時,我們不妨回顧一下上面所提到過的CPU,並再次理解併發與并行的區別,從而溫故知新 ~我TM簡直是個天才!~

    單核CPU:交替運行【併發】
    多核CPU:同時運行【并行】

    併發給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的!

    3、特殊的一個單線程:主線程(Main線程)

    我們常說的主線程就是Main線程,它是一個特殊的單線程,話不多說,直接擼碼:

    定義一個用於測試的demo類Person

    package demo;
    
    public class Person {
       public String name;
    
       public Person(String name){
           this.name=name;
       }
    
       public void run(){
           int i=1;
           while (i<5){
               System.out.println(name+i);
               i++;
           }
       }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    編寫Main方法

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
    
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    運行結果就已經很顯而易見了,放心我不是靠你們運行結果而是單純的先分析主線程。

    運行結果:
        常威1
        常威2
        常威3
        常威4
        來福1
        來福2
        來福3
        來福4

    3.1、分析主線程原理

    3.2、 單線程的局限性

    單線程不僅效率低下,而且存在很大的局限性,惟一的優點就是安全。所以說女孩子長得安全其實也是一種優點,噗哈哈哈…

    如何體現出單線程效率低下以及它的局限性呢?其實只要一句代碼即可,還是以上面的單線程Main線程為例:

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
            int a=6/0;  //=====================特別注意這行代碼
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    試想一下運行結果…

    如果對上面的運行結果有問題,或者疑問。那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),很有可能異常機制沒學好,好吧我給你貼出來:

    言歸正傳,效率低下何以見得?這是數據少,如果是一億條數據呢,單線程就是一個一個打印。那局限性又何以見得呢?從上面運行結果來看也能看出,只因為一行代碼而導致下面代碼不再執行。已經很明顯了。

    4、 創建多線程的四種方式

    說是說創建多線程有四種方式,但考慮到是入門文章還是主要寫入門的兩種方式,剩下的兩個暫時忽略。忽略的兩種方法有:實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorServiceCallableFuture實現有返回結果的線程。現在可能對於入門的童鞋來說是接收不了的,以後再去了解也不晚!

    4.1、繼承Thread類

    Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來代表這段程序流。

    Java中通過繼承Thread類來創建啟動多線程的步驟如下:

    1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱為線程執行體。
    2. 創建Thread子類的實例,即創建了線程對象
    3. 調用線程對象的start()方法來啟動該線程

    代碼如下:

    測試類:

    public class Demo01 {
        public static void main(String[] args) {
            //創建自定義線程對象
            MyThread mt = new MyThread("新的線程!");
            //開啟新線程
            mt.start();
            //在主方法中執行for循環
            for (int i = 0; i < 10; i++) {
                System.out.println("main線程!"+i);
            }
        }
    }

    自定義線程類:

    public class MyThread extends Thread {
        //定義指定線程名稱的構造方法
        public MyThread(String name) {
            //調用父類的String參數的構造方法,指定線程的名稱
            super(name);
        }
        /**
         * 重寫run方法,完成該線程執行的邏輯
         */
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName()+":正在執行!"+i);
            }
        }
    }

    Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新線程並執行自己定義的run()方法。

    4.2、實現Runnable接口

    如果自己的類已經繼承另一個類,就無法直接繼承Thread,此時,可以實現一個Runnable接口來創建線程,顯然實現Runnable接口方式創建線程的優勢就很明顯了。

    直接擼碼:

    自定義一個類實現Runnable接口,並重寫接口中的run()方法,併為run方法添加要執行的代碼方法。

    public class RunableDemo implements Runnable{
    
        @Override
        public void run() {
            int a = 1;
            while (a<20){
                System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()為獲取當前線程的名字
                a++;
            }
        }
    }

    編寫Main方法

    為了啟動自定義類RunableDemo ,需要首先實例化一個Thread,並傳入RunableDemo 實例

    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            //實例化一個Thread並傳入自己的RunableDemo 實例
            Thread thread=new Thread(runn);
            thread.start();
    
            int a = 1;
            while (a<20){
                //Thread.currentThread().getName()為獲取當前線程的名字
                System.out.println(Thread.currentThread().getName()+ a);
                a++;
            }
        }
    }

    運行結果:

    main1
    main2
    main3
    Thread-01
    Thread-02
    Thread-03
    Thread-04
    Thread-05
    Thread-06
    ....

    其實多運行幾遍,你會方法每次運行的結果順序都不一樣,這主要是由於多線程會去搶佔CPU的資源,誰搶到了誰就執行,而Main和Thread兩個線程一直在爭搶。

    實際上,當傳入一個Runnable target(目標)參數給Thread后,Threadrun()方法就會調用target.run(),參考JDK源代碼:

    public void run() {  
      if (target != null) {  
       target.run();  
      }  
    }  

    4.3、兩種入門級創建線程的區別

    採用繼承Thread類方式:

    (1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
    (2)缺點:因為線程類已經繼承了Thread類,所以不能再繼承其他的父類。

    採用實現Runnable接口方式:

    (1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相
    同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
    (2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。

    小結:
    如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

    實現Runnable接口比繼承Thread類的優勢:

    1.適合多個相同代碼的線程去處理同一個資源。

    2.可以避免java中單繼承的限制。

    3.增加代碼的健壯性,實現解耦。代碼可以被多個線程共享,代碼和數據獨立。

    4.線程池中只能放入實現Runnable或Callable類線程,不能放入繼承Thread的類【線程池概念之後會慢慢涉及】

    所以,如果選擇哪種方式,盡量選擇實現Runnable接口

    其實學到後面的線程池,你會發現上面兩種創建線程的方法實際上很少使用,一般都是用線程池的方式比較多一點。使用線程池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到“線程資源必須通過線程池提供,不允許在應用中自行显示創建線程”。不過處於入門階段的童鞋博主還是強烈建議一步一個腳印比較好!

    5、使用匿名內部類方式創建線程

    談起匿名內部類,可能很多小白是比較陌生的,畢竟開發中使用的還是比較少,但是同樣是非常重要的一個知識!於此同時我就貼出關於匿名內部類的文章如果小白童鞋能看懂下面這個代碼,真的你不需要看那篇文章了,你T喵的簡直是個天才!

    package AnonymousInner;
    
    public class NiMingInnerClassThread {
        public static void main(String[] args) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i<5;i++){
                        System.out.println("熊孩子:"+i);
                    }
                }
            };
            new Thread(r).start();
            for (int i = 0; i < 5 ; i++){
                System.out.println("傻狍子:"+i);
            }
        }
    }

    小白童鞋還愣着幹啥呀趕緊去補補…

    6、線程安全問題

    線程安全問題主要是共享資源競爭的問題,也就是在多個線程情況下,一個或多個線程同時搶佔同一資源導致出現的一些不必要的問題,最典型的例子就是火車四個窗口售票問題了,這裏就不再舉售票例子了,已經爛大街了,這裏就簡單實現一個線程安全問題代碼….

    實現Runnable接口方式為例,主要實現過程是:實例化三個Thread,並傳入同一個RunableDemo 實例作為參數,最後開啟三條相同參數的線程,代碼如下:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
        
        @Override
        public void run() {
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }
    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            Thread thread1=new Thread(runn);
            Thread thread2=new Thread(runn);
            Thread thread3=new Thread(runn);
            thread1.start();
            thread2.start();
            thread3.start();
            }
     }

    運行結果:

    Thread-0==100
    Thread-0==99
    Thread-1==100
    Thread-1==97
    Thread-1==96
    Thread-1==95
    Thread-2==98
    ...

    根據結果可以看出,確實是三條線程(Thread-0、1、2)在執行,安全問題就出在線程會出現相同的結果比如上面的100就出現了兩次,如果循環條件更改一下可能也會出現負數的情況。這種情況該怎麼解決呢?這個時候就需要線程同步了!

    7、解決線程安全問題:線程同步

    實際上,線程安全問題的解決方法有三種:

    1、同步代碼塊
    2、同步方法
    3、鎖機制

    7.1、 synchronized同步代碼塊

    第一種方法:同步代碼塊

    格式:

    synchronized(鎖對象) {
    可能會出現線程安全問題的代碼(訪問共享數據的代碼)
    }

    使用同步代碼塊特別注意:
    1、通過代碼塊的鎖對象,可以是任意對象
    2、必須保證多個線程使用的鎖對象必須是同一個
    3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行

    還是以上麵線程安全問題為例子,使用同步代碼塊舉例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        Object object=new Object(); //事先準備好一個鎖對象
    
        @Override
        public void run() {
            synchronized (object){  //使用同步代碼塊
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            }
        }
    }

    Main方法沒有任何改動,運行一下結果是絕對沒問題的,數據都是正確的沒有出現重複情況這一出,各位可以自己嘗試一下!

    同步代碼塊的原理:

    使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之後當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
    同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
    缺點:頻繁的獲取釋放鎖對象,降低程序效率

    7.2、同步方法

    使用步驟:

    1、把訪問了共享數據的代碼抽取出來,放到一個方法中
    2、在該方法上添加 synchronized 修飾符

    格式:

    修飾符 synchronized 返回值類型 方法名稱(參數列表) {
      方法體...
    }

    代碼示例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        @Override
        public void run() {
            while (true){
                sell(); //調用下面的sell方法
            }
        }
        
        //訪問了共享數據的代碼抽取出來,放到一個方法sell中 
        public synchronized void sell(){
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰。

    說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據     =====此時共享數據也要加上static
    
        @Override
        public void run() {
            while (true){
                sell();
            }
        }
    
        public static synchronized void sell(){  //注意添加了static關鍵字
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    使用靜態同步方法時,此時共享數據也要加上static,因為static成員才能訪問static成員,如果對static關鍵字不是他別理解的可以補補了,放心,博主有信心讓你有所收穫,會讓你重新認識到static的魅力:

    當然靜態同步方法了解即可!

    7.3、Lock鎖

    Lock接口位於java.util.concurrent.locks.Lock它是JDK1.5之後出現的,Lock接口中的方法:

    void lock(): 獲取鎖

     

    void unlock(): 釋放鎖

    Lock接口的一個實現類java.util.concurrent.locks.ReentrantLock implements Lock接口

    使用方法:
    1、在Runable實現類的成員變量創建一個ReentrantLock對象
    2、在可能產生線程安全問題的代碼該對象調用lock方法獲取鎖
    3、在可能產生線程安全問題的代碼該對象調用unlock方法釋放鎖

    代碼示例:

    import java.util.concurrent.locks.ReentrantLock;
    
    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據
    
        //1、在Runable實現類的成員變量創建一個ReentrantLock對象============
        ReentrantLock reentrantLock = new ReentrantLock();
    
        @Override
        public void run() {
            // 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖=======
            reentrantLock.lock();
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            // 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖======
            reentrantLock.unlock();
        }
    
    }

    當然更安全的寫法是,在線程安全問題代碼中try...catchy,最後在finally語句中添加reentrantLock.unlock();,這樣方為上上策!

    7.4、三種方法小結

    第一種
    synchronized 同步代碼塊:可以是任意的對象必須保證多個線程使用的鎖對象是同一個

     

    第二種
    synchronized 同步方法: 鎖對象是this,誰調用鎖對象就是誰

     

    synchronized 靜態同步方法: 鎖對象是其class對象,該對象可以用this.getClass()方法獲取,也可以使用當前類名.class 表示。【了解即可】

     

    第三種
    Look鎖方法:該方法提供的方法遠遠多於synchronized方式,主要在Runable實現類的成員變量創建一個ReentrantLock對象,並使用該對象調用lock方法獲取鎖以及unlock方法釋放鎖!

    8、線程常用方法

    8.1、Thread類

      Thread():用於構造一個新的Thread。

      Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。

      Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該

      線程使用了指定target的run方法。

      currentThread():獲得當前運行線程的對象引用。

      interrupt():將當前線程置為中斷狀態。

      sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少為指定毫秒數。

      join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束后才繼續本線程。

      yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其他就緒線程執行。

    8.2、Object類

      wait():讓當前線程進入等待阻塞狀態,直到其他線程調用了此對象的notify()或notifyAll()方法后,當前線程才被喚醒進入就緒狀態。

      notify():喚醒在此對象監控器(鎖對象)上等待的單個線程。

      notifyAll():喚醒在此對象監控器(鎖對象)上等待的所有線程。

    注意:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每個對象只有一個,所以這些方法定義在Object類中,而不是Thread類中。

    8.3、yield()、sleep()、wait()比較

       wait():讓線程從運行狀態進入等待阻塞狀態,並且會釋放它所持有的同步鎖。

       yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。

       sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。

    9、線程的狀態

    以上只是簡單的一個線程狀態圖,其實線程狀態博大精深,要講清楚還是要一大篇文筆,作為入門文章先了解一下吧,之後的併發編程文章將再講述吧!

    如果想要去深入了解一下的話也是可以的:

    10、線程池

    在java中只要說到池,基本都是一個套路,啥數據庫連接池、jdbc連接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建資源而消耗過多資源。

    10.1、線程池概述

    線程池其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

    合理利用線程池能夠帶來三個好處:

    1. 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
    2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
    3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

    10.2、 線程池的使用

    Java裏面線程池的最頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService

    要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

    Executors類中有個創建線程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

    獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法如下:

    • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

    Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

    使用線程池中線程對象的步驟:

    1. 創建線程池對象。
    2. 創建Runnable接口子類對象。(task)
    3. 提交Runnable接口子類對象。(take task)
    4. 關閉線程池(一般不操作這一步)。

    Runnable實現類代碼:

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我要一個游泳教練");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("教練來了: " + Thread.currentThread().getName());
            System.out.println("教我游泳,教會後,教練又回到了游泳池");
        }
    }

    線程池測試類:

    public class ThreadPoolDemo {
        public static void main(String[] args) {
            // 創建線程池對象
            ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
            // 創建Runnable實例對象
            MyRunnable r = new MyRunnable();
    
            //自己創建線程對象的方式
            // Thread t = new Thread(r);
            // t.start(); ---> 調用MyRunnable中的run()
    
            // 從線程池中獲取線程對象,然後調用MyRunnable中的run()
            service.submit(r);
            // 再獲取個線程對象,調用MyRunnable中的run()
            service.submit(r);
            service.submit(r);
            // 注意:submit方法調用結束后,程序並不終止,是因為線程池控制了線程的關閉。
            // 將使用完的線程又歸還到了線程池中
            // 關閉線程池
            //service.shutdown();
        }
    }

    以上只是簡單的使用線程池,僅僅是入門階段!道阻且長,路還很長….

    到這裏,本文章入門暫時告一段落,以後有時間盡量抽空更新….

    如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝你~

    歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 利用SSH隧道技術穿越內網訪問遠程設備

    利用SSH隧道技術穿越內網訪問遠程設備

    本文為作者原創,轉載請註明出處:

    通常,我們用於調試的計算機無法遠程訪問位於局域網中的待調試設備。通過 ssh 的端口轉發(又稱 ssh 隧道)技術,可以實現這種遠程調試功能。

    下文中,sshc 指 ssh 客戶端,sshd 指 ssh 服務器。

    1. ssh 端口轉發模式簡介

    ssh 客戶端運行於本地機器,它的作用是:登錄到目標機器並在目標機器上執行命令。它可以建立一個安全通道,為不安全網絡上兩個不受信任的主機提供安全的加密通信。X11 連接、任意 TCP 端口和 UNIX 域套接字也可以通過 ssh 安全通道進行轉發。

    ssh 連接並登錄到指定的主機名(用戶名可選)。如果指定了命令,命令將在遠程主機上執行,而不是在本機 shell 里執行。

    1.1 ssh 常用選項簡介

    ssh 端口轉發相關的常用選項如下:

    -C

    請求壓縮所有數據(包括 stdin、stdout、stderr 和用於轉發的 X11、TCP 和 UNIX 域連接的數據)。壓縮算法與 gzip 使用的算法相同,壓縮級別由 ssh 協議版本 1 的 CompressionLevel 選項控制。在調製解調器線路和其他慢速連接上採用壓縮是可取的,但它會減慢快速網絡上的速度。

    -f

    請求 ssh 在執行命令之前轉到後台。如果用戶希望 ssh 在後台運行,但 ssh 需要用戶提供密碼或口令,使用 -f 選項就很有用,在用戶輸入密碼之後,ssh 就會轉入後台運行。這個選項隱含了 -n 選項的功能(-n 選項將 stdin 重定向到 /dev/null,從而避免後台進程讀 stdin)。在遠程站點上啟動 X11 程序的推薦方法是使用 “ssh -f host xterm” 。

    如果 ExitOnForwardFailure 配置選項設置的是 “yes”,則使用 -f 選項啟動的 ssh 客戶端會等所有的遠程端口轉發建立成功后才將自己轉到後台運行。

    -n

    將 stdin 重定向到 /dev/null (實際上是為了防止後台進程從stdin讀取數據)。當 ssh 在後台運行時必須使用此選項。

    一個常見的技巧是使用它在目標機器上運行 X11 程序。例如,ssh -n shadow.cs.hut.fi emacs & 將在 shadows.cs.hut.fi 上啟動 emacs 程序。X11 的連接將通過加密通道自動轉發。ssh 程序將在後台運行。(如果 ssh 需要請求密碼或口令,則此操作無效;參見-f選項。)

    -N

    不執行遠程命令。此選項用於只需要端口轉發功能時。

    -g

    允許遠程主機連接到本地轉發端口。如果用於多路復用連接,則必須在主進程上指定此選項。

    -t

    強制分配一個偽終端。在目標機上執行任意的基於屏幕的程序時(例如,實現菜單服務),分配偽終端很有用。使用多個 -t 選項則會強制分配終端,即使 ssh 沒有本地終端。

    -T

    禁止分配偽終端。

    -L [bind_address:]port:host:hostport
    -L [bind_address:]port:remote_socket
    -L local_socket:host:hostport
    -L local_socket:remote_socket

    數據從本機轉發到遠程。本機上指定 TCP 端口或 UNIX 套接字的連接將被轉發到目標機上指定端口或套接字。

    上述參數中,bind_address 指本地地址;port 指本地端口;local_socket 指本地 UNIX 套接字;host 指遠程主機地址;hostport 指遠程端口;remote_socket 指遠程 UNIX 套接字。

    本地(ssh 客戶端)與遠程(ssh 服務端)建立一條連接,此連接的形式有四種:

    本地 [bind_address:]port    <====>   遠程 host:hostport  
    本地 [bind_address:]port    <====>   遠程 remote_socket  
    本地 local_socket           <====>   遠程 host:hostport  
    本地 local_socket           <====>   遠程 remote_socket  

    位於本機的 ssh 客戶端會分配一個套接字來監聽本地 TCP 端口(port),此套接字可綁定本機地址(bind_address, 可選,本機不同網卡具有不同的 IP 地址)或本地 UNIX 套接字(local_socket)。每當一個連接建立於本地端口或本地套接字時,此連接就會通過安全通道進行轉發。

    也可在配置文件中設置端口轉發功能。只有超級用戶可以轉發特權端口。

    默認情況下,本地端口是根據 GatewayPorts 設置選項綁定的。但是,使用顯式的bind_address 可將連接綁定到指定地址。bind_address 值是 “localhost”時,表示僅監聽本機內部數據[TODO: 待驗證],值為空或“*”時,表示監聽本機所有網卡的監聽端口。

    注意:localhost 是個域名,不是地址,它可以被配置為任意的 IP 地址,不過通常情況下都指向 127.0.0.1(ipv4)和 。127.0.0.1 這個地址通常分配給 loopback 接口。loopback 是一個特殊的網絡接口(可理解成虛擬網卡),用於本機中各個應用之間的網絡交互。

    GatewayPorts 說明 (查閱 man sshd_config):指定是否允許遠程主機(ssh客戶端)連接到本機(ssh服務端)轉發端口。默認情況下,sshd(8)將遠程端口轉發綁定到環回地址,這將阻止其他遠程主機連接到本機轉發端口。GatewayPorts 也可設置為將將遠程端口轉發綁定到非環回地址,從而允許其他遠程主機連接到本機。GatewayPorts 值“no”,表示強制遠程端口轉發僅對本機可用;值“yes”,表示強制遠程端口轉發綁定到通配符地址;值“clientspecified”,表示允許客戶端選擇轉發綁定到的地址。默認是“no”。

    -R [bind_address:]port:host:hostport
    -R [bind_address:]port:local_socket
    -R remote_socket:host:hostport
    -R remote_socket:local_socket

    此選項在本地機上執行,目標機上指定 TCP 端口或 UNIX 套接字的連接將被轉發到本機上指定端口或套接字。

    上述參數中,bind_address 指遠程地址;port 指遠程端口;remote_socket 指遠程 UNIX 套接字;host 指本地地址;hostport 指本地端口;local_socket 指本地 UNIX 套接字。

    工作原理:位於遠程的 ssh 服務端會分配一個套接字來監聽 TCP 端口或 UNIX 套接字。當目標機(服務端)上有新的連接建立時,此連接會通過安全通道進行轉發,本地機執行當前命令的進程收到此轉發的連接后,會在本機內部新建一條 ssh 連接,連接到當前選項中指定的端口或套接字。參 2.3 節分析。

    也可在配置文件中設置端口轉發功能。只有超級用戶可以轉發特權端口。

    默認情況下,目標機(服務端)上的 TCP 監聽套接字只綁定迴環接口。也可將目標機上的監聽套接字綁定指定的 bind_address 地址。bind_address 值為空或 “*” 時,表示目標機上的監聽套接字會監聽目標機上的所有網絡接口。僅當目標機上 GatewayPorts 設置選項使能時,通過此選項為目標機指定 bind_address 才能綁定成功(參考 sshd_config(5))。

    如果 port 參數是 ‘0’,目標機(服務端)可在運行時動態分配監聽端口並通知本地機(客戶端),如果同時指定了 “-O forward” 選項,則動態分配的監聽端口會被打印在標準輸出上。

    -D [bind_address:]port

    指定本地“動態”應用程序級端口轉發。它的工作方式是分配一個套接字來監聽本地端口(可選綁定指定的 bind_address)。每當連接到此端口時,連接都通過安全通道進行轉發,然後使用應用程序協議確定將遠程計算機連接到何處。目前支持 SOCKS4 和 SOCKS5 協議,ssh 將充當 SOCKS 服務器。只有 root 用戶可以轉發特權端口。還可以在配置文件中指定動態端口轉發。

    IPv6 地址可以通過將地址括在方括號中來指定。只有超級用戶可以轉發特權端口。默認情況下,本地端口是根據 GatewayPorts 設置選項進行綁定的。但是,可以使用顯式的 bind_address 將連接綁定到特定的地址。bind_address 值為 “localhost” 時表示監聽端口僅綁定為本地使用,而空地址或 “*” 表示監聽所有網絡接口的此端口。

    1.2 ssh 端口轉發模式

    ssh 的端口轉發有三種模式:

    • 本地:ssh -C -f -N -g -L local_listen_port:remote_host:remote_port agent_user@agent_host

      將本地機監聽端口 local_listen_port 上的數據轉發到遠程端口 remote_host:remote_port

    • 遠程:ssh -C -f -N -g -R agent_listen_port:local_host:local_port agent_user@agent_host

      將代理機監聽端口 agent_listen_port 上的數據轉發到本地端口 local_host:local_port

    • 動態:ssh -C -f -N -g -D listen_port agent_user@agent_host

    2. 利用 ssh 隧道建立遠程調試環境

    組網環境下設備角色如下:

    代理機:把一個具有公網 IP 的中間服務器用作 ssh 代理,將這台代理機稱作代理 A(Agent)。

    目標機:把待調試的目標機器稱作目標機 T(Target)。目標機通常是待調試的設備,處於局域網內,外網無法直接訪問內網中的設備。

    本地機:把調試用的本地計算機稱作本地機 L(Local)。本地機通常也位於局域網內。

    L 和 T 無法互相訪問,但 L 和 T 都能訪問 A。我們將 T 通過 ssh 連接到A,將 L 也通過 ssh 連接到A,A 用於轉發數據,這樣就能使用本地計算機 L 來訪問遠端設備 R。

    2.1 目標機 T (sshc)

    2.1.1 shell 中 T 連接 A

    目標機 T 上的 sshc 連接代理機 A 上的 sshd:

    ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    這條命令的作用:
    1. 建立一條 ssh 連接,T 上的 ssh 客戶端連接到 A 上的 ssh 服務器,A 的 IP 是 120.198.45.126,端口號是 10022,賬號是10022;
    2. 如果有其他 ssh 客戶端連接到了 A 的 10022 端口上,則 A 會將這條連接轉發到 T,T 在內部建立新的連接,連接到本機 22 端口。

    這條命令在 T 上執行。在 T 連接 A 這條命令里,T 是本地主機(local),A 是遠程主機(remote)。

    解釋一下此命令各選項:

    • -T 不分配偽終端;
    • -f 使 ssh 進程在用戶輸入密碼之後轉入後台運行;
    • -N 不執行遠程指令,即遠程主機(代理機A)不需執行指令,只作端口轉發;
    • -g 允許遠程主機(代理機A)連接到本地轉發端口;
    • -R 將遠程主機(代理機A)指定端口上的連接轉發到本機端口;
    • frank@120.198.45.126
      表示使用遠程主機 120.198.45.126 上的用戶 frank 來連接遠程主機;
    • :10022:127.0.0.1:22
      表示本機迴環接口(127.0.0.1,也可使用本機其他網絡接口的地址,比如以太網 IP 或 WiFi IP)的 22 端口連接到遠程主機的 10022 接口,因遠程主機 10022 綁定的地址為空,所以遠程主機會監聽其所有網絡接口的 10022 端口。

    在目標機 shell 中查看連接是否建立:

    root@localhost:~# ps | grep ssh
    22850 root      2492 S    ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    22894 root      3500 S    grep ssh

    在目標機 shell 中關閉 ssh 連接:

    kill -9 $(pidof ssh)

    此時在目標機 T 和代理機 A 中查看 ssh 連接信息,兩端都可以看到 ssh 連接不存在了。

    2.1.2 C 代碼中 T 連接 A 的處理

    C 代碼中主要還是調用 2.1.1 節中的命令。但是由 C 代碼編譯生成的進程無法在命令行和用戶進行交互,因此要避免交互問題。

    1. 避免首次連接時的 y/n(或yes/no) 詢問

    如果是首次登錄代理機 A,本機(目標機 T)沒有 A 的信息,需用用戶手動輸入 y 之後才能繼續。打印如下:

    root@localhost:~# ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    Host '120.198.45.126' is not in the trusted hosts file.
    (ssh-rsa fingerprint md5 86:09:0c:1b:fd:0b:02:8c:29:62:7f:ff:70:1b:64:f5)
    Do you want to continue connecting? (y/n) 

    如果 T 上有 A 的信息,可通過執行刪除操作:rm ~/.ssh/known_hosts 再進行上述測試。

    如果是在 C 代碼中執行登錄命令,進程在後台自動運行,是無法和用戶進行交互的。為了避免交互動作,應該禁止 ssh 發出 y/n 的詢問。

    如果 ssh 客戶端是 dropbear ssh,則添加 -y 參數,如下:

    ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    如果 ssh 客戶端是 openssh,則添加 -o StrictHostKeyChecking=no 選項,如下:

    ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    2. 避免輸入登錄密碼

    避免由用戶手動輸入登錄密碼有如下方法:

    1) 用 ssh-copy-id 把本地主機的公鑰複製到遠程主機的authorized_keys文件上,登錄不需要輸入密碼。
    2) 用 expect 調用 shell 腳本,向 shell 腳本發送密碼。這種方式是模擬鍵盤輸入。
    3) 如果是 openssh,則用 sshpass 向 ssh 命令行傳遞密碼。如果是 dropbear,則通過 DROPBEAR_PASSWORD 環境變量向 ssh 命令行傳遞密碼。

    我們採用第 3 種方法。

    假如代理機 A 上用戶 frank 密碼是 123456,則最終 C 代碼里應執行的指令如下:

    # openssh
    sshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    # dropbear
    DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    dropbear 無法接收 DROPBEAR_PASSWORD 變量傳遞密碼的處理方法:

    dropbear 包含 ssh 客戶端和 ssh 服務器,體積小巧,常用於嵌入式設備。dropbear ssh 無法接收 sshpass 傳入的密碼信息。但 dropbear ssh 可以通過環境變量 DROPBEAR_PASSWORD 傳入密碼信息。openwrt 從某一版開始,通過打補丁的方式禁用了 DROPBEAR_PASSWORD 選項,我們可以找到對應的補丁,開啟 DROPBEAR_PASSWORD 選項,再重新編譯生成 dropbear。如下:

    修改 dropbear patch 文件(如下路徑位於 openwrt 源碼根目錄):

    vim package/network/services/dropbear/patches/120-openwrt_options.patch

    將如下幾行刪除:

    @@ -226,7 +226,7 @@ much traffic. */
      * note that it will be provided for all "hidden" client-interactive
      * style prompts - if you want something more sophisticated, use 
      * SSH_ASKPASS instead. Comment out this var to remove this functionality.*/
    -#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"
    +/*#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"*/
     
     /* Define this (as well as ENABLE_CLI_PASSWORD_AUTH) to allow the use of
      * a helper program for the ssh client. The helper program should be

    重新編譯生成 dropbear,並替換設備里已安裝的 dropbear。

    #define DEFAULT_SSH_AGENT_HOST      "120.198.45.126"
    #define DEFAULT_SSH_AGENT_PORT      "10022"
    #define DEFAULT_SSH_AGENT_USER      "ssha_debug"
    #define DEFAULT_SSH_AGENT_PASSWD    "220011ssha"
    int login_to_ssh_agent(const char *host, const char *port, const char *user, const char *passwd)
    {
        // openssh client:
        // sshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
        // dropbear ssh clent:
        // DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
        char cmd[256];
        snprintf(cmd, sizeof(cmd), "DROPBEAR_PASSWORD='%s' ssh -y -T -f -N -g -R :%s:127.0.0.1:22 %s@%s", 
                 (passwd != NULL) ? passwd : DEFAULT_SSH_AGENT_PASSWD, 
                 (port != NULL) ? port : DEFAULT_SSH_AGENT_PORT,
                 (user != NULL) ? user : DEFAULT_SSH_AGENT_USER,
                 (host != NULL) ? host : DEFAULT_SSH_AGENT_HOST);
        printf("login to ssh agent: \n%s\n", cmd);
        system(cmd);
    
        return 0;
    }

    2.2 代理機 A (sshd)

    在 /etc/ssh/sshd_config 中添加如下幾行后重啟 ssh 服務:

    GatewayPorts yes
    UseDNS no
    GSSAPIAuthentication no

    目標機 T 發起連接后,在代理機 A 上查詢目標機 T 是否連接成功:

    sudo netstat -anp | grep 10022

    打印形如:

    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      8264/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      8264/sshd: frank

    上述打印中,8264 就是和目標機 T 保持連接的 sshd 進程號,如需關閉當前連接重新建立一個新的連接,則先在代理機 A 上執行:

    kill -9 8264

    然後再執行 2.1 節的指令,就會建立一次新的代理連接。

    為了安全,我們可以專門新建一個用戶,僅用於 ssh 端口轉發功能,不能在 shell 中使用此用戶登錄。如下創建一個 ssha_debug 的用戶,無 shell 登錄權限。然後為此用戶創建密碼。注意系統中 nologin 文件的位置,不同系統可能路徑不同。

    sudo useradd ssha_debug -M -s /usr/sbin/nologin
    sudo passwd ssha_debug

    2.3 本地機 L (sshc)

    2.3.1 本地機 L 登錄目標機 T

    有三種方式:

    1. 在本地機 L 上通過 ssh 登錄代理機 A,在 A 的 shell 中再登錄目標機 T

    代理服務器的公網 ip 是 120.198.45.126,內網 ip 是 192.168.1.102。

    1) 先使用 ssh(SecureCRT 或 OpenSSH 命令行) 登錄上代理服務器的 shell。如果調試機在內網,既可登錄代理機的外網 ip,也可登錄其內網 ip。

    2) 在代理機的 shell 中執行如下命令登錄遠程設備:

    ssh -p 10022 root@127.0.0.1 -vvv

    注意,此命令中用戶 root 及其密碼是遠程設備上的賬戶。

    如果提示 Host key 認證失敗之類的信息,請按提示執行如下命令:

    ssh-keygen -f "/home/frank/.ssh/known_hosts" -R [127.0.0.1]:10022

    也可直接刪除當前用戶目錄下的 .ssh/known_hosts 文件。
    然後重新執行登錄設備操作。

    建議優先使用此方法。

    2. 在本地機 L 上使用 ssh 命令登錄目標機 T

    Win 10 系統默認安裝有 OpenSSH 客戶端。可以在調試機 Windows 命令行中執行:

    ssh -p 10022 root@120.198.45.126 -vvv

    對於本地計算機來說,待調試的設備 ip 地址不可見。本機登錄到代理機 120.198.45.126 的轉發端口 10022,通過代理機轉發功能,本地機能成功登錄到遠程設備上。注意,此命令中用戶 root 及其密碼是設備上的賬戶,不是 SSH 代理服務器上的賬戶。

    如果出現認證失敗之類的信息。可刪除 C:/Users/當前用戶/.ssh/known_hosts 文件,然後再試。

    3. 在本地機 L 上使用 SecureCRT 工具登錄目標機 T

    也可以直接使用 SecureCRT 軟件,設置好代理機的 ip(120.198.45.126) 和端口號(10022),填上設備的登錄用戶和登錄密碼。

    不建議使用此方法。因為連接過程太長或連接失敗的話,無法看到錯誤提示信息。

    2.3.2 查看代理機 A 打印信息

    在 L 執行登錄 T 之前查看打印信息:

    frank@SERVER:~$ sudo netstat -anp  | grep 10022
    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      106438/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      106438/sshd: frank

    在 L 執行登錄 T 之後查看打印信息:

    frank@SERVER:~$ sudo netstat -anp  | grep 10022
    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      106438/sshd: frank
    tcp        0      0 192.168.1.102:10022     120.229.163.51:27027    ESTABLISHED 106438/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      106438/sshd: frank

    可以看到,上述第二行是 L 執行登錄命令后新出現的打印信息。表示新建立了一條 L 到 A 的 ssh 連接。

    L 端的外網地址 120.229.163.51:27027 連接到 A 上的 192.168.1.102:10022,L 通常位於局域網內、具有一個內網地址,120.229.163.51 可能是 L 連接的路由器的公網 IP。

    這條連接建立后,A 將這條連接轉發到 R。

    2.3.3 查看目標機 T 打印信息

    在 L 執行登錄 T 之前查看打印信息:

    root@localhost:~# netstat -anp | grep 22
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      917/sshd
    tcp        0      0 192.168.202.140:47989   120.198.45.126:22       ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:22      192.168.202.100:64737   ESTABLISHED 2041/sshd
    tcp        0      0 :::22                   :::*                    LISTEN      917/sshd

    在 L 執行登錄 T 之後查看打印信息:

    root@localhost:~# netstat -anp | grep 22
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      917/sshd
    tcp        0      0 192.168.202.140:47989   120.198.45.126:22       ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:51732   192.168.202.140:22      ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:22      192.168.202.140:51732   ESTABLISHED 9579/sshd
    tcp        0      0 :::22                   :::*                    LISTEN      917/sshd

    可以看到,上述第 3 行和第 4 行是登錄之後新增加的打印信息。

    第 2 行,表示 T 上的 ssh 客戶端連接到了 A 上的 ssh 服務端,進程號是 9452。第 3 行,表示進程 9452 收到了 A 轉發來的 ssh 連接后,在本機內部建立新的 ssh 連接,使用 51732 端口號作為 ssh 客戶端,連接到本機 22 端口,22 端口是 sshd 端口。第 4 行,表示本機新啟動一個 sshd 進程,來接收 sshc 的連接。

    這樣,L 到 T 的 ssh 通路徹底打通了。A 將來自 L 的連接轉發到 R,R 在內部啟動了 sshd 來處理來自 L 的請求,通過 A 的代理作用,實現了 L 上的 sshc 和 T 上的 sshd 的交互。

    3. 典型使用場景步驟總結

    上文已涵蓋詳細使用方法,但篇幅太長。此處簡單總結使用步驟如下:

    3.1 在代理機 A 上執行

    使用 SecureCRT 登錄代理機 A。代理機外網 ip 120.198.45.126,內網 ip 192.168.1.102,端口 22。如果本地機與代理機在同一個局域網裡,使用代理機的內網 ip 登錄即可。

    在代理機 shell 中查看是否有未關閉的 ssh 隧道:

    sudo netstat -anp | grep 10022

    若打印形如:

    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      8264/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      8264/sshd: frank

    則表示有未關閉的 ssh 隧道連接。執行如下命令可關閉連接。

    kill -9 8264

    3.2 在目標機 T 上執行

    使用遠程應用程序接口或者在遠程設備 T 上做一些特殊操作,觸發 T 執行如下兩條指令之一:

    # openssh
    sshpass -p '123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    # dropbear
    DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    3.3 在本地機 L 上執行

    在本地機 L 上執行如下指令,登錄遠程目標機 T:

    ssh -vvv -p 10022 root@120.198.45.126

    另外一種變通的方式是,在本地機先通過 ssh 登錄上代理機 A 的 shell。然後在 A 的 shell 中執行如下指令:

    ssh -vvv -p 10022 root@127.0.0.1

    4. 注意事項

    1. 確保代理機 A 所在的網絡防火牆不屏蔽 10022 端口
    2. 確保代理機 A 上 /etc/ssh/sshd_config 配置文件設置正確
    3. 關閉 ssh 隧道既可在代理機 A 上進行(關閉相應的 sshd 進程),也可在目標機 T 上進行(關閉相應的 ssh 進程)
    4. 每次只能訪問一台目標機。如果想同時訪問多台,可以代理機上設置多個轉發端口,每條連接使用一個端口進行轉發
    5. 為保證安全,打開 ssh 隧道時盡量使用無登錄權限的用戶,並且此用戶的密碼建議經常更新

    5. 參考資料

    [1] 阮一峰,
    [2]
    [3]

    6. 修改記錄

    2019-11-20 V1.0 初稿

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

    【其他文章推薦】

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 用雲開發快速製作客戶業務需求收集小程序丨實戰

    用雲開發快速製作客戶業務需求收集小程序丨實戰

    一、導語

    ​ 如何省去企業上門(現場)搜集客戶需求的環節,節約企業人力和時間成本,將客戶的業務定製需求直接上傳至雲數據庫?雲開發為我們提供了這個便利!

    二、需求背景

    ​ 作為一名XX公司IT萌萌新,這段時間對小程序開發一直有非常濃厚的興趣,並且感慨於“雲開發·不止於快”的境界。近期工作中,剛好碰見業務部門的一個需求,目的是節約上門跟客戶收集業務定製資料的時間,以往是每變更一次,就需要上門一次,碰見地域較遠的,費時費力,且往往要求幾天內完成上線,時間非常緊迫。因此,結合一直以來對雲開發的各種優勢的了解,我說服公司領導通過小程序·雲開發來實現。

    下面是其中一項業務定製界面的展示:
    (1)業務對業務流程有簡單說明;
    (2)相關業務介紹;
    (3)不同客戶輸入個性化需求;
    (4)雲存儲後台實現需求表單的收集。

    ​ 得力於雲開發提供的API和WeUI庫的便利,本項目在我在極短的時間內就實現了比較理想的效果 。接下來,我就從本項目入手,講講我是如何依靠小程序·雲開發將想法快速實現的,其實我也是剛入門沒多久,只是想分享一下自身在學習小程序開發項目中的一些知識點和體會,代碼可能略為粗糙,邏輯也有待優化,歡迎大家在評論區多多交流。

    三、開發過程

    1、組件

    主要使用了官方WeUI擴展能力的一些組件庫來實現主要功能。

    核心的WeUI庫主要有 Msg、Picker、圖片的Upload等(以快為目的,節省自己寫CSS樣式的時間,也方便0基礎的同學上手,這裏又體會到了小程序開發的便捷)。

    2、實現代碼

    本次雲開發包括雲數據庫雲存儲兩大功能:

    (1)雲數據庫

    雲數據庫的主要就是搜集客戶提交上來的表單信息,包括客戶的聯繫方式和選擇的業務類型等,並存儲在雲數據庫中,方便業務經理搜集需求。

    我們來看簡單的實現過程:

    首先是表單,用到了 form 表單組件以及它的 bindsubmit 方法,在 wxml 中放置 form 表單:

    <form bindsubmit="formSubmit">
        <view class="form">
          <view class="section">
            <picker bindchange="bindPickerGsd" mode="selector" value="{{indexGsd}}" range="{{arrayGsd}}">
              <view class="picker">歸屬縣市</view>
              <view class="picker-content" >{{arrayGsd[indexGsd]?arrayGsd[indexGsd]:"(必填項) 請下拉選擇歸屬地"}}</view> 
            </picker>
          </view>    
          <!---中間部分詳見代碼--->
        </view>
    
        <view class="footer">
          <button class="dz-btn" formType="submit" loading="{{formStatus.submitting}}" disabled="{{formStatus.submitting}}" bindtap="openSuccess">提交</button>
        </view>
      </form>

    表單中除了普通的文本輸入,增加有下拉列表的實現(畢竟客戶有時候是比較懶的)。

    來看一下具體代碼:

    bindPickerGsd: function (e) {    
      console.log('歸屬地已選擇,攜帶值為', e.detail.value)
      console.log('歸屬地選擇:', this.data.arrayGsd[e.detail.value])    
      this.setData({
         indexGsd: e.detail.value     
       })   
       this.data.formData.home_county = this.data.arrayGsd[e.detail.value]
    },

    最後表單上傳到雲數據庫:

      // 表單提交
      formSubmit: function (e) {
        var minlength = '';
        var maxlength = '';
        console.log("表單內容",e)
        var that = this;
        var formData = e.detail.value;
        var result = this.wxValidate.formCheckAll(formData);
        
        console.log("表單提交formData", formData);
        console.log("表單提交result", result)
        wx.showLoading({
          title: '發布中...',
        })
        const db = wx.cloud.database()
        db.collection('groupdata').add({
          data: {
            time: getApp().getNowFormatDate(),
            home_county: this.data.formData.home_county,
            group_name: formData.group_name,
            contact_name: formData.contact_name,
            msisdn: formData.msisdn,
            product_name: this.data.formData.product_name,
            word: formData.word,
          },
          success: res => {
            wx.hideLoading()
            console.log('發布成功', res)
    
          },
          fail: err => {
            wx.hideLoading()
            wx.showToast({
              icon: 'none',
              title: '網絡不給力....'
            })
            console.error('發布失敗', err)
          }
        })
      },
    (2)雲存儲

    ​ 因為業務的定製需要填單客戶所在單位的授權證明材料,因此需要提單人(使用人)上傳證明文件,因此增加了使用雲存儲的功能。

    核心代碼:

        promiseArr.push(new Promise((reslove,reject)=>{
            wx.cloud.uploadFile({
                cloudPath: "groupdata/" + group_name + "/" + app.getNowFormatDate() +suffix,
                filePath:filePath
            }).then(res=>{
                console.log("授權文件上傳成功")          
                })
                reslove()
                }).catch(err=>{
                console.log("授權文件上傳失敗",err)
        })
    
        因為涉及到不同頁面的數據傳遞,即將表單頁面的group_name作為雲存儲的文件夾用於存儲該客戶在表單中上傳的圖片,因此還需要用到getCurrentPages()來進行頁面間的數據傳遞 
    
        var pages = getCurrentPages();
        var prePage = pages[pages.length - 2];//pages.length就是該集合長度 -2就是上一個活動的頁面,也即是跳過來的頁面
        var group_name = prePage.data.formData.group_name.value//取上頁data里的group_name數據用於標識授權文件所存儲文件夾的名稱

    3、待進一步優化

    ​ 基於時間關係,本次版本僅對需求進行了簡單實現,作為公司一個可靠的項目,還需要關注”客戶隱私”、“數據安全”,以及更人性化的服務。比如:

    (1)提單人確認和認證過程

    可靠性:增加驗證碼驗證(防止他人冒名登記),以及公司受理業務有個客戶本人提交憑證。

    (2)訂閱消息

    受理成功后,可以給客戶進行處理結果的反饋,增強感知。

    (3)人工客服

    進行在線諮詢等。

    四、總結

    ​ 在本次項目開發中,我深刻體會到了雲開發的“快”,特別是雲數據庫的增刪查改功能非常方便。雲開發提供的種種便利,讓我在有新創意的時候,可以迅速採用小程序雲開發快速實現,省時省力,還能免費使用騰訊雲服務器,推薦大家嘗試!

    源碼地址

    如果你想要了解更多關於雲開發CloudBase相關的技術故事/技術實戰經驗,請掃碼關注【騰訊云云開發】公眾號~

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 技術人如何利用 github+Jekyll ,搭建一個獨立免費的技術博客

    技術人如何利用 github+Jekyll ,搭建一個獨立免費的技術博客

    上次有人留言說,技術博客是程序員的標配,但據我所知絕大部分技術同學到現在仍然沒有自己的技術博客。原因有很多,有的是懶的寫,有的是怕寫不好,還有的是一直想憋個大招,幻想做到完美再發出來,結果一直胎死腹中。但其實更多程序員是不知道如何去搭建一個博客,其實如今搭建一個個人技術博客非常簡單,其中最簡單搭建方式莫屬使用 GitHub Pages + Jekyll 了,我的博客就是使用這種技術。

    GitHub Pages

    Github Pages 是面向用戶、組織和項目開放的公共靜態頁面搭建託管服務,站點可以被免費託管在 Github 上,你可以選擇使用 Github Pages 默認提供的域名 github.io 或者自定義域名來發布站點。Github Pages 支持 自動利用 Jekyll 生成站點,也同樣支持純 HTML 文檔,將你的 Jekyll 站點託管在 Github Pages 上是一個不錯的選擇。

    使用 Github Pages 搭建博客有以下幾個優點:

    • 完全免費,其中服務器、流量、域名什麼的都管,完全零費用搭建一個技術博客
    • 寫博客就是提交代碼,讓寫作和編程的體驗保持一致
    • 支持綁定自己的域名
    • 提供流行的網頁主題模板

    缺點也是有的:

    • 不支持動態內容,博客必須都是靜態網頁,一般會使用 Jekyll 來構建內容。
    • 博客不能被百度索引,因 Github 和百度有過節,所以 Github 就把百度給屏蔽了。
    • 倉庫空間不大於1G
    • 每個月的流量不超過100G
    • 每小時更新不超過 10 次

    Github Pages 使用 Jekyll 來構建內容,那麼 Jekyll 是什麼呢?

    Jekyll 介紹

    Jekyll 是一個簡單的博客形態的靜態站點生產機器。它有一個模版目錄,其中包含原始文本格式的文檔,通過一個轉換器(如 Markdown)和我們的 Liquid 渲染器轉化成一個完整的可發布的靜態網站,你可以發布在任何你喜愛的服務器上。Jekyll 也可以運行在 GitHub Page 上,也就是說,你可以使用 GitHub 的服務來搭建你的項目頁面、博客或者網站,而且是完全免費的。

    但如果我們只是在 GitHub 上面使用的話,到不需要知道 Jekyll 的語法,一般 Github 會自動將我們寫的 Markdown 文件轉換成靜態頁面。使用 Jekyll 需要使用 Markdown 語法來寫你的文章,不過 Markdown 語法非常簡單,做為程序員來講基本上兩三天就掌握了,大家也可以參考這篇文章:。

    給大家分享一些 Jekyll 主題,這個網站下有很多 主題,大家可以根據自己的愛好去選擇博客主題。

    我的個人博客

    我的博客經過了三個階段,第一個階段,完全依託於使用 GitHub Pages 來構建;第二個階段,將博客託管於國外的一個服務商;第三個階段,服務器遷移回到國內、域名備案。之前也寫過幾篇關於技術博客的文章,如下:

    使用 Github Pages + Jekyll 構建一個技術博客很簡單,基本上步驟就是網上找一個自己喜歡的主題,直接 Fork 到自己的 Github ,然後在刪掉原博客中的內容,在上傳自己的文章即可,以我自己的博客為例。

    我的博客最初使用的是,但這個主題已經盡兩年多都沒有更新了。因此後期我在這個主題的基礎上做了一些改動,其中有依賴組件的更新,結合個人情況對個別頁面進行了改版,就成為了現在的樣子:

    使用這個主題的原因是,我比較喜歡簡潔大氣的風格,並且此博客主題對代碼展示支持良好。

    快速構建一個博客

    以我的博客為例,介紹如何最快搭建一個博客。這也是我博客經歷的第一個階段。

    1、首先打開地址,點擊 Fork 按鈕將代碼複製一份到自己的倉庫。

    過上一分鐘,你的 github 倉庫發現一個 ityouknow.github.io 項目。

    2、刪除 CNAME 文件

    刪除項目中的 CNAME 文件,CNAME 是定製域名的時候使用的內容,如果不使用定製域名會存在衝突。

    3、設置 GitHub Pages

    點擊 Settings 按鈕打開設置頁面,頁面往下拉到 GitHub Pages 相關設置,在 Source 下面的複選框中選擇 master branch ,然後點擊旁邊的 Save 按鈕保存設置。

    4、重命名項目

    點擊 Settings 按鈕打開設置頁面,重命名項目名稱為:github_username.github.io。

    github_username 是你的 github 登錄用戶名

    5、重命名之後,再次回到 Settings > GitHub Pages 頁面

    會發現存在這樣一個地址:

    這個時候,你訪問此地址已經可以看到博客的首頁,但是點擊文章的時鏈接跳轉地址不對,這是因為少配置了一個文件。

    6、配置 _config.yml

    打開項目目錄下的 _config.yml 文件,修改以下配置:

    repository: github_username/github_username.github.io
    github_url: https://github.com/github_username
    url: https://github_username.github.io

    這時候在訪問地址: https://github_username.github.io,就會發現博客就已經構建完成了。剩下的事情就是去項目的 _posts 目錄下刪除掉我的文章,然後按照 Jekyll 的語法就寫自己的文章就好了。

    github_username 為你的 github id。

    自定義域名

    雖然通過地址https://github_username.github.io可以正常訪問博客,但是技術小夥伴們肯定有人想使用自己的域名訪問博客,這樣的需求 GitHub Pages 也是支持的。

    首先需要設置域名解析,將域名的地址指向自己的 github 博客地址。這裏以萬網的域名配置為例,選擇需要設置的域名點擊解析,在域名解析頁面添加以下兩條記錄

    紅框內,需要填寫自己github_username值。

    然後重新打開項目的 Settings > GitHub Pages 頁面,Custom domain 下的輸入框輸入剛才設置的域名:xxx.com,點擊保存即可。

    重新配置 _config.yml

    打開項目目錄下的 _config.yml 文件,修改以下配置:

    url: http://www.xxx.com

    等待一分鐘之後,瀏覽器訪問地址:www.xxx.com 即可訪問博客。

    自定義 DIY 博客

    一般同學到上面這一步也就完成了,基本滿足了 80% 技術同學的需求。但還是有一些同學們有更高的追求,比如說使用 Github Pages 雖然簡單方便,但是不能被百度檢索白白流失了大量的流量,還有一個原因有些時候,博客網絡訪問穩定性不是很高。

    當時我在國外有幾個虛擬機,本來用作它用,後來在上面安裝了一個 Nginx 作為靜態頁面的服務器。首先我在本機(win10)安裝了 Jekyll 環境,將 Github 上的博客代碼下載下來之後,在本機編譯成靜態的 Html ,然後手動上傳到服務的 Nginx 目錄下;然後將域名指向虛擬機。

    非常不建議大家實踐以上這段內容,win10 上面安裝 Jekyll 環境是一段慘痛的經歷。

    就這樣很麻煩的步驟我用了幾個月後,實在是受不了了,一方面因為服務器在國外,有時候仍然不穩定(可能因為服務器安裝了代理),另一方面我需要使用一些功能,使用這些功能的前提是網站需要備案,那段時間騰訊雲在做活動,就把博客又從國外搬了回來,順便重新優化了一下流程。

    仍然把博客託管在 Github 上面,每次提交完代碼后,在騰訊雲上面執行一個腳本,這個腳本會自動從 Github 拉取最新更新的文件,並自動生產靜態的 Html 文件推送到 Nginx 目錄,域名重新指向這台服務器。可以在 Github 上面設置一些鈎子,當提交代碼的時候自動觸髮腳本,也可以定時觸髮腳本來發布文章。

    腳本內容如下:

    cd /usr/local/ityouknow.github.io
    git pull http://github.com/ityouknow/ityouknow.github.io.git
    jekyll build --destination=/usr/share/nginx/html

    執行此腳本的前提是安裝好 git\jekyll 環境,這個網上有很多案例,這裏就不再多描述了。
    關於 Jekyll 環境搭建和使用可以參考這裏:

    自動化部署

    這两天看到,我也按照他的步驟實踐了一番,很好用,所以把自動化部署這段寫補上。

    配置 Webhook

    在開發過程中的 Webhook,是一種通過通常的 callback,去增加或者改變 Web page或者 Web app 行為的方法。這些 Callback 可以由第三方用戶和開發者維持當前,修改,管理,而這些使用者與網站或者應用的原始開發沒有關聯。Webhook 這個詞是由 Jeff Lindsay 在 2007 年在計算機科學 hook 項目第一次提出的。

    用大白話講就是,代碼倉庫在收到代碼提交的時候,會自動觸發一個 url 類型的通知,你可以根據這個通知去做一些事情,比如提交了代碼就自動去部署項目。

    我們的自動部署博客也是利用了這個機制,Github 自帶了 Webhook 功能,我們直接配置即可使用。

    在 Github 倉庫的項目界面,比如本博客項目 https://github.com/ityouknow/ityouknow.github.io,點擊 Setting->Webhooks->Add Webhook,在添加 Webhooks 的配置信息,我的配置信息如下:

    Payload URL: http://www.ityouknow.com/deploy
    Content type: application/json
    Secret: 123456

    如下圖:

    服務器接受推送

    我們需要在博客的服務器上面建立一個服務,來接收 Github 提交代碼后的推送,從而來觸發部署的腳本。 Github 上有一個開源項目可以做這個事情 。

    這個開源項目目的很單純,就是負責接收 Github 推送過來的通知,然後執行部署腳本,不過他是使用 NodeJs 來開發的,所以我們先需要在 Centos 上面按照 Node 環境。

    centos7 安裝 Node 環境

    首先添加源

    sudo rpm -ivh https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
    
    //yum安裝node.js
    yum install -y nodejs

    然後在安裝 github-webhook-handler

    npm install -g github-webhook-handler     #安裝 github-webhook-handler
    #如果沒有安裝成功,可以選擇法2來安裝
    npm install -g cnpm --registry=http://r.cnpmjs.org
    cnpm install -g github-webhook-handler

    安裝成功之後,我們需要添加一個腳本。進入到安裝目錄下:

    cd  /usr/lib/node_modules/github-webhook-handler

    新建 deploy.js

    vi deploy.js

    腳本內容如下:

    var http = require('http')
    var createHandler = require('github-webhook-handler')
    var handler = createHandler({ path: '/deploy', secret: 'ityouknow' }) //監聽請求路徑,和Github 配置的密碼
     
    function run_cmd(cmd, args, callback) {
      var spawn = require('child_process').spawn;
      var child = spawn(cmd, args);
      var resp = "";
     
      child.stdout.on('data', function(buffer) { resp += buffer.toString(); });
      child.stdout.on('end', function() { callback (resp) });
    }
     
    http.createServer(function (req, res) {
      handler(req, res, function (err) {
        res.statusCode = 404
        res.end('no such location')
      })
    }).listen(3006)//監聽的端口
     
    handler.on('error', function (err) {
      console.error('Error:', err.message)
    })
     
    handler.on('push', function (event) {
      console.log('Received a push event for %s to %s',
        event.payload.repository.name,
        event.payload.ref);
      run_cmd('sh', ['/usr/local/depoly.sh'], function(text){ console.log(text) });//成功后,執行的腳本。
    })

    腳本的作業就是啟動一個監聽端口來接收請求,接收到請求后執行部署腳本,腳本內容的關鍵點已經標註上註釋。

    部署博客的腳本如下:depoly.sh

    echo `date`
    cd /usr/local/ityouknow.github.io
    echo start pull from github 
    git pull http://github.com/ityouknow/ityouknow.github.io.git
    echo start build..
    jekyll build --destination=/usr/share/nginx/html

    就是拉取代碼,進行部署而已。

    這個腳本的啟動需要藉助 Node 中的一個管理 forever 。forever 可以看做是一個 nodejs 的守護進程,能夠啟動,停止,重啟我們的 app 應用。

    不過我們的先安裝 forever,然後需要使用 forever 來啟動 deploy.js 的服務,執行命令如下:

    npm install forever -g   #安裝
    $ forever start deploy.js          #啟動
    $ forever stop deploy.js           #關閉
    $ forever start -l forever.log -o out.log -e err.log deploy.js   #輸出日誌和錯誤
    /root/node-v8.12.0-linux-x64/lib/node_modules/forever/bin/forever start -l forever.log -o out.log -e err.log deploy.js
    
    如果報錯:
    /root/node-v8.12.0-linux-x64/lib/node_modules/forever/bin/forever start -a -l forever.log -o out.log -e err.log deploy.js

    同時一般情況下,我們不會對外保留很多端口,所以需要通過博客的地址來轉發,需要在 Nginx 上面添加一個轉發配置,用來監聽的 /deploy 請求轉發到 nodejs 服務上,配置代碼如下:

    location = /deploy {
         proxy_pass http://127.0.0.1:3006/deploy;
    }

    這樣我們整個自動化部署就完了,每次提交代碼時,Github 會發送 Webhook 給地址http://www.ityouknow.com/deploy,Nginx 將 /deploy 地址轉發給 Nodejs 端口為 3306 的服務,最後通過 github-webhook-handler 來執行部署腳本,已到達自動部署的目的。

    以後只需要我們提交代碼到 Github ,就會自動觸發博客的自動化部署。

    可能會出現的問題

    有一些小夥伴反饋在克隆博客的時候出現了一些問題,在這裏集中回復一下。

    1、克隆博客后格式丟失

    這是很多讀者反饋的第一個問題,因為我的博客 css 和 圖片是放到另外一個域名下的:www.itmind.net ,因此這塊大家克隆過去需要改成本地的。

    主要涉及的文件 ityouknow.github.io\_includes 目錄下 head.html 和 footer.html 兩個文件夾,將文件中的 http://www.ityouknow.com/xxx/xxx 改為相對路徑/xxx/xxx即可。

    2、留言功能丟失

    這裏就需要大家修改一下 _config.yml 中 gitalk 的配置信息。具體如何操作大家可以參考這篇文章 。註冊完之後,需要在 _config.yml 配置以下信息:

    gitalk:
        owner: ityouknow
        repo: blog-comments
        clientID: 61bfc53d957e74e78f8f
        clientSecret: 31c61e66cdcc9ada8db2267ee779d0bdafac434c

    將這裏改成你註冊好的信息

    3、博客

    博客現在還缺檢索功能,下一頁和上一頁功能、系列文章優化查看的功能,大家克隆後有完善功能的,也請幫忙留意,共同把這個博客完善的更好。

    最後,大家可以在這篇文章下留下你的個人博客地址,方便同行們觀賞、交流、學習。

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

    【其他文章推薦】

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • Cesium坐標系及坐標轉換詳解

    Cesium坐標系及坐標轉換詳解

    前言

    Cesium項目中經常涉及到模型加載、瀏覽以及不同數據之間的坐標轉換,弄明白Cesium中採用的坐標系以及各個坐標系之間的轉換,是我們邁向三維GIS大門的前提,本文詳細的介紹了Cesium中採用的兩大坐標系以及之間轉換的各種方法。

    Cesium中的坐標系

    Cesium中常用的坐標有兩種WGS84地理坐標系和笛卡爾空間坐標系,我們平時常用的以經緯度來指明一個地點就是用的WGS84坐標,笛卡爾空間坐標系常用來做一些空間位置變換如平移旋轉縮放等等。二者的聯繫如下圖。

    其中,WGS84地理坐標系包括 WGS84經緯度坐標系(沒有實際的對象)和 WGS84弧度坐標系(Cartographic);

             笛卡爾空間坐標系包括 笛卡爾空間直角坐標系(Cartesian3)、平面坐標系(Cartesian2),4D笛卡爾坐標系(Cartesian4)。

    WGS84坐標系

    World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統,坐標原點為地球質心,其地心空間直角坐標系的Z軸指向BIH (國際時間服務機構)1984.O定義的協議地球極(CTP)方向,X軸指向BIH 1984.0的零子午面和CTP赤道的交點,Y軸與Z軸、X軸垂直構成右手坐標系。我們平常手機上的指南針显示的經緯度就是這個坐標系下當前的坐標,進度範圍[-180,180],緯度範圍[-90,90]。

    WGS84坐標系

    Cesium目前支持兩種坐標系WGS84和WebMercator,但是在Cesium中沒有實際的對象來描述WGS84坐標,都是以弧度的方式來進行運用的也就是Cartographic類:

    new Cesium.Cartographic(longitude, latitude, height),這裏的參數也叫longitude、latitude,就是經度和緯度,計算方法:弧度= π/180×經緯度角度。

     笛卡爾空間直角坐標系(Cartesian3)

    笛卡爾空間坐標的原點就是橢球的中心,我們在計算機上進行繪圖時,不方便使用經緯度直接進行繪圖,一般會將坐標系轉換為笛卡爾坐標系,使用計算機圖形學中的知識進行繪圖。這裏的Cartesian3,有點類似於三維繫統中的Point3D對象,new Cesium.Cartesian3(x, y, z),裏面三個分量x、y、z。

    笛卡爾空間直角坐標系

    平面坐標系(Cartesian2)

    平面坐標系也就是平面直角坐標系,是一個二維笛卡爾坐標系,與Cartesian3相比少了一個z的分量,new Cesium.Cartesian2(x, y)。Cartesian2經常用來描述屏幕坐標系,比如鼠標在電腦屏幕上的點擊位置,返回的就是Cartesian2,返回了鼠標點擊位置的xy像素點分量。

    平面坐標系

    坐標轉換

    經緯度和弧度的轉換

    var radians=Cesium.Math.toRadians(degrees);//經緯度轉弧度
    var degrees=Cesium.Math.toDegrees(radians);//弧度轉經緯度

    WGS84經緯度坐標和WGS84弧度坐標系(Cartographic)的轉換

    //方法一:
    var longitude = Cesium.Math.toRadians(longitude1); //其中 longitude1為角度
    
    var latitude= Cesium.Math.toRadians(latitude1); //其中 latitude1為角度
    
    var cartographic = new Cesium.Cartographic(longitude, latitude, height);
    
    //方法二:
    var cartographic= Cesium.Cartographic.fromDegrees(longitude, latitude, height);//其中,longitude和latitude為角度
    
    //方法三:
    var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude為弧度

    WGS84坐標系和笛卡爾空間直角坐標系(Cartesian3)的轉換

    通過經緯度或弧度進行轉換
    var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);//其中,高度默認值為0,可以不用填寫;longitude和latitude為角度
    
    var positions = Cesium.Cartesian3.fromDegreesArray(coordinates);//其中,coordinates格式為不帶高度的數組。例如:[-115.0, 37.0, -107.0, 33.0]
    
    var positions = Cesium.Cartesian3.fromDegreesArrayHeights(coordinates);//coordinates格式為帶有高度的數組。例如:[-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]
    
    //同理,通過弧度轉換,用法相同,具體有Cesium.Cartesian3.fromRadians,Cesium.Cartesian3.fromRadiansArray,Cesium.Cartesian3.fromRadiansArrayHeights等方法

    注意:上述轉換函數中最後均有一個默認參數ellipsoid(默認值為Ellipsoid.WGS84)。

    通過過度進行轉換

    具體過度原理可以參考上邊的注意事項。

    var position = Cesium.Cartographic.fromDegrees(longitude, latitude, height);
    var positions = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
    var positions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray([position1,position2,position3]);

    笛卡爾空間直角坐標系轉換為WGS84

    直接轉換
    var cartographic= Cesium.Cartographic.fromCartesian(cartesian3);

    轉換得到WGS84弧度坐標系后再使用經緯度和弧度的轉換,進行轉換到目標值

    間接轉換
    var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
    var cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray([cartesian1,cartesian2,cartesian3]);

    平面坐標系(Cartesian2)和笛卡爾空間直角坐標系(Cartesian3)的轉換

    平面坐標系轉笛卡爾空間直角坐標系

    這裏注意的是當前的點(Cartesian2)必須在三維球上,否則返回的是undefined;通過ScreenSpaceEventHandler回調會取到的坐標都是Cartesian2。

    屏幕坐標轉場景坐標-獲取傾斜攝影或模型點擊處的坐標

    這裏的場景坐標是包含了地形、傾斜攝影表面、模型的坐標。

    通過viewer.scene.pickPosition(movement.position)獲取,根據窗口坐標,從場景的深度緩衝區中拾取相應的位置,返回笛卡爾坐標。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.pickPosition(movement.position);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:若屏幕坐標處沒有傾斜攝影表面、模型時,獲取的笛卡爾坐標不準,此時要開啟地形深度檢測(viewer.scene.globe.depthTestAgainstTerrain = true; //默認為false)。

    屏幕坐標轉地表坐標-獲取加載地形后對應的經緯度和高程

    這裡是地球表面的世界坐標,包含地形,不包括模型、傾斜攝影表面。

    通過viewer.scene.globe.pick(ray, scene)獲取,其中ray=viewer.camera.getPickRay(movement.position)。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var ray = viewer.camera.getPickRay(movement.position);
         var position = viewer.scene.globe.pick(ray, viewer.scene);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為該點的地形高程值。

    屏幕坐標轉橢球面坐標-獲取鼠標點的對應橢球面位置

    這裏的橢球面坐標是參考橢球的WGS84坐標(Ellipsoid.WGS84),不包含地形、模型、傾斜攝影表面。

    通過 viewer.scene.camera.pickEllipsoid(movement.position, ellipsoid)獲取,可以獲取當前點擊視線與橢球面相交處的坐標,其中ellipsoid是當前地球使用的橢球對象:viewer.scene.globe.ellipsoid,默認為Ellipsoid.WGS84

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為0(此值應該為地表坐標減去地形的高程)。

    笛卡爾空間直角坐標系轉平面坐標系

    var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

    空間位置變換

    經緯度轉換到笛卡爾坐標系后就能運用計算機圖形學中的仿射變換知識進行空間位置變換如平移旋轉縮放。

    Cesium為我們提供了很有用的變換工具類:Cesium.Cartesian3(相當於Point3D)Cesium.Matrix3(3×3矩陣,用於描述旋轉變換)Cesium.Matrix4(4×4矩陣,用於描述旋轉加平移變換),Cesium.Quaternion(四元數,用於描述圍繞某個向量旋轉一定角度的變換)。

    下面舉個例子:

          一個局部坐標為p1(x,y,z)的點,將它的局部坐標原點放置到loc(lng,lat,alt)上,局部坐標的z軸垂直於地表,局部坐標的y軸指向正北,並圍繞這個z軸旋轉d度,求此時p1(x,y,z)變換成全局坐標笛卡爾坐p2(x1,y1,z1)是多少?

    var rotate = Cesium.Math.toRadians(d);//轉成弧度
    var quat = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, rotate); //quat為圍繞這個z軸旋轉d度的四元數
    var rot_mat3 = Cesium.Matrix3.fromQuaternion(quat);//rot_mat3為根據四元數求得的旋轉矩陣
    var v = new Cesium.Cartesian3(x, y, z);//p1的局部坐標
    var m = Cesium.Matrix4.fromRotationTranslation(rot_mat3, Cesium.Cartesian3.ZERO);//m為旋轉加平移的4x4變換矩陣,這裏平移為(0,0,0),故填個Cesium.Cartesian3.ZERO
    m = Cesium.Matrix4.multiplyByTranslation(m, v);//m = m X v
    var cart3 = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lng, lat, alt)); //得到局部坐標原點的全局坐標
    var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(cart3);//m1為局部坐標的z軸垂直於地表,局部坐標的y軸指向正北的4x4變換矩陣
    m = Cesium.Matrix4.multiplyTransformation(m, m1);//m = m X m1
    var p2 = Cesium.Matrix4.getTranslation(m);//根據最終變換矩陣m得到p2
    console.log('x=' + p2.x + ',y=' + p2.y + ',z=' + p2.z );

    總結

    通過本文,介紹了各個坐標系間的轉換問題,在具體項目中,可結合實際需求,靈活組合解決具體的實際問題。注意,博文是參照網上相關博客及結合自己的實踐總結得來,希望本文對你有所幫助,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • Mirantis 收購 Docker EE | 雲原生生態周報 Vol. 28

    Mirantis 收購 Docker EE | 雲原生生態周報 Vol. 28

    作者 | 禪鳴、進超、心水、心貴

    業界要聞

    Mirantis 是一家紮根於 OpenStack 的雲公司,最近專註於 Kubernetes。該公司剛剛收購了 Docker 的企業部門,該業務部門包括 Docker Enterprise 技術平台及所有相關的知識產權、約 400 名員工中的 300 人、750 家企業客戶以及所有企業夥伴關係。

    Project Quay 包含一系列在 Apache 2.0 和其他開源許可證下許可的開源軟件。它遵循一個帶有維護者委員會的開源治理模型。

    KubeCon + CloudNativeCon North America 2019 於 11 月 18 日 在 San Diego 正式召開。

    上游重要進展

    KEP

    主要為了解決以下問題:

    • PreSidecars 將在普通容器之前啟動,但在 init 容器之後啟動,這樣它們就可以在您的主進程開始之前準備好;
    • PostSidecars 將在普通容器之後啟動,以便它們在您的主進程啟動后可以執行某些操作,例如更新 css 文件,轉發日誌等。

    主要為了解決以下問題:

    • Client/Server 版本偏移;
    • API 擴展支持;
    • 提供更簡單的選項來與 cli 工具進行集成(例如 jq);
    • 提供與 unix cli 標準集成的接口(xargs/find -exec/globbing);
    • 保留配置註釋,結構等。

    IP地址類型分解為IPv4IPv6。並逐步棄用原有地址類型,其在 1.17 中對新的 EndpointSlices 無效,然後在 1.18 中變得完全無效。

    K8S PR

    提供一種在 --show-enable-metrcis-for-version 設置時重新註冊隱藏指標的機制。

    有兩個原因:

    • 新版本中 http.CloseNotifier 已經被廢棄;
    • 如果請求協議為 HTTP/2.x,原始代碼使用 http.CloseNotifier 的情況下,每一個 Watch 將多花費 1 個 goruntine。在大規模場景下,過多的 goruntine 對 API Server 是一個非常大的負擔和性能瓶頸。

    在 Windows 上使用 Containerd 時,將由 kubelet 管理“ C: Windows  System32  drivers  etc  hosts”文件。

    為了減少 service controller 在節點有更新時,更新 backend 的延遲。

    當提供客戶端證書證書文件后,始終保持從磁盤重新啟動證書文件以進行新連接,並在證書更改時關閉連接。

    Knative

    當前 Kubernetes 社區(Kubebuilder 和 Metacontroller)正在研究控制平面可伸縮性,認為雖然用於 Kubernetes 工作的”無服務器控制器”是一個思想實驗,但距離我們並不遠,並且在技術上也是可行的。

    開源項目推薦

    阿里雲容器服務團隊自研 CNI 網絡插件,支持 VPC 和 ENI 等。

    Vmware 開源基於 OVS 的 Kubernetes 網絡方案。

    KubeSphere 是在 Kubernetes 之上構建的以應用為中心的多租戶容器管理平台,目前已經達到 GA 狀態。

    具有硬件資源感知工作負載放置策略的 Kubernetes Container Runtime Interface 代理服務。

    本周閱讀推薦

    CRDs/controllers 是 Kubernetes 中重要的組件,它們會將集群內的各種資源調整到期望狀態。學習 Reconciling 可以幫助我們更好的理解 CRDs/controllers 是如何工作的。

    通過漫畫的形式對 Openshift 及相關產品加以介紹,比較有趣。

    隨着時間的推移,Docker 開始根植於我們的日常生活當中。然而,Docker 一切輝煌的背後,技術社區中開始有不少人認為 Docker 正一路朝着沉沒的方向前進。那麼,這樣的判斷有沒有依據?Docker 真的快要不行了嗎?或者說,這隻是技術領域當中部分小年輕們一廂情願的偏執?

    由於目前國內並沒有比較好的 Go 語言書籍,而國外的優秀書籍因為英文的緣故在一定程度上也為不少 Go 語言愛好者帶來了一些學習上的困擾。

    為了加快擴散 Go 愛好者的國內群體,譯者在完成 《The Way to Go》 一書的閱讀後,決定每天抽出一點時間來進行翻譯工作,並以開源的形式免費分享給有需要的 Go 語言愛好者。

    在 Istio 服務網格中,每個 Envoy 佔用的內存也許並不算多,但所有 sidecar 增加的內存累積起來則是一個不小的数字。在進行商用部署時,我們需要考慮如何優化並減少服務網格帶來的額外內存消耗。

    Buoyant 創始人、Service Mesh 技術的提出者、第一個 Service Mesh Linkerd 的作者 Willian Morgan 為您解析 Service Mesh 現狀。

    “ 阿里巴巴雲原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術公眾號。”

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

    【其他文章推薦】

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • Tsx寫一個通用的button組件

    Tsx寫一個通用的button組件

    一年又要到年底了,vue3.0都已經出來了,我們也不能一直還停留在過去的js中,是時候學習並且在項目中使用一下Ts了。

      如果說jsx是基於js的話,那麼tsx就是基於typescript的

      廢話也不多說,讓我們開始寫一個Tsx形式的button組件,

      ts真的不僅僅只有我們常常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的

      項目是基於vue-cli 3.0 下開發的,可以自己配置Ts,不會的話那你真的太難了

      

     

         我們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊

      我們再button文件夾下創建 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺少的

           

     

       分析一下button需要的一些東西

      第一個當然是props,還有一個是點擊事件,所以我們第一步就定義一下這兩個類型

    type ButtonProps = {
      tag: string,
      size: ButtonSize,
      type: ButtonType,
      text: String
    }
    
    type ButtonEvents = {
      onClick?(event: Event) :void
    }
    type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
    type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

      因為button是很簡單的組件,內部也沒有一些特別的狀態需要改變,所以我們用函數式組件的方式去寫(之後的render會用到這個方法)

    function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
      const { tag, size, type } = props
      let text
      console.log(slots)
      text = slots.default ? slots.default() : props.text
      function onClick (event: Event) {
        emit(ctx, 'click', event)
      }
      let classes = [size,type]
      return (
        <tag
          onClick = {onClick}
          class = {classes}
        >
          {text}
        </tag>
      )
    }

      h 是一個預留參數,這裏並沒有用到 ,CreateElement  這個是vue從2.5之後提供的一個類型,也是為了方便在vue項目上使用ts

      props 就是button組件的傳入的屬性,slots插槽,ctx,代表的是當前的組件,可以理解為當前rendercontext執行環境this

      DefaultSlots是我們自定義的一個插槽類型

    export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;
    
    export type ScopedSlots = {
      [key: string]: ScopedSlot | undefined;
    }

      插槽的內容我們都是需要從ctx中讀取的,默認插槽的key就是defalut,具名插槽就是具體的name

      button放發內部還有一個具體的點擊事件,還有一個emit方法,從名字我們也可以看的出,他是粗發自定義事件的,我們這裏當然不能使用this.emit去促發,

      所以我們需要單獨這個emit方法,我們知道組件內所以的自定義事件都是保存在listeners里的,我們從ctx中拿取到所以的listeners

    
    
    
    

      import { RenderContext, VNodeData } from ‘vue’ // 從vue中引入一些類型


    function
    emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

      這樣我們組件內部的事件觸發就完成了

      我們的button肯定是有一些默認的屬性,所以,我們給button加上默認的屬性

    Button.props = {
      text: String,
      tag: {
        type: String,
        default: 'button'
      },
      type: {
        type: String,
        default: 'default'
      },
      size: {
        type: String,
        default: 'normal'
      }
    }

      我們定義一個通用的functioncomponent 類型

    type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
      (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
      props?: PropsDefs
    }

      PropsDefinition<T>  這個是vue內部提供的,對 props的約束定義

      不管怎麼樣我們最終返回的肯定是一個對象,我們把這個類型也定義一下

      ComponentOptions<Vue> 這個也是vue內部提供的

     interface DrmsComponentOptions extends ComponentOptions<Vue> {
      functional?: boolean;
      install?: (Vue: VueConstructor) => void;
    }

      最終生成一個組件對象

    function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
      return {
        functional: true, // 函數時組件,這個屬性一定要是ture,要不render方法,第二個context永遠為underfine
        props: fn.props,
        model: fn.model,
        render: (h, context): any => fn(h, context.props, unifySlots(context), context)
      }
    }

      unifySlots 是讀取插槽的內容

    // 處理插槽的內容
    export function unifySlots (context: RenderContext) {
      // use data.scopedSlots in lower Vue version
      const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
      const slots = context.slots()
    
      Object.keys(slots).forEach(key => {
        if (!scopedSlots[key]) {
          scopedSlots[key] = () => slots[key]
        }
      })
    
      return scopedSlots
    }

      當然身為一個組件,我們肯定是要提供全局注入接口,並且能夠按需導入

      所以我們給組件加上名稱和install方法,install 是 vue.use() 方法使用的,這樣我們能全部註冊組件

    export function CreateComponent (name:string) {
      return function <Props = DefaultProps, Events = {}, Slots = {}> (
        sfc:DrmsComponentOptions | FunctionComponent) {
        if (typeof sfc === 'function') {
          sfc = transformFunctionComponent(sfc)
        }
        sfc.functional = true
        sfc.name = 'drms-' + name
        sfc.install = install
        return sfc 
      }
    }

      index.tsx 中的最後一步,導出這個組件

    export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

      還少一個install的具體實現方法,加上install方法,就能全局的按需導入了

    function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
      const { name } = this
      Vue.component(name as string, this)
    }

     

       最終實現的效果圖,事件的話也是完全ok的,這個我也是測過的

     

       代碼參考的是vant的源碼:

      該代碼已經傳到git:     dev分支應該是代碼全的,master可能有些並沒有合併

     

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

    【其他文章推薦】

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

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

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

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

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