Quantcast
Channel: 黑暗執行緒
Viewing all 2433 articles
Browse latest View live

ASP傳奇系列-在Windows x64註冊WSC

$
0
0

話說公司的ASP不死鳥正努力在Windows 2012R2 x64展開新生活(參考:Windows 2012 R2 x64執行ASP經驗分享),接獲通報,不死鳥因水土不服烙賽,提起藥箱前往探視。

問題出現在某段使用OracleInProcServer.XOraSession又使用自訂WSC元件的VBScript程式:


Set objOra = CreateObject("OracleInProcSession.XOraSession")
Set objWsc = CreateObject("MyComponent.wsc")

copyText();

遇到的狀況是:從檔案總管直接點擊該vbs會產生ActiveX component can't create object: 'OracleInProcServer.XOraSession'錯誤;若是開Command視窗跑%SystemRoot%\SysWOW64\cscript執行該Script則會冒出ActiveX component can't create object: 'MyComponent.wsc'

註:OracleInProcServer.XOraSession是Oracle古老程式庫(OO4O)中用來連資料庫的ActiveX元件,WSC則是Windows Script Component,一種把WScript包成ActiveX元件的技術。如果讀到這邊,你對以上術語一頭霧水也沒什麼關係,請安心關上網頁出去逛街踏青跑步騎車唱歌看電影都行,不要浪費美好生命讀下去,純大部分的人此生不會再跟它們有交集。

好了,願意繼續讀到這段的同學,想必都是「ASP不死鳥親衛隊」的成員(不管自願還是非自願),我整理了在Windows x64平台處理WSC註冊的注意事項:

  1. 很多程式有兩份,不要搞混
    在Widows目錄下,除了System32,還有一個SysWOW64,底下都有regsvr32.exe、wscript.exe、cscript.exe、scrobj.dll這幾個檔案。區別在於System32下放的是64位元版regsvr32、cscript、wscript及scrobj.dll;而SysWOW64放的則是32位元版regsvr32、cscript、wscript及scrobj.dll。
    VBScript可以跑64位元,也可以跑32位元,視你用哪個版本的wscript或cscript執行決定。
  2. 在檔案總管直接點擊執行.vbs會跑64位元
    預設Windows x64會用64位元wscript.exe跑vbs檔,而OracleInProcServer.XOraSession為32位元件,VBScript跑64位元時會找不到。改用%SystemRoot%\cscript Test.vbs才會跑32位元,但認得了OO4O,卻又不認得MyComponent.wsc,底層原因則是WSC被註冊成64位元。這就是本案例遇到的狀況。
  3. 神奇的Regsvr32
    理論上註冊32位元dll要用SysWOW64\regsvr32.exe,註冊64位元dll要用System32\regsvr32.exe,後者明明是64位元,檔名硬是也加了32有詐欺之嫌!但後來發現微軟在兩個版本的regsvr32.exe加了魔法,當用32位元regsvr32註冊64位元的dll,它會自動叫出64位元版regsvr32接手,反之亦然(參考), 故regsvr32.exe與被註冊元件的32/64位元不匹配也不致出錯。看在它這麼聰明的份上,64位元程式檔名亂加32的事我們就不計較了 XD
    (話說,System32放64位元程式,SysWOW64才是32位元,這點更雷。自己搞過一些向前相容的骯髒活兒,倒也能體諒Windows架構設計者的無奈。)
  4. 將WSC註冊到正確版本的Runtime版本
    前面提到,Windows Script Component Runtime(scrobj.dll)也有區分32及64版本,故視你的WSC元件要執行在32還是64環境,必須註冊到正確的版本。
    32位元註冊及解除註冊:參考
    regsvr32 /i:"X:\Path\MyComponent.wsc" %SystemRoot%\SysWOW64\scrobj.dll
    regsvr32 /u /n /i:"X:\Path\MyComponent.wsc" %SystemRoot%\SysWOW64\scrobj.dll
    64位元註冊及解除註冊:
    regsvr32 /i:"X:\Path\MyComponent.wsc" %SystemRoot%\System\scrobj.dll
    regsvr32 /u /n /i:"X:\Path\MyComponent.wsc" %SystemRoot%\System\scrobj.dll

Excel CSV輔助工具強化版-支援換行符號

$
0
0

避免Excel開啟CSV時截掉左補零的小工具是我三年前的作品,用來克服Excel開啟CSV時"00001"會變成"1"的問題。最近網友g提供了一個轉換失敗案例,引發我的興趣,檢查CSV後發現幾項問題:

  1. CSV內含日文,使用Shift-JIS編碼(ANSI)而非UTF8,當初將所有ANSI檔案視為BIG5,形成亂碼
  2. 部分欄位內容夾帶換行符號(如黃底所示),擾亂原本以"\r\n"分隔資料列的解析邏輯
  3. 程式未考慮CSV部分欄位自帶雙引號的情況,造成雙引號重複(=""…"")。

用小工具轉換開啟會變成這副德行…

編碼問題好解,但CSV欄位內真的可以夾換行符號嗎?

實測發現,問題CSV若用Google試算表開啟,沒有亂碼,換行符號正確,但跟Excel一樣,第一欄020的前導零被刪去,仍不算完義的解決方案。

Google試算表解析成功帶來一些信心,足以推斷CSV欄位夾帶雙引號是有解的!經過測試,我發現只需將CSV檔轉成UTF8編碼,Excel也能正確解析雙引號包夾的換行,有個但書-雙引號前方不可再加上等於符號,否則會識別失敗。另外透過實驗得知,雙引號包夾內容若要用到雙引號,要用連續兩個雙引號代替,逗號則可直接嵌入,不需跳脫字元。

搞懂規則,要讓小工具搞定換行符號就不是難事。我採行的策略是逐字元讀入,隨時掌握目前是否處於雙引號包夾範圍,若在包夾內容出現換行符號(\r\n),先置換成ASCII 0x07 (BEL字元,DOS時代顯示字串時會嗶一聲,它在CSV出現機率幾乎為零,應無撞碼疑慮),之後再依原邏輯切割處理。另外,逗號(,)也要比照先換成0x08(Backspace)之後再還原,以免影響解析。

調整後的程式核心如下:


using (StreamReader sr = new StreamReader(fn, encoding, true))
    {
        StringBuilder sb = new StringBuilder();
bool quotMarkMode = false;
string newLineReplacement = "\x07";
string commaReplacement = "\x08";
//支援CSV雙引號內含換行符號規則,採逐字讀入解析
//雙引號內如需表示", 使用""代替
while (sr.Peek() >= 0)
        {
            var ch = (char)sr.Read();
if (quotMarkMode)
            {
//雙引號包含區段內遇到雙引號有兩種情境
if (ch == '"')
                {
//連續兩個雙引號,為欄位內雙引號字元
if (sr.Peek() == '"')
                        sb.Append((char)sr.Read());
//遇到結尾雙引號,雙引號包夾模式結束
else
                        quotMarkMode = false;
                    sb.Append(ch);
                }
//雙引號內遇到換行符號,先置換成特殊字元,稍後換回
elseif (ch == '\r'&& sr.Peek() == '\n')
                {
                    sr.Read();
                    sb.Append(newLineReplacement);
                }
//雙引號內遇到逗號,先置換成特殊字元,稍後換回
elseif (ch == ',')
                    sb.Append(commaReplacement);
//否則,正常插入字元
else
                    sb.Append(ch);
            }
else
            {
                sb.Append(ch);
if (ch == '"') quotMarkMode = true;
            }
        }
        var fixedCsv = sb.ToString();
        sb.Length = 0;
string line;
using (var lr = new StringReader(fixedCsv))
        {
while ((line = lr.ReadLine()) != null)
            {
string[] p = line.Split(',');
                sb.AppendLine(string.Join(",",
//若欄位以0起首,重新組裝成="...."格式
                    p.Select(o => 
                        o.StartsWith("0") ? 
string.Format("=\"{0}\"", o) : 
//還原換行符號及逗號
                            o.StartsWith("\"") ? 
                                o.Replace(newLineReplacement, "\r\n")
                                .Replace(commaReplacement, ",") : o
                    ).ToArray()));
            }
        }
 
//調整結果另存為同目錄下*.fixed.csv檔
string fixedFile = Path.Combine(
            Path.GetDirectoryName(fn), 
            Path.GetFileNameWithoutExtension(fn) + ".fixed.csv");
//一律存為UTF8
        File.WriteAllText(fixedFile, sb.ToString(), Encoding.UTF8);
//開啟CSV
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo(fixedFile);
        proc.Start();
    }

另外,我加了讓使用者選擇CSV檔案編碼的功能。原本想做到自動識別,但ANSI識別要做到完全正確難度頗高,不如開放使用者自己選,只增加一點點不便,換取腦細胞少死數千 XD

為測試效果,設計了一個刁鑽案例,欄位內有換行有逗號:

強化版小工具挑戰成功!

原始碼已上到Github,會寫C#的朋友可以取回自行編譯使用,也歡迎參考、改寫。

另外FB群組則有編譯好的執行檔供程式麻瓜同學取用,大家使用上如遇到問題請再回饋給我。

VS2015程式檔BIG5相容問題快速解法-修改csproj/vbproj

$
0
0

繼發現VS2015有許功蓋BIG5相容問題、寫了批次轉檔潛盾機,接到網友們陸續回饋,我學會好幾種不同做法,也知道了幾個批次轉檔軟體,在此感謝。

昨天ChrisTorng再分享了一個存檔時自動轉UTF8的VS套件,粗略評估,它主要功能在於存檔時強制存成UTF8編碼,若專案存在大量BIG5編碼程式檔,得逐一開啟存檔,仍是苦工。但順著套件的日文介紹,我找到一篇微軟Visual Studio日本支援團隊的MSDN部落格文章,雖然日文看不懂,但我看到幾個關鍵字:Shift-JIS(日文的ANSI編碼,相當於我們的BIG5)、csproj、PropertyGroup、CodePage、932(Shift-JIS的CodePage)… 秒懂,有了重大發現。

原來.csproj中有個隱藏參數CodePage,若Visual Studio在開啟專案時因BIG5編碼不相容出錯,可使用文字編輯器打開.csproj,如下圖加入<CodePage>950</CodePage>指定使用BIG5編碼解析ANSI編碼程式檔:

薑!薑!薑!薑~ 程式碼不用改編碼,VS2015 BIG5不相容問題自動消失!

就長遠來看,所有程式檔統一改存UTF8才是王道。但如果想快速解決問題,這招很好用。

PS:這個方法應可套用於所有ANSI編碼程式檔,例如:若為簡體中文GB2312/GBK,CodePage改指定936即可。

SQL筆記:Index Scan vs Index Seek

$
0
0

寫這篇筆記是因為前陣子在實驗SQL查詢效能,發現自己對於「相似的SQL查詢有時使用Index Scan,有時又選擇Index Seek」的行為有些迷惑,決定花點時間重新認識這幾個基本資料庫概念。(對於SQL我只有玩票的水準,內容如有謬誤,敬請指正)

當我們對資料庫下達SELECT … WHERE …指令,資料庫引擎必須對指令進行分析,找出最有效率的方法儘快查到資料算出結果,而這個「找到資料算出結果的方法」就是所謂的執行計劃(Execution Plan)。要從資料表找到指定的資料,有很多做法,最笨的方法就是整個資料表每一筆都翻出來檢查(Scan),或者預先把常查的欄位拉出來排序做成索引(Index),找資料時先查索引,再透過索引指向資料所在位置。(就像查字典時由注音找到字,再翻到指定頁數)

以下是在資料表找資料的幾種做法:

Table Scan

當資料表沒有任何Index,資料庫引擎唯一的選擇是讀取整個資料表逐筆比對。查詢過程耗費時間與結果筆數無關,因為整個資料表要掃一遍,這是最沒效率的做法。

Clustered Index Scan

當資料表有建立Clustered-Index,評估查詢條件無法藉由其他Index加速,資料庫引擎逐筆讀取整個Clustered Index進行比對,由於全部資料要掃一次,查詢耗費時間不受結果筆數影響。
由於Clustered Index順序與資料實際儲存順序一致,不需要額外尋找動作(例如:Key Lookup)就能讀到SELECT要求的欄位。

Index Scan

查詢條件包含Index的組成欄位,資料庫引擎逐筆讀取Index(由第一筆到最後一筆),從中找出符合者。若SELECT要求Index未包含的欄位,則還需透過Key Lookup找到資料列讀取資料。不管最後找到幾筆,都得讀完整個Index,尋找過程耗費時間與結果筆數無關(不考慮讀取資料時間)。

Index Seek

當查詢條件包含Index組成的第一個欄位(由此可知,建立多欄位Index時順序很重要),資料庫引擎可透過B Tree演算法較快找到第一筆相符資料,逐筆讀取直到資料不相符為止(如同Index Scan,必要時還需配合Key Lookup),Index Seek時間長短與結果筆數多寡成正比。

一般而言,Index Seek比Index Scan有效率,但當資料表很小,或結果筆數佔全部資料的比例偏高,Index Scan邏輯單純,有可能比Index Seek來得快。資料庫引擎很聰明,有能力依結果筆數佔全部資料比例,決定該用Index Seek還是Index Scan。

我們來用實驗證明看看。以SQL 2014 Express搭配範例資料庫Adventure Works 2008為樣本,挑選有12,1317筆的Sales.SalesOrderDetail,SalesOrderDetail為ProductID欄位建過Index,我們就以ProdcutID當WHERE條件。

開始前先用以下查詢了解各ProductID所佔筆數,整個SalesOrderDetail共出現266種ProductID,不同ProductID的資料筆數從4688到2筆都有。

挑了六個不同的ProdcutID值查詢資料表,讀取其ProductID及OrderQty(OrderQty不在Index中)。


SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 870 --4688筆
 
SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 871 --2025筆
 
SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 766 --460筆
 
SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 972 --380筆
 
SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 937 --255筆
 
SELECT ProductID, OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 723 --52筆

執行查詢並觀察執行計劃:

六組查詢的結果筆數分別為4688、2025、460、380、255及52筆,先前的理論獲得兩點印證:

  1. 資料筆數多時,Index Scan比Index Seek划算
    六次查詢的結果筆數由多至少,前三次SQL決定使用Cluster Index Scan,後三次則是Index Seek + Key Lookup。證實結果筆數佔總筆數的比例愈高,SQL愈傾向使用Index Scan而非Index Seek。
    至於比例超過多少用Index Scan較好,似乎不是由固定數字決定,猜想SQL或各家資料庫系統自有一套複雜演算法,以達到查詢效能最佳化。
    至於前三次為何不用包含ProductID的Index做Index Scan,而是不包含ProductID的Clustered Index Scan?(Clustered Index欄位為SalesOrderID及SalesOrderDetailID)
    Clustered Index與實際資料儲存順序一致,可以省去Index Scan完的Key Lookup步驟,SQL應是評估Clustered Index Scan比Index Scan+Key Lookup更有效率而做的決定。
  2. Index Scan的搜索時間與結果筆數無關
    前三次查詢結果分別為4688、2025及460筆,但由其佔時間均為六次查詢總時間的20%,證明使用Index Scan時,不管結果多寡,花費的搜索時間相同。(此處不考慮讀取資料傳回的成本)

【結論】

相同的SELECT … WHERE Col = 'Blah'查詢,會因為'Blah'值不同,有時用Index Scan,有時用Index Seek,這是SQL Server基於執行效率考量的結果。

SQL筆記:Literal, Variable與Parameter

$
0
0

繼續研究不同SQL寫法對執行計劃的影響。

如果大家讀過上一篇筆記,就會知道以下兩則查詢將使用不同的執行計劃,前者走Clustered Index Scan,後者則是Index Seek + Key Lookup。

SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 870 --4688筆
 
SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = 897 --2筆

經實測,執行計劃正如預期:

那,如果我將SQL改成這樣呢?將原本寫死的WHERE條件,改用變數(Variable)傳入,仍然查詢870跟897:

DECLARE @p INT
 
SET @p = 870
SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = @p --4688筆
 
SET @p = 897
SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = @p --2筆

查詢結果筆相同,但執行計劃變了,二者都走Index Scan:

呃,為什麼?上回不是說資料筆數少用Index Seek,筆數多用Index Scan,這回又不照規矩來,SQL Server你搞得我好亂吶~

莫驚慌,SQL這麼做有它的理由,不是故意要把大家搞瘋。要找出最適合的執行計劃,全靠執行前對SQL指令進行分析。當我們使用WHERE ProductID = 897(把比對值直接寫在指令裡,術語稱為Literal),SQL分析時已知搜尋對象為ProductID 897,由統計資料預測結果筆數不多,使用Index Seek效率較佳;而宣告變數(Variable),指定變數值再WHERE ProductID = @p的做法,SQL於執行前無從得知@p的內容(雖然指令中有SET @p = 897,但分析期間不會真的下去跑指令),故只能用平均值進行預測,而266個ProductID大部分用Index Scan效率較佳,SQL押Index Scan贏面大,故遇到WHERE ProductID = 變數的場合一律用Index Scan。

WHERE ProductID = @p裡的@p可能還有另一種意義-Stored Procedure的參數(Parameter)。對SQL而言,Procedure內的邏輯固定,可以預先分析,而參數值在傳入時已知,不像變數到執行期間才確定,故SQL可以依參數值決定最適合的執行計劃(術語為Parameter Sniffing)。

為求簡潔,這裡我用sp_executesql示範,大家如果看不習慣,可以自行改寫成Stored Procedure,結果會一樣。

exec sp_executesql 
N'SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = @p', N'@p INT', @p = 897 --2筆
 
exec sp_executesql 
N'SELECT ProductID,  OrderQty FROM Sales.SalesOrderDetail 
WHERE ProductID = @p', N'@p INT', @p = 870 --4688筆

如上所示,將原本Variable範例改為Parameter,但這回我們先查897(2筆)再查870(4688筆)。果然,Index Seek出現了:

等一下!! 897(2筆)用Index Seek我沒意見,但870(4688筆)怎麼也用Index Seek,啊啊啊啊~

到這裡,我相信已沒人懷疑我想把大家逼瘋的決心了 XD

這與SQL的另一項效能武器有關:分析SQL指令得耗費資源,尤其實務使用的SQL指令,遠比我們寫的Hello World範例複雜數十上百倍,故SQL一般會使用快取保留分析結果,第二次執行相同指令時不用重新分析,直接套用執行計劃即可。

在我們的範例中,由於兩次sp_executesql的SQL指令完全相同,故第一次執行,由參數897決定用Index Seek + Key Lookup並將執行計劃快取起來,第二次執行@p=870,但仍沿用快取的執行計劃,故仍使用Index Seek。

做個實驗,我們重啟SQL Server(強制將快取清空,或使用指令DBCC FREEPROCCACHE就不用重啟)並將指令前後對調,先跑870(4688筆)再跑897(2筆):

兩次都走Index Scan,證明第一次的執行計劃被快取,第二次沿用。

再來一個實驗,故意在兩次SQL指令加上不同註解:

SQL指令有別(黃底註解部分不同),不共享快取,出現兩份執行計劃,各自最佳化。但是,這兩組快取也將被繼續用於各式參數值,未必為最佳化選擇。

由以上實驗,理出三點結論:

  1. 只有Literal式寫法(WHERE Product=870)才能保證SQL針對該次查詢條件進行最佳化。
  2. Variable會阻礙SQL分析,只能依統計平均值決定執行計劃。
  3. SQL會針對第一次的Parameter值執行分析選定執行計劃,並存入快取沿用,不因後續傳入參數值重新分析調整。

留下一個疑問:「Stored Procedure可重覆使用執行計劃,有助提升效能」是常識,但實驗可知Literal寫法才能確保SQL針對當次查詢條件最佳化,如此卻又無法重覆利用執行計劃,二者矛盾?

是的,凡事都有取捨,看起來我們得在「浪費效能重新編譯SQL指令決定執行計劃」與「未依當下條件參數選用最佳執行計劃」間抉擇。前面提過,實務上重新編譯SQL指令耗損的資源比沒選用「最佳」執行計劃的代價高一點,所以「重覆利用執行計劃可提升效能」才會變成常識。當然,凡事總有特例,若你遇到執行計劃快取會讓SQL誤判暴慢的情境,有一些救濟手段,執行Stored Procedure時可以加入WITH RECOMPILE,若使用sp_executesql,則可寫成SELECT … FROM … WHERE … OPTION (RECOMPILE),強迫每次重新編譯重新針對參數選擇執行計劃,但要記住,非常手段自有其副作用,多半只適用於非常狀況,使用前宜謹慎評估。

SQL筆記:WHERE 1=1會拖累效能嗎?

$
0
0

稱不上DB咖的我,反常地連寫兩篇SQL筆記,其實都是研究「動態產生SQL查詢條件」議題的副產品,這篇才算步入正題,鴨架子湯先來兩碗,烤鴨才上桌,哈!但這樣安排是對的,以下探討有一部分需要先前筆記的基礎才好聊下去。

兩週前,參加一場Code Review討論,會中大家剛好聊到「動態產生SQL查詢條件」這檔事兒。它的情境是:使用者在操作介面上有多項條件選擇,例如:日期、類別、關鍵字,每個條件使用者可選擇輸入或不輸入(不輸入代表不限定)。從程式的角度,使用者依輸入條件不同,可能形成以下幾種SQL查詢條件:

  1. 全部都不填
    不需WHERE條件,查詢全部資料
  2. 填日期
    WHERE Date = @date
  3. 填類別
    WHERE Catg = @catg
  4. 填關鍵字
    WHERE Subject LIKE '%' + @keywd + '%'
  5. 填日期+填類別
    WHERE Date = @date AND Catg = @catg
  6. 填日期+填關鍵字
    WHERE Date = @date AND Subject LIKE '%' + @keywd + '%'
  7. 填類別+填關鍵字
    WHERE Catg = @catg AND Subject LIKE '%' + @keywd + '%'
  8. 填日期+填類別+填關鍵字
    WHERE Date = @date AND Catg = @catg AND Subject LIKE '%' + @keywd + '%'

為了應付這種情境,老鳥們應該都看過一種寫法:

staticvoid DynaWhereSample(DateTime? date, string catg, string keywd)
        {
            SqlCommand cmd = new SqlCommand();
string sql = "SELECT * FROM MyTable WHERE 1=1 ";
if (date != null)
            {
                sql += "AND Date = @date";
                cmd.Parameters.Add("@date", SqlDbType.DateTime).Value = date.Value;
            }
if (!string.IsNullOrEmpty(catg))
            {
//嚴正提醒:catg等參數來自使用者輸入,請使用SqlParameter,禁止直接串接
//錯誤示範 => sql += "AND Catg='" + catg + "'"
//以上寫法隱藏SQL Injection風險,招致難以想像的災難(嚴重者可至家破人亡)
//開發者犯此錯誤,依江湖聯合幫規第127條,處唯一死刑-阿魯巴到死!
                sql += "AND Catg = @catg";
                cmd.Parameters.Add("@catg", SqlDbType.VarChar).Value = catg;
            }
if (!string.IsNullOrEmpty(keywd))
            {
                sql += "AND Subject LIKE '%' + @keywd + '%'";
                cmd.Parameters.Add("@keywd", SqlDbType.NVarChar).Value = keywd;
            }
            cmd.CommandText = sql;
//執行SQL,以下省略
        }

程式依date, catg, keywd是否有值動態組裝出不同的WHERE條件組合(再次提醒,請使用SqlParameter,切忌直接內容直接串成SQL指令,否則…),若使用者什麼都沒填,SQL查詢指令會變成SELECT * FROM MyTable WHERE 1=1,效果與SELEC * FROM MyTable相同。

「WHERE 1=1」差不多已經是這種寫法的代名詞吧?我發現只要說出「WHERE 1=1再動態接條件」,許多老鳥都會點頭會心一笑,足見此招在江湖流傳之廣。(不信大家可以用Google查「WHERE 1=1」 XD)

提到WHERE 1=1,不意外地就有人起疑「多加這種條件會不會影響效能?會不會該查詢變成Table Scan(Index Scan)?」

依我的認知,市面上的主流資料庫早已歷經千鎚百鍊,不致呆頭呆腦被1=1騙,編譯SQL最佳化時自有能力排除這類永遠成立或永遠不成立的無意義條件。過去,對於這點主張只能想當然爾,在搞懂Index Scan與Index Seek的意義及發生時機後,現在我們能靠檢查執行計劃驗證:

如上圖所示,加上WHRE 1=1,仍然走Index Seek,證明SQL未受1=1混淆,仍選擇適合的執行計劃。

等等,難道動態組條件一定非得WHERE 1=1?

寫WHERE 1=1的好處是邏輯簡單,只要無腦地有給值就串字串AND Col…,即使完全不限條件也能靠WHERE 1=1全選。若不這麼做,得判斷第一個條件加WHERE Col=…,第二個以後的條件加AND Col=…,全無條件時連WHERE都不要加,邏輯當真複雜不少,這也難怪WHERE 1=1這招能風行一時。不過,都到了LINQ時代,出現好多新武器,用短短幾行程式碼就能寫出沒有贅肉的動態WHERE條件組裝邏輯:

staticvoid DynaWhereSample(DateTime? date, string catg, string keywd)
        {
            SqlCommand cmd = new SqlCommand();
            List<string> cond = new List<string>();
if (date != null)
            {
                cond.Add("Date = @date");
                cmd.Parameters.Add("@date", SqlDbType.DateTime).Value = date.Value;
            }
if (!string.IsNullOrEmpty(catg))
            {
                cond.Add("Catg = @catg");
                cmd.Parameters.Add("@catg", SqlDbType.VarChar).Value = catg;
            }
if (!string.IsNullOrEmpty(keywd))
            {
                cond.Add("Subject LIKE '%' + @keywd + '%'");
                cmd.Parameters.Add("@keywd", SqlDbType.NVarChar).Value = keywd;
            }
            cmd.CommandText = string.Format(
"SELECT * FROM MyTable{0}{1}",
                cond.Count > 0 ? " WHERE " : "",
string.Join(" AND ", cond.ToArray()));
//執行SQL,以下省略
            Console.WriteLine(cmd.CommandText);
        }

幾行簡潔程式碼就能組出有馬甲線的精實SQL指令,何苦再用怪招?大家就忘了WHERE 1=1吧!:P

SQL筆記:再談動態WHERE條件

$
0
0

前一篇文章探討了「WHERE 1=1動態查詢條件組裝」的效能問題,並介紹如何利用C#語言特性簡單寫出沒有多餘WHERE 1=1的馬甲線SQL指令。而在前文提到的Code Review會議,還有一招不需要組裝WHERE指令的做法也被提及。

//REF: http://goo.gl/SBF1Wi by 91
/// <summary>
/// 當資料物件為null時傳回DBNull.Value
/// </summary>
/// <param name="obj"></param>
/// <param name="convEmpty">空字串是否也要傳DBNull.Value</param>
/// <returns></returns>
publicstaticobject NullToDBNullValue(thisobject obj, bool convEmpty = false)
        {
if (convEmpty && obj != null&& 
                obj isstring&& string.IsNullOrEmpty((string)obj))
            {
return DBNull.Value;
            }
return obj ?? DBNull.Value;
        }
 
staticvoid DynaWhereSample2(DateTime? date, string catg, string keywd)
        {
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = @"
SELECT * FROM MyTable
WHERE 
(@date IS NULL OR Date = @date) AND
(@catg IS NULL OR Catg = @catg) AND
(@keywd IS NULL OR Subject LIKE '%' + @keywd + '%')";
            cmd.Parameters.Add("@date", SqlDbType.DateTime).Value
                = date.NullToDBNullValue();
            cmd.Parameters.Add("@catg", SqlDbType.VarChar).Value
                = catg.NullToDBNullValue(true);
            cmd.Parameters.Add("@keywd", SqlDbType.NVarChar).Value
                = keywd.NullToDBNullValue(true);
//執行SQL,以下省略
        }

如上範例,不管使用者是否填寫限制條件都一律傳入參數,若使用者未填值,參數值設為NULL,透過(@catg IS NULL OR Catg = @catg) 實現「@catg不是NULL時執行比對,否則就不設限制」的效果。程式中有一眉角,SqlParameter要設成NULL較嚴謹的做法是傳入DBNull.Value,我參考了91的做法,利用Extension Method統一轉換,並增設將String.Empty也轉成DBNull.Value的選項。

這麼寫的好處是SQL指令從頭到尾固定,可節去重新編譯SQL指令的效能損耗,另外,少了依參數值決定是否串接AND條件的繁瑣邏輯,程式碼看來清爽許多,感覺是個簡潔又有效率的好做法。但事實真是如此?學會判別執行計劃優劣後,我們學會用更精確的角度衡量SQL指令效能。

依我的直覺,面對 (@catg IS NULL OR Catg = @catg) ,SQL應該會比照WHERE 1=1,聰明地略過永遠成立或永遠不成立的部分,選擇適合的執行計劃。

不過,實測結果顯然與想像有出入:(第一列的dbcc freeproccache指令用來清除執行計劃快取,確保SQL依本次傳入參數進行最佳化)

如圖所示,WHERE (@prodId IS NULL OR ProductID = @prodId) 在結果只有兩筆的情況下,仍選擇使用Index Scan,而且不是Clustered Index Scan,而且是Index Scan加Key Lookup,意味@prodID IS NULL確實影響SQL對執行計劃的選擇。

試著加上OPTION (RECOMPILE),強迫每次執行重新編譯SQL,測試結果才貼近我們期望的最佳化(筆數少 Index Seek+Key Lookup,筆數多Index Scan),但如此也喪失原本想節省重新編譯程序的優勢。

再延伸一個更複雜的案例,我們在查詢摻入子查詢,當@prodId與@qty均為NULL時,SQL理應不要理會SpecialOfferID及OrderQty比對,用Clustered Index Scan就好。

SELECT ProductId, OrderQty FROM Sales.SalesOrderDetail WHERE
(@prodID ISNULLOR SpecialOfferID IN
  (SELECT SpecialOfferID FROM Sales.SpecialOfferProduct 
WHERE ProductId = @prodId))
AND (@qty ISNULLOR OrderQty > @qty) 

實測結果讓人意外,即使已知@prodId及@qty為NULL,SQL在執行計劃裡仍納入OR後方的子查詢!

一樣得加上OPTION (RECOMPILE)才會導回我們期望的最佳化結果-Clustered Index Scan。

看到這裡,大家是否覺得SQL好笨?其實不然,我們得從另一個角度思考,當未指定OPTION (RECOMPILE),編譯SQL產生的執行計劃將會被Cache並用在後續的查詢,故SQL必須準備一個不管參數為何都能適用的執行計劃。若因第一次參數傳NULL,就將只做Index Scan不含子查詢的執行計劃寫入Cache,之後傳入的參數有值需要子查詢,豈不開天窗?因此,SQL在策略上必須基於「所有參數都有值」的假設決定執行計劃,才能確保不管參數為何都能順利執行,可以想見,這種(@p1 IS NULL OR P1 = @p1) AND (@p2 IS NULL OR P2 = @p2) 的SQL指令,將會假設@p1與@p2都有值決定執行計劃。

由此看來,「SQL指令固定靠參數是否為NULL動態切換條件」的做法,必須每次重新編譯SQL(加上OPTION (RECOMPILE))才避免套用以最複雜條件為前題的執行計劃,但如此又無法藉重覆利用執行計劃提升效能,整體評估後,還是前篇介紹的串接WHERE的做法略勝一籌。

閒談:.NET Remoting、WCF、WebAPI、Socket,該怎麼選?

$
0
0

周遭幾個系統正面臨相似抉擇:使用.NET Remoting的舊程式,需與伺服器建立連線進行高頻率雙向傳輸,計劃翻寫新版劃。.NET Remoting是.NET 2.0時代的技術,十年後多了不少選擇:WCF、Web API、SingalR… 選擇變多煩惱跟著來,該怎麼選擇才對?

面對這道選擇題,我只有模糊的概念,趁此機會整理我的個人看法,也歡迎大家回饋。

.NET Remoting是2.0時代的產物,在微軟Roadmap中已被WCF取代(.NET Remoting做得到的事,理論上WCF都可以實現):參考來源

.NET Framework 2.0以及前版本中,微軟發展了Web ServiceSOAP with HTTP communication),.NET RemotingTCP/HTTP/Pipeline communication)以及基礎的Winsock等通訊支援,由於各個通訊方法的設計方法不同,而且彼此之間也有相互的重疊性(例如.NET Remoting可以開發SOAP, HTTP通訊),對於開發人員來說,不同的選擇會有不同的程式設計模型,而且必須要重新學習,讓開發人員在使用者有許多不便。同時,服務導向架構Service-Oriented Architecture)也開始盛行於軟體工業中,因此微軟重新檢視了這些通訊方法,並設計了一個統一的程式開發模型,對於資料通訊提供了最基本最有彈性的支援,這就是Windows Communication Foundation

基於這點,.NET Remoting選項應該可以剔除,因此它屬於.NET 2.0,微軟策略上以WCF取而代之,未來不再投入資源開發,可用資源(文件、KB)只會愈來愈少。依據微軟一份效能測試,WCF在效能上比ASP.NET Web Service快了25%-50%,比.NET Remoting快25%,棄.NET Remoting改用WCF將有效能上的突破。最後,就由微軟.NET Remoting官方介紹加註於文首的聲明來補最後一刀:

This topic is specific to a legacy technology that is retained for backward compatibility with existing applications and is not recommended for new development. Distributed applications should now be developed using theWindows Communication Foundation (WCF).
【翻譯】此文所言乃古物,未刪只因相容故;君若有緣開新局,勿疑逕取WCF

由此說來,WCF是取代Web Service及.NET Remoting的接班人無誤。這些年來,Web/HTTP的各式應用當紅,WCF仍持續發展,但熱度及受矚目程度明顯不如可以輕鬆跨平台接軌的Web API。然而,Web API能全面取代 WCF嗎?倒也未必,MSDN有張比較表,指明二者特色:參考

WCFASP.NET Web 應用程式開發介面
能支援多重傳輸通訊協定 (HTTP、TCP、UDP 和自訂傳輸) 的建置服務,並允許兩者切換。 僅限 HTTP。 適用於 HTTP 的第一級程式設計模型。更適合從各種不同的瀏覽器、行動裝置等項目進行存取,確保廣泛連線。
適用於支援相同訊息型別之多重編碼 (文字、MTOM 和 Binary) 的建置服務,並允許兩者切換。 能建置支援各種媒體型別的 Web 應用程式開發介面,包括 XML、JSON 等。
支援 WS-* 標準的建置服務,例如可信賴傳訊、交易、訊息安全性。 使用基本通訊協定和格式,例如 HTTP、WebSockets、SSL、JQuery、JSON 和 XML。不支援層級較高的通訊協定,例如可信賴傳訊或交易。
支援要求-回覆、單向和雙工訊息交換模式。 HTTP 可進行要求/回應,其他模式則可透過 SignalR和 WebSockets 整合來支援。
您可在 WSDL 中說明 WCF SOAP 服務,以利自動化工具產生用戶端 Proxy (亦適用於包含複雜結構描述的服務)。 不論是從自動 HTML 說明頁面說明片段到 OData 整合之應用程式開發介面的結構化中繼資料,都有各種說明 Web 應用程式開發介面的方式。
隨附於 .NET Framework。 隨附於 .NET Framework,但為開放原始碼且可透過頻外個別下載取得

二者相比,WCF功能強大許多,支援各種通訊協定、訊息編碼格式、安全管控,透過設定檔就能自由切換,且Visual Studio支援完整,加入WCF參照就會自動產生Proxy類別,用起來跟本地物件沒兩樣,但是,WCF設定過於複雜一直為人垢病。(我的觀察啦)

WFC因功能強大,支援協定、格式眾多,設定複雜度直逼747儀錶板,有數十上百個參數可供組合調整,稍有不慎便可能出錯,光處理設定茶包就飽了,踩過雷者莫不聞風喪膽。這窘境或許起因於很少人下功夫研究過WCF設定(Visual Studio使用者已習慣凡事拖拖拉拉點點選選就該搞定,花上一天讀文件學習「如何調設定檔」?成何體統!),缺少人性化GUI設定介面,系統出錯時,往往「不知道哪裡沒設好,瞎試一通搞定又不知為什麼就變好」,挫折經驗一多,WCF就像陰晴不定、喜怒無常的公主病女王,若非受虐狂,誰會有好感?換個角度,若能徹底搞懂serviceBehaviors、endpoint、wsHttpBinding、mexHttpBinding… 這堆鬼東西,熟悉其代表意義(我直接承認我不懂),WCF設定或許沒這麼可怕,遇到系統出錯馬上有明確追查方向,狀況應會改觀。

這幾年工作都繞著瀏覽器、行動裝置打轉,讓我偏愛Web API。最主要考量是Web API在不同開發平台都能找到現成程式庫及技術資源,只要有能力發送HTTP Request跟解析JSON就能引用,寫Web API時不必操心客戶端能否支援。(我最愛的一點是「都走Web API還不知道怎麼接,要嘛是客戶端開發者能力不足,要嘛是他們的語言工具太鳥,絕對與我們無關」XD)

撇開客戶端是手機或非.NET平台的案例不談,拉回服務端與客戶端都是.NET的情境,此時Web API的跨語言支援廣泛優勢不再,反過頭來還損失Visual Studio為WCF自動建立強型別資料物件及Proxy類別的一整套便利機制。為此,我多半會自己搞Code Gen機制在Web API C#端、TypeScript端同時建立對應的強型別物件,順帶產生C# Proxy供.NET Web API客戶端使用,自己做完一堆粗活。即便如此,採用Web API仍有不少好處-部署設定方便,失敗率極低,最吸引人的一點是,只要開個瀏覽器就能測試偵錯,不像WCF還得依賴WCF Storm之類的第三方工具(即使用現成工具還是常常觸礁)才能查問題,這點深得我心。不過,這個比較觀點忽略「為Web API量身打造類似Visual Studio WCF自動類別/Proxy產生器」所投入成本,並不全然客觀。

重新拉回服務端與客戶端都是.NET的場景,若你不想自己處理TCP連線、序列化反序列化、產生強型別物件、Proxy…等細節,WCF仍是較佳的選擇,可享受現成的Framework、開發工具支援,至於WCF難以駕御的問題,恐得花點時間搞懂WCF設定的諸多細節,等能玩弄其於股掌之間,或許能和WCF做好朋友。

若需求涉及高頻率的資料交換,還有另一種選擇:「自訂封閉式傳輸協定」。最典型做法是Client/Server間建Socket傳資料,依據系統需求自訂通訊協定,理論上如此可達到最佳傳輸效率,但得自已搞定通訊協定、序列化規則、TCP連線處理(容錯、斷線重連)等細節,有一定的複雜度,若能做到夠穩定夠強韌,在速度及效率上得到的回報將能徹底擊敗WCF,但要付出相當的開發成本。若要選擇自訂傳輸協定,有一些Framwork可以協助處理底層的TCP/Socket傳輸細節,省去可觀的功夫。例如:

最後,總結我的抉擇觀點:

  • 速度!速度!速度!
    自建Socket連線是較佳的選擇,但要寫出強韌可靠的Socket底層難度不低,可考慮借用現有Framework。
  • 彈性+速度
    WCF使用TCP加二進位格式,效能可直追Socket,也可掛在網站跨越網際網路溝通。不動程式只改設定就能切換不同傳輸通道,如果你沒被設定檔搞瘋的話,但需花點心思研究設定才能成功駕御。
  • 跨平台跨語言跨裝置+簡單
    如果客戶端很多樣化跨越多種平台、語言、裝置,Web API是最佳選擇。或者你不喜歡太複雜的設定,加上HTTP協定+JSON的效能表現就能滿足要求,Web API只要懂Web就能寫、只要有瀏覽器就能測,應是最容易上手的選項。
  • 鍾愛古玩+能忍孤寂
    就選.NET Remoting吧!願原力與你同在~

Hackpad @ Ubuntu on Azure安裝筆記

$
0
0

HackpadG0V開源社群愛用的線上文件協同編輯平台,在學運期間通過1500人壓測一戰成名

Hackpad 基本是個簡便易用的網路共筆平台,支援多人共用即時更新,編輯文字、上傳圖檔還算便利,能追蹤編修歷程,比Wiki系統好用,我們開發團隊一直覺得用它整理系統架構文件、共用程式庫規格、茶包處理FAQ…等,會是很棒的選擇。但是對不少企業來說,這類「雲端服務」解決方案最大的致命傷始終在於「資訊儲放在網際網路可能外洩」的疑慮,用腳底板想都知道一提出來就會被網管或資安打槍(雖然放在公司裡也不保證絕對安全)。唯一的途徑是像Hipchat一樣,採自建伺服器自行管理模式才可能過關。

前天跟同事正好就聊到這個,才在感嘆可惜Hackpad沒有私服方案,追到 Github 找到 Hackpad 公司帳號,只發現一個空空如也的Hackpad Repository,唉~ 看來得想其他解決方案。

昨天早上,在FB看到一則好消息:Dropbox finally open-sources its Hackpad collaborative document editor。讀完才知故事始末,去年四月Dropbox併購了Hackpad,在今年四月左右Hackpad開發團隊曾發了通知說幾週後將會讓Hackpad開源,但近四個月過去什麼沒發生,還在Github討論區引發一些質疑。就在前天晚上,Dropbox終於釋出Hackpad原始碼

說曹操曹操到的巧合真令人開心,馬上來試裝看看。懶得自己重灌VM,用MSDN訂閱附贈的額度在Azure開一台Ubuntu做測試,幾分鐘就有一台灌好OS的新VM可以玩,對於抱著玩膩就甩掉心態的花心IT,Azure真是搞一夜情做Lab的好地方。選Ubuntu的原因是想貼近Hackpad原有的開發執行環境,應能少踩一些雷,另一方面,也為將來的ASP.NET 5 on Linux預做準備,該好好溫一下Linux操作。

關於在Azure上開Ubuntu VM,小歐有一篇圖文並茂的說明:Linux on Azure:如何跨出第一步,不熟Azure操作的同學可以參考。Azure管理畫面已改版過,但步驟跟輸入欄位差不多。要留意一點,記得選A2以上等級,一開始我選A1 單核 1.5G RAM,啟動Hackpad時冒出記憶體不足的訊息,糗!後來調成A2 雙核 3.5G 才成功。

我選的OS是Ubuntu Server 15.04版(Ubuntu Server 14.x LTS是較穩定的版本,反正我是練刀沒差),依小歐的文件,我只裝到可用Putty登入,桌面環境跟遠端桌面就不裝了,純當Server用,靠指令搞定就好。

Github上的Hackpad 安裝文件寫得還算詳細,照著做就能成功安裝,以下是我的筆記:(要搞定還是需要基本Linux知識)

  1. 先更新套件資訊
    sudo apt-get update
  2. 安裝JDK 1.7 參考 
    sudo apt-get install default-jre 預設裝的就是1.7版
    sudo apt-get install default-jdk 預設裝的就是1.7版
  3. 安裝MySQL 參考:Ubuntu 安裝和設定 MySQL
    sudo apt-get install mysql-server
  4. 安裝MySQL Connect/J
    (Java連MySQL的程式庫,這個安裝文件沒提到,有可能被當成常識吧!)
    sudo apt-get install libmysql-java
  5. 安裝Scala
    直接 sudo apt-get install scala 只會裝2.9版,而安裝文件要求2.11
    所以要用以下步驟 參考
    sudo apt-get remove scala-library scala
    wget http://www.scala-lang.org/files/archive/scala-2.11.7.deb
    sudo dpkg -i scala-2.11.7.deb
    sudo apt-get update
    sudo apt-get install scala
  6. 由Github取回Hackpad程式碼
    wget https://github.com/dropbox/hackpad/archive/master.zip
    sudo apt-get install unzip
    unzip master.zip
    將檔案解到hackpad-master目錄下
  7. 修改 hackpad-master/bin/exports.sh
    nano exports.sh (建議用nano文字編輯器)
    修改以下項目(依Ubuntu版本、環境或操作步驟不同,路徑可能有異,請自行校正)
    export SCALA_HOME="/usr/share/scala"
    export SCALA_LIBRARY_JAR="$SCALAHOME/lib/scala-library.jar"
    export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"
    export MYSQL_CONNECTOR_JAR="/usr/share/java/mysql.jar"
  8. 緊張的開獎時間
    執行hackpad-master/bin/build.sh 編譯程式 

    編譯過程冒出一些警告(Warning)搞得氣氛有點緊張,所幸人品通過檢測,編譯成功!
  9. 設定資料庫
    執行hackpad-master/contrib/scripts/setup-mysql-db.sh
    Creating database hackpad...
    Enter password: 輸入MySQL root密碼
    Granting priviliges...
    Enter password:
    Success
  10. 建立設定檔
    將hackpad-master/etherpad/etc下的etherpad.localdev-default.properties複製成 etherpad.local.properties
    nano etherpad.local.properties 使用nano修改設定 參考
    我只按安裝說明改了以下兩項
    etherpad.superUserEmailAddresses = 管理者email
    topdomains = localhost,xxxx.cloudapp.net
  11. 安裝gemoji表情字型
    wget https://github.com/github/gemoji/archive/master.zip
    unzip master.zip
    將gemoji-master/images/emoji/unicode下的png檔複製到
    hackpad-master/etherpad/src/static/img/emoji/unicode


懶得打通Facebook、Google帳號整合認證(反正未來在公司內部跑也用不到),也不想花時間搞定SMTP送信,使用者註冊只能選輸入電子郵件:

畫面提示需收信點連結才能完成認證程序,但你知道我知道獨眼龍也知道,等一輩子也不會等到信滴。

解決方法是直接下SQL,到MySQL資料庫由hackpad.email_signup查出token:

自行組出身分認證連結URL,httq://localhost:9000/ep/account/validate-email?email=YOUR_EMAIL&token=TOKEN,用瀏覽器直接開啟連結就可完成認證開始使用。

就這樣,一台Hackpad私服就安裝完成囉!

經過簡單測試,新増文件、編輯都OK,但部分功能有點小問題,例如圖檔上傳不Work、表格無法打字,而前後端不時連線Dropbox及Facebook(可能是某些整合沒關閉),看來還有不少怪要打,但這已是極好的第一步。

感謝Hackpad開發團隊,Open Source萬歲!

Windows系統更新失敗之初步排除步驟

$
0
0

遇過不少次Windows更新或Windows功能安裝失敗,上網爬文得到的回答往往都是先用SFC(系統檔案檢查程式)檢查系統檔案是否遺失或毁損,嚴然已是排除這類問題的SOP,就跟修車要先拆坐墊一樣天經地義。這幾天剛好有一次SFC檢測及系統檔案修補經驗,特筆記備忘。

我遇到無法安裝Windows功能的狀況,爬文大多建議先跑SFC檢查,SFC /SCANNOW還真的發現錯誤,可惜雙手一攤說沒法修復:

KB文件的方法用findstr /c:"[SR]" %windir%\Logs\CBS\CBS.log > d:\sfc-results.txt 過濾出SFC執行記錄,在其中看到失敗項目:

2015-08-26 18:01:33, Info                  CSI    00000998 [SR] Beginning Verify and Repair transaction
2015-08-26 18:01:33, Info                  CSI    0000099a [SR] Cannot repair member file [l:36{18}]"Amd64\CNBJ2530.DPB" of prncacla.inf, Version = 6.3.9600.17415, pA = PROCESSOR_ARCHITECTURE_AMD64 (9), Culture neutral, VersionScope = 1 nonSxS, PublicKeyToken = {l:8 b:31bf3856ad364e35}, Type = [l:24{12}]"driverUpdate", TypeName neutral, PublicKey neutral in the store, hash mismatch
2015-08-26 18:01:33, Info                  CSI    0000099c [SR] Cannot repair member file [l:36{18}]"Amd64\CNBJ2530.DPB" of prncacla.inf, Version = 6.3.9600.17415, pA = PROCESSOR_ARCHITECTURE_AMD64 (9), Culture neutral, VersionScope = 1 nonSxS, PublicKeyToken = {l:8 b:31bf3856ad364e35}, Type = [l:24{12}]"driverUpdate", TypeName neutral, PublicKey neutral in the store, hash mismatch
2015-08-26 18:01:33, Info                  CSI    0000099d [SR] This component was referenced by [l:166{83}]"Package_2709_for_KB3000850~31bf3856ad364e35~amd64~~6.3.1.8.3000850-6825_neutral_GDR"
2015-08-26 18:01:33, Info                  CSI    0000099e [SR] Repair complete

遇到這種狀況,可以試著用DISM(Deployment Imaging and Servicing Management)部署映像服務與管理工具進行修補,指令為 DISM /Online /Cleanup-Image /RestoreHealth。/Online選項代表執行對象為目前運作的Windows,/Cleanup-Image表示要執行清除或還原作業,/RestoreHealth表示掃描映像是否發生元件存放區損毀並自動執行修復作業,DISM遇到檔案缺損將自動由Windows Update伺服器下載正確的版本。

執行要花點時間,系統檔案缺損問題就修復囉。

如果是在企業內部已啟用WSUS(公司自行管理的Windows更新服務),可能需在「本機群組原則編輯器」加入以下設定:

在「指定選用之元件安裝和元件修復的相關設定」,指定直接連絡Windows Update而不要從WSUS下載。

最後補充,修復系統檔案未必能解決Windows更新或Windows功能無法安裝問題(我的問題就沒解… orz),但先把這類疑慮排除肯定有益無害,未來遇更新或功能安裝問題可優先試試。

【茶包射手日記】安裝WCF TCP Activation天堂路

$
0
0

一切只起因我想試試WCF net.tcp,卻遇上近年來數一數二的刁鑽茶包,纏鬥超過一天…

故事從前幾天寫的.NET Remoting、WCF、Web API、Socket評估文說起,結論指出當Client/Server都是.NET,WCF仍是極出色的選項,尤其使用TCP管道配合二進位格式傳輸,效能直追Socket,雖然它的設定磨人,同事與我決定再給它一次機會,研究WCF應用在專案的可能性。

最早WCF要跑TCP得自己寫服務或獨立EXE,IIS7起推出了WAS(Windows Process Activation Service),支援TCP、Named Pipe、MSMQ這些非HTTP/HTTPS協定,WCF一律寫成網站,透過設定即可提供TCP、Named Pipe、MSMQ、HTTP多種連線方式,不必為不同傳輸方式多寫半行程式,應是使用WCF最方便的做法。

使用WAS的第一步,是在「開啟或關閉Windows功能」新増「WCF服務/TCP啟用」(TCP Activation)選項:(我的作業系統是Windows 8.1)

結果…

更!WCF不但設定難搞,還安裝過程都要刁我一下是怎樣?

爬文發現安裝Windows功能遇上0x800F0922錯誤常起因於系統檔案毁損,有兩種解決方式:

  1. 改由Windows安裝媒體(原始安裝光碟)或Windows Update安裝
    參考
    找出要安裝功能的完整名稱(WCF-TCP-Activation45)
    Dism /online /get-features > e:\Features.txt
    使用安裝光碟(D槽)
    Dism /online /enable-feature /featurename:WCF-TCP-Activation45 /All /Source:D:\sources\sxs /LimitAccess
    或由Windows Update下載安裝
    Dism /online /enable-feature /featurename:WCF-TCP-Activation45 /All
    結果:安裝失敗,一樣傳回:
    錯誤: 0x800f0922
    DISM 失敗。未執行任何操作。
    如需詳細資訊,請檢閱記錄檔。
    在 C:\Windows\Logs\DISM\dism.log 中可找到 DISM 記錄檔
  2. 修復系統檔案
    就是昨天提到的Windows系統更新失敗之初步排除步驟,修復完畢仍無法解決問題。

也有人提到安裝失敗可能跟防毒軟體有關,找了同事的Windows 8.1(Windows版本、防毒軟體等環境相似)測試安裝「TCP啟用」功能,像風一般瞬間安裝完成!(跌坐在地)啊啊啊啊,為什麼只有我被詛咒?orz

反覆試了好幾次,重開機數回,在Windows\Logs\CBS\CBS.log找到類似下方的失敗記錄:

(0) LockComponentPath (10): flags: 0 comp: {l:16 b:fa7fd5a65ce0d0011300000034264c2a} pathid: {l:16 b:fa7fd5a65ce0d0011400000034264c2a} path: [l:230{115}]"\SystemRoot\WinSxS\amd64_netfx-wcf-tcpactivation-registration_31bf3856ad364e35_4.0.9600.16384_none_6ef4da88718e1ebe" pid: 2634 starttime: 130851077419943182 (0x01d0e05c9417c50e)

2015-08-27 08:09:33, Error CSI 00000001 (F) Logged @2015/8/27:00:09:33.447 : [ml:84{42},l:82{41}]"installing service NetTcpActivator online"

[gle=0x80004005]

2015-08-27 08:09:33, Error CSI 00000002 (F) Logged @2015/8/27:00:09:33.449 : [ml:58{29},l:56{28}]"CreateService failed (15100)"

[gle=0x80004005]

2015-08-27 08:09:33, Error CSI 00000003@2015/8/27:00:09:33.449 (F) CMIADAPTER: Inner Error Message from AI HRESULT = HRESULT_FROM_WIN32(15100)

[

[18]"\u8cc7\u6e90\u8f09\u5165\u5668\u627e\u4e0d\u5230 MUI \u6a94\u6848\u3002

]

[gle=0x80004005]

2015-08-27 08:09:33, Error CSI 00000004@2015/8/27:00:09:33.450 (F) CMIADAPTER: AI failed. HRESULT = HRESULT_FROM_WIN32(15100)

可以確定問題出在無法安裝amd64_netfx-wcf-tcpactivation-registration,而錯誤訊息\u8cc7\u6e90\u8f09\u5165\u5668\u627e\u4e0d\u5230 MUI \u6a94\u6848\u3002轉成中文是「資源載入器找不到 MUI 檔案。/ The resource loader failed to find MUI file.」,已做過DISM修復,SFC掃瞄沒有任何錯誤,為什麼還缺檔?茫然無頭緒之際,「重新安裝Windows吧」的不好念頭悄悄在腦海飄過… 不行!重灌是弱者魯蛇的行為,身為茶包射手,拔刀才戰兩回就想著要不要投降成何體統,收拾書包回家冷靜冷靜。

第二天晨跑時繼續琢磨,頓時靈光一現:「笨!既然找不到MUI檔案,用Process Monitor查查缺什麼檔補上不就好了?」。回到公司再戰,開了Process Monitor側錄,卻沒發現任何找不到檔案的記錄。我不放棄,在可以正常安裝的Windows 8.1筆電也側錄一份Process Monitor記錄做比對,在數萬行記錄大海找尋可疑差異。

Process Monitor記錄一秒就好幾百筆,此時CBS.log中 2015/8/27:00:09:33.449 這種精確度到毫秒的資訊格外珍貴,鎖定 installing service NetTcpActivator online 到 CreateService failed (15100) 期間安裝程式的存取記錄,發現一項疑點:

有個 HKEY_USERS\.DEFAULT\Software\Classes\Local Settings\MuiCache\6df\474A91C\@%systemroot\\Microsoft.NET\\Framework64\\v4.0.30319\\ServiceModelInstallRC.dll,-8199 Registry 找不到,「MuiCache」讓人精神一振,該不會錯誤訊息說的找不到MUI檔案其實是讀不到MUI相關Registry?

我還發現不同Windows 8.1版本這部分結構也不同,MuiCache\*\474A91C下有很多服務、程式相關的中文字串,筆電是Windows 8.1專業版,就沒有"@%systemroot\\Microsoft.NET\\Framework64\\v4.0.30319\\ServiceModelInstallRC.dll,-8199"這一項,最後在同事的Windows 8.1企業版倒是有找到它。請同事匯出Registry,自己補成reg檔案(每台機器的Registry有別,例如6df這個數字,同事跟我的機器就不同,無法直接套用要手工調):

Windows Registry Editor Version 5.00

[HKEY_USERS\.DEFAULT\Software\Classes\Local Settings\MuiCache\6df\474A91C]

"@%systemroot\\Microsoft.NET\\Framework64\\v4.0.30319\\ServiceModelInstallRC.dll,-8199"="Net.Tcp Listener Adapter"

"@%systemroot%\\Microsoft.NET\\Framework64\\v4.0.30319\\ServiceModelInstallRC.dll,-8198"="透過 net.tcp 通訊協定收到啟用要求,並將它們傳送至 Windows Process Activation Service。"

補上Registry,懷著緊張的心情再試一次… 媽啊!像是打出全壘打般,我恨不得在辦公室繞場一圈。

我 成 功 裝 好 TCP啟動 了!

從沒想過裝好一項Windows功能會讓我如此激動… 補聲暗!

筆記-Windows環境變數需IISRESET才會生效

$
0
0

專案陸續改用Managed ODP.NET,實際用過幾回,發現設定TNS_ADMIN環境變數最省事直覺,只要維護一份TNSNAMES.ORA,設定一次即可供所有網站共用,TNS_ADMIN的變數名稱又很清楚明瞭。(參考:指定TNSNAMES.ORA共有config設定/複製TNSNAMES.ORA檔案到執行檔路徑/TNS_ADMIN環境變數/ORACLE_HOME環境變數相對路徑等做法)

今天處理一台伺服器上線,設完TNS_ADMIN,卻一直吐出「ORA-12154 TNS: 無法解析指定的連線ID」,反覆檢查有沒有打錯字,眼睛睜大到眼眶都快滲血,也重啟過Appliation Pool,改用config setting設定(參考如下)就正常驗證TNSNAMES.ORA內容無誤,但怎麼就是無法用TNS_ADMIN環境變數搞定。

<oracle.manageddataaccess.client>
<versionnumber="*">
<settings>
<settingname="TNS_ADMIN"value="X:\Oracle\..略...\Network\Admin"/>
</settings>
<version>
<oracle.manageddataaccess.client>

花了近三小時,學到一則教訓

設完環境變數後,要先IISRESET才會在ASP.NET生效 句點

刻骨銘心,沒齒難忘!

WCF探勘1-WAS與net.tcp

$
0
0

前陣子對.NET Remoting、WCF、Web API做了評估,重新肯定WCF在Client/Server皆為.NET情境下的優越性,決定展開一系列對WCF較深入的研究,重新評估這個被我嫌棄多年的技術。

我深信「開發者擁有選擇或棄用某項技術的決定權,但必須基於理性分析評估優劣,而非單憑模糊印象或靠某種說不上來的感覺」。為此,雖然過去對WCF繁瑣易錯的設定方式從未有過好感,還是該咬牙征服它,才有資格大聲說「我決定不用WCF,是因為Blah Blah…」

但歷經一番研究,發現WCF在.NET 4.0之後有不少改良,設定上簡化不少,機車度不若以往,大家可以順著文章看看,或許對WCF也會有所改觀。

過去對WCF的HTTP/HTTPS用法接觸較多,這次重新探勘將聚焦於幾項WCF優於Web API的特色,包含:

  • WCF TCP傳輸
  • TCP與HTTP之傳輸效率比較
  • 雙工服務模式(Web API雖然也有SignalR助陣,但WCF內建,勝!)

支援多種傳輸通道是WCF一大特點:WCF內建支援HTTP/HTTPS、TCP、Named Pipe、MSMQ等多種傳輸協定,且標榜不改程式光調設定就能自由切換。不同傳輸方式各有優劣,MSDN有篇文章羅列各種傳輸方式的比較,在此只簡單摘要,想深入了解可參考原文:

  1. HTTP/HTTPS:可通過大部分防火牆及網路限制,穿透力最強,且享有較豐富的偵察、除錯工具支援,Request間不具狀態性,需自行保存。
  2. TCP:Client與Server直接建立TCP連線,傳輸效率最好
  3. Named Pipe(具名管道):效率與TCP相彷,適用單一電腦不同程式間的WCF溝通,可阻絕來自其他電腦的存取。
  4. MSMQ:架構最複雜,但能確保Request及Response使命必達,不管天崩地裂都會交到對方手上,適用對通訊過程出錯容忍度很低的場合。(可以想見,必須付出效能作為代價)

WCF有多種傳輸方式可供選擇是好事,但若要求開發者自己打點TCP/Named Pipe/MSMQ的連線細節,會讓人很想死。從IIS7起,IIS附加了WAS(Windows Process Activation Service),讓IIS 有能力支援HTTP/HTTPS以外的WCF傳輸協定。只需比照Web Application將WCF網站掛上IIS,再設定該網站支援的傳輸方式,就能透過TCP/Named Pipe/MSMQ等管道進行WCF溝通,非常神奇!

以下我們用一個範例來看WAS如何讓WCF支援TCP協定?

  1. 由於WAS依附於IIS,開發機最好安裝IIS,並開啟「.NET Framework 4.5進階服務/TCP啟用」功能以利測試:


    不同Windows版本安裝功能的介面有所差異,上圖以Windows 8.1為例。下面再附上2012R2的安裝範例,可使用新增角色與功能精靈安裝:


  2. 開啟Visual Studio,新増一個WCF Service Application(.NET Framework版本請選4.5以上)


    自.NET 4.0起,WCF大幅簡化config設定要求,若可沿用預設值,不必像過去寫一堆behavior、endpoint… 繁雜設定,設定檔清爽很多。


  3. 將WCF Service Application目錄掛成IIS網站應用程式,打開網站應用程式的「進階設定」功能,「已啟用的通訊協定」屬性預設為"http",改為"http,net.tcp",這樣子WCF服務就同時支援TCP與HTTP兩種協定囉。


  4. 在Visual Studio裡加入另一個WCFClient Console Application測試專案,使用「Add Service Reference」加入WCF參照:


    由於WCF Service已掛在本機IIS,URL輸入httq://localhost/wcfwas/service1.svc即可,Visual Studio會自動識別WCF資訊並修改WCFClient的app.config設定。


  5. 由於WcfWas被設成"http,net.tcp"雙頻,加入參照時Visual Studio已貼心地在app.config加入兩組endpoint設定,第一組是常見的HTTP設定,第二組(黃框)則是走net.tcp協定,請記住兩組endpoint的name屬性,稍後會用到。


  6. 傳統WCF的new Service1Client()寫法在雙頻時不適用,因為.NET無法確定你想走HTTP還是TCP。


    正確寫法需在建構時指定endpoint名稱,以下程式示範分別用TCP及HTTP呼叫GetData方法,都順利取得結果。

 

在這篇文章中,我們見證了「WCF+WAS + Visual Studio 讓我們只調設定不加程式就支援不同傳輸管道」的神奇效果,而新版WCF的設定檔簡化許多,不若以往可僧,感覺是個不錯的開始。(代誌沒有傻人想得哈尼甘單… 讓我們繼續看下去)

WCF探勘2-跨機器執行net.tcp

$
0
0

本機測試完畢,準備將WCF部署到VM進行更多觀察,才誇WCF 4設定精簡,馬上踩到小圖釘。

沿續前文範例,將WcfWas部署到一台Windows 2012R2上,比照本機操作安裝「TCP啟用」設定IIS使用「http,net.tcp」協定,執行WCFClient程式,HTTP傳輸OK,執行net.tcp時產生「伺服器已經拒絕用戶端認證」錯誤。(英文:The server has rejected the client credentials)

爬文得知net.tcp預設連線時需完成身分驗證,不想跟驗證關卡糾纏(沒辦法,過去有太不好的回憶),決定先走匿名存取,修改WCFClient app.config,在netTcpBinding的bindingConfiguration中加入<security mode="None" />:

再試一次,錯誤訊息又變成「通訊端連線已中止。這可能是因為處理訊息時發生錯誤、或是遠端主機超過收逾時,或是基礎網路資訊問題所致。本機通訊端逾時是'00:00:59.1319179'。」(英文:The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '…')

缺乏經驗如我、一開始被逾時、基礎網路資源這些字眼迷惑,以為與封包大小、伺服器執行卡住或防火牆有關,爬文章後得到一項心得:「net.tcp出了任何差錯幾乎都是傳回這個鳥訊息」,這段訊息唯一告訴我們的就是「哦哦!某些地方壞掉了,快修好它」,問題是Server/Client端都找不到線索,這時我開始體會到前一篇文章說「HTTP比TCP容易偵錯」的意義。:P

猜想問題跟認證有關,剛才在WCFClient設好security mode="None",有可能Server端也需要設定。設定的過程,我得到第二項體會「WCF4的設定檔簡化是假象,一旦不用預設值就現出原形」!

如下圖所示,為了讓net.tcp免除身分驗證,得定義NetTcpBinding的Binding Configuration,宣告service的BasicHttpBinding及NetTcpBinding兩個endpoint,並將netTcpBinding的bindingConfiguration指向免驗證的額外設定。當net.tcp使用預設驗證,這一整段都可省略;一旦有部分需客製調整,得整段補上。

還有另一種做法是將netTcpBinding的security預設值改掉,如此可省掉<services>…</services>那一大段。寫法是binding不要指定name屬性(如下所示),代表要將netTcpBinding的預設值改為security mode="None":

<netTcpBinding>
<binding>
<securitymode="None"/>
</binding>
</netTcpBinding>

但要注意修改預設值將影響所有WCF服務,有可能讓其他服務及endpoint不知不覺套用安全性較低的設定。

經過這番設定,終於成功完成跨機器的HTTP與TCP WCF呼叫囉~

如果想使用啟用驗證跑net.tcp該怎麼做?netTcpBinding預設的security mode為Transport,採Windows認證,所以只要確保「執行WCF Client的身分可以通過WCF Server的認證」即可過關,跟解決HTTP的Windows或MSDTC認證問題原理相似,得確保執行WCF Client的帳號能自動登入WCF Server所在Windows,一般有兩種做法:

  1. Client與Server都加入網域,使用網域帳號執行WCF Client
  2. 在Server建立相同名稱、相同密碼的帳號

WCF採Windows認證,非常適合應用在Windows Form/Console Application作為Client的情境,除了得知endpoint位址,使用者還必須具備Server認可的帳號,才允許使用WCF服務,安全大大加分。但若WCF Client為ASP.NET,指定執行身分不利管理,也易衍生額外風險,我較常用的做法是採security mode="None",但配合鎖定ASP.NET Server的IP,亦可維持一定程度的安全性。

WCF探勘3-WCF設定檔編輯器

$
0
0

WCF設定的繁瑣難搞向來惡名昭彰,這回重新評估WCF,意外發現浪子回頭的一絲曙光 XD

Visual Studio 2012起默默在設定檔的右鍵選單增加了「Edit WCF Configuration」選項:

嘩!有GUI的WCF設定編輯器耶~(正式名稱為Microsoft Service Configuration Editor)

如上圖所示,Services/WcfWas.Service1下的兩個Endpoint對照到我們web.config(請參考下圖)裡的兩個endpoint設定,而BindingConfiguration屬性提供下拉選單可選取定義好的NoneSecurityNetTcpBinding,減少出錯機率。

編輯器的另一項優點是可以看到完整的屬性清單及預設值,例如我們在endpoint未指定的isSystemEndpoint、listenUriMode,在編輯器裡可看到它們的值分別是False及Explicit。

另外,編輯器也讓我們看到完整的ProtocolMapping清單:

其實Visual Studio在編輯web.config/app.config時已提供足夠貼心的Intellisense(如下圖),但由編輯器可一窺整個設定檔的全貌,讓開發人員更容易了解及掌握狀況,要新增Endpoint/Service時也有精靈導引,讓新手不致不知所措。

GUI編輯器的出現讓WCF設定粗重髒活變得文明,但不變的事實是:搞不懂Endpoint、Binding、Behavior一堆參數的意義,就算儀表板升級再升級,不懂收襟翼看水平儀,你還是不會開飛機。吾人還是該回歸冷靜,繼續鑽研奧妙的WCF設定。


WCF探勘4-HTTP與TCP傳輸量比較

$
0
0

稍早我們見識到net.tcp不容易偵錯的黑暗面,但net.tcp的好處在於通訊協定簡單,較HTTP簡潔輕巧,資料採二進位格式,體積比SOAP XML小。由於資料傳輸較少,預期會有較好的執行效能。但以上所說都是按理推想,我對二者資料傳輸量的真實差異感到好奇,便用Microsoft Network Monitor幫它們做了一次斷層掃瞄,分享給對二者差異有興趣的朋友。

沿用先前的範例,在WCF Client端做一點修改:

程式透過HTTP及TCP各呼叫一次GetData(0),程式執行同時開啟MNM側錄網路封包。得到HTTP傳輸內容如下:

分析其傳輸過程為:

  1. 建立TCP連線:三個封包 52+52+40=144 bytes
  2. Client傳送HTTP Post Header:395 bytes
  3. Server回覆Status 100請Client繼續送:65 bytes
  4. Client送出Post本文,以SOAP包裝的Value參數0:197 bytes
  5. Server傳回以SOAP包裝的執行結果"You entered: 0":541bytes

排除TCP連線建立過程,整個呼叫涉及四個封包,總傳輸量為395+65+197+541=1198 bytes

接著來看TCP傳輸:

圖中出現的NMF是.Net Message Framing Portocol的縮寫,是一種可取代SOAP XML的二進位資料封裝格式,用於WCF TCP及MSMQ傳輸,它被定位成非公開的WCF內部實作黑箱,所幸在MSDN仍找得到相關文件,這堆封包才沒變成天書。

TCP呼叫的傳輸過程為:

  1. 建立TCP連線:三個封包 52+52+40=144 bytes
  2. Client送出一個NFM Preamble Message:93 bytes
  3. Server回應Preamble Ack Message:41 bytes (下一個TCP控制封包略過不計)
  4. Client送出內含呼叫參數的Sized Envelop Message:378 bytes
  5. Server回應內含執行結果"You entered: 0"的Sized Envelop Message:212 bytes (如下圖)

排除TCP連線建立過程,整個呼叫也涉及四個封包,總傳輸量為93+41+378+212=724 bytes

在這個單次呼叫案例中,TCP傳輸比HTTP傳輸少了474 bytes,資料量只有HTTP傳輸的60%。

接著我們再調整程式,建立Service1Client後連呼叫三次GetData()

比較二者傳輸:(我用紅黃藍底色標出三次呼叫的封包)

 

結果顯示NFM的Preamble Message及Preamble Ack Message只需傳送一次,之後每次GetData()只需兩個封包,而且我還注意到第二次跟第三次的Request與Response的封包都比第一次小(378->263、212->116),比對內容,第二、三次的封包省略掉某些內容(可能先前傳過就不再傳,而HTTP第一個封包也從395->371,其他未變),因此三次呼叫的傳輸量為:

HTTP:395+65+197+541+371+65+197+541+371+65+197+541=3546

TCP:93+41+378+212+263+116+264+116=1483

TCP的傳輸量只有HTTP的41.8%。由此可得,TCP在傳輸效率上大勝HTTP,不難想見此一資料量差距對執行效能的影響。

結論:WCF應用於Intranet時,若Client/Server未受防火牆阻隔,TCP是較佳的選擇。

WCF探勘5-預設Binding介紹

$
0
0

前面的文章出現過BasicHttpBinding、NetTcpBinding,又提到「BasicHttpBinding接受匿名存取,NetTcpBinding需要認證」的預設行為,後續還會看到一堆BlahBlahBinding,這裡花點時間做個簡單介紹。

BasicHttpBinding、NetTcpBinding這些都是WCF內建預設定義好的服務設定組合,裡面已定義好通訊協定、安全等級、Session支援、Transaction、雙工(Duplex)支援等特性,當然,不足時WCF也允許開發者自己定義特殊組合,做成自訂Binding。下表是完整的WCF預設Binding清單及其支援狀況。

表格資料來源:Configuring System-Provided Bindings

表格中各欄位的意義如下:

  • Interoperability Type:該Binding所支援的公開協定或技術
  • Security:共有五種,欄位列出的是可支援的項目,括號括起來的是預設選項
    • None 不加密也不認證
    • Transport 在傳輸層實現安全要求(例如:SSL、建立連線時的身分認證) ,認證種類有None/Basic/Windows/NTLM/Digest/Certificate等選擇,跟IIS的身分管控類似
    • Message 在訊息層實現安全要求,在訊息內加入身分認證、簽章資訊,也可對訊息加密。訊息層可用的認證種類有None/Windows/Username/Certificate/Issue Token。
    • Mixed 又稱為TransportWithCredentials,雙管齊下,在訊息層處理身分認證、在傳輸層處理加密及防止竄改
    • Both 訊息層跟傳輸層都加入安全機制,只有MSMQ辦得到
  • Session:是否支援Session
  • Transaction:是否啟用交易模式
  • Duplex:是否允許雙工(Server端呼叫Client端的方法),要做到雙工必須支援Session

由上表可知,之前測試跨機器使用NetTcpBinding得修改Security Mode是因為它的預設值為Transport,在傳輸層啟用了身分認證。

最後,簡單歸納各Binding的適用場合:

  1. BasicHttpBinding:HTTP/HTTPS+XML方式傳輸,依循WS-Basic標準,適用ASMX這類舊式服務,安全性較差
  2. WSHttpBinding:HTTP/HTTPS,適合非雙工作業
  3. WSDual HttpBinding:HTTP/HTTPS,適合雙工作業,使用SOAP傳輸
  4. WS2007HttpBinding:HTTP/HTTPS,支援Session/ReliableSession/TransactionFlow
  5. WSFederationHttpBinidng:HTTP/HTTPS,支援WS-Federation,適用使用AD Federation的環境
  6. WS2007FederationHttpBinding:HTTP/HTTPS,WS2007HttpBinding支援WS-Federation
  7. NetTcpBinding:TCP,跨機器,網路可直通,講求高效率
  8. NetPeerTcpBinding:TCP,多機器間溝通
  9. NetNamedPipeBinding:Named Pipe,同機器不同程式間以WCF溝通
  10. NetMsmqBinding:MSMQ,跨機器且需要可靠的傳輸機制
  11. WebHttpBinding:HTTP/HTTPS,以HTTP Request取代SOAP Message
  12. MsmqIntegrationBinding:WCF與MSMQ現有程式溝通

BasicHttpBinding的通透性最高,可以像ASMX一樣跨平台提供服務,Net*Binding則限定Client/Server都是Windows才能運作。至於Ws*Binding,背後依循了公開的MTOM、WS-Security等規範,其他平台廠商或平台只要支援該規範,就可整合應用實現SOA跨平台整合的夢想。只是,技術趨勢三不五時就轉風向,上回聽到SOA這名詞彷彿已是唐朝的事,至今是否還有人玩是個好問題,呵~ 這一系列我只想乖乖研究WCF在Windows平台的應用,跨平台還是交給Web API吧!

結論:要向前相容就用BasicHttpBinding/WSHttpBinding,要雙工只能選WSDualHttpBinding、NetTcpBinding、NetNamedPipeBinding;若WCF Server及Client都是Windows,Client/Server都在同一台機器又不想對外開放可選NetNamedPipeBinding,跨機器就用NetTcpBinding。如果有Reuqest/Reponse傳送過程不容許任何差錯,則可考慮NetMsmqBinding。

我的跑步新裝備-Fenix 3使用心得

$
0
0

從認真跑步起,GPS錶就是我不可或缺的重要裝備,一開始只是不想在操場繞圈要扳手指計數,後來學會看Pace抓配速,最後則是用來盯緊心跳,意不在挑戰最大心率再創佳成績,而是年紀老大不小,得當心操過頭一路跑去見佛祖。

第一支GPS錶也是伴我最久的大頭錶GH-625M,硬式心跳帶,於2012年四月入手。當初選它著眼於GPS精準,C/P值極高,至於造型笨重樸實題,就靠「人帥」來克服囉 。XD 大頭錶伴我兩年長征數千公里,撐完田中馬錶帶斷裂,經一番評估買了第二支錶,Bryton Cardio 40H。

40H比大頭錶輕巧精美,有手錶模式可日常佩戴,附軟式心跳帶且可計步頻有節拍器,C/P值也很高。可惜它的GPS誤差比大頭錶高出許多(操場繞圈尤其明顯),但最致命的點在於續航力,五小時不到就電力耗盡的經驗讓人發毛,不管山路豔陽路濕風狂都要跑進430,臣妾做不到啊~ 於是只能將40H獻給女王,繼續換回大頭錶。歷經兩次斷錶帶,按鈕故障換錶殼,送修兩回,大頭錶畢竟老了,按鈕反應日益遲鈍,硬式心跳帶不如ANT+軟式心跳帶舒適精準,敗家之心蠢蠢欲動。

上回評估時,雖對Garmin Forerunner神往,考慮它走的是高級專業的檔次,自己一介弱雞無顏高攀,打算待Sub 4挑戰成功後再考慮它!時間久了,自己倒是愈來愈明白,憑我三腳老貓的實力,也許一輩子都無緣摸到Sub 4,退出跑壇之日沒試過GPS錶界的BMW,豈不遺憾?愛要即時,同理,「敗」也要即時!(謎:理由很薄弱啊)

去年曾有個機會,相中Forerunner 620要下手之際,得知Garmin還有另一款登山跑步滑雪單車游泳多用途的Fenix,一見之下驚為天錶,大呼這才是我的菜!金屬錶殼,電力持久,日常生活長戴也OK,日後重操舊業回去爬山也很適合。無奈Garmin遲遲不在台灣發行,不甘心買水貨享受不到傳說中高水準的售後服務失去買Garmin的意義,祭出「戒急用忍」,靠大頭錶再撐著囉。

今年Fenix推出第3代,正式在台灣發售,等了一年,第三支GPS錶終於入手。下手前則為了要不要選藍寶石款猶豫再三,結婚買對錶時才第一次聽到藍寶石錶面這名詞,戴了近十年,錶身進過水錶面也斑駁,但錶面很爭氣,連半絲刮痕都沒有,徹底打破我對歲月一定會在錶面留下痕跡的刻板印象。手錶每天都要戴,不必小心翼翼擔心刮傷,才是完全的舒適自在。只是藍寶石款價差三千,而3C電子產品不比傳統石英錶能戴上數十年,至少電池就活不了那麼久,到時只怕沒得修,徒留錶面仍完美無瑕的廢鐵也是枉然,把錢省下來不知可買多少保護貼,太空灰,就決定是你了。

網路上有詳盡到如同讀者自己親身把玩的開箱文,在此不班門弄斧,只歸納我的使用心得。

上面這張照片最能突顯我愛Fenix的理由。它極其成功地隱藏自己是一支GPS運動錶的事實,這個造型這種錶面,誰會想到它是跑步爬山騎單車用來計里程測心跳用的工具?日常生活24小時佩戴完全不會突兀,就算戴著它出席國宴、APAC會議,或是奧斯卡頒獎典禮依然得體。(先生您哪位?)82克的金屬錶殼比傳統塑膠錶重,但不致成為跑步的負擔,換取耐用耐看,我覺得很值得。

  1. GPS精準度

    常跑操場,GPS棈準度是我在意的點。依感覺,Fenix 3的GPS精準度跟大頭錶相當,甚至準度還高一些(畢竟晶片差了好幾個世代,又有金屬錶殼結合天線)。但GPS棈確度跟天候、地形有關,偶爾仍會突鎚(例如下圖右下的大暴走),Fenix的精度水準已符合我的要求,而它的GPS定位速度極快,幾乎都能在30秒內完成,比大頭錶動輒花一兩分鐘快上許多。

  2. 電池續航力

    以我的使用習慣,開WiFi自動更新+關藍牙/App 通知+開計步器+開睡眠品質偵測+每天5K+週末10-20K,大約一週充一次電即可,每次充電不用兩小時,續航力不錯。

  3. 活動自動上傳

    這功能我很愛!跑完步不用手動選取上傳,手錶會自動連上無線網路把資料送到Garmin Connect網站,從網頁跟App都看得到。馬拉松世界也支援自動從Garmin Connect抓資料,這是用大廠產品的優點之一吧!不過,我還是寫了外掛將Connect的繞圈資料轉成圖表再丟到馬拉松世界,Coding4Fun Again。

  4. 睡眠品質偵測

    手錶內建一般手環都有的睡眠品質偵測,大致就是由翻身頻率或手臂移動判定「深度睡眠」或「淺層睡眠」。手動切換睡覺及起床狀態比程式自動判斷精準。不太肯定深淺眠時數的真確性及對應睡眠品質的相關度,但有個模糊感覺「統計資料之深度睡眠時少時比較爬不起來」。:P

  5. 計步器功能

    手錶也內建計步功能,可設定每天要走步數的目標,還有靜坐過久該起來活動一下的提醒。個人是覺得沒什麼用處,目標步數幾乎都靠早上晨跑補足,上班時間走動機會有限,收到起身提醒就馬上中斷工作,得付出Coding思緒被打斷的代價太高,還是自己控制定期活動時機比較實際。

  6. 系統穩定性

    使用一個多月只當過一次(跑完5K要存檔時自己重開機,記錄遺失),一次更新後需重新對應心跳帶,目前為止已遇過兩次韌體更新(手錶自動下載好,問你要不要安裝,頗方便),系統穩定度還不錯。

  7. 通知服務

    手錶可跟平板或手機上的Garmin Connect App整合,透過藍牙傳送通知(像Apple Watch一樣)。它的運作原理是將平板手機本身的系統通知訊息一五一十地同步到手錶上,所以像LINE訊息、電子郵件、簡訊、電話,只要App會發通知,手錶就會震動及顯示訊息。有任何通知就震動,對我來說太耗電又不利工作專注,我選擇不用。

  8. 氣壓計(高度計)、指南針、溫度計、返航導引

    Fenix被定義成戶外運動手錶,不限定跑步,還支援游泳、滑雪、登山、自行車,所以有氣壓計(偵測暴風雨)、氣壓式高度計、溫度計、電子羅盤、天氣預報(搭配App)、日落日出時間提醒等額外功能,另外也可記憶路徑後,顯示在錶面讓你沿著原路走回去的導航模式,不能取代GPS地圖,但在爬陌生山路想原路折返時挺實用。這部分都還沒實際試用,未來如有機會帶著它爬山再試。
    以下這張照片是蘇迪勒颱風過境,兩天內氣壓由1002下殺到974再回升到1000的記錄。

  9. HRM-RUN心跳帶

    Fenix 3跟920XT/Forerunner 620一樣,支援HRM-RUN心跳帶,除了心率外,還支援步頻偵測(比手錶內建準確),另外可測量垂直振幅是獨門特色,有利於觀察自己的跑姿。只是這半年練跑愈來愈隨興,對跑姿、步頻不怎麼上心,這項進階功能就放著吧!倒是Garmin的心跳帶不錯戴,比硬式好不在話下,比起Bryton的似乎又更柔軟服貼,大部分時間會忘記它的存在,但我也說不上來關鍵差在哪裡。:P

  10. Connect IQ

    Connect IQ可以想像成「為Garmin手錶寫的App」,如果你對錶面設計不滿意,可以上網找網友設計好的錶面,或者自已做一個玩玩。透用SDK,開發者還可寫出各式花式應用,例如:控制手機平板音樂播放之類的,感覺可玩的東西很多。不過我Coding4Fun待辦項目排很長,排定時間又少,若沒臨門一腳的機緣或衝動,很久之後才會輪到它吧! XD

以上就是我的不專業心得。

WCF探勘6-OperationContract(IsOneWay=true)的用途

$
0
0

在範例程式看到OperationContract(IsOneWay=true),不求甚解以為是指單向呼叫(傳回值是void)跟著亂抄亂用(錯誤示範,大家不要學),搞出一些奇怪現象才回頭查,發現觀念錯得離譜。

IsOneWay是用來定義單向合約沒錯,但重點在「呼叫端呼叫WCF作業,但不期待收到任何回應」,其真正意義更傾向「Fire and Forget」(射後不理)。因此,指定OperationContract(IsOneWay=true)的方法傳回值應為void,最重要的差別在於呼叫該方法後,不會等待作業完成就繼續往下執行。

用一個範例來驗證。IService1提供兩個方法,Delay5Seconds()及Delay5SecondsOneWay(),差在後者宣告為[OperationContract(IsOneWay=true)]:

using System.ServiceModel;
using System.Threading;
 
namespace WcfTest
{
    [ServiceContract]
publicinterface IService1
    {
 
        [OperationContract]
void Delay5Seconds();
        [OperationContract(IsOneWay = true)]
void Delay5SecondsOneWay();
    }
 
publicclass Service1 : IService1
    {
 
publicvoid Delay5Seconds()
        {
            Thread.Sleep(5000);
        }
 
publicvoid Delay5SecondsOneWay()
        {
            Thread.Sleep(5000);
        }
    }
}

呼叫端程式如下:

staticvoid Main(string[] args)
        {
            var sc = new WcfTest.Service1Client();
            Console.WriteLine("{0:HH:mm:ss.fff} Start", DateTime.Now);
            sc.Delay5Seconds();
            Console.WriteLine("{0:HH:mm:ss.fff} Mid", DateTime.Now);
            sc.Delay5SecondsOneWay();
            Console.WriteLine("{0:HH:mm:ss.fff} End", DateTime.Now);
            Console.Read();
        }

執行結果,呼叫DelaySeconds()時會等5秒程式才往下執行,而Delay5SecondsOneWay()則不等待Server端執行就繼續往下走。

因此,針對單純下達執行命令,結果或狀態會以其他管道回饋的作業,或是執行耗時的長期作業,可使用IsOneWay旗標滿足Client端的非同步需求。

WCF探勘7-XML序列化資料量觀察

$
0
0

早先我們觀察過WCF HTTP vs TCP的傳輸量差異,該測試呼叫GetData()作業傳入數字接回字串,並不算真的用到WCF的DataContract/DataMember序列化功能,故這次改聚焦在物件資料的序列化上,再做一次比較。

我小幅改寫Visual Studio WCF專案範本的CompositeType,增加一個IntValue。

    [DataContract]
publicclass CompositeType
    {
bool boolValue = true;
string stringValue = "Hello ";
int intValue = 1;
 
        [DataMember]
publicbool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }
 
        [DataMember]
publicstring StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
 
        [DataMember]
publicint IntValue
        {
            get { return intValue; }
            set { intValue = value; }
        }
    }

GetDataUsingDataContract()也做一點改寫,傳回值改成CompositeType[],傳回資料筆數由傳入的CompositeType.IntValue決定,藉此彈性控制傳回結果筆數,以觀察資料筆數多寡對資料量的影響。

public CompositeType[] GetDataUsingDataContract(CompositeType composite)
        {
if (composite == null)
            {
thrownew ArgumentNullException("composite");
            }
            var res = new List<CompositeType>();
for (var i = 0; i < composite.IntValue; i++)
                res.Add(new CompositeType()
                {
                    BoolValue = composite.BoolValue,
                    StringValue = "Item" + i,
                    IntValue = i
                });
return res.ToArray();
        }

Client端則修改如下,改呼叫GetDataUsingDataContract(),透過IntValue要求Server傳回1、16、64、128或256筆結果,取得結果後加總各筆資料的IntValue並檢視最後一筆的StringValue,簡單驗證資料正確性。

staticvoid Main(string[] args)
        {
string[] bindingTypes = newstring[] {
"BasicHttpBinding_IService1",
"NetTcpBinding_IService1"
            };
foreach (var bindingType in bindingTypes)
            {
                var tsc = new WcfWas.Service1Client(bindingType);
                var compData = new WcfWas.CompositeType()
                {
                    BoolValue = true
                };
                var counts = newint[] {1, 16, 64, 128, 256};
for (int i = 0; i < counts.Length; i++)
                {
                    compData.IntValue = counts[i];
                    var res = tsc.GetDataUsingDataContract(compData);
                    var sum = res.Sum(o => o.IntValue);
                    var txt = res.Last().StringValue;
                    Console.WriteLine("Items Count={0}, Sum={1}, String={2}", 
                        res.Length, sum, txt);
                }
            }
            Console.Read();
        }

執行結果如下,顯示內容沒什麼意義,期間的資料傳輸量才是重點。

Items Count=1, Sum=0, String=Item0
Items Count=16, Sum=120, String=Item15
Items Count=64, Sum=2016, String=Item63
Items Count=128, Sum=8128, String=Item127
Items Count=256, Sum=32640, String=Item255
Items Count=1, Sum=0, String=Item0
Items Count=16, Sum=120, String=Item15
Items Count=64, Sum=2016, String=Item63
Items Count=128, Sum=8128, String=Item127
Items Count=256, Sum=32640, String=Item255

所以我們開啟MNM,觀察HTTP及TCP傳輸的封包記錄如下。黃底部分是每次Server用來回傳結果的封包,旁邊已加註該次傳回資料筆數方便對照。

BasicHttpBinding

NetTcpBinding

整理結果如下,中間兩欄的數字為HTTP及TCP傳回不同結果筆數產生資料量(Bytes),最後一欄則為TCP資料量除以HTTP資料量的比例。

數字有點可疑,BasicHttpBinding使用XML序列化,NetTcpBinding採二進位序列化,但差距比想像的小很多,且筆數愈多,差距愈小,在256筆時,資料量比例約為10:8,與直覺出入甚大。莫非與HTTP壓縮有關?檢視封包內容,果然看到SOAP XML被IIS壓縮,實際上傳送的不是XML,而是被壓縮過看似亂碼的資料。

為觀察未壓縮的原始資料量,在IIS關閉動態內容壓縮:

重新測試一次,這回可在封包觀察到SOAP XML的模樣,很肥!

而用XML傳256筆資料要花35,118 bytes。

重新統計IIS未壓縮模式下的SOAP XML傳輸,除了單筆資料外,不管筆數多寡,TCP資料量固定維持SOAP XML的15%,符合我們的認知。

實務上,IIS會啟用動態壓縮,最後的實驗純屬好奇並非常態,第一次做的統計才接近真實狀況。也就是說,在IIS壓縮加持下,HTTP+SOAP XML的資料量膨脹不如想像嚴重(例如:256筆時僅多21%),但每次傳輸耗用較多封包數,資料量較大仍是事實,基於效能考量,Intranet的純Windows應用仍應優先考慮TCP。

Viewing all 2433 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>