線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:36356
推到 Plurk!
推到 Facebook!
[<<] [1] [2] [3] [>>]

無責任技評 : DataSnap

 
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#1 引用回覆 回覆 發表時間:2015-02-17 23:48:42 IP:111.243.xxx.xxx 訂閱
 最近數日,副站連po他對datasnap的相關經驗與學習心得等。我也從中接收了一些不錯的資訊。謝謝他 ^_^

在觀看了幾篇後,覺得蠻有意思的,於是乎就引我稍深入的去了解DataSnap。我必需承認,在過去,我大概只用了3次以內的datasnap。經過三天左右的深入點的研究後,我也產生了一些看法,部份看與與 ruru 副站不太一樣。本來在心遲疑是否要把心得與看法分享一下,原因當然是怕會讓人誤會我在打對台。但又想想,一件事情有多種看法是有幫助的,無論是正面的意見或是負面的意見,但值得去思考並從中得取價值。於是決定還是來寫一下幾天來的心得。在寫之前,我突有所想,在年輕時有一位c 者作--候捷,他曾寫過「無責任書評」與「深入淺出mfc」等等好書。我喜歡他的許多觀點,無論就技術上的,或是學習方法上的經驗。因此,我想開始寫一些「無責任技評」。

什麼是無責任技評?從候先生在199x年繼承了「李宗盛的無責任樂評」寫了「無責任書評」的一致想法 : 就是"本人"對一些東西暢所欲言、不必負責。你也可以當做是一種「個人」對事情的看法。當然,非常有主觀意識,也不見得是正確。因此,也不必非常過度的認真。「技評」,我用這二個字的原因是我並非要評論某書,而是想評論某些「資訊技術相關的東西」。本次,就從我對 datasnap 這項技述來評論一下。

評論,不代表都以負面觀點來看待,雖然用負面的說詞比較嗨,也比較容易激發感覺,但我還是儘量的照自己的想法把正面與負面的都說一下。另外,評論是對「事」而不對「人」(至少儘力而為)。而我本身也樂意被批論,當然最好是就技術上,非人格上喔。哈哈。

本文即將開始:

「不管是黑貓或白貓,能抓老鼠的都是好貓」。

「不管是玩具車或是一般車,都是好車」-- 蕭沖

為什麼我提到玩具車? 因為近來我發現不少技術都像是玩具車。玩具車沒什麼不好,你試想若世上沒玩具車,那小孩子們的樂趣就少很多了。小孩當然只能開玩具車來滿足他們的愉快。大人們當然就是想開一般車 : 跑車,高級房車… 。生產玩具車也是能賺錢的,個人覺得目前embarcadero公司在delphi/cb一些技術上就是在生產玩具車。

於是就來評論一下這款 datasnap 玩具車。在評論前,不免俗的要先對一些名詞做些定義,緊接著就要用「深入淺出」的方式 -- 化整為零,解構後吸收至內力,然後感覺其實也沒什麼 -- 來做一些結論。

「何為n層架構?」喔 ruru 他在他的那篇也有開宗名義的提到。不過,我當然是有所不同的看法,所以才再提。軟體的架構除了寫成"一個" exe 檔外,其實還有一些選擇。原因不外乎軟體若深入的解析,大致上可以分成「展示層」、「商業邏輯層」、「資料層」。展示層就是「畫面」與人互動的畫面,也就是一般說的 UI 。「商業邏輯層」指的就是因應不同的領域,對資料做一些計算(演算)的處理,比如會計軟體就要實作「借貸法則」。而資料層呢? 當然就是對計算或是記錄的結果來做存與取。簡單的說它可以是一些ini檔,data檔,dbase檔,或是RDBMS(關連資料庫),如 mysql,mssql… 。 以上是說明了三層的架構。那麼有一些人所提的 三 層以上的架構是啥? 個人覺得是將 三層 「水平」的在分出來的層。比如把「邏輯層」再次水平分出幾個層次,意即一種分散式處理。

所以,小結一下三層架構:

1層: 將軟體3層的內涵全都寫在一個程式裡面,無論是畫面,計算,對資料的存取(指比如隨機讀文字檔)通通一個程式全部搞定。這種也很常見。

2層: 最一般的說法是將「資料層」與其他二層分離。通常,這種資料層都是用高級資料庫,比如 mssql,mysql,oracle…。2 層沒什麼特別的,就單純的是把一些資料的存取由高級資料庫來擔任。程式人員不必去寫「二元樹」等資料結構演算法,因為他們被database給做掉了。這種軟體也是很普遍。

3層: 就是把軟體的內函三層都分出來了。其中,展示層--畫面 被移出變成一個模組或執行檔。邏輯層則變成了另一個模組或程式。而資料層依然是高級資料庫本身。換句話說,至少有三個模組(dll 或 exe) 分離出來,並擔任各自的責任。


三層架構的名詞定義完。接著就來談一個相關的東西 -- 瘦客戶 -- 不是客戶他很瘦喔。英文是Thin Client。我平常比較喜歡用英文技術詞,比較不會意思出入。比如若是我來翻譯,我可能叫它 -- 輕量級客戶端。在 過去 midas4 至 2010年前比較常被提到。他的意思就是希望 end user的電腦上只要裝一些很輕量的程式 (意味著重點在畫面),就可以跑。為了要輕,當然就要把一些層給移除了。於是就有3層開發的一些研究與討論。當時,網路的技術在 web 2.0 前後,最常被說的就是 web application。他就是標準的 3 層 thin client。

以web架構來看。browser本身就是負責 畫面 。(僅管目前前端javascript 過度重用,導致愈來愈不輕)。故 browser就是第1層。而第二層是 web server 後端程式,比如 iis 配上 asp,或是 apache配上php。中間層負責把user送來的資訊加以整理、計算後輸出。第三層,依然是資料庫層。僅管軟體內函三層不一定要是機器分開三台,但通常是會分開,才能將優點更突出。小結web開發之三層。

第1層 : browser ,使用DHTML javascript 。
第2層 : web server,使用 cgi / php/ asp/ isapi/ asp net/ jsp …
第3層 : SQL server: Orcale, MSSQL, MySQL....

以上三層定義與應用大致上說明完畢。

待續‧‧‧‧‧

PS. 請看倌們思考此刻 2015年,我們需要 瘦客戶 嗎? cpu 超快,記憶體很大,硬碟更是大到不像話… 瘦客戶有什麼好處? 你是delphi / cb 人員的話要怎麼來寫類 web application 三層?
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-17 23:54:08, 註解 無‧
aftcast 重新編輯於 2015-02-18 00:00:54, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#2 引用回覆 回覆 發表時間:2015-02-18 02:40:34 IP:111.243.xxx.xxx 訂閱
 2層架構 vs 3 層架構,誰輸誰贏?

事實上「在資料傳輸上」,2層唯一比較明確輸3層的只有一個:

◎當資料庫與程式在不同機器上時,且程式需透過網際網路連絡 database 時, 3 層比2層安全非常的多。

比較安全是因為程式在網際網路的任何地點上不能直連 db,意味著無法直接控制整個db。

其他方面, 3層 與 2層 很難說誰的效能好。

◎「連線數過多,效能變差? 」 其實… 「一般」來說,連線數少,效能才差!

假始 20人有資料的請求,建立20條連線絕對比建立 1條或2、3條快非常多。喔,當然,這與 ruru 的說法有些不同,是吧? … 這… 請自己判別吧,我只能說依我的了解是這樣的。給你一個例子,假如某個 query 要查很久,比如查30秒鐘才會完成。那麼若你僅建立一條連線,那麼第 20 人要收到資料時,因為排隊大概也好幾分鐘。現在的機器與database,絕對可以承受多連線同時處理,並發揮多核的效能。故連立少數連線來服務多數人,效能一般是差!

那麼3層的資料存取是否有機會勝過2層? 當然有機會… 但前題是,中間層要能夠建立cache。(但,事實上db本身就會一些cache)。如果說,中間層可以把被要求過的 table (在記憶體中) cache住,等下一個連入請求時,再給他們,這就3層就可能勝出。 話說到此,先別高興,

◎datasnap在2009前,根本就是沒有,或是部份有cache的能力! 這就是我說他是玩具的道理。暫時不講細節。換句話說,2009年前,3層的架構(指機器分開的)肯定慢! 除了安全性,我看不出有太多理由用 datasnap。

◎直連db server的通訊協定是快速的。也就是說,你的程式向database要求資料,與回傳資料的那些"流",在網路線上,是快速的二進位。相對的,若是透過中間層… 那肯定是較慢的。你有想過當你的程式與中間層間的資料是長怎樣的嗎? 二進位? 字串? 包很大?。這方面… 2層一定勝3層。

以上是就「資料傳輸來論」,2層通常贏。那麼三層分立的優勢 : 瘦客戶 在哪裡? 喔… 再來一個思考題,假設你要求一個極大的質數,這需要很多的cpu運算才能達成。若家用pc算起來有點吃力(雖然現在家用也很強)。那麼,把這樣的請求交給雲上的「超級中間層」處理後再回傳給你,肯定比較快。說到這裡… 你可能又覺得3層好…但依然別先高興,

◎datasnap在2009前,根本無法(至多極小部份)做「商業邏輯運算」。它只會依你的請求,幫你從db上取資料送給你。是跛腳的中間層!


結論: 總的來說,2層架構輸在 安全性,其他方面則不一定是輸的,而且經常是贏的!


PS. 真心歡迎大家來踢館、指教、指正。有討論才會愈進步。 對於技術,本人不在意面子。除非是針對人身啦!

未完,待續…
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-18 02:42:32, 註解 無‧
aftcast 重新編輯於 2015-02-18 02:48:22, 註解 無‧
aftcast 重新編輯於 2015-02-18 02:53:23, 註解 無‧
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#3 引用回覆 回覆 發表時間:2015-02-18 08:04:46 IP:211.79.xxx.xxx 未訂閱
其實我開始的時候就在想要不要針對我的 DataSnap 主題另開吐槽串。

想想可能沒人理就作罷,想不到蕭大俠就先開炮了。哈!

多層,究竟有幾層?試著定義一下。 一文中。

我就有提到關於三層的內容,也許我講的還太模糊。

硬是忽略 Database 的處理,對於「層數」,我會在下個版本再做修正說明。

附帶補充,我手繪的圖形架構是完整的三層架構!

===============================

「效率」在這邊又細分:
就傳輸效率來說,三層就像是自排車,不論效率再怎麼提升,所能提供的效能,
就只能趨近於,頂多等於,絕對不可能「優於」手排二層。

也就是沒有經過第三手封裝,最原始的資料串流(Binary Stream)。

就「連線數」來說,
我指的是「常時而多數的兩層連線,會造成資料庫主機的反應變慢。」
而蕭大所提到的是中間層的執行緒處理,
就例子來看是對應到 DataSnap 中「Single Instance」的概念。

Cache 在 Delphi 2009 以前來說就有了,那時就有提到 MIDAS Connection Pool 的概念。
但必須搭配 COM / MTS 進行實作,在 Client 端也要採行 TDCOMConnection 的連線模式才能做到。

但 MTS 好實作、好部署、好控制嗎?
還有 DCOM 連線在跨網際網路下的安全性問題,好解決嗎?

於是它幾乎被放棄了。

====================================

「除了安全性,我看不出有太多理由用 DataSnap」

這應該也可以用在 Web Application 上頭,就目前我所知道的。
Web Application 在複雜應用上,用戶端的回饋反應也絕對不會太好。

現在 Web 應用也因為偏重 JavaScript 進行開發,所以 PHP / ASP 等泛中間層語言的操作就越來越簡化。

太繁瑣的處理的大都還是會丟給 Database 去處理,在 PHP / ASP 下做絕對快不了。

現在 Web 前端幾乎就要把 PHP / ASP 的工作做完了,泛中間層只需要橋接結果寫回資料庫。

而 JavaScript 現在幾乎是 Browser 的戰場,看看各大瀏覽器的發展,JavaScript 支援及處理是首推特色!

嗯……看似跛腳的中間層卻好像是未來的發展呢。(笑)

DataSnap 與其說是玩具車,我倒覺得像是概念車,但大多數的人會拿概念車做成玩具車。
可以做 Lexus 或 Luxgen;要做成 BMW 或 Benz 就要買泊來品(3rd);想打造瑪莎拉蒂或法拉利,這不在 DataSnap 的範籌內。

感謝蕭大百忙中提供 DataSnap 評析,有新的感想也歡迎繼續指教!

編輯記錄
GrandRURU 重新編輯於 2015-02-18 08:26:28, 註解 無‧
GrandRURU 重新編輯於 2015-02-18 08:59:29, 註解 無‧
sryang
尊榮會員


發表:39
回覆:762
積分:920
註冊:2002-06-27

發送簡訊給我
#4 引用回覆 回覆 發表時間:2015-02-18 09:40:18 IP:59.127.xxx.xxx 訂閱
我也來說一下連線數的問題

要賣錢的資料庫,比如 MSSQL,通常連線數是要算錢的,預算有限的狀況之下,連線數是有限的

而一般二層式的應用程式一旦連上資料庫,就不放手了

為什麼不放手?方便嘛!懶嘛!

使得有限的連線數被佔用,無法有效的利用

而三層式應用程式的資料庫連線是中間層使用,只要是「無狀態」的中間層,資料庫連線可以比二層式有更有效的運用

小弟淺見供大家參考
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#5 引用回覆 回覆 發表時間:2015-02-18 10:42:28 IP:111.243.xxx.xxx 訂閱
回覆我的好友ruru :

還是要再強調一下,我寫這評論不是針對你的文章,更不是針對你。我真心期待你把你的經驗一直寫下去,我都很認真的在看,在學。所以,這絕對不是吐槽串。更進一步的說,未來我有時間,也會寫對不同技術的一些看法,並且評論之,純當記錄與愉樂。

關於你說「連線數」的問題。我覺得我與你應該有部份是一樣的想法。但可能又有一小部份不一樣,或者因為說詞不同而誤會。你說:
「常時而多數的兩層連線,會造成資料庫主機的反應變慢。」
我覺得上面的句子應該是 「長時而多數的連線,會造成資料庫主機的反應變慢」。並非吹毛求疵。而是對db來說,連線就是連線,理當不強調二層或是三層的連線。而「長時且多數」(我想常是你打錯字吧?但這不是重點,除非你真的是常) 是個關鍵。照我個人的看法與說法,我會說「許多長時間閒置的連線,會造成db主機反應變慢」。或許你是一樣的看法,只是說詞略有不同。這個「許多」、 「長時間閒置」是關鍵,這就是我在後面要批論 datasnap是玩具的原因之一。待後面再回頭論述。

不過,我看不出來在 datasnap2009版前(以下都改寫datasnap02,表舊的,datasnap09表新的,數字是年份左右)不自己改裝的情形下,要如何讓這種情形變沒有。簡單講就是,在普通的datasnap寫法上,一個client端的請求,比如要一個table的資料,一定會有一個與db的連線。換句話說,是一個請求對一個db連線。我說過,除非有cache的能力,不然請求一次,就一定會有一條與db的連線 (不管是重連,或是新連,就是一條連線在上面)。於是,若 20 人的程式同時請求中間層,以datasnap02普通實作上來看,就是20條 db 連線。完全看不出連線數有少。我會這樣說,當然是基於一些觀察與研究。我在稍後會解構 datasnap02,就應該會清楚些。容我後面講到時,再回頭補充。

你提到:
「Cache 在 Delphi 2009 以前來說就有了,那時就有提到 MIDAS Connection Pool 的概念。
但必須搭配 COM / MTS 進行實作,在 Client 端也要採行 TDCOMConnection 的連線模式才能做到。」

以上完全與我這幾天的研究有相反到。我已確認的是,當使用TWebConnection時,才會有cache的能力,以下是書本的貼文:

Object Pooling
Finally, a Web connection can use object pooling. This feature enables the server to create a pool of multiple server instances for client requests. This way, the DataSnapServer doesn't use the resource for the remote data module and database connection unless it's actually needed.
Object pooling gives you the ability to set a maximum for the number of instances of the remote data module inside the DataSnap server application. Whenever a client request is received, the DataSnap server checks to see if a free remote data module exists in the pool. If not, it creates a remote data module instance (but never more than the specified maximum number of remote data module instances) or raises an exception with the message Server too busy. The remote data module, in its turn, services the client requests and duly waits for the next one. After a certain period of time without client requests, the remote data module is freed automatically (by the object pooling mechanism).

上面的話來自 Borland C BuilderT 6 Developer's Guide : 第21章。詳做法可參考它內文。

至於用com 來實作pool,我只能說依我對com元件的了解,當然是可以。但把 com 與 TRemoteDataModule 如何掛在一起,我是不清楚。這問題也是在後面我解構 datasanp之 com 元件架構會提到。到時候可以再反回來看。不過,稍早的文我就提到「部份的」有cache的能力。指的就是這個,只是想稍後再提之。

DCOM 連線在跨網際網路下的問題,我覺得最嚴重的不是安全性。是跨不了防火牆。防火不易過的情形下,什麼都很難談。這就是為何datasnap02 會有另外的二種選擇。HTTP 或 TCP 。我可以稍透露DCOM是基於 UDP 的。細節後面講。

最後,最最讓我感到有趣的是,你提到了現在的web application,把前端的javascript提的很高,許多的演算都在前端,而中間的反而變成了資料的轉運站之類的。我看到此,想法很多,真的也覺得這是很有趣的理解與想法。我覺得開心,因為有觸發。

你說的,與觀察的,真的很真。所以,我很想說,我們來發明一個叫「瘦中間」或「瘦中間件」的詞吧。(偷笑)。 在我解說三層架構裡,我提到軟體的「內涵可以大致上分3層」。但沒人說哪一層應該比較瘦。僅管過去大家認為應該要瘦客戶。但也沒說一定不能瘦中間。但我思考後的看法呢?

基於3層架構的情形下,win form程式,一般而言瘦中間是比較好。以會計軟體來說,若是加減等運算都要往中間去送,那麼軟體的反應肯定差,體感不佳! 除非該win form程式用到相當大的運算。

基於3層之web app,我覺得現在的程式人員,把客戶端搞肥真的很爛! 我曾使用舊的機器,比如win2000時代的配備,來看現在的網頁,只能說慘到機器掛點。這表示什麼? browser裡的程式太肥了! 即使是現在的pc,在開了10來個網頁的時候,也會發生主機發燒,記憶體不足的情形。這都是誰的錯? 馬的,就是 「肥客戶」的錯! 多數網頁又不是在搞網頁式商用app,比如會計web版。把pc搞熱,怎麼不去把 雲 的主機搞爛。難道能稱之為雲的主機比user的主機爛嗎? 「這種肥客戶」的網頁思維不可取! 換句話說,就是把 ajax 的技術爛用了。ajax 是為了某些時候網頁不能好 一直 refresh。一方面一直refresh體驗差一點,二方面因為http是無態的(注:我會深刻的在後面提什麼是有態/無態),所以後端程式為了讓畫面看起來好像沒refresh過,要做很多的處理。這些時候都是ajax使用的時機。但不是隨時連入後面要資料,然後在前面就搞起來了…

我不能要求寫web的人照我的想法寫,但我個人真的很厭惡那樣的爛用。網頁還是要 瘦客戶 才佳!

以上,都是基於一定要3層的情形。但許多的時候,若網路是封閉的,比如在企業內部,能用2層就用2層,絕對效能高。

評論datasnap是玩具這件事,絕不是基於到目前為止的一些說法而已。我感覺,好像要寫很多才能完全這評論。我依然覺得它就是玩具,連改裝都難,當然,說「無法」改裝是太過了。但說它先天很不足,極難改裝肯定是真的。所以它是 … 玩具。是不是玩具,可以在完結篇時再來重新探論吧!

經過你的回覆,我又吸收與思考了一些想法,謝謝! 也希望有不同想法的人可以提出看法。激發一下。


------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#6 引用回覆 回覆 發表時間:2015-02-18 11:06:13 IP:111.243.xxx.xxx 訂閱
真的很開心,好友 sryang也來論。這樣大家level更快昇等!  ^_^

你說的我認同。而關於「無態」中間層的連線可以比2層更有效。我的看法是:

因為無態,所以可以把資料集(dataset)重複使用,拿去給需要的人。並不需要再次向db要。

不知你的看法是這個意思嗎?

若是,我只能說,datasnap要如何達答這樣的任務? 「無態」可以。但 dataset重複使用? 在 datasnap02(指舊的)的做法,大致只有用 webconnection (即使用 isapi 配 iis )的情形下,配合 object pool (我上面有貼文)。但那不是預設就可以,要調整。另,在新的 datasnap09下,就容易一點點。用方法回傳資料集合…但不能選 per session,也不能是 invoke… 否則都無法「重複使用」。詳我會在後面談。

或是你有別的做法可以讓 dataset重複使用? 這方面你經驗足,我應該有不少沒想到的… 還請多多指教。

^^


------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
sryang
尊榮會員


發表:39
回覆:762
積分:920
註冊:2002-06-27

發送簡訊給我
#7 引用回覆 回覆 發表時間:2015-02-18 13:50:17 IP:59.127.xxx.xxx 訂閱
DataSet 重複使用是一個很大的課題,我說的並不是這一部份,而是讓有限的資源,也就是資料庫連線數得以充分利用,以舒緩很貴的連線數閒置浪費的問題

至於 cache 因為這個問題太大了,以我有限的腦筋解決不了這個問題的,所以還是讓 DBMS 來處理就好
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#8 引用回覆 回覆 發表時間:2015-02-18 13:56:30 IP:111.243.xxx.xxx 訂閱
 接下來要進入「深入淺出」「庖丁解牛」的時刻。評論一個技術,對我來說,當然不能只是簡單的一些概念、想法就來評,至少也要有一些深入的技術分析,這樣才能服眾!

「什麼是MIDAS ? 米達斯? DataSnap ? 資料快照? 資料快擷? 」

通通都只能一個包裝名詞而已啦,這樣比較嚇人,比較神祕,比較好賣?!

⓪ DataSnap 根子裡,它就是 RPC (Remote Process Call),或是用 OOP 的概念來說,它是 RMI (Remote Method Invocation)。

拆解了第一層後,不禁要問,那什麼又是 RPC/RMI ? 用白話一點的說法,就是在二個程序(process)間,由其中一個叫用另一個的物件方法。我們知道,在物件導向的實作上,我們會在類別裡實作許多的方法,並且在需要的時候初始化它 (create/new)。產生了一個instance後,就開始用它的一些方法。而RMI 所關注的問題是,要如何的在不同的process間叫別人的物件方法。當然,若是同一個process,沒什麼特別的,就new/create就用了。

RMI 的重點又是什麼? 喔,這可以用一點直覺來看待就理解。
1/ 要呼叫對方的哪個物件,哪個方法?
2/ 參數與回傳值要怎麼表達給對方的process知道?
3/ 當被叫的process幫忙產生物件後,要何時把物件殺死?
4/ 在呼叫端要如何使用遠端的物件?
5/ 用什麼方式傳遞過去?

這些問題,在windows上,就是 DCOM 的實作。跨平台就是CORBA的實作。於是,要了解 DataSnap的真面目,就要了解 RMI 他所處理的事務,也就是上面列出的5個點是怎麼實作的。

其次,在此先說明DataSnap02扣出SOAP外,另三種模式通通都是基於DCOM -- 它們是 :
1/ 原生 DCOM
2/ HTTP 包DCOM
3/ TCP 包DCOM

別以為 http 或是 tcp 就不是DCOM,通通都是。就好比香蕉禮盒被芭樂盒子包起來,表皮看是芭樂,打開後依然是香蕉。

更重要的是,DataSnap02 其實只用了RMI的一個小小的「一個」功能 -- 單純的傳請求的資料集。直到DataSnap09,才算是比較完整一點的RMI ,因為新的DataSnap很容易自訂型別,參數也比較多元一點。(註:完稿後發現,ruru也指出,DataSnap02也是可以自訂"有限制"的方法,方法的參數要是 com 元件的variant型別,詳參考 docwiki.embarcadero.com/RADStudio/XE7/en/Calling_Server_Interfaces )

直到你對RMI很了解後,你就很明白的知道為什麼server端要拉某些元件,而client端又要配合某些元件。這決對不是靠背的,是由理解中可知道為何需要某些元件。也因此才能夠正確地,發揮所長地操控所有物件,尤其是針對DataSnap09。

下一篇要細說DCOM,以助理解RMI。

未完待續…
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-18 14:05:53, 註解 無‧
aftcast 重新編輯於 2015-02-23 06:29:46, 註解 無‧
aftcast 重新編輯於 2015-02-23 06:31:00, 註解 無‧
aftcast 重新編輯於 2015-02-27 11:31:26, 註解 無‧
aftcast 重新編輯於 2015-02-27 11:32:26, 註解 無‧
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#9 引用回覆 回覆 發表時間:2015-02-18 19:59:29 IP:182.235.xxx.xxx 未訂閱
自訂方法!嘿嘿,DataSnap02 就已經出現囉!但我不明白蕭大所提的「改裝」之意?

就像 Sryang 大說的,要保留 TDataSet 等物件的重複使用,在 DataSnap 下,是無法採用的。
重複使用 DataSet,那一堆 Client 該如何更新?再來找 TDataSet Post?這……

DataSnap 頂多只能做到保留 Remote DataModule Instance,省下 Client 再次建立及初始化 RDM 所需要的時間。。

Object pooling 章節中所提到的,在 Delphi 和 C Builder 是以不一樣的方式實做,以 Delphi 來說:
可參考:三層應用 webconnection與httpsrvr.dll與IIS的有關的問題

再看 C Builder........... 只能說 CB 真的是小三的孩子,連實作都很硬。

再談 Cache 的結論:RDM HttpSrvr.dll TWebConnection 才能夠享受 Object pooling 的效果。

再到 Doc 裡查了一下,發現 RegisterPooled 開放的連接池,僅保留第一個 Instance ,後面的參數是設定 Client 連線數的上限。
和 COM Object Pooling 還是不太一樣。

又, Sryang 大所說「很貴的連線數閒置浪費」的問題,係指在中間層可設定同時連線的上限(剛好 RegisterPooled 就是做這個工作),這樣就可以規避 Client 數的版權費用!

DBMS 的 Cache,這個方法讚!

YA!可以看到蕭大來「庖丁解牛」了!這應該是 2015 年開始以來最精彩的時刻了!
編輯記錄
GrandRURU 重新編輯於 2015-02-19 01:14:53, 註解 無‧
GrandRURU 重新編輯於 2015-02-19 01:16:54, 註解 無‧
Jasonwong
版主


發表:49
回覆:931
積分:581
註冊:2006-10-27

發送簡訊給我
#10 引用回覆 回覆 發表時間:2015-02-19 14:07:47 IP:125.227.xxx.xxx 訂閱
 
— 連線數過多效能會變差的原因

假設一個連線數就是一個櫃台,今天有很多人來銀行提領現金。



3層架構有分有狀態及無狀態兩種模式,以下是以無狀態的模式講解






「孩子,別傻了」
------
聰明的人,喜歡猜心;雖然每次都猜對了,卻失去了自己的心
傻氣的人,喜歡給心;雖然每次都被笑了,卻得到了別人的心
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#11 引用回覆 回覆 發表時間:2015-02-21 08:06:43 IP:220.134.xxx.xxx 未訂閱
用「櫃台」的概念講解真是開大絕了(用櫃姐會更好!!!)

讚一個!

===================引 用 Jasonwong 文 章===================
... 43...





「孩子,別傻了」
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#12 引用回覆 回覆 發表時間:2015-02-22 20:45:30 IP:114.32.xxx.xxx 訂閱
感謝 jason 也來幫忙解說。三層的解釋加入了歴史的註解,更加清析了它的意義!

關於「連線數過多效能會變差的原因」(註:指客戶應用程式看到的效能,或是流量。這也是客戶最重視的! 若單對 db 本身來說,它忙不是重點,只要不是瞎忙)。我想做最後一次的個人見地解說,希望能把問題更見其真面目。

就我來看,「連線數過多」並不是根源,甚至不是一種"錯"。僅管"表象上來看",多數的情形下,連線過多效能會開始轉差。

什麼才是真的"錯"與根源? 「閒置過久」。因之前已經回覆ruru同樣的說法,但我覺得應該有必要再淺出一點。


假如一群人去賣當勞點餐。

櫃台很長,可以排10行的客戶。但很不幸的,事實上能服務的點餐人員就只有一人…

店長說:「我覺得若能儘量在"同時間"處理客戶的點餐,那應該可以很快的把所有人的餐都點完」。

於是請一群人排成10行,然後可憐的服務員就定時的移動腳步從第一排,到第二排,第…10排。輪詢問要點什麼。

經過一陣子後,店長看一看… 馬的,效果很差,於是就觀察,怎麼與他之前想的不一樣呢?

服務員在第一排問:「先生請問點什麼?」
先生 : 「………我看一下。」
服務員在第二排問:「先生請問點什麼?」
先生 : 「………我看一下。」
服務員在第三排問:「先生請問點什麼?」
先生 : 「………我看一下。」
服務員在第四排問:「先生請問點什麼?」
先生 : 「………我點1號餐。」
服務員在第四排問:「那飲料呢?」
先生 : 「………嗯? 熱的好? 還是…… 我想想…」
服務員在第五排問:「先生請問點什麼?」
先生 : 「………我看一下。」

‧‧‧‧‧
‧‧‧‧‧

服務員在第十排問:「先生請問點什麼?」
先生 : 「我要2號餐」
服務員在第十排問:「飲料呢?」
先生 : 「可樂」
服務員在第十排問:「要不要變大杯?」
先生 : 「……… 我想一下…」

服務員在第一排問:「先生想好了沒? 要點什麼」
先生 : 「………很難決定…讓我再看一下」
服務員:「#$#)dfe%」 (os 有這麼難嗎? 想那麼久…)

服務員在第二排問:「先生想好了沒? 要點什麼」






店長發現,小姐跑了十排,問了十人,結果一個餐點都沒完成。原來都是「他馬的,一堆事情沒想好、」造成小姐疲於奔命!

店長於是改了策略: 下令,櫃台只能有一排
服務員先生: 「……我想想…」
服務員:「挖鼻孔等他…」
服務員:「想好了沒?」
先生:「喔,那就1號餐」
服務員: 「飲料呢?… (繼續挖鼻孔等)」
先生: 「可樂,不要加大。也不要加購。順便給我ketchup。」
服務員: 「先生,你怎麼突然變好快? 」
先生: 「因為你在挖鼻孔,我要快點結束…」

換第二位客戶。
服務員:「先生請問要點什麼?」

換第三位客人。
服務員:「先生請問要拿什麼?」

先生 : 「要」

就在這時候,第四位客人以後的所有客人開始不爽,「我都想好點什麼了,怎麼他馬的那麼慢,我餓死了啦! …xxx



店長於是又覺得… 我看,這樣好了,就開個 「 馬的,怎麼只有4個櫃台,我們早就想好,要立刻點,她還在那裡跑來跑去的… 慢死了啦!」


這故事告訴我們,事情的真象就是:

1/「 閒置很久才要求一次,會殺死人!」。
2/「如果請求是一直來的,都不閒置,那就該讓他們一起上才快!


連線數多不是問題,甚至多才會讓效能好。但閒置很久的連線,只會讓系統瞎忙。如果閒置久且數量多,那只有罵fuxx!







希望有多一點人會用心的把故事看完並思考之。畢竟看似小故事,其實是經過無數思維後的結果。此外,這也是 socket 阻塞,多線等同樣的問題。
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-22 20:50:10, 註解 無‧
aftcast 重新編輯於 2015-02-22 21:24:50, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#13 引用回覆 回覆 發表時間:2015-02-22 21:14:32 IP:114.32.xxx.xxx 訂閱
 附上與 datasnap 無關的一個 蕭沖心法。  給有緣人,不花時間再多做解釋!

Number of concurrent Logins : L
Number of Concurrent Sessions : S
Number of Concurrent Requests : R
S = L;
R <= S
最大連線數 : M
最低永久連線數 (共用連線/Pool) :P
動態連線數: D
P D <= M
(多工) : 由於 Requests 小於等於 Sessions,故有MultiPlexing(多工)的可能性。
Pooling 通常是為了 MultiPlexing。也就是說Pooling裡的objects最佳是比Requests略大。


訣 :

三層攻短防長,然可借以下修練補強。



他登由他登,清風拂山崗;
他多任他多,明月照大江;
他自緩來自求,我自一池資源足!



二層攻長防短,易失真氣,可補:

一次要足入圍欄,去了連線自演算;
一朝更新需連線,完成任務又再斷。

演示圖:
//---------------------------------------------------------------------------
void __fastcall TForm14::FormShow(TObject *Sender)
{
this->_con1->Close(); // keepconnection = flase
}
//---------------------------------------------------------------------------
void __fastcall TForm14::_btn1Click(TObject *Sender)
{

this->ds1->Connection = this->_con1;
this->ds1->Connection = NULL;
ds1->SaveToFile("d:\\ds1.data"); // 你的公事包
}
//---------------------------------------------------------------------------
void __fastcall TForm14::_btn3Click(TObject *Sender)
{
//---------------------------------------------------------------------------


常云二層不可斷,虛耗內力傷元氣,實因一些是克捨,不如外道愛迪歐。

bj4
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#14 引用回覆 回覆 發表時間:2015-02-23 08:08:55 IP:114.32.xxx.xxx 訂閱
回 ruru 的留言:

1/ 關於指出 com 元件使用datansap的情形。我原本想修正之前的章,加入註解。但那篇很可能太長,無論如何都不給修正。只好重貼於此:

!first available remote data module in the pool. If there is no available remote data module, it creates a new one (up to a maximum number that you specify)。

另 cb 6 書的另一個說法:
Object pooling gives you the ability to set a maximum for the number of instances of the remote data module inside the DataSnap server application. Whenever a client request is received, the DataSnap server checks to see free remote data module exists in the pool. If not, it creates a remote data module instance (but never more than the specified maximum number of remote data module instances) or raises an exception with the message Server too busy.

所以應該是指 : 「當連線來的時候,會去找第一個可用的instance來用」。我想會強調「第一個」應該是說它的實作並沒有用 Round-Robin 等一些演算法分配,而是找到可用的,就馬上用它,就算上一次還是它。

4/ 不過僅管 http pooling 可以把 RDM 留於池中,但照我的研究與看法,它不能夠對 db connection 部份做特別的pooling。換句話說,若每個instances的db conn 都是open的,那就會一直保持open。下個呼叫用它時,它依然是open的(除非上回的那個讓它close了)。換句話說,http pooling設一個上限後,到頂時,user端叫用就會失敗。然而,com 的做法則真的會自動開開關關,它的行為一如 jason wong 的說法一樣,可以往上,但又可以保留一個固定值,參照他回我的最後面的部份。也就是說 com 是比較彈性的可高可低,也可保有最低水位。以上在 http pooling的部份說詞,主要來自官網的文件,因它沒特別強調可以db conn pooling。而com 則講了 object pooling,與 db conn pooling 二個。我不敢100%說我的說法是完全正確的,因為我沒去實驗。另 com 的db conn pooling功能其實是 db driver 完成的,李哥是用ado,官網說dbx也可以。但bde或新的firedac可能不行。

5/ 限制住 http 的pooling 數,雖可" 間接 "的控制住 db connections,但一但到設的頂,就會無法服務,跳出 server too busy。假始你沒有設定pooling,每個客戶連線請求會令RDM創出來,並且在客戶應用程式關閉時RDM會死亡。有沒有 pooling 只差在「省下 Client 再次建立及初始化 RDM 所需要的時間」,一如你所言! 進一步的推論出: 「這樣的pooling方式並不能使用 db connection 有效率的 pooling(多功)」於是它與一般二層架構上,20人就20個db連線沒什麼不同。」至於 clientdataset上如何去讓遠端的db connection開又關,關又開,其實我也不懂。你知道的,我多數是用ado,對它不熟。但 ado 二層是可以 開關、關開的,也就是它也可以不用一直保持20個連線。

6/ 一定要再拉回原主題,我們談到「三層解決二層多連線問題」這個議題,並不少人強調「多連線造成效能低」。這樣的結論,無非是強調「三層優於二層」。然而三層真的優於的,理當主要是「安全性」(不能直連它),至於效能? 連線數問題? 應該不是「為何要三層」的理由。李哥在com 的書上,第十章左右,有下了一個結論,筆數一但超過1000?(我不想再翻,記憶是)筆後,三層會比二層差到極大。真實的例子:
delphi.ktop.com.tw/board.php
11萬筆,3 秒 對 5分鐘,也就是差了 100 倍。就算他的架構可以再改進,但可想而知還是會差很大吧! (網友pedro我見過,有機會可以再問他) 。

7/ 講後那麼多的連線問題,無非是要提出 : 你何時需要三層? 我依然看法是,「在意安全」為主,「需大量運算為中間層」,「想提供給不同平台的客戶端都能吃」。不然可以試一下, ms sql 二層,用戶程式直連入 sql server (經 internet下,開1433 等 port) 真的不會比較快?,且也可以選項性的、技巧性的斷線再連。 另外,李哥在com 最後一章提到有人問他用了三層的dataset後,他的quick report變超級慢,要如何pooling? 李哥說,怎麼pooling都改善不大,最後是要它寫 stored procedure,於是差了10多倍。我提這個是因為,我說 db 是第三層,但很多時候沒有發揮每一層的能力。比如 db 若不寫 stored procedure,就算是有一點小對不起二/三層架構。把一堆 dataset 拉到第一層然後再演算處理,不如在 db 上就演算差不多後,把結果拉回來。喔,上面算是題外話,突想到的。

8/ 最後,關於 dataset 的cache問題。我想主因是 tsqldataset 根本是一個 cursor 。它應該要叫 TSqlCursor 才符合他的真實本位。它只是一個指標,指向 db server上的某一筆,要的時候往下取一筆下來。伺服器若是沒連線, 指標不就 野 了。我天馬行空的想,若中間層用的是 adodataset (靜態),並不要取用狀態相關的東西,那麼在 instance pooling 的情形下,該 dataset 是真的有筆數的,下一個user連入並用這個instance的時候,理當不需再向 sql server 要資料! 因為 adodataset裡就有資料。這就有 cache的作用。不過… 當然… 極不完美。因為不同 instance下的 ado dataset 內容可能不一樣。這些東西除非自己實作 (就是我說的改裝之類的)。算了,煩又不易。


我說的「改裝」就是說,若照原廠的使用方式會有不少的缺點。若能夠修改源碼,或是自己加入不少的東西補強,就算是改裝。一如玩具手槍有機會改成土制手槍… 但前題是,玩具手槍本身就很接近真實的手槍,總不能是塑膠玩具手槍然後試著要改成真實手槍吧?


回完這篇,下回要進入 dcom 的解說。同時間,再次強調,關於 datasnap 我是學理為主,可以說沒有實作(一二次不算),還是期待 ruru 發表所有他的經驗與實戰。我的文章僅 參考、愉樂 就可以! ^ ^


===================引 用 GrandRURU 文 章===================
自訂方法!嘿嘿,DataSnap02 就已經出現囉!但我不明白蕭大所提的「改裝」之意?

就像 Sryang 大說的,要保留 TDataSet 等物件的重複使用,在 DataSnap 下,是無法採用的。
重複使用 DataSet,那一堆 Client 該如何更新?再來找 TDataSet Post?這……

DataSnap 頂多只能做到保留 Remote DataModule Instance,省下 Client 再次建立及初始化 RDM 所需要的時間。。

Object pooling 章節中所提到的,在 Delphi 和 C Builder 是以不一樣的方式實做,以 Delphi 來說:
可參考:三層應用 webconnection與httpsrvr.dll與IIS的有關的問題

再看 C Builder........... 只能說 CB 真的是小三的孩子,連實作都很硬。

再談 Cache 的結論:RDM HttpSrvr.dll TWebConnection 才能夠享受 Object pooling 的效果。

再到 Doc 裡查了一下,發現 RegisterPooled 開放的連接池,僅保留第一個 Instance ,後面的參數是設定 Client 連線數的上限。
和 COM Object Pooling 還是不太一樣。

又, Sryang 大所說「很貴的連線數閒置浪費」的問題,係指在中間層可設定同時連線的上限(剛好 RegisterPooled 就是做這個工作),這樣就可以規避 Client 數的版權費用!

DBMS 的 Cache,這個方法讚!

YA!可以看到蕭大來「庖丁解牛」了!這應該是 2015 年開始以來最精彩的時刻了!U
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-23 08:12:17, 註解 無‧
sryang
尊榮會員


發表:39
回覆:762
積分:920
註冊:2002-06-27

發送簡訊給我
#15 引用回覆 回覆 發表時間:2015-02-23 14:25:11 IP:59.127.xxx.xxx 訂閱
要使用 COM+ ,用 RemoteDataModule 還是不方便的,應該用 MTSDataModule / TransactionalDataModule

以以前個人實做的記憶 (10 多年前了,那時還用的是 D5),COM 有幾個好處

1. Object pooling,可以預先建立某個數量的物件等著被使用,若用戶數多於這個數量,則會再建立新的物件以應付需求,而最低會維持這個數量的物件,也可以設定物件消滅之前的閒置等待時間

2. 交易控制以及 two-phase commit,可以用 COM 設定來控制是否要參加交易,是否一定要起新的交易,以及交易隔離等級。而寫程式時就不用再自己控制資料庫交易了,只要記得成功了要呼叫 SetComplete,失敗了要呼叫 SetAbort。(SetComplete 與 SetAbort 是 MTSDataModule / TransactionalDataModule 提供的方法)

3. db connection pooling 確實是 database driver 提供的 (其實 ODBC 就內建這個功能)

以上是憑記憶寫的,不見得正確,望各位先進指導。
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#16 引用回覆 回覆 發表時間:2015-02-23 15:20:07 IP:220.134.xxx.xxx 未訂閱
Http Pooling 在這邊更明確的說法,應是 RDMPool(Remote DataModule Pool):

For HttpSrvr.dll 中關於 Object Pooling 這一部分
是我理解錯誤,「找出第一個可用的 RDM Instance」才是對的。

RegisterPooled,
表示 RDM 會在一開始就把 Instance 開到指定的數量,
不過設定大於 IIS 能夠接續的上限還是無用。

COM+的 Object Pooling 有它自己的一套規則。

Database Pooling
從 .NET 是有個 .NET 應用程式集區這部分來看
在 Delphi 中應是依存在 AP 當中(可參閱Delphi DBX4 programming

如果 AP 被消滅,則 Database Pooling 則同時消滅。
我認為它的存在感並不高。
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#17 引用回覆 回覆 發表時間:2015-02-23 17:30:29 IP:114.32.xxx.xxx 訂閱
Sryang,謝謝你的實戰確認。 你就是先進啦,沒什麼人敢指導你。

至於我上面貼RemoteDataModule 用在com 上,是因當時不知 com 上可以使用這樣的東西。誠然,事後了解後,理當是你說的TSDataModule / TransactionalDataModule。因他們也實作了 IAppServer 介面,故可與ClientDataSet 相通。 ;)

 
===================引 用 sryang 文 章===================
要使用 COM ,用 RemoteDataModule 還是不方便的,應該用 MTSDataModule / TransactionalDataModule

以以前個人實做的記憶 (10 多年前了,那時還用的是 D5),COM 有幾個好處

1. Object pooling,可以預先建立某個數量的物件等著被使用,若用戶數多於這個數量,則會再建立新的物件以應付需求,而最低會維持這個數量的物件,也可以設定物件消滅之前的閒置等待時間

2. 交易控制以及 two-phase commit,可以用 COM 設定來控制是否要參加交易,是否一定要起新的交易,以及交易隔離等級。而寫程式時就不用再自己控制資料庫交易了,只要記得成功了要呼叫 SetComplete,失敗了要呼叫 SetAbort。(SetComplete 與 SetAbort 是 MTSDataModule / TransactionalDataModule 提供的方法)

3. db connection pooling 確實是 database driver 提供的 (其實 ODBC 就內建這個功能)

以上是憑記憶寫的,不見得正確,望各位先進指導。
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#18 引用回覆 回覆 發表時間:2015-02-23 17:31:42 IP:114.32.xxx.xxx 訂閱
 下面這張圖是從microsoft網站借來的。關於DCOM的方法傳遞。
https://msdn.microsoft.com/en-us/library/windows/desktop/ms692621(v=vs.85).aspx?f=255&MSPPError=-2147217396
原預計要講細一點,但感覺過度技術細節會讓人想睡,而且我也要寫很多。於是改變策略,以評論為重點,而不是變成技術專書。若有需要了解深入的,再與我討論。
上一篇我提到:
RMI 的重點又是什麼? 喔,這可以用一點直覺來看待就理解。
1/ 要呼叫對方的哪個物件,哪個方法?
2/ 參數與回傳值要怎麼表達給對方的process知道?
3/ 當被叫的process幫忙產生物件後,要何時把物件殺死?
4/ 在呼叫端要如何使用遠端的物件?
5/ 用什麼方式傳遞過去?
這5個點的答案就在那圖裡。
比如被叫端(server side)有個物件叫 foo,裡面有個方法 string echo(String); 那麼對應到上述的5個點分別是:
1/ 怎麼知道有個方法叫 echo ?
2/ string 字串的資料如何序列化給server side?
3/ 在server side 上的 foo 何時被殺死?
4/ 本地(客戶端)沒有一個類別叫foo的,我要怎麼使用foo.echo('Test')? 在編譯時應該會錯。
5/ 我要用什麼通訊方式,比如tcp或udp等等的來叫伺服端?
再次回到圖的內容。請看
⓪ proxy: 它就是1,2,4的答案。它的目的就是代表遠端的foo類別,但名稱不一定要叫foo,可以叫bar。但可以透過它來對應遠端的foo。在delphi2009後,datasnap09,你比較明顯的可以感覺它的存在。因為在建立 datasnap client端的程式時,範例都會要你把寫好的 datasnap server 跑起來,然後在client端的connection上右鍵,然後把 server 上的方法給傳過來,進一步的就是你的proxy類別就準備好了,你不必寫。然而,在datasnap02的時代,proxy是誰? 就是TClientDataSet。而它就代表了遠端的另一個物件TDataSetProvider。因為datasnap02的主要目的是一個,就是把資料集合傳到客戶端,所以定死了,因此也不用你自己依情形來寫proxy的部份。這就是 datasnap02 與 datasnap09 在應用上,你會覺得怎麼 09 的要多處理這件事! 更深入的說,其實09也可以產生 javascript 的proxy,給網頁前端用。也可以產生別的平台的proxy。proxy就是一個空的代理人,有方法與介面,並知道依不同平台語言等可以marshal出所要的流,進而送出到伺服器上。
⓪ stub: 代表的就是遠端的解讀物件,在02時是TDataSetProvider,在09時是DSServerClass。它的存在是對應著proxy的存在。它負責把封包(方法與參數都被proxy給序列化了,比如說'ABC'這個字串可能變成了 414243的二進位等)解開,然後叫用真的實作,比如foo物件或是TSQLDataSet。
⓪ RPC Runtime : 這個很重要,比如說你要叫用 foo.echo('Test'),通常為了安裝性,還有參數的變化,很少直接就把那段字串傳給了遠端的stub。而是會將它格式化。在DCOM下,格式化的標準就是 MSRPC 封包格式(http://en.wikipedia.org/wiki/Microsoft_RPC),而字串也會被proxy物件給marshalling過(可略稱它叫序列化),故也不會是foo.echo('Test')這樣的型式,而是某種二進位的樣子,規則則是 com 元件marshalling的規則。
說到這裡,RPC的marshalling與封包格式非常的重要,一來是安全,二來是速度。我要說datasnap09是玩具的原因之一也是這點。
⓪ Channel: 對應了問題5。就是封包如何傳去給對方。原DCOM是 udp 的方式,udp的缺點就是它是點對點的,所以若有防火牆在,要開一整片的ports,才不會擋住。於是datasnap才會改用http時是tcp來傳。把封裝好的內容,交給http或是tcp的方式就容易穿過防火,因為你只要開一個port就可以。但http或tcp的方式,封包裡依然是 com,它在 iis下的 httpsrvr.dll就會把它解開,脫去http。而若是tcp的方式,則是在scktsrvr.exe的接收下來解開。最終 httpsrvr 或是 scktsrver 都是叫用了DCOM元件。httpsrvr 或是 scktsrver 這二個模組都是想拌演 com 這種container application的角色。居中協調 com 元件的生成。若要深入研究,可讀scktsrver的源碼。這個channel觀念對應了:TSocketConnection / TWebConnection 或 TSQLConnection。
至於問題3,DCOM 的生命週期:
在 datasnap02時,
甲/ 若採 原DCOM的通訊方式,則是clinet端叫它時,它就建,client端不要他時,它在伺服器上就會死。若網路不好,伺服端沒收到要殺,那麼這個DCOM可能就會卡死在內存中……這ruru好像有提到。
乙/ 若採 http 或 tcp,若不使用pooling模式,一如上面。若採 pooling模式,即使client不用叫它死,com元件是不會真的消失的,它會在池裡。且一但設了活的時間,就可以確保不會因為網路不好,留下一堆不死之com。
在datasnap09時,可於 TDSServerClass 的屬性 LifeCycle 設定
甲/ Invocation: 簡單說就是叫一次 echo方法,就產生一次foo物件。叫完echo後,foo跟著死。再叫foo的bar,foo又再被產生,bar結束,foo再死。肯定的這要用在沒有狀態的情形下。(對於狀態,我很想仔細的說,但一時沒心情細說啦。我很肯許多人對這觀念不是很正確、深入的理解,僅一知半解)。
乙/ Server: 就是datasnap第一起run起來時,foo就被產出了。未來所有的客戶,都重複用它。比如叫echo後,foo還是不會死。別人在叫bar方法,foo依然不死… 直到datasnap程式結束。很肯定的,這也是要儘量無態。
丙/ Session (預設值): 就是每一個連線下,foo只會被產生一次,但該客戶連線結束後,foo就死了。若同時間有3個客戶連線,datasnap的記憶體是有3個foo的。這個設定當然是 有態、無態 隨你爽。
再次強調一下,若你真懂有態或無態,你應該會知道其實不管哪一種生命週期,你都可以把有態略成無態,把無態加注變有態。有態與無態,是以伺服器端來看待。server可以是有態,但將之用為無態。server也可以是無態的,加入程式碼讓它變成有態的… (以上若不清楚,就暫略過吧)
末了,我就把上述的觀念用來解說為什麼 datasnap09在使用Remote Datamodule時需要「多一個DSProviderConnection」。因為… datasnap09的marshalling與RPC封包與datasnap02時不一樣了。所以 DSProviderConnection 是一個轉換器,讓ClientDataSet輸出的格式被DSProviderConnection吃下後,轉出沒有marshaling的格式。你可以把它想成是把原TDSConnction DataSetProvider(負責逆轉的)。經DSProviderConnection逆轉成"正"後馬上又再轉成新的marshaling格式。
ClientDataSet <--Marshal A -> ProviderDataset <- Unmarshal A <-> TDataset
ClientDataSet <--Marshal A -> DSProviderConnection <- Marshal B -> TSQLConnection <--Unmarshal B->DSServerClass <-> ProviderDataSet
所以在新版裡使用ClientDataSet似乎是會慢更多!! 還有,你知道 Marshal B 是啥? 看了真是怕! 下篇講。
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#19 引用回覆 回覆 發表時間:2015-02-24 03:24:37 IP:114.32.xxx.xxx 訂閱




RPC Protol: MS RPC
Transport: UDP/TCP(scktsrver.exe)/HTTP(httpsrvr.dll)
⓪ DataSnap09:
Marshaling: ASCII binary based
Indy based)
上一篇講解了RPC/RMI的觀念,就是為了要引出新的DataSnap09可怕在哪裡。必竟,過去的datasnap應該用的人會愈來愈少。主要理當是新的DataSnap。




1. DataSanp Server
3. DataSanp Web Broker
照我目前的觀察,3樣都是








以上,只是client與DataSanp Server間互動的一小部份。重點在它是 JSON-RPC。以上若換成DataSnap02,那就是一堆的2進位封包。

(註: 文章儘量變小,篇數多,不然若事後要修正可能又會不行,sorry)
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-24 03:26:02, 註解 無‧
aftcast 重新編輯於 2015-02-24 03:27:53, 註解 無‧
aftcast 重新編輯於 2015-02-25 18:07:05, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#20 引用回覆 回覆 發表時間:2015-02-24 03:46:39 IP:114.32.xxx.xxx 訂閱
 上篇我提到DataSanp09是基於JSON-RPC,並非REST。那你是否會說,
1. DataSanp Server
2. DataSnap Rest Server
3. DataSanp Web Broker
其中的2不就是 REST? 是。但就好比我之前說datasnap02時,當時的http/tcp 也是把com元件包住,說穿了,打開後依然是com。同理,選項 2 只是將JSON-RPC的 Calling方式由URI來取代,如:
http://localhost/datasnap/rest/TServerMethods1/ReverseString/12345678
。但回傳值的部份,依然是JSON-RPC的一部份。REST 本身又是個有趣的題目。在此我不多說,最快方式可以 (zh.wikipedia.org/wiki/REST) 。
PUT /datasnap/tunnel?dss=27854.786997.434231&c=386 HTTP/1.1
Content-Length: 386
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"connect","params":[{"DriverUnit":"Data.DBXDataSnap","HostName":"192.168.1.111","Port":"8080","CommunicationProtocol":"http","DatasnapContext":"datasnap/","DriverAssemblyLoader":"Borland.Data.TDBXClientDriverLoader,Borland.Data.DbxClientDriver,Version=21.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b","Filters":"{}","DriverName":"DataSnap","UNLICENSED_DRIVERS":"0"}]}
PUT /datasnap/tunnel?dss=27854.786997.434231&c=82 HTTP/1.1
Content-Length: 82
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"execute","params":[{"fields":[-1,false,"Dbx.MetaData","GetDatabase"]}]}
GET /datasnap/tunnel?dss=27854.786997.434231&c=32768 HTTP/1.1
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
PUT /datasnap/tunnel?dss=27854.786997.434231&c=93 HTTP/1.1
Content-Length: 93
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"prepare","params":[-1,false,"DataSnap.ServerMethod","TServerMethods1.EchoString"]}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 39
Server: DatasnapHTTPService/2011
{"response":["27854.786997.434231",93]}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 131
Server: DatasnapHTTPService/2011
{"result":[{"handle":[1]},{"fields":[0,false]},{"parameters":[2,[26,0,1,0,0,"value",0,0,0],[26,0,4,0,0,"ReturnParameter",0,0,0]]}]}
PUT /datasnap/tunnel?dss=27854.786997.434231&c=66 HTTP/1.1
Content-Length: 66
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"execute","params":[{"handle":[1]},{"data":[6,$A123]}]}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 39
Server: DatasnapHTTPService/2011
{"response":["27854.786997.434231",66]}
GET /datasnap/tunnel?dss=27854.786997.434231&c=32768 HTTP/1.1
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 45
Server: DatasnapHTTPService/2011
{"result":[{"rows":[0]},{"data":[6,$A123]}]}
PUT /datasnap/tunnel?dss=27854.786997.434231&c=39 HTTP/1.1
Content-Length: 39
Host: 192.168.1.111:8080
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"command_close","params":[1]}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 39
Server: DatasnapHTTPService/2011
{"response":["27854.786997.434231",39]}
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#21 引用回覆 回覆 發表時間:2015-02-24 03:59:44 IP:114.32.xxx.xxx 訂閱





另值得注意的是:

上面那句話是怪怪的,因為webbroker本來就會有iis/apache模組。但我要說的是…假如indy很強,那DataSnap REST不一定要用IIS/Apache模組來理。但是EMBT喜歡一魚多吃,我想…他們讓webbroker再用,一來可以讓過去的webbroker專案可以擴充,二來可以讓你擺脫indy,然後也可宣稱…若覺得indy不夠強,就用webbroker的方式吧!






以上是我自己推論他的想法)。
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-25 16:12:59, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#22 引用回覆 回覆 發表時間:2015-02-24 04:47:32 IP:114.32.xxx.xxx 訂閱
 因新年過了,時間有限,講完了以上的技術後,快快的要來評論了!(目標是論三層架構,不討論 3 vs 2)
⓪ DataSnap02的部份:
若採用 com 方式來實作是最佳的。不能說它是玩具。整體來說,從RPC的protocol,marshalling機制,pooling機制,可以說是大型企業級可採用的方式。
若比較 http 或 tcp 的方式,則http應該比較優,因為 IIS 護著它,什麼是護著? 就是 IIS 是一個多工處理強的server application。躲在它後面,認真處理com元件就好,不用管大量登入等問題。
至於 tcp 的方式,經scktsrver.exe來協調…它…還可以而已,也沒用到IOCP的socket模式。
那原生DCOM直接呼叫呢? 如果不經過internet,其實也ok,效能我覺得也可以很好,沒有http/tcp的再包裝。
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-27 11:28:37, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#23 引用回覆 回覆 發表時間:2015-02-24 04:57:39 IP:114.32.xxx.xxx 訂閱


缺點:




由此,你是不是也可以猜出為什麼李哥在他付費課程中會說用REST 或 Webbroker才是商業模式。因為……Apache與IIS擋在前面啦! 那二個server,無庸置疑可以承受比indy更高的壓力!


舉例:


由於函式庫效能差,進一步就是造成DataSnap效能變差,因為它是基於JSON-RPC。

未完…待續… 寫累了…
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-24 05:06:18, 註解 無‧
aftcast 重新編輯於 2015-02-24 05:06:46, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#24 引用回覆 回覆 發表時間:2015-02-25 09:16:06 IP:114.32.xxx.xxx 訂閱
 感覺我似乎還是要再補一些資料對大家可能更有一點幫助。所以本來想再一篇就要來結束的,還是再拖台一下…  反正我研究都研究了,不給大家參考藏身上也沒意思啦!

先來三張我自製的圖,是關於 DataSnap09 的三種精靈。僅管 ruru 也曾問過李維大哥,但我覺得那樣不夠清析,所以補上我的圖來說明。

「DataSnap Server 是提供一般應用程式形態的DataSnap伺機器,
DataSnap Rest Appliation是為DataSnap伺機器加入REST的功能, 可執行在Web Server中
DataSnap WebBroker Application是為WebBroker技術加入DataSnap功能,
主要目的應該是為了把早期WebBroker或是Internet Express應用程式昇級成DataSnap伺機器」

以上是李哥過去回覆ruru的。感覺還是有一些模糊,而且…你們知道的…李哥在那個職位上,不好像我這樣亂說話,更不用說負面性的評論自家產品。我就沒有人在江湖身不由己的太多問題。所以嘛,才敢寫「無責任」技評。

DataSnap Server

DataSnap WebBroker
DataSnap REST
請注意,以上是我個人研究的看法,不是官網記載或說明的。所以有可能有錯誤! 加上我才花短短數日研究, 僅供參考。若有錯誤,也請指正!
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#25 引用回覆 回覆 發表時間:2015-02-25 18:18:08 IP:114.32.xxx.xxx 訂閱







73 22 3a 5b 30 5d 7d 2c 7b 22 64 61 74 61 22 3a s":[0]},{"data":
c0 25 ff

85 6d 55 86





從上面的一個對照表,你會發現base64就是用了64個字元。其中沒有那些括號等。所以不會和json的有意義的符號產生錯亂。
Delphi一定把它的JSON libray做了一些調整! 進而導致它的json lib 在一般的情形下無法快起來。
我們已經猜測了,它用 長度 來控制後面的"直寫二進位"。換句話說,這樣的解析器 json parser,必然性的要 「從頭至尾的解析」不能夠 「跳著解」。
什麼意思呢? 你想想,若你是寫JSON 解析器的人,你如何解出每個值? 以下面的例子來說:
{"result":[{"rows":[0]},{"data":[6,$ABCD]}, {"test":[1,2]}]}
若熟regular express的人,可能會說,找那些(大、中)括號,然後做為一個切割之類的。



這樣的marshalling做法,是讓json裡的字串或二進位可以直寫,進而json的size變小。但也讓它變成了非準標,且在處理正常的json解析時,犧牲了快速解析能力。另有副作用是,一般的別的程式語言或別人實作的json解析器會解錯。

⓪ 以上是針對 JSON RPC 裡的JSON格式的做法。換句話說,DataSnap有將JSON RPC的東西做變種,讓它承載二進位,並配合自家的json解析器。



使用REST的DataSnap效能會高很多! 也就是說,使用變種JSON RPC的DataSnap,會將傳輸內容變小,效能增高!

未完…待續… (都沒人頂…唉…)
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-25 18:20:50, 註解 無‧
aftcast 重新編輯於 2015-02-25 18:22:13, 註解 無‧
aftcast 重新編輯於 2015-02-25 18:22:51, 註解 無‧
aftcast 重新編輯於 2015-02-25 18:26:01, 註解 無‧
aftcast 重新編輯於 2015-02-25 18:32:12, 註解 無‧
JamesJuan
中階會員


發表:2
回覆:76
積分:80
註冊:2003-04-08

發送簡訊給我
#26 引用回覆 回覆 發表時間:2015-02-25 18:26:18 IP:59.127.xxx.xxx 未訂閱
大頂!大頂!
herbert2
尊榮會員


發表:58
回覆:640
積分:894
註冊:2004-04-16

發送簡訊給我
#27 引用回覆 回覆 發表時間:2015-02-25 20:25:59 IP:202.39.xxx.xxx 訂閱
頂!那能不頂!
您的大作,從不敢不看,只是門外漢總是插不上嘴!

不過鑑於瀏覽器與 K.Top 相容問題,若有長篇,最好先打好存在 HD 上,
再貼到 K.Top,可減少被沒收的遺憾!
sryang
尊榮會員


發表:39
回覆:762
積分:920
註冊:2002-06-27

發送簡訊給我
#28 引用回覆 回覆 發表時間:2015-02-26 08:30:13 IP:59.127.xxx.xxx 訂閱
頂!
強烈要求置頂加精!
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#29 引用回覆 回覆 發表時間:2015-02-27 10:28:40 IP:182.235.xxx.xxx 未訂閱
REST 指的是一種設計風格

DataSnap09 是基於JSON-RPC 協定,DataSnap09 整體實作的內容應算是 RESTful

但說它不是 REST 是有點牽強

因為它的確是採用 REST 的設計風格

以上
編輯記錄
GrandRURU 重新編輯於 2015-02-27 10:30:38, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#30 引用回覆 回覆 發表時間:2015-02-27 11:49:57 IP:59.115.xxx.xxx 訂閱
純討論:

你說「DataSnap09 整體實作的內容應算是 RESTful」。

我想若我們有看法上的出入,那應該是 REST 架構是什麼?

至於RESTful這個詞,只是表達「符合REST架構所製定的限制條件」就算該系統或所提供之API是RESTful。

我想聽想你對REST的看法。如何是符合REST架構。 交流交流。然後我自己順便重新思維一下自己原來的觀點 。 :)


===================引 用 GrandRURU 文 章===================
REST 指的是一種設計風格

DataSnap09 是基於JSON-RPC 協定,DataSnap09 整體實作的內容應算是 RESTful

但說它不是 REST 是有點牽強

因為它的確是採用 REST 的設計風格

以上
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#31 引用回覆 回覆 發表時間:2015-02-27 14:02:34 IP:59.115.xxx.xxx 訂閱
Content-Length: 386
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/3.0 (compatible; Indy Library)
{"method":"connect","params":[{"DriverUnit":"Data.DBXDataSnap","HostName":…{}","DriverName":"DataSnap","UNLICENSED_DRIVERS":"0"}]}
PUT /datasnap/tunnel?dss=27854.786997.434231&c=82 HTTP/1.1
Host: 192.168.1.111:8080
Accept-Encoding: identity

後記: 其實我經常在想,名詞原本不是最重要的東西,內涵最重要,你要說它是a,說它是b,都ok。唯在人與人溝通之間,會有所誤會。如果說你講 DataSnap09 是 REST/RESTful 我的第一個直覺是 : 「這樣的東西一定不維持狀態,也不會維持狀態,因為它表明說它是RESTful !,是合與REST架構的限制」。


若有其他人有不同的看法,也可以發表見解。多方討論與吸收。 謝謝!

註: 我再補一下 REST 這個詞,多數國內外的看法都是指 web service上的,因為實作它且用最多的大概基於web上,故講REST時,經常會認定是用 http URI 呼叫方式,配合GET/PUT/POST/DELETE。 RESTful 這個詞就廣一點,理當是符合REST架構的任意軟體服務都可以稱之,不限是http。重點都在 REST提出的限制 與 實作後能產生的優勢。

------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2015-02-27 14:06:21, 註解 無‧
aftcast 重新編輯於 2015-02-27 14:15:01, 註解 無‧
aftcast 重新編輯於 2015-02-27 14:24:13, 註解 無‧
[<<] [1] [2] [3] [>>]
系統時間:2024-03-28 17:20:30
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!