文章出處

  “上一篇”介紹了我在c/s程序中用了那些技術,如今只談c/s不談b/s那未免out了,勢必要寫一寫b/s的程序與大家共勉。

  回憶做技術這些年,06年每天盯著“天轟穿”的視頻不亦樂乎,估計那是一代程序員的記憶,08年受益于Artech老師的WCF,為現在的 SOA開發打下了基礎,后來又涉及到MVC,EXTJS,Telerik,devexpress,工作流,報表,AGILE 等知識,都存儲在我的硬盤里,為了與大家一起分享盛宴,全都拿出來倒騰一番。

  閑話少說,切入正題。

  今天我要說的,對于多數人來說并不陌生。Instant Messenger,簡稱IM,中文名:即時通信,土鱉叫法:網頁聊天程序。不管有著什么樣的名字,就是指的我今天要說的東東,首先上個截圖讓大家欣賞一下,有圖有真相。

  最初打算把這個軟件包裝一下,做出一個商品來賣,就連商品的名稱都取好了“兩只狐貍”,由于自己的懶惰遲遲沒有動起來。這個聊天軟件很炫吧,支技圖片的傳輸,表情的發送,在線用戶的更新等。別看功能炫,核心代碼就幾十行javascript和一百多行C#代碼。大家看到這兒是不是蠢蠢欲動,準備開始模仿了,那我們就來一起行動吧!

熱點一:Comet,實現服務器推技術

  Comet從字面上理解有慧星之義,有些天文常識的人都知道,慧星有一個尾巴,拖著閃亮的尾巴劃空而過,非常的漂亮,聽說這個時候許愿很靈驗。今個兒說的Comet可不是一顆漂亮的慧星,而是一個Http請求。Comet因為一個尾巴出名,那么這里的Http就應該有一個長長的尾巴,那怎樣才能讓HTTP有一個長長的尾巴嗎?一個正常的HTTP,包括了客戶端的請求和服務器端的響應,一般請求時間和響應時間都能在比較短的時間完成,這個時候的時間狀態圖,就像是一個點。假設客戶端發出了一個請求,服務器端在等待別的事件,遲遲不給響應,導致了一個請求時間變長。這時候的時間狀態圖是不是像一顆慧星,拖的很長很長?所以就給這種特殊的HTTP請求取個別名叫Comet。

上邊的步驟是一個循環的過程,往往牽涉的循環的東西就很難理解,如果今天止步于紙上談兵,就有傷大家的興致,還得實踐一把才行。

.net里,有一個IHttpAsyncHandler的接口,就能實現HTTP的尾巴功能,拿編程的專業術語來說就是Async(異步),具體的異步實現請口味代碼。

public class TalkAsyncHandler : IHttpAsyncHandler
    {
        private TFTalkRepository talkRepository = default(TFTalkRepository);

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            talkRepository = new TFTalkRepository(context.Server.MapPath(ConfigurationManager.AppSettings["TwoFoxTalkDBPath"]));
            //throw new NotImplementedException();
            TalkAsynResult talkAsynResult = new TalkAsynResult(context, cb, extraData, talkRepository);
            String clientUid = context.Request.Params["uid"];
            String clientUname = context.Request.Params["uname"];
            String talkContent = context.Request.Params["content"];

            /*接收客戶端的請求,并作相應的處理(聊天)
             * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
            if (talkContent.Equals("-1", StringComparison.CurrentCultureIgnoreCase))
            {
                talkAsynResult.OnlineUID = clientUid;
                talkAsynResult.ONlineUName = clientUname;
                HttpConnection.TalkConns.Add(talkAsynResult);
            }
            else
            {
                talkAsynResult.Send();
                
                //否則將遍歷所有已緩存的client,并將當前內容輸出到客戶端
                List<TalkAsynResult> sendTalk = new List<TalkAsynResult>();
                sendTalk = HttpConnection.TalkConns.ToList();
                HttpConnection.TalkConns.Clear();
                foreach (TalkAsynResult itemTalk in sendTalk)
                {
                    //把聊天記錄插入到數據庫
                    TFTalkModel talkData = new TFTalkModel();
                    talkData.TalkID = Guid.NewGuid().ToString();
                    talkData.FromID = context.Request.Params["uid"];
                    talkData.ToID = itemTalk.OnlineUID;
                    talkData.TalkContent = talkContent;
                    talkData.FromDate = DateTime.Now;
                    talkData.IsAccept = false;
                    talkData.AcceptDate = DateTime.MinValue;
                    talkRepository.AddTalkRecord(talkData);
                    itemTalk.Send();
                }
            }
            return talkAsynResult;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            //throw new NotImplementedException();
        }

        public bool IsReusable
        {
            //get { throw new NotImplementedException(); }
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //throw new NotImplementedException();
        }
    }

    public class TalkAsynResult : IAsyncResult
    {
        bool _IsCompleted = false;
        HttpContext _context;
        AsyncCallback _cb;
        object _extraData;
        TFTalkRepository _talkRepository;

        public String OnlineUID { get; set; }

        public String ONlineUName { get; set; }

        public TalkAsynResult(HttpContext context, AsyncCallback cb, object extraData, TFTalkRepository talkRepository)
        {
            _context = context;
            _cb = cb;
            _extraData = extraData;
            _talkRepository = talkRepository;
        }

        //在Message類中的添加消息方法中,調用該方法,將消息輸入到客戶端,從而實現廣播的功能
        public void Send()
        {
            List<TFTalkModel> listTalkRecored = _talkRepository.GetTalkRecored(_context.Request.Params["uid"]);
            String responseContent = JsonConvert.SerializeObject(listTalkRecored, Formatting.Indented, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
            _context.Response.Write(responseContent);
            if (_cb != null)
            {
                _cb(this);
            }
            _IsCompleted = true;
        }


        public object AsyncState
        {
            //get { throw new NotImplementedException(); }
            get { return null; }
        }

        public WaitHandle AsyncWaitHandle
        {
            //get { throw new NotImplementedException(); }
            get { return null; }
        }

        public bool CompletedSynchronously
        {
            //get { throw new NotImplementedException(); }
            get { return false; }
        }

        public bool IsCompleted
        {
            //get { throw new NotImplementedException(); }
            get { return _IsCompleted; }
        }
    }

客戶端,就想起了大家都會的Jquery了,想想07年的時候非常傷心,當時用XmlhttpRequest來實現異步,記得創建一個XmlhttpRequest對象都得考慮瀏覽器的兼容。

$(document).ready(function () {
            $("#btnSend").click(function () { talksend(); });
            function talksend() {
                $.post("../talkasync.asyn", { uid: '<%=CurrnetUserInfo.UID %>', uname: '<%=CurrnetUserInfo.UName %>', content: $("#<%=txtNotice.ClientID %>").val() });
                editor.html("");
            }

            var ISoddOrEven = true;
            function talkwait() {
                $.post("../talkasync.asyn"
                    , { uid: '<%=CurrnetUserInfo.UID %>', uname: '<%=CurrnetUserInfo.UName %>', content: "-1" }
                    , function (data, status) {
                        var resultData = JSON.parse(data);
                        $(resultData).each(function (itemID, itemData) {
                            var itemResult = '<div class="itemTitle">' + itemData.FromName + ' ' + itemData.FromDate + '</div><div class="itemContent">' + itemData.TalkContent + '</div>';
                            if (ISoddOrEven) {
                                itemResult = '<div class="divItemResult-odd">' + itemResult + '</div>';
                                ISoddOrEven = false;
                            }
                            else {
                                itemResult = '<div class="divItemResult-even">' + itemResult + '</div>';
                                ISoddOrEven = true;
                            }
                            $("#divResult").append(itemResult);
                        });
                        document.getElementById("divResult").scrollTop = document.getElementById("divResult").scrollHeight;
                        //服務器返回消息,再次立連接
                        talkwait();
                    }
                    , "html");
            }

            //初始化聊天連接
            talkwait();
        });

道理很哆嗦,代碼很簡單,有時候用代碼來說話,是不是更具有說服力。

熱點二:Sqlite的數據存儲

  第一次接觸Sqlite在博客園,如今好久沒看到博客園發Sqlite的博客了,今天感覺有義務重提一下Sqlite。Sqlite一般是作為嵌入式開發的數據存儲的介質。經常用于手機開發,C++嵌入式開發,桌面應用程序等領域。由于一直沒有機會把Sqlite用于商業項目中,但是不能磨滅我對Sqlite的探討。

  Step1:我們先找到Sqlite的官方網站,下載在Windows的.net平臺下開發的類庫    http://www.sqlite.org/download.html

  Step2:要找一個好用的Sqlite可視化工具確是難事,我在網上隨便搜索一把, Sqlite3Explorer,SQLite Database Browser,SQLiteStudio ,SQLiteAdmin,Sqlite.Developer,SQLite Manager,Navicat for SQLite,SQLiteSpy就有這些工具,經過我的實踐,我感覺SQLiteStudio比較適合我。首先簡單易用,其次是免費的,再次是有中文版,最后官方還保持了更新,你說我們有什么理由不去使用呢?

  可以在 http://sqlitestudio.pl  下載SQLiteStudio的最新版,同樣無圖無真相。

   這個界面是不是似成相識呢,好像操作習慣都與微軟的Sql Server的客戶端好相似。不知道是誰模仿誰,在這里不作深究。

熱點三:JSON的轉換(時間格式)

  JSON是一個簡單的數據規范描述,就像XML一樣,就是用來定義描述數據的格式。在實際項目中,最常用的有以一兩種場景:

  1:把前端的JSON格式的字符串,提交到服務端。相當于一次可以提交多條記錄到服務器端,服務器端得到這個JSON格式的字符串,轉換為List<T>。

  2:服務器端的List<T>以JOSN字符串的格式一次性把多條數據輸出到客戶端。

  其實這兩個過程我們手寫代碼來實,完全是沒問題的,我早期也是這么做的,不過也有現呈的工具,我們就沒必要閉門造車了。

  去 http://www.json.org/js.html 這個網站把json2.js這個文件給下載過來。json2.js這個功能就非常強大了,比如我們前端對josn字符串進行添加一個 json對象,刪除一個JOSN對象都比較麻煩,但是在JS中,操作Array是件非常容易的事,我們可以用json2.js把josn格式的字符串與Array數據互相轉換后操作Array。

  在.net的服務端有我們的強大的 Newtonsoft.Json.dll,但是偏偏又遇上了一個時間格式化JSON字符串的問題,這個問題是一個老問題,在Google上搜一大把相關的資料。因為 Newtonsoft.Json把時間轉換成了類似于 Date/232378978  這種的時間格式。解決這個問題有著千奇百怪的方法。網上主流是以js來轉換時間格式。

function ChangeDateFormat(cellval) {
    var date = new Date(parseInt(cellval.replace("/Date(", "").replace(")/", ""), 10));
    var month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
    var currentDate = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
    return date.getFullYear() + "-" + month + "-" + currentDate;
}

有些人感覺這種JS時間格式法太麻煩,直接把時間格式 DateTime 定義為  String。用String來保存時間格式。

不過我感覺這種兩種方法,都不是最佳解決方案。何不嘗試用:

JsonConvert.SerializeObject(listTalkRecored, Formatting.Indented, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });

這就把把時間格式化為  “yyyy-MM-dd HH:mm:ss”的JSON字符串了。有細心的朋友估計已經在Comet那段代碼里看到了這行代碼。我感覺不單獨提出來說,很多人都發現不了。 

好文要頂,下篇準備介紹數據庫開發,“怎樣提升千萬級別數據庫表的性能,以及消除數據庫主從表的設計”

本文源碼下載:源碼下載


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()