1. 世紀末軟體革命復刻版

    世紀末軟體革命復刻版

    http://www.books.com.tw/exep/prod/booksfile.php?item=0010334718

    沒想到電腦書也流行復古… 真是令人懷念的一本書啊,電腦書通常都是半年到一年就過時了,堆了兩年以上的書很少還會拿出來翻… 不過這本 (原版,不是復刻版) 例外,足足有好幾年的時間我還是會沒事就拿起來翻一翻。每想到十來年後還會有書局再版… 一定要推一下。

    這本書的觀念寫的實在不錯,我有很多OOP的觀念都是靠這本書建立起來的。常常有人問我學物件導向要看那本書,我都很想推這一本,不過早就絕版了那買的到… 前陣子才發現竟然有復刻版… :D

    再版的這本書是把原本上下兩冊併在一起,不知道有沒有附當年作者自己寫的 GUI 範例程式? 老實說那個範例程式在當年是蠻猛的,一張磁片就放的下的程式 (還包含 source code),一個像 windows 3.1 的視窗環境,該有的功能一點都不少,你還可以用它的 API 開發在那個 GUI 環境下的應用程式…,不過那個東西實際的用途不大,現在搞不好也沒有環境能跑了,不過看到有人可以用 C++ 建立起像 Windows 3.1 那樣的環境,而且真的可以跑,還真是打從心底佩服…。

    技術可能都退流行了,現在流行的東西書上都沒提到,不過觀念的部份還是很實用,想加強自己物件導向觀念的人不要錯過這本書… [Y]

    2008/08/23 技術隨筆 敗家 物件導向

  2. Thread Sync #2. 實作篇 - 互相等待的兩個執行緒

    上篇有人跟我講太深奧了… Orz, 其實不會,只是還沒看到 Code 而以…。就先來幫黑暗魔人賽說明一下程式碼…。首先來看的是黑暗大魔王: GameHost..

    GameHost 呼叫 Player 的片段

    public void Start(Player p)
    {
        // 略...
        int[] guess = p.StartGuess(_maxNum, _digits);
        // 略...
        Hint hint = compare(guess);
        // 略...
        while (hint.A != _digits)
        {
            // 略...
            guess = p.GuessNext(hint);
            // 略...
            hint = compare(guess);
        }
        p.Stop();
        // 略...
    }
    

    這段程式完全是老闆的角度在思考。抓到 PLAYER 後就叫它開始猜 StartGuess(),然後拼命的叫 PLAYER 再猜 GuessNext(), 直到猜中才可以休息 Stop()

    很典型的多型 ( Polymorphism ) 應用,實際上會 RUN 什麼 CODE,就看繼承 PLAYER 的人是怎麼寫的…。這次我們再從弱勢勞工的角度來看看 PLAYER 該怎麼實作 (以 darkthread 附的 DummyPlayer 為例):

    Player 實作的範例 ( DummyPlayer )

    public class DummyPlayer : Player
    {
        private int[] _currAnswer = null;
        private Random _rnd = new Random();
    
        private void randomGuess()
        {
            List<int> lst = new List<int>();
            for (int i = 0; i < _digits; i++)
            {
                int r = _rnd.Next(_maxNum);
                while (lst.Contains(r))
                    r = _rnd.Next(_maxNum);
                lst.Add(r);
                _currAnswer[i] = r;
            }
        }
    
        public override int[] StartGuess(int maxNum, int digits)
        {
            base.StartGuess(maxNum, digits);
            _currAnswer = new int[digits];
            randomGuess();
            return _currAnswer;
        }
        public override int[] GuessNext(Hint lastHint)
        {
            randomGuess();
            return _currAnswer;
        }
    }
    

    因為 CODE 不多,我就不刪了,全文照貼。另一個原因是我想讓各位看看拆成好幾段的 CODE 是不是還能夠一眼就還原成原來的邏輯? 如果只看這段 CODE 十秒鐘,沒有看註解或說明,誰能馬上回答這段 CODE 解題的邏輯是什麼?

    別誤會,不是指這 CODE 不易讀,而是因為呼叫的方式邏輯被迫配合 GameHost 而被切散了,你得再重新把它拼湊起來。它的邏輯很簡單,甚至簡單到連問題的答案都被忽略掉了,不過就每次都隨機丟個數字回去,在 StartGuess( ) 及 GuessNext( ) 都是。

    可憐的勞動階級要站起來啊~ 先幻想一下,如果勞工 (PLAYER) 才是老闆,那麼程式可以改成怎麼樣? 這也才是我們本篇的主角。先來看看成果再回頭來看怎麼實作。這次看的是修改後的版本: AsyncDummyPlayer.

    換 PLAYER 的角度思考的邏輯: AsyncDummyPlayer

    public class AsyncDummyPlayer : AsyncPlayer
    {
        private int[] _currAnswer = null;
        private Random _rnd = new Random();
        private void randomGuess()
        {
            List<int> lst = new List<int>();
            for (int i = 0; i < _digits; i++)
            {
                int r = _rnd.Next(_maxNum);
                while (lst.Contains(r))
                    r = _rnd.Next(_maxNum);
                lst.Add(r);
                _currAnswer[i] = r;
            }
        }
        protected override void Init(int maxNum, int digits)
        {
            _currAnswer = new int[digits];
        }
        protected override void Think()
        {
            while (true)
            {
                this.randomGuess();
                Hint h = this.GameHost_AskQuestion(this._currAnswer);
                if (h.A == this._digits) break;
            }
        }
    }
    

    程式碼也沒比較少,都差不多。不過是那堆 CODE 換個地方擺而以。但是仔細看看,這個版本的邏輯清楚多了,PLAYER 一開始就是執行 Init( ) 的部份,而 GameHost 叫 Player 開始解題時, Player 就開始思考 (Think),而這個無腦的 Player 也很直接,就一直執行 while (true) { …. } 這個無窮迴圈,直到亂猜猜中為止。

    如果 Player 在思考的時,不管在那裡它都可以適時的呼叫 GameHost_AskQuestion( …. ) 來跟 GameHost 問答案。什麼時後該猜數字? 該猜什麼數字? 這正是整個 Player 的核心,也就是 “怎麼猜” 這件事。以人的思考方式一定會分階段,比如一開始先把所有數字猜一輪,有個概念後再想想怎麼猜能更逼近答案,最後才是致命的一擊,找出正確答案送出去,贏得比賽。

    這樣的作法,如果套在 DummyPlayer (原版本),每個階段都要塞在一個大的 switch case, 放在 GuessNext( ) 裡。而現在是那個階段? 只能靠 instance variable 了,先存著等到下次又被呼叫時再拿出來回想一下,上回作到那…。

    而第二個版本,則完全沒這個問題,就把它當作一般程式思考就夠了,第一階段就是一個 LOOP,有它自己用的一些變數。第一階段處理完畢就離開 LOOP 繼續執行後面的 CODE… 直到最後離開 Think( ) 這個 method (認輸) 或是猜中答案光榮返鄉…。

    兩者的差別看出來了嗎? DummyPlayer 像是被動的勞工,老闆說一動他就作一動。第一動作完就拿個筆計記下來,等著下次老闆再叫他,他就翻翻筆記看看之前做到那,這次繼續…。

    而 AsyncDummyPlayer 這個主動的勞工呢? 老闆交待給他一件任務後,他就自己思考起該怎麼做了。中間都不需要老闆下令。反而是過程中勞工需要老闆的協助時,老闆再適時伸出援手就可以了,一切雜務都由這位主動優秀的勞工自己處理掉。

    有沒有差這麼多? 這麼神奇? 是怎麼辦到的? 先來看看類別關系圖:

    ThreadSync

    上圖中,AsyncPlayer 就是改變這種型態的關鍵類別。AsyncPlayer 會用我們在上一篇講到的關念,化被動為主動,轉換這兩種呼叫模式。先來看看這個類別的程式碼到底變了什麼把戲,可以讓弱勢的勞工也有自主的權力?

    AsyncPlayer 實作: 化被動為主動

    public abstract class AsyncPlayer : Player
    {
        public override int[] StartGuess(int maxNum, int digits)
        {
            base.StartGuess(maxNum, digits);
            Thread thinkThread = new Thread(this.ThinkCaller);
            thinkThread.Start();
            this._host_return.WaitOne();
            return this._temp_number;
        }
        public override int[] GuessNext(Hint lastHint)
        {
            this._temp_hint = lastHint;
            this._host_call.Set();
            this._host_return.WaitOne();
            return this._temp_number;
        }
        public override void Stop()
        {
            base.Stop();
            this._temp_hint = new Hint(this._digits, 0);
            this._host_call.Set();
            this._host_end.WaitOne();
            this._host_complete = true;
        }
        private void ThinkCaller()
        {
            try
            {
                this.Init(this._maxNum, this._digits);
                this.Think();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Player Exception: {0}", ex);
            }
            finally
            {
                this._host_end.Set();
            }
        }
        protected abstract void Init(int maxNum, int digits);
        protected abstract void Think();
        private AutoResetEvent _host_call = new AutoResetEvent(false);
        private AutoResetEvent _host_return = new AutoResetEvent(false);
        private AutoResetEvent _host_end = new AutoResetEvent(false);
        private bool _host_complete = false;
        private int[] _temp_number;
        private Hint _temp_hint;
        protected Hint GameHost_AskQuestion(int[] number)
        {
            if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");
            lock (this)
            {
                try
                {
                    this._temp_number = number;
                    this._host_return.Set();
                    this._host_call.WaitOne();
                    return this._temp_hint;
                }
                finally {
                    this._temp_number = null;
                    this._temp_hint = new Hint(-1, -1);
                }
            }
        }
    }
    

    這段程式碼長了一點,內容也都刪不得,各位請耐心點看。上一篇我畫了張概念性的時序圖,這次我們再拿同一張圖,不過這次會標上程式碼:

    ThreadSync2

    請注意一下各個箭頭的上下順序。由上往下代表時間的進行,如果應該在後面執行的 CODE 不巧先被呼叫了,則動作較快的那個 THREAD 會被迫暫停,等待另一邊的進度跟上。先來看看 StartGuess( ) 怎麼跟 Think( ) 互動:

    StartGuess(…)

    public override int[] StartGuess(int maxNum, int digits)
    {
        base.StartGuess(maxNum, digits);
        Thread thinkThread = new Thread(this.ThinkCaller);
        thinkThread.Start();
        this._host_return.WaitOne();
        return this._temp_number;
    }
    

    GameHost 呼叫 Player.StartGuess( ) 有兩個目的,一個是給 Player 題目範圍,讓 Player 做好準備動作。另一個則是準備好之後 GameHost 要取得 Player 傳回的第一個問題。

    程式碼很忠實的做了一樣的事,只不過 StartGuess( ) 建立了新的執行緒來負責。新的執行緒會執行 ThinkCaller( ),啟動之後 GameHost 這邊就什麼都不作,等待 _host_return 這個 WaitHandle 被叫醒,代表另一邊已經做好了,可以從共用變數 _temp_number 取得問題傳回去。

    既然 GameHost 在等待某人通知它,我們就來看看是誰會通知他題目已經準備好了:

    GameHost_AskQuestion(…)

    protected Hint GameHost_AskQuestion(int[] number)
    {
        if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");
        lock (this)
        {
            try
            {
                this._temp_number = number;
                this._host_return.Set();
                this._host_call.WaitOne();
                return this._temp_hint;
            }
            finally {
                this._temp_number = null;
                this._temp_hint = new Hint(-1, -1);
            }
        }
    }
    

    就在 GameHost 正在等題目的時後,另一個執行緒正在進行 “思考” 的動作,直到有結論後會呼叫 GameHost_AskQuestion( … ) 送出問題。這時這個問題會被放到 _temp_number, 而下一步就是 _host_return.Set( ), 通知另一個執行緒,正在等這個結果的人: “喂! 東西已經準備好了,可以來取貨了!!”

    整個機制就這樣串起來了。GameHost 那邊怎麼把答案傳回來? 同樣的作法,反過來而以。GameHost 會藉著呼叫 Player.GuessNext(…) 把答案傳回來,而這時就觸動一樣的機制,讓另一邊 Player Thread 呼叫的 GameHost_AskQuestion( … ) 醒過來,把答案拿走, RETURN 回去。

    這樣一直重複下去,剩下最後一個同步的點,就是結束遊戲的地方。說穿了也是一樣的把戲,只是這次是藉著 GameHost 呼叫 Player.Stop( ),而另一邊 Player Thread 執行完 Think( ) 後,兩邊就一起結束遊戲了。

    總算講完了。其實 thread 能解決的問題還真是五花八門。每次當我想出這些方法來簡化問題時,我就會覺的很有成就感。雖然寫出這個不會讓我贏得比賽,反而因為同步的關係,AsyncDummyPlayer 執行的速度還遠遠落後 DummyPlayer (我的機器跑起來,大概差了四~五倍 …) 。不過我知道我簡單的頭腦,不先把問題簡化的話,我大概解決不了太複雜的問題…。也許是缺了這種能力,才讓我更有動力去想簡化問題的方式吧?

    最後,為什麼每次講到 thread 的文章,都是 code 一點點,文章跟圖一大堆? 咳咳… 難道我也到了靠一張嘴混日子的地步了嘛? Orz… 本系列到此結束,以後還會有什麼主題? 想到再說啦~~ 今天各位記得去拜拜~~ 下回見!

    2008/08/15 系列文章: 多執行緒的處理技巧 .NET Tips 作業系統 多執行緒 技術隨筆 物件導向

  3. 話題人物?

    之前因為被盜文,除了被弄的不大爽之外,倒也沒什麼事,不過有人給我個 link,無意間發現對岸竟然有這麼篇文章…

    (最原始的網頁不知為何被下架了,底下提供的 LINK 是目前還連的到的…)

    URL: http://www.cnblogs.com/hullfqaz/archive/2008/07/28/1254315.html

    image

    看了又好氣又好笑… 沒想到對岸有網友在討論我碰到的事件啊,底下還真的有人回應批評百度…。前同事 在 MSN 跟我打屁,說這篇引用我的事例來討論的網站,也一樣沒有經過我的同意就轉貼,我的言論可能會引起兩岸對立,引發第三次世界大戰… Orz…

    這次好一點,雖然也是原文照貼我的文章,至少是全文照貼,頭尾都留著,LINK 也留著,沒有很 “自動” 的翻成簡體中文…後來好奇用相關關鑑字再去 GOOGLE 找看看,多找到一篇:

    URL: http://blog.const.net.cn/news/20080728/ec3b4ba924bde2b4.htm

    不過這次運氣差了點,我的 IP 被封鎖不能看… 我應該沒有偉大到讓對方直接封我的 IP 吧?

    image

    不過人氣好像不大夠,搜了半天才搜到這兩篇… 看來要上頭條新聞還久的很.. 哈哈..

    2008/08/14 有的沒的 火大

  4. Thread Sync #1. 概念篇 - 如何化被動為主動?

    別以為我轉行了… 這篇不是勵志文章,教你用主動積極的態度面對人生…. 而是討論執行緒同步機制及如何用來解決惱人的流程問題。會寫這篇的念頭來自黑暗程式魔人辦的猜數字程式設計大賽,在處理的過程中想到的解法…,不過這篇要講的不是猜數字,而是不相干的東西: Thread Sync (執行緒的同步機制)。

    一般程式寫久了,會很習慣一路到底的思考方式,程式也完全照這樣的思路被設計出來。不過寫 GAME 這類的程式就不是這麼一回事了。就先舉十五年前我用 C 寫的俄羅斯方塊的遊戲當例子 (大驚! 十… 十五年?),腦袋裡想的流程八九不離十,一定是像這樣:

    「隨機從上面掉一個方塊下來,時間到了就往下掉,USER有按方向鍵就左右移動或是旋轉,直到卡到底下或是其它方塊為止…」

    很正確的想法,很可惜你的程式完全不能這樣寫,為什麼? 當你沒有使用多執行緒或是其它的技巧時,你的主程式流程一定得被限定在固定時間 refresh 畫面的無窮迴圈… 上面的邏輯怎麼辦? 會被迫拆成好幾塊,然後被主程式定期呼叫… 程式寫起來大概會像這樣:

    TETRIS

    public void ProcessBrick()
    {
        switch (status)
        {
            case 1:
                //
                //  按右鍵, 往右移一格
                //
                break;
            case 2:
                //
                //  按左鍵, 往左移一格
                //
                break;
            case 3:
                //
                //  按上鍵, 順時針旋轉 90 度
                //
                break;
            case 4:
                //
                //  按下鍵, 往下移一格
                //
                break;
            case 5:
                //
                //  .......
                //
                break;
        }
    }
    

    原本好好的邏輯被切成好幾塊,然後再藉著狀態等資訊,每次的 LOOP 各挑這次要執行的那一小段,然後拼湊出原本的邏輯…。別哀怨,誰叫你寫的 CODE 不是老大? 老大是控制畫面的主程式,你既然是當小的就乖乖躲在旁邊被呼叫… 委屈一點是應該的…。

    真是黑心啊… 誰叫老闆永遠是對的。這次黑心… 不,黑暗魔人出的題目正好又讓我聯想到一樣的狀況。黑暗魔人出的題目,是先實作了 GameHost 類別 (就是老大啦),及 Player 抽象型別 (小角色就是他),再藉著多型 (Polymorphism) 的方式,由 GameHost 不斷的呼叫 Player 提供的 GuessNum( ),來讓 Player 問問題,同時把上一次問題的答案傳給 Player …

    程式也不難寫,大家都玩過 1A1B 的遊戲吧? 腦袋裡一定是這樣想的:

    「一開始先隨便猜幾個,把結果記下來….。」

    「再來刪去法,比較可能的幾個數字再猜一猜…。」

    「快猜到了,幾種組合列出來想一想怎麼辦好? 好! 就猜這個…。」

    「BINGO! 猜中了!!!」

    很高興的想好流程後,真的要開始寫就傻住了… 這堆流程跟邏輯,要我拆成一直會被重複呼叫的 “單一個” method, 每一回合會被呼叫一次…。意思是一連串複雜的處理過程,要依序切成完全一樣的片段 (就是指重複呼叫同一段程式碼) ? 更慘的是這次問的問題,下一次呼叫才拿的到答案!? MY GOD… 要想出怎麼猜到對方的數字已經夠頭痛了,還得來處理這些 “行政” 問題?

    My God, 頭越想越痛,這樣下去來我大概只能寫出比 DummyPlayer (註: 參賽程式附的 Player 範例,隨機產生問題,無腦的一直問,直到猜中為止…) 高明一點點的程式而已了。曾 MSN 跟黑暗魔人討論過,看看能不能讓 GameHost 化主動為被動,改由 GameHost 提供 callback 讓 Player 呼叫的可能性? 不過後來想想不對,那有主持人在旁邊看等著被來賓訪問的道理? 何況如果以後是兩個 Player 對戰怎麼辦? 誰要來當 “小的” ?? 問題還是一樣沒解決…

    過去大家對於 THREAD 的印象都是 “多工”,需要用 thread 解決的問題大多是在改善執行效能上面,因為同時用兩個 thread 可以做兩件事,效率會比較好。其實 thread 也很適合解決這類的問題,因為執行緒讓我們有機會,不需切斷 GameHost / Player 的 “思路”,讓兩邊都能用很直覺的思考方式寫程式。

    大概畫一下時序圖對照一下,先看看原本的作法:

    投影片2

    再看一下改用兩個執行緒的作法:

    投影片3

    不管之中的技術障礙怎麼克服,至少這樣改起來,兩邊都能各自用更合理簡單的方式思考自己的問題,也更接近實際的情況 (莊家跟玩家不會共用一個腦子吧…)。也唯有把問題簡化之後,我們才有辦法想出更複雜的方式來解決問題,科技不就是這樣進步的嘛?

    這次的例子裡,執行緒是用來簡化問題的,而不是拿來增進效率的。兩個人腦袋各自想著問題,總要溝通吧? thread 之間溝通的機制就很單純了,共用變數加上同步機制,來確定對方是否準備好我要的東西,或是對方是否已經準備好要接招了?

    這次搬出來的是過去說明 thread pool 提到的 AutoResetEvent,現在又重現江湖了。方法很簡單,要拿資料的那一方,就去 Wait( ) 等資料準備好,另一方把資料放在共用變數之後就呼叫 Set( ),叫醒另一個還在 Wait( ) 的執行緒,可以起床拿東西閃人了…。

    接下來當然就各忙各的,直到雙方又有交換資料的需求,同樣的方式就再來一次。只是隨著資料交換的方向不同 (比如問問題是把題目由 player –> host,而取得答案則是 host –> player),上述的動作雙方角色要互換才能順利進行。

    Orz,本來想一篇打完的,不過打到快睡著,加上內容還剩不少… 就分兩集吧! 程式碼及實作各位就耐心點,下一篇會端出實際操作執行緒的範例。

    2008/08/14 系列文章: 多執行緒的處理技巧 .NET 多執行緒 技術隨筆 物件導向

  5. 原來是 IPv6 搞的鬼...

    以前 (古早以前) 寫過一個簡單的 LIBRARY,就是去抓現在連上網頁的 CLIENT IP,然後簡單的套上 NET MASK,看看是不是在指定的網段內? 是的話就作些特別的處理 blah blah… 原本的 code 有點雜,我精簡之後變這樣,如果是 192.168.2.0 / 24 這範圍內的使用者連到這網頁,就會顯示 “Is Intranet? YES” … 夠簡單吧? (怎麼連幾篇都這種不入流的 sample code …)

    這段 code 一直都運作的很好,沒碰過什麼大問題,不過就是把 IP address 切成四個 bytes, 然後利用位元運算併成 unsing integer, 方便跟後面的 netmask 作 bits and …。不過某日興沖沖裝好 vista x64 + IIS7 之後發現,程式竟然不動了!?

    先來看一下原始碼:

    ASP.NET 程式範例

    <%@ Page Language="C#" Trace="true" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            this.Trace.Warn(System.Net.IPAddress.Parse(this.Request["REMOTE_HOST"]).AddressFamily.ToString());
            this.IPLabel.Text = this.IsInSubNetwork(
                "192.168.2.0",
                "255.255.255.0",
                this.Request.ServerVariables["REMOTE_HOST"]) ? ("YES") : ("NO");
        }
    
        private bool IsInSubNetwork(string network, string mask, string address)
        {
            uint netval = _IP2INT(network);
            uint maskval = _IP2INT(mask);
            uint addval = _IP2INT(address);
    
            return (netval & maskval) == (addval & maskval);
        }
        
        private uint _IP2INT(string address)
        {
            string[] segments = address.Split('.');
    
            uint ipval = 0;
            foreach (string segment in segments)
            {
                ipval = ipval * 256 + uint.Parse(segment);
            }
    
            return ipval;
        }
     
    </script>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
        Is Intranet? <asp:Label ID="IPLabel" runat="server" />
        </div>
        </form>
    </body>
    </html>
    

    後來追了半天才意外發現問題出在這… 打開 ASP.NET Trace, 看一下 REMOTE_ADDR 到底抓到啥子東西?

    image

    嘖嘖嘖,搞半天原來是 Vista 預設把 IPv6 給開了起來,IIS7 / DevWeb 都中獎,直接回報 IPv6 格式的 IP Address 回來… 怎麼解? 這種問題說穿了就不值錢,強迫用 IPv4 就好。我試過幾種可行的方式,有:

    1. 直接用 IPv4 的位址連線: 這簡單,以我來說,URL 從 http://localhost/default.aspx 改成 http://192.168.100.40/default.aspx 就好了。不過這樣對 DevWeb 就沒用了,DevWeb 只接受來自 localhost 的連線…

      image

    2. 改 IIS 設定,直接綁到 IPv4 的位址,不過這招試不出來,似呼沒啥用,localhost 不會連到 192.168.100.40,而我直接打這 IP 的話就會變成範例1…

      image

    3. 改 c:\windows\system32\drivers\etc\hosts

      無意間 PING 看看 localhost, 才發現連 localhost 都被對應到 IPv6 了…

      image

      打開 C:\windows\system32\drivers\etc\hosts 這檔案看一看,果然…

      image

      把 IPv6 那行拿掉後再試試 ping localhost …

      image

      耶! 這次 IP 就變成 IPv4 的了… 開 IE, 連 http://localhost/default.aspx 看看,it works!

      image

      因為這招是直接把 localhost 對應到 127.0.0.1,因此對於鎖 localhost 的 WEBDEV 也可以用。

    4. 大絕招: 直接關掉 IPv6 …

      真是個沒品的傢伙,打不過就來這套…

      image

      image

      這樣也可以…

    碰到這種怪問題,一時之間還熊熊不知道是那裡掛掉,還真是麻煩… 特地記一下這篇,讓一樣吃過 IPv6 苦頭的人參考一下。至於怎樣作才對? 當然是用 “正規” 的方式來處理 IP Address… System.Net.IPAddress 類別包含一個靜態方法: IPAddress Parse(string ipaddress), 用它可以把字串格式的 IP 換成這個類別的 instance, 用它內建的 property: AddressFamily,看看值是 enum 型態的 InterNetwork 還是 InterNetworkV6 就知道了,不要像我當年年少不更事一樣,自己硬去拆字串… Orz

    2008/08/13 .NET ASP.NET