zoukankan      html  css  js  c++  java
  • Comet实现的网页聊天程序

      “上一篇”介绍了我在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那段代码里看到了这行代码。我感觉不单独提出来说,很多人都发现不了。 

    好文要顶,下篇准备介绍数据库开发,“怎样提升千万级别数据库表的性能,以及消除数据库主从表的设计”

    本文源码下载:源码下载

  • 相关阅读:
    CodeForces 7B
    CodeForces 4D
    离散化
    线段树入门
    洛谷 P3951 小凯的疑惑(赛瓦维斯特定理)
    Codeforces 1295D Same GCDs (欧拉函数)
    Codeforces 1295C Obtain The String (二分)
    Codeforces 1295B Infinite Prefixes
    Codeforces 1295A Display The Number(思维)
    Codeforces 1294F Three Paths on a Tree(树的直径,思维)
  • 原文地址:https://www.cnblogs.com/xcj26/p/3220943.html
Copyright © 2011-2022 走看看