不到7萬買馬自達/三菱/日產?這些緊湊型車叫板合資_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

但海馬本身對於馬自達的質控學習不過關,在質控上明顯要弱於吉利奇瑞長安這些一線國產廠商。2東南菱悅作為三菱在中國的合作夥伴,東南當然有着一部分三菱的技術儲備。這款V3菱悅就是最好的例子,其的前身實際是大名鼎鼎的Lance EVO,也就是當時叱吒拉力賽的三菱神車,也是現在三菱藍瑟翼神的前身。

緊湊型轎車

隨着我們時代的變遷以及我國汽車產業的發展,汽車的價格不斷下降,車型也越發多了起來。對於我們來說,不僅價格便宜了,還有着更多的選擇。則一些老牌的品牌車型為了提高競爭力甚至不得不降價,這就成為了能用不到7萬的價錢買到以前十萬甚至十萬以上車型的體驗。

海馬的前生其實是海南馬自達,和着馬自達合作的海南馬自達在那時是引進了馬自達323,也就是現在馬自達3昂克賽拉的前身。在脫離馬自達“單幹”以後,海馬繼續生產323.並在這些年吸收其技術,所以研發了這款海馬福美來,並且對於過往的323有着更大的車身和空間表現,在動力方面有着自主研發的1.6L以及1.5T發動機可供選擇,搭配的是6MT或者6AT變速箱,

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

更為優秀的是延續了馬自達在底盤上的造詣,底盤表現出色。但海馬本身對於馬自達的質控學習不過關,在質控上明顯要弱於吉利奇瑞長安這些一線國產廠商。

作為三菱在中國的合作夥伴,東南當然有着一部分三菱的技術儲備。這款V3菱悅就是最好的例子,其的前身實際是大名鼎鼎的Lance EVO,也就是當時叱吒拉力賽的三菱神車,也是現在三菱藍瑟翼神的前身。所以在底盤方面表現非常出色,有着同級別中非常少見的后多連桿式懸架,加上三菱在底盤方面的浸淫,東南菱悅的底盤表現在同價位中可謂是出類拔萃的。但是發動機方面,東南和三菱一樣原地踏步,僅僅只有一個“年長”的1.6L自然發動機勉強擺上台,與對手相比僅僅只有一個保養維修便宜的優點。

對於日產騏達以及頤達相信大家都不會陌生,它們有着保養便宜、油耗低以及同級別中幾近最大的空間的特點,所以在當時非常的暢銷。而啟辰D50就是上一代的日產頤達重新設計外觀而成,有着頤達所有的優點,並且比起當初售價十萬以上的頤達要便宜近一半價錢,搭載的動力總成也繼續延續頤達的動力總成,只是4AT變速箱有些年頭,對油耗不是很友好。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

這款15萬級的國產SUV氣場竟然完爆路虎?_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

車身側面和尾部的設計風格則可以看出相當高的原創度,沒有採用溜背式的“極光”造型,而是用了較為方正的懸浮式車頂設計,這樣會使得車身側面顯得更加剛毅,整車的力量感與肌肉感也顯現出來。內飾層面同樣也是向成熟車型借鑒的意圖比較明顯,黑紅色的雙拼搭配是營造一台車內飾運動氛圍屢試不爽的設計風格,並且採用了一定量的鍍鉻裝飾進行點綴,中控造型十分前衛也比較符合當下設計潮流。

路虎,SUV車型中絕對的明星品牌,如果我告訴你十五萬不到就可以買到“路虎”,你還是會想到陸風X7?還是想到眾泰又準備複製哪一款成熟車型?不,今天的主角是一款可以比肩路虎的SUV——來自廣汽吉奧的GX6。

廣汽吉奧GX6

指導價格:10.98-14.68萬

我們可以看到吉奧GX6的外形讓人感到十分熟悉,很多路虎極光的設計元素在其身上,但是不得不說也融入了當下非常流行的運動化設計,讓GX6看上去十分年輕時尚。車身尺寸為4640*1815*1800,而且軸距達到了2745,而路虎攬勝極光的尺寸為4370*1900*1635,軸距為2660,尺寸上吉奧GX6不可謂不大。

車身側面和尾部的設計風格則可以看出相當高的原創度,

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

沒有採用溜背式的“極光”造型,而是用了較為方正的懸浮式車頂設計,這樣會使得車身側面顯得更加剛毅,整車的力量感與肌肉感也顯現出來。

內飾層面同樣也是向成熟車型借鑒的意圖比較明顯,黑紅色的雙拼搭配是營造一台車內飾運動氛圍屢試不爽的設計風格,並且採用了一定量的鍍鉻裝飾進行點綴,中控造型十分前衛也比較符合當下設計潮流。而且做工的細緻程度也是比較精細,不會有過於明顯的粗糙感。只是用料偏廉價,硬質塑料比較多。

動力總成方面廣汽吉奧GX6所使用的是來自三菱的4G69S4N汽油發動機,排量為2.4L,並且搭載的是手動變速箱,三菱的自然吸氣發動機可靠性毋庸置疑,但是全系沒有自動變速箱的配置着實有點對不起觀眾;而且這款發動機技術比較過時,以目前的汽車發展來說,吉奧GX6的油耗並不低。

編輯總結:採用借鑒的方式進行汽車的設計和生產是國內很多車企都在進行或者曾經做過的方式,好壞與否暫且不論,但這也說明這種外觀的設計是比較討好消費者的設計。

GX6的整車配置不低,像是目前自主品牌SUV車型普遍搭載的中控大屏、倒車影像、一鍵啟動、無鑰匙進入等等配置較為齊全,比較可惜的是這款車目前搭載的是全手動變速箱,據悉裝配了自動變速箱的新款吉奧GX6會在今年年底上市,屆時或許可以對於它的新車做一下期待。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

Microsoft 與 Parallels 合作,要將 Windows 10 帶到 M1 版 Mac 上_網頁設計

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

Apple 在上個月推出了以 ARM 為基礎的 M1 晶片,現在應該全球的開發者都在日以繼夜地努力將自家的應用程導入這個新平台上,對於雙系統用戶來說,還是最關心 Windows 10 到底什麼時候可以支援 M1 晶片。日前 Microsoft 與 Parallels 開展合作,盡量讓大家不用久等就能享用。

Microsoft 與 Parallels 合作,要將 Windows 10 帶到 M1 版 Mac 上

對雙系統用戶來說,Parallels 絕對不陌生,該公司已經開始開發自己的軟體,要將 Windows 10 帶到 Apple Silicon 的世界,在不久前該公司才剛發布了第一個測試預覽版本,讓用戶能夠盡早試用該產品。事實證明,Parallels 並不是唯一一個在這方面使力的人,雖然不清楚兩家公司的合作方式為何,但 Microsoft 旗下 OneDrive 副總裁 Omar Shahine 在 Twitter 上面讚揚了此一合作。

Dang this is amazing! Windows 10 ARM running on MacBook Air M1. Great performance and battery life! Thanks @ParallelsMac and @Microsoft for releasing the ARM build of Windows! pic.twitter.com/BR8vZhJV7V

— Omar Shahine (@OmarShahine) December 23, 2020

有趣的是,在不久前,Apple 的高層主管曾表示將 Windows 引進 Apple Silicon 是僅有 Microsoft 自己能夠做決定,在彼時,Apple 軟體工程高級副總裁 Craig Federighi 曾表示 Apple 擁有 Microsoft 能做到這點的核心技術,可以直接運行 ARM 版本的 Windows,但又能反過來支援 X86 的應用程式,但這必須由 Microsoft 做出決定,將該技術授權給用戶在 M1 版 Mac 設備上運行。

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

於此同時,Apple Silicon 世界正在持續壯大,不久前也有傳聞 Microsoft 也正在為新晶片的軟體最佳化投入大量資金,Office、Teams 與其他 Microsoft 應用程式正陸陸續續開放支援 M1 晶片,以求一切都能在新的 Apple 設備上盡可能地平穩運作。

◎資料來源:SoftPedia

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

小米推出小米便攜鼠標 2,內建雙模式可操作兩台電腦、4 段可調 DPI、一顆 5 號電池可用一年_貨運

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

小米便攜鼠標第一代推出至今也有超過二年的時間,依據其他產品的經驗,這款可說更新的相當慢,終於在稍早小米官方正式宣布二代「小米便攜鼠標 2」登場,除了延續上一代的雙模式特色外,還加入了 4 段可調 DPI 與更強悍的待機續航力,更重要是,價格還只需要 79 人民幣(約台幣 340 元),真的超值阿。

小米推出小米便攜鼠標 2

這次小米便攜鼠標 2 跟很多小米產品一樣,採用眾籌的方式開賣,眾籌價為 79 人民幣,預計 12/30 早上 10 開售,一直到 1/6 早上 10 點結束,隨後價格也會調整回零售的 99 人民幣(約台幣 425 元)。

就外型來看,小米便攜鼠標 2 跟上一代沒有太大差異,不過側面線條更佳時尚,也沒那麼厚,我覺得變得更好看,表面也採用細膩噴砂金屬處理,擁有耐磨耐腐蝕特性。另外雙模式(藍牙4.2 + USB 接收器)的按鍵也移到正面,使用上更容易:

搭載消耗功率低 Telink 藍牙晶片,並內建自動休眠技術,來降低待機功耗,實現更好的省電節能。根據官方測試數據,裝一顆 5 號電池,就能正常使用一整年的時間,用到後面你搞不好會以為它不需要裝電池。電量低於 10% 時,正面上方的 LED 紅燈也會亮起,來提醒使用者:

上一代 DPI 為固定的 1200,小米便攜鼠標 2 全新加入 4 段靈敏度調整,分別為 1200、1800、2400 與 4000,滿足使用者各種需求。DPI 功能鍵位於底部,切換時也會透過 LED 燈來提示你目前是什麼 DPI:

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

體積一樣相當輕巧,含電池僅 86 公克,攜帶上非常容易:

有白銀與黑灰兩種顏色。至於會不會進台灣,我是覺得有點難,畢竟第一代都沒有了。無論如何,有興趣入手的人還是可以透過集運之類方式,眾籌部分預計 1/13 起開始出貨:

多款小米筆電 Pro 現身 Geekbench,單核效能大幅提升

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

容器技術之Dockerfile(一)_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

  在前邊的隨筆中我們聊到了docker的基本命令,鏡像,網絡,存儲卷以及基於現有容器製做docker鏡像,相關隨筆可參考https://www.cnblogs.com/qiuhom-1874/category/1766327.html;今天我們來聊一聊docker的另一個製作鏡像的方式dockerfile;

  什麼是dockerfile?所謂dockerfile就是用來描述docker鏡像製作過程的一指令文件;該文件是一個純文本文件,docker Daemon 進程可以從該文件中讀取指令,從而自動生成鏡像;客戶端可以使用docker bulid命令來指定dockerfile 所在目錄,來生成以dockerfile里描述的鏡像構建過程的鏡像;

  首先我們來了解下dockerfile的格式,dockerfile是一個純文本文件,我們可以理解為構建鏡像的源碼;不同於其他編程語言,dockerfile里沒有if else 沒有循環,它裏面僅僅有註釋和構建鏡像的指令;對於dockerfile註釋就是以井號開頭的行為註釋,這個和shell和其他配置文件的語法一樣;除此之外dockerfile里就只有指令了;嚴格的講指令是不區分字符大小寫的,通常我們約定俗成指令都是純大寫;除此之外在dockerfile的第一非註釋行必須是FROM開頭來明確的說明我們在基於那個鏡像為基礎鏡像做鏡像;

  通常情況下dockerfile是放在一個目錄下,該目錄就是dockerfile的工作目錄,我們在製作鏡像所依賴的文件都必須放在該目錄下,而dockerfile的名稱也必須是Dockerfile(首字母大寫,名稱必須是Dockerfile),如果我們依賴的文件是一個目錄下的部分文件,我們也可在dockerfile所在目錄創建一個.dockerignore文件,把我們要忽略的文件名稱寫到裏面即可,該文件支持通配符;

  了解了上面的dockerfile的基本環境結構,接下來我們來說說dockerfile的指令的用法;

  1、FROM:用於指定所創建鏡像的基礎鏡像,如果本地不存在,默認會從dockerhub中下載;該指令是dockerfile里的第一個非註釋行的指令;語法格式 FROM <repository>[:<tag>] 或者FROM <repository>@<digest>  其中repository是指定的base 鏡像的名稱 <tag>是base 鏡像的標籤為可選項,不指定標籤默認表示使用指定鏡像的latest版本;

  2、MAINTAINER:用於指定維護者信息,dockerfile並不限制MAINTAINER指令可出現的位置,通常我們建議將該指令放在FROM之後;語法格式 MAINTAINER <authtor’s detail>, <author’s detail>可是任何文本信息,但約定俗成地使用作者名稱及郵件地址;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"


[root@node1 test]# 

  提示:通常我們都不用MAINTAINER來指定作者信息,該指令幾乎是廢棄狀態,建議使用LEBEL指令來指定;

  3、LABEL:該指令用於指定添加鏡像的元數據信息;語法格式為LABEL <key>=<value> <key>=<value> <key>=<value> …;如果添加的元數據信息值中有空格,我們需要使用引號和反斜線進行命令行解析;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."


[root@node1 test]# 

  提示:LABEL指令可以使用多次,用於添加更多的元數據信息;

  4、COPY:該指令用於從Docker主機複製文件至創建的鏡像文件中;語法格式 COPY <src> … <dest> 或 COPY [“<src>”,… “<dest>”];<src>表示要複製的源文件或目錄,支持使用通配符;<dest>表示目標路徑,即正在創建的image的文件系統路徑;建議為<dest>使用絕對路徑,否則,COPY指定則以WORKDIR為其起始路徑;注意:在路徑中有空白字符時,通常使用第二種格式;

  文件複製準則

  1、<src>必須是Dockerfile所在目錄中的文件,不能是其父目錄中的文件;

  2、如果<src>是目錄,則其內部文件或子目錄會被遞歸複製,但<src>目錄自身不會被複制;

  3、如果指定了多個<src>,或在<src>中使用了通配符,則<dest>必須是一個目錄,且必須以“/”結尾;

  4、如果<dest>事先不存在,它會被自動創建,則包括其父目錄路徑;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

COPY html /var/www/html/

COPY test.html /tmp/
[root@node1 test]# tree
.
├── Dockerfile
├── html
│   ├── test1.html
│   └── test2.html
└── test.html

1 directory, 4 files
[root@node1 test]# 

  測試:我們基於上面的Dockerfile構建鏡像,看看構建好的鏡像是否把對應文件都複製到對應目錄下了?

[root@node1 test]# ls
Dockerfile  html  test.html
[root@node1 test]# pwd
/root/test
[root@node1 test]# docker build . -t myimg:v0.1
Sending build context to Docker daemon  5.632kB
Step 1/6 : FROM busybox:latest
latest: Pulling from library/busybox
d9cbbca60e5f: Pull complete 
Digest: sha256:836945da1f3afe2cfff376d379852bbb82e0237cb2925d53a13f53d6e8a8c48c
Status: Downloaded newer image for busybox:latest
 ---> 78096d0a5478
Step 2/6 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Running in c311afd3d522
Removing intermediate container c311afd3d522
 ---> bf9b966a914b
Step 3/6 : LABEL version="1.0"
 ---> Running in 2ffb577afa08
Removing intermediate container 2ffb577afa08
 ---> 5dd2e4b3f594
Step 4/6 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Running in 489165bad668
Removing intermediate container 489165bad668
 ---> 994f06ff65f8
Step 5/6 : COPY html /var/www/html/
 ---> f2b46094d9a9
Step 6/6 : COPY test.html /tmp/
 ---> c78c7188f804
Successfully built c78c7188f804
Successfully tagged myimg:v0.1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.1                c78c7188f804        8 seconds ago       1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# 

  提示:可以看到docker在讀dockerfile時會把一條指令啟動一容器來執行,然後每一條指令都會構建成一層鏡像;如果我們本地倉庫中沒有我們指定的基礎鏡像,它默認會從dockerhub倉庫中去把指定鏡像拖到本地;docker build命令是用來基於dockerfile來製作鏡像的命令,其中-t表示指定我們生成的鏡像的標籤信息;

  運行我們剛才製作好的鏡像,看看我們的指定的目錄和文件是否都複製到指定鏡像中的目錄中去了?

[root@node1 test]# docker run --name test --rm -it myimg:v0.1 /bin/sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # ls /var/www/html/
test1.html  test2.html
/ # ls /tmp/
test.html
/ # cat /var/www/html/test1.html 
this is test1 html
/ # cat /var/www/html/test2.html 
this is test2 html
/ # cat /tmp/test.html 
this is test dir html
/ # exit
[root@node1 test]# 

  提示:可以看到我們以剛才製作的鏡像運行成容器,容器對應目錄有我們指定複製的文件;
  5、ADD:該指令類似於COPY指令,ADD支持使用TAR文件和URL路徑;語法格式 ADD <src> … <dest> 或 ADD [“<src>”,… “<dest>”];如果<src>為URL且<dest>不以/結尾,則<src>指定的文件將被下載並直接被創建為<dest>;如果<dest>以/結尾,則URL指定的文件將被直接下載並保存為<dest>/<filename>;如果<src>是一個本地系統上的壓縮格式的tar文件,它將被展開為一個目錄,其行為類似於“tar -x”命令;然而,通過URL獲取到的tar文件將不會自動展開;如果<src>有多個,或其間接或直接使用了通配符,則<dest>必須是一個以/結尾的目錄路徑;如果<dest>不以/結尾,則其被視作一個普通文件,<src>的內容將被直接寫入到<dest>;

  示例:

[root@node1 test]# ls
Dockerfile  html  nginx-1.19.0.tar.gz  test.html
[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

COPY html /var/www/html/

COPY test.html /tmp/

ADD http://nginx.org/download/nginx-1.18.0.tar.gz /usr/src/

ADD nginx-1.19.0.tar.gz /tmp/
[root@node1 test]# 

  提示:以上Dokcerfile里指定把當前目錄下的nginx-1.19.0.tar.gz 添加到鏡像中的/tmp/下,並且會把nginx-1.19.0.tar.gz展開成一個目錄;而對於

ADD http://nginx.org/download/nginx-1.18.0.tar.gz /usr/src/這條指令它會把指定url的文件下載到/usr/src/,但是不會tar文件展開;

  測試:把上面dockerfile編譯成鏡像,看看對應目錄中的文件是否都展開了?

[root@node1 test]# docker build . -t myimg:v0.2
Sending build context to Docker daemon   1.05MB
Step 1/8 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/8 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/8 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/8 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/8 : COPY html /var/www/html/
 ---> Using cache
 ---> f2b46094d9a9
Step 6/8 : COPY test.html /tmp/
 ---> Using cache
 ---> c78c7188f804
Step 7/8 : ADD http://nginx.org/download/nginx-1.18.0.tar.gz /usr/src/
Downloading   1.04MB/1.04MB
 ---> 23f47a028853
Step 8/8 : ADD nginx-1.19.0.tar.gz /tmp/
 ---> 6ba2693b3084
Successfully built 6ba2693b3084
Successfully tagged myimg:v0.2
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.2                6ba2693b3084        27 seconds ago      8.54MB
myimg               v0.1                c78c7188f804        25 minutes ago      1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v0.2 /bin/sh
/ # ls /usr/src/
nginx-1.18.0.tar.gz
/ # ls /tmp
nginx-1.19.0  test.html
/ # exit
[root@node1 test]# 

  提示:從上面的信息可以看到,在容器中的/usr/src/目錄下nginx-1.18.0.tar.gz並沒有展開,而在/tmp/下卻把niginx-1.19.0.tar.gz展開為nginx-1.19.0;從build的過程來看,除開後面的ADD過程,其他過程都很快,並且明確告訴我們使用了cache,則意味着在本地倉庫中有的鏡像層,默認是共享的;

  6、WORKDIR:該指令用於設定Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指令的工作目錄;一個Dockerfile中可以多次使用WORKDIR來指定當前WORKDIR指令到下一個WORKDIR指令之間的指令的工作目錄;語法格式WORKDIR <dirpath>;在Dockerfile文件中,WORKDIR指令可出現多次,其路徑也可以為相對路徑,不過,其是相對此前一個WORKDIR指令指定的路徑,另外,WORKDIR也可調用由ENV指定定義的變量;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

WORKDIR /var/www/

COPY html html/

COPY test.html /tmp/

WORKDIR /usr/

ADD http://nginx.org/download/nginx-1.18.0.tar.gz src/

ADD nginx-1.19.0.tar.gz /tmp/
[root@node1 test]# 

  提示:我們在dockerfile中指定了workdir的路徑后,其後的指令就可以以上一個workdir指定的路徑 為基準後面使用相對路徑;

  測試:製作鏡像運行容器,看看是否把對應文件都添加到容器的指定目錄里?

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

WORKDIR /var/www/

COPY html html/

COPY test.html /tmp/

WORKDIR /usr/

ADD http://nginx.org/download/nginx-1.18.0.tar.gz src/

ADD nginx-1.19.0.tar.gz /tmp/
[root@node1 test]# 
[root@node1 test]# docker build . -t myimg:v0.3
Sending build context to Docker daemon   1.05MB
Step 1/10 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/10 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/10 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/10 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/10 : WORKDIR /var/www/
 ---> Running in 623002a4c418
Removing intermediate container 623002a4c418
 ---> 7e6898b36dd5
Step 6/10 : COPY html html/
 ---> 57ff5a48a104
Step 7/10 : COPY test.html /tmp/
 ---> f66a1bf4040a
Step 8/10 : WORKDIR /usr/
 ---> Running in b905ff1b4529
Removing intermediate container b905ff1b4529
 ---> 1fdceb9a6bfd
Step 9/10 : ADD http://nginx.org/download/nginx-1.18.0.tar.gz src/
Downloading   1.04MB/1.04MB
 ---> 92868df29f5b
Step 10/10 : ADD nginx-1.19.0.tar.gz /tmp/
 ---> 68161c3beb90
Successfully built 68161c3beb90
Successfully tagged myimg:v0.3
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.3                68161c3beb90        11 seconds ago      8.54MB
myimg               v0.2                6ba2693b3084        18 minutes ago      8.54MB
myimg               v0.1                c78c7188f804        43 minutes ago      1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v0.3 /bin/sh
/usr # pwd
/usr
/usr # ls 
sbin  src
/usr # ls src/
nginx-1.18.0.tar.gz
/usr # ls /tmp/
nginx-1.19.0  test.html
/usr # ls /var/www/
html
/usr # exit
[root@node1 test]#

  提示:可以看到我們製作的鏡像啟動為容器后,默認是我們指定的最後一個workdir的路徑;

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

  示例:使用環境變量指定workdi的路徑;

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ENV webhome="/var/www/" src_home="/usr/"


WORKDIR $webhome

COPY html html/

COPY test.html /tmp/

WORKDIR $src_home

ADD http://nginx.org/download/nginx-1.18.0.tar.gz src/

ADD nginx-1.19.0.tar.gz /tmp/
[root@node1 test]#

  提示:我們在原有的Dockerfile中添加了ENV webhome=”/var/www/” src_home=”/usr/” 表示添加兩個環境變量,而這兩個環境的變量的值分別是“/var/www/” 和“/usr” 這樣一來,在後面workdir引用該環境變量時 就會把對應的值給替換過去;dockerfile中變量引用同shell中的變量引用一樣都是使用$符合引用變量;

  測試:利用上面的Dockerfile編譯成鏡像,然後運行成容器,看看我們在dockerfile中定義的環境變量是否能夠被容器所引用?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.3                68161c3beb90        34 minutes ago      8.54MB
myimg               v0.2                6ba2693b3084        53 minutes ago      8.54MB
myimg               v0.1                c78c7188f804        About an hour ago   1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker build . -t myimg:v0.4
Sending build context to Docker daemon   1.05MB
Step 1/11 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/11 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/11 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/11 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/11 : ENV webhome="/var/www/" src_home="/usr/"
 ---> Running in 82738c50a595
Removing intermediate container 82738c50a595
 ---> 9f089c14778f
Step 6/11 : WORKDIR $webhome
 ---> Running in 3e9d7d4276bf
Removing intermediate container 3e9d7d4276bf
 ---> 94e17268d7ea
Step 7/11 : COPY html html/
 ---> cf3be18998db
Step 8/11 : COPY test.html /tmp/
 ---> 3b81cb058412
Step 9/11 : WORKDIR $src_home
 ---> Running in 70478cb9d405
Removing intermediate container 70478cb9d405
 ---> e0d8ab9331f4
Step 10/11 : ADD http://nginx.org/download/nginx-1.18.0.tar.gz src/
Downloading   1.04MB/1.04MB
 ---> b4e546989783
Step 11/11 : ADD nginx-1.19.0.tar.gz /tmp/
 ---> df3d040b5766
Successfully built df3d040b5766
Successfully tagged myimg:v0.4
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.4                df3d040b5766        4 minutes ago       8.54MB
myimg               v0.3                68161c3beb90        41 minutes ago      8.54MB
myimg               v0.2                6ba2693b3084        59 minutes ago      8.54MB
myimg               v0.1                c78c7188f804        About an hour ago   1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v0.4 /bin/sh
/usr # pwd
/usr
/usr # ls /var/www/
html
/usr # ls /usr/src/
nginx-1.18.0.tar.gz
/usr # 

  提示:可以看到我們指定的ENV環境變量是能夠被WORKDIR說引用

  7、ENV:用於為鏡像定義所需的環境變量,並可被Dockerfile文件中位於其後的其它指令(如ENV、ADD、COPY等)所調用;調用格式為$variable_name或${variable_name};語法格式ENV <key> <value> 或 ENV <key>=<value> …;第一種格式中,<key>之後的所有內容均會被視作其<value>的組成部分,因此,一次只能設置一個變量;第二種格式可用一次設置多個變量,每個變量為一個”<key>=<value>”的鍵值對,如果<value>中包含空格,可以以反斜線(\)進行轉義,也可通過對<value>加引號進行標識;另外,反斜線也可用於續行;定義多個變量時,建議使用第二種方式,以便在同一層中完成所有功能;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ENV web_home="/var/www/html/"

COPY html ${web_home}


[root@node1 test]# 

  提示:上面Dockerfile設置了環境變量web_home=”/var/www/html” ,後面的COPY引用時直接使用${web_home}即可;COPY html ${web_home}表示把html目錄中的文件複製到web_home這個變量所指定的目錄中,即/var/www/html/;

  測試:編譯成鏡像,看看是否把對應html目錄下的文件複製到/var/www/html/目錄下了?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.4                df3d040b5766        17 minutes ago      8.54MB
myimg               v0.3                68161c3beb90        54 minutes ago      8.54MB
myimg               v0.2                6ba2693b3084        About an hour ago   8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker build . -t myimg:v0.5
Sending build context to Docker daemon   1.05MB
Step 1/6 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/6 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/6 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/6 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/6 : ENV web_home="/var/www/html/"
 ---> Running in 4c136d790dd0
Removing intermediate container 4c136d790dd0
 ---> 90e193e4e810
Step 6/6 : COPY html ${web_home}
 ---> e21e9479b0a7
Successfully built e21e9479b0a7
Successfully tagged myimg:v0.5
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.5                e21e9479b0a7        4 seconds ago       1.22MB
myimg               v0.4                df3d040b5766        17 minutes ago      8.54MB
myimg               v0.3                68161c3beb90        54 minutes ago      8.54MB
myimg               v0.2                6ba2693b3084        About an hour ago   8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v0.5 /bin/sh
/ # ls /var/www/html/
test1.html  test2.html
/ # exit
[root@node1 test]# 

  提示:可以看到我們打包的鏡像運行成容器后,ENV指定的環境變量目錄里有html目錄下的所有文件;

  8、ARG:該指令用於編譯階段可以使用docker build –build-arg向dockerfile里ARG 指定的變量傳遞值;語法格式ARG <name>[=<default value>];該指令可以在dockerfile中使用多次,也可以給定默認變量一個默認的值,在用戶沒有向該變量傳遞值的情況;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home="/var/www/html/"

COPY html ${web_home}


[root@node1 test]# 

  提示:以上Dockerfile 指定了web_home變量的默認值是“/var/www/html/” 如果在我們build階段沒有使用–build-arg來向web_home傳遞值時,它默認就是“/var/www/html/”,如果我們使用了 –build-agr 指定web_home的值后,後面引用web_home變量的值就是我們用–build-arg傳遞給它的值;

  測試:使用–build-arg向web_home傳遞值

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.5                e21e9479b0a7        14 minutes ago      1.22MB
myimg               v0.4                df3d040b5766        31 minutes ago      8.54MB
myimg               v0.3                68161c3beb90        About an hour ago   8.54MB
myimg               v0.2                6ba2693b3084        About an hour ago   8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker build . --build-arg web_home="/usr/share/www/html/" -t myimg:v0.6
Sending build context to Docker daemon   1.05MB
Step 1/6 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/6 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/6 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/6 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/6 : ARG web_home="/var/www/html/"
 ---> Running in d8697abf9206
Removing intermediate container d8697abf9206
 ---> 6abb65dab341
Step 6/6 : COPY html ${web_home}
 ---> 385cba27c288
Successfully built 385cba27c288
Successfully tagged myimg:v0.6
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.6                385cba27c288        6 seconds ago       1.22MB
myimg               v0.5                e21e9479b0a7        15 minutes ago      1.22MB
myimg               v0.4                df3d040b5766        32 minutes ago      8.54MB
myimg               v0.3                68161c3beb90        About an hour ago   8.54MB
myimg               v0.2                6ba2693b3084        About an hour ago   8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v0.6 /bin/sh
/ # ls /usr/share/www/html/
test1.html  test2.html
/ # exit
[root@node1 test]# 

  提示:可以看到我們傳遞進去的路徑下有html目錄下的兩個網頁文件;這說明通過–build-arg 選項可以向Dockerfile里ARG指定指定的變量傳遞值的;

  示例:引用變量給定變量默認值

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}


[root@node1 test]# 

  提示:引用變量給定變量默認值的方式同shell中的用法一樣;${web_home:-“/data/htdoc/”} 表示如果web_home這個變量的值未設定或者為空時,就使用默認的值“/data/htdoc/”這個值,如果設置了,那麼設置的是什麼值就是什麼值;

  測試:不使用–build-arg向web_home傳遞值,看看web_home是否會拿到默認值?

[root@node1 test]# docker build . -t myimg:v0.7
Sending build context to Docker daemon   1.05MB
Step 1/6 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/6 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/6 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/6 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/6 : ARG web_home
 ---> Running in 9d98f8e3a1f0
Removing intermediate container 9d98f8e3a1f0
 ---> f164cc3e24ad
Step 6/6 : COPY html ${web_home:-"/data/htdoc/"}
 ---> 4bedea2590b7
Successfully built 4bedea2590b7
Successfully tagged myimg:v0.7
[root@node1 test]# docker run --name test --rm -it myimg:v0.7 /bin/sh
/ # ls /
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # ls /data/
htdoc
/ # ls /data/htdoc/
test1.html  test2.html
/ # exit
[root@node1 test]# 

  提示:可以看到不想web_home傳遞值,默認就是使用我們給設定的默認值

  測試:向web_home傳遞值,看看是否還會使用默認值呢?

[root@node1 test]# docker build . --build-arg web_home=/web/html/ -t myimg:v0.9
Sending build context to Docker daemon   1.05MB
Step 1/6 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/6 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/6 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/6 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/6 : ARG web_home
 ---> Using cache
 ---> f164cc3e24ad
Step 6/6 : COPY html ${web_home:-"/data/htdoc/"}
 ---> 4b6993911e6d
Successfully built 4b6993911e6d
Successfully tagged myimg:v0.9
[root@node1 test]# docker run --name test --rm -it myimg:v0.9 /bin/sh          
/ # ls /
bin   dev   etc   home  proc  root  sys   tmp   usr   var   web
/ # ls /web/
html
/ # ls /web/html/
test1.html  test2.html
/ # exit
[root@node1 test]# 

  提示:可以看到通過–build-arg傳遞了web_home的值為“/web/htm/”后,默認的“/data/htdoc/”的值就不會生效;

  9、VOLUME:用於在image中創建一個掛載點目錄,以掛載Docker host上的卷或其它容器上的卷;語法格式 VOLUME <mountpoint> 或 VOLUME [“<mountpoint>”];如果掛載點目錄路徑下此前的文件存在,docker run命令會在卷掛載完成后將此前的所有文件複製到新掛載的卷中;

  示例:

[root@node1 test]# cat Dockerfile
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}


[root@node1 test]# 

  提示:volume是指定鏡像里的掛載點,如果該掛載點裏面原來是有文件存在,在使用docker run 時,用-v指定把宿主機上的某個目錄掛載到該掛載點時,默認會把原來有的文件複製新掛載的卷中;這裏還需要特被說一下,這裏指定卷是在鏡像內部創建一個掛載點,運行成容器還需要我們手動的用-v去指定把那個目錄掛載到該掛載點,如果不指定默認就是docker-managed 類型的卷;

  測試:編譯成鏡像,然後運行成容器,看看我們指定的卷宗的文件會不會被覆蓋掉?是否會有文件存在?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v0.9                4b6993911e6d        21 minutes ago      1.22MB
myimg               v0.7                4bedea2590b7        23 minutes ago      1.22MB
myimg               v0.6                385cba27c288        30 minutes ago      1.22MB
myimg               v0.5                e21e9479b0a7        46 minutes ago      1.22MB
myimg               v0.4                df3d040b5766        About an hour ago   8.54MB
myimg               v0.3                68161c3beb90        2 hours ago         8.54MB
myimg               v0.2                6ba2693b3084        2 hours ago         8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker build . -t myimg:v1.0
Sending build context to Docker daemon   1.05MB
Step 1/7 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/7 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/7 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/7 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/7 : ARG web_home
 ---> Using cache
 ---> f164cc3e24ad
Step 6/7 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 4bedea2590b7
Step 7/7 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Running in 34cad9ca1f79
Removing intermediate container 34cad9ca1f79
 ---> 9554284e4bba
Successfully built 9554284e4bba
Successfully tagged myimg:v1.0
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.0                9554284e4bba        2 minutes ago       1.22MB
myimg               v0.9                4b6993911e6d        24 minutes ago      1.22MB
myimg               v0.7                4bedea2590b7        26 minutes ago      1.22MB
myimg               v0.6                385cba27c288        33 minutes ago      1.22MB
myimg               v0.5                e21e9479b0a7        49 minutes ago      1.22MB
myimg               v0.4                df3d040b5766        About an hour ago   8.54MB
myimg               v0.3                68161c3beb90        2 hours ago         8.54MB
myimg               v0.2                6ba2693b3084        2 hours ago         8.54MB
myimg               v0.1                c78c7188f804        2 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v1.0 /bin/sh
/ # ls /
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # ls /data/htdoc/
test1.html  test2.html
/ # [root@node1 test]# 
[root@node1 test]# docker container inspect test -f {{.Mounts}}
[{volume f3409b021267bd27d58c4b16fb1bc0dae77d4f92f7b5e6255f4ed26a6c27e459 /var/lib/docker/volumes/f3409b021267bd27d58c4b16fb1bc0dae77d4f92f7b5e6255f4ed26a6c27e459/_data /data/htdoc local  true }]
[root@node1 test]# ll /var/lib/docker/volumes/f3409b021267bd27d58c4b16fb1bc0dae77d4f92f7b5e6255f4ed26a6c27e459/_data
total 8
-rw-r--r-- 1 root root 19 May 31 01:51 test1.html
-rw-r--r-- 1 root root 19 May 31 01:51 test2.html
[root@node1 test]# 

  提示:可以看到運行成容器后,默認會把我們指定卷掛載成docker-managed類型的卷;同時我們也看到了對應掛載點上目錄會把原有文件複製到現volume中;

  測試:手動指定宿主機目錄掛載到掛載點,文件是否還會存在呢?

[root@node1 ~]# mkdir /work
[root@node1 ~]# docker run --name test1 --rm -it -v /work:/data/htdoc/ myimg:v1.0 /bin/sh 
/ # ls /data/htdoc/
/ # [root@node1 ~]# ls /work/
[root@node1 ~]# 

  提示:手動指定掛載關係,它就不會把原有目錄下的文件複製到現掛載目錄中了;也就是說,人為手動指定掛載關係,宿主機上的的目錄會覆蓋容器內掛載點下的文件;只有docker自身管理的掛載的卷才會把原目錄下的文件複製到現掛載目錄里;

  10、EXPOSE:用於為容器打開指定要監聽的端口以實現與外部通信(暴露端口);語法格式 EXPOSE <port>[/<protocol>] [<port>[/<protocol>] …]; <protocol>用於指定傳輸層協議,可為tcp或udp二者之一,默認為TCP協議;EXPOSE指令可一次指定多個端口;

  示例:

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp


[root@node1 test]# 

  提示:以上dockerfile中暴露了tcp的80和443端口;在dockerfile中使用EXPOSE暴露端口,在運行成容器時,如果不使用-P來暴露端口,容器里的端口還是暴露不出來;這裏只是build階段明確說明要暴露80和443,而運行成容器我們需要使用-P來把build暴露的端口暴露出來;

  測試:運行時不使用-P暴露端口,看看是否能夠把80和443暴露出來呢?

[root@node1 test]# docker build . -t myimg:v1.1
Sending build context to Docker daemon   1.05MB
Step 1/8 : FROM busybox:latest
 ---> 78096d0a5478
Step 2/8 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> bf9b966a914b
Step 3/8 : LABEL version="1.0"
 ---> Using cache
 ---> 5dd2e4b3f594
Step 4/8 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 994f06ff65f8
Step 5/8 : ARG web_home
 ---> Using cache
 ---> f164cc3e24ad
Step 6/8 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 4bedea2590b7
Step 7/8 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 9554284e4bba
Step 8/8 : EXPOSE 80/tcp 443/tcp
 ---> Running in 8d7d5b4aab94
Removing intermediate container 8d7d5b4aab94
 ---> 79c118ea9eb3
Successfully built 79c118ea9eb3
Successfully tagged myimg:v1.1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.1                79c118ea9eb3        4 seconds ago       1.22MB
myimg               v1.0                9554284e4bba        24 minutes ago      1.22MB
myimg               v0.9                4b6993911e6d        46 minutes ago      1.22MB
myimg               v0.7                4bedea2590b7        48 minutes ago      1.22MB
myimg               v0.6                385cba27c288        55 minutes ago      1.22MB
myimg               v0.5                e21e9479b0a7        About an hour ago   1.22MB
myimg               v0.4                df3d040b5766        About an hour ago   8.54MB
myimg               v0.3                68161c3beb90        2 hours ago         8.54MB
myimg               v0.2                6ba2693b3084        2 hours ago         8.54MB
myimg               v0.1                c78c7188f804        3 hours ago         1.22MB
busybox             latest              78096d0a5478        2 weeks ago         1.22MB
[root@node1 test]# docker run --name test --rm -it myimg:v1.0 /bin/sh                        
/ # [root@node1 test]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
ca4cea9a6f58        myimg:v1.0          "/bin/sh"           15 seconds ago      Up 15 seconds                           test
[root@node1 test]# docker container port test
[root@node1 test]# 

  提示:可以看到運行時,我們不使用-P來暴露端口是把容器內部端口暴露不出來的;

  示例:使用-P來暴露dockerfile里定義的端口

[root@node1 test]# docker run --name test --rm -it -P myimg:v1.1 /bin/sh
/ # [root@node1 test]# docker container port test
443/tcp -> 0.0.0.0:32768
80/tcp -> 0.0.0.0:32769
[root@node1 test]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                           NAMES
a78f6cebc0de        myimg:v1.1          "/bin/sh"           19 seconds ago      Up 19 seconds       0.0.0.0:32769->80/tcp, 0.0.0.0:32768->443/tcp   test
[root@node1 test]# 

  提示:可以看到使用-P就可以在運行時把build階段定義的暴露端口全部給暴露出來;

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

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

一個工業級、跨平台、輕量級的 tcp 網絡服務框架:gevent_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

作為公司的公共產品,經常有這樣的需求:就是新建一個本地服務,產品線作為客戶端通過 tcp 接入本地服務,來獲取想要的業務能力。

與印象中動輒處理成千上萬連接的 tcp 網絡服務不同,這個本地服務是跑在客戶機器上的,Win32 上作為開機自啟動的 windows 服務運行;

Linux 上作為 daemon 在後台運行。總的說來就是用於接收幾個產品進程的連接,因此輕量化是其最重要的要求,在這個基礎上要能兼顧跨平台就可以了。

其實主要就是 windows,再兼顧一點兒 linux。

 

考察了幾個現有的開源網絡框架,從 ACE 、boost::asio 到 libevent,都有不盡於人意的地方:

a) ACE:太重,只是想要一個網絡框架,結果它扒拉扒拉一堆全提供了,不用還不行;

b) boost::asio:太複雜,牽扯到 boost 庫,並且引入了一堆 c++ 模板,需要高版本 c++ 編譯器支持;

c) libevent:這個看着不錯,當時確實用這個做底層封裝了一版,結果發版后發現一個比較致命的問題,導致在防火牆設置比較嚴格的機器上初始化失敗,這個後面我會詳細提到。

 

其它的就更不用說了,之前也粗略看過陳碩的 muddo,總的感覺吧,它是基於其它開源框架不足地方改進的一個庫,有相當可取的地方,但是這個改進的方向也主要是解決更大併發、更多連接,不是我的痛點,所以沒有繼續深入研究。

 

好了,與其在不同開源框架之間糾結,不如自己動手寫一個。

反正我的場景比較固定,不用像它們那樣面面俱,我給自己羅列了一些這個框架需要支持基本的功能:

1)同步寫、異步讀;

2)可同時監聽多路事件,基於 1)這裏只針對異步 READ 事件(包含連接進入、連接斷開),寫數據是同步的,因而不需要處理異步 WRITE 事件;

3)要有設置一次性和周期性定時器的能力 (業務決定的);

4)不需要處理信號 (windows 上也沒信號這一說,linux 自己搞搞 sigaction 就好啦);

……

 

雖然這個框架未來只會運行在用戶的單機上,但是我不希望它一出生就帶有性能缺陷,所以性能平平的 select 沒能進入我的法眼,我決定給它裝上最強大的心臟:

Windows 平台: iocp

Linux 平台:epoll

 

ok,從需求到底層技術路線,貌似都講清楚了,依照 libevent 我給它取名為 gevent,下面我們從代碼級別看下這個框架是怎麼簡化 tcp 服務搭建這類工作的。

首先看一下這個 tcp 服務框架的 sample:

svc_handler.h

 1 #include "EventBase.h"
 2 #include "EventHandler.h"
 3 
 4 class GMyEventBase : public GEventBase
 5 {
 6 public:
 7     GEventHandler* create_handler (); 
 8 }; 
 9 
10 
11 class svc_handler : public GJsonEventHandler
12 {
13 public:
14     virtual ~svc_handler () {}
15     virtual void on_read_msg (Json::Value const& val); 
16 };

epoll_svc.cpp

 1 #include <stdio.h>
 2 #include "svc_handler.h"
 3 #include <signal.h>
 4 
 5 GMyEventBase g_base; 
 6 GEventHandler* GMyEventBase::create_handler () 
 7 {
 8     return new svc_handler; 
 9 }
10 
11 void sig_int (int signo)
12 {
13     printf ("%d caught\n", signo); 
14     g_base.exit (1); 
15     printf ("exit ok\n"); 
16 }
17 
18 int main (int argc, char *argv[])
19 {
20     if (argc < 2)
21     {
22         printf ("usage: epoll_svc port\n"); 
23         return -1; 
24     }
25 
26     unsigned short port = atoi (argv[1]);
27 
28 #ifndef WIN32
29     struct sigaction act; 
30     act.sa_handler = sig_int; 
31     sigemptyset(&act.sa_mask);   
32     act.sa_flags = SA_RESTART; 
33     if (sigaction (SIGINT, &act, NULL) < 0)
34     {
35         printf ("install SIGINT failed, errno %d\n", errno); 
36         return -1; 
37     }
38     else
39         printf ("install SIGINT ok\n"); 
40 #endif
41 
42     // to test small message block
43     if (g_base.init (/*8, 10*/) < 0)
44         return -1; 
45 
46     printf ("init ok\n"); 
47     do
48     {
49         if (!g_base.listen (port))
50         {
51             g_base.exit (0); 
52             printf ("exit ok\n"); 
53             break; 
54         }
55 
56         printf ("listen ok\n"); 
57         g_base.run (); 
58         printf ("run  over\n"); 
59     } while (0); 
60 
61     g_base.fini (); 
62     printf ("fini ok\n"); 
63 
64     g_base.cleanup (); 
65     printf ("cleanup ok\n"); 
66     return 0; 
67 }

 

這個服務的核心是 GMyEventBase 類,它使用了框架中的 GEventBase 類,從後者派生而來,

只改寫了一個 create_handler 接口來提供我們的事件處理對象 svc_handler,它是從框架中的 GEventHandler 派生而來,

svc_handler 只改寫了一個 on_read_msg 來處理 Json 格式的消息輸入。

 

程序的運行就是分別調用 GMyEventBase(實際上是GEventBase)  的 init / listen / run / fini / cleaup 方法。

而與業務相關的代碼,都在 svc_handler 中處理:

svc_handler.cpp

 1 #include "svc_handler.h"
 2 
 3 void svc_handler::on_read_msg (Json::Value const& val)
 4 {
 5     int key = val["key"].asInt (); 
 6     std::string data = val["data"].asString (); 
 7     printf ("got %d:%s\n", key, data.c_str ()); 
 8 
 9     Json::Value root; 
10     Json::FastWriter writer; 
11     root["key"] = key + 1; 
12     root["data"] = data; 
13 
14     int ret = 0;
15     std::string resp = writer.write(root); 
16     resp = resp.substr (0, resp.length () - 1); // trim tailing \n
17     if ((ret = send (resp)) <= 0)
18         printf ("send response failed, errno %d\n", errno); 
19     else 
20         printf ("response %d\n", ret); 
21 }

 

它期待 Json 格式的數據,並且有兩個字段 key(int) 與 data (string),接收數據后將 key 增 1 后返回給客戶端。

再來看下客戶端 sample:

clt_handler.h

 1 #include "EventBaseAR.h"
 2 #include "EventHandler.h"
 3 
 4 class GMyEventBase : public GEventBaseWithAutoReconnect
 5 {
 6 public:
 7     GEventHandler* create_handler (); 
 8 }; 
 9 
10 
11 class clt_handler : public GJsonEventHandler
12 {
13 public:
14     virtual ~clt_handler () {}
15 #ifdef TEST_TIMER
16     virtual bool on_timeout (GEV_PER_TIMER_DATA *gptd); 
17 #endif
18     virtual void on_read_msg (Json::Value const& val); 
19 };

 

epoll_clt.cpp

  1 #include <stdio.h>
  2 #include "clt_handler.h"
  3 #include <signal.h>
  4 
  5 //#define TEST_READ
  6 //#define TEST_CONN
  7 //#define TEST_TIMER
  8 
  9 GMyEventBase g_base; 
 10 GEventHandler* GMyEventBase::create_handler () 
 11 {
 12     return new clt_handler; 
 13 }
 14 
 15 
 16 int sig_caught = 0; 
 17 void sig_int (int signo)
 18 {
 19     sig_caught = 1; 
 20     printf ("%d caught\n", signo); 
 21     g_base.exit (0); 
 22     printf ("exit ok\n"); 
 23 }
 24 
 25 void do_read (GEventHandler *eh, int total)
 26 {
 27     char buf[1024] = { 0 }; 
 28     int ret = 0, n = 0, key = 0, err = 0;
 29     char *ptr = nullptr; 
 30     while ((total == 0 ||  n++ < total) && fgets (buf, sizeof(buf), stdin) != NULL)
 31     {
 32         // skip \n
 33         buf[strlen(buf) - 1] = 0; 
 34         //n = sscanf (buf, "%d", &key); 
 35         key = strtol (buf, &ptr, 10); 
 36         if (ptr == nullptr)
 37         {
 38             printf ("format: int string\n"); 
 39             continue; 
 40         }
 41 
 42         Json::Value root; 
 43         Json::FastWriter writer; 
 44         root["key"] = key; 
 45         // skip space internal
 46         root["data"] = *ptr == ' ' ? ptr + 1 : ptr;  
 47 
 48         std::string req = writer.write (root); 
 49         req = req.substr (0, req.length () - 1); // trim tailing \n
 50         if ((ret = eh->send (req)) <= 0)
 51         {
 52             err = 1; 
 53             printf ("send %d failed, errno %d\n", req.length (), errno); 
 54             break; 
 55         }
 56         else 
 57             printf ("send %d\n", ret); 
 58     }
 59 
 60     if (total == 0)
 61         printf ("reach end\n"); 
 62 
 63     if (!err)
 64     {
 65         eh->disconnect (); 
 66         printf ("call disconnect to notify server\n"); 
 67     }
 68 
 69     // wait receiving thread 
 70     //sleep (3); 
 71     // if use press Ctrl+D, need to notify peer our break
 72 }
 73 
 74 #ifdef TEST_TIMER
 75 void test_timer (unsigned short port, int period_msec, int times)
 76 {
 77     int n = 0; 
 78     GEventHandler *eh = nullptr; 
 79 
 80     do
 81     {
 82         eh = g_base.connect (port); 
 83         if (eh == nullptr)
 84             break;
 85 
 86         printf ("connect ok\n"); 
 87         void* t = g_base.timeout (1000, period_msec, eh, NULL); 
 88         if (t == NULL)
 89         {
 90             printf ("timeout failed\n"); 
 91             break; 
 92         }
 93         else 
 94             printf ("set timer %p ok\n", t); 
 95 
 96         // to wait timer
 97         do
 98         {
 99             sleep (400); 
100             printf ("wake up from sleep\n"); 
101         } while (!sig_caught && n++ < times);
102 
103         g_base.cancel_timer (t); 
104     } while (0); 
105 }
106 #endif
107 
108 #ifdef TEST_CONN
109 void test_conn (unsigned short port, int per_read, int times)
110 {
111 #  ifdef WIN32
112     srand (GetCurrentProcessId()); 
113 #  else
114     srand (getpid ()); 
115 #  endif
116     int n = 0, elapse = 0; 
117     clt_handler *eh = nullptr; 
118 
119     do
120     {
121         eh = (clt_handler *)g_base.connect (port); 
122         if (eh == nullptr)
123             break;
124 
125         printf ("connect ok\n"); 
126 
127         do_read (eh, per_read); 
128 #  ifdef WIN32
129         elapse = rand() % 1000; 
130         Sleep(elapse); 
131         printf ("running  %d ms\n", elapse); 
132 #  else
133         elapse = rand () % 1000000; 
134         usleep (elapse); 
135         printf ("running  %.3f ms\n", elapse/1000.0); 
136 #  endif
137 
138     } while (!sig_caught && n++ < times);
139 }
140 #endif
141 
142 #ifdef TEST_READ
143 void test_read (unsigned short port, int total)
144 {
145     int n = 0; 
146     GEventHandler *eh = nullptr; 
147 
148     do
149     {
150         eh = g_base.connect (port); 
151         if (eh == nullptr)
152             break;
153 
154         printf ("connect ok\n"); 
155         do_read (eh, total); 
156     } while (0); 
157 }
158 #endif
159 
160 int main (int argc, char *argv[])
161 {
162     if (argc < 2)
163     {
164         printf ("usage: epoll_clt port\n"); 
165         return -1; 
166     }
167 
168     unsigned short port = atoi (argv[1]); 
169 
170 #ifndef WIN32
171     struct sigaction act; 
172     act.sa_handler = sig_int; 
173     sigemptyset(&act.sa_mask);   
174     // to ensure read be breaked by SIGINT
175     act.sa_flags = 0; //SA_RESTART;  
176     if (sigaction (SIGINT, &act, NULL) < 0)
177     {
178         printf ("install SIGINT failed, errno %d\n", errno); 
179         return -1; 
180     }
181 #endif
182 
183     if (g_base.init (2) < 0)
184         return -1; 
185 
186     printf ("init ok\n"); 
187 
188 #if defined(TEST_READ)
189     test_read (port, 0); // 0 means infinite loop until user break
190 #elif defined(TEST_CONN)
191     test_conn (port, 10, 100); 
192 #elif defined (TEST_TIMER)
193     test_timer (port, 10, 1000); 
194 #else
195 #  error please define TEST_XXX macro to do something!
196 #endif
197 
198     if (!sig_caught)
199     {
200         // Ctrl + D ?
201         g_base.exit (0); 
202         printf ("exit ok\n"); 
203     }
204     else 
205         printf ("has caught Ctrl+C\n"); 
206 
207     g_base.fini (); 
208     printf ("fini ok\n"); 
209 
210     g_base.cleanup (); 
211     printf ("cleanup ok\n"); 
212     return 0; 
213 }

 

客戶端同樣使用了 GEventBase 的派生類 GMyEventBase 來作為事件循環的核心,所不同的是(注意並非之前例子里的那個類,雖然同名),它提供了 clt_handler 來處理自己的業務代碼。

另外為了提供連接中斷後自動向服務重連的功能,這裏 GMyEventBase 派生自 GEventBase 類的子類 GEventBaseWithAutoReconnect (位於 EventBaseAR.h/cpp 中)。

程序的運行是分別調用 GEventBase 的 init / connect / fini / cleaup 方法以及 GEventHandler 的 send / disconnect 來測試讀寫與連接。

定義宏 TEST_READ 用來測試讀寫;定義宏 TEST_CONN 可以測試連接的通斷及讀寫;定義宏 TEST_TIMER 來測試周期性定時器及讀寫。它們是互斥的。

clt_handler 主要用來異步接收服務端的回送數據並打印:

clt_handler.cpp

 1 #include "clt_handler.h"
 2 
 3 #ifdef TEST_TIMER
 4 extern void do_read (clt_handler *, int); 
 5 bool clt_handler::on_timeout (GEV_PER_TIMER_DATA *gptd)
 6 {
 7     printf ("time out ! id %p, due %d, period %d\n", gptd, gptd->due_msec, gptd->period_msec); 
 8     do_read ((clt_handler *)gptd->user_arg, 1); 
 9     return true; 
10 }
11 #endif
12 
13 void clt_handler::on_read_msg (Json::Value const& val)
14 {
15     int key = val["key"].asInt (); 
16     std::string data = val["data"].asString (); 
17     printf ("got %d:%s\n", key, data.c_str ()); 
18 }

 

這個測試程序可以通過在控制台手工輸入數據來驅動,也可以通過測試數據文件來驅動,下面的 awk 腳本用來製造符合格式的測試數據:

epoll_gen.awk

 1 #! /bin/awk -f
 2 BEGIN {
 3         WORDNUM = 1000
 4         for (i = 1; i <= WORDNUM; i++) {
 5                 printf("%d %s\n", randint(WORDNUM), randword(20))
 6         }
 7 }
 8 
 9 # randint(n): return a random integer number which is >= 1 and <= n
10 function randint(n) {
11         return int(n *rand()) + 1
12 }
13 
14 # randlet(): return a random letter, which maybe upper, lower or number. 
15 function randlet() {
16         return substr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", randint(62), 1)
17 }
18 
19 # randword(LEN): return a rand word with a length of LEN
20 function randword(LEN) {
21         randw=""
22         for( j = 1; j <= LEN; j++) {
23                 randw=randw randlet()
24         }
25         return randw
26 }

 

生成的測試文件格式如下:

238 s0jKlYkEjwE4q3nNJugF
568 0cgNaSgDpP3VS45x3Wum
996 kRF6SgmIReFmrNBcCecj
398 QHQqCrB5fC61hao1BV2x
945 XZ6KLtA4jZTEnhcAugAM
619 WE95NU7FnsYar4wz279j
549 oVCTmD516yvmtuJB2NG3
840 NDAaL5vpzp8DQX0rLRiV
378 jONIm64AN6UVc7uTLIIR
251 EqSBOhc40pKXhCbCu8Ey

 

整個工程編譯的話就是一個 CMakeLists 文件,可以通過 cmake 生成對應的 Makefile 或 VS solution 來編譯代碼:

CMakeLists.txt

 1 cmake_minimum_required(VERSION 3.0)
 2 project(epoll_svc)
 3 include_directories(../core ../include)
 4 set(CMAKE_CXX_FLAGS "-std=c++11 -pthread -g -Wall ${CMAKE_CXX_FLAGS}")
 5 link_directories(${PROJECT_SOURCE_DIR}/../lib)
 6 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin)
 7 
 8 add_executable (epoll_svc epoll_svc.cpp svc_handler.cpp ../core/EventBase.cpp ../core/EventHandler.cpp ../core/log.cpp)
 9 IF (WIN32)
10 target_link_libraries(epoll_svc jsoncpp ws2_32)
11 ELSE ()
12 target_link_libraries(epoll_svc jsoncpp rt)
13 ENDIF ()
14 
15 add_executable (epoll_clt epoll_clt.cpp clt_handler.cpp ../core/EventBase.cpp ../core/EventBaseAR.cpp ../core/EventHandler.cpp ../core/log.cpp)
16 target_compile_definitions(epoll_clt PUBLIC -D TEST_READ)
17 IF (WIN32)
18 target_link_libraries(epoll_clt jsoncpp ws2_32)
19 ELSE ()
20 target_link_libraries(epoll_clt jsoncpp rt)
21 ENDIF ()
22 
23 add_executable (epoll_local epoll_local.cpp)
24 IF (WIN32)
25 target_link_libraries(epoll_local jsoncpp ws2_32)
26 ELSE ()
27 target_link_libraries(epoll_local jsoncpp rt)
28 ENDIF ()

 

 這個項目包含三個編譯目標,分別是 epoll_svc 、epoll_clt 與 epoll_local,其中前兩個可以跨平台編譯,后一個只能在 Linux 平台編譯,用來驗證 epoll 的一些特性。

編譯完成后,首先運行服務端:

>./epoll_svc 1025 

 然後運行客戶端:

>./epoll_clt 1025 < demo

測試多個客戶端同時連接,可以使用下面的腳本:

epoll_start.sh

1 #! /bin/bash
2 # /bin/sh -> /bin/dash, do not recognize our for loop
3 
4 for((i=0;i<10;i=i+1))
5 do
6     ./epoll_clt 1025 < demo &
7     echo "start $i"
8 done

 

可以同時啟動 10 個客戶端。

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

通過 Ctrl+C 退出服務端;通過 Ctrl+C 或 Ctrl+D 退出單個客戶端;

通過下面的腳本來停止多個客戶端與服務端:

epoll_stop.sh

1 #! /bin/sh
2 pkill -INT epoll_clt
3 sleep 1
4 pkill -INT epoll_svc

 

 

框架的用法介紹完之後,再簡單遊覽一下這個庫的各層級對外接口。

EventBase.h

  1 #pragma once
  2 
  3 
  4 #include "EventHandler.h" 
  5 #include <string>
  6 #include <map>
  7 #include <mutex> 
  8 #include <condition_variable>
  9 #include "thread_group.hpp"
 10 
 11 #define GEV_MAX_BUF_SIZE 65536
 12 
 13 class GEventBase : public IEventBase
 14 {
 15 public:
 16     GEventBase();
 17     ~GEventBase();
 18 
 19 #ifdef WIN32
 20     virtual HANDLE iocp () const; 
 21 #else
 22     virtual int epfd () const; 
 23 #endif
 24     virtual bool post_timer(GEV_PER_TIMER_DATA *gptd); 
 25     virtual GEventHandler* create_handler() = 0; 
 26 
 27     // thr_num : 
 28     //  =0 - no default thread pool, user provide thread and call run
 29     //  <0 - use max(|thr_num|, processer_num)
 30     //  >0 - use thr_num
 31     bool init(int thr_num = -8, int blksize = GEV_MAX_BUF_SIZE
 32 #ifndef WIN32
 33               , int timer_sig = SIGUSR1
 34 #endif
 35               ); 
 36 
 37     bool listen(unsigned short port, unsigned short backup = 10);
 38     GEventHandler* connect(unsigned short port, GEventHandler* exist_handler = NULL); 
 39     // PARAM
 40     // due_msec: first timeout milliseconds
 41     // period_msec: later periodically milliseconds
 42     // arg: user provied argument
 43     // exist_handler: reuse the timer handler
 44     //
 45     // RETURN
 46     //   NULL: failed
 47     void* timeout(int due_msec, int period_msec, void *arg, GEventHandler *exist_handler);
 48     bool cancel_timer(void* tid); 
 49     void fini();  
 50     void run(); 
 51     void exit(int extra_notify = 0); 
 52     void cleanup(); 
 53 
 54 protected:
 55 #ifdef WIN32
 56     bool do_accept(GEV_PER_IO_DATA *gpid); 
 57     bool do_recv(GEV_PER_HANDLE_DATA *gphd, GEV_PER_IO_DATA *gpid); 
 58     void do_error(GEV_PER_HANDLE_DATA *gphd); 
 59 
 60     int init_socket();
 61     bool issue_accept(); 
 62     bool issue_read(GEV_PER_HANDLE_DATA *gphd);
 63     bool post_completion(DWORD bytes, ULONG_PTR key, LPOVERLAPPED ol); 
 64 
 65 #else
 66     bool do_accept(int fd); 
 67     bool do_recv(conn_key_t key); 
 68     void do_error(conn_key_t key); 
 69 
 70     bool init_pipe(); 
 71     void close_pipe(); 
 72     bool post_notify (char ch, void* ptr = nullptr); 
 73     void promote_leader (std::unique_lock<std::mutex> &guard); 
 74 
 75     GEventHandler* find_by_key (conn_key_t key, bool erase); 
 76     GEventHandler* find_by_fd (int fd, conn_key_t &key, bool erase); 
 77 
 78 #  ifdef HAS_SIGTHR
 79     void sig_proc (); 
 80 #  endif
 81 #endif
 82 
 83     bool do_timeout(GEV_PER_TIMER_DATA *gptd); 
 84 
 85     virtual bool on_accept(GEV_PER_HANDLE_DATA *gphd);
 86     virtual bool on_read(GEventHandler *h, GEV_PER_IO_DATA *gpid); 
 87     virtual void on_error(GEventHandler *h);
 88     virtual bool on_timeout (GEV_PER_TIMER_DATA *gptd); 
 89     
 90 
 91 protected:
 92     volatile bool m_running = false;
 93     int m_thrnum = 0; 
 94     int m_blksize = GEV_MAX_BUF_SIZE; 
 95     std::thread_group m_grp; 
 96     SOCKET m_listener = INVALID_SOCKET;
 97 
 98     std::mutex m_mutex;  // protect m_map
 99     std::mutex m_tlock; // protect m_tmap
100     // timer_t may conflict when new timer created after old timer closed
101     //std::map <timer_t, GEventHandler *> m_tmap; 
102     std::map <GEV_PER_TIMER_DATA*, GEventHandler *> m_tmap; 
103 
104 #ifdef WIN32
105     LPFN_ACCEPTEX m_acceptex = nullptr; 
106     LPFN_GETACCEPTEXSOCKADDRS m_getacceptexsockaddrs = nullptr; 
107     HANDLE m_iocp = NULL; 
108     HANDLE m_timerque = NULL; 
109 
110     std::map<GEV_PER_HANDLE_DATA*, GEventHandler*> m_map; 
111 #else
112     int m_ep = -1; 
113     int m_pp[2]; 
114     int m_tsig = 0; // signal number for timer
115 
116     std::mutex m_lock;   // protect epoll
117     pthread_t m_leader = -1; 
118     std::map<conn_key_t, GEventHandler*> m_map; 
119 #  ifdef HAS_SIGTHR
120     // special thread only cares about signal
121     std::thread *m_sigthr = nullptr; 
122 #  endif
123 #endif
124 };

 

  • init,它在底層啟動 thr_num 個線程來跑 run 方法;每次 IO 的塊緩衝區大小由 blksize 指定;它內部還創建了對應的 iocp 或 epoll 對象,便於之后加入 socket 句柄進行處理。
  • exit,它通知線程池中的所有線程退出等待,windows 上是通過 PostQueuedCompletionStatus,Linux 上是通過在自建的一個 pipe 上寫數據以觸發 epoll 退出(這個 pipe 在 init 中創建並加入 epoll);
  • fini,它在所有工作線程退出后,關閉之前創建的對象,清理事件循環用到的資源;
  • cleanup,它清理之前建立的 fd-handler 映射,清理遺留的處理器並釋放資源;
  • run,它是線程池運行函數,windows 上是通過 GetQueuedCompletionStatus 在 iocp 上等待;在 linux 上是通過 epoll_wait 在 epoll 上等待事件。當有事件產生后,根據事件類型,分別調用 do_accept / on_accept、do_recv / on_read、do_error / on_error 回調來分派事件;
  • listen,創建偵聽 socket 並加入到 iocp 或 epoll 中;
  • connect,連接到遠程服務並將成功連接的 socket 加入到 iocp 或  epoll 中;
  • timeout,設置定時器事件,windows 上是通過 CreateTimerQueueTimer 實現定時器超時;linux 則是通過 timer_create 實現的,都是系統現成的東西,只不過在系統定時器到期后,給對應的 iocp 或 epoll 對象發送了一個通知而已,在 linux 上這個通知機制是上面提到過的 pipe 來實現的,因而有一定延遲,不能指定精度太小的定時器;
  • cancel_timer,取消之前設置的定時器。

 

然後看下 GEventHandler 提供的回調接口,應用可以從它派生並完成業務相關代碼:

EventHandler.h

  1 #pragma once
  2 #include "platform.h"
  3 
  4 #ifdef WIN32
  5 // must ensure <winsock2.h> precedes <widnows.h> included, to prevent winsock2.h conflict with winsock.h
  6 #  include <WinSock2.h>
  7 #  include <Windows.h>
  8 #  include <mswsock.h>  // for LPFN_ACCEPTEX & LPFN_GETACCEPTEXSOCKADDRS later in EventBase.h
  9 #else
 10 #  include <unistd.h> // for close
 11 #  include <sys/socket.h>
 12 #  include <sys/epoll.h>
 13 #  include <sys/time.h>
 14 #  include <netinet/in.h> // for struct sockaddr_in
 15 #  include <arpa/inet.h> // for inet_addr/inet_ntoa
 16 #  include <string.h> // for memset/memcpy
 17 #  include <signal.h>
 18 #endif
 19 
 20 #include <mutex>
 21 #include "jsoncpp/json.h"
 22 
 23 
 24 class GEventHandler; 
 25 struct GEV_PER_TIMER_DATA; 
 26 class IEventBase
 27 {
 28 public:
 29 #ifdef WIN32
 30     virtual HANDLE iocp () const = 0; 
 31 #else
 32     virtual int epfd () const = 0; 
 33 #endif
 34 
 35     virtual void* timeout(int due_msec, int period_msec, void *arg, GEventHandler *exist_handler) = 0; 
 36     virtual bool cancel_timer(void* tid) = 0; 
 37     virtual bool post_timer(GEV_PER_TIMER_DATA *gptd) = 0; 
 38 };
 39 
 40 
 41 #ifdef WIN32
 42 enum GEV_IOCP_OP
 43 {
 44     OP_TIMEOUT = 1, 
 45     OP_ACCEPT,
 46     OP_RECV,
 47 };
 48 #else 
 49 // the purpose of this key is to distinguish different connections with same fd !
 50 // (when connection break and re-established soon, fd may not change but port will change)
 51 struct conn_key_t
 52 {
 53     int fd; 
 54     unsigned short lport; 
 55     unsigned short rport; 
 56 
 57     conn_key_t (int f, unsigned short l, unsigned short r); 
 58     bool operator< (struct conn_key_t const& rhs) const; 
 59 }; 
 60 #endif
 61 
 62 
 63 struct GEV_PER_HANDLE_DATA
 64 {
 65     SOCKET so;
 66     SOCKADDR_IN laddr;
 67     SOCKADDR_IN raddr;
 68 
 69 #ifndef WIN32
 70     conn_key_t key () const; 
 71 #endif
 72 
 73     GEV_PER_HANDLE_DATA(SOCKET s, SOCKADDR_IN *l, SOCKADDR_IN *r); 
 74     virtual ~GEV_PER_HANDLE_DATA(); 
 75 };
 76 
 77 struct GEV_PER_IO_DATA
 78 {
 79     SOCKET so;
 80 #ifdef WIN32
 81     GEV_IOCP_OP op;
 82     OVERLAPPED ol;
 83     WSABUF wsa;         // wsa.len is buffer length
 84     DWORD bytes;        // after compeleted, bytes trasnfered
 85 #else
 86     char *buf; 
 87     int len; 
 88 #endif
 89 
 90     GEV_PER_IO_DATA(
 91 #ifdef WIN32
 92             GEV_IOCP_OP o, 
 93 #endif
 94             SOCKET s, int l); 
 95     virtual ~GEV_PER_IO_DATA(); 
 96 };
 97 
 98 struct GEV_PER_TIMER_DATA
 99 #ifdef WIN32
100        : public GEV_PER_IO_DATA
101 #endif
102 {
103     IEventBase *base; 
104     int due_msec; 
105     int period_msec; 
106     void *user_arg;
107     bool cancelled;
108 #ifdef WIN32
109     HANDLE timerque; 
110     HANDLE timer; 
111 #else
112     timer_t timer; 
113 #endif
114 
115     GEV_PER_TIMER_DATA(IEventBase *base, int due, int period, void *arg
116 #ifdef WIN32
117             , HANDLE tq);
118 #else
119             , timer_t tid); 
120 #endif
121 
122     virtual ~GEV_PER_TIMER_DATA(); 
123     void cancel (); 
124 };
125 
126 class GEventHandler
127 {
128 public:
129     GEventHandler();
130     virtual ~GEventHandler();
131 
132     GEV_PER_HANDLE_DATA* gphd(); 
133     GEV_PER_TIMER_DATA* gptd(); 
134     bool connected();
135     void disconnect(); 
136     void clear(); 
137     SOCKET fd(); 
138 
139     int send(char const* buf, int len);
140     int send(std::string const& str);
141     
142     virtual bool reuse();
143     virtual bool auto_reconnect();
144     virtual void arg(void *param) = 0;
145     virtual void reset(GEV_PER_HANDLE_DATA *gphd, GEV_PER_TIMER_DATA *gptd, IEventBase *base);
146     virtual bool on_read(GEV_PER_IO_DATA *gpid) = 0;
147     virtual void on_error(GEV_PER_HANDLE_DATA *gphd); 
148     // note when on_timeout called, handler's base may cleared by cancel_timer, use gptd->base instead if it is not null.
149     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd) = 0; 
150     virtual void cleanup(bool terminal);
151     void close(bool terminal);
152 
153 protected:
154     GEV_PER_HANDLE_DATA *m_gphd = nullptr; 
155     GEV_PER_TIMER_DATA *m_gptd = nullptr; 
156     IEventBase *m_base = nullptr;
157     // us so instead of m_gphd, 
158     // as the later one may destroyed during using..
159     SOCKET m_so;
160 };
161 
162 // a common handler to process json protocol.
163 class GJsonEventHandler : public GEventHandler
164 {
165 public:
166     //virtual void on_read();
167     virtual void arg(void *param);
168     virtual void reset(GEV_PER_HANDLE_DATA *gphd, GEV_PER_TIMER_DATA *gptd, IEventBase *base);
169     virtual bool on_read(GEV_PER_IO_DATA *gpid);
170     virtual void on_read_msg(Json::Value const& root) = 0;
171     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd);
172     virtual void cleanup(bool terminal);
173 
174 protected:
175     // protect m_stub to prevent multi-entry
176 #ifdef HAS_ET
177     std::mutex m_mutex; 
178 #endif
179 
180     std::string m_stub;
181 };

 

這裏主要有兩個類,GEventHandler 處理通用的基於流的數據;GJsonEventHandler 處理基於 json 格式的數據。

前者需要重寫 on_read 方法來處理塊數據;後者需要重寫 on_read_msg 方法來處理 json 數據。

目前 json 的解析是通過 jsoncpp 庫完成的,這個庫本身是跨平台的(本 git 庫僅提供 64 位 Linux 靜態鏈接庫及 VS2013 的 32 位 Release 版本 Windows 靜態庫)。

svc_handler 與 clt_handler  均從 GJsonEventHandler 派生。

如果有新的流格式需要處理 ,只需要從 GEventHandler 類派生新的處理類即可。

 

除了讀取連接上的數據,還有其它一些重要的回調接口,列明如下:

  • on_read,連接上有數據到達;
  • on_error,連接斷開;
  • on_tmeout,定時器事件;
  • ……

如果有新的事件需要處理 ,也可以在這裏擴展。

最後看下 GEventBaseWithAutoReconnect 提供的與自動重連相關的接口:

EventBaseAR.h

 1 #pragma once
 2 
 3 
 4 #include "EventBase.h"
 5 #include <thread>
 6 
 7 #define GEV_RECONNECT_TIMEOUT 2 // seconds
 8 #define GEV_MAX_RECONNECT_TIMEOUT 256 // seconds
 9 
10 class GEventBaseWithAutoReconnect : public GEventBase
11 {
12 public:
13     GEventBaseWithAutoReconnect(int reconn_min = GEV_RECONNECT_TIMEOUT, int reconn_max = GEV_MAX_RECONNECT_TIMEOUT);
14     ~GEventBaseWithAutoReconnect();
15 
16     bool do_connect(unsigned short port, void *arg);
17     GEventHandler* connector(); 
18 
19 protected:
20     virtual void on_error(GEventHandler *h);
21     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd);
22 
23     virtual void on_connect_break(); 
24     virtual bool on_connected(GEventHandler *app);
25 
26 protected:
27     void do_reconnect(void *arg);
28 
29 protected:
30     unsigned short m_port; 
31     GEventHandler* m_app;
32     GEventHandler* m_htimer; 
33     void* m_timer;
34     int m_reconn_min; 
35     int m_reconn_max; 
36     int m_reconn_curr;
37 };

 

其實比較簡單,只比 GEventBase 類多了一個  do_connect 方法,來擴展 connect 不能自動重連的問題。

底層的話,是通過定時器來實現指數後退重連算法的。

 

最後,如果你還是感到雲里霧裡的,可以參考一下下面的類結構圖:

 

 

黑色標註的是框架提供的類,紅色是服務端派生的類,藍色是客戶端派生的類,其實 GMyEventBase 的唯一作用就是將 svc_handler 與 clt_handler 分別引入各自的框架中,

所以用戶的關注點主要還是在派生自己的 GEventHandler 類,並在其中的回調接口中處理數據就可以了。

 

 

後記

這個框架已經應用到我司的公共產品中,併為數個 tcp 服務提供底層支撐,經過百萬級別用戶機器驗證,運行穩定性還是可以的,所以當得起“工業級”這三個字。

 

前面在說到開源庫的選型時還留了一個口子沒有交待,這裏一併說下。

其實最早的重構版本是使用 libevent 來實現的,但是發現它在 windows 上使用的是低效的 select,

而且為了增加、刪除句柄,它又使用了一種 self-pipe-trick 的技巧,簡單說來的就是下面的代碼序列:

listen (listen_fd, 1); 
……
connect (connect_fd, &addr, size); 
……
accept_fd = accept (listen_fd, &addr, &size); 

 

在缺乏 pipe 調用的 win32 環境製造了一個 socket 自連接,從而進行一些通知。

這一步是必要的,如果不能成功連接就會導致整個 libevent 初始化失敗,從而運行不起來。

不巧的是,在一些 windows 機器上(約佔用戶總量 10%),由於防火牆設置嚴格,上述 listen 與 connect 調用可以成功,

但是 accept 會失敗返回,從而導致整個服務退出 (防火牆會嚴格禁止不在白名單上偵聽的端口的連接)。

對於已知端口,可以通過在防火牆上設置白名單來避免,但是對於這種隨機 listen 的端口,真的是太難了,基本無解。

 

回頭考察了一下 asio,windows 上使用的是 iocp,自然沒有這個自連接;

ACE 有多種實現可供選擇,如果使用  ACE_Select_Reactor / ACE_TP_Reactor 是會有這個自連接,

但是你可以選擇其它實現,如基於 WaitForMultipleEvents 的 ACE_WFMO_Reactor(最大隻支持 62 個句柄,放棄),

或基於 iocp 的 ACE_Proactor (前攝式,與反應式在編程上稍有不同,更接近於 asio)就沒有這個自連接。

 

再說的深一點,其實公司最早的網絡庫使用的就是基於 boost 的 asio,大量的使用了 c++ 模板,

有時候產生了一些崩潰,但是根據 dump 完全無法定位崩潰點(各種冗長的模板展開名稱),

導致了一些頑固的已知 bug 一直找不到崩潰點而無法解決(雖然量不大),所以才有了要去重新選型網絡庫以及後來這一系列的東西。

 

本來一開始我是想用 ACE 的,因為我讀過這個庫的源碼,對裏面所有的東西都非常熟悉,

但是看看 ACE 小 5 MB 的 dll 尺寸,還是放棄了(產品本身安裝包也就這麼大吧),

對於一個公司底層的公共組件,被各種產品攜帶,需要嚴格控制“體重”

(後來聽說 ACE 按功能拆分了代碼模塊,你只需要選自己依賴的部分即可,不過我還沒有試過)。

 

使用這個庫代替之前的 boost::asio 后,我還有一個意外收穫,就是編譯出來的 dll 尺寸明顯小了很多,700 K -> 500 K 的樣子,看來所謂模板膨脹是真有其事……

 

最後奉上 gevent 的 github 鏈接,歡迎有相同需求的小夥伴前來“復刻” :

https://github.com/goodpaperman/gevent

 

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

繽智、XR-V都顫抖!這款1.5T小鋼炮SUV來到有多強?_包裝設計

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

XX-18。XX萬之間,屆時以下車型將會是其最大競爭對手:繽智(XRV)新車指導價:12。88-18。98萬2014年10月25日上市的廣汽本田繽智,已經上市銷售了兩年,目前還在以每個月1萬5千台銷量持續在買。雖然1。5T/1。8L這種組合在目前來看依然主流,但以推動1。

前言

馬自達靠家轎、MpV多款車型佔據市場的場面,但最近幾年已經還在大賣的車型已經變得越來越少了,隨着CX-4以及昂克賽拉的改款,以及以下這台即將來臨的CX-3,不僅會讓馬自達變得好轉,而且會讓目前沉悶的小型SUV市場更加活躍起來!

CX-3其實是以馬自達2基礎而來,可以說兩者幾乎是同平台產物,所以操控性乃至空間上CX-3都與馬自達2沒什麼區別,關鍵在於CX-3是一款緊湊型SUV,因此馬自達很有可能為其配備1.5T/2.0L兩套動力系統,兩者都為創馳藍天技術的發動機,1.5T應該是基於柴油發動機,最大105匹馬力,而2.0升自吸車型則有望配備156匹馬力。當然還有目前在阿特茲上基於發動機和底盤整合控制的GVC系統(G-Vectoring Control)。

外觀將繼續沿用目前家族化的前臉–魂動設計,除了必須有的魂動紅之外,還將新增有鋼鐵灰、雲母藍和躍雪白三種車身顏色。同時還會在高配車型上配備LED大燈以及215/50的18寸輪轂,

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

並將會加大塞入隔音棉以利於NVH隔音提升車身安靜度。(現款昂克賽拉以及阿特茲都比同級車型有更多道路信息)

其他配置方面,將會配備能自動剎停的主動防護系統。根據目前CX-5的16.98-24.58萬以及CX-4的14.08-21.58萬價格區間,CX-3很有可能會定位在12.XX-18.XX萬之間,屆時以下車型將會是其最大競爭對手:

繽智(XRV)

新車指導價:12.88-18.98萬

2014年10月25日上市的廣汽本田繽智,已經上市銷售了兩年,目前還在以每個月1萬5千台銷量持續在買。雖然1.5T/1.8L這種組合在目前來看依然主流,但以推動1.2噸-1.39噸的車身來說在後半段加速時確實有些力不從心,對於一向追求黑科技的本田來說,更新確實迫在眉睫。

北京現代ix25

新車指導價:11.98-18.68萬

ix25上市之初確實成為一道亮麗的小鮮肉,特別是都金秀賢的代言讓其一下子就其紅火起來,不過一樣是2014年10月10日上市的ix25目前月銷量僅有7700多以及大幅落後於XRV了更別說繽智。雖然2016款新增了兩台160匹馬力的1.6T車型,不過也難力挽狂瀾,最大原因是售價稍高的緣故吧。

今年6月21日上市的馬自達CX-4,未曾上市就受到了多方追捧,可惜4個月過去了,累計銷量僅2萬1千台,雖然同比馬自達CX-5月銷量上升了1000多,可是比起目前已經存在的緊湊SUV廣闊市場來說還是力量薄弱,CX-3其實已經在日本、台灣局部地區開始銷售了,但並非是全球車型,馬自達的自閉症啥時候才能治呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

Caviar 推出奢侈黄金版 AirPods Max 、PlayStation 5 ,前者要價 300 萬元起_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

月初我們才剛分享過 CAVIAR 這間來自俄羅斯的客製化品牌的 iPhone 12 Pro 鈦合金潛行款,隨著今年下半年兩款別具話題的產品 AirPods Max 與 PlayStation 5(PS5)開賣後, CAVIAR 這次也將腦筋動到了這兩款產品上。
一如既往, CAVIAR 選用了黃金和鱷魚皮作為客製化版本的材質,當然價格也不是一般消費者能負擔得起的程度,光是相對便宜的黃金版 AirPods Max 就要價約 300 萬元新台幣。

Caviar 推出奢侈黄金版 AirPods Max 、PlayStation 5 ,前者要價 300 萬元起

Caviar 近期宣布將於 2021 年推出度有純金的 AirPods Max ,售價高達 108,000 美元(約合新台幣 304.2 萬元)。據悉這款 AirPods Max 黃金版的耳罩部分將原本的鋁金屬材質改為 18K 金打造,頭帶部分則以鱷魚皮材質取代原本的網狀材質。

CAVIAR 將推出掰色和黑色兩種顏色的鱷魚皮搭配以黃金為主體的 AirPods Max 。雖然 CAVIAR 官方未公布黃金版本的 AirPods Max 機身重量有多重,不過回顧已經不算輕盈的標準版 AirPods Max  重量就有 384.8 公克,黃金版本的重量肯定沈重不少。

耳罩部分包括控制按鈕和旋鈕都以鍍金處理,更提升了整體的奢華感:

價格方面, AirPods Max 黃金版建議售價為 108,000 美元(約合新台幣 304.2 萬元),大約可等同 164 組標準版 AirPods Max 的價位。

與 AirPods Max 建議售價相當的 PlayStation 5 ,也同樣在 CAVIAR 這次黃金版客製化的名單中,黃金版 PS5 一樣採用 18K 黃金材質打造,總共使用了大約 20 公斤的黃金:

與以往 CAVIAR 客製化產品相同, PS5 黃金版主要在外殼上進行重新設計,不僅在機身換上兩塊 3D 打造的 18K 純金實心外殼:

CAVIAR 設計師表示這款 Golden Rock 的 PS5 靈感源自於金礦石的獨特幾何形狀和優美優美的岩石輪廓:

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

除此之外,在 Dual Sense 遊戲控制器除了黃金還有加入鱷魚皮等材質:

PS5 黃金版目前 CAVIAR 尚未公開價格,不過光是以黃金價格來估算就能推測他價格將非常驚人。

圖片/消息來源:CAVIAR

延伸閱讀:
iOS 應用小技巧:自動停止播放影片/音樂,睡前喜愛滑手機的人都必學!

Caviar 發表 iPhone 12 Pro 鈦合金「潛行款」,取消所有相機還加三倍價賣

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

340 萬台,據報 PS5 破 PlayStation 首月出貨最高紀錄_貨運

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

雖然距離前代 PlayStation 4 突破億台的銷量還有很長一段路要走,不過身為 2020 年 Sony 端出的最強次世代主機,PlayStation 5 還是以各種開箱攻佔了各大媒體與社群平台的熱門話題。現在據報也已經以首四週 340 萬台的好銷售達到破紀錄的銷售成績。繼續閱讀 340 萬台,據報 PS5 破 PlayStation 首月出貨最高紀錄報導內文。

340 萬台,據報 PS5 破 PlayStation 首月出貨最高紀錄

缺貨到讓人又愛又恨的 PlayStation 5,很多也好奇到底這次的銷量成績與同樣都受到極高歡迎的先前幾代有沒有更強的銷售力道?是說,儘管在極度缺貨的日本,銷量表現似乎有所抑制(但我們還是搶先開箱日本版的 PlayStation 5 了)不過 Sony 這台次世代遊戲主機的全球銷量部分,現在已經從 Digitimes 關於出貨量的報導裡,間接揭露了 PlayStation 5 全球首四週的銷量,已直破以往自家主機的紀錄,來到 340 萬台的好成績。

是說,這篇報導主要劃的重點是在於緊接著在 2021 年初,得益於產線的改進年產量預估可達 1,680 萬至 1,800 萬台;1 月起將預留供貨給等得焦急的亞洲消費者(太好惹)。但對於 Sony 來說,這銷量相較於以往大受歡迎的 PS4(2018 年 8 月開賣至 12 月初,達到 210 萬台)應該真的讓他們有點措手不及吧,必須馬上修正提供更高的產量對應。

必須說,看來在這全人類都因為疫情悶得發慌的年代裡, PlayStation 5 出現的時間點也算是剛好對應了許多人居家娛樂的需求。

引用來源

延伸閱讀:

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

PlayStation 5 第一手搶先開箱!絕對是現今最值得入手的遊戲主機

iPhone 12 Pro Max 深度實拍體驗:ProRAW 讓 iPhone 踏入專業

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

輕鬆實現記錄與撤銷——C#中的Command模式_網頁設計公司

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

Command模式屬於行為模式,作為大名鼎鼎的23個設計模式之一,Command模式理解起來不如工廠模式,單例模式等那麼簡單直白。究其原因,行為模式着重於使用,如果沒有編程實踐,確實不如創造模式那麼直白。我們先看看UML類圖。

估計很多同學看着圖就暈了,那麼多東西,Command和Concrete Command還好理解,那些Receiver和Invoker又是什麼東西呢?
 
別著急,只要理解了一點,這個模式就很容易理解了,下面划重點,Command模式最主要的特點,是將命令封裝成類,在類中保存命令執行的上下文(即該命令執行的參數,執行的對象),以實現命令執行對象和命令發出對象的解耦
 
這樣一來是不是覺得好理解多了?Command類裏面的Receiver,就是命令具體執行的對象。這裏的Client可以理解為裝配環境,在這裏面代碼實例化Command。Invoker內部保存命令(可以保存多條命令,實現命令記錄查看,撤銷等),客戶端代碼通過Invoker來操作命令。接下來我們看看示例代碼。

 

定義Command接口

首先我們定義一個支持撤銷的Command接口。

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

    interface Command
    {
        void Execute();
        void Undo();
    }

定義Receiver

接下來我們定義Receiver,也就是命令的執行對象,這裏我們定義一個Ball類。

    class Ball
    {
        public int Size { get; set; } = 10;
        public string Name { get; set; } = "My First Ball";
        public void Inspect()
        {
            Console.WriteLine("My Name is {0} and size is {1}", Name, Size);
        }
    }

定義具體命令

這裏定義兩個命令,一個修改名字,一個修改大小。

    class ChangeNameCommand : Command
    {
        private Ball _Ball;
        private string _OldName;
        public string NameYouWant { get; set; }
        public ChangeNameCommand(Ball ball)
        {
            _Ball = ball;
        }

        public void Execute()
        {
            _OldName = _Ball.Name;
            _Ball.Name = NameYouWant;
        }

        public void Undo()
        {
            _Ball.Name = _OldName;
        }
    }
    
	class ChangeSizeCommand : Command
	{
	//代碼大同小異,略
	}

定義Invoker

接下來是Invoker,,也就是存儲命令,並最終會被用戶代碼調用的類,這裏我們叫它CommandManager。

    class CommandManager
    {
        private Stack<Command> commands = new Stack<Command>();

        public void RunCommand(Command command)
        {
            command.Execute();
            commands.Push(command);
        }

        public void Undo()
        {
            if (commands.Count > 0)
            {
                var command = commands.Pop();
                command.Undo();
            }
        }
        
		public void ShowCommands()
        {
            var temp = commands.Reverse();
            foreach(var command in temp)
            {
                //display command
            }
        }
    }

使用命令

現在我們看看客戶端代碼是怎麼使用他們的,定義Ball,定義命令,通過CommandManager去調用,這樣可以方便查看命令記錄,撤銷命令,等。

        static void Main(string[] args)
        {
            Ball ball = new Ball();
            ball.Inspect();

            ChangeNameCommand changeName = new ChangeNameCommand(ball) { NameYouWant = "Changed" };
            ChangeSizeCommand changeSize = new ChangeSizeCommand(ball) { SizeYouWant = 20 };

            CommandManager manager = new CommandManager();
            manager.RunCommand(changeName);
            manager.RunCommand(changeSize);
            ball.Inspect();

			manager.ShowCommands();
            
            manager.Undo();
            ball.Inspect();

            manager.Undo();
            ball.Inspect();            
        }

就醬,我們已經實現了命令模式,並且還支持命令的記錄與撤銷,希望能對大家有點幫助。

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

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境