Wednesday, June 27, 2007 - 文章

KB-Connection Closed Exception of FtpWebRequest

幾天前我寫了一篇Post介紹如何用System.Net.FtpWebRequest開發一個支援續傳功能的FTP Client。

在專案中開始使用它來傳大檔時,卻發現不知FtpWebRequest是不是為了炫耀它的續傳功能,在花了半小時傳完一個400MB的ZIP檔之後,都會觸發一個"The underlying connection was closed: An unexpected error occurred on a receive."的Exception,但是檔案的大小正確,代表每一個Byte都順利傳回來了,但就是逼得我得Run第二次"續傳餘下的0 Byte",程式才會正常結束。

同樣的程式若下載的是5MB的ZIP檔,則不會發生問題。因為"Size Matters",所以我推斷可能可能與Timeout之類的屬性有關。雖然發現FtpWebRequest.Timeout官方文件寫錯了,預設值是100,000ms而不是無限大,但是測試發現它並不是導致問題的原因。

後來想到另一件事,傳統的FTP軟體的訊息視窗中總會看到定期送出NOOP指令來保持連線不被Server切斷(依據這篇文件,一般的FTP Server,只要5分鐘沒收到Control Connection傳來的任何訊息,就會中斷連線),而FtpWebRequest並沒有實做這類的機制。因此,只要下載時間超過FTP Server的Idle Timeout,在Stream.Close()會觸發檢查Connection的Logic,雖然資料已順利傳完,但由於Control Connection已經Idle過久被切了,於是拋出以下錯誤:

Error: The underlying connection was closed: An unexpected error occurred on a receive.
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.FtpWebRequest.RequestCallback(Object obj)
   at System.Net.CommandStream.Abort(Exception e)
   at System.Net.CommandStream.CheckContinuePipeline()
   at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
   at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.FtpDataStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()

事到如今,要解決這個問題有幾種手段:

  1. 去找有實作NOOP Keep Alive機制的3rd Party .NET Component,如: Rebex FTP for .NET,但需考量採購成本
  2. 自己實作NOOP機制,但需要處理複雜的Control Connection與Data Connection問題,有陷入"重新發明輪子"迷思的疑慮,還不如去買現有元件
  3. 由於資料已順利傳完,實際上我們已不再需要Control Connection,而是Stream.Close()時所觸發的制式檢查,那麼忽略Connection已斷的事實又何妨?
    雖說將Exception納為正常流程有違反一般的效能準則,但進入Exception流程所多出的時間應該不及1ms,相較於超過5分鐘的下載時間,倒是可以被忽略。

偷懶的我決定用方法3),將先前Post程式的片段修改如下:

//2007-06-27 因為沒有NOOP的Keep Alive機制
//當下載時間超過FTP Server的Idel限制,會在Stream.Close()時
//發生以下錯誤:
//The underlying connection was closed: 
//An unexpected error occurred on a receive.
//若檔案已順利下載完成,則忽略此一WebException
bool bFinish = false;
 
try
{
    using (Stream stm = ftpResp.GetResponseStream())
    {
        //由於檔案頗大,因此分Block寫入
        byte[] buff = new byte[2048];
        int len = 0;
        //取得要下載的檔案大小
        long totalSize = ftpResp.ContentLength;
        while (fs.Length < totalSize)
        {
            len = stm.Read(buff, 0, buff.Length);
            fs.Write(buff, 0, len);
        }
        fs.Flush();
        //檔案完整傳完, bFinish=true
        bFinish = (fs.Length == totalSize);
        fs.Close();
        //下載時間過久時,stm.Close()會觸發Exception
        stm.Close();
    }
}
catch (System.Net.WebException we)
{
    //若未傳完才要觸發Exception
    if (!bFinish) throw we;
}
ftpResp.Close();
KB-FtpWebRequest.Timeout Default Value

用FtpWebRequest下載大檔案時,出現了Connection Closed Exception,除錯過程中懷疑是逾時間題,所以我找到FtpWebRequest有以下兩個Timeout相關屬性:

  • Timeout: 指完成Request的逾時限制,文件上說預設值是Infinite(-1),永不逾時。
  • ReadWriteTimeOut: 每次寫入或讀取Stream動作所允許的Timeout。

文件說Timeout的預設值是無限大,但為了謹慎起見,我還是進入Debug Mode,看了FtpWebRequest Instance的Timeout值,100000!! What? 不是應該是-1嗎?

這激發了我的好奇心,套足柯南的名言: "真相只有一個!",再挖進去,用Reflector解析了System.Net.FtpWebRequest,看到以下的Code:

static FtpWebRequest()
{
    m_AsyncCallback = 
    new GeneralAsyncDelegate(FtpWebRequest.AsyncCallbackWrapper);
    m_CreateConnectionCallback = 
    new CreateConnectionDelegate(FtpWebRequest.CreateFtpConnection);
    DefaultFtpNetworkCredential = 
    new NetworkCredential("anonymous", "anonymous@", string.Empty, false);
    s_DefaultTimeout = 0x186a0;
    s_DefaultTimerQueue = 
    TimerThread.GetOrCreateQueue(s_DefaultTimeout);
}

Bingo! 0x186a0==100,000,就是Debug時看到的數字。

還在想如何通報此一文件錯誤,就驚喜地發現新版的MSDN官方文件提供了Developer Community直接在文件上寫註解(Community Content)的功能。於是,我在MSDN Library留下了我第一個腳印

Web 2.0時代,Content Provider與Consumer的界線愈來愈模糊,另一場資訊革命正悄悄展開!

Posted 27 June 2007 02:41 PMJeffrey | no comments
Filed under:
TIPS-Remember User's Preference with Cookie

在Key單UI的實務上,有個有趣的現象。假設Key單Web介面中有個下拉選單(DropDown, <SELECT>),張三因為承做甲業務,所以永遠都選第三個選項,而李四較常在處理乙業務,所以總是選第五個選項。如果介面在顯示時可以預設就停在使用者最常用的選項上,每次Key單時就可以少敲幾次鍵。

此時有幾種設計方法: 第一種是建立User的Profile,由其負責的業務來判斷預設選項,再不然就是開放User設定自己偏好的預設選項。我個人更偏好一種更直覺更簡單的做法---用Cookie記住User上回選什麼就好了,不用在Server/DB上找地方存User設定,也不用擔心User可能一陣子做甲業務,一陣子搞乙業務,設定得調來調去。總之,上次輸入什麼,這次就預先帶出什麼值,KISS(Keep It Simple and Stupid)永遠是最美妙滴!!

底下是一段用ASP寫的Sample Code(ASP.NET裡的寫法大同小異),Product Category下拉選單會用Cookie記住你上次選的值,下次再進入頁面時會預設停在上面。

有幾點補充一下:

1) 記得要設Cookie的Expires屬性,否則Cookie會在Browser關掉後立刻消失。

2) 設好Cookie後,每次發Request到Web時都會夾帶你的Cookie內容(是的,每個Request都要!),所以別存太多資料在Cookie中(記住! 它只是Cookie,別把它當Pizza),以免形成傳輸負擔。

3) oSel.options[i].value=='<%=sDefaultProdCatg%>'的寫法記得要Escape掉單引號,至於換行符號,應該是可以偷懶不處理囉。

<% Option Explicit %>
<%
Dim sDefaultProdCatg
Const cookieName="AfaProdKeyIn"
If (Request("txtClientName")<>"") Then 'Postback
 'Set Expires to keep this cookie for one month, 
 'If Expires is not set, cookie will be gone after browser closed
 Response.Cookies(cookieName).Expires = DateAdd("m", 1, Now)
 Response.Cookies(cookieName)=Request("selCategory")
 Response.Write "<script>alert('Inserted!');</script>"
 Response.End
Else
 sDefaultProdCatg = Replace(Request.Cookies(cookieName), "'", "\'")
End If
%>
<html><body>
<form method="POST" action="userPref.asp">
Client Name: <input type="text" name="txtClientName" ><br>
Product Category: 
 <select name="selCategory">
 <option value="Notebook">Notebook</option>
 <option value="PC">PC</option>
 <option value="LCD">LCD</option>
 </select><br>
<script type="text/javascript">
var oSel = document.getElementById("selCategory");
for (var i=0; i<oSel.options.length; i++)
 if (oSel.options[i].value=='<%=sDefaultProdCatg%>') {
 oSel.selectedIndex=i;
 break;
 }
</script>
<input type="submit" value="Add">
</form>
</body></html>
TIPS-Design A Mouseless Web UI

經歷過DOS->Windows轉換的老人家們應該不會忘記第一次踏著滑鼠在視窗上衝浪的莫名感動,一樣是打電腦,用滑滑點點取代一長串按鍵,感覺就是不一樣,一瞬間,人生彷彿由黑白變彩色~~~

隨著GUI視窗化介面的普及,大部分的開發者開始認定充分使用滑鼠才是最人性化的界面設計方式,但是世界上有一群人持著不一樣的看法。

滑鼠操作的動作範圍較小,2D空間定位的直覺性是鍵盤無從取代的,但這仍無法改變我們必須依賴鍵盤輸入文字的事實。當你被要求每天輸入幾百上千筆相似的資料,若輸入幾個字元就要滑一下滑鼠,意味著每幾秒鐘需轉動手肘將手掌從鍵盤移駕到滑鼠背上,接著得眼睛盯著螢幕做手眼協調練習,轉轉手腕,動動手指後再回到鍵盤上。一天下來,等同於手臂做了上萬次伸展運動,除了累積職業傷病給付的領取資格外,依據工業工程(很巧地,工工也叫IE,沒想到二技唸工管的我,在這個議題上,居然有在電腦科班大軍面前"裝懂"的一天,哈!)裡的動作經濟原則(Motion Economy Principle By Ralph M. Barnes)三級動作(指牽動手肘、手腕、手指的動作)絕對比一、二級動作不利於工作效率。

換句話說,對於重度的Key In使用者來說,Keyboard動作肯定無法避免(總不能用螢幕小鍵盤去點出所有的字母吧?),所以設法讓所有的操作以Keyboard完成,減少使用者切換滑鼠/鍵盤的次數,對於降低使用者的肉體疲勞與精神折磨(可以閉著眼睛敲Key解決,絕對比盯著螢幕滑滑鼠來得輕鬆)及提高使用者操作流暢度,都有神奇的改善效果。

除了全程使用Keyboard外,還有一個設計重點: 務必減少使用者必須按鍵的次數,可以敲兩個鍵做到的事,就不要逼使用按三下才完成。這些操作介面上的改善,其實都適用工作研究中的諸項原理,誰說拿著碼錶計時法只能用來提升工廠作業員的工作流暢度?

以下提供幾則設計Mouseless Web UI的小技巧:

  1. 善用tabindex屬性
    幾乎各Input都有這個屬性,使用者在輸入完一個欄位後,按下Tab鍵,就將焦點移到下一個要輸入的欄位上,讓使用者不要花時間自行切換焦點,可以減少可觀的按鍵次數。
  2. 焦點快速鍵
    除了tabindex,若使用者想隨機跳到某個欄位輸入資料,<input>還有個accesskey屬性,<input type="text" accesskey="f">代表使用者在IE按下Alt-F,在FireFox按下Alt-Shift-F,焦點就會立刻跳到該欄位上。
    Radio Button/Checkbox也支援accesskey,按下後可直接checked!
  3. Radio Button & Dropdown快速鍵
    當焦點移在Radio Button(<input type="radio">)或Dropdown(<select>)時,Browser都支援按上、下、左、右鍵切換選項。Dropdown時,按下字母鍵還可快速跳到該字母開頭的Option上。
  4. 按Enter鍵送出表單
    ASP.NET 2.0新增了DefaultButton,可以讓使用者按Enter時自動Submit表單,詳情請見先前的KB
  5. 以AutoComplete取代冗長的Dropdown List
    當選項很多時,使用者必須使用下捲在Dropdown的選項大海中查找,我個人的建議是利用AutoComplete的AJAX技巧取代Dropdown。

搜尋

Go

<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication