“上一篇”介绍了我在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(异步),具体的异步实现请口味代码。

  1. public class TalkAsyncHandler : IHttpAsyncHandler
  2. {
  3. private TFTalkRepository talkRepository = default(TFTalkRepository);
  4.  
  5. public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
  6. {
  7. talkRepository = new TFTalkRepository(context.Server.MapPath(ConfigurationManager.AppSettings["TwoFoxTalkDBPath"]));
  8. //throw new NotImplementedException();
  9. TalkAsynResult talkAsynResult = new TalkAsynResult(context, cb, extraData, talkRepository);
  10. String clientUid = context.Request.Params["uid"];
  11. String clientUname = context.Request.Params["uname"];
  12. String talkContent = context.Request.Params["content"];
  13.  
  14. /*接收客户端的请求,并作相应的处理(聊天)
  15. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  16. if (talkContent.Equals("-1", StringComparison.CurrentCultureIgnoreCase))
  17. {
  18. talkAsynResult.OnlineUID = clientUid;
  19. talkAsynResult.ONlineUName = clientUname;
  20. HttpConnection.TalkConns.Add(talkAsynResult);
  21. }
  22. else
  23. {
  24. talkAsynResult.Send();
  25.  
  26. //否则将遍历所有已缓存的client,并将当前内容输出到客户端
  27. List<TalkAsynResult> sendTalk = new List<TalkAsynResult>();
  28. sendTalk = HttpConnection.TalkConns.ToList();
  29. HttpConnection.TalkConns.Clear();
  30. foreach (TalkAsynResult itemTalk in sendTalk)
  31. {
  32. //把聊天记录插入到数据库
  33. TFTalkModel talkData = new TFTalkModel();
  34. talkData.TalkID = Guid.NewGuid().ToString();
  35. talkData.FromID = context.Request.Params["uid"];
  36. talkData.ToID = itemTalk.OnlineUID;
  37. talkData.TalkContent = talkContent;
  38. talkData.FromDate = DateTime.Now;
  39. talkData.IsAccept = false;
  40. talkData.AcceptDate = DateTime.MinValue;
  41. talkRepository.AddTalkRecord(talkData);
  42. itemTalk.Send();
  43. }
  44. }
  45. return talkAsynResult;
  46. }
  47.  
  48. public void EndProcessRequest(IAsyncResult result)
  49. {
  50. //throw new NotImplementedException();
  51. }
  52.  
  53. public bool IsReusable
  54. {
  55. //get { throw new NotImplementedException(); }
  56. get { return true; }
  57. }
  58.  
  59. public void ProcessRequest(HttpContext context)
  60. {
  61. //throw new NotImplementedException();
  62. }
  63. }
  64.  
  65. public class TalkAsynResult : IAsyncResult
  66. {
  67. bool _IsCompleted = false;
  68. HttpContext _context;
  69. AsyncCallback _cb;
  70. object _extraData;
  71. TFTalkRepository _talkRepository;
  72.  
  73. public String OnlineUID { get; set; }
  74.  
  75. public String ONlineUName { get; set; }
  76.  
  77. public TalkAsynResult(HttpContext context, AsyncCallback cb, object extraData, TFTalkRepository talkRepository)
  78. {
  79. _context = context;
  80. _cb = cb;
  81. _extraData = extraData;
  82. _talkRepository = talkRepository;
  83. }
  84.  
  85. //在Message类中的添加消息方法中,调用该方法,将消息输入到客户端,从而实现广播的功能
  86. public void Send()
  87. {
  88. List<TFTalkModel> listTalkRecored = _talkRepository.GetTalkRecored(_context.Request.Params["uid"]);
  89. String responseContent = JsonConvert.SerializeObject(listTalkRecored, Formatting.Indented, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
  90. _context.Response.Write(responseContent);
  91. if (_cb != null)
  92. {
  93. _cb(this);
  94. }
  95. _IsCompleted = true;
  96. }
  97.  
  98. public object AsyncState
  99. {
  100. //get { throw new NotImplementedException(); }
  101. get { return null; }
  102. }
  103.  
  104. public WaitHandle AsyncWaitHandle
  105. {
  106. //get { throw new NotImplementedException(); }
  107. get { return null; }
  108. }
  109.  
  110. public bool CompletedSynchronously
  111. {
  112. //get { throw new NotImplementedException(); }
  113. get { return false; }
  114. }
  115.  
  116. public bool IsCompleted
  117. {
  118. //get { throw new NotImplementedException(); }
  119. get { return _IsCompleted; }
  120. }
  121. }

客户端,就想起了大家都会的Jquery了,想想07年的时候非常伤心,当时用XmlhttpRequest来实现异步,记得创建一个XmlhttpRequest对象都得考虑浏览器的兼容。

  1. $(document).ready(function () {
  2. $("#btnSend").click(function () { talksend(); });
  3. function talksend() {
  4. $.post("../talkasync.asyn", { uid: '<%=CurrnetUserInfo.UID %>', uname: '<%=CurrnetUserInfo.UName %>', content: $("#<%=txtNotice.ClientID %>").val() });
  5. editor.html("");
  6. }
  7.  
  8. var ISoddOrEven = true;
  9. function talkwait() {
  10. $.post("../talkasync.asyn"
  11. , { uid: '<%=CurrnetUserInfo.UID %>', uname: '<%=CurrnetUserInfo.UName %>', content: "-1" }
  12. , function (data, status) {
  13. var resultData = JSON.parse(data);
  14. $(resultData).each(function (itemID, itemData) {
  15. var itemResult = '<div class="itemTitle">' + itemData.FromName + ' ' + itemData.FromDate + '</div><div class="itemContent">' + itemData.TalkContent + '</div>';
  16. if (ISoddOrEven) {
  17. itemResult = '<div class="divItemResult-odd">' + itemResult + '</div>';
  18. ISoddOrEven = false;
  19. }
  20. else {
  21. itemResult = '<div class="divItemResult-even">' + itemResult + '</div>';
  22. ISoddOrEven = true;
  23. }
  24. $("#divResult").append(itemResult);
  25. });
  26. document.getElementById("divResult").scrollTop = document.getElementById("divResult").scrollHeight;
  27. //服务器返回消息,再次立连接
  28. talkwait();
  29. }
  30. , "html");
  31. }
  32.  
  33. //初始化聊天连接
  34. talkwait();
  35. });

道理很哆嗦,代码很简单,有时候用代码来说话,是不是更具有说服力。

热点二: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来转换时间格式。

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

有些人感觉这种JS时间格式法太麻烦,直接把时间格式 DateTime 定义为  String。用String来保存时间格式。

不过我感觉这种两种方法,都不是最佳解决方案。何不尝试用:

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

这就把把时间格式化为  “yyyy-MM-dd HH:mm:ss”的JSON字符串了。有细心的朋友估计已经在Comet那段代码里看到了这行代码。我感觉不单独提出来说,很多人都发现不了。

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

本文源码下载:源码下载

Comet实现的网页聊天程序的更多相关文章

  1. Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  2. Spring之WebSocket网页聊天以及服务器推送

    Spring之WebSocket网页聊天以及服务器推送 转自:http://www.xdemo.org/spring-websocket-comet/ /Springframework /Spring ...

  3. SpringBoot基于websocket的网页聊天

    一.入门简介正常聊天程序需要使用消息组件ActiveMQ或者Kafka等,这里是一个Websocket入门程序. 有人有疑问这个技术有什么作用,为什么要有它?其实我们虽然有http协议,但是它有一个缺 ...

  4. WebSocket 网页聊天室的实现(服务器端:.net + windows服务,前端:Html5)

    websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率.废话不多说,直接进入题. 网页聊天室包括2个部分,后端服务器 ...

  5. 你也可以写聊天程序 - C# Socket学习1

    简述 我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解.我今天就来和大家一起学习下Socket,并写一个简单的聊天程序. 一些基础类 首先我们每天打开浏览器访问网页信 ...

  6. 你也可以写个聊天程序 - C# Socket学习1

    原文:你也可以写个聊天程序 - C# Socket学习1 简述 我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解.我今天就来和大家一起学习下Socket,并写一个简单 ...

  7. WebSocket 网页聊天室

    先给大家开一个原始的websocket的连接使用范例 <?php /* * recv是从套接口接收数据,也就是拿过来,但是不知道是什么 * read是读取拿过来的数据,就是要知道recv过来的是 ...

  8. Socket聊天程序——Common

    写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...

  9. Socket聊天程序——客户端

    写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...

随机推荐

  1. [转载]centos7 快速安装 mariadb(mysql)

    http://blog.csdn.net/default7/article/details/41973887 从最新版本的linux系统开始,默认的是 Mariadb而不是mysql! yum ins ...

  2. make: Nothing to be done for `first'

    在qt目录下make后出现以下错误: make: Nothing to be done for `first' 解决:将你当前目录下的,删除你程序主要的 *.cpp 和 *.h文件以外的所有文件. 接 ...

  3. mha报错

    用命令检查集群复制状态:masterha_check_repl --conf=/etc/masterha/app1.cnf 报错如下: Tue Jan 12 09:25:51 2016 - [info ...

  4. Oracle 服务手动启动关闭

    在windows7中安装完Oracle11g之后会出现一下七种服务:可通过运行->services.msc查看. 其中各个服务名称中的ORCL或orcl为SID即System IDentifie ...

  5. AppFuse3.5对接oracle数据库

    AppFuse是一个使用Java语言开发web应用系统的集成框架.java开发人员最头痛的事情就是面对大量的框架不知该如何选择.这些框架性能如何,兼容性如何等等都需要筛选比较.Appfuse作者Mat ...

  6. Sublime+Golang Plugin

    很喜欢在Sublime开发Golang程序,主要是要一个Sublime Golang Plugin, 可以给代码autocomplement.相当的棒! 1.安装Sublime https://www ...

  7. iOS-详细解读Const

    在过去开发中,几乎每一个人都会定义宏,因为这东西实在是好用,省去了代码量而且还不容易错,而我这篇文中所介绍的const可以完美替带宏定义. 并且苹果也建议大家抛弃宏定义而转投const ,并且swif ...

  8. iOS进阶_三方使用步骤

    一.配置环境(:后为在终端输入的命令) 打开终端 查看自己电脑的Ruby环境:gem sources -l 如果环境已经是淘宝镜像了,此时不需要再进行环境的修改. 如果不是,发送gem sources ...

  9. oracle入门必备

    //................创建表空间 \  赋予角色  \ 创建数据表  \  插入数据  \  创建序列  \ 添加注释   ........................... --创 ...

  10. JMeter HTTP Cookie管理器的跨域使用

    Jmeter的一个测试计划只能有一个cookie管理器,当多个manager同时存在时,无法指定是用的哪一个manager.如果想让cookie manager跨域使用,修改JMeter.proper ...